diff options
Diffstat (limited to 'lib/ssl/src/tls_handshake_1_3.erl')
-rw-r--r-- | lib/ssl/src/tls_handshake_1_3.erl | 1299 |
1 files changed, 964 insertions, 335 deletions
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index c0c47c26c4..cd9beecb3f 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -31,6 +31,7 @@ -include("ssl_connection.hrl"). -include("ssl_internal.hrl"). -include("ssl_record.hrl"). +-include("tls_record_1_3.hrl"). -include_lib("public_key/include/public_key.hrl"). %% Encode @@ -50,12 +51,19 @@ do_wait_sh/2, do_wait_ee/2, do_wait_cert_cr/2, + do_wait_eoed/2, + early_data_size/1, get_ticket_data/3, maybe_add_binders/3, maybe_add_binders/4, - maybe_automatic_session_resumption/1]). + maybe_add_early_data_indication/3, + maybe_automatic_session_resumption/1, + maybe_send_early_data/1, + update_current_read/3]). --export([is_valid_binder/4]). +-export([get_max_early_data/1, + is_valid_binder/4, + maybe/0]). %% crypto:hash(sha256, "HelloRetryRequest"). -define(HELLO_RETRY_REQUEST_RANDOM, <<207,33,173,116,229,154,97,17, @@ -115,15 +123,82 @@ server_hello_random(server_hello, #security_parameters{server_random = Random}) server_hello_random(hello_retry_request, _) -> ?HELLO_RETRY_REQUEST_RANDOM. +maybe_add_cookie_extension(#state{ssl_options = #{cookie := false}} = State, + ServerHello) -> + {State, ServerHello}; +maybe_add_cookie_extension(#state{connection_states = ConnectionStates, + ssl_options = #{cookie := true}, + handshake_env = + #handshake_env{ + tls_handshake_history = + {[CH1|_], _}} = HsEnv0} = State, + #server_hello{extensions = Extensions0} = ServerHello) -> + HKDFAlgo = get_hkdf_algorithm(ConnectionStates), + MessageHash0 = message_hash(CH1, HKDFAlgo), + MessageHash = iolist_to_binary(MessageHash0), + + %% Encrypt MessageHash + IV = crypto:strong_rand_bytes(16), + Shard = crypto:strong_rand_bytes(32), + Cookie = ssl_cipher:encrypt_data(<<"cookie">>, MessageHash, Shard, IV), + + HsEnv = HsEnv0#handshake_env{cookie_iv_shard = {IV, Shard}}, + Extensions = Extensions0#{cookie => #cookie{cookie = Cookie}}, + {State#state{handshake_env = HsEnv}, + ServerHello#server_hello{extensions = Extensions}}; +maybe_add_cookie_extension(undefined, ClientHello) -> + ClientHello; +maybe_add_cookie_extension(Cookie, + #client_hello{extensions = Extensions0} = ClientHello) -> + Extensions = Extensions0#{cookie => #cookie{cookie = Cookie}}, + ClientHello#client_hello{extensions = Extensions}. + +validate_cookie(_Cookie, #state{ssl_options = #{cookie := false}}) -> + ok; +validate_cookie(undefined, #state{ssl_options = #{cookie := true}}) -> + ok; +validate_cookie(Cookie0, #state{ssl_options = #{cookie := true}, + handshake_env = + #handshake_env{ + tls_handshake_history = + {[_CH2,_HRR,MessageHash|_], _}, + cookie_iv_shard = {IV, Shard}}}) -> + Cookie = ssl_cipher:decrypt_data(<<"cookie">>, Cookie0, Shard, IV), + case Cookie =:= iolist_to_binary(MessageHash) of + true -> + ok; + false -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)} + end. -encrypted_extensions(#state{handshake_env = #handshake_env{alpn = undefined}}) -> - #encrypted_extensions{ - extensions = #{} - }; -encrypted_extensions(#state{handshake_env = #handshake_env{alpn = ALPNProtocol}}) -> - Extensions = ssl_handshake:add_alpn(#{}, ALPNProtocol), +encrypted_extensions(#state{handshake_env = HandshakeEnv}) -> + E0 = #{}, + E1 = case HandshakeEnv#handshake_env.alpn of + undefined -> + E0; + ALPNProtocol -> + ssl_handshake:add_alpn(#{}, ALPNProtocol) + end, + E2 = case HandshakeEnv#handshake_env.max_frag_enum of + undefined -> + E1; + MaxFragEnum -> + E1#{max_frag_enum => MaxFragEnum} + end, + E3 = case HandshakeEnv#handshake_env.sni_guided_cert_selection of + false -> + E2; + true -> + E2#{sni => #sni{hostname = ""}} + end, + E = case HandshakeEnv#handshake_env.early_data_accepted of + false -> + E3; + true -> + E3#{early_data => #early_data_indication{}} + end, #encrypted_extensions{ - extensions = Extensions + extensions = E }. @@ -179,7 +254,11 @@ filter_tls13_algs(Algo) -> %% opaque certificate_request_context<0..2^8-1>; %% CertificateEntry certificate_list<0..2^24-1>; %% } Certificate; -certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, Role) -> +certificate(undefined, _, _, _, client) -> + {ok, #certificate_1_3{ + certificate_request_context = <<>>, + certificate_list = []}}; +certificate([OwnCert], CertDbHandle, CertDbRef, _CRContext, Role) -> case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of {ok, _, Chain} -> CertList = chain_to_cert_list(Chain), @@ -201,8 +280,12 @@ certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, Role) -> {ok, #certificate_1_3{ certificate_request_context = <<>>, certificate_list = []}} - end. - + end; +certificate([_,_| _] = Chain, _,_,_,_) -> + CertList = chain_to_cert_list(Chain), + {ok, #certificate_1_3{ + certificate_request_context = <<>>, + certificate_list = CertList}}. certificate_verify(PrivateKey, SignatureScheme, #state{connection_states = ConnectionStates, @@ -213,7 +296,7 @@ certificate_verify(PrivateKey, SignatureScheme, ssl_record:pending_connection_state(ConnectionStates, write), #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR, - {HashAlgo, _, _} = + {HashAlgo, SignAlgo, _} = ssl_cipher:scheme_to_components(SignatureScheme), Context = lists:reverse(Messages), @@ -224,7 +307,7 @@ certificate_verify(PrivateKey, SignatureScheme, %% Digital signatures use the hash function defined by the selected signature %% scheme. - case sign(THash, ContextString, HashAlgo, PrivateKey) of + case sign(THash, ContextString, HashAlgo, PrivateKey, SignAlgo) of {ok, Signature} -> {ok, #certificate_verify_1_3{ algorithm = SignatureScheme, @@ -486,24 +569,9 @@ certificate_entry(DER) -> %% 79 %% 00 %% 0101010101010101010101010101010101010101010101010101010101010101 -sign(THash, Context, HashAlgo, #'ECPrivateKey'{} = PrivateKey) -> - Content = build_content(Context, THash), - try public_key:sign(Content, HashAlgo, PrivateKey) of - Signature -> - {ok, Signature} - catch - error:badarg -> - {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)} - end; -sign(THash, Context, HashAlgo, PrivateKey) -> +sign(THash, Context, HashAlgo, PrivateKey, SignAlgo) -> Content = build_content(Context, THash), - - %% The length of the Salt MUST be equal to the length of the output - %% of the digest algorithm: rsa_pss_saltlen = -1 - try public_key:sign(Content, HashAlgo, PrivateKey, - [{rsa_padding, rsa_pkcs1_pss_padding}, - {rsa_pss_saltlen, -1}, - {rsa_mgf1_md, HashAlgo}]) of + try ssl_handshake:digitally_signed({3,4}, Content, HashAlgo, PrivateKey, SignAlgo) of Signature -> {ok, Signature} catch @@ -511,25 +579,9 @@ sign(THash, Context, HashAlgo, PrivateKey) -> {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)} end. - -verify(THash, Context, HashAlgo, Signature, {?'id-ecPublicKey', PublicKey, PublicKeyParams}) -> +verify(THash, Context, HashAlgo, SignAlgo, Signature, PublicKeyInfo) -> Content = build_content(Context, THash), - try public_key:verify(Content, HashAlgo, Signature, {PublicKey, PublicKeyParams}) of - Result -> - {ok, Result} - catch - error:badarg -> - {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)} - end; -verify(THash, Context, HashAlgo, Signature, {?rsaEncryption, PublicKey, _PubKeyParams}) -> - Content = build_content(Context, THash), - - %% The length of the Salt MUST be equal to the length of the output - %% of the digest algorithm: rsa_pss_saltlen = -1 - try public_key:verify(Content, HashAlgo, Signature, PublicKey, - [{rsa_padding, rsa_pkcs1_pss_padding}, - {rsa_pss_saltlen, -1}, - {rsa_mgf1_md, HashAlgo}]) of + try ssl_handshake:verify_signature({3, 4}, Content, {HashAlgo, SignAlgo}, Signature, PublicKeyInfo) of Result -> {ok, Result} catch @@ -537,7 +589,6 @@ verify(THash, Context, HashAlgo, Signature, {?rsaEncryption, PublicKey, _PubKeyP {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)} end. - build_content(Context, THash) -> Prefix = binary:copy(<<32>>, 64), <<Prefix/binary,Context/binary,?BYTE(0),THash/binary>>. @@ -551,34 +602,44 @@ build_content(Context, THash) -> %% TLS Server do_start(#client_hello{cipher_suites = ClientCiphers, session_id = SessionId, - extensions = Extensions} = _Hello, - #state{connection_states = _ConnectionStates0, - ssl_options = #{ciphers := ServerCiphers, + extensions = Extensions} = Hello, + #state{ssl_options = #{ciphers := ServerCiphers, signature_algs := ServerSignAlgs, supported_groups := ServerGroups0, alpn_preferred_protocols := ALPNPreferredProtocols, - honor_cipher_order := HonorCipherOrder}, - session = #session{own_certificate = Cert}} = State0) -> + keep_secrets := KeepSecrets, + honor_cipher_order := HonorCipherOrder, + early_data := EarlyDataEnabled}} = State0) -> + SNI = maps:get(sni, Extensions, undefined), ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined), - ClientGroups = get_supported_groups(ClientGroups0), - ServerGroups = get_supported_groups(ServerGroups0), - - ClientShares0 = maps:get(key_share, Extensions, undefined), - ClientShares = get_key_shares(ClientShares0), - - OfferedPSKs = get_offered_psks(Extensions), - - ClientALPN0 = maps:get(alpn, Extensions, undefined), - ClientALPN = ssl_handshake:decode_alpn(ClientALPN0), + EarlyDataIndication = maps:get(early_data, Extensions, undefined), + {Ref,Maybe} = maybe(), + try + ClientGroups = Maybe(get_supported_groups(ClientGroups0)), + ServerGroups = Maybe(get_supported_groups(ServerGroups0)), + + ClientShares0 = maps:get(key_share, Extensions, undefined), + ClientShares = get_key_shares(ClientShares0), + + OfferedPSKs = get_offered_psks(Extensions), + + ClientALPN0 = maps:get(alpn, Extensions, undefined), + ClientALPN = ssl_handshake:decode_alpn(ClientALPN0), + + ClientSignAlgs = get_signature_scheme_list( + maps:get(signature_algs, Extensions, undefined)), + ClientSignAlgsCert = get_signature_scheme_list( + maps:get(signature_algs_cert, Extensions, undefined)), + + CookieExt = maps:get(cookie, Extensions, undefined), + Cookie = get_cookie(CookieExt), - ClientSignAlgs = get_signature_scheme_list( - maps:get(signature_algs, Extensions, undefined)), - ClientSignAlgsCert = get_signature_scheme_list( - maps:get(signature_algs_cert, Extensions, undefined)), + #state{connection_states = ConnectionStates0, + session = #session{own_certificates = [Cert | _]}} = State1 = + Maybe(ssl_gen_statem:handle_sni_extension(SNI, State0)), - {Ref,Maybe} = maybe(), + Maybe(validate_cookie(Cookie, State1)), - try %% Handle ALPN extension if ALPN is configured ALPNProtocol = Maybe(handle_alpn(ALPNPreferredProtocols, ClientALPN)), @@ -587,17 +648,15 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% and a signature algorithm/certificate pair to authenticate itself to %% the client. Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)), - Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)), Maybe(validate_client_key_share(ClientGroups, ClientShares)), - - {PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert), + {PublicKeyAlgo, SignAlgo, SignHash, RSAKeySize} = get_certificate_params(Cert), %% Check if client supports signature algorithm of server certificate Maybe(check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, ClientSignAlgsCert)), %% Select signature algorithm (used in CertificateVerify message). - SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs)), + SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, RSAKeySize, ClientSignAlgs, ServerSignAlgs)), %% Select client public key. If no public key found in ClientShares or %% ClientShares is empty, trigger HelloRetryRequest as we were able @@ -608,7 +667,23 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% Generate server_share KeyShare = ssl_cipher:generate_server_share(Group), - State1 = update_start_state(State0, + State2 = case maps:get(max_frag_enum, Extensions, undefined) of + MaxFragEnum when is_record(MaxFragEnum, max_frag_enum) -> + ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), + HsEnv1 = (State1#state.handshake_env)#handshake_env{max_frag_enum = MaxFragEnum}, + State1#state{handshake_env = HsEnv1, + connection_states = ConnectionStates1}; + _ -> + State1 + end, + + State3 = if KeepSecrets =:= true -> + set_client_random(State2, Hello#client_hello.random); + true -> + State2 + end, + + State4 = update_start_state(State3, #{cipher => Cipher, key_share => KeyShare, session_id => SessionId, @@ -623,13 +698,15 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% message if it is able to find an acceptable set of parameters but the %% ClientHello does not contain sufficient information to proceed with %% the handshake. - case Maybe(send_hello_retry_request(State1, ClientPubKey, KeyShare, SessionId)) of + case Maybe(send_hello_retry_request(State4, ClientPubKey, KeyShare, SessionId)) of {_, start} = NextStateTuple -> NextStateTuple; - {_, negotiated} = NextStateTuple -> + {State5, negotiated} -> + %% Determine if early data is accepted + State = handle_early_data(State5, EarlyDataEnabled, EarlyDataIndication), %% Exclude any incompatible PSKs. - PSK = Maybe(handle_pre_shared_key(State1, OfferedPSKs, Cipher)), - Maybe(session_resumption(NextStateTuple, PSK)) + PSK = Maybe(handle_pre_shared_key(State, OfferedPSKs, Cipher)), + Maybe(session_resumption({State, negotiated}, PSK)) end catch {Ref, #alert{} = Alert} -> @@ -642,22 +719,26 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, #state{static_env = #static_env{role = client, host = Host, port = Port, + protocol_cb = Connection, transport_cb = Transport, socket = Socket}, - handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, + ocsp_stapling_state = OcspState}, connection_env = #connection_env{negotiated_version = NegotiatedVersion}, ssl_options = #{ciphers := ClientCiphers, supported_groups := ClientGroups0, use_ticket := UseTicket, session_tickets := SessionTickets, log_level := LogLevel} = SslOpts, - session = #session{own_certificate = Cert} = Session0, + session = #session{own_certificates = OwnCerts} = Session0, connection_states = ConnectionStates0 } = State0) -> - ClientGroups = get_supported_groups(ClientGroups0), - {Ref,Maybe} = maybe(), try + ClientGroups = Maybe(get_supported_groups(ClientGroups0)), + CookieExt = maps:get(cookie, Extensions, undefined), + Cookie = get_cookie(CookieExt), + ServerKeyShare = maps:get(key_share, Extensions, undefined), SelectedGroup = get_selected_group(ServerKeyShare), @@ -679,9 +760,12 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, %% of the triggering HelloRetryRequest. ClientKeyShare = ssl_cipher:generate_client_shares([SelectedGroup]), TicketData = get_ticket_data(self(), SessionTickets, UseTicket), + OcspNonce = maps:get(ocsp_nonce, OcspState, undefined), Hello0 = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, - SessionId, Renegotiation, Cert, ClientKeyShare, - TicketData), + SessionId, Renegotiation, OwnCerts, ClientKeyShare, + TicketData, OcspNonce), + %% Echo cookie received in HelloRetryrequest + Hello1 = maybe_add_cookie_extension(Cookie, Hello0), %% Update state State1 = update_start_state(State0, @@ -695,15 +779,20 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, #state{handshake_env = #handshake_env{tls_handshake_history = HHistory0}} = State2, %% Update pre_shared_key extension with binders (TLS 1.3) - Hello = tls_handshake_1_3:maybe_add_binders(Hello0, HHistory0, TicketData, NegotiatedVersion), + Hello = tls_handshake_1_3:maybe_add_binders(Hello1, HHistory0, TicketData, NegotiatedVersion), + + {BinMsg0, ConnectionStates, HHistory} = + Connection:encode_handshake(Hello, NegotiatedVersion, ConnectionStates0, HHistory0), + + %% D.4. Middlebox Compatibility Mode + {#state{handshake_env = HsEnv} = State3, BinMsg} = + maybe_prepend_change_cipher_spec(State2, BinMsg0), - {BinMsg, ConnectionStates, HHistory} = - tls_connection:encode_handshake(Hello, NegotiatedVersion, ConnectionStates0, HHistory0), tls_socket:send(Transport, Socket, BinMsg), ssl_logger:debug(LogLevel, outbound, 'handshake', Hello), ssl_logger:debug(LogLevel, outbound, 'record', BinMsg), - State = State2#state{ + State = State3#state{ connection_states = ConnectionStates, session = Session0#session{session_id = Hello#client_hello.session_id}, handshake_env = HsEnv#handshake_env{tls_handshake_history = HHistory}, @@ -716,9 +805,12 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, Alert end. - do_negotiated({start_handshake, PSK0}, #state{connection_states = ConnectionStates0, + handshake_env = + #handshake_env{ + early_data_accepted = EarlyDataAccepted}, + static_env = #static_env{protocol_cb = Connection}, session = #session{session_id = SessionId, ecc = SelectedGroup, dh_public_value = ClientPublicKey}, @@ -730,47 +822,61 @@ do_negotiated({start_handshake, PSK0}, ssl_record:pending_connection_state(ConnectionStates0, read), #security_parameters{prf_algorithm = HKDF} = SecParamsR, - {Ref,Maybe} = maybe(), try %% Create server_hello ServerHello = server_hello(server_hello, SessionId, KeyShare, PSK0, ConnectionStates0), - - {State1, _} = tls_connection:send_handshake(ServerHello, State0), + State1 = Connection:queue_handshake(ServerHello, State0), + %% D.4. Middlebox Compatibility Mode + State2 = maybe_queue_change_cipher_spec(State1, last), PSK = get_pre_shared_key(PSK0, HKDF), - State2 = + State3 = calculate_handshake_secrets(ClientPublicKey, ServerPrivateKey, SelectedGroup, - PSK, State1), - - State3 = ssl_record:step_encryption_state(State2), + PSK, State2), + + %% Step only write state if early_data is accepted + State4 = + case EarlyDataAccepted of + true -> + ssl_record:step_encryption_state_write(State3); + false -> + %% Read state is overwritten when hanshake secrets are set. + %% Trial_decryption and early_data_limit must be set here! + update_current_read( + ssl_record:step_encryption_state(State3), + true, %% trial_decryption + false %% early data limit + ) + + end, %% Create EncryptedExtensions - EncryptedExtensions = encrypted_extensions(State2), + EncryptedExtensions = encrypted_extensions(State4), %% Encode EncryptedExtensions - State4 = tls_connection:queue_handshake(EncryptedExtensions, State3), + State5 = Connection:queue_handshake(EncryptedExtensions, State4), %% Create and send CertificateRequest ({verify, verify_peer}) - {State5, NextState} = maybe_send_certificate_request(State4, SslOpts, PSK0), + {State6, NextState} = maybe_send_certificate_request(State5, SslOpts, PSK0), %% Create and send Certificate (if PSK is undefined) - State6 = Maybe(maybe_send_certificate(State5, PSK0)), + State7 = Maybe(maybe_send_certificate(State6, PSK0)), %% Create and send CertificateVerify (if PSK is undefined) - State7 = Maybe(maybe_send_certificate_verify(State6, PSK0)), + State8 = Maybe(maybe_send_certificate_verify(State7, PSK0)), %% Create Finished - Finished = finished(State7), + Finished = finished(State8), %% Encode Finished - State8 = tls_connection:queue_handshake(Finished, State7), + State9 = Connection:queue_handshake(Finished, State8), %% Send first flight - {State9, _} = tls_connection:send_handshake_flight(State8), + {State, _} = Connection:send_handshake_flight(State9), - {State9, NextState} + {State, NextState} catch {Ref, #alert{} = Alert} -> @@ -790,10 +896,15 @@ do_wait_cert(#certificate_1_3{} = Certificate, State0) -> end. -do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, State0) -> +do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, #state{static_env = #static_env{role = Role}} = State0) -> {Ref,Maybe} = maybe(), try - State1 = Maybe(verify_signature_algorithm(State0, CertificateVerify)), + State1 = case Role of + server -> + Maybe(verify_signature_algorithm(State0, CertificateVerify)); + client -> + State0 + end, Maybe(verify_certificate_verify(State1, CertificateVerify)) catch {Ref, {#alert{} = Alert, State}} -> @@ -824,31 +935,29 @@ do_wait_finished(#finished{verify_data = VerifyData}, end; %% TLS Client do_wait_finished(#finished{verify_data = VerifyData}, - #state{static_env = #static_env{role = client}} = State0) -> - + #state{static_env = #static_env{role = client, + protocol_cb = Connection}} = State0) -> + {Ref,Maybe} = maybe(), try Maybe(validate_finished(State0, VerifyData)), - + %% D.4. Middlebox Compatibility Mode + State1 = maybe_queue_change_cipher_spec(State0, first), + %% Signal change of cipher + State2 = maybe_send_end_of_early_data(State1), %% Maybe send Certificate + CertificateVerify - State1 = Maybe(maybe_queue_cert_cert_cv(State0)), - - Finished = finished(State1), - + State3 = Maybe(maybe_queue_cert_cert_cv(State2)), + Finished = finished(State3), %% Encode Finished - State2 = tls_connection:queue_handshake(Finished, State1), - + State4 = Connection:queue_handshake(Finished, State3), %% Send first flight - {State3, _} = tls_connection:send_handshake_flight(State2), - - State4 = calculate_traffic_secrets(State3), - State5 = maybe_calculate_resumption_master_secret(State4), - State6 = forget_master_secret(State5), - + {State5, _} = Connection:send_handshake_flight(State4), + State6 = calculate_traffic_secrets(State5), + State7 = maybe_calculate_resumption_master_secret(State6), + State8 = forget_master_secret(State7), %% Configure traffic keys - ssl_record:step_encryption_state(State6) - + ssl_record:step_encryption_state(State8) catch {Ref, #alert{} = Alert} -> Alert @@ -863,14 +972,15 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, supported_groups := ClientGroups0, session_tickets := SessionTickets, use_ticket := UseTicket}} = State0) -> - ClientGroups = get_supported_groups(ClientGroups0), - ServerKeyShare0 = maps:get(key_share, Extensions, undefined), - ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined), - SelectedIdentity = get_selected_identity(ServerPreSharedKey), - ClientKeyShare = get_key_shares(ClientKeyShare0), - + {Ref,Maybe} = maybe(), try + ClientGroups = Maybe(get_supported_groups(ClientGroups0)), + ServerKeyShare0 = maps:get(key_share, Extensions, undefined), + ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined), + SelectedIdentity = get_selected_identity(ServerPreSharedKey), + ClientKeyShare = get_key_shares(ClientKeyShare0), + %% Go to state 'start' if server replies with 'HelloRetryRequest'. Maybe(maybe_hello_retry_request(ServerHello, State0)), @@ -902,8 +1012,8 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, PSK = Maybe(get_pre_shared_key(SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity)), State3 = calculate_handshake_secrets(ServerPublicKey, ClientPrivateKey, SelectedGroup, PSK, State2), - State4 = ssl_record:step_encryption_state(State3), - + %% State4 = ssl_record:step_encryption_state(State3), + State4 = ssl_record:step_encryption_state_read(State3), {State4, wait_ee} catch @@ -918,21 +1028,30 @@ do_wait_ee(#encrypted_extensions{extensions = Extensions}, State0) -> ALPNProtocol0 = maps:get(alpn, Extensions, undefined), ALPNProtocol = get_alpn(ALPNProtocol0), + EarlyDataIndication = maps:get(early_data, Extensions, undefined), {Ref, Maybe} = maybe(), try + %% RFC 6066: handle received/expected maximum fragment length + Maybe(maybe_max_fragment_length(Extensions, State0)), + + %% Check if early_data is accepted/rejected + State1 = maybe_check_early_data_indication(EarlyDataIndication, State0), + %% Go to state 'wait_finished' if using PSK. - Maybe(maybe_resumption(State0)), + Maybe(maybe_resumption(State1)), %% Update state - #state{handshake_env = HsEnv} = State0, - State1 = State0#state{handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol}}, + #state{handshake_env = HsEnv} = State1, + State2 = State1#state{handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol}}, - {State1, wait_cert_cr} + {State2, wait_cert_cr} catch {Ref, {State, StateName}} -> - {State, StateName} + {State, StateName}; + {Ref, #alert{} = Alert} -> + Alert end. @@ -956,6 +1075,25 @@ do_wait_cert_cr(#certificate_request_1_3{} = CertificateRequest, State0) -> end. +do_wait_eoed(#end_of_early_data{}, State0) -> + {Ref,_Maybe} = maybe(), + try + %% Step read state to enable reading handshake messages from the client. + %% Write state is already stepped in state 'negotiated'. + State1 = ssl_record:step_encryption_state_read(State0), + + %% Early data has been received, no more early data is expected. + HsEnv = (State1#state.handshake_env)#handshake_env{early_data_accepted = false}, + State2 = State1#state{handshake_env = HsEnv}, + {State2, wait_finished} + catch + {Ref, #alert{} = Alert} -> + {Alert, State0}; + {Ref, {#alert{} = Alert, State}} -> + {Alert, State} + end. + + %% For reasons of backward compatibility with middleboxes (see %% Appendix D.4), the HelloRetryRequest message uses the same structure %% as the ServerHello, but with Random set to the special value of the @@ -972,6 +1110,16 @@ maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = maybe_hello_retry_request(_, _) -> ok. +maybe_max_fragment_length(Extensions, State) -> + ServerMaxFragEnum = maps:get(max_frag_enum, Extensions, undefined), + ClientMaxFragEnum = ssl_handshake:max_frag_enum( + maps:get(max_fragment_length, State#state.ssl_options, undefined)), + if ServerMaxFragEnum == ClientMaxFragEnum -> + ok; + true -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)} + end. + maybe_resumption(#state{handshake_env = #handshake_env{resumption = true}} = State) -> {error, {State, wait_finished}}; @@ -985,17 +1133,68 @@ handle_resumption(#state{handshake_env = HSEnv0} = State, _) -> HSEnv = HSEnv0#handshake_env{resumption = true}, State#state{handshake_env = HSEnv}. +%% @doc Enqueues a change_cipher_spec record as the first/last message of +%% the current flight buffer +%% @end +maybe_queue_change_cipher_spec(#state{flight_buffer = FlightBuffer0} = State0, first) -> + {State, FlightBuffer} = maybe_prepend_change_cipher_spec(State0, FlightBuffer0), + State#state{flight_buffer = FlightBuffer}; +maybe_queue_change_cipher_spec(#state{flight_buffer = FlightBuffer0} = State0, last) -> + {State, FlightBuffer} = maybe_append_change_cipher_spec(State0, FlightBuffer0), + State#state{flight_buffer = FlightBuffer}. + +%% @doc Prepends a change_cipher_spec record to the input binary +%% +%% It can only prepend the change_cipher_spec record only once in +%% order to accurately emulate a legacy TLS 1.2 connection. +%% +%% D.4. Middlebox Compatibility Mode +%% If not offering early data, the client sends a dummy +%% change_cipher_spec record (see the third paragraph of Section 5) +%% immediately before its second flight. This may either be before +%% its second ClientHello or before its encrypted handshake flight. +%% If offering early data, the record is placed immediately after the +%% first ClientHello. +%% @end +maybe_prepend_change_cipher_spec(#state{ + ssl_options = + #{middlebox_comp_mode := true}, + handshake_env = + #handshake_env{ + change_cipher_spec_sent = false} = HSEnv} = State, Bin) -> + CCSBin = create_change_cipher_spec(State), + {State#state{handshake_env = + HSEnv#handshake_env{change_cipher_spec_sent = true}}, + [CCSBin|Bin]}; +maybe_prepend_change_cipher_spec(State, Bin) -> + {State, Bin}. + +%% @doc Appends a change_cipher_spec record to the input binary +%% @end +maybe_append_change_cipher_spec(#state{ + ssl_options = + #{middlebox_comp_mode := true}, + handshake_env = + #handshake_env{ + change_cipher_spec_sent = false} = HSEnv} = State, Bin) -> + CCSBin = create_change_cipher_spec(State), + {State#state{handshake_env = + HSEnv#handshake_env{change_cipher_spec_sent = true}}, + Bin ++ [CCSBin]}; +maybe_append_change_cipher_spec(State, Bin) -> + {State, Bin}. maybe_queue_cert_cert_cv(#state{client_certificate_requested = false} = State) -> {ok, State}; maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0, session = #session{session_id = _SessionId, - own_certificate = OwnCert}, + own_certificates = OwnCerts}, ssl_options = #{} = _SslOpts, key_share = _KeyShare, handshake_env = #handshake_env{tls_handshake_history = _HHistory0}, static_env = #static_env{ role = client, + protocol_cb = Connection, cert_db = CertDbHandle, cert_db_ref = CertDbRef, socket = _Socket, @@ -1004,11 +1203,10 @@ maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0, {Ref,Maybe} = maybe(), try %% Create Certificate - Certificate = Maybe(certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, client)), + Certificate = Maybe(certificate(OwnCerts, CertDbHandle, CertDbRef, <<>>, client)), %% Encode Certificate - State1 = tls_connection:queue_handshake(Certificate, State0), - + State1 = Connection:queue_handshake(Certificate, State0), %% Maybe create and queue CertificateVerify State = Maybe(maybe_queue_cert_verify(Certificate, State1)), {ok, State} @@ -1026,12 +1224,13 @@ maybe_queue_cert_verify(_Certificate, #state{connection_states = _ConnectionStates0, session = #session{sign_alg = SignatureScheme}, connection_env = #connection_env{private_key = CertPrivateKey}, - static_env = #static_env{role = client} + static_env = #static_env{role = client, + protocol_cb = Connection} } = State) -> {Ref,Maybe} = maybe(), try CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme, State, client)), - {ok, tls_connection:queue_handshake(CertificateVerify, State)} + {ok, Connection:queue_handshake(CertificateVerify, State)} catch {Ref, #alert{} = Alert} -> {error, Alert} @@ -1064,15 +1263,21 @@ compare_verify_data(_, _) -> {error, ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error)}. -send_hello_retry_request(#state{connection_states = ConnectionStates0} = State0, +send_hello_retry_request(#state{connection_states = ConnectionStates0, + static_env = #static_env{protocol_cb = Connection}} = State0, no_suitable_key, KeyShare, SessionId) -> - ServerHello = server_hello(hello_retry_request, SessionId, KeyShare, undefined, ConnectionStates0), - {State1, _} = tls_connection:send_handshake(ServerHello, State0), + ServerHello0 = server_hello(hello_retry_request, SessionId, KeyShare, undefined, ConnectionStates0), + {State1, ServerHello} = maybe_add_cookie_extension(State0, ServerHello0), + + State2 = Connection:queue_handshake(ServerHello, State1), + %% D.4. Middlebox Compatibility Mode + State3 = maybe_queue_change_cipher_spec(State2, last), + {State4, _} = Connection:send_handshake_flight(State3), %% Update handshake history - State2 = replace_ch1_with_message_hash(State1), + State5 = replace_ch1_with_message_hash(State4), - {ok, {State2, start}}; + {ok, {State5, start}}; send_hello_retry_request(State0, _, _, _) -> %% Suitable key found. {ok, {State0, negotiated}}. @@ -1082,33 +1287,55 @@ session_resumption({#state{ssl_options = #{session_tickets := disabled}} = State session_resumption({#state{ssl_options = #{session_tickets := Tickets}} = State, negotiated}, undefined) when Tickets =/= disabled -> {ok, {State, negotiated}}; -session_resumption({#state{ssl_options = #{session_tickets := Tickets}} = State0, negotiated}, PSK) +session_resumption({#state{ssl_options = #{session_tickets := Tickets}, + handshake_env = #handshake_env{ + early_data_accepted = false}} = State0, negotiated}, PSK) when Tickets =/= disabled -> State = handle_resumption(State0, ok), - {ok, {State, negotiated, PSK}}. - - + {ok, {State, negotiated, PSK}}; +session_resumption({#state{ssl_options = #{session_tickets := Tickets}, + handshake_env = #handshake_env{ + early_data_accepted = true}} = State0, negotiated}, PSK0) + when Tickets =/= disabled -> + State1 = handle_resumption(State0, ok), + %% TODO Refactor PSK-tuple {Index, PSK}, index might not be needed. + {_ , PSK} = PSK0, + State2 = calculate_client_early_traffic_secret(State1, PSK), + %% Set 0-RTT traffic keys for reading early_data + State3 = ssl_record:step_encryption_state_read(State2), + State = update_current_read(State3, true, true), + {ok, {State, negotiated, PSK0}}. + +%% Session resumption with early_data +maybe_send_certificate_request(#state{ + handshake_env = + #handshake_env{ + early_data_accepted = true}} = State, + _, PSK) when PSK =/= undefined -> + %% Go wait for End of Early Data + {State, wait_eoed}; %% Do not send CR during session resumption maybe_send_certificate_request(State, _, PSK) when PSK =/= undefined -> {State, wait_finished}; maybe_send_certificate_request(State, #{verify := verify_none}, _) -> {State, wait_finished}; -maybe_send_certificate_request(State, #{verify := verify_peer, - signature_algs := SignAlgs, - signature_algs_cert := SignAlgsCert}, _) -> +maybe_send_certificate_request(#state{static_env = #static_env{protocol_cb = Connection}} = State, + #{verify := verify_peer, + signature_algs := SignAlgs, + signature_algs_cert := SignAlgsCert}, _) -> CertificateRequest = certificate_request(SignAlgs, SignAlgsCert), - {tls_connection:queue_handshake(CertificateRequest, State), wait_cert}. - + {Connection:queue_handshake(CertificateRequest, State), wait_cert}. maybe_send_certificate(State, PSK) when PSK =/= undefined -> {ok, State}; -maybe_send_certificate(#state{session = #session{own_certificate = OwnCert}, +maybe_send_certificate(#state{session = #session{own_certificates = OwnCerts}, static_env = #static_env{ + protocol_cb = Connection, cert_db = CertDbHandle, cert_db_ref = CertDbRef}} = State, _) -> - case certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server) of + case certificate(OwnCerts, CertDbHandle, CertDbRef, <<>>, server) of {ok, Certificate} -> - {ok, tls_connection:queue_handshake(Certificate, State)}; + {ok, Connection:queue_handshake(Certificate, State)}; Error -> Error end. @@ -1117,11 +1344,12 @@ maybe_send_certificate(#state{session = #session{own_certificate = OwnCert}, maybe_send_certificate_verify(State, PSK) when PSK =/= undefined -> {ok, State}; maybe_send_certificate_verify(#state{session = #session{sign_alg = SignatureScheme}, + static_env = #static_env{protocol_cb = Connection}, connection_env = #connection_env{ private_key = CertPrivateKey}} = State, _) -> case certificate_verify(CertPrivateKey, SignatureScheme, State, server) of {ok, CertificateVerify} -> - {ok, tls_connection:queue_handshake(CertificateVerify, State)}; + {ok, Connection:queue_handshake(CertificateVerify, State)}; Error -> Error end. @@ -1143,41 +1371,71 @@ maybe_send_session_ticket(#state{ssl_options = #{session_tickets := disabled}} = maybe_send_session_ticket(State, 0) -> State; maybe_send_session_ticket(#state{connection_states = ConnectionStates, - static_env = #static_env{trackers = Trackers}} = State0, N) -> + static_env = #static_env{trackers = Trackers, + protocol_cb = Connection} + + } = State0, N) -> Tracker = proplists:get_value(session_tickets_tracker, Trackers), #{security_parameters := SecParamsR} = ssl_record:current_connection_state(ConnectionStates, read), #security_parameters{prf_algorithm = HKDF, resumption_master_secret = RMS} = SecParamsR, Ticket = tls_server_session_ticket:new(Tracker, HKDF, RMS), - {State, _} = tls_connection:send_handshake(Ticket, State0), + {State, _} = Connection:send_handshake(Ticket, State0), maybe_send_session_ticket(State, N - 1). +create_change_cipher_spec(#state{ssl_options = #{log_level := LogLevel}}) -> + %% Dummy connection_states with NULL cipher + ConnectionStates = + #{current_write => + #{compression_state => undefined, + cipher_state => undefined, + sequence_number => 1, + security_parameters => + #security_parameters{ + bulk_cipher_algorithm = 0, + compression_algorithm = ?NULL, + mac_algorithm = ?NULL + }, + mac_secret => undefined}}, + {BinChangeCipher, _} = + tls_record:encode_change_cipher_spec(?LEGACY_VERSION, ConnectionStates), + ssl_logger:debug(LogLevel, outbound, 'record', BinChangeCipher), + [BinChangeCipher]. + process_certificate_request(#certificate_request_1_3{}, - #state{session = #session{own_certificate = undefined}} = State) -> + #state{session = #session{own_certificates = undefined}} = State) -> {ok, {State#state{client_certificate_requested = true}, wait_cert}}; process_certificate_request(#certificate_request_1_3{ - extensions = Extensions}, - #state{session = #session{own_certificate = Cert} = Session} = State) -> + extensions = Extensions}, + #state{ssl_options = #{signature_algs := ClientSignAlgs}, + session = #session{own_certificates = [Cert|_]} = Session} = + State) -> ServerSignAlgs = get_signature_scheme_list( maps:get(signature_algs, Extensions, undefined)), ServerSignAlgsCert = get_signature_scheme_list( maps:get(signature_algs_cert, Extensions, undefined)), - {_PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert), - - %% Check if server supports signature algorithm of client certificate - case check_cert_sign_algo(SignAlgo, SignHash, ServerSignAlgs, ServerSignAlgsCert) of - ok -> - {ok, {State#state{client_certificate_requested = true}, wait_cert}}; - {error, _} -> - %% Certificate not supported: send empty certificate in state 'wait_finished' + {PublicKeyAlgo, SignAlgo, SignHash, MaybeRSAKeySize} = get_certificate_params(Cert), + {Ref, Maybe} = maybe(), + try + SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, MaybeRSAKeySize, ServerSignAlgs, ClientSignAlgs)), + %% Check if server supports signature algorithm of client certificate + case check_cert_sign_algo(SignAlgo, SignHash, ServerSignAlgs, ServerSignAlgsCert) of + ok -> {ok, {State#state{client_certificate_requested = true, - session = Session#session{own_certificate = undefined}}, wait_cert}} + session = Session#session{sign_alg = SelectedSignAlg}}, wait_cert}}; + {error, _} -> + %% Certificate not supported: send empty certificate in state 'wait_finished' + {ok, {State#state{client_certificate_requested = true, + session = Session#session{own_certificates = undefined}}, wait_cert}} + end + catch + {Ref, #alert{} = Alert} -> + Alert end. - process_certificate(#certificate_1_3{ certificate_request_context = <<>>, certificate_list = []}, @@ -1189,7 +1447,6 @@ process_certificate(#certificate_1_3{ certificate_list = []}, #state{ssl_options = #{fail_if_no_peer_cert := true}} = State0) -> - %% At this point the client believes that the connection is up and starts using %% its traffic secrets. In order to be able send an proper Alert to the client %% the server should also change its connection state and use the traffic @@ -1197,56 +1454,30 @@ process_certificate(#certificate_1_3{ State1 = calculate_traffic_secrets(State0), State = ssl_record:step_encryption_state(State1), {error, {?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required), State}}; -process_certificate(#certificate_1_3{certificate_list = Certs0}, - #state{ssl_options = - #{signature_algs := SignAlgs, - signature_algs_cert := SignAlgsCert} = SslOptions, - static_env = - #static_env{ - role = Role, - host = Host, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - crl_db = CRLDbHandle}} = State0) -> - %% TODO: handle extensions! - %% Remove extensions from list of certificates! - Certs = convert_certificate_chain(Certs0), - case is_supported_signature_algorithm(Certs, SignAlgs, SignAlgsCert) of - true -> - case validate_certificate_chain(Certs, CertDbHandle, CertDbRef, - SslOptions, CRLDbHandle, Role, Host) of - {ok, {PeerCert, PublicKeyInfo}} -> - State = store_peer_cert(State0, PeerCert, PublicKeyInfo), - {ok, {State, wait_cv}}; - {error, Reason} -> - State = update_encryption_state(Role, State0), - {error, {Reason, State}}; - {ok, #alert{} = Alert} -> - State = update_encryption_state(Role, State0), - {error, {Alert, State}} - end; - false -> - State1 = calculate_traffic_secrets(State0), - State = ssl_record:step_encryption_state(State1), - {error, {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, - "Client certificate uses unsupported signature algorithm"), State}} +process_certificate(#certificate_1_3{certificate_list = CertEntries}, + #state{ssl_options = SslOptions, + static_env = + #static_env{ + role = Role, + host = Host, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef, + 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 + {ok, {PeerCert, PublicKeyInfo}} -> + State = store_peer_cert(State0, PeerCert, PublicKeyInfo), + {ok, {State, wait_cv}}; + {error, Reason} -> + State = update_encryption_state(Role, State0), + {error, {Reason, State}}; + {ok, #alert{} = Alert} -> + State = update_encryption_state(Role, State0), + {error, {Alert, State}} end. - -%% TODO: check whole chain! -is_supported_signature_algorithm(Certs, SignAlgs, undefined) -> - is_supported_signature_algorithm(Certs, SignAlgs); -is_supported_signature_algorithm(Certs, _, SignAlgsCert) -> - is_supported_signature_algorithm(Certs, SignAlgsCert). -%% -is_supported_signature_algorithm([BinCert|_], SignAlgs0) -> - #'OTPCertificate'{signatureAlgorithm = SignAlg} = - public_key:pkix_decode_cert(BinCert, otp), - SignAlgs = filter_tls13_algs(SignAlgs0), - Scheme = ssl_cipher:signature_algorithm_to_scheme(SignAlg), - lists:member(Scheme, SignAlgs). - - %% Sets correct encryption state when sending Alerts in shared states that use different secrets. %% - If client: use handshake secrets. %% - If server: use traffic secrets as by this time the client's state machine @@ -1258,46 +1489,34 @@ update_encryption_state(client, State) -> State. -validate_certificate_chain(Certs, CertDbHandle, CertDbRef, +validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef, #{server_name_indication := ServerNameIndication, partial_chain := PartialChain, - verify_fun := VerifyFun, - customize_hostname_check := CustomizeHostnameCheck, - crl_check := CrlCheck, - log_level := LogLevel, - depth := Depth} = SslOptions, - CRLDbHandle, Role, Host) -> + ocsp_responder_certs := OcspResponderCerts + } = SslOptions, CRLDbHandle, Role, Host, OcspState0) -> + {Certs, CertExt, OcspState} = split_cert_entries(CertEntries, OcspState0), ServerName = ssl_handshake:server_name(ServerNameIndication, Host, Role), - [PeerCert | ChainCerts ] = Certs, - try - {TrustedCert, CertPath} = - ssl_certificate:trusted_cert_and_path(Certs, CertDbHandle, CertDbRef, + [PeerCert | _ChainCerts ] = Certs, + try + PathsAndAnchors = + ssl_certificate:trusted_cert_and_paths(Certs, CertDbHandle, CertDbRef, PartialChain), - ValidationFunAndState = - ssl_handshake:validation_fun_and_state(VerifyFun, Role, - CertDbHandle, CertDbRef, ServerName, - CustomizeHostnameCheck, - CrlCheck, CRLDbHandle, CertPath, LogLevel), - Options = [{max_path_length, Depth}, - {verify_fun, ValidationFunAndState}], - %% TODO: Validate if Certificate is using a supported signature algorithm - %% (signature_algs_cert)! - case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of - {ok, {PublicKeyInfo,_}} -> - {ok, {PeerCert, PublicKeyInfo}}; - {error, Reason} -> - {ok, ssl_handshake:handle_path_validation_error(Reason, PeerCert, ChainCerts, - SslOptions, Options, - CertDbHandle, CertDbRef)} - end - catch - error:{badmatch,{error, {asn1, Asn1Reason}}} -> - %% ASN-1 decode of certificate somehow failed - {error, ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason})}; - error:OtherReason -> - {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason})} - end. - + case path_validate(PathsAndAnchors, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + {3, 4}, SslOptions, #{cert_ext => CertExt, + ocsp_state => OcspState, + ocsp_responder_certs => OcspResponderCerts}) of + {ok, {PublicKeyInfo,_}} -> + {ok, {PeerCert, PublicKeyInfo}}; + {error, Reason} -> + {ok, ssl_handshake:path_validation_alert(Reason)} + end + catch + error:{badmatch,{error, {asn1, Asn1Reason}}} -> + %% ASN-1 decode of certificate somehow failed + {error, ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason})}; + error:OtherReason -> + {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason})} + end. store_peer_cert(#state{session = Session, handshake_env = HsEnv} = State, PeerCert, PublicKeyInfo) -> @@ -1305,13 +1524,21 @@ store_peer_cert(#state{session = Session, handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo}}. -convert_certificate_chain(Certs) -> - Fun = fun(#certificate_entry{data = Data}) -> - {true, Data}; - (_) -> - false - end, - lists:filtermap(Fun, Certs). +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) -> + 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}). %% 4.4.1. The Transcript Hash @@ -1343,6 +1570,11 @@ replace_ch1_with_message_hash(#state{connection_states = ConnectionStates, tls_handshake_history = {[HRR,MessageHash|HHistory], LM}}}. +get_hkdf_algorithm(ConnectionStates) -> + #{security_parameters := SecParamsR} = + ssl_record:pending_connection_state(ConnectionStates, read), + #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR, + HKDFAlgo. message_hash(ClientHello1, HKDFAlgo) -> [?MESSAGE_HASH, @@ -1366,26 +1598,88 @@ calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup, PSK, %% Calculate [sender]_handshake_traffic_secret {Messages, _} = HHistory, - ClientHSTrafficSecret = tls_v1:client_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)), ServerHSTrafficSecret = tls_v1:server_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)), %% Calculate traffic keys - #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite), - {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ClientHSTrafficSecret), - {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ServerHSTrafficSecret), + KeyLength = tls_v1:key_length(CipherSuite), + {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ClientHSTrafficSecret), + {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ServerHSTrafficSecret), %% Calculate Finished Keys ReadFinishedKey = tls_v1:finished_key(ClientHSTrafficSecret, HKDFAlgo), WriteFinishedKey = tls_v1:finished_key(ServerHSTrafficSecret, HKDFAlgo), - update_pending_connection_states(State0, HandshakeSecret, undefined, + State1 = maybe_store_handshake_traffic_secret(State0, ClientHSTrafficSecret, ServerHSTrafficSecret), + + update_pending_connection_states(State1, HandshakeSecret, undefined, undefined, undefined, ReadKey, ReadIV, ReadFinishedKey, WriteKey, WriteIV, WriteFinishedKey). +%% Server +calculate_client_early_traffic_secret(#state{connection_states = ConnectionStates, + handshake_env = + #handshake_env{ + tls_handshake_history = {Hist, _}}} = State, PSK) -> + + #{security_parameters := SecParamsR} = + ssl_record:pending_connection_state(ConnectionStates, read), + #security_parameters{cipher_suite = CipherSuite} = SecParamsR, + #{cipher := Cipher, + prf := HKDF} = ssl_cipher_format:suite_bin_to_map(CipherSuite), + calculate_client_early_traffic_secret(Hist, PSK, Cipher, HKDF, State). + +%% Client +calculate_client_early_traffic_secret( + ClientHello, PSK, Cipher, HKDFAlgo, + #state{connection_states = ConnectionStates, + ssl_options = #{keep_secrets := KeepSecrets}, + static_env = #static_env{role = Role}} = State0) -> + EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, PSK}), + ClientEarlyTrafficSecret = + tls_v1:client_early_traffic_secret(HKDFAlgo, EarlySecret, ClientHello), + %% Calculate traffic key + KeyLength = ssl_cipher:key_material(Cipher), + {Key, IV} = + tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ClientEarlyTrafficSecret), + %% Update pending connection states + case Role of + client -> + PendingWrite0 = ssl_record:pending_connection_state(ConnectionStates, write), + PendingWrite1 = maybe_store_early_data_secret(KeepSecrets, ClientEarlyTrafficSecret, + PendingWrite0), + PendingWrite = update_connection_state(PendingWrite1, undefined, undefined, + undefined, + Key, IV, undefined), + State0#state{connection_states = ConnectionStates#{pending_write => PendingWrite}}; + server -> + PendingRead0 = ssl_record:pending_connection_state(ConnectionStates, read), + PendingRead1 = maybe_store_early_data_secret(KeepSecrets, ClientEarlyTrafficSecret, + PendingRead0), + PendingRead2 = update_connection_state(PendingRead1, undefined, undefined, + undefined, + Key, IV, undefined), + %% Signal start of early data. This is to prevent handshake messages to be + %% counted in max_early_data_size. + PendingRead = PendingRead2#{count_early_data => true}, + State0#state{connection_states = ConnectionStates#{pending_read => PendingRead}} + end. + +update_current_read(#state{connection_states = CS} = State, TrialDecryption, EarlyDataLimit) -> + Read0 = ssl_record:current_connection_state(CS, read), + Read = Read0#{trial_decryption => TrialDecryption, + early_data_limit => EarlyDataLimit}, + State#state{connection_states = CS#{current_read => Read}}. + +maybe_store_early_data_secret(true, EarlySecret, State) -> + #{security_parameters := SecParams0} = State, + SecParams = SecParams0#security_parameters{client_early_data_secret = EarlySecret}, + State#{security_parameters := SecParams}; +maybe_store_early_data_secret(false, _, State) -> + State. %% Server get_pre_shared_key(undefined, HKDFAlgo) -> @@ -1410,7 +1704,7 @@ get_pre_shared_key(manual = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentit {ok, binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo))}; illegal_parameter -> {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}; - {_, PSK} -> + {_, PSK, _, _, _} -> {ok, PSK} end; get_pre_shared_key(auto = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity) -> @@ -1422,19 +1716,35 @@ get_pre_shared_key(auto = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity) illegal_parameter -> tls_client_ticket_store:unlock_tickets(self(), UseTicket), {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}; - {Key, PSK} -> + {Key, PSK, _, _, _} -> tls_client_ticket_store:remove_tickets([Key]), %% Remove single-use ticket tls_client_ticket_store:unlock_tickets(self(), UseTicket -- [Key]), {ok, PSK} end. - +%% +%% Early Data +get_pre_shared_key_early_data(SessionTickets, UseTicket) -> + TicketData = get_ticket_data(self(), SessionTickets, UseTicket), + case choose_psk(TicketData, 0) of + undefined -> %% Should not happen + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}; + illegal_parameter -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}; + {_Key, PSK, Cipher, HKDF, MaxSize} -> + {ok, {PSK, Cipher, HKDF, MaxSize}} + end. choose_psk(undefined, _) -> undefined; choose_psk([], _) -> illegal_parameter; -choose_psk([{Key, SelectedIdentity, _, PSK, _, _}|_], SelectedIdentity) -> - {Key, PSK}; +choose_psk([#ticket_data{ + key = Key, + pos = SelectedIdentity, + psk = PSK, + cipher_suite = {Cipher, HKDF}, + max_size = MaxSize}|_], SelectedIdentity) -> + {Key, PSK, Cipher, HKDF, MaxSize}; choose_psk([_|T], SelectedIdentity) -> choose_psk(T, SelectedIdentity). @@ -1464,9 +1774,9 @@ calculate_traffic_secrets(#state{ tls_v1:server_application_traffic_secret_0(HKDFAlgo, MasterSecret, lists:reverse(Messages)), %% Calculate traffic keys - #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite), - {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ClientAppTrafficSecret0), - {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ServerAppTrafficSecret0), + KeyLength = tls_v1:key_length(CipherSuite), + {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ClientAppTrafficSecret0), + {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ServerAppTrafficSecret0), update_pending_connection_states(State0, MasterSecret, undefined, ClientAppTrafficSecret0, ServerAppTrafficSecret0, @@ -1538,6 +1848,36 @@ overwrite_master_secret(ConnectionState = #{security_parameters := SecurityParam ConnectionState#{security_parameters => SecurityParameters}. +set_client_random(#state{connection_states = + #{pending_read := PendingRead, + pending_write := PendingWrite, + current_read := CurrentRead, + current_write := CurrentWrite} = CS} = State, ClientRandom) -> + State#state{connection_states = CS#{pending_read => overwrite_client_random(PendingRead, ClientRandom), + pending_write => overwrite_client_random(PendingWrite, ClientRandom), + current_read => overwrite_client_random(CurrentRead, ClientRandom), + current_write => overwrite_client_random(CurrentWrite, ClientRandom)}}. + + +overwrite_client_random(ConnectionState = #{security_parameters := SecurityParameters0}, ClientRandom) -> + SecurityParameters = SecurityParameters0#security_parameters{client_random = ClientRandom}, + ConnectionState#{security_parameters => SecurityParameters}. + + +maybe_store_handshake_traffic_secret(#state{connection_states = + #{pending_read := PendingRead} = CS, + ssl_options = #{keep_secrets := true}} = State, + ClientHSTrafficSecret, ServerHSTrafficSecret) -> + PendingRead1 = store_handshake_traffic_secret(PendingRead, ClientHSTrafficSecret, ServerHSTrafficSecret), + State#state{connection_states = CS#{pending_read => PendingRead1}}; +maybe_store_handshake_traffic_secret(State, _, _) -> + State. + +store_handshake_traffic_secret(ConnectionState, ClientHSTrafficSecret, ServerHSTrafficSecret) -> + ConnectionState#{client_handshake_traffic_secret => ClientHSTrafficSecret, + server_handshake_traffic_secret => ServerHSTrafficSecret}. + + update_pending_connection_states(#state{ static_env = #static_env{role = server}, connection_states = @@ -1582,8 +1922,9 @@ update_connection_state(ConnectionState = #{security_parameters := SecurityParam master_secret = HandshakeSecret, resumption_master_secret = ResumptionMasterSecret, application_traffic_secret = ApplicationTrafficSecret}, + BulkCipherAlgo = SecurityParameters#security_parameters.bulk_cipher_algorithm, ConnectionState#{security_parameters => SecurityParameters, - cipher_state => cipher_init(Key, IV, FinishedKey)}. + cipher_state => cipher_init(BulkCipherAlgo, Key, IV, FinishedKey)}. update_start_state(State, Map) -> @@ -1639,7 +1980,12 @@ update_resumption_master_secret(#state{connection_states = ConnectionStates0} = State#state{connection_states = ConnectionStates}. -cipher_init(Key, IV, FinishedKey) -> +cipher_init(?AES_CCM_8, Key, IV, FinishedKey) -> + #cipher_state{key = Key, + iv = IV, + finished_key = FinishedKey, + tag_len = 8}; +cipher_init(_BulkCipherAlgo, Key, IV, FinishedKey) -> #cipher_state{key = Key, iv = IV, finished_key = FinishedKey, @@ -1745,13 +2091,12 @@ maybe_update_selected_sign_alg(State, _, _) -> State. -verify_certificate_verify(#state{ - static_env = #static_env{role = Role}, - connection_states = ConnectionStates, - handshake_env = - #handshake_env{ - public_key_info = PublicKeyInfo, - tls_handshake_history = HHistory}} = State0, +verify_certificate_verify(#state{static_env = #static_env{role = Role}, + connection_states = ConnectionStates, + handshake_env = + #handshake_env{ + public_key_info = PublicKeyInfo, + tls_handshake_history = HHistory}} = State0, #certificate_verify_1_3{ algorithm = SignatureScheme, signature = Signature}) -> @@ -1759,7 +2104,7 @@ verify_certificate_verify(#state{ ssl_record:pending_connection_state(ConnectionStates, write), #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR, - {HashAlgo, _, _} = + {HashAlgo, SignAlg, _} = ssl_cipher:scheme_to_components(SignatureScheme), Messages = get_handshake_context_cv(HHistory), @@ -1773,7 +2118,7 @@ verify_certificate_verify(#state{ %% Digital signatures use the hash function defined by the selected signature %% scheme. - case verify(THash, ContextString, HashAlgo, Signature, PublicKeyInfo) of + case verify(THash, ContextString, HashAlgo, SignAlg, Signature, PublicKeyInfo) of {ok, true} -> {ok, {State0, wait_finished}}; {ok, false} -> @@ -1935,7 +2280,7 @@ select_cipher_suite(_, [], _) -> select_cipher_suite(true, ClientCiphers, ServerCiphers) -> select_cipher_suite(false, ServerCiphers, ClientCiphers); select_cipher_suite(false, [Cipher|ClientCiphers], ServerCiphers) -> - case lists:member(Cipher, tls_v1:suites('TLS_v1.3')) andalso + case lists:member(Cipher, tls_v1:exclusive_suites(4)) andalso lists:member(Cipher, ServerCiphers) of true -> {ok, Cipher}; @@ -1977,12 +2322,12 @@ check_cert_sign_algo(SignAlgo, SignHash, _, ClientSignAlgsCert) -> %% DSA keys are not supported by TLS 1.3 -select_sign_algo(dsa, _ClientSignAlgs, _ServerSignAlgs) -> +select_sign_algo(dsa, _RSAKeySize, _PeerSignAlgs, _OwnSignAlgs) -> {error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)}; -select_sign_algo(_, [], _) -> +select_sign_algo(_, _RSAKeySize, [], _) -> {error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)}; -select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) -> - {_, S, _} = ssl_cipher:scheme_to_components(C), +select_sign_algo(PublicKeyAlgo, RSAKeySize, [PeerSignAlg|PeerSignAlgs], OwnSignAlgs) -> + {_, S, _} = ssl_cipher:scheme_to_components(PeerSignAlg), %% RSASSA-PKCS1-v1_5 and Legacy algorithms are not defined for use in signed %% TLS handshake messages: filter sha-1 and rsa_pkcs1. %% @@ -1991,16 +2336,48 @@ select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) -> %% RSASSA-PSS PSS algorithms: If the public key is carried in an X.509 certificate, %% it MUST use the RSASSA-PSS OID. case ((PublicKeyAlgo =:= rsa andalso S =:= rsa_pss_rsae) - orelse (PublicKeyAlgo =:= rsa_pss andalso S =:= rsa_pss_pss) + orelse (PublicKeyAlgo =:= rsa_pss_pss andalso S =:= rsa_pss_pss) orelse (PublicKeyAlgo =:= ecdsa andalso S =:= ecdsa)) andalso - lists:member(C, ServerSignAlgs) of + lists:member(PeerSignAlg, OwnSignAlgs) of true -> - {ok, C}; + validate_key_compatibility(PublicKeyAlgo, RSAKeySize, + [PeerSignAlg|PeerSignAlgs], OwnSignAlgs); false -> - select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs) + select_sign_algo(PublicKeyAlgo, RSAKeySize, PeerSignAlgs, OwnSignAlgs) end. +validate_key_compatibility(PublicKeyAlgo, RSAKeySize, [PeerSignAlg|PeerSignAlgs], OwnSignAlgs) + when PublicKeyAlgo =:= rsa orelse + PublicKeyAlgo =:= rsa_pss_pss -> + case is_rsa_key_compatible(RSAKeySize, PeerSignAlg) of + true -> + {ok, PeerSignAlg}; + false -> + select_sign_algo(PublicKeyAlgo, RSAKeySize, PeerSignAlgs, OwnSignAlgs) + end; +validate_key_compatibility(_, _, [PeerSignAlg|_], _) -> + {ok, PeerSignAlg}. + +is_rsa_key_compatible(KeySize, SigAlg) -> + {Hash, _, _} = ssl_cipher:scheme_to_components(SigAlg), + HashSize = ssl_cipher:hash_size(Hash), + + %% OpenSSL crypto lib defines a limit on the size of the random salt + %% in PSS signatures based on the size of signing RSA key. + %% If the limit is unchecked, it causes handshake failures when the + %% configured certificates contain short (e.g. 1024-bit) RSA keys. + %% For more information see the OpenSSL crypto library + %% (rsa_pss:c{77,86}). + %% TODO: Move this check into crypto. Investigate if this is a bug in + %% OpenSSL crypto lib. + if (KeySize < (HashSize + 2)) -> + false; + (HashSize > (KeySize - HashSize - 2)) -> + false; + true -> + true + end. do_check_cert_sign_algo(_, _, []) -> {error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)}; @@ -2015,10 +2392,8 @@ do_check_cert_sign_algo(SignAlgo, SignHash, [Scheme|T]) -> %% id-RSASSA-PSS (rsa_pss) indicates that the key may only be used for PSS signatures. -%% TODO: Uncomment when rsa_pss signatures are supported in certificates -%% compare_sign_algos(rsa_pss, Hash, Algo, Hash) -%% when Algo =:= rsa_pss_pss -> -%% true; +compare_sign_algos(rsa_pss_pss, Hash, rsa_pss_pss, Hash) -> + true; %% rsaEncryption (rsa) allows the key to be used for any of the standard encryption or %% signature schemes. compare_sign_algos(rsa, Hash, Algo, Hash) @@ -2030,23 +2405,28 @@ compare_sign_algos(Algo, Hash, Algo, Hash) -> compare_sign_algos(_, _, _, _) -> false. - get_certificate_params(Cert) -> - {SignAlgo0, _Param, PublicKeyAlgo0} = ssl_handshake:get_cert_params(Cert), - {SignHash0, SignAlgo} = public_key:pkix_sign_types(SignAlgo0), - %% Convert hash to new format - SignHash = case SignHash0 of - sha -> - sha1; - H -> H - end, - PublicKeyAlgo = public_key_algo(PublicKeyAlgo0), - {PublicKeyAlgo, SignAlgo, SignHash}. - - + {SignAlgo0, Param, SubjectPublicKeyAlgo0, RSAKeySize} = + ssl_handshake:get_cert_params(Cert), + {SignHash, SignAlgo} = oids_to_atoms(SignAlgo0, Param), + SubjectPublicKeyAlgo = public_key_algo(SubjectPublicKeyAlgo0), + {SubjectPublicKeyAlgo, SignAlgo, SignHash, RSAKeySize}. + +oids_to_atoms(?'id-RSASSA-PSS', #'RSASSA-PSS-params'{maskGenAlgorithm = + #'MaskGenAlgorithm'{algorithm = ?'id-mgf1', + parameters = #'HashAlgorithm'{algorithm = HashOid}}}) -> + Hash = public_key:pkix_hash_type(HashOid), + {Hash, rsa_pss_pss}; +oids_to_atoms(SignAlgo, _) -> + case public_key:pkix_sign_types(SignAlgo) of + {sha, Sign} -> + {sha1, Sign}; + {_,_} = Algs -> + Algs + end. %% Note: copied from ssl_handshake public_key_algo(?'id-RSASSA-PSS') -> - rsa_pss; + rsa_pss_pss; public_key_algo(?rsaEncryption) -> rsa; public_key_algo(?'id-ecPublicKey') -> @@ -2065,14 +2445,21 @@ get_signature_scheme_list(#signature_algorithms{ lists:filter(fun (E) -> is_atom(E) andalso E =/= unassigned end, ClientSignatureSchemes). +get_supported_groups(undefined = Groups) -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, {supported_groups, Groups})}; get_supported_groups(#supported_groups{supported_groups = Groups}) -> - Groups. + {ok, Groups}. get_key_shares(#key_share_client_hello{client_shares = ClientShares}) -> ClientShares; get_key_shares(#key_share_server_hello{server_share = ServerShare}) -> ServerShare. +get_cookie(undefined) -> + undefined; +get_cookie(#cookie{cookie = Cookie}) -> + Cookie. + get_selected_identity(undefined) -> undefined; get_selected_identity(#pre_shared_key_server_hello{selected_identity = SelectedIdentity}) -> @@ -2174,18 +2561,18 @@ maybe_add_binders(Hello0, {[HRR,MessageHash|_], _}, TicketData, Version) when Ve maybe_add_binders(Hello, _, _, Version) when Version =< {3,3} -> Hello. - create_binders(Context, TicketData) -> create_binders(Context, TicketData, []). %% create_binders(_, [], Acc) -> lists:reverse(Acc); -create_binders(Context, [{_, _, _, PSK, _, HKDF}|T], Acc) -> +create_binders(Context, [#ticket_data{ + psk = PSK, + cipher_suite = {_, HKDF}}|T], Acc) -> FinishedKey = calculate_finished_key(PSK, HKDF), Binder = calculate_binder(FinishedKey, HKDF, Context), create_binders(Context, T, [Binder|Acc]). - %% Removes the binders list from the ClientHello. %% opaque PskBinderEntry<32..255>; %% @@ -2219,6 +2606,18 @@ truncate_client_hello(HelloBin0) -> {Truncated, _} = split_binary(HelloBin0, size(HelloBin0) - BindersSize - 2), Truncated. +maybe_add_early_data_indication(#client_hello{ + extensions = Extensions0} = ClientHello, + EarlyData, + Version) + when Version =:= {3,4} andalso + is_binary(EarlyData) andalso + size(EarlyData) > 0 -> + Extensions = Extensions0#{early_data => + #early_data_indication{}}, + ClientHello#client_hello{extensions = Extensions}; +maybe_add_early_data_indication(ClientHello, _, _) -> + ClientHello. %% The PskBinderEntry is computed in the same way as the Finished %% message (Section 4.4.4) but with the BaseKey being the binder_key @@ -2253,13 +2652,21 @@ update_binders(#client_hello{extensions = maybe_automatic_session_resumption(#state{ ssl_options = #{versions := [Version|_], ciphers := UserSuites, - session_tickets := SessionTickets} = SslOpts0 + early_data := EarlyData, + session_tickets := SessionTickets, + server_name_indication := SNI} = SslOpts0 } = State0) when Version >= {3,4} andalso SessionTickets =:= auto -> AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version), HashAlgos = cipher_hash_algos(AvailableCipherSuites), - UseTicket = tls_client_ticket_store:find_ticket(self(), HashAlgos), + Ciphers = ciphers_for_early_data(AvailableCipherSuites), + %% Find a pair of tickets KeyPair = {Ticket0, Ticket2} where Ticket0 satisfies + %% requirements for early_data and session resumption while Ticket2 can only + %% be used for session resumption. + EarlyDataSize = early_data_size(EarlyData), + KeyPair = tls_client_ticket_store:find_ticket(self(), Ciphers, HashAlgos, SNI, EarlyDataSize), + UseTicket = choose_ticket(KeyPair, EarlyData), tls_client_ticket_store:lock_tickets(self(), [UseTicket]), State = State0#state{ssl_options = SslOpts0#{use_ticket => [UseTicket]}}, {[UseTicket], State}; @@ -2268,6 +2675,144 @@ maybe_automatic_session_resumption(#state{ } = State) -> {UseTicket, State}. +early_data_size(undefined) -> + undefined; +early_data_size(EarlyData) when is_binary(EarlyData) -> + byte_size(EarlyData). + +%% Choose a ticket based on the intention of the user. The first argument is +%% a 2-tuple of ticket keys where the first element refers to a ticket that +%% fulfills all criteria for sending early_data (hash, cipher, early data size). +%% Second argument refers to a ticket that can only be used for session +%% resumption. +choose_ticket({Key, _}, _) when Key =/= undefined -> + Key; +choose_ticket({_, Key}, EarlyData) when EarlyData =:= undefined -> + Key; +choose_ticket(_, _) -> + %% No tickets found that fulfills the original intention of the user + %% (sending early_data). It is possible to do session resumption but + %% in that case the configured early data would have to be removed + %% and that would contradict the will of the user. Returning undefined + %% here prevents session resumption instead. + undefined. + +maybe_send_early_data(#state{ + handshake_env = #handshake_env{tls_handshake_history = {Hist, _}}, + protocol_specific = #{sender := _Sender}, + ssl_options = #{versions := [Version|_], + use_ticket := UseTicket, + session_tickets := SessionTickets, + early_data := EarlyData} = _SslOpts0 + } = State0) when Version =:= {3,4} andalso + UseTicket =/= [undefined] andalso + EarlyData =/= undefined -> + %% D.4. Middlebox Compatibility Mode + State1 = maybe_queue_change_cipher_spec(State0, last), + %% Early traffic secret + EarlyDataSize = early_data_size(EarlyData), + case get_pre_shared_key_early_data(SessionTickets, UseTicket) of + {ok, {PSK, Cipher, HKDF, MaxSize}} when EarlyDataSize =< MaxSize -> + State2 = calculate_client_early_traffic_secret(Hist, PSK, Cipher, HKDF, State1), + %% Set 0-RTT traffic keys for sending early_data and EndOfEarlyData + State3 = ssl_record:step_encryption_state_write(State2), + {ok, encode_early_data(Cipher, State3)}; + {ok, {_, _, _, _MaxSize}} -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, too_much_early_data)}; + {error, Alert} -> + {error, Alert} + end; +maybe_send_early_data(State) -> + {ok, State}. + +encode_early_data(Cipher, + #state{ + flight_buffer = Flight0, + protocol_specific = #{sender := _Sender}, + ssl_options = #{versions := [Version|_], + early_data := EarlyData} = _SslOpts0 + } = State0) -> + #state{connection_states = + #{current_write := + #{security_parameters := SecurityParameters0} = Write0} = ConnectionStates0} = State0, + BulkCipherAlgo = ssl_cipher:bulk_cipher_algorithm(Cipher), + SecurityParameters = SecurityParameters0#security_parameters{ + cipher_type = ?AEAD, + bulk_cipher_algorithm = BulkCipherAlgo}, + Write = Write0#{security_parameters => SecurityParameters}, + ConnectionStates1 = ConnectionStates0#{current_write => Write}, + {BinEarlyData, ConnectionStates} = tls_record:encode_data([EarlyData], Version, ConnectionStates1), + State0#state{connection_states = ConnectionStates, + flight_buffer = Flight0 ++ [BinEarlyData]}. + +maybe_send_end_of_early_data( + #state{ + handshake_env = #handshake_env{early_data_accepted = true}, + protocol_specific = #{sender := _Sender}, + ssl_options = #{versions := [Version|_], + use_ticket := UseTicket, + early_data := EarlyData}, + static_env = #static_env{protocol_cb = Connection} + } = State0) when Version =:= {3,4} andalso + UseTicket =/= [undefined] andalso + EarlyData =/= undefined -> + %% EndOfEarlydata is encrypted with the 0-RTT traffic keys + State1 = Connection:queue_handshake(#end_of_early_data{}, State0), + %% Use handshake keys after EndOfEarlyData is sent + ssl_record:step_encryption_state_write(State1); +maybe_send_end_of_early_data(State) -> + State. + +maybe_check_early_data_indication(EarlyDataIndication, + #state{ + handshake_env = HsEnv, + ssl_options = #{versions := [Version|_], + use_ticket := UseTicket, + early_data := EarlyData} + } = State) when Version =:= {3,4} andalso + UseTicket =/= [undefined] andalso + EarlyData =/= undefined andalso + EarlyDataIndication =/= undefined -> + signal_user_early_data(State, accepted), + State#state{handshake_env = HsEnv#handshake_env{early_data_accepted = true}}; +maybe_check_early_data_indication(EarlyDataIndication, + #state{ + protocol_specific = #{sender := _Sender}, + ssl_options = #{versions := [Version|_], + use_ticket := UseTicket, + early_data := EarlyData} = _SslOpts0 + } = State) when Version =:= {3,4} andalso + UseTicket =/= [undefined] andalso + EarlyData =/= undefined andalso + EarlyDataIndication =:= undefined -> + signal_user_early_data(State, rejected), + %% Use handshake keys if early_data is rejected. + ssl_record:step_encryption_state_write(State); +maybe_check_early_data_indication(_, State) -> + %% Use handshake keys if there is no early_data. + ssl_record:step_encryption_state_write(State). + +signal_user_early_data(#state{ + connection_env = + #connection_env{ + user_application = {_, User}}, + static_env = + #static_env{ + socket = Socket, + protocol_cb = Connection, + transport_cb = Transport, + trackers = Trackers}} = State, + Result) -> + CPids = Connection:pids(State), + SslSocket = Connection:socket(CPids, Transport, Socket, Trackers), + User ! {ssl, SslSocket, {early_data, Result}}. + +handle_early_data(State, enabled, #early_data_indication{}) -> + %% Accept early data + HsEnv = (State#state.handshake_env)#handshake_env{early_data_accepted = true}, + State#state{handshake_env = HsEnv}; +handle_early_data(State, _, _) -> + State. cipher_hash_algos(Ciphers) -> Fun = fun(Cipher) -> @@ -2276,6 +2821,14 @@ cipher_hash_algos(Ciphers) -> end, lists:map(Fun, Ciphers). +ciphers_for_early_data(CipherSuites0) -> + %% Use only supported TLS 1.3 cipher suites + Supported = lists:filter(fun(CipherSuite) -> + lists:member(CipherSuite, tls_v1:exclusive_suites(4)) end, + CipherSuites0), + %% Return supported block cipher algorithms + lists:map(fun(#{cipher := Cipher}) -> Cipher end, + lists:map(fun ssl_cipher_format:suite_bin_to_map/1, Supported)). get_ticket_data(_, undefined, _) -> undefined; @@ -2293,25 +2846,51 @@ process_user_tickets(UseTicket) -> process_user_tickets([], Acc, _) -> lists:reverse(Acc); process_user_tickets([H|T], Acc, N) -> - #{hkdf := HKDF, - sni := _SNI, - psk := PSK, - timestamp := Timestamp, - ticket := NewSessionTicket} = erlang:binary_to_term(H), + case process_ticket(H, N) of + error -> + process_user_tickets(T, Acc, N + 1); + TicketData -> + process_user_tickets(T, [TicketData|Acc], N + 1) + end. + +%% Used when session_tickets = manual +process_ticket(#{cipher_suite := CipherSuite, + sni := _SNI, %% TODO user's responsibility to handle SNI? + psk := PSK, + timestamp := Timestamp, + ticket := NewSessionTicket}, N) -> #new_session_ticket{ ticket_lifetime = _LifeTime, ticket_age_add = AgeAdd, ticket_nonce = Nonce, ticket = Ticket, - extensions = _Extensions + extensions = Extensions } = NewSessionTicket, TicketAge = erlang:system_time(seconds) - Timestamp, ObfuscatedTicketAge = obfuscate_ticket_age(TicketAge, AgeAdd), Identity = #psk_identity{ identity = Ticket, obfuscated_ticket_age = ObfuscatedTicketAge}, - process_user_tickets(T, [{undefined, N, Identity, PSK, Nonce, HKDF}|Acc], N + 1). - + MaxEarlyData = get_max_early_data(Extensions), + #ticket_data{ + key = undefined, + pos = N, + identity = Identity, + psk = PSK, + nonce = Nonce, + cipher_suite = CipherSuite, + max_size = MaxEarlyData}; +process_ticket(_, _) -> + error. + +get_max_early_data(Extensions) -> + EarlyDataIndication = maps:get(early_data, Extensions, undefined), + case EarlyDataIndication of + undefined -> + undefined; + #early_data_indication_nst{indication = MaxSize} -> + MaxSize + end. %% The "obfuscated_ticket_age" %% field of each PskIdentity contains an obfuscated version of the @@ -2320,3 +2899,53 @@ process_user_tickets([H|T], Acc, N) -> %% (see Section 4.6.1), modulo 2^32. obfuscate_ticket_age(TicketAge, AgeAdd) -> (TicketAge + AgeAdd) rem round(math:pow(2,32)). + +path_validate([{TrustedCert, Path}], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) -> + path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, + CRLDbHandle, Version, SslOptions, CertExt); +path_validate([{TrustedCert, Path} | Rest], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) -> + case path_validation(TrustedCert, Path, ServerName, + Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) of + {ok, _} = Result -> + Result; + {error, _} -> + path_validate(Rest, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) + end. + +path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version, + #{verify_fun := VerifyFun, + customize_hostname_check := CustomizeHostnameCheck, + crl_check := CrlCheck, + log_level := LogLevel, + signature_algs := SignAlgos, + signature_algs_cert := SignAlgosCert, + depth := Depth}, + #{cert_ext := CertExt, + ocsp_responder_certs := OcspResponderCerts, + ocsp_state := OcspState}) -> + ValidationFunAndState = + ssl_handshake:validation_fun_and_state(VerifyFun, #{role => Role, + certdb => CertDbHandle, + certdb_ref => CertDbRef, + server_name => ServerName, + customize_hostname_check => + CustomizeHostnameCheck, + crl_check => CrlCheck, + crl_db => CRLDbHandle, + signature_algs => filter_tls13_algs(SignAlgos), + signature_algs_cert => + filter_tls13_algs(SignAlgosCert), + version => Version, + issuer => TrustedCert, + cert_ext => CertExt, + ocsp_responder_certs => OcspResponderCerts, + ocsp_state => OcspState + }, + Path, LogLevel), + Options = [{max_path_length, Depth}, + {verify_fun, ValidationFunAndState}], + public_key:pkix_path_validation(TrustedCert, Path, Options). |