Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:26
erlang
1662-ssl-stapling-and-OCSP-implementation.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 1662-ssl-stapling-and-OCSP-implementation.patch of Package erlang
From 8e20f461978eb9c103ed2554651adcf56a64f4e0 Mon Sep 17 00:00:00 2001 From: Jakub Witczak <kuba@erlang.org> Date: Mon, 29 Jan 2024 11:11:53 +0100 Subject: [PATCH 2/2] ssl: stapling and OCSP implementation --- lib/ssl/doc/src/ssl.xml | 35 +-- lib/ssl/doc/src/standards_compliance.xml | 12 +- .../{pem_and_cert_cache.md => ssl_notes.md} | 60 ++++- lib/ssl/src/dtls_connection.erl | 24 +- lib/ssl/src/dtls_handshake.erl | 4 +- lib/ssl/src/ssl.app.src | 2 +- lib/ssl/src/ssl.erl | 69 +++--- lib/ssl/src/ssl_alert.erl | 19 +- lib/ssl/src/ssl_certificate.erl | 59 +++-- lib/ssl/src/ssl_connection.hrl | 4 +- lib/ssl/src/ssl_gen_statem.erl | 10 +- lib/ssl/src/ssl_handshake.erl | 107 ++++---- lib/ssl/src/ssl_internal.hrl | 5 +- lib/ssl/src/ssl_trace.erl | 25 +- lib/ssl/src/tls_client_connection_1_3.erl | 4 +- lib/ssl/src/tls_connection.erl | 36 +-- lib/ssl/src/tls_dtls_connection.erl | 66 ++--- lib/ssl/src/tls_gen_connection.erl | 2 +- lib/ssl/src/tls_gen_connection_1_3.erl | 1 + lib/ssl/src/tls_handshake.erl | 21 +- lib/ssl/src/tls_handshake_1_3.erl | 62 ++--- lib/ssl/test/Makefile | 2 +- lib/ssl/test/make_certs.erl | 42 ++-- ...p_SUITE.erl => openssl_stapling_SUITE.erl} | 234 ++++++++++++------ lib/ssl/test/ssl_api_SUITE.erl | 44 ++-- lib/ssl/test/ssl_gh.spec | 1 - lib/ssl/test/ssl_test_lib.erl | 2 + 27 files changed, 538 insertions(+), 414 deletions(-) rename lib/ssl/internal_doc/{pem_and_cert_cache.md => ssl_notes.md} (50%) rename lib/ssl/test/{openssl_ocsp_SUITE.erl => openssl_stapling_SUITE.erl} (54%) diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 393399d57a..efda94b2d6 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -1272,28 +1272,19 @@ fun(srp, Username :: binary(), UserState :: term()) -> </desc> </datatype> - <!-- <datatype> --> - <!-- <name name="ocsp_stapling"/> --> - <!-- <desc><p>If true, OCSP stapling will be enabled, an extension of type "status_request" will be --> - <!-- included in the client hello to indicate the desire to receive certificate status information. --> - <!-- If false (the default), OCSP stapling will be disabled.</p> --> - <!-- </desc> --> - <!-- </datatype> --> - - <!-- <datatype> --> - <!-- <name name="ocsp_responder_certs"/> --> - <!-- <desc><p>This option is a list of DER encoded certificates of OCSP responders which the client --> - <!-- trusts. This option is an empty list by default which means that the responders are implicitly --> - <!-- known to the server.</p> --> - <!-- </desc> --> - <!-- </datatype> --> - - <!-- <datatype> --> - <!-- <name name="ocsp_nonce"/> --> - <!-- <desc><p>If true (the default), the nonce will be included as one of the request extensions in the --> - <!-- request. If false, nonce will not present in the request extensions.</p> --> - <!-- </desc> --> - <!-- </datatype> --> + <datatype> + <name name="stapling"/> + <desc><p>If <c>staple</c> or a map, OCSP stapling will be enabled, an + extension of type "status_request" will be included in the + client hello to indicate the desire to receive certificate + status information. If <c>no_staple</c> (the default), OCSP stapling will + be disabled.</p> + + <p>When map is used, boolean ocsp_nonce key may indicate if + OCSP nonce should be requested by the client (default is + <c>true</c>).</p> + </desc> + </datatype> </datatypes> <datatypes> diff --git a/lib/ssl/doc/src/standards_compliance.xml b/lib/ssl/doc/src/standards_compliance.xml index ca18039830..c519b02b49 100644 --- a/lib/ssl/doc/src/standards_compliance.xml +++ b/lib/ssl/doc/src/standards_compliance.xml @@ -309,8 +309,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">status_request (RFC6066)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>27.0</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -1473,8 +1473,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">status_request (RFC6066)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle">27.0</cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -1509,8 +1509,8 @@ </url> </cell> <cell align="left" valign="middle"><em>Client</em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>PC</em></cell> + <cell align="left" valign="middle"><em>27.0</em></cell> </row> <row> <cell align="left" valign="middle"></cell> diff --git a/lib/ssl/internal_doc/pem_and_cert_cache.md b/lib/ssl/internal_doc/ssl_notes.md similarity index 50% rename from lib/ssl/internal_doc/pem_and_cert_cache.md rename to lib/ssl/internal_doc/ssl_notes.md index 52fac1e6fe..723aa15941 100644 --- a/lib/ssl/internal_doc/pem_and_cert_cache.md +++ b/lib/ssl/internal_doc/ssl_notes.md @@ -1,5 +1,53 @@ -# Notes on the PEM and cert caches -## Data relations +# ssl dev notes +## client-side OCSP stapling +1. stapling - is ssl option holding configuration provided by user + - ocsp_nonce :: true|false +2. stapling_state - holds handshake process data + - status :: not_negotiated | negotiated | not_received | received_staple + - ocsp_nonce :: binary() +3. stapling_info - holds date required for verifying the certificate chain + +```mermaid +classDiagram + stapling .. stapling_state + stapling_state ..* stapling_info + + stapling: ocsp_nonce + note for stapling "- stapling option is a boolean or a map\n- map is interpreted as stapling enabled\n- ocsp_nonce is boolean" + stapling_state: configured + stapling_state: ocsp_nonce + note for stapling_state "ocsp_nonce is random binary" + stapling_state: status + stapling_state: response + stapling_info: cert_ext #{SubjectId => Status} +``` +## ssl test certificates +- test certificates are generated by `ssl/test/make_certs.erl/` + +```mermaid +--- +title: Test certs +--- +flowchart RL + localhost["`2:localhost + 3:localhost`"] --> erlangCA[["BIG_RAND_SERIAL:erlangCA"]] + otpCA[[1:otpCA]] --> erlangCA + client["`1:client + 2:client`"] --> otpCA + server["`3:server + 4:server`"] --> otpCA + aserver["`9:a.server + 10:a.server`"] --> otpCA + bserver["`11:b.server + 12:b.server`"] --> otpCA + revoked["`5:revoked + 6:revoked`"] --> otpCA + undetermined["`7:undetermined + 8:undetermined`"] --> otpCA +``` + +## Notes on the PEM and cert caches +### Data relations |---------------| |------------------------| | PemCache | | CertDb | @@ -17,21 +65,21 @@ | Ref (FK) | | Counter | |-----------------| |------------| -### PemCache +#### PemCache 1. stores a copy of file content in memory 2. includes files from cacertfile, certfile, keyfile options 3. content is added unless FileMapDb table contains entry with specified path -### FileMapDb +#### FileMapDb 1. holds relation between specific path (PEM file with CA certificates) and a ref 2. ref is generated when file from path is added for 1st time 3. ref is used as path identifier in CertDb and RefDb tables -### RefDb +#### RefDb 1. holds an active connections counter for a specific ref 2. when counter reaches zero - related data in CertDb, FileMapDb, RefDb is deleted -### CertDb +#### CertDb 1. holds decoded CA ceritificates (only those taken from cacertfile option) 2. used for building certificate chains 3. it is an ETS set table - when iterating in search of Issuer certificate, diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index cedcd334ac..be26bbb410 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -88,7 +88,7 @@ %% | Abbrev Flight 1 to Abbrev Flight 2 part 1 %% | %% New session | Resumed session -%% WAIT_OCSP_STAPLING CERTIFY <----------------------------------> ABBREVIATED +%% WAIT_STAPLING CERTIFY <----------------------------------> ABBREVIATED %% %% <- Possibly Receive -- | | %% OCSP Stapel ------> | Send/ Recv Flight 5 | @@ -142,7 +142,7 @@ downgrade/3, hello/3, user_hello/3, - wait_ocsp_stapling/3, + wait_stapling/3, certify/3, wait_cert_verify/3, cipher/3, @@ -304,7 +304,7 @@ hello(internal, #hello_verify_request{cookie = Cookie}, host = Host, port = Port}, handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, - ocsp_stapling_state = OcspState0} = HsEnv, + stapling_state = StaplingState0} = HsEnv, connection_env = CEnv, ssl_options = SslOpts, session = #session{session_id = Id}, @@ -320,7 +320,7 @@ hello(internal, #hello_verify_request{cookie = Cookie}, State0#state{handshake_env = HsEnv#handshake_env{ tls_handshake_history = ssl_handshake:init_handshake_history(), - ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce}}}), + stapling_state = StaplingState0#{ocsp_nonce => OcspNonce}}}), {State2, Actions} = dtls_gen_connection:send_handshake(Hello, State1), State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, % RequestedVersion @@ -365,18 +365,18 @@ hello(internal, #server_hello{} = Hello, static_env = #static_env{role = client}, handshake_env = #handshake_env{ renegotiation = {Renegotiation, _}, - ocsp_stapling_state = OcspState0} = HsEnv, + stapling_state = StaplingState0} = HsEnv, connection_states = ConnectionStates0, session = #session{session_id = OldId}, ssl_options = SslOptions} = State) -> try - {Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} = + {Version, NewId, ConnectionStates, ProtoExt, Protocol, StaplingState} = dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId), tls_dtls_connection:handle_session( Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol, State#state{handshake_env = HsEnv#handshake_env{ - ocsp_stapling_state = maps:merge(OcspState0,OcspState)}}) + stapling_state = maps:merge(StaplingState0,StaplingState)}}) catch throw:#alert{} = Alert -> ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State) end; @@ -430,17 +430,17 @@ abbreviated(Type, Event, State) -> gen_handshake(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- --spec wait_ocsp_stapling(gen_statem:event_type(), term(), #state{}) -> +-spec wait_stapling(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- -wait_ocsp_stapling(enter, _Event, State0) -> +wait_stapling(enter, _Event, State0) -> {State, Actions} = handle_flight_timer(State0), {keep_state, State, Actions}; -wait_ocsp_stapling(info, Event, State) -> +wait_stapling(info, Event, State) -> gen_info(Event, ?FUNCTION_NAME, State); -wait_ocsp_stapling(state_timeout, Event, State) -> +wait_stapling(state_timeout, Event, State) -> handle_state_timeout(Event, ?FUNCTION_NAME, State); -wait_ocsp_stapling(Type, Event, State) -> +wait_stapling(Type, Event, State) -> gen_handshake(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index 1f85bae2ce..30fc4e7f69 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -226,12 +226,12 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, HelloExt, SslOpt, ConnectionStates0, Renegotiation, IsNew) -> - {ConnectionStates, ProtoExt, Protocol, OcspState} = + {ConnectionStates, ProtoExt, Protocol, StaplingState} = ssl_handshake:handle_server_hello_extensions( dtls_record, Random, CipherSuite, HelloExt, dtls_v1:corresponding_tls_version(Version), SslOpt, ConnectionStates0, Renegotiation, IsNew), - {Version, SessionId, ConnectionStates, ProtoExt, Protocol, OcspState}. + {Version, SessionId, ConnectionStates, ProtoExt, Protocol, StaplingState}. %%-------------------------------------------------------------------- diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 80f033b126..48eee9967e 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -434,10 +434,8 @@ {session_tickets, client_session_tickets()} | {use_ticket, use_ticket()} | {early_data, client_early_data()} | - {use_srtp, use_srtp()}. -%% {ocsp_stapling, ocsp_stapling()} | -%% {ocsp_responder_certs, ocsp_responder_certs()} | -%% {ocsp_nonce, ocsp_nonce()}. + {use_srtp, use_srtp()} | + {stapling, stapling()}. -type client_verify_type() :: verify_type(). -type client_reuse_session() :: session_id() | {session_id(), SessionData::binary()}. @@ -459,9 +457,7 @@ -type max_fragment_length() :: undefined | 512 | 1024 | 2048 | 4096. -type fallback() :: boolean(). -type ssl_imp() :: new | old. -%% -type ocsp_stapling() :: boolean(). -%% -type ocsp_responder_certs() :: [public_key:der_encoded()]. -%% -type ocsp_nonce() :: boolean(). +-type stapling() :: staple | no_staple | map(). %% ------------------------------------------------------------------------------------------------------- @@ -1645,7 +1641,7 @@ ssl_options() -> middlebox_comp_mode, max_fragment_length, next_protocol_selector, next_protocols_advertised, - ocsp_stapling, ocsp_responder_certs, ocsp_nonce, + stapling, padding_check, partial_chain, password, @@ -1687,7 +1683,7 @@ process_options(UserSslOpts, SslOpts0, Env) -> SslOpts2 = opt_verification(UserSslOptsMap, SslOpts1, Env), SslOpts3 = opt_certs(UserSslOptsMap, SslOpts2, Env), SslOpts4 = opt_tickets(UserSslOptsMap, SslOpts3, Env), - SslOpts5 = opt_ocsp(UserSslOptsMap, SslOpts4, Env), + SslOpts5 = opt_stapling(UserSslOptsMap, SslOpts4, Env), SslOpts6 = opt_sni(UserSslOptsMap, SslOpts5, Env), SslOpts7 = opt_signature_algs(UserSslOptsMap, SslOpts6, Env), SslOpts8 = opt_alpn(UserSslOptsMap, SslOpts7, Env), @@ -2073,33 +2069,30 @@ opt_tickets(UserOpts, #{versions := Versions} = Opts, #{role := server}) -> Opts#{session_tickets => SessionTickets, early_data => EarlyData, anti_replay => AntiReplay, stateless_tickets_seed => STS}. -opt_ocsp(UserOpts, #{versions := _Versions} = Opts, #{role := Role}) -> - {Stapling, SMap} = - case get_opt(ocsp_stapling, ?DEFAULT_OCSP_STAPLING, UserOpts, Opts) of - {old, Map} when is_map(Map) -> {true, Map}; - {_, Bool} when is_boolean(Bool) -> {Bool, #{}}; - {_, Value} -> option_error(ocsp_stapling, Value) +opt_stapling(UserOpts, #{versions := _Versions} = Opts, #{role := client}) -> + {Stapling, Nonce} = + case get_opt(stapling, ?DEFAULT_STAPLING_OPT, UserOpts, Opts) of + {old, StaplingMap} when is_map(StaplingMap) -> + {true, maps:get(ocsp_nonce, StaplingMap, ?DEFAULT_OCSP_NONCE_OPT)}; + {_, staple} -> + {true, ?DEFAULT_OCSP_NONCE_OPT}; + {_, no_staple} -> + {false, ignore}; + {_, Map} when is_map(Map) -> + {true, maps:get(ocsp_nonce, Map, ?DEFAULT_OCSP_NONCE_OPT)}; + {_, Value} -> + option_error(stapling, Value) end, - assert_client_only(Role, Stapling, ocsp_stapling), - {_, Nonce} = get_opt_bool(ocsp_nonce, ?DEFAULT_OCSP_NONCE, UserOpts, SMap), - option_incompatible(Stapling =:= false andalso Nonce =:= false, - [{ocsp_nonce, false}, {ocsp_stapling, false}]), - {_, ORC} = get_opt_list(ocsp_responder_certs, ?DEFAULT_OCSP_RESPONDER_CERTS, - UserOpts, SMap), - CheckBinary = fun(Cert) when is_binary(Cert) -> ok; - (_Cert) -> option_error(ocsp_responder_certs, ORC) - end, - [CheckBinary(C) || C <- ORC], - option_incompatible(Stapling =:= false andalso ORC =/= [], - [ocsp_responder_certs, {ocsp_stapling, false}]), case Stapling of true -> - Opts#{ocsp_stapling => - #{ocsp_nonce => Nonce, - ocsp_responder_certs => ORC}}; + Opts#{stapling => + #{ocsp_nonce => Nonce}}; false -> Opts - end. + end; +opt_stapling(UserOpts, Opts, #{role := server}) -> + assert_client_only(stapling, UserOpts), + Opts. opt_sni(UserOpts, #{versions := _Versions} = Opts, #{role := server}) -> {_, SniHosts} = get_opt_list(sni_hosts, [], UserOpts, Opts), @@ -2609,10 +2602,6 @@ assert_server_only(client, Bool, Option) -> role_error(Bool, server_only, Option); assert_server_only(_, _, _) -> ok. -assert_client_only(server, Bool, Option) -> - role_error(Bool, client_only, Option); -assert_client_only(_, _, _) -> - ok. role_error(false, _ErrorDesc, _Option) -> ok; @@ -3054,9 +3043,9 @@ unambiguous_path(Value) -> %%%# %%%# Tracing %%%# -handle_trace(csp, {call, {?MODULE, opt_ocsp, [UserOpts | _]}}, Stack) -> +handle_trace(csp, {call, {?MODULE, opt_stapling, [UserOpts | _]}}, Stack) -> {format_ocsp_params(UserOpts), Stack}; -handle_trace(csp, {return_from, {?MODULE, opt_ocsp, 3}, Return}, Stack) -> +handle_trace(csp, {return_from, {?MODULE, opt_stapling, 3}, Return}, Stack) -> {format_ocsp_params(Return), Stack}; handle_trace(rle, {call, {?MODULE, listen, Args}}, Stack0) -> Role = server, @@ -3066,8 +3055,6 @@ handle_trace(rle, {call, {?MODULE, connect, Args}}, Stack0) -> {io_lib:format("(*~w) Args = ~W", [Role, Args, 10]), [{role, Role} | Stack0]}. format_ocsp_params(Map) -> - Stapling = maps:get(ocsp_stapling, Map, '?'), + Stapling = maps:get(stapling, Map, '?'), Nonce = maps:get(ocsp_nonce, Map, '?'), - Certs = maps:get(ocsp_responder_certs, Map, '?'), - io_lib:format("Stapling = ~W Nonce = ~W Certs = ~W", - [Stapling, 5, Nonce, 5, Certs, 5]). + io_lib:format("Stapling = ~W Nonce = ~W", [Stapling, 5, Nonce, 5]). diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl index e23db08ffb..ee8cad8d02 100644 --- a/lib/ssl/src/ssl_alert.erl +++ b/lib/ssl/src/ssl_alert.erl @@ -27,7 +27,7 @@ %% %%---------------------------------------------------------------------- -module(ssl_alert). - +-feature(maybe_expr, enable). -include("ssl_alert.hrl"). -include("ssl_record.hrl"). -include("ssl_internal.hrl"). @@ -113,9 +113,20 @@ own_alert_format_depth(#alert{reason = Reason} = Alert) -> {" ~s\n ~P", [Txt, Reason, ?DEPTH]} end. -own_alert_txt(#alert{level = Level, description = Description, where = #{line := Line, file := Mod}, role = Role}) -> - "at " ++ Mod ++ ":" ++ integer_to_list(Line) ++ " generated " ++ string:uppercase(atom_to_list(Role)) ++ " ALERT: " ++ - level_txt(Level) ++ description_txt(Description). +own_alert_txt(#alert{level = Level, description = Description, + where = #{line := Line, file := Mod} = Where, + role = Role}) -> + DefaultLeft = "at " ++ Mod ++ ":" ++ integer_to_list(Line), + DefaultRight = " generated " ++ string:uppercase(atom_to_list(Role)) ++ " ALERT: " ++ + level_txt(Level) ++ description_txt(Description), + maybe + debug ?= get(log_level), + {current_stacktrace, Stacktrace} ?= maps:get(st, Where, undefined), + DefaultLeft ++ io_lib:format("~n~p~n", [Stacktrace]) ++ DefaultRight + else + _ -> + DefaultLeft ++ DefaultRight + end. alert_format(Alert) -> Txt = alert_txt(Alert), diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 505eb10f79..e3114a3f24 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -78,7 +78,6 @@ select_extension/2, extensions_list/1, public_key_type/1, - foldl_db/3, find_cross_sign_root_paths/4, handle_cert_auths/4, available_cert_key_pairs/1, @@ -571,22 +570,45 @@ verify_hostname(Hostname, Customize, Cert, UserState) -> verify_cert_extensions(Cert, #{cert_ext := CertExts} = UserState) -> Id = public_key:pkix_subject_id(Cert), Extensions = maps:get(Id, CertExts, []), - verify_cert_extensions(Cert, UserState, Extensions, #{}). + verify_cert_extensions(Cert, UserState, Extensions, + #{certificate_valid => false}). +verify_cert_extensions(_Cert, _UserState = #{stapling_state := #{configured := true}, + path_len := 0}, [], + _Context = #{certificate_valid := false}) -> + {fail, missing_certificate_status}; verify_cert_extensions(Cert, UserState, [], _) -> {valid, UserState#{issuer => Cert}}; -verify_cert_extensions(Cert, #{ocsp_responder_certs := ResponderCerts, - ocsp_state := OscpState, - issuer := Issuer} = UserState, - [#certificate_status{response = OcspResponsDer} | Exts], +verify_cert_extensions(_, #{stapling_state := #{configured := false}}, + [#certificate_status{} | _], _) -> + {fail, unexpected_certificate_status}; +verify_cert_extensions(Cert, #{stapling_state := StaplingState, + issuer := Issuer, + certdb := CertDbHandle, + certdb_ref := CertDbRef} = UserState, + [#certificate_status{response = OcspResponseDer} | Exts], Context) -> - #{ocsp_nonce := Nonce} = OscpState, - case public_key:pkix_ocsp_validate(Cert, Issuer, OcspResponsDer, - ResponderCerts, Nonce) of - valid -> - verify_cert_extensions(Cert, UserState, Exts, Context); - {bad_cert, _} = Status -> - {fail, Status} + #{ocsp_nonce := Nonce} = StaplingState, + IsTrustedResponderFun = + fun(#cert{der = DerResponderCert, otp = OtpCert}) -> + OtpTbsCert = OtpCert#'OTPCertificate'.tbsCertificate, + #'OTPTBSCertificate'{ + issuer = IssuerId, serialNumber = SerialNr} = OtpTbsCert, + case ssl_manager:lookup_trusted_cert( + CertDbHandle, CertDbRef, SerialNr, IssuerId) of + {ok, #cert{der = DerResponderCert}} -> + true; + _ -> + false + end + end, + case public_key:pkix_ocsp_validate(Cert, Issuer, OcspResponseDer, Nonce, + [{is_trusted_responder_fun, IsTrustedResponderFun}]) of + ok -> + verify_cert_extensions(Cert, UserState, Exts, + Context#{certificate_valid => true}); + {error, {bad_cert, _} = Reason} -> + {fail, Reason} end; verify_cert_extensions(Cert, UserState, [_|Exts], Context) -> %% Skip unknown extensions! @@ -841,16 +863,15 @@ handle_trace(crt, {call, {?MODULE, verify_cert_extensions, %% {io_lib:format(" no more extensions (~s)", [ssl_test_lib:format_cert(Cert)]), Stack}; handle_trace(crt, {call, {?MODULE, verify_cert_extensions, [Cert, - #{ocsp_responder_certs := _ResponderCerts, - ocsp_state := OcspState, + #{stapling_state := StaplingState, issuer := Issuer} = _UserState, [#certificate_status{response = OcspResponsDer} | _Exts], _Context]}}, Stack) -> - {io_lib:format("#2 OcspState = ~W Issuer = [~W] OcspResponsDer = ~W [~W]", - [OcspState, 10, Issuer, 3, OcspResponsDer, 2, Cert, 3]), + {io_lib:format("#2 StaplingState = ~W Issuer = [~W] OcspResponsDer = ~W [~W]", + [StaplingState, 10, Issuer, 3, OcspResponsDer, 2, Cert, 3]), Stack}; - %% {io_lib:format("#2 OcspState = ~W Issuer = (~s) OcspResponsDer = ~W (~s)", - %% [OcspState, 10, ssl_test_lib:format_cert(Issuer), + %% {io_lib:format("#2 StaplingState = ~W Issuer = (~s) OcspResponsDer = ~W (~s)", + %% [StaplingState, 10, ssl_test_lib:format_cert(Issuer), %% OcspResponsDer, 2, ssl_test_lib:format_cert(Cert)]), handle_trace(crt, {return_from, {ssl_certificate, verify_cert_extensions, 4}, diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index 1b8efa0710..37006e0535 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -88,8 +88,8 @@ server_psk_identity :: binary() | 'undefined', % server psk identity hint cookie_iv_shard :: {binary(), binary()} %% IV, Shard | 'undefined', - ocsp_stapling_state = #{ocsp_stapling => false, - ocsp_expect => no_staple} + stapling_state = #{configured => false, + status => not_negotiated} }). -record(connection_env, { diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl index 8e90c0a23e..1d34906198 100644 --- a/lib/ssl/src/ssl_gen_statem.erl +++ b/lib/ssl/src/ssl_gen_statem.erl @@ -523,7 +523,7 @@ initial_hello({call, From}, {start, Timeout}, cert_db_ref = CertDbRef, protocol_cb = Connection}, handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, - ocsp_stapling_state = OcspState0}, + stapling_state = StaplingState0}, connection_env = CEnv, ssl_options = #{%% Use highest version in initial ClientHello. %% Versions is a descending list of supported versions. @@ -582,16 +582,16 @@ initial_hello({call, From}, {start, Timeout}, {#state{handshake_env = HsEnv1} = State5, _} = Connection:send_handshake_flight(State4), - OcspStaplingKeyPresent = maps:is_key(ocsp_stapling, SslOpts), + StaplingKeyPresent = maps:is_key(stapling, SslOpts), State = State5#state{ connection_env = CEnv#connection_env{ negotiated_version = RequestedVersion}, session = Session, handshake_env = HsEnv1#handshake_env{ - ocsp_stapling_state = - OcspState0#{ocsp_nonce => OcspNonce, - ocsp_stapling => OcspStaplingKeyPresent}}, + stapling_state = + StaplingState0#{ocsp_nonce => OcspNonce, + configured => StaplingKeyPresent}}, start_or_recv_from = From, key_share = KeyShare}, NextState = next_statem_state(Versions, Role), diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 4da3a8fa5c..8a0c0b6a79 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -337,7 +337,7 @@ next_protocol(SelectedProtocol) -> %%-------------------------------------------------------------------- certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, #{partial_chain := PartialChain} = SSlOptions, - CRLDbHandle, Role, Host, Version, CertExt) -> + CRLDbHandle, Role, Host, Version, ExtInfo) -> ServerName = server_name(SSlOptions, Host, Role), [PeerCert | _ChainCerts ] = ASN1Certs, try @@ -346,7 +346,7 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, PartialChain), case path_validate(PathsAndAnchors, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, - Version, SSlOptions, CertExt) of + Version, SSlOptions, ExtInfo) of {ok, {PublicKeyInfo, _}} -> {PeerCert, PublicKeyInfo}; {error, Reason} -> @@ -1293,13 +1293,10 @@ maybe_add_tls13_extensions(?TLS_1_3, maybe_add_tls13_extensions(_, HelloExtensions, _, _, _, _,_) -> HelloExtensions. -maybe_add_certificate_status_request(_Version, #{ocsp_stapling := OcspStapling}, +maybe_add_certificate_status_request(_Version, #{stapling := _Stapling}, OcspNonce, HelloExtensions) -> - OcspResponderCerts = maps:get(ocsp_responder_certs, OcspStapling), - OcspResponderList = get_ocsp_responder_list(OcspResponderCerts), OcspRequestExtns = public_key:ocsp_extensions(OcspNonce), - Req = #ocsp_status_request{responder_id_list = OcspResponderList, - request_extensions = OcspRequestExtns}, + Req = #ocsp_status_request{request_extensions = OcspRequestExtns}, CertStatusReqExtn = #certificate_status_request{ status_type = ?CERTIFICATE_STATUS_TYPE_OCSP, request = Req @@ -1309,9 +1306,6 @@ maybe_add_certificate_status_request(_Version, _SslOpts, _OcspNonce, HelloExtensions) -> HelloExtensions. -get_ocsp_responder_list(ResponderCerts) -> - lists:map(fun public_key:ocsp_responder_id/1, ResponderCerts). - %% TODO: Add support for PSK key establishment %% RFC 8446 (TLS 1.3) - 4.2.8. Key Share @@ -1523,10 +1517,10 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, ok end, - case handle_ocsp_extension(SslOpts, Exts) of + case handle_cert_status_extension(SslOpts, Exts) of #alert{} = Alert -> Alert; - OcspState -> + StaplingState -> %% If we receive an ALPN extension then this is the protocol selected, %% otherwise handle the NPN extension. ALPN = maps:get(alpn, Exts, undefined), @@ -1534,14 +1528,14 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, %% ServerHello contains exactly one protocol: the one selected. %% We also ignore the ALPN extension during renegotiation (see encode_alpn/2). [Protocol] when not Renegotiation -> - {ConnectionStates, alpn, Protocol, OcspState}; + {ConnectionStates, alpn, Protocol, StaplingState}; [_] when Renegotiation -> - {ConnectionStates, alpn, undefined, OcspState}; + {ConnectionStates, alpn, undefined, StaplingState}; undefined -> NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined), NextProtocolSelector = maps:get(next_protocol_selector, SslOpts, undefined), Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtocolSelector, Renegotiation), - {ConnectionStates, npn, Protocol, OcspState}; + {ConnectionStates, npn, Protocol, StaplingState}; {error, Reason} -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); [] -> @@ -1947,22 +1941,22 @@ extension_value(#psk_key_exchange_modes{ke_modes = Modes}) -> extension_value(#cookie{cookie = Cookie}) -> Cookie. -handle_ocsp_extension(#{ocsp_stapling := _OcspStapling}, Extensions) -> +handle_cert_status_extension(#{stapling := _Stapling}, Extensions) -> case maps:get(status_request, Extensions, false) of - undefined -> %% status_request in server hello is empty - #{ocsp_stapling => true, - ocsp_expect => staple}; - false -> %% status_request is missing (not negotiated) - #{ocsp_stapling => true, - ocsp_expect => no_staple}; + undefined -> %% status_request received in server hello + #{configured => true, + status => negotiated}; + false -> + #{configured => true, + status => not_negotiated}; _Else -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, status_request_not_empty) end; -handle_ocsp_extension(_SslOpts, Extensions) -> +handle_cert_status_extension(_SslOpts, Extensions) -> case maps:get(status_request, Extensions, false) of - false -> %% status_request is missing (not negotiated) - #{ocsp_stapling => false, - ocsp_expect => no_staple}; + false -> + #{configured => false, + status => not_negotiated}; _Else -> %% unsolicited status_request ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_status_request) end. @@ -2010,11 +2004,11 @@ certificate_authorities_from_db(_CertDbHandle, {extracted, CertDbData}) -> %%-------------Handle handshake messages -------------------------------- path_validate(TrustedAndPath, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, - Version, SslOptions, CertExt) -> + Version, SslOptions, ExtInfo) -> InitialPotentialError = {error, {bad_cert, unknown_ca}}, InitialInvalidated = [], path_validate(TrustedAndPath, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, - Version, SslOptions, CertExt, InitialInvalidated, InitialPotentialError). + Version, SslOptions, ExtInfo, InitialInvalidated, InitialPotentialError). validation_fun_and_state({Fun, UserState0}, VerifyState, CertPath, LogLevel) -> {fun(OtpCert, {extension, _} = Extension, {SslState, UserState}) -> @@ -2199,25 +2193,21 @@ bad_key(#{algorithm := ecdsa}) -> unacceptable_ecdsa_key. cert_status_check(_, - #{ocsp_state := #{ocsp_stapling := true, - ocsp_expect := stapled}}, + #{stapling_state := #{configured := true, + status := received_staple}}, _VerifyResult, _, _) -> - %% OCSP staple will now be checked by + %% OCSP staple(s) will now be checked by %% ssl_certificate:verify_cert_extensions/2 in ssl_certificate:validate valid; cert_status_check(OtpCert, - #{ocsp_state := #{ocsp_stapling := false}} = SslState, + #{stapling_state := #{configured := false}} = SslState, VerifyResult, CertPath, LogLevel) -> maybe_check_crl(OtpCert, SslState, VerifyResult, CertPath, LogLevel); cert_status_check(_OtpCert, - #{ocsp_state := #{ocsp_stapling := true, - ocsp_expect := undetermined}}, - _VerifyResult, _CertPath, _LogLevel) -> - {bad_cert, {revocation_status_undetermined, not_stapled}}; -cert_status_check(_OtpCert, - #{ocsp_state := #{ocsp_stapling := true, - ocsp_expect := no_staple}}, - _VerifyResult, _CertPath, _LogLevel) -> + #{stapling_state := #{configured := true, + status := StaplingStatus}}, + _VerifyResult, _CertPath, _LogLevel) + when StaplingStatus == not_negotiated; StaplingStatus == not_received -> {bad_cert, {revocation_status_undetermined, not_stapled}}. maybe_check_crl(_, #{crl_check := false}, _, _, _) -> @@ -3799,23 +3789,28 @@ path_validate([], _, _, _, _, _, _, _, _, _, {error, {bad_cert, root_cert_expire path_validate([], _, _, _, _, _, _, _, _, _, Error) -> Error; path_validate([{TrustedCert, Path} | Rest], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, - Version, SslOptions, CertExt, InvalidatedList, Error) -> + Version, SslOptions, ExtInfo, InvalidatedList, Error) -> CB = path_validation_cb(Version), case CB:path_validation(trusted_unwrap(TrustedCert), Path, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, - Version, SslOptions, CertExt) of + Version, SslOptions, ExtInfo) of {error, {bad_cert, root_cert_expired}} = NewError -> NewInvalidatedList = [TrustedCert | InvalidatedList], - Alt = ssl_certificate:find_cross_sign_root_paths(Path, CertDbHandle, CertDbRef, NewInvalidatedList), - path_validate(Alt ++ Rest, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, - Version, SslOptions, CertExt, NewInvalidatedList, NewError); + Alt = ssl_certificate:find_cross_sign_root_paths( + Path, CertDbHandle,CertDbRef, NewInvalidatedList), + path_validate(Alt ++ Rest, ServerName, Role, CertDbHandle, + CertDbRef, CRLDbHandle, Version, SslOptions, + ExtInfo, NewInvalidatedList, NewError); {error, {bad_cert, unknown_ca}} = NewError -> - Alt = ssl_certificate:find_cross_sign_root_paths(Path, CertDbHandle, CertDbRef, InvalidatedList), - path_validate(Alt ++ Rest, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, - Version, SslOptions, CertExt, InvalidatedList, error_to_propagate(Error, NewError)); + Alt = ssl_certificate:find_cross_sign_root_paths( + Path, CertDbHandle, CertDbRef, InvalidatedList), + path_validate(Alt ++ Rest, ServerName, Role, CertDbHandle, + CertDbRef, CRLDbHandle, Version, SslOptions, + ExtInfo, InvalidatedList, + error_to_propagate(Error, NewError)); {error, _} when Rest =/= []-> path_validate(Rest, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, - Version, SslOptions, CertExt, InvalidatedList, Error); + Version, SslOptions, ExtInfo, InvalidatedList, Error); Result -> Result end. @@ -3834,11 +3829,10 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR crl_check := CrlCheck, log_level := Level} = Opts, #{cert_ext := CertExt, - ocsp_responder_certs := OcspResponderCerts, - ocsp_state := OcspState}) -> + stapling_state := StaplingState}) -> SignAlgos = maps:get(signature_algs, Opts, undefined), SignAlgosCert = maps:get(signature_algs_cert, Opts, undefined), - ValidationFunAndState = + ValidationFunAndState = validation_fun_and_state(VerifyFun, #{role => Role, certdb => CertDbHandle, certdb_ref => CertDbRef, @@ -3852,8 +3846,7 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR crl_db => CRLDbHandle, cert_ext => CertExt, issuer => TrustedCert, - ocsp_responder_certs => OcspResponderCerts, - ocsp_state => OcspState, + stapling_state => StaplingState, path_len => length(Path) }, Path, Level), @@ -3880,6 +3873,6 @@ handle_trace(csp, [_Version, SslOpts, _OcspNonce, _HelloExtensions]}}, Stack) -> - OcspStapling = maps:get(ocsp_stapling, SslOpts, false), - {io_lib:format("#1 ADD crt status request / OcspStapling option = ~W", - [OcspStapling, 10]), Stack}. + Stapling = maps:get(stapling, SslOpts, false), + {io_lib:format("#1 ADD crt status request / Stapling option = ~W", + [Stapling, 10]), Stack}. diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index cbe497616a..091dee3583 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -204,7 +204,6 @@ }). -define(DEFAULT_DEPTH, 10). --define(DEFAULT_OCSP_STAPLING, false). --define(DEFAULT_OCSP_NONCE, true). --define(DEFAULT_OCSP_RESPONDER_CERTS, []). +-define(DEFAULT_STAPLING_OPT, no_staple). +-define(DEFAULT_OCSP_NONCE_OPT, true). -endif. % -ifdef(ssl_internal). diff --git a/lib/ssl/src/ssl_trace.erl b/lib/ssl/src/ssl_trace.erl index 70ac33004c..068b0c215a 100644 --- a/lib/ssl/src/ssl_trace.erl +++ b/lib/ssl/src/ssl_trace.erl @@ -430,25 +430,28 @@ trace_profiles() -> fun(M, F, A) -> dbg:ctpl(M, F, A) end, [{ssl_handshake, [{maybe_add_certificate_status_request, 4}, {client_hello_extensions, 10}, {cert_status_check, 5}, - {get_ocsp_responder_list, 1}, {handle_ocsp_extension, 2}, + {handle_cert_status_extension, 2}, {path_validation, 10}, {handle_server_hello_extensions, 9}, {handle_client_hello_extensions, 10}, {cert_status_check, 5}]}, {public_key, [{ocsp_extensions, 1}, {pkix_ocsp_validate, 5}, - {ocsp_responder_id, 1}, {otp_cert, 1}]}, - {pubkey_ocsp, [{find_responder_cert, 2}, {do_verify_ocsp_signature, 4}, - {verify_ocsp_response, 3}, {verify_ocsp_nonce, 2}, - {verify_ocsp_signature, 5}, {do_verify_ocsp_response, 3}, - {is_responder, 2}, {find_single_response, 3}, - {ocsp_status, 1}, {match_single_response, 4}]}, - {ssl, [{opt_ocsp, 3}]}, + {otp_cert, 1}]}, + {pubkey_ocsp, [{do_verify_signature, 4}, + {verify_response, 5}, {verify_nonce, 2}, + {verify_signature, 7}, + {is_responder_cert, 2}, {find_single_response, 3}, + {status, 1}, {match_single_response, 4}, + {designated_for_ocsp_signing, 1}]}, + {ssl, [{opt_stapling, 3}]}, {ssl_certificate, [{verify_cert_extensions, 4}]}, {ssl_test_lib, [{init_openssl_server, 3}, {openssl_server_loop, 3}]}, - {tls_connection, [{wait_ocsp_stapling, 3}]}, + {tls_connection, [{wait_stapling, 3}]}, {dtls_connection, [{initial_hello, 3}, {hello, 3}, {connection, 3}]}, - {tls_dtls_connection, [{wait_ocsp_stapling, 3}, {certify, 3}]}, - {tls_handshake, [{ocsp_nonce, 1}, {ocsp_expect, 1}, {client_hello, 11}]}, + {tls_dtls_connection, [{wait_stapling, 3}, {certify, 3}]}, + {tls_handshake, [{ocsp_nonce, 1}, {client_hello, 11}]}, + {tls_handshake_1_3, [{validate_certificate_chain, 8}, + {process_certificate, 2}]}, {dtls_handshake, [{client_hello, 8}]}]}, {crt, %% certificates fun(M, F, A) -> dbg:tpl(M, F, A, x) end, diff --git a/lib/ssl/src/tls_client_connection_1_3.erl b/lib/ssl/src/tls_client_connection_1_3.erl index b4da359196..552ca2d2e8 100644 --- a/lib/ssl/src/tls_client_connection_1_3.erl +++ b/lib/ssl/src/tls_client_connection_1_3.erl @@ -521,7 +521,7 @@ do_handle_exlusive_1_3_hello_or_hello_retry_request( transport_cb = Transport, socket = Socket}, handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, - ocsp_stapling_state = OcspState}, + stapling_state = StaplingState}, connection_env = #connection_env{negotiated_version = NegotiatedVersion}, protocol_specific = PS, @@ -565,7 +565,7 @@ do_handle_exlusive_1_3_hello_or_hello_retry_request( ClientKeyShare = ssl_cipher:generate_client_shares([SelectedGroup]), TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket), - OcspNonce = maps:get(ocsp_nonce, OcspState, undefined), + OcspNonce = maps:get(ocsp_nonce, StaplingState, undefined), Hello0 = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, SessionId, Renegotiation, diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 83ebdbd167..44d84439d2 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -71,8 +71,8 @@ %% | Send/Recv Flight 2 or Abbrev Flight 1 - Abbrev Flight 2 part 1 %% | %% New session | Resumed session -%% WAIT_OCSP_STAPLING CERTIFY <----------------------------------> ABBREVIATED -%% WAIT_CERT_VERIFY +%% WAIT_STAPLING CERTIFY <----------------------------------> ABBREVIATED +%% WAIT_CERT_VERIFY %% <- Possibly Receive -- | | %% OCSP Staple/CertVerify -> | Flight 3 part 1 | %% | | @@ -123,7 +123,7 @@ downgrade/3, hello/3, user_hello/3, - wait_ocsp_stapling/3, + wait_stapling/3, certify/3, wait_cert_verify/3, cipher/3, @@ -248,27 +248,27 @@ hello(internal, #server_hello{} = Hello, connection_env = CEnv, static_env = #static_env{role = client}, handshake_env = #handshake_env{ - ocsp_stapling_state = OcspState0, + stapling_state = StaplingState0, renegotiation = {Renegotiation, _}} = HsEnv, session = #session{session_id = OldId}, ssl_options = SslOptions} = State) -> try case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of %% Legacy TLS 1.2 and older - {Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} -> + {Version, NewId, ConnectionStates, ProtoExt, Protocol, StaplingState} -> tls_dtls_connection:handle_session( Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol, State#state{ handshake_env = HsEnv#handshake_env{ - ocsp_stapling_state = maps:merge(OcspState0,OcspState)}}); + stapling_state = maps:merge(StaplingState0,StaplingState)}}); %% TLS 1.3 - {next_state, wait_sh, SelectedVersion, OcspState} -> + {next_state, wait_sh, SelectedVersion, StaplingState} -> %% Continue in TLS 1.3 'wait_sh' state {next_state, wait_sh, State#state{handshake_env = - HsEnv#handshake_env{ocsp_stapling_state = - maps:merge(OcspState0, OcspState)}, + HsEnv#handshake_env{stapling_state = + maps:merge(StaplingState0, StaplingState)}, connection_env = CEnv#connection_env{negotiated_version = SelectedVersion}}, [{change_callback_module, tls_client_connection_1_3}, @@ -304,12 +304,12 @@ abbreviated(Type, Event, State) -> end. %%-------------------------------------------------------------------- --spec wait_ocsp_stapling(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). +-spec wait_stapling(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). %%-------------------------------------------------------------------- -wait_ocsp_stapling(info, Event, State) -> +wait_stapling(info, Event, State) -> gen_info(Event, ?FUNCTION_NAME, State); -wait_ocsp_stapling(Type, Event, State) -> +wait_stapling(Type, Event, State) -> try tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State) catch throw:#alert{} = Alert -> ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State) @@ -373,7 +373,7 @@ connection(internal, #hello_request{}, session_cache_cb = CacheCb}, handshake_env = #handshake_env{ renegotiation = {Renegotiation, peer}, - ocsp_stapling_state = OcspState}, + stapling_state = StaplingState}, connection_env = #connection_env{cert_key_alts = CertKeyAlts}, session = Session0, ssl_options = SslOpts, @@ -386,7 +386,7 @@ connection(internal, #hello_request{}, Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, Session#session.session_id, Renegotiation, undefined, - undefined, maps:get(ocsp_nonce, OcspState, undefined), + undefined, maps:get(ocsp_nonce, StaplingState, undefined), CertDbHandle, CertDbRef), {State, Actions} = tls_gen_connection:send_handshake(Hello, State0#state{connection_states = @@ -407,12 +407,12 @@ connection(internal, #hello_request{}, }, handshake_env = #handshake_env{ renegotiation = {Renegotiation, _}, - ocsp_stapling_state = OcspState}, + stapling_state = StaplingState}, ssl_options = SslOpts, connection_states = ConnectionStates} = State0) -> Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, <<>>, Renegotiation, undefined, - undefined, maps:get(ocsp_nonce, OcspState, undefined), + undefined, maps:get(ocsp_nonce, StaplingState, undefined), CertDbHandle, CertDbRef), {State, Actions} = tls_gen_connection:send_handshake(Hello, State0), @@ -610,6 +610,6 @@ choose_tls_fsm(_, _) -> %%%# Tracing %%%# handle_trace(csp, - {call, {?MODULE, wait_ocsp_stapling, + {call, {?MODULE, wait_stapling, [Type, Event|_]}}, Stack) -> {io_lib:format("Type = ~w Event = ~W", [Type, Event, 10]), Stack}. diff --git a/lib/ssl/src/tls_dtls_connection.erl b/lib/ssl/src/tls_dtls_connection.erl index e0869ee7f5..4810d09c6f 100644 --- a/lib/ssl/src/tls_dtls_connection.erl +++ b/lib/ssl/src/tls_dtls_connection.erl @@ -58,7 +58,7 @@ abbreviated/3, certify/3, wait_cert_verify/3, - wait_ocsp_stapling/3, + wait_stapling/3, cipher/3, connection/3, downgrade/3, @@ -261,30 +261,31 @@ abbreviated(Type, Event, State) -> ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State). %%-------------------------------------------------------------------- --spec wait_ocsp_stapling(gen_statem:event_type(), - #certificate{} | #certificate_status{} | term(), - #state{}) -> - gen_statem:state_function_result(). +-spec wait_stapling(gen_statem:event_type(), + #certificate{} | #certificate_status{} | term(), + #state{}) -> + gen_statem:state_function_result(). %%-------------------------------------------------------------------- -wait_ocsp_stapling(internal, #certificate{}, +wait_stapling(internal, #certificate{}, #state{static_env = #static_env{protocol_cb = _Connection}} = State) -> %% Postpone message, should be handled in certify after receiving staple message {next_state, ?FUNCTION_NAME, State, [{postpone, true}]}; %% Receive OCSP staple message -wait_ocsp_stapling(internal, #certificate_status{} = CertStatus, +wait_stapling(internal, #certificate_status{} = CertStatus, #state{static_env = #static_env{protocol_cb = _Connection}, handshake_env = - #handshake_env{ocsp_stapling_state = OcspState} = HsEnv} = State) -> + #handshake_env{stapling_state = StaplingState} = HsEnv} = State) -> {next_state, certify, State#state{handshake_env = - HsEnv#handshake_env{ocsp_stapling_state = - OcspState#{ocsp_expect => stapled, - ocsp_response => CertStatus}}}}; + HsEnv#handshake_env{ + stapling_state = + StaplingState#{status => received_staple, + staple => CertStatus}}}}; %% Server did not send OCSP staple message -wait_ocsp_stapling(internal, Msg, +wait_stapling(internal, Msg, #state{static_env = #static_env{protocol_cb = _Connection}, handshake_env = #handshake_env{ - ocsp_stapling_state = OcspState} = HsEnv} = State) + stapling_state = StaplingState} = HsEnv} = State) when is_record(Msg, server_key_exchange) orelse is_record(Msg, hello_request) orelse is_record(Msg, certificate_request) orelse @@ -292,12 +293,12 @@ wait_ocsp_stapling(internal, Msg, is_record(Msg, client_key_exchange) -> {next_state, certify, State#state{handshake_env = - HsEnv#handshake_env{ocsp_stapling_state = - OcspState#{ocsp_expect => undetermined}}}, + HsEnv#handshake_env{stapling_state = + StaplingState#{status => not_received}}}, [{postpone, true}]}; -wait_ocsp_stapling(internal, #hello_request{}, _) -> +wait_stapling(internal, #hello_request{}, _) -> keep_state_and_data; -wait_ocsp_stapling(Type, Event, State) -> +wait_stapling(Type, Event, State) -> ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State). %%-------------------------------------------------------------------- @@ -329,8 +330,8 @@ certify(internal, #certificate{}, certify(internal, #certificate{}, #state{static_env = #static_env{protocol_cb = Connection}, handshake_env = #handshake_env{ - ocsp_stapling_state = #{ocsp_expect := staple}}} = State) -> - Connection:next_event(wait_ocsp_stapling, no_record, State, [{postpone, true}]); + stapling_state = #{status := negotiated}}} = State) -> + Connection:next_event(wait_stapling, no_record, State, [{postpone, true}]); certify(internal, #certificate{asn1_certificates = [Peer|_]} = Cert, #state{static_env = #static_env{ role = Role, @@ -340,14 +341,18 @@ certify(internal, #certificate{asn1_certificates = [Peer|_]} = Cert, cert_db_ref = CertDbRef, crl_db = CRLDbInfo}, handshake_env = #handshake_env{ - ocsp_stapling_state = #{ocsp_expect := Status} = OcspState}, + stapling_state = #{status := StaplingStatus} = + StaplingState}, connection_env = #connection_env{ negotiated_version = Version}, - ssl_options = Opts} = State0) when Status =/= staple -> - OcspInfo = ocsp_info(OcspState, Opts, Peer), + ssl_options = Opts} = State0) + when StaplingStatus == not_negotiated; StaplingStatus == received_staple -> + %% this clause handles also scenario with stapling disabled, so + %% 'not_negotiated' appears in guard + ExtInfo = ext_info(StaplingState, Peer), case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, Opts, CRLDbInfo, Role, Host, - ensure_tls(Version), OcspInfo) of + ensure_tls(Version), ExtInfo) of {PeerCert, PublicKeyInfo} -> State = case Role of server -> @@ -1667,16 +1672,13 @@ ensure_tls(Version) when ?DTLS_1_X(Version) -> ensure_tls(Version) -> Version. -ocsp_info(#{ocsp_expect := stapled, ocsp_response := CertStatus} = OcspState, - #{ocsp_stapling := OcspStapling} = _SslOpts, PeerCert) -> - #{ocsp_responder_certs := OcspResponderCerts} = OcspStapling, +ext_info(#{status := received_staple, staple := CertStatus} = StaplingState, + PeerCert) -> #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => [CertStatus]}, - ocsp_responder_certs => OcspResponderCerts, - ocsp_state => OcspState}; -ocsp_info(#{ocsp_expect := no_staple} = OcspState, _, PeerCert) -> + stapling_state => StaplingState}; +ext_info(#{status := not_negotiated} = StaplingState, PeerCert) -> #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => []}, - ocsp_responder_certs => [], - ocsp_state => OcspState}. + stapling_state => StaplingState}. select_client_cert_key_pair(Session0,_, [#{private_key := NoKey, certs := [[]] = NoCerts}], @@ -1727,5 +1729,5 @@ default_cert_key_pair_return(Default, _) -> %%%# Tracing %%%# handle_trace(csp, - {call, {?MODULE, wait_ocsp_stapling, [Type, Msg | _]}}, Stack) -> + {call, {?MODULE, wait_stapling, [Type, Msg | _]}}, Stack) -> {io_lib:format("Type = ~w Msg = ~W", [Type, Msg, 10]), Stack}. diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl index 7a64bc7db3..95d4f76878 100644 --- a/lib/ssl/src/tls_gen_connection.erl +++ b/lib/ssl/src/tls_gen_connection.erl @@ -399,7 +399,7 @@ handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA}, StateName, StateName == hello; StateName == certify; StateName == wait_cert_verify; - StateName == wait_ocsp_stapling; + StateName == wait_stapling; StateName == abbreviated; StateName == cipher -> diff --git a/lib/ssl/src/tls_gen_connection_1_3.erl b/lib/ssl/src/tls_gen_connection_1_3.erl index 22df97f066..c92a35a5aa 100644 --- a/lib/ssl/src/tls_gen_connection_1_3.erl +++ b/lib/ssl/src/tls_gen_connection_1_3.erl @@ -54,6 +54,7 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trackers}, User, {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) -> + put(log_level, maps:get(log_level, SSLOptions)), %% Use highest supported version for client/server random nonce generation #{versions := [Version|_]} = SSLOptions, MaxEarlyDataSize = init_max_early_data_size(Role), diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index 76337e3e26..397ce18c36 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -161,7 +161,6 @@ hello(#server_hello{server_version = LegacyVersion, selected_version = Version}} = HelloExt}, #{versions := SupportedVersions} = SslOpt, ConnectionStates0, Renegotiation, OldId) -> - Stapling = maps:get(ocsp_stapling, SslOpt, ?DEFAULT_OCSP_STAPLING), %% In TLS 1.3, the TLS server indicates its version using the "supported_versions" extension %% (Section 4.2.1), and the legacy_version field MUST be set to 0x0303, which is the version %% number for TLS 1.2. @@ -181,10 +180,12 @@ hello(#server_hello{server_version = LegacyVersion, HelloExt, SslOpt, ConnectionStates0, Renegotiation, IsNew); SelectedVersion -> - %% TLS 1.3 + %% TLS 1.3 status_request and OCSP + %% responses provided in Certificate + %% messages {next_state, wait_sh, SelectedVersion, - #{ocsp_stapling => Stapling, - ocsp_expect => ocsp_expect(Stapling)}} + #{configured => maps:is_key(stapling, SslOpt), + status => not_negotiated}} end; false -> throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) @@ -310,7 +311,7 @@ get_tls_handshakes(Version, Data, Buffer, Options) -> %% Description: Get an OCSP nonce %%-------------------------------------------------------------------- ocsp_nonce(SslOpts) -> - case maps:get(ocsp_stapling, SslOpts, disabled) of + case maps:get(stapling, SslOpts, disabled) of #{ocsp_nonce := true} -> public_key:der_encode('Nonce', crypto:strong_rand_bytes(8)); _ -> @@ -386,12 +387,12 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, HelloExt, SslOpt, ConnectionStates0, Renegotiation, IsNew) -> - {ConnectionStates, ProtoExt, Protocol, OcspState} = + {ConnectionStates, ProtoExt, Protocol, StaplingState} = ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite, HelloExt, Version, SslOpt, ConnectionStates0, Renegotiation, IsNew), - {Version, SessionId, ConnectionStates, ProtoExt, Protocol, OcspState}. + {Version, SessionId, ConnectionStates, ProtoExt, Protocol, StaplingState}. do_hello(undefined, _Versions, _CipherSuites, _Hello, _SslOpts, _Info, _Renegotiation) -> throw(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)); @@ -475,12 +476,6 @@ decode_handshake(?TLS_1_3, Tag, Msg) -> decode_handshake(Version, Tag, Msg) -> ssl_handshake:decode_handshake(Version, Tag, Msg). - -ocsp_expect(true) -> - staple; -ocsp_expect(_) -> - no_staple. - get_signature_ext(Ext, HelloExt, ?TLS_1_2) -> case maps:get(Ext, HelloExt, undefined) of %% Signature algorithms was not sent diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index 3eb692d507..ec98148e0e 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -450,9 +450,10 @@ process_certificate(#certificate_1_3{certificate_list = CertEntries}, crl_db = CRLDbHandle}, handshake_env = #handshake_env{ - ocsp_stapling_state = OcspState}} = State0) -> - case validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef, - SslOptions, CRLDbHandle, Role, Host, OcspState) of + stapling_state = StaplingState}} = State0) -> + case validate_certificate_chain( + CertEntries, CertDbHandle, CertDbRef, SslOptions, CRLDbHandle, Role, + Host, StaplingState) of #alert{} = Alert -> State = update_encryption_state(Role, State0), {error, {Alert, State}}; @@ -805,42 +806,35 @@ update_encryption_state(client, State) -> validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef, - SslOptions, CRLDbHandle, Role, Host, OcspState0) -> - {Certs, CertExt, OcspState} = split_cert_entries(CertEntries, OcspState0), - OcspResponderCerts = - case maps:get(ocsp_stapling, SslOptions, disabled) of - #{ocsp_responder_certs := V} -> - V; - disabled -> - ?DEFAULT_OCSP_RESPONDER_CERTS - end, - ssl_handshake:certify(#certificate{asn1_certificates = Certs}, CertDbHandle, CertDbRef, - SslOptions, CRLDbHandle, Role, Host, ?TLS_1_3, - #{cert_ext => CertExt, - ocsp_state => OcspState, - ocsp_responder_certs => OcspResponderCerts}). + SslOptions, CRLDbHandle, Role, Host, StaplingState) -> + {Certs, ExtInfo} = split_cert_entries(CertEntries, StaplingState, [], #{}), + ssl_handshake:certify(#certificate{asn1_certificates = Certs}, CertDbHandle, + CertDbRef, SslOptions, CRLDbHandle, Role, Host, ?TLS_1_3, + ExtInfo). store_peer_cert(#state{session = Session, handshake_env = HsEnv} = State, PeerCert, PublicKeyInfo) -> State#state{session = Session#session{peer_certificate = PeerCert}, handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo}}. -split_cert_entries(CertEntries, OcspState) -> - split_cert_entries(CertEntries, OcspState, [], #{}). - -split_cert_entries([], OcspState, Chain, Ext) -> - {lists:reverse(Chain), Ext, OcspState}; -split_cert_entries([#certificate_entry{data = DerCert, extensions = Extensions0} | CertEntries], - OcspState0, Chain, Ext) -> +split_cert_entries([], StaplingState, Chain, CertExt) -> + {lists:reverse(Chain), #{cert_ext => CertExt, + stapling_state => StaplingState}}; +split_cert_entries([#certificate_entry{data = DerCert, + extensions = Extensions0} | CertEntries], + #{configured := StaplingConfigured} = StaplingState0, Chain, + CertExt) -> Id = public_key:pkix_subject_id(DerCert), Extensions = [ExtValue || {_, ExtValue} <- maps:to_list(Extensions0)], - OcspState = case maps:get(status_request, Extensions0, undefined) of - undefined -> - OcspState0; - _ -> - OcspState0#{ocsp_expect => stapled} - end, - split_cert_entries(CertEntries, OcspState, [DerCert | Chain], Ext#{Id => Extensions}). + StaplingState = case {maps:get(status_request, Extensions0, undefined), + StaplingConfigured} of + {undefined, _} -> + StaplingState0; + {_, true} -> + StaplingState0#{status => received_staple} + end, + split_cert_entries(CertEntries, StaplingState, [DerCert | Chain], + CertExt#{Id => Extensions}). %% 4.4.1. The Transcript Hash %% @@ -1882,8 +1876,7 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR signature_algs := SignAlgos, signature_algs_cert := SignAlgosCert} = Opts, #{cert_ext := CertExt, - ocsp_responder_certs := OcspResponderCerts, - ocsp_state := OcspState}) -> + stapling_state := StaplingState}) -> ValidationFunAndState = ssl_handshake:validation_fun_and_state(VerifyFun, #{role => Role, @@ -1900,8 +1893,7 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR version => Version, issuer => TrustedCert, cert_ext => CertExt, - ocsp_responder_certs => OcspResponderCerts, - ocsp_state => OcspState, + stapling_state => StaplingState, path_len => length(Path) }, Path, LogLevel), diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile index d8bb5b5913..d1d6f59ba6 100644 --- a/lib/ssl/test/Makefile +++ b/lib/ssl/test/Makefile @@ -102,7 +102,7 @@ MODULES = \ inet_epmd_dist_cryptcookie_inet \ inet_epmd_dist_cryptcookie_socket \ inet_epmd_cryptcookie_socket_ktls \ - openssl_ocsp_SUITE \ + openssl_stapling_SUITE \ tls_server_session_ticket_SUITE \ tls_client_ticket_store_SUITE diff --git a/lib/ssl/test/make_certs.erl b/lib/ssl/test/make_certs.erl index e786cddeb6..e06ffc9be4 100644 --- a/lib/ssl/test/make_certs.erl +++ b/lib/ssl/test/make_certs.erl @@ -24,19 +24,20 @@ %-export([all/1, all/2, rootCA/2, intermediateCA/3, endusers/3, enduser/3, revoke/3, gencrl/2, verify/3]). -record(config, {commonName, - organizationalUnitName = "Erlang OTP", - organizationName = "Ericsson AB", - localityName = "Stockholm", - countryName = "SE", - emailAddress = "peter@erix.ericsson.se", - default_bits = 2048, - v2_crls = true, - ecc_certs = false, - issuing_distribution_point = false, - crldp_crlissuer = false, - crl_port = 8000, - openssl_cmd = "openssl", - hostname = "host.example.com"}). + organizationalUnitName = "Erlang OTP", + organizationName = "Ericsson AB", + localityName = "Stockholm", + countryName = "SE", + emailAddress = "peter@erix.ericsson.se", + default_bits = 2048, + v2_crls = true, + ecc_certs = false, + issuing_distribution_point = false, + crldp_crlissuer = false, + crl_port = 8000, + openssl_cmd = "openssl", + hostname = "host.example.com", + cert_profile = "user_cert"}). default_config() -> @@ -88,7 +89,10 @@ all(DataDir, PrivDir, C = #config{}) -> create_rnd(DataDir, PrivDir), % For all requests rootCA(PrivDir, "erlangCA", C), intermediateCA(PrivDir, "otpCA", "erlangCA", C), - endusers(PrivDir, "otpCA", ["client", "server", "revoked", "undetermined", "a.server", "b.server"], C), + endusers(PrivDir, "otpCA", ["client", "server", "revoked", "undetermined", + "a.server"], C), + endusers(PrivDir, "otpCA", ["b.server"], + C#config{cert_profile="user_cert_ocsp_signing"}), endusers(PrivDir, "erlangCA", ["localhost"], C), %% Create keycert files SDir = filename:join([PrivDir, "server"]), @@ -165,7 +169,7 @@ enduser(Root, CA, User, C) -> create_req(Root, CnfFile, KeyFile, ReqFile, C), %create_req(Root, CnfFile, KeyFile, ReqFile), CertFileAllUsage = filename:join([UsrRoot, "cert.pem"]), - sign_req(Root, CA, "user_cert", ReqFile, CertFileAllUsage, C), + sign_req(Root, CA, C#config.cert_profile, ReqFile, CertFileAllUsage, C), CertFileDigitalSigOnly = filename:join([UsrRoot, "digital_signature_only_cert.pem"]), sign_req(Root, CA, "user_cert_digital_signature_only", ReqFile, CertFileDigitalSigOnly, C), CACertsFile = filename:join(UsrRoot, "cacerts.pem"), @@ -651,6 +655,14 @@ ca_cnf( "issuerAltName = issuer:copy\n" %"crlDistributionPoints=@crl_section\n" + "[user_cert_ocsp_signing]\n" + "basicConstraints = CA:false\n" + "keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n" + "extendedKeyUsage = OCSPSigning\n" + "subjectKeyIdentifier = hash\n" + "authorityKeyIdentifier = keyid,issuer:always\n" + "subjectAltName = DNS.1:" ++ Hostname ++ "\n" + "issuerAltName = issuer:copy\n" %%"[crl_section]\n" %% intentionally invalid %%"URI.1=http://localhost/",C#config.commonName,"/crl.pem\n" diff --git a/lib/ssl/test/openssl_ocsp_SUITE.erl b/lib/ssl/test/openssl_stapling_SUITE.erl similarity index 54% rename from lib/ssl/test/openssl_ocsp_SUITE.erl rename to lib/ssl/test/openssl_stapling_SUITE.erl index 045915cc84..e4a19aaf89 100644 --- a/lib/ssl/test/openssl_ocsp_SUITE.erl +++ b/lib/ssl/test/openssl_stapling_SUITE.erl @@ -18,7 +18,7 @@ %% %CopyrightEnd% %% --module(openssl_ocsp_SUITE). +-module(openssl_stapling_SUITE). -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). @@ -35,45 +35,57 @@ end_per_testcase/2]). %% Testcases --export([stapling_basic/0, stapling_basic/1, - stapling_with_nonce/0, stapling_with_nonce/1, - stapling_with_responder_cert/0, stapling_with_responder_cert/1, - stapling_revoked/0, stapling_revoked/1, - stapling_undetermined/0, stapling_undetermined/1, - stapling_no_staple/0, stapling_no_staple/1 +-export([staple_by_issuer/0, staple_by_issuer/1, + staple_by_designated/0, staple_by_designated/1, + staple_by_trusted/0, staple_by_trusted/1, + staple_not_designated/0, staple_not_designated/1, + staple_wrong_issuer/0, staple_wrong_issuer/1, + staple_with_nonce/0, staple_with_nonce/1, + cert_status_revoked/0, cert_status_revoked/1, + cert_status_undetermined/0, cert_status_undetermined/1, + staple_missing/0, staple_missing/1 ]). %% spawn export --export([ocsp_responder_init/4]). +-export([ocsp_responder_init/3]). -define(OCSP_RESPONDER_LOG, "ocsp_resp_log.txt"). -define(DEBUG, false). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -all() -> +all() -> [{group, 'tlsv1.3'}, + {group, no_next_update}, {group, 'tlsv1.2'}, {group, 'dtlsv1.2'}]. -groups() -> +groups() -> [{'tlsv1.3', [], ocsp_tests()}, + {no_next_update, [], [{group, 'tlsv1.3'}]}, {'tlsv1.2', [], ocsp_tests()}, {'dtlsv1.2', [], ocsp_tests()}]. ocsp_tests() -> - [stapling_basic, - stapling_with_nonce, - stapling_with_responder_cert, - stapling_revoked, - stapling_undetermined, - stapling_no_staple - ]. + positive() ++ negative(). + +positive() -> + [staple_by_issuer, + staple_by_designated, + staple_by_trusted, + staple_with_nonce]. + +negative() -> + [staple_not_designated, + staple_wrong_issuer, + cert_status_revoked, + cert_status_undetermined, + staple_missing]. %%-------------------------------------------------------------------- init_per_suite(Config0) -> - Config = lists:merge([{debug, ?DEBUG}], - ssl_test_lib:init_per_suite(Config0, openssl)), + Config = [{debug, ?DEBUG}] ++ + ssl_test_lib:init_per_suite(Config0, openssl), case ssl_test_lib:openssl_ocsp_support(Config) of true -> do_init_per_suite(Config); @@ -82,72 +94,103 @@ init_per_suite(Config0) -> end. do_init_per_suite(Config) -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - - %% Prepare certs - {ok, _} = make_certs:all(DataDir, PrivDir), - - ResponderPort = get_free_port(), - Pid = start_ocsp_responder(ResponderPort, PrivDir, ?config(debug, Config)), - - NewConfig = - lists:merge( - [{responder_port, ResponderPort}, - {responder_pid, Pid} - ], Config), - - ssl_test_lib:cert_options(NewConfig). + {ok, _} = make_certs:all(?config(data_dir, Config), + ?config(priv_dir, Config)), + ssl_test_lib:cert_options(Config). end_per_suite(Config) -> - ResponderPid = proplists:get_value(responder_pid, Config), - ssl_test_lib:close(ResponderPid), - [ssl_test_lib:ct_pal_file(?OCSP_RESPONDER_LOG) || ?config(debug, Config)], ssl_test_lib:end_per_suite(Config). %%-------------------------------------------------------------------- +init_per_group(no_next_update, Config) -> + Config; init_per_group(GroupName, Config) -> ssl_test_lib:init_per_group_openssl(GroupName, Config). +end_per_group(no_next_update, Config) -> + Config; end_per_group(GroupName, Config) -> ssl_test_lib:end_per_group(GroupName, Config). %%-------------------------------------------------------------------- -init_per_testcase(_TestCase, Config) -> +init_per_testcase(staple_by_trusted = Testcase, Config) -> + PrivDir = ?config(priv_dir, Config), + ok = public_key:cacerts_load(filename:join(PrivDir, "otpCA/cacerts.pem")), + init_per_testcase_helper(Testcase, Config); +init_per_testcase(Testcase, Config) -> + init_per_testcase_helper(Testcase, Config). + +init_per_testcase_helper(Testcase, Config0) -> ct:timetrap({seconds, 10}), + Default = "otpCA", + TestcaseMapping = #{staple_by_issuer => Default, + staple_by_trusted => "erlangCA", + staple_by_designated => "b.server", + staple_not_designated => "a.server", + staple_wrong_issuer => "localhost"}, + ResponderFolder = maps:get(Testcase, TestcaseMapping, Default), + Config = start_ocsp_responder( + [{responder_folder, ResponderFolder} | Config0]) ++ Config0, ssl_test_lib:ct_log_supported_protocol_versions(Config), Config. -end_per_testcase(_TestCase, Config) -> +end_per_testcase(staple_by_trusted, Config) -> + public_key:cacerts_load(), + end_per_testcase_helper(Config); +end_per_testcase(_Testcase, Config) -> + end_per_testcase_helper(Config). + +end_per_testcase_helper(Config) -> + ResponderPid = ?config(responder_pid, Config), + ssl_test_lib:close(ResponderPid), + [ssl_test_lib:ct_pal_file(?OCSP_RESPONDER_LOG) || ?config(debug, Config)], Config. %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- -stapling_basic() -> - [{doc, "Verify OCSP stapling works without nonce and responder certs."}]. -stapling_basic(Config) +%% Test various certs used for signing OCSP response +%% Assuming Issuer issued a.server certificate used by TLS server +%% 1. otpCA - [OK] Issuer signs response directly +%% 2. b.server - [OK] Responder certificate issued directly by Issuer +%% and designated for OCSP signing +%% 3 localhost - [OK] Certificate not issued by Issuer, but present in trust store +%% 4. a.server - [NOK] Certificate signed directly by Issuer but not designated +%% 5. localhost - [NOK] Certificate not issued by Issuer + +staple_by_issuer() -> + [{doc, "Verify OCSP stapling works without nonce." + "Response signed directly by issuer of server certificate"}]. +staple_by_issuer(Config) when is_list(Config) -> - stapling_helper(Config, [{ocsp_nonce, false}]). - -stapling_with_nonce() -> - [{doc, "Verify OCSP stapling works with nonce."}]. -stapling_with_nonce(Config) + stapling_helper(Config, #{ocsp_nonce => false}). + +staple_by_designated() -> + [{doc,"Verify OCSP stapling works without nonce." + "Response signed with certificate issued directly by issuer of server " + "certificate and is designated for OCSP signing (extKeyUsage allows " + "for OCSP signing)."}]. +staple_by_designated(Config) + when is_list(Config) -> + stapling_helper(Config, #{ocsp_nonce => false}). + +staple_by_trusted() -> + [{doc,"Verify OCSP stapling works without nonce." + "Response signed with certificate issued directly by issuer of server " + "certificate and is designated for OCSP signing (extKeyUsage allows " + "for OCSP signing)."}]. +staple_by_trusted(Config) when is_list(Config) -> - stapling_helper(Config, [{ocsp_nonce, true}]). + stapling_helper(Config, #{ocsp_nonce => false}). -stapling_with_responder_cert() -> - [{doc, "Verify OCSP stapling works with nonce and responder certs."}]. -stapling_with_responder_cert(Config) +staple_with_nonce() -> + [{doc, "Verify OCSP stapling works with nonce."}]. +staple_with_nonce(Config) when is_list(Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - {ok, ResponderCert} = - file:read_file(filename:join(PrivDir, "b.server/cert.pem")), - [{'Certificate', Der, _IsEncrypted}] = - public_key:pem_decode(ResponderCert), - stapling_helper(Config, [{ocsp_nonce, true}, {ocsp_responder_certs, [Der]}]). + stapling_helper(Config, #{ocsp_nonce => true}). -stapling_helper(Config, Opts) -> +stapling_helper(Config, StaplingOpt) -> + %% ok = logger:set_application_level(ssl, debug), PrivDir = proplists:get_value(priv_dir, Config), CACertsFile = filename:join(PrivDir, "a.server/cacerts.pem"), Data = "ping", %% 4 bytes @@ -160,7 +203,7 @@ stapling_helper(Config, Opts) -> ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, {cacertfile, CACertsFile}, {server_name_indication, disable}, - {ocsp_stapling, true}] ++ Opts, + {stapling, StaplingOpt}], Config), Client = ssl_test_lib:start_client(erlang, [{port, Port}, @@ -170,24 +213,44 @@ stapling_helper(Config, Opts) -> Data = ssl_test_lib:check_active_receive(Client, Data), ssl_test_lib:close(Server), ssl_test_lib:close(Client). + %%-------------------------------------------------------------------- -stapling_revoked() -> +staple_not_designated() -> + [{doc,"Verify OCSP stapling works without nonce." + "Response signed with certificate issued directly by issuer of server " + "certificate but not designated for OCSP signing (extKeyUsage missing " + "OCSP signing)."}]. +staple_not_designated(Config) + when is_list(Config) -> + stapling_negative_helper(Config, "a.server/cacerts.pem", + openssl_ocsp, bad_certificate). + +staple_wrong_issuer() -> + [{doc,"Verify OCSP stapling works without nonce." + "Response signed with certificate not related to issuer of server " + "certificate."}]. +staple_wrong_issuer(Config) + when is_list(Config) -> + stapling_negative_helper(Config, "a.server/cacerts.pem", + openssl_ocsp, bad_certificate). + +cert_status_revoked() -> [{doc, "Verify OCSP stapling works with revoked certificate."}]. -stapling_revoked(Config) +cert_status_revoked(Config) when is_list(Config) -> stapling_negative_helper(Config, "revoked/cacerts.pem", openssl_ocsp_revoked, certificate_revoked). -stapling_undetermined() -> +cert_status_undetermined() -> [{doc, "Verify OCSP stapling works with certificate with undetermined status."}]. -stapling_undetermined(Config) +cert_status_undetermined(Config) when is_list(Config) -> stapling_negative_helper(Config, "undetermined/cacerts.pem", openssl_ocsp_undetermined, bad_certificate). -stapling_no_staple() -> +staple_missing() -> [{doc, "Verify OCSP stapling works with a missing OCSP response."}]. -stapling_no_staple(Config) +staple_missing(Config) when is_list(Config) -> %% Start a server that will not include an OCSP response. stapling_negative_helper(Config, "a.server/cacerts.pem", @@ -206,9 +269,8 @@ stapling_negative_helper(Config, CACertsPath, ServerVariant, ExpectedError) -> ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, {server_name_indication, disable}, {cacertfile, CACertsFile}, - {ocsp_stapling, true}, - {ocsp_nonce, true} - ], Config), + {stapling, #{ocsp_nonce => true}}], + Config), Client = ssl_test_lib:start_client_error([{node, ClientNode},{port, Port}, {host, Hostname}, {from, self()}, {options, ClientOpts}]), @@ -218,30 +280,42 @@ stapling_negative_helper(Config, CACertsPath, ServerVariant, ExpectedError) -> %%-------------------------------------------------------------------- %% Internal functions ----------------------------------------------- %%-------------------------------------------------------------------- -start_ocsp_responder(ResponderPort, PrivDir, Debug) -> +start_ocsp_responder(Config) -> + ResponderPort = get_free_port(), Starter = self(), - Pid = erlang:spawn( - ?MODULE, ocsp_responder_init, - [ResponderPort, PrivDir, Starter, Debug]), + process_flag(trap_exit, true), + Pid = erlang:spawn_link(?MODULE, ocsp_responder_init, + [ResponderPort, Starter, Config]), receive {started, Pid} -> - Pid; + [{responder_port, ResponderPort}, {responder_pid, Pid}]; {'EXIT', Pid, Reason} -> throw({unable_to_start_ocsp_service, Reason}) end. -ocsp_responder_init(ResponderPort, PrivDir, Starter, Debug) -> +ocsp_responder_init(ResponderPort, Starter, Config) -> + ResponderFolder = ?config(responder_folder, Config), + PrivDir = ?config(priv_dir, Config), Index = filename:join(PrivDir, "otpCA/index.txt"), - CACerts = filename:join(PrivDir, "b.server/cacerts.pem"), - Cert = filename:join(PrivDir, "b.server/cert.pem"), - Key = filename:join(PrivDir, "b.server/key.pem"), - DebugArgs = case Debug of + CACerts = filename:join(PrivDir, "otpCA/cacerts.pem"), + Cert = filename:join(PrivDir, ResponderFolder ++ "/cert.pem"), + %% search for key.pem file, since generated intermediate CAs + %% "hide" their key.pem inside "private" subfolder + [Key] = filelib:fold_files(filename:join(PrivDir, ResponderFolder), + "key.pem", true, fun(X, Acc) -> [X | Acc] end, []), + Debug = case ?config(debug, Config) of true -> ["-text", "-out", ?OCSP_RESPONDER_LOG]; _ -> [] end, + NextUpdate = case ?config(tc_group_path, Config) of + [[{name,no_next_update}]] -> + []; + _ -> + ["-nmin", "5"] + end, Args = ["ocsp", "-index", Index, "-CA", CACerts, "-rsigner", Cert, "-rkey", Key, "-port", erlang:integer_to_list(ResponderPort)] ++ - DebugArgs, + Debug ++ NextUpdate, process_flag(trap_exit, true), Port = ssl_test_lib:portable_open_port("openssl", Args), ?CT_LOG("OCSP responder: Started Port = ~p", [Port]), diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl index 79b64cfa5d..31b3f9c3e1 100644 --- a/lib/ssl/test/ssl_api_SUITE.erl +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -2386,7 +2386,7 @@ options_whitebox(Config) when is_list(Config) -> options_renegotiate(Config), options_middlebox(Config), options_frag_len(Config), - options_oscp(Config), + options_stapling(Config), options_padding(Config), options_identity(Config), options_reuse_session(Config), @@ -2940,31 +2940,25 @@ options_frag_len(_Config) -> %% max_fragment_length ?ERR({max_fragment_length,2000}, [{max_fragment_length, 2000}], client), ok. -options_oscp(Config) -> - Cert = proplists:get_value( - cert, ssl_test_lib:ssl_options(server_rsa_der_opts, Config)), - NoOcspOption = [ocsp_stapling, ocsp_nonce, ocsp_responder_certs], - - ?OK(#{}, [], client, NoOcspOption), - ?OK(#{}, [{ocsp_stapling, false}], client, NoOcspOption), - ?OK(#{ocsp_stapling := #{ocsp_nonce := true, ocsp_responder_certs := []}}, - [{ocsp_stapling, true}], client, [ocsp_nonce, ocsp_responder_certs]), - ?OK(#{ocsp_stapling := - #{ocsp_nonce := false, ocsp_responder_certs := [_, _]}}, - [{ocsp_stapling, true}, {ocsp_nonce, false}, - {ocsp_responder_certs, [Cert,Cert]}], - client, [ocsp_nonce, ocsp_responder_certs]), +options_stapling(_Config) -> + ?OK(#{}, [], client, [stapling]), + ?OK(#{}, [{stapling, no_staple}], client, [stapling]), + + ?OK(#{stapling := #{ocsp_nonce := true}}, + [{stapling, staple}], client), + ?OK(#{stapling := #{ocsp_nonce := true}}, + [{stapling, #{}}], client), + ?OK(#{stapling := #{ocsp_nonce := true}}, + [{stapling, #{ocsp_nonce => true}}], client), + + ?OK(#{stapling := #{ocsp_nonce := false}}, + [{stapling, #{ocsp_nonce => false}}], client), + %% Errors - ?ERR({ocsp_stapling, foo}, [{ocsp_stapling, 'foo'}], client), - ?ERR({ocsp_nonce, foo}, [{ocsp_nonce, 'foo'}], client), - ?ERR({ocsp_responder_certs, foo}, [{ocsp_responder_certs, 'foo'}], client), - ?ERR({options, incompatible, [{ocsp_nonce, false}, {ocsp_stapling, false}]}, - [{ocsp_nonce, false}], client), - ?ERR({options, incompatible, [ocsp_responder_certs, {ocsp_stapling, false}]}, - [{ocsp_responder_certs, [Cert]}], server), - ?ERR({ocsp_responder_certs, [_]}, - [{ocsp_stapling, true}, {ocsp_responder_certs, ['NOT A BINARY']}], - client), + ?ERR({stapling, foo}, [{stapling, 'foo'}], client), + ?ERR({option, client_only, stapling}, [{stapling, true}], server), + ?ERR({option, client_only, stapling}, [{stapling, false}], server), + ?ERR({option, client_only, stapling}, [{stapling, #{}}], server), ok. options_padding(_Config) -> diff --git a/lib/ssl/test/ssl_gh.spec b/lib/ssl/test/ssl_gh.spec index 6942caa68e..1df89d2d01 100644 --- a/lib/ssl/test/ssl_gh.spec +++ b/lib/ssl/test/ssl_gh.spec @@ -2,7 +2,6 @@ {alias,dir,"../ssl_test"}. {suites,dir,all}. -{skip_suites,dir,[openssl_ocsp_SUITE],"Unstable testcases"}. {skip_groups,all_nodes, dir, openssl_session_ticket_SUITE, 'openssl_server', {cases,[openssl_server_hrr]},"Unstable testcases"}. {skip_groups,dir,ssl_bench_SUITE,setup,"Benchmarks run separately"}. diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 0cb75719ef..608851e0bd 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -424,6 +424,8 @@ openssl_ocsp_support(Config) -> case proplists:get_value(openssl_version, Config) of "OpenSSL 1.1.1" ++ _Rest -> true; + "OpenSSL 3" ++ _Rest -> + true; _ -> false end. -- 2.35.3
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor