%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2007-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %%---------------------------------------------------------------------- %% Purpose: Help funtions for handling the TLS 1.3 (specific parts of) %%% TLS handshake protocol %%---------------------------------------------------------------------- -module(tls_handshake_1_3). -include("tls_handshake_1_3.hrl"). -include("ssl_alert.hrl"). -include("ssl_cipher.hrl"). -include("ssl_connection.hrl"). -include("ssl_internal.hrl"). -include("ssl_record.hrl"). -include_lib("public_key/include/public_key.hrl"). %% Encode -export([encode_handshake/1, decode_handshake/2]). %% Create handshake messages -export([certificate/5, certificate_verify/4, encrypted_extensions/0]). -export([do_start/2, do_negotiated/2, do_wait_cert/2, do_wait_cv/2, do_wait_finished/2, do_wait_sh/2, do_wait_ee/2, do_wait_cert_cr/2]). %% crypto:hash(sha256, "HelloRetryRequest"). -define(HELLO_RETRY_REQUEST_RANDOM, <<207,33,173,116,229,154,97,17, 190,29,140,2,30,101,184,145, 194,162,17,22,122,187,140,94, 7,158,9,226,200,168,51,156>>). %%==================================================================== %% Create handshake messages %%==================================================================== server_hello(MsgType, SessionId, KeyShare, ConnectionStates, ALPN) -> #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates, read), Extensions = server_hello_extensions(MsgType, KeyShare, ALPN), #server_hello{server_version = {3,3}, %% legacy_version cipher_suite = SecParams#security_parameters.cipher_suite, compression_method = 0, %% legacy attribute random = server_hello_random(MsgType, SecParams), session_id = SessionId, extensions = Extensions }. %% The server's extensions MUST contain "supported_versions". %% Additionally, it SHOULD contain the minimal set of extensions %% necessary for the client to generate a correct ClientHello pair. As %% with the ServerHello, a HelloRetryRequest MUST NOT contain any %% extensions that were not first offered by the client in its %% ClientHello, with the exception of optionally the "cookie" (see %% Section 4.2.2) extension. server_hello_extensions(hello_retry_request = MsgType, KeyShare, _) -> SupportedVersions = #server_hello_selected_version{selected_version = {3,4}}, Extensions = #{server_hello_selected_version => SupportedVersions}, ssl_handshake:add_server_share(MsgType, Extensions, KeyShare); server_hello_extensions(MsgType, KeyShare, undefined) -> SupportedVersions = #server_hello_selected_version{selected_version = {3,4}}, Extensions = #{server_hello_selected_version => SupportedVersions}, ssl_handshake:add_server_share(MsgType, Extensions, KeyShare); server_hello_extensions(MsgType, KeyShare, ALPN0) -> Extensions0 = ssl_handshake:add_selected_version(#{}), %% {3,4} (TLS 1.3) Extensions1 = ssl_handshake:add_alpn(Extensions0, ALPN0), ssl_handshake:add_server_share(MsgType, Extensions1, KeyShare). server_hello_random(server_hello, #security_parameters{server_random = Random}) -> Random; %% 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 %% SHA-256 of "HelloRetryRequest": %% %% CF 21 AD 74 E5 9A 61 11 BE 1D 8C 02 1E 65 B8 91 %% C2 A2 11 16 7A BB 8C 5E 07 9E 09 E2 C8 A8 33 9C server_hello_random(hello_retry_request, _) -> ?HELLO_RETRY_REQUEST_RANDOM. %% TODO: implement support for encrypted_extensions encrypted_extensions() -> #encrypted_extensions{ extensions = #{} }. certificate_request(SignAlgs0, SignAlgsCert0) -> %% Input arguments contain TLS 1.2 algorithms due to backward compatibility %% reasons. These {Hash, Algo} tuples must be filtered before creating the %% the extensions. SignAlgs = filter_tls13_algs(SignAlgs0), SignAlgsCert = filter_tls13_algs(SignAlgsCert0), Extensions0 = add_signature_algorithms(#{}, SignAlgs), Extensions = add_signature_algorithms_cert(Extensions0, SignAlgsCert), #certificate_request_1_3{ certificate_request_context = <<>>, extensions = Extensions}. add_signature_algorithms(Extensions, SignAlgs) -> Extensions#{signature_algorithms => #signature_algorithms{signature_scheme_list = SignAlgs}}. add_signature_algorithms_cert(Extensions, undefined) -> Extensions; add_signature_algorithms_cert(Extensions, SignAlgsCert) -> Extensions#{signature_algorithms_cert => #signature_algorithms_cert{signature_scheme_list = SignAlgsCert}}. filter_tls13_algs(undefined) -> undefined; filter_tls13_algs(Algo) -> lists:filter(fun is_atom/1, Algo). %% enum { %% X509(0), %% RawPublicKey(2), %% (255) %% } CertificateType; %% %% struct { %% select (certificate_type) { %% case RawPublicKey: %% /* From RFC 7250 ASN.1_subjectPublicKeyInfo */ %% opaque ASN1_subjectPublicKeyInfo<1..2^24-1>; %% %% case X509: %% opaque cert_data<1..2^24-1>; %% }; %% Extension extensions<0..2^16-1>; %% } CertificateEntry; %% %% struct { %% opaque certificate_request_context<0..2^8-1>; %% CertificateEntry certificate_list<0..2^24-1>; %% } Certificate; certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, Role) -> case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of {ok, _, Chain} -> CertList = chain_to_cert_list(Chain), %% If this message is in response to a CertificateRequest, the value of %% certificate_request_context in that message. Otherwise (in the case %%of server authentication), this field SHALL be zero length. {ok, #certificate_1_3{ certificate_request_context = <<>>, certificate_list = CertList}}; {error, Error} when Role =:= server -> {error, {no_suitable_certificates, Error}}; {error, _Error} when Role =:= client -> %% The client MUST send a Certificate message if and only if the server %% has requested client authentication via a CertificateRequest message %% (Section 4.3.2). If the server requests client authentication but no %% suitable certificate is available, the client MUST send a Certificate %% message containing no certificates (i.e., with the "certificate_list" %% field having length 0). {ok, #certificate_1_3{ certificate_request_context = <<>>, certificate_list = []}} end. certificate_verify(PrivateKey, SignatureScheme, #state{connection_states = ConnectionStates, handshake_env = #handshake_env{ tls_handshake_history = {Messages, _}}}, Role) -> #{security_parameters := SecParamsR} = ssl_record:pending_connection_state(ConnectionStates, write), #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR, {HashAlgo, _, _} = ssl_cipher:scheme_to_components(SignatureScheme), Context = lists:reverse(Messages), %% Transcript-Hash uses the HKDF hash function defined by the cipher suite. THash = tls_v1:transcript_hash(Context, HKDFAlgo), ContextString = context_string(Role), %% Digital signatures use the hash function defined by the selected signature %% scheme. case sign(THash, ContextString, HashAlgo, PrivateKey) of {ok, Signature} -> {ok, #certificate_verify_1_3{ algorithm = SignatureScheme, signature = Signature }}; {error, badarg} -> {error, badarg} end. finished(#state{connection_states = ConnectionStates, handshake_env = #handshake_env{ tls_handshake_history = {Messages, _}}}) -> #{security_parameters := SecParamsR, cipher_state := #cipher_state{finished_key = FinishedKey}} = ssl_record:current_connection_state(ConnectionStates, write), #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR, VerifyData = tls_v1:finished_verify_data(FinishedKey, HKDFAlgo, Messages), #finished{ verify_data = VerifyData }. %%==================================================================== %% Encode handshake %%==================================================================== encode_handshake(#certificate_request_1_3{ certificate_request_context = Context, extensions = Exts})-> EncContext = encode_cert_req_context(Context), BinExts = encode_extensions(Exts), {?CERTIFICATE_REQUEST, <>}; encode_handshake(#certificate_1_3{ certificate_request_context = Context, certificate_list = Entries}) -> EncContext = encode_cert_req_context(Context), EncEntries = encode_cert_entries(Entries), {?CERTIFICATE, <>}; encode_handshake(#certificate_verify_1_3{ algorithm = Algorithm, signature = Signature}) -> EncAlgo = encode_algorithm(Algorithm), EncSign = encode_signature(Signature), {?CERTIFICATE_VERIFY, <>}; encode_handshake(#encrypted_extensions{extensions = Exts})-> {?ENCRYPTED_EXTENSIONS, encode_extensions(Exts)}; encode_handshake(#new_session_ticket{ ticket_lifetime = LifeTime, ticket_age_add = Age, ticket_nonce = Nonce, ticket = Ticket, extensions = Exts}) -> TicketSize = byte_size(Ticket), BinExts = encode_extensions(Exts), {?NEW_SESSION_TICKET, <>}; encode_handshake(#end_of_early_data{}) -> {?END_OF_EARLY_DATA, <<>>}; encode_handshake(#key_update{request_update = Update}) -> {?KEY_UPDATE, <>}; encode_handshake(HandshakeMsg) -> ssl_handshake:encode_handshake(HandshakeMsg, {3,4}). %%==================================================================== %% Decode handshake %%==================================================================== decode_handshake(?SERVER_HELLO, <>) when Random =:= ?HELLO_RETRY_REQUEST_RANDOM -> HelloExtensions = ssl_handshake:decode_hello_extensions(Extensions, {3,4}, {Major, Minor}, hello_retry_request), #server_hello{ server_version = {Major,Minor}, random = Random, session_id = Session_ID, cipher_suite = Cipher_suite, compression_method = Comp_method, extensions = HelloExtensions}; decode_handshake(?CERTIFICATE_REQUEST, <>) -> Exts = decode_extensions(EncExts, certificate_request), #certificate_request_1_3{ certificate_request_context = <<>>, extensions = Exts}; decode_handshake(?CERTIFICATE_REQUEST, <>) -> Exts = decode_extensions(EncExts, certificate_request), #certificate_request_1_3{ certificate_request_context = Context, extensions = Exts}; decode_handshake(?CERTIFICATE, <>) -> CertList = decode_cert_entries(Certs), #certificate_1_3{ certificate_request_context = <<>>, certificate_list = CertList }; decode_handshake(?CERTIFICATE, <>) -> CertList = decode_cert_entries(Certs), #certificate_1_3{ certificate_request_context = Context, certificate_list = CertList }; decode_handshake(?CERTIFICATE_VERIFY, <>) -> Algorithm = ssl_cipher:signature_scheme(EncAlgo), #certificate_verify_1_3{ algorithm = Algorithm, signature = Signature}; decode_handshake(?ENCRYPTED_EXTENSIONS, <>) -> #encrypted_extensions{ extensions = decode_extensions(EncExts, encrypted_extensions) }; decode_handshake(?NEW_SESSION_TICKET, <>) -> Exts = decode_extensions(BinExts, encrypted_extensions), #new_session_ticket{ticket_lifetime = LifeTime, ticket_age_add = Age, ticket_nonce = Nonce, ticket = Ticket, extensions = Exts}; decode_handshake(?END_OF_EARLY_DATA, _) -> #end_of_early_data{}; decode_handshake(?KEY_UPDATE, <>) -> #key_update{request_update = Update}; decode_handshake(Tag, HandshakeMsg) -> ssl_handshake:decode_handshake({3,4}, Tag, HandshakeMsg). %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- encode_cert_req_context(<<>>) -> <>; encode_cert_req_context(Bin) -> Size = byte_size(Bin), <>. encode_cert_entries(Entries) -> CertEntryList = encode_cert_entries(Entries, []), Size = byte_size(CertEntryList), <>. encode_cert_entries([], Acc) -> iolist_to_binary(lists:reverse(Acc)); encode_cert_entries([#certificate_entry{data = Data, extensions = Exts} | Rest], Acc) -> DSize = byte_size(Data), BinExts = encode_extensions(Exts), encode_cert_entries(Rest, [<> | Acc]). encode_algorithm(Algo) -> Scheme = ssl_cipher:signature_scheme(Algo), <>. encode_signature(Signature) -> Size = byte_size(Signature), <>. decode_cert_entries(Entries) -> decode_cert_entries(Entries, []). decode_cert_entries(<<>>, Acc) -> lists:reverse(Acc); decode_cert_entries(<>, Acc) -> Exts = decode_extensions(BinExts, certificate_request), decode_cert_entries(Rest, [#certificate_entry{data = Data, extensions = Exts} | Acc]). encode_extensions(Exts)-> ssl_handshake:encode_extensions(extensions_list(Exts)). decode_extensions(Exts, MessageType) -> ssl_handshake:decode_extensions(Exts, {3,4}, MessageType). extensions_list(HelloExtensions) -> [Ext || {_, Ext} <- maps:to_list(HelloExtensions)]. %% TODO: add extensions! chain_to_cert_list(L) -> chain_to_cert_list(L, []). %% chain_to_cert_list([], Acc) -> lists:reverse(Acc); chain_to_cert_list([H|T], Acc) -> chain_to_cert_list(T, [certificate_entry(H)|Acc]). certificate_entry(DER) -> #certificate_entry{ data = DER, extensions = #{} %% Extensions not supported. }. %% The digital signature is then computed over the concatenation of: %% - A string that consists of octet 32 (0x20) repeated 64 times %% - The context string %% - A single 0 byte which serves as the separator %% - The content to be signed %% %% For example, if the transcript hash was 32 bytes of 01 (this length %% would make sense for SHA-256), the content covered by the digital %% signature for a server CertificateVerify would be: %% %% 2020202020202020202020202020202020202020202020202020202020202020 %% 2020202020202020202020202020202020202020202020202020202020202020 %% 544c5320312e332c207365727665722043657274696669636174655665726966 %% 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, badarg} end; sign(THash, Context, HashAlgo, PrivateKey) -> 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 Signature -> {ok, Signature} catch error:badarg -> {error, badarg} end. verify(THash, Context, HashAlgo, Signature, {?'id-ecPublicKey', PublicKey, PublicKeyParams}) -> Content = build_content(Context, THash), try public_key:verify(Content, HashAlgo, Signature, {PublicKey, PublicKeyParams}) of Result -> {ok, Result} catch error:badarg -> {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 Result -> {ok, Result} catch error:badarg -> {error, badarg} end. build_content(Context, THash) -> Prefix = binary:copy(<<32>>, 64), <>. %%==================================================================== %% Handle handshake messages %%==================================================================== %% TLS Server do_start(#client_hello{cipher_suites = ClientCiphers, session_id = SessionId, extensions = Extensions} = _Hello, #state{connection_states = _ConnectionStates0, ssl_options = #ssl_options{ciphers = ServerCiphers, signature_algs = ServerSignAlgs, supported_groups = ServerGroups0, alpn_preferred_protocols = ALPNPreferredProtocols}, session = #session{own_certificate = Cert}} = State0) -> 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), 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)), {Ref,Maybe} = maybe(), try %% Handle ALPN extension if ALPN is configured ALPNProtocol = Maybe(handle_alpn(ALPNPreferredProtocols, ClientALPN)), %% If the server does not select a PSK, then the server independently selects a %% cipher suite, an (EC)DHE group and key share for key establishment, %% and a signature algorithm/certificate pair to authenticate itself to %% the client. Cipher = Maybe(select_cipher_suite(ClientCiphers, ServerCiphers)), Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)), Maybe(validate_client_key_share(ClientGroups, ClientShares)), {PublicKeyAlgo, SignAlgo, SignHash} = 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)), %% Select client public key. If no public key found in ClientShares or %% ClientShares is empty, trigger HelloRetryRequest as we were able %% to find an acceptable set of parameters but the ClientHello does not %% contain sufficient information. {Group, ClientPubKey} = get_client_public_key(Groups, ClientShares), %% Generate server_share KeyShare = ssl_cipher:generate_server_share(Group), State1 = update_start_state(State0, #{cipher => Cipher, key_share => KeyShare, session_id => SessionId, group => Group, sign_alg => SelectedSignAlg, peer_public_key => ClientPubKey, alpn => ALPNProtocol}), %% 4.1.4. Hello Retry Request %% %% The server will send this message in response to a ClientHello %% 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. Maybe(send_hello_retry_request(State1, ClientPubKey, KeyShare, SessionId)) %% TODO: session handling catch {Ref, {insufficient_security, no_suitable_groups}} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups); {Ref, illegal_parameter} -> ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); {Ref, no_suitable_cipher} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher); {Ref, {insufficient_security, no_suitable_signature_algorithm}} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, "No suitable signature algorithm"); {Ref, {insufficient_security, no_suitable_public_key}} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key); {Ref, no_application_protocol} -> ?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL) end; %% TLS Client do_start(#server_hello{cipher_suite = SelectedCipherSuite, session_id = SessionId, extensions = Extensions} = _ServerHello, #state{static_env = #static_env{role = client, host = Host, port = Port, transport_cb = Transport, socket = Socket, session_cache = Cache, session_cache_cb = CacheCb}, handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, tls_handshake_history = _HHistory} = HsEnv, connection_env = CEnv, ssl_options = #ssl_options{ciphers = ClientCiphers, supported_groups = ClientGroups0} = SslOpts, session = #session{own_certificate = Cert} = Session0, connection_states = ConnectionStates0 } = State0) -> ClientGroups = get_supported_groups(ClientGroups0), {Ref,Maybe} = maybe(), try ServerKeyShare = maps:get(key_share, Extensions, undefined), SelectedGroup = get_selected_group(ServerKeyShare), %% Upon receipt of this extension in a HelloRetryRequest, the client %% MUST verify that (1) the selected_group field corresponds to a group %% which was provided in the "supported_groups" extension in the %% original ClientHello and (2) the selected_group field does not %% correspond to a group which was provided in the "key_share" extension %% in the original ClientHello. If either of these checks fails, then %% the client MUST abort the handshake with an "illegal_parameter" %% alert. Maybe(validate_selected_group(SelectedGroup, ClientGroups)), Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)), %% Otherwise, when sending the new ClientHello, the client MUST %% replace the original "key_share" extension with one containing only a %% new KeyShareEntry for the group indicated in the selected_group field %% of the triggering HelloRetryRequest. ClientKeyShare = ssl_cipher:generate_client_shares([SelectedGroup]), Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert, ClientKeyShare), HelloVersion = tls_record:hello_version(SslOpts#ssl_options.versions), %% Update state State1 = update_start_state(State0, #{cipher => SelectedCipherSuite, key_share => ClientKeyShare, session_id => SessionId, group => SelectedGroup}), %% Replace ClientHello1 with a special synthetic handshake message State2 = replace_ch1_with_message_hash(State1), #state{handshake_env = #handshake_env{tls_handshake_history = HHistory}} = State2, {BinMsg, ConnectionStates, Handshake} = tls_connection:encode_handshake(Hello, HelloVersion, ConnectionStates0, HHistory), tls_socket:send(Transport, Socket, BinMsg), ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'handshake', Hello), ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'record', BinMsg), State = State2#state{ connection_states = ConnectionStates, connection_env = CEnv#connection_env{negotiated_version = HelloVersion}, %% Requested version session = Session0#session{session_id = Hello#client_hello.session_id}, handshake_env = HsEnv#handshake_env{tls_handshake_history = Handshake}, key_share = ClientKeyShare}, {State, wait_sh} catch {Ref, {illegal_parameter, Reason}} -> ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, Reason) end. do_negotiated(start_handshake, #state{connection_states = ConnectionStates0, session = #session{session_id = SessionId, own_certificate = OwnCert, ecc = SelectedGroup, sign_alg = SignatureScheme, dh_public_value = ClientPublicKey}, ssl_options = #ssl_options{} = SslOpts, key_share = KeyShare, handshake_env = #handshake_env{tls_handshake_history = _HHistory0, alpn = ALPN}, connection_env = #connection_env{private_key = CertPrivateKey}, static_env = #static_env{ cert_db = CertDbHandle, cert_db_ref = CertDbRef, socket = _Socket, transport_cb = _Transport} } = State0) -> ServerPrivateKey = get_server_private_key(KeyShare), {Ref,Maybe} = maybe(), try %% Create server_hello %% Extensions: supported_versions, key_share, (pre_shared_key) ServerHello = server_hello(server_hello, SessionId, KeyShare, ConnectionStates0, ALPN), {State1, _} = tls_connection:send_handshake(ServerHello, State0), State2 = calculate_handshake_secrets(ClientPublicKey, ServerPrivateKey, SelectedGroup, State1), State3 = ssl_record:step_encryption_state(State2), %% Create EncryptedExtensions EncryptedExtensions = encrypted_extensions(), %% Encode EncryptedExtensions State4 = tls_connection:queue_handshake(EncryptedExtensions, State3), %% Create and send CertificateRequest ({verify, verify_peer}) {State5, NextState} = maybe_send_certificate_request(State4, SslOpts), %% Create Certificate Certificate = Maybe(certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server)), %% Encode Certificate State6 = tls_connection:queue_handshake(Certificate, State5), %% Create CertificateVerify CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme, State6, server)), %% Encode CertificateVerify State7 = tls_connection:queue_handshake(CertificateVerify, State6), %% Create Finished Finished = finished(State7), %% Encode Finished State8 = tls_connection:queue_handshake(Finished, State7), %% Send first flight {State9, _} = tls_connection:send_handshake_flight(State8), {State9, NextState} catch {Ref, badarg} -> ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {digitally_sign, badarg}); {Ref, {no_suitable_certificates, Reason}} -> ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {no_suitable_certificates, Reason}) end. do_wait_cert(#certificate_1_3{} = Certificate, State0) -> {Ref,Maybe} = maybe(), try Maybe(process_certificate(Certificate, State0)) catch {Ref, {certificate_required, State}} -> {?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required), State}; {Ref, {{certificate_unknown, Reason}, State}} -> {?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, Reason), State}; {Ref, {{internal_error, Reason}, State}} -> {?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), State}; {Ref, {{handshake_failure, Reason}, State}} -> {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason), State}; {Ref, {#alert{} = Alert, State}} -> {Alert, State}; {#alert{} = Alert, State} -> {Alert, State} end. do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, State0) -> {Ref,Maybe} = maybe(), try State1 = Maybe(verify_signature_algorithm(State0, CertificateVerify)), Maybe(verify_certificate_verify(State1, CertificateVerify)) catch {Ref, {{bad_certificate, Reason}, State}} -> {?ALERT_REC(?FATAL, ?BAD_CERTIFICATE, {bad_certificate, Reason}), State}; {Ref, {badarg, State}} -> {?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {verify, badarg}), State}; {Ref, {{handshake_failure, Reason}, State}} -> {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {handshake_failure, Reason}), State} end. %% TLS Server do_wait_finished(#finished{verify_data = VerifyData}, #state{static_env = #static_env{role = server}} = State0) -> {Ref,Maybe} = maybe(), try Maybe(validate_client_finished(State0, VerifyData)), State1 = calculate_traffic_secrets(State0), %% Configure traffic keys ssl_record:step_encryption_state(State1) catch {Ref, decrypt_error} -> ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error) end; %% TLS Client do_wait_finished(#finished{verify_data = _VerifyData}, #state{static_env = #static_env{role = client}} = State0) -> {Ref,Maybe} = maybe(), try %% Maybe(validate_client_finished(State0, VerifyData)), %% Maybe send Certificate + CertificateVerify State1 = Maybe(maybe_queue_cert_cert_cv(State0)), Finished = finished(State1), %% Encode Finished State2 = tls_connection:queue_handshake(Finished, State1), %% Send first flight {State3, _} = tls_connection:send_handshake_flight(State2), State4 = calculate_traffic_secrets(State3), %% Configure traffic keys ssl_record:step_encryption_state(State4) catch {Ref, decrypt_error} -> ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error); {Ref, badarg} -> ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {digitally_sign, badarg}); {Ref, {no_suitable_certificates, Reason}} -> ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {no_suitable_certificates, Reason}) end. do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, session_id = SessionId, extensions = Extensions} = ServerHello, #state{key_share = ClientKeyShare0, ssl_options = #ssl_options{ciphers = ClientCiphers, supported_groups = ClientGroups0}} = State0) -> ClientGroups = get_supported_groups(ClientGroups0), ServerKeyShare0 = maps:get(key_share, Extensions, undefined), ClientKeyShare = get_key_shares(ClientKeyShare0), {Ref,Maybe} = maybe(), try %% Go to state 'start' if server replies with 'HelloRetryRequest'. Maybe(maybe_hello_retry_request(ServerHello, State0)), ServerKeyShare = get_key_shares(ServerKeyShare0), Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)), Maybe(validate_server_key_share(ClientGroups, ServerKeyShare)), %% Get server public key {SelectedGroup, ServerPublicKey} = get_server_public_key(ServerKeyShare), {_, ClientPrivateKey} = get_client_private_key([SelectedGroup], ClientKeyShare), %% Update state State1 = update_start_state(State0, #{cipher => SelectedCipherSuite, key_share => ClientKeyShare0, session_id => SessionId, group => SelectedGroup, peer_public_key => ServerPublicKey}), State2 = calculate_handshake_secrets(ServerPublicKey, ClientPrivateKey, SelectedGroup, State1), State3 = ssl_record:step_encryption_state(State2), {State3, wait_ee} catch {Ref, {State, StateName, ServerHello}} -> {State, StateName, ServerHello}; {Ref, {insufficient_security, no_suitable_groups}} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups); {Ref, illegal_parameter} -> ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); {Ref, no_suitable_cipher} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher); {Ref, {insufficient_security, no_suitable_signature_algorithm}} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, "No suitable signature algorithm"); {Ref, {insufficient_security, no_suitable_public_key}} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key) end. do_wait_ee(#encrypted_extensions{extensions = _Extensions}, State0) -> {Ref,_Maybe} = maybe(), try {State0, wait_cert_cr} catch {Ref, {insufficient_security, no_suitable_groups}} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups); {Ref, illegal_parameter} -> ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); {Ref, no_suitable_cipher} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher); {Ref, {insufficient_security, no_suitable_signature_algorithm}} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, "No suitable signature algorithm"); {Ref, {insufficient_security, no_suitable_public_key}} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key) end. do_wait_cert_cr(#certificate_1_3{} = Certificate, State0) -> {Ref,Maybe} = maybe(), try Maybe(process_certificate(Certificate, State0)) catch {Ref, {certificate_required, _State}} -> ?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required); {Ref, {{certificate_unknown, Reason}, _State}} -> ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, Reason); {Ref, {{internal_error, Reason}, _State}} -> ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason); {Ref, {{handshake_failure, Reason}, _State}} -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); {Ref, {#alert{} = Alert, State}} -> {Alert, State} end; do_wait_cert_cr(#certificate_request_1_3{} = CertificateRequest, State0) -> {Ref,Maybe} = maybe(), try Maybe(process_certificate_request(CertificateRequest, State0)) catch {Ref, {certificate_required, _State}} -> ?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required); {Ref, {{certificate_unknown, Reason}, _State}} -> ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, Reason); {Ref, {illegal_parameter, Reason}} -> ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, Reason); {Ref, {{internal_error, Reason}, _State}} -> ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason); {Ref, {{handshake_failure, Reason}, _State}} -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason) end. %% TODO: Remove this function! %% not_implemented(State, Reason) -> %% {error, {not_implemented, State, Reason}}. %% not_implemented(update_secrets, State0, Reason) -> %% State1 = calculate_traffic_secrets(State0), %% State = ssl_record:step_encryption_state(State1), %% {error, {not_implemented, State, Reason}}. %% 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 %% SHA-256 of "HelloRetryRequest": %% %% CF 21 AD 74 E5 9A 61 11 BE 1D 8C 02 1E 65 B8 91 %% C2 A2 11 16 7A BB 8C 5E 07 9E 09 E2 C8 A8 33 9C %% %% Upon receiving a message with type server_hello, implementations MUST %% first examine the Random value and, if it matches this value, process %% it as described in Section 4.1.4). maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = ServerHello, State0) -> {error, {State0, start, ServerHello}}; maybe_hello_retry_request(_, _) -> ok. 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}, ssl_options = #ssl_options{} = _SslOpts, key_share = _KeyShare, handshake_env = #handshake_env{tls_handshake_history = _HHistory0}, static_env = #static_env{ role = client, cert_db = CertDbHandle, cert_db_ref = CertDbRef, socket = _Socket, transport_cb = _Transport} } = State0) -> {Ref,Maybe} = maybe(), try %% Create Certificate Certificate = Maybe(certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, client)), %% Encode Certificate State1 = tls_connection:queue_handshake(Certificate, State0), %% Maybe create and queue CertificateVerify State = Maybe(maybe_queue_cert_verify(Certificate, State1)), {ok, State} catch {Ref, badarg} -> {error, badarg} end. %% Clients MUST send this message whenever authenticating via a certificate %% (i.e., when the Certificate message is non-empty). maybe_queue_cert_verify(#certificate_1_3{certificate_list = []}, State) -> {ok, State}; 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} } = State) -> {Ref,Maybe} = maybe(), try CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme, State, client)), {ok, tls_connection:queue_handshake(CertificateVerify, State)} catch {Ref, badarg} -> {error, badarg} end. %% Recipients of Finished messages MUST verify that the contents are %% correct and if incorrect MUST terminate the connection with a %% "decrypt_error" alert. validate_client_finished(#state{connection_states = ConnectionStates, handshake_env = #handshake_env{ tls_handshake_history = {Messages0, _}}}, VerifyData) -> #{security_parameters := SecParamsR, cipher_state := #cipher_state{finished_key = FinishedKey}} = ssl_record:current_connection_state(ConnectionStates, read), #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR, %% Drop the client's finished message, it is not part of the handshake context %% when the client calculates its finished message. [_|Messages] = Messages0, ControlData = tls_v1:finished_verify_data(FinishedKey, HKDFAlgo, Messages), compare_verify_data(ControlData, VerifyData). compare_verify_data(Data, Data) -> ok; compare_verify_data(_, _) -> {error, decrypt_error}. send_hello_retry_request(#state{connection_states = ConnectionStates0, handshake_env = #handshake_env{alpn = ALPN}} = State0, no_suitable_key, KeyShare, SessionId) -> ServerHello = server_hello(hello_retry_request, SessionId, KeyShare, ConnectionStates0, ALPN), {State1, _} = tls_connection:send_handshake(ServerHello, State0), %% Update handshake history State2 = replace_ch1_with_message_hash(State1), {ok, {State2, start}}; send_hello_retry_request(State0, _, _, _) -> %% Suitable key found. {ok, {State0, negotiated}}. maybe_send_certificate_request(State, #ssl_options{verify = verify_none}) -> {State, wait_finished}; maybe_send_certificate_request(State, #ssl_options{ verify = verify_peer, signature_algs = SignAlgs, signature_algs_cert = SignAlgsCert}) -> CertificateRequest = certificate_request(SignAlgs, SignAlgsCert), {tls_connection:queue_handshake(CertificateRequest, State), wait_cert}. process_certificate_request(#certificate_request_1_3{}, #state{session = #session{own_certificate = 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) -> 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' {ok, {State#state{client_certificate_requested = true, session = Session#session{own_certificate = undefined}}, wait_cert}} end. process_certificate(#certificate_1_3{ certificate_request_context = <<>>, certificate_list = []}, #state{ssl_options = #ssl_options{ fail_if_no_peer_cert = false}} = State) -> {ok, {State, wait_finished}}; process_certificate(#certificate_1_3{ certificate_request_context = <<>>, certificate_list = []}, #state{ssl_options = #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 %% secrets. State1 = calculate_traffic_secrets(State0), State = ssl_record:step_encryption_state(State1), {error, {certificate_required, State}}; process_certificate(#certificate_1_3{certificate_list = Certs0}, #state{ssl_options = #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, {{handshake_failure, "Client certificate uses unsupported signature algorithm"}, 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 %% already stepped into the 'connection' state. update_encryption_state(server, State0) -> State1 = calculate_traffic_secrets(State0), ssl_record:step_encryption_state(State1); update_encryption_state(client, State) -> State. validate_certificate_chain(Certs, CertDbHandle, CertDbRef, SslOptions, CRLDbHandle, Role, Host) -> ServerName = ssl_handshake:server_name(SslOptions#ssl_options.server_name_indication, Host, Role), [PeerCert | ChainCerts ] = Certs, try {TrustedCert, CertPath} = ssl_certificate:trusted_cert_and_path(Certs, CertDbHandle, CertDbRef, SslOptions#ssl_options.partial_chain), ValidationFunAndState = ssl_handshake:validation_fun_and_state(SslOptions#ssl_options.verify_fun, Role, CertDbHandle, CertDbRef, ServerName, SslOptions#ssl_options.customize_hostname_check, SslOptions#ssl_options.crl_check, CRLDbHandle, CertPath), Options = [{max_path_length, SslOptions#ssl_options.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,{asn1, Asn1Reason}} -> %% ASN-1 decode of certificate somehow failed {error, {certificate_unknown, {failed_to_decode_certificate, Asn1Reason}}}; error:OtherReason -> {error, {internal_error, {unexpected_error, OtherReason}}} end. 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}}. convert_certificate_chain(Certs) -> Fun = fun(#certificate_entry{data = Data}) -> {true, Data}; (_) -> false end, lists:filtermap(Fun, Certs). %% 4.4.1. The Transcript Hash %% %% As an exception to this general rule, when the server responds to a %% ClientHello with a HelloRetryRequest, the value of ClientHello1 is %% replaced with a special synthetic handshake message of handshake type %% "message_hash" containing Hash(ClientHello1). I.e., %% %% Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) = %% Hash(message_hash || /* Handshake type */ %% 00 00 Hash.length || /* Handshake message length (bytes) */ %% Hash(ClientHello1) || /* Hash of ClientHello1 */ %% HelloRetryRequest || ... || Mn) %% %% NOTE: Hash.length is used in practice (openssl) and not message length! %% It is most probably a fault in the RFC. replace_ch1_with_message_hash(#state{connection_states = ConnectionStates, handshake_env = #handshake_env{ tls_handshake_history = {[HRR,CH1|HHistory], LM}} = HSEnv} = State0) -> #{security_parameters := SecParamsR} = ssl_record:pending_connection_state(ConnectionStates, read), #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR, MessageHash = message_hash(CH1, HKDFAlgo), State0#state{handshake_env = HSEnv#handshake_env{ tls_handshake_history = {[HRR,MessageHash|HHistory], LM}}}. message_hash(ClientHello1, HKDFAlgo) -> [?MESSAGE_HASH, 0,0,ssl_cipher:hash_size(HKDFAlgo), crypto:hash(HKDFAlgo, ClientHello1)]. calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup, #state{connection_states = ConnectionStates, handshake_env = #handshake_env{ tls_handshake_history = HHistory}} = State0) -> #{security_parameters := SecParamsR} = ssl_record:pending_connection_state(ConnectionStates, read), #security_parameters{prf_algorithm = HKDFAlgo, cipher_suite = CipherSuite} = SecParamsR, %% Calculate handshake_secret PSK = binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo)), EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, PSK}), IKM = calculate_shared_secret(PublicKey, PrivateKey, SelectedGroup), HandshakeSecret = tls_v1:key_schedule(handshake_secret, HKDFAlgo, IKM, EarlySecret), %% 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), %% Calculate Finished Keys ReadFinishedKey = tls_v1:finished_key(ClientHSTrafficSecret, HKDFAlgo), WriteFinishedKey = tls_v1:finished_key(ServerHSTrafficSecret, HKDFAlgo), update_pending_connection_states(State0, HandshakeSecret, ReadKey, ReadIV, ReadFinishedKey, WriteKey, WriteIV, WriteFinishedKey). calculate_traffic_secrets(#state{ static_env = #static_env{role = Role}, connection_states = ConnectionStates, handshake_env = #handshake_env{ tls_handshake_history = HHistory}} = State0) -> #{security_parameters := SecParamsR} = ssl_record:pending_connection_state(ConnectionStates, read), #security_parameters{prf_algorithm = HKDFAlgo, cipher_suite = CipherSuite, master_secret = HandshakeSecret} = SecParamsR, MasterSecret = tls_v1:key_schedule(master_secret, HKDFAlgo, HandshakeSecret), %% Get the correct list messages for the handshake context. Messages = get_handshake_context(Role, HHistory), %% Calculate [sender]_application_traffic_secret_0 ClientAppTrafficSecret0 = tls_v1:client_application_traffic_secret_0(HKDFAlgo, MasterSecret, lists:reverse(Messages)), ServerAppTrafficSecret0 = 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), update_pending_connection_states(State0, MasterSecret, ReadKey, ReadIV, undefined, WriteKey, WriteIV, undefined). get_server_private_key(#key_share_server_hello{server_share = ServerShare}) -> get_private_key(ServerShare). get_private_key(#key_share_entry{ key_exchange = #'ECPrivateKey'{} = PrivateKey}) -> PrivateKey; get_private_key(#key_share_entry{ key_exchange = {_, PrivateKey}}) -> PrivateKey. %% X25519, X448 calculate_shared_secret(OthersKey, MyKey, Group) when is_binary(OthersKey) andalso is_binary(MyKey) andalso (Group =:= x25519 orelse Group =:= x448)-> crypto:compute_key(ecdh, OthersKey, MyKey, Group); %% FFDHE calculate_shared_secret(OthersKey, MyKey, Group) when is_binary(OthersKey) andalso is_binary(MyKey) -> Params = #'DHParameter'{prime = P} = ssl_dh_groups:dh_params(Group), S = public_key:compute_key(OthersKey, MyKey, Params), Size = byte_size(binary:encode_unsigned(P)), ssl_cipher:add_zero_padding(S, Size); %% ECDHE calculate_shared_secret(OthersKey, MyKey = #'ECPrivateKey'{}, _Group) when is_binary(OthersKey) -> Point = #'ECPoint'{point = OthersKey}, public_key:compute_key(Point, MyKey). update_pending_connection_states(#state{ static_env = #static_env{role = server}, connection_states = CS = #{pending_read := PendingRead0, pending_write := PendingWrite0}} = State, HandshakeSecret, ReadKey, ReadIV, ReadFinishedKey, WriteKey, WriteIV, WriteFinishedKey) -> PendingRead = update_connection_state(PendingRead0, HandshakeSecret, ReadKey, ReadIV, ReadFinishedKey), PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret, WriteKey, WriteIV, WriteFinishedKey), State#state{connection_states = CS#{pending_read => PendingRead, pending_write => PendingWrite}}; update_pending_connection_states(#state{ static_env = #static_env{role = client}, connection_states = CS = #{pending_read := PendingRead0, pending_write := PendingWrite0}} = State, HandshakeSecret, ReadKey, ReadIV, ReadFinishedKey, WriteKey, WriteIV, WriteFinishedKey) -> PendingRead = update_connection_state(PendingRead0, HandshakeSecret, WriteKey, WriteIV, WriteFinishedKey), PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret, ReadKey, ReadIV, ReadFinishedKey), State#state{connection_states = CS#{pending_read => PendingRead, pending_write => PendingWrite}}. update_connection_state(ConnectionState = #{security_parameters := SecurityParameters0}, HandshakeSecret, Key, IV, FinishedKey) -> %% Store secret SecurityParameters = SecurityParameters0#security_parameters{ master_secret = HandshakeSecret}, ConnectionState#{security_parameters => SecurityParameters, cipher_state => cipher_init(Key, IV, FinishedKey)}. update_start_state(State, Map) -> Cipher = maps:get(cipher, Map, undefined), KeyShare = maps:get(key_share, Map, undefined), SessionId = maps:get(session_id, Map, undefined), Group = maps:get(group, Map, undefined), SelectedSignAlg = maps:get(sign_alg, Map, undefined), PeerPublicKey = maps:get(peer_public_key, Map, undefined), ALPNProtocol = maps:get(alpn, Map, undefined), update_start_state(State, Cipher, KeyShare, SessionId, Group, SelectedSignAlg, PeerPublicKey, ALPNProtocol). %% update_start_state(#state{connection_states = ConnectionStates0, handshake_env = #handshake_env{} = HsEnv, connection_env = CEnv, session = Session} = State, Cipher, KeyShare, SessionId, Group, SelectedSignAlg, PeerPublicKey, ALPNProtocol) -> #{security_parameters := SecParamsR0} = PendingRead = maps:get(pending_read, ConnectionStates0), #{security_parameters := SecParamsW0} = PendingWrite = maps:get(pending_write, ConnectionStates0), SecParamsR = ssl_cipher:security_parameters_1_3(SecParamsR0, Cipher), SecParamsW = ssl_cipher:security_parameters_1_3(SecParamsW0, Cipher), ConnectionStates = ConnectionStates0#{pending_read => PendingRead#{security_parameters => SecParamsR}, pending_write => PendingWrite#{security_parameters => SecParamsW}}, State#state{connection_states = ConnectionStates, handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol}, key_share = KeyShare, session = Session#session{session_id = SessionId, ecc = Group, sign_alg = SelectedSignAlg, dh_public_value = PeerPublicKey, cipher_suite = Cipher}, connection_env = CEnv#connection_env{negotiated_version = {3,4}}}. cipher_init(Key, IV, FinishedKey) -> #cipher_state{key = Key, iv = IV, finished_key = FinishedKey, tag_len = 16}. %% Get handshake context for verification of CertificateVerify. %% %% Verify CertificateVerify: %% ClientHello (client) (1) %% ServerHello (server) (2) %% EncryptedExtensions (server) (8) %% CertificateRequest (server) (13) %% Certificate (server) (11) %% CertificateVerify (server) (15) %% Finished (server) (20) %% Certificate (client) (11) %% CertificateVerify (client) (15) - Drop! Not included in calculations! get_handshake_context_cv({[<<15,_/binary>>|Messages], _}) -> Messages. %% Get handshake context for traffic key calculation. %% %% Client is authenticated with certificate: %% ClientHello (client) (1) %% ServerHello (server) (2) %% EncryptedExtensions (server) (8) %% CertificateRequest (server) (13) %% Certificate (server) (11) %% CertificateVerify (server) (15) %% Finished (server) (20) %% Certificate (client) (11) - Drop! Not included in calculations! %% CertificateVerify (client) (15) - Drop! Not included in calculations! %% Finished (client) (20) - Drop! Not included in calculations! %% %% Client is authenticated but sends empty certificate: %% ClientHello (client) (1) %% ServerHello (server) (2) %% EncryptedExtensions (server) (8) %% CertificateRequest (server) (13) %% Certificate (server) (11) %% CertificateVerify (server) (15) %% Finished (server) (20) %% Certificate (client) (11) - Drop! Not included in calculations! %% Finished (client) (20) - Drop! Not included in calculations! %% %% Client is not authenticated: %% ClientHello (client) (1) %% ServerHello (server) (2) %% EncryptedExtensions (server) (8) %% Certificate (server) (11) %% CertificateVerify (server) (15) %% Finished (server) (20) %% Finished (client) (20) - Drop! Not included in calculations! %% %% Drop all client messages from the front of the iolist using the property that %% incoming messages are binaries. get_handshake_context(server, {Messages, _}) -> get_handshake_context_server(Messages); get_handshake_context(client, {Messages, _}) -> get_handshake_context_client(Messages). get_handshake_context_server([H|T]) when is_binary(H) -> get_handshake_context_server(T); get_handshake_context_server(L) -> L. get_handshake_context_client([H|T]) when is_list(H) -> get_handshake_context_client(T); get_handshake_context_client(L) -> L. %% If the CertificateVerify message is sent by a server, the signature %% algorithm MUST be one offered in the client's "signature_algorithms" %% extension unless no valid certificate chain can be produced without %% unsupported algorithms %% %% If sent by a client, the signature algorithm used in the signature %% MUST be one of those present in the supported_signature_algorithms %% field of the "signature_algorithms" extension in the %% CertificateRequest message. verify_signature_algorithm(#state{ static_env = #static_env{role = Role}, ssl_options = #ssl_options{ signature_algs = LocalSignAlgs}} = State0, #certificate_verify_1_3{algorithm = PeerSignAlg}) -> case lists:member(PeerSignAlg, LocalSignAlgs) of true -> {ok, maybe_update_selected_sign_alg(State0, PeerSignAlg, Role)}; false -> State1 = calculate_traffic_secrets(State0), State = ssl_record:step_encryption_state(State1), {error, {{handshake_failure, "CertificateVerify uses unsupported signature algorithm"}, State}} end. maybe_update_selected_sign_alg(#state{session = Session} = State, SignAlg, client) -> State#state{session = Session#session{sign_alg = SignAlg}}; 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, #certificate_verify_1_3{ algorithm = SignatureScheme, signature = Signature}) -> #{security_parameters := SecParamsR} = ssl_record:pending_connection_state(ConnectionStates, write), #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR, {HashAlgo, _, _} = ssl_cipher:scheme_to_components(SignatureScheme), Messages = get_handshake_context_cv(HHistory), Context = lists:reverse(Messages), %% Transcript-Hash uses the HKDF hash function defined by the cipher suite. THash = tls_v1:transcript_hash(Context, HKDFAlgo), ContextString = peer_context_string(Role), %% Digital signatures use the hash function defined by the selected signature %% scheme. case verify(THash, ContextString, HashAlgo, Signature, PublicKeyInfo) of {ok, true} -> {ok, {State0, wait_finished}}; {ok, false} -> State1 = calculate_traffic_secrets(State0), State = ssl_record:step_encryption_state(State1), {error, {{handshake_failure, "Failed to verify CertificateVerify"}, State}}; {error, badarg} -> State1 = calculate_traffic_secrets(State0), State = ssl_record:step_encryption_state(State1), {error, {badarg, State}} end. context_string(server) -> <<"TLS 1.3, server CertificateVerify">>; context_string(client) -> <<"TLS 1.3, client CertificateVerify">>. %% Return context string for verifing peer signature peer_context_string(server) -> <<"TLS 1.3, client CertificateVerify">>; peer_context_string(client) -> <<"TLS 1.3, server CertificateVerify">>. %% If there is no overlap between the received %% "supported_groups" and the groups supported by the server, then the %% server MUST abort the handshake with a "handshake_failure" or an %% "insufficient_security" alert. select_common_groups(_, []) -> {error, {insufficient_security, no_suitable_groups}}; select_common_groups(ServerGroups, ClientGroups) -> Fun = fun(E) -> lists:member(E, ClientGroups) end, case lists:filter(Fun, ServerGroups) of [] -> {error, {insufficient_security, no_suitable_groups}}; L -> {ok, L} end. %% RFC 8446 - 4.2.8. Key Share %% This vector MAY be empty if the client is requesting a %% HelloRetryRequest. Each KeyShareEntry value MUST correspond to a %% group offered in the "supported_groups" extension and MUST appear in %% the same order. However, the values MAY be a non-contiguous subset %% of the "supported_groups" extension and MAY omit the most preferred %% groups. %% %% Clients can offer as many KeyShareEntry values as the number of %% supported groups it is offering, each representing a single set of %% key exchange parameters. %% %% Clients MUST NOT offer multiple KeyShareEntry values %% for the same group. Clients MUST NOT offer any KeyShareEntry values %% for groups not listed in the client's "supported_groups" extension. %% Servers MAY check for violations of these rules and abort the %% handshake with an "illegal_parameter" alert if one is violated. validate_client_key_share(_ ,[]) -> ok; validate_client_key_share([], _) -> {error, illegal_parameter}; validate_client_key_share([G|ClientGroups], [{_, G, _}|ClientShares]) -> validate_client_key_share(ClientGroups, ClientShares); validate_client_key_share([_|ClientGroups], [_|_] = ClientShares) -> validate_client_key_share(ClientGroups, ClientShares). %% Verify that selected group is offered by the client. validate_server_key_share([G|_ClientGroups], {_, G, _}) -> ok; validate_server_key_share([_|ClientGroups], {_, _, _} = ServerKeyShare) -> validate_server_key_share(ClientGroups, ServerKeyShare). validate_selected_group(SelectedGroup, [SelectedGroup|_]) -> {error, {illegal_parameter, "Selected group sent by the server shall not correspond to a group" " which was provided in the key_share extension"}}; validate_selected_group(SelectedGroup, ClientGroups) -> case lists:member(SelectedGroup, ClientGroups) of true -> ok; false -> {error, {illegal_parameter, "Selected group sent by the server shall correspond to a group" " which was provided in the supported_groups extension"}} end. get_client_public_key([Group|_] = Groups, ClientShares) -> get_client_public_key(Groups, ClientShares, Group). %% get_client_public_key(_, [], PreferredGroup) -> {PreferredGroup, no_suitable_key}; get_client_public_key([], _, PreferredGroup) -> {PreferredGroup, no_suitable_key}; get_client_public_key([Group|Groups], ClientShares, PreferredGroup) -> case lists:keysearch(Group, 2, ClientShares) of {value, {_, _, ClientPublicKey}} -> {Group, ClientPublicKey}; false -> get_client_public_key(Groups, ClientShares, PreferredGroup) end. get_client_private_key([Group|_] = Groups, ClientShares) -> get_client_private_key(Groups, ClientShares, Group). %% get_client_private_key(_, [], PreferredGroup) -> {PreferredGroup, no_suitable_key}; get_client_private_key([], _, PreferredGroup) -> {PreferredGroup, no_suitable_key}; get_client_private_key([Group|Groups], ClientShares, PreferredGroup) -> case lists:keysearch(Group, 2, ClientShares) of {value, {_, _, {_, ClientPrivateKey}}} -> {Group, ClientPrivateKey}; {value, {_, _, #'ECPrivateKey'{} = ClientPrivateKey}} -> {Group, ClientPrivateKey}; false -> get_client_private_key(Groups, ClientShares, PreferredGroup) end. get_server_public_key({key_share_entry, Group, PublicKey}) -> {Group, PublicKey}. %% RFC 7301 - Application-Layer Protocol Negotiation Extension %% It is expected that a server will have a list of protocols that it %% supports, in preference order, and will only select a protocol if the %% client supports it. In that case, the server SHOULD select the most %% highly preferred protocol that it supports and that is also %% advertised by the client. In the event that the server supports no %% protocols that the client advertises, then the server SHALL respond %% with a fatal "no_application_protocol" alert. handle_alpn(undefined, _) -> {ok, undefined}; handle_alpn([], _) -> {error, no_application_protocol}; handle_alpn([_|_], undefined) -> {ok, undefined}; handle_alpn([ServerProtocol|T], ClientProtocols) -> case lists:member(ServerProtocol, ClientProtocols) of true -> {ok, ServerProtocol}; false -> handle_alpn(T, ClientProtocols) end. select_cipher_suite([], _) -> {error, no_suitable_cipher}; select_cipher_suite([Cipher|ClientCiphers], ServerCiphers) -> case lists:member(Cipher, tls_v1:suites('TLS_v1.3')) andalso lists:member(Cipher, ServerCiphers) of true -> {ok, Cipher}; false -> select_cipher_suite(ClientCiphers, ServerCiphers) end. %% RFC 8446 4.1.3 ServerHello %% A client which receives a cipher suite that was not offered MUST abort the %% handshake with an "illegal_parameter" alert. validate_cipher_suite(Cipher, ClientCiphers) -> case lists:member(Cipher, ClientCiphers) of true -> ok; false -> {error, illegal_parameter} end. %% RFC 8446 (TLS 1.3) %% TLS 1.3 provides two extensions for indicating which signature %% algorithms may be used in digital signatures. The %% "signature_algorithms_cert" extension applies to signatures in %% certificates and the "signature_algorithms" extension, which %% originally appeared in TLS 1.2, applies to signatures in %% CertificateVerify messages. %% %% If no "signature_algorithms_cert" extension is %% present, then the "signature_algorithms" extension also applies to %% signatures appearing in certificates. %% Check if the signature algorithm of the server certificate is supported %% by the client. check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, undefined) -> do_check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs); check_cert_sign_algo(SignAlgo, SignHash, _, ClientSignAlgsCert) -> do_check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgsCert). %% DSA keys are not supported by TLS 1.3 select_sign_algo(dsa, _ClientSignAlgs, _ServerSignAlgs) -> {error, {insufficient_security, no_suitable_public_key}}; select_sign_algo(_, [], _) -> {error, {insufficient_security, no_suitable_signature_algorithm}}; select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) -> {_, S, _} = ssl_cipher:scheme_to_components(C), %% RSASSA-PKCS1-v1_5 and Legacy algorithms are not defined for use in signed %% TLS handshake messages: filter sha-1 and rsa_pkcs1. %% %% RSASSA-PSS RSAE algorithms: If the public key is carried in an X.509 %% certificate, it MUST use the rsaEncryption OID. %% 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 =:= ecdsa andalso S =:= ecdsa)) andalso lists:member(C, ServerSignAlgs) of true -> {ok, C}; false -> select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs) end. do_check_cert_sign_algo(_, _, []) -> {error, {insufficient_security, no_suitable_signature_algorithm}}; do_check_cert_sign_algo(SignAlgo, SignHash, [Scheme|T]) -> {Hash, Sign, _Curve} = ssl_cipher:scheme_to_components(Scheme), case compare_sign_algos(SignAlgo, SignHash, Sign, Hash) of true -> ok; _Else -> do_check_cert_sign_algo(SignAlgo, SignHash, T) end. %% 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; %% rsaEncryption (rsa) allows the key to be used for any of the standard encryption or %% signature schemes. compare_sign_algos(rsa, Hash, Algo, Hash) when Algo =:= rsa_pss_rsae orelse Algo =:= rsa_pkcs1 -> true; compare_sign_algos(Algo, Hash, Algo, Hash) -> true; 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}. %% Note: copied from ssl_handshake public_key_algo(?'id-RSASSA-PSS') -> rsa_pss; public_key_algo(?rsaEncryption) -> rsa; public_key_algo(?'id-ecPublicKey') -> ecdsa; public_key_algo(?'id-dsa') -> dsa. get_signature_scheme_list(undefined) -> undefined; get_signature_scheme_list(#signature_algorithms_cert{ signature_scheme_list = ClientSignatureSchemes}) -> ClientSignatureSchemes; get_signature_scheme_list(#signature_algorithms{ signature_scheme_list = ClientSignatureSchemes}) -> %% Filter unassigned and legacy elements lists:filter(fun (E) -> is_atom(E) andalso E =/= unassigned end, ClientSignatureSchemes). get_supported_groups(#supported_groups{supported_groups = Groups}) -> Groups. get_key_shares(#key_share_client_hello{client_shares = ClientShares}) -> ClientShares; get_key_shares(#key_share_server_hello{server_share = ServerShare}) -> ServerShare. get_selected_group(#key_share_hello_retry_request{selected_group = SelectedGroup}) -> SelectedGroup. maybe() -> Ref = erlang:make_ref(), Ok = fun(ok) -> ok; ({ok,R}) -> R; ({error,Reason}) -> throw({Ref,Reason}) end, {Ref,Ok}.