aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src/tls_handshake_1_3.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src/tls_handshake_1_3.erl')
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl857
1 files changed, 706 insertions, 151 deletions
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 8a4ad922e1..c29366e717 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -39,14 +39,23 @@
%% Create handshake messages
-export([certificate/5,
certificate_verify/4,
- encrypted_extensions/0,
- server_hello/4]).
+ encrypted_extensions/1]).
-export([do_start/2,
do_negotiated/2,
do_wait_cert/2,
do_wait_cv/2,
- do_wait_finished/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
@@ -64,11 +73,24 @@ server_hello(MsgType, SessionId, KeyShare, ConnectionStates) ->
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) ->
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_random(server_hello, #security_parameters{server_random = Random}) ->
Random;
%% For reasons of backward compatibility with middleboxes (see
@@ -79,13 +101,17 @@ server_hello_random(server_hello, #security_parameters{server_random = Random})
%% 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, _) ->
- crypto:hash(sha256, "HelloRetryRequest").
+ ?HELLO_RETRY_REQUEST_RANDOM.
-%% TODO: implement support for encrypted_extensions
-encrypted_extensions() ->
+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{
+ extensions = Extensions
}.
@@ -111,7 +137,7 @@ add_signature_algorithms_cert(Extensions, undefined) ->
Extensions;
add_signature_algorithms_cert(Extensions, SignAlgsCert) ->
Extensions#{signature_algorithms_cert =>
- #signature_algorithms{signature_scheme_list = SignAlgsCert}}.
+ #signature_algorithms_cert{signature_scheme_list = SignAlgsCert}}.
filter_tls13_algs(undefined) -> undefined;
@@ -119,7 +145,6 @@ filter_tls13_algs(Algo) ->
lists:filter(fun is_atom/1, Algo).
-%% TODO: use maybe monad for error handling!
%% enum {
%% X509(0),
%% RawPublicKey(2),
@@ -142,18 +167,28 @@ 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, server) ->
+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.
- #certificate_1_3{
- certificate_request_context = <<>>,
- certificate_list = CertList};
- {error, Error} ->
- ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {server_has_no_suitable_certificates, Error})
+ {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.
@@ -161,7 +196,7 @@ certificate_verify(PrivateKey, SignatureScheme,
#state{connection_states = ConnectionStates,
handshake_env =
#handshake_env{
- tls_handshake_history = {Messages, _}}}, server) ->
+ tls_handshake_history = {Messages, _}}}, Role) ->
#{security_parameters := SecParamsR} =
ssl_record:pending_connection_state(ConnectionStates, write),
#security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
@@ -173,11 +208,11 @@ certificate_verify(PrivateKey, SignatureScheme,
%% 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, <<"TLS 1.3, server CertificateVerify">>,
- HashAlgo, PrivateKey) of
+ case sign(THash, ContextString, HashAlgo, PrivateKey) of
{ok, Signature} ->
{ok, #certificate_verify_1_3{
algorithm = SignatureScheme,
@@ -252,6 +287,21 @@ encode_handshake(HandshakeMsg) ->
%% Decode handshake
%%====================================================================
+
+decode_handshake(?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+ ?BYTE(SID_length), Session_ID:SID_length/binary,
+ Cipher_suite:2/binary, ?BYTE(Comp_method),
+ ?UINT16(ExtLen), Extensions:ExtLen/binary>>)
+ 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, <<?BYTE(0), ?UINT16(Size), EncExts:Size/binary>>) ->
Exts = decode_extensions(EncExts, certificate_request),
#certificate_request_1_3{
@@ -384,6 +434,15 @@ 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, badarg}
+ end;
sign(THash, Context, HashAlgo, PrivateKey) ->
Content = build_content(Context, THash),
@@ -401,7 +460,16 @@ sign(THash, Context, HashAlgo, PrivateKey) ->
end.
-verify(THash, Context, HashAlgo, Signature, PublicKey) ->
+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
@@ -428,15 +496,17 @@ 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 = #ssl_options{ciphers = ServerCiphers,
signature_algs = ServerSignAlgs,
- supported_groups = ServerGroups0},
+ supported_groups = ServerGroups0,
+ alpn_preferred_protocols = ALPNPreferredProtocols,
+ honor_cipher_order = HonorCipherOrder},
session = #session{own_certificate = Cert}} = State0) ->
-
ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined),
ClientGroups = get_supported_groups(ClientGroups0),
ServerGroups = get_supported_groups(ServerGroups0),
@@ -444,23 +514,27 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
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)),
- %% TODO: use library function if it exists
- %% Init the maybe "monad"
{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)),
+ Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)),
Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)),
- Maybe(validate_key_share(ClientGroups, ClientShares)),
+ Maybe(validate_client_key_share(ClientGroups, ClientShares)),
{PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert),
@@ -479,8 +553,14 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
%% Generate server_share
KeyShare = ssl_cipher:generate_server_share(Group),
- State1 = update_start_state(State0, Cipher, KeyShare, SessionId,
- Group, SelectedSignAlg, ClientPubKey),
+ 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
%%
@@ -490,10 +570,7 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
%% the handshake.
Maybe(send_hello_retry_request(State1, ClientPubKey, KeyShare, SessionId))
- %% TODO:
- %% - session handling
- %% - handle extensions: ALPN
- %% (do not handle: NPN, srp, renegotiation_info, ec_point_formats)
+ %% TODO: session handling
catch
{Ref, {insufficient_security, no_suitable_groups}} ->
@@ -505,7 +582,87 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
{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)
+ ?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.
@@ -515,7 +672,7 @@ do_negotiated(start_handshake,
own_certificate = OwnCert,
ecc = SelectedGroup,
sign_alg = SignatureScheme,
- dh_public_value = ClientKey},
+ dh_public_value = ClientPublicKey},
ssl_options = #ssl_options{} = SslOpts,
key_share = KeyShare,
handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
@@ -526,6 +683,8 @@ do_negotiated(start_handshake,
socket = _Socket,
transport_cb = _Transport}
} = State0) ->
+ ServerPrivateKey = get_server_private_key(KeyShare),
+
{Ref,Maybe} = maybe(),
try
@@ -536,12 +695,12 @@ do_negotiated(start_handshake,
{State1, _} = tls_connection:send_handshake(ServerHello, State0),
State2 =
- calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare, State1),
+ calculate_handshake_secrets(ClientPublicKey, ServerPrivateKey, SelectedGroup, State1),
State3 = ssl_record:step_encryption_state(State2),
%% Create EncryptedExtensions
- EncryptedExtensions = encrypted_extensions(),
+ EncryptedExtensions = encrypted_extensions(State2),
%% Encode EncryptedExtensions
State4 = tls_connection:queue_handshake(EncryptedExtensions, State3),
@@ -550,7 +709,7 @@ do_negotiated(start_handshake,
{State5, NextState} = maybe_send_certificate_request(State4, SslOpts),
%% Create Certificate
- Certificate = certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server),
+ Certificate = Maybe(certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server)),
%% Encode Certificate
State6 = tls_connection:queue_handshake(Certificate, State5),
@@ -574,14 +733,16 @@ do_negotiated(start_handshake,
catch
{Ref, badarg} ->
- ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {digitally_sign, 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_client_certificate(Certificate, State0))
+ Maybe(process_certificate(Certificate, State0))
catch
{Ref, {certificate_required, State}} ->
{?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required), State};
@@ -591,6 +752,8 @@ do_wait_cert(#certificate_1_3{} = Certificate, State0) ->
{?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.
@@ -599,8 +762,8 @@ do_wait_cert(#certificate_1_3{} = Certificate, State0) ->
do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, State0) ->
{Ref,Maybe} = maybe(),
try
- Maybe(verify_signature_algorithm(State0, CertificateVerify)),
- Maybe(verify_certificate_verify(State0, CertificateVerify))
+ 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};
@@ -610,20 +773,9 @@ do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, State0) ->
{?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {handshake_failure, Reason}), State}
end.
-
+%% TLS Server
do_wait_finished(#finished{verify_data = VerifyData},
- #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{
- cert_db = _CertDbHandle,
- cert_db_ref = _CertDbRef,
- socket = _Socket,
- transport_cb = _Transport}
- } = State0) ->
+ #state{static_env = #static_env{role = server}} = State0) ->
{Ref,Maybe} = maybe(),
@@ -639,19 +791,237 @@ do_wait_finished(#finished{verify_data = VerifyData},
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) ->
+
+ ALPNProtocol0 = maps:get(alpn, Extensions, undefined),
+ ALPNProtocol = get_alpn(ALPNProtocol0),
+
+ {Ref,_Maybe} = maybe(),
+
+ try
+ %% Update state
+ #state{handshake_env = HsEnv} = State0,
+ State1 = State0#state{handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol}},
+
+ {State1, 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
@@ -684,7 +1054,7 @@ send_hello_retry_request(#state{connection_states = ConnectionStates0} = State0,
ServerHello = server_hello(hello_retry_request, SessionId, KeyShare, ConnectionStates0),
{State1, _} = tls_connection:send_handshake(ServerHello, State0),
- %% TODO: Fix handshake history!
+ %% Update handshake history
State2 = replace_ch1_with_message_hash(State1),
{ok, {State2, start}};
@@ -703,19 +1073,44 @@ maybe_send_certificate_request(State, #ssl_options{
{tls_connection:queue_handshake(CertificateRequest, State), wait_cert}.
-process_client_certificate(#certificate_1_3{
- certificate_request_context = <<>>,
- certificate_list = []},
- #state{ssl_options =
- #ssl_options{
- fail_if_no_peer_cert = false}} = State) ->
+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_client_certificate(#certificate_1_3{
- certificate_request_context = <<>>,
- certificate_list = []},
- #state{ssl_options =
- #ssl_options{
- fail_if_no_peer_cert = true}} = State0) ->
+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
@@ -724,19 +1119,18 @@ process_client_certificate(#certificate_1_3{
State1 = calculate_traffic_secrets(State0),
State = ssl_record:step_encryption_state(State1),
{error, {certificate_required, State}};
-process_client_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) ->
+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
@@ -747,13 +1141,11 @@ process_client_certificate(#certificate_1_3{certificate_list = Certs0},
State = store_peer_cert(State0, PeerCert, PublicKeyInfo),
{ok, {State, wait_cv}};
{error, Reason} ->
- State1 = calculate_traffic_secrets(State0),
- State = ssl_record:step_encryption_state(State1),
+ State = update_encryption_state(Role, State0),
{error, {Reason, State}};
- #alert{} = Alert ->
- State1 = calculate_traffic_secrets(State0),
- State = ssl_record:step_encryption_state(State1),
- {Alert, State}
+ {ok, #alert{} = Alert} ->
+ State = update_encryption_state(Role, State0),
+ {error, {Alert, State}}
end;
false ->
State1 = calculate_traffic_secrets(State0),
@@ -777,6 +1169,17 @@ is_supported_signature_algorithm([BinCert|_], SignAlgs0) ->
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,
@@ -797,12 +1200,12 @@ validate_certificate_chain(Certs, CertDbHandle, CertDbRef, SslOptions, CRLDbHand
{ok, {PublicKeyInfo,_}} ->
{ok, {PeerCert, PublicKeyInfo}};
{error, Reason} ->
- ssl_handshake:handle_path_validation_error(Reason, PeerCert, ChainCerts,
- SslOptions, Options,
- CertDbHandle, CertDbRef)
+ {ok, ssl_handshake:handle_path_validation_error(Reason, PeerCert, ChainCerts,
+ SslOptions, Options,
+ CertDbHandle, CertDbRef)}
end
catch
- error:{badmatch,{asn1, Asn1Reason}} ->
+ error:{badmatch,{error, {asn1, Asn1Reason}}} ->
%% ASN-1 decode of certificate somehow failed
{error, {certificate_unknown, {failed_to_decode_certificate, Asn1Reason}}};
error:OtherReason ->
@@ -861,7 +1264,7 @@ message_hash(ClientHello1, HKDFAlgo) ->
crypto:hash(HKDFAlgo, ClientHello1)].
-calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare,
+calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup,
#state{connection_states = ConnectionStates,
handshake_env =
#handshake_env{
@@ -874,13 +1277,13 @@ calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare,
%% Calculate handshake_secret
PSK = binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo)),
EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, PSK}),
- PrivateKey = get_server_private_key(KeyShare), %% #'ECPrivateKey'{}
- IKM = calculate_shared_secret(ClientKey, PrivateKey, SelectedGroup),
+ 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 =
@@ -899,10 +1302,13 @@ calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare,
ReadKey, ReadIV, ReadFinishedKey,
WriteKey, WriteIV, WriteFinishedKey).
-calculate_traffic_secrets(#state{connection_states = ConnectionStates,
- handshake_env =
- #handshake_env{
- tls_handshake_history = HHistory}} = State0) ->
+
+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,
@@ -913,7 +1319,7 @@ calculate_traffic_secrets(#state{connection_states = ConnectionStates,
tls_v1:key_schedule(master_secret, HKDFAlgo, HandshakeSecret),
%% Get the correct list messages for the handshake context.
- Messages = get_handshake_context(HHistory),
+ Messages = get_handshake_context(Role, HHistory),
%% Calculate [sender]_application_traffic_secret_0
ClientAppTrafficSecret0 =
@@ -942,11 +1348,6 @@ get_private_key(#key_share_entry{
{_, PrivateKey}}) ->
PrivateKey.
-%% TODO: implement EC keys
-get_public_key({?'rsaEncryption', PublicKey, _}) ->
- PublicKey.
-
-
%% X25519, X448
calculate_shared_secret(OthersKey, MyKey, Group)
when is_binary(OthersKey) andalso is_binary(MyKey) andalso
@@ -966,9 +1367,11 @@ calculate_shared_secret(OthersKey, MyKey = #'ECPrivateKey'{}, _Group)
public_key:compute_key(Point, MyKey).
-update_pending_connection_states(#state{connection_states =
- CS = #{pending_read := PendingRead0,
- pending_write := PendingWrite0}} = State,
+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) ->
@@ -977,8 +1380,23 @@ update_pending_connection_states(#state{connection_states =
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
@@ -988,11 +1406,24 @@ update_connection_state(ConnectionState = #{security_parameters := SecurityParam
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, ClientPubKey) ->
+ Group, SelectedSignAlg, PeerPublicKey, ALPNProtocol) ->
#{security_parameters := SecParamsR0} = PendingRead =
maps:get(pending_read, ConnectionStates0),
#{security_parameters := SecParamsW0} = PendingWrite =
@@ -1003,11 +1434,12 @@ update_start_state(#state{connection_states = ConnectionStates0,
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 = ClientPubKey,
+ dh_public_value = PeerPublicKey,
cipher_suite = Cipher},
connection_env = CEnv#connection_env{negotiated_version = {3,4}}}.
@@ -1071,25 +1503,41 @@ get_handshake_context_cv({[<<15,_/binary>>|Messages], _}) ->
%%
%% Drop all client messages from the front of the iolist using the property that
%% incoming messages are binaries.
-get_handshake_context({Messages, _}) ->
- get_handshake_context(Messages);
-get_handshake_context([H|T]) when is_binary(H) ->
- get_handshake_context(T);
-get_handshake_context(L) ->
+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{ssl_options =
- #ssl_options{
- signature_algs = ServerSignAlgs}} = State0,
- #certificate_verify_1_3{algorithm = ClientSignAlg}) ->
- case lists:member(ClientSignAlg, ServerSignAlgs) of
+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;
+ {ok, maybe_update_selected_sign_alg(State0, PeerSignAlg, Role)};
false ->
State1 = calculate_traffic_secrets(State0),
State = ssl_record:step_encryption_state(State1),
@@ -1098,11 +1546,19 @@ verify_signature_algorithm(#state{ssl_options =
end.
-verify_certificate_verify(#state{connection_states = ConnectionStates,
- handshake_env =
- #handshake_env{
- public_key_info = PublicKeyInfo,
- tls_handshake_history = HHistory}} = State0,
+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}) ->
@@ -1120,12 +1576,11 @@ verify_certificate_verify(#state{connection_states = ConnectionStates,
%% Transcript-Hash uses the HKDF hash function defined by the cipher suite.
THash = tls_v1:transcript_hash(Context, HKDFAlgo),
- PublicKey = get_public_key(PublicKeyInfo),
+ ContextString = peer_context_string(Role),
%% Digital signatures use the hash function defined by the selected signature
%% scheme.
- case verify(THash, <<"TLS 1.3, client CertificateVerify">>,
- HashAlgo, Signature, PublicKey) of
+ case verify(THash, ContextString, HashAlgo, Signature, PublicKeyInfo) of
{ok, true} ->
{ok, {State0, wait_finished}};
{ok, false} ->
@@ -1139,6 +1594,19 @@ verify_certificate_verify(#state{connection_states = ConnectionStates,
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
@@ -1172,14 +1640,36 @@ select_common_groups(ServerGroups, ClientGroups) ->
%% 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_key_share(_ ,[]) ->
+validate_client_key_share(_ ,[]) ->
ok;
-validate_key_share([], _) ->
+validate_client_key_share([], _) ->
{error, illegal_parameter};
-validate_key_share([G|ClientGroups], [{_, G, _}|ClientShares]) ->
- validate_key_share(ClientGroups, ClientShares);
-validate_key_share([_|ClientGroups], [_|_] = ClientShares) ->
- validate_key_share(ClientGroups, ClientShares).
+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) ->
@@ -1197,32 +1687,79 @@ get_client_public_key([Group|Groups], ClientShares, PreferredGroup) ->
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_client_public_key(Group, ClientShares) ->
-%% case lists:keysearch(Group, 2, ClientShares) of
-%% {value, {_, _, ClientPublicKey}} ->
-%% ClientPublicKey;
-%% false ->
-%% %% 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.
-%% no_suitable_key
-%% end.
-
-select_cipher_suite([], _) ->
+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) ->
+%% If honor_cipher_order is set to true, use the server's preference for
+%% cipher suite selection.
+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
lists:member(Cipher, ServerCiphers) of
true ->
{ok, Cipher};
false ->
- select_cipher_suite(ClientCiphers, ServerCiphers)
+ select_cipher_suite(false, 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
@@ -1246,15 +1783,20 @@ 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}};
-%% TODO: Implement support for ECDSA keys!
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_rsae))
+ orelse (PublicKeyAlgo =:= rsa_pss andalso S =:= rsa_pss_pss)
+ orelse (PublicKeyAlgo =:= ecdsa andalso S =:= ecdsa))
andalso
lists:member(C, ServerSignAlgs) of
true ->
@@ -1331,7 +1873,20 @@ get_supported_groups(#supported_groups{supported_groups = Groups}) ->
Groups.
get_key_shares(#key_share_client_hello{client_shares = ClientShares}) ->
- 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.
+
+get_alpn(ALPNProtocol0) ->
+ case ssl_handshake:decode_alpn(ALPNProtocol0) of
+ undefined ->
+ undefined;
+ [ALPNProtocol] ->
+ ALPNProtocol
+ end.
maybe() ->
Ref = erlang:make_ref(),