From a23417def960581d535e7438f4db10616249f567 Mon Sep 17 00:00:00 2001 From: Paul Swartz Date: Sun, 7 Jan 2024 22:32:55 -0500 Subject: [PATCH] feat: tell `httpc` to close the connection if we're using client certs In addition to the separate profile, this header serves as a note to both the remote server and `httpc` that we're not keeping the connection open after the request.` --- src/oidcc_http_util.erl | 41 ++++++++++++++++++++++++++++++---- test/oidcc_http_util_SUITE.erl | 13 +++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/oidcc_http_util.erl b/src/oidcc_http_util.erl index adb5072..2b2d6a0 100644 --- a/src/oidcc_http_util.erl +++ b/src/oidcc_http_util.erl @@ -85,17 +85,32 @@ when Accumulator :: term()}, TelemetryOpts :: telemetry_opts(), RequestOpts :: request_opts(). -request(Method, Request, TelemetryOpts, RequestOpts) -> +request(Method, Request0, TelemetryOpts, RequestOpts) -> TelemetryTopic = maps:get(topic, TelemetryOpts), TelemetryExtraMeta = maps:get(extra_meta, TelemetryOpts, #{}), Timeout = maps:get(timeout, RequestOpts, timer:minutes(1)), SslOpts = maps:get(ssl, RequestOpts, undefined), HttpOpts0 = [{timeout, Timeout}], - {HttpOpts, HttpProfile} = + HttpOpts = case SslOpts of - undefined -> {HttpOpts0, default}; - _Opts -> {[{ssl, SslOpts} | HttpOpts0], oidcc_app:httpc_profile()} + undefined -> HttpOpts0; + _Opts -> [{ssl, SslOpts} | HttpOpts0] + end, + + %% if we're using special SSL opts, always close the connection after we're + %% done. we do this to work around an issue with httpc where it doesn't take + %% the client certificates into account when pipelining, so if the HTTP + %% implementation changes we should think about undoing this. + {Request, HttpProfile} = + case using_client_certificate(SslOpts) of + false -> + {Request0, default}; + true -> + ReqHeaders0 = element(2, Request0), + ReqHeaders1 = [{"connection", "close"} | ReqHeaders0], + ReqHeaders = lists:ukeysort(1, ReqHeaders1), + {setelement(2, Request0, ReqHeaders), oidcc_app:httpc_profile()} end, telemetry:span( @@ -166,3 +181,21 @@ fetch_content_type(Headers) -> _Other -> unknown end. + +-spec using_client_certificate([ssl:tls_client_option()] | undefined) -> boolean(). +using_client_certificate(undefined) -> + false; +using_client_certificate(SslOpts) -> + lists:foldl( + fun + (_, true) -> true; + ({key, _}, _) -> true; + ({keyfile, _}, _) -> true; + ({cert, _}, _) -> true; + ({certfile, _}, _) -> true; + ({certs_keys, _}, _) -> true; + (_, Acc) -> Acc + end, + false, + SslOpts + ). diff --git a/test/oidcc_http_util_SUITE.erl b/test/oidcc_http_util_SUITE.erl index 09d331b..58bdd96 100644 --- a/test/oidcc_http_util_SUITE.erl +++ b/test/oidcc_http_util_SUITE.erl @@ -73,6 +73,19 @@ client_cert(_Config) -> keyfile => KeyFile } ], + + ?assertMatch( + {error, {http_error, 403, <<"">>}}, + oidcc_http_util:request( + get, {"https://certauth.idrix.fr/json/", []}, telemetry_opts(), #{ + ssl => [ + {verify, verify_peer}, + {cacerts, public_key:cacerts_get()} + ] + } + ) + ), + ?assertMatch( {ok, { {json, #{