From 52d5f209f852ba180e2820ceefc8ec4cac62f22c Mon Sep 17 00:00:00 2001 From: hhaensel Date: Wed, 10 Jun 2026 17:10:38 +0200 Subject: [PATCH 1/4] migrate HTTP to v2 --- Project.toml | 8 +++++--- src/OpenIDConnect.jl | 39 ++++++++++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/Project.toml b/Project.toml index e5ddc09..ce823a7 100644 --- a/Project.toml +++ b/Project.toml @@ -1,10 +1,10 @@ name = "OpenIDConnect" uuid = "195a77af-17c0-520f-ac58-3336c7ea8576" keywords = ["openidconnect", "openid-connect", "openid", "openidc", "julia", "identity"] -authors = ["Tanmay Mohapatra "] license = "MIT" desc = "OpenID Connect for Julia" version = "0.1.8" +authors = ["Tanmay Mohapatra "] [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" @@ -13,15 +13,17 @@ JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JWTs = "d850fbd6-035d-5a70-a269-1ca2e636ac6c" MbedTLS = "739be429-bea8-5141-9913-cc70e7f3736d" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +Reseau = "802f3686-a58f-41ce-bb0c-3c43c75bba36" SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce" [compat] -julia = "1" -HTTP = "0.8, 0.9, 1" +HTTP = "2.1.1" JSON = "0.21, 1" JWTs = "0.1, 0.2, 0.3" MbedTLS = "0.6.8, 0.7, 1" +Reseau = "1.2.1" SHA = "<0.0.1, 0.7, 1" +julia = "1" [extras] Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" diff --git a/src/OpenIDConnect.jl b/src/OpenIDConnect.jl index b3a6390..b61fec8 100644 --- a/src/OpenIDConnect.jl +++ b/src/OpenIDConnect.jl @@ -5,9 +5,13 @@ using JSON using MbedTLS using Base64 using Random -using SHA +using SHA using JWTs +# Import Reseau.TLS for HTTP v2 custom certificate support +import Reseau +const TLS = Reseau.TLS + const DEFAULT_SCOPES = ["openid", "profile", "email"] const DEFAULT_STATE_TIMEOUT_SECS = 60 const DEFAULT_SKEW_SECS = 2*60 @@ -43,19 +47,40 @@ struct OIDCCtx endswith(issuer, "/") || (issuer = issuer * "/") openid_config_url = issuer * ".well-known/openid-configuration" http_tls_opts = Dict{Symbol,Any}() - http_tls_opts[:socket_type_tls] = MbedTLS.SSLContext if verify !== nothing http_tls_opts[:require_ssl_verification] = verify end if cacrt !== nothing - if isa(cacrt, String) - cacrt = isfile(cacrt) ? MbedTLS.crt_parse_file(cacrt) : MbedTLS.crt_parse(cacrt) + if isa(cacrt, MbedTLS.CRT) + error("cacrt must be a file path (String) in HTTP v2; MbedTLS.CRT objects are no longer supported") + end + if !isfile(cacrt) + error("cacrt must be a path to an existing certificate file; got: $cacrt") end - conf = MbedTLS.SSLConfig(verify === nothing || verify) - MbedTLS.ca_chain!(conf, cacrt) - http_tls_opts[:sslconfig] = conf + # Create custom TLS.Config with CA file + tls_config = TLS.Config( + nothing, # server_name + verify === nothing || verify, # verify_peer + verify === nothing || verify, # verify_hostname + TLS.ClientAuthMode.NoClientCert, # client_auth + nothing, # cert_file + nothing, # key_file + cacrt, # ca_file + nothing, # client_ca_file + String[], # alpn_protocols + UInt16[], # curve_preferences + Int64(30_000_000_000), # handshake_timeout_ns (30 seconds) + TLS.TLS1_2_VERSION, # min_version + nothing, # max_version + false, # session_tickets_disabled + 64, # session_cache_capacity + ) + # Create custom transport with TLS config + transport = HTTP.Transport(; tls_config) + # Create custom client + http_tls_opts[:client] = HTTP.Client(; transport) end # fetch and store the openid config, along with the additional args for SSL From 32445fdfd3ebcab835bd22d4e82eadfaf2b16195 Mon Sep 17 00:00:00 2001 From: hhaensel Date: Wed, 10 Jun 2026 17:12:45 +0200 Subject: [PATCH 2/4] remove MbedTLS from direct deps --- Project.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Project.toml b/Project.toml index ce823a7..0515489 100644 --- a/Project.toml +++ b/Project.toml @@ -11,7 +11,6 @@ Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JWTs = "d850fbd6-035d-5a70-a269-1ca2e636ac6c" -MbedTLS = "739be429-bea8-5141-9913-cc70e7f3736d" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Reseau = "802f3686-a58f-41ce-bb0c-3c43c75bba36" SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce" @@ -20,7 +19,6 @@ SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce" HTTP = "2.1.1" JSON = "0.21, 1" JWTs = "0.1, 0.2, 0.3" -MbedTLS = "0.6.8, 0.7, 1" Reseau = "1.2.1" SHA = "<0.0.1, 0.7, 1" julia = "1" From 2996403bff844dc3bd94c99bfd9480eda625fa62 Mon Sep 17 00:00:00 2001 From: hhaensel Date: Wed, 10 Jun 2026 18:48:56 +0200 Subject: [PATCH 3/4] fix MbedTLS dependency error --- src/OpenIDConnect.jl | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/OpenIDConnect.jl b/src/OpenIDConnect.jl index b61fec8..d97396f 100644 --- a/src/OpenIDConnect.jl +++ b/src/OpenIDConnect.jl @@ -2,7 +2,6 @@ module OpenIDConnect using HTTP using JSON -using MbedTLS using Base64 using Random using SHA @@ -41,7 +40,7 @@ struct OIDCCtx random_device::RandomDevice function OIDCCtx(issuer::String, redirect_uri::String, client_id::String, client_secret::String, scopes::Vector{String}=DEFAULT_SCOPES; - verify::Union{Nothing,Bool}=nothing, cacrt::Union{Nothing,String,MbedTLS.CRT}=nothing, + verify::Union{Nothing,Bool}=nothing, cacrt::Union{Nothing,String}=nothing, state_timeout_secs::Int=DEFAULT_STATE_TIMEOUT_SECS, allowed_skew_secs::Int=DEFAULT_SKEW_SECS, key_refresh_secs::Int=DEFAULT_KEY_REFRESH_SECS, random_device::RandomDevice=RandomDevice()) endswith(issuer, "/") || (issuer = issuer * "/") @@ -53,9 +52,6 @@ struct OIDCCtx end if cacrt !== nothing - if isa(cacrt, MbedTLS.CRT) - error("cacrt must be a file path (String) in HTTP v2; MbedTLS.CRT objects are no longer supported") - end if !isfile(cacrt) error("cacrt must be a path to an existing certificate file; got: $cacrt") end From 01967473e74b6d3a54fd890f40d2b9afb94ab834 Mon Sep 17 00:00:00 2001 From: tan Date: Thu, 11 Jun 2026 23:07:17 +0530 Subject: [PATCH 4/4] Fix HTTP v2 migration: CI matrix, julia compat, and verify+cacrt bug - Raise julia compat to 1.10 (required by HTTP 2.1.1 / Reseau 1.2.1) and drop Julia 1.0 plus all x86 CI jobs (Reseau has no 32-bit support). - Fix OIDCCtx construction throwing when verify=false is combined with a custom cacrt: HTTP v2 rejects require_ssl_verification overrides on an explicit Client, so carry the verify intent via the TLS.Config instead. - Document the narrowed cacrt contract (file path only) and bump version to 0.2.0 for the breaking API change. - Add a Custom CA certificate testset covering the cacrt path, the verify=false regression, and the bad-path error. --- .github/workflows/ci.yml | 7 ++----- Project.toml | 7 ++++--- README.md | 4 ++-- src/OpenIDConnect.jl | 17 ++++++++++------- test/runtests.jl | 22 ++++++++++++++++++++++ 5 files changed, 40 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc475e9..e29da84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,14 +12,14 @@ jobs: fail-fast: false matrix: version: - - '1.0' + - '1.10' # minimum supported Julia (required by HTTP v2 / Reseau) - '1' # automatically expands to the latest stable 1.x release of Julia - nightly os: - ubuntu-latest arch: + # Reseau (HTTP v2 TLS backend) does not support 32-bit; x64 only. - x64 - - x86 include: # test macOS and Windows with latest Julia only - os: macOS-latest @@ -28,9 +28,6 @@ jobs: - os: windows-latest arch: x64 version: 1 - - os: windows-latest - arch: x86 - version: 1 steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v2 diff --git a/Project.toml b/Project.toml index 0515489..ac896da 100644 --- a/Project.toml +++ b/Project.toml @@ -3,7 +3,7 @@ uuid = "195a77af-17c0-520f-ac58-3336c7ea8576" keywords = ["openidconnect", "openid-connect", "openid", "openidc", "julia", "identity"] license = "MIT" desc = "OpenID Connect for Julia" -version = "0.1.8" +version = "0.2.0" authors = ["Tanmay Mohapatra "] [deps] @@ -21,11 +21,12 @@ JSON = "0.21, 1" JWTs = "0.1, 0.2, 0.3" Reseau = "1.2.1" SHA = "<0.0.1, 0.7, 1" -julia = "1" +julia = "1.10" [extras] +NetworkOptions = "ca575930-c2e3-43a9-ace4-1e988b2c1908" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test", "Random"] +test = ["Test", "Random", "NetworkOptions"] diff --git a/README.md b/README.md index 7b8dbcb..117b124 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ function OIDCCtx( client_secret::String, scopes::Vector{String}=DEFAULT_SCOPES; verify::Union{Nothing,Bool}=nothing, - cacrt::Union{Nothing,String,MbedTLS.CRT}=nothing, + cacrt::Union{Nothing,String}=nothing, state_timeout_secs::Int=DEFAULT_STATE_TIMEOUT_SECS, allowed_skew_secs::Int=DEFAULT_SKEW_SECS, key_refresh_secs::Int=DEFAULT_KEY_REFRESH_SECS), @@ -34,7 +34,7 @@ Parameters: Keyword Parameters: - `verify`: whether to validate the server certificate -- `cacrt`: the CA certificate to use to check the server certificate +- `cacrt`: path to a CA certificate file (PEM) to use to check the server certificate. Must be a path to an existing file (since the HTTP v2 / Reseau migration, passing certificate contents or an `MbedTLS.CRT` is no longer supported) - `state_timeout_secs`: seconds for which to keep the state associated with an authorization request (default: 60 seconds), server responses beyond this are rejected as stale - `allowed_skew_secs`: while validating tokens, seconds to allow to account for time skew between machines (default: 120 seconds) - `key_refresh_secs`: time interval in which to refresh the JWT signing keys (default: 1hr) diff --git a/src/OpenIDConnect.jl b/src/OpenIDConnect.jl index d97396f..7063727 100644 --- a/src/OpenIDConnect.jl +++ b/src/OpenIDConnect.jl @@ -47,19 +47,19 @@ struct OIDCCtx openid_config_url = issuer * ".well-known/openid-configuration" http_tls_opts = Dict{Symbol,Any}() - if verify !== nothing - http_tls_opts[:require_ssl_verification] = verify - end - if cacrt !== nothing if !isfile(cacrt) error("cacrt must be a path to an existing certificate file; got: $cacrt") end - # Create custom TLS.Config with CA file + # A custom CA requires a custom HTTP.Client built around a Reseau TLS.Config. + # The verify intent is baked into the TLS.Config below (verify_peer/verify_hostname); + # we must NOT also pass require_ssl_verification, since HTTP v2 throws when that + # keyword is combined with an explicit client. + verify_peer = verify === nothing || verify tls_config = TLS.Config( nothing, # server_name - verify === nothing || verify, # verify_peer - verify === nothing || verify, # verify_hostname + verify_peer, # verify_peer + verify_peer, # verify_hostname TLS.ClientAuthMode.NoClientCert, # client_auth nothing, # cert_file nothing, # key_file @@ -77,6 +77,9 @@ struct OIDCCtx transport = HTTP.Transport(; tls_config) # Create custom client http_tls_opts[:client] = HTTP.Client(; transport) + elseif verify !== nothing + # No custom CA: control verification through the per-request keyword on the default client. + http_tls_opts[:require_ssl_verification] = verify end # fetch and store the openid config, along with the additional args for SSL diff --git a/test/runtests.jl b/test/runtests.jl index fab8aaf..bf14145 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,6 +2,7 @@ using OpenIDConnect using Test using Random using HTTP +using NetworkOptions function test_state_store() @testset "State store" begin @@ -68,7 +69,28 @@ function test_oidc_flow() end end +function test_custom_cacrt() + @testset "Custom CA certificate" begin + cafile = NetworkOptions.ca_roots_path() + + # A custom CA file builds a custom HTTP.Client; the context must construct + # and resolve the openid configuration successfully. + ctx = OIDCCtx("https://accounts.google.com", "http://127.0.0.1:8888/auth/login", "test_client_id", "test_client_secret"; cacrt=cafile) + @test OpenIDConnect.token_endpoint(ctx) == "https://oauth2.googleapis.com/token" + + # Regression: verify=false together with cacrt must not throw. HTTP v2 rejects + # require_ssl_verification overrides when an explicit client is passed, so the + # verify intent has to be carried by the TLS config instead. + ctx = OIDCCtx("https://accounts.google.com", "http://127.0.0.1:8888/auth/login", "test_client_id", "test_client_secret"; verify=false, cacrt=cafile) + @test OpenIDConnect.token_endpoint(ctx) == "https://oauth2.googleapis.com/token" + + # A cacrt that is not an existing file is rejected with a clear error. + @test_throws ErrorException OIDCCtx("https://accounts.google.com", "http://127.0.0.1:8888/auth/login", "test_client_id", "test_client_secret"; cacrt="/no/such/ca-file.pem") + end +end + @testset "OpenIDConnect" begin test_state_store() test_oidc_flow() + test_custom_cacrt() end