aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl46
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl165
-rw-r--r--lib/ssl/src/tls_handshake_1_3.hrl5
3 files changed, 148 insertions, 68 deletions
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
index 4795ade2a1..9f7a82223e 100644
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -159,21 +159,22 @@ negotiated(internal,
#state{connection_states = ConnectionStates0,
session = #session{session_id = SessionId,
own_certificate = OwnCert},
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
ssl_options = #ssl_options{} = SslOpts,
key_share = KeyShare,
tls_handshake_history = HHistory0,
- static_env = #static_env{socket = Socket,
- transport_cb = Transport}}, _Module) ->
-
+ private_key = CertPrivateKey,
+ static_env = #static_env{
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ socket = Socket,
+ transport_cb = Transport}}, _Module) ->
%% Create server_hello
%% Extensions: supported_versions, key_share, (pre_shared_key)
ServerHello = tls_handshake_1_3:server_hello(SessionId, KeyShare,
ConnectionStates0, Map),
%% Update handshake_history (done in encode!)
%% Encode handshake
- {BinMsg, ConnectionStates, HHistory} =
+ {BinMsg, ConnectionStates1, HHistory1} =
tls_connection:encode_handshake(ServerHello, {3,4}, ConnectionStates0, HHistory0),
%% Send server_hello
tls_connection:send(Transport, Socket, BinMsg),
@@ -187,7 +188,7 @@ negotiated(internal,
ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}),
#{security_parameters := SecParamsR} =
- ssl_record:pending_connection_state(ConnectionStates, read),
+ ssl_record:pending_connection_state(ConnectionStates1, read),
#security_parameters{prf_algorithm = HKDFAlgo,
cipher_suite = CipherSuite} = SecParamsR,
@@ -196,6 +197,7 @@ negotiated(internal,
ClientKey = maps:get(client_share, Map), %% Raw data?! What about EC?
SelectedGroup = maps:get(group, Map),
+ SignatureScheme = maps:get(sign_alg, Map),
PrivateKey = get_server_private_key(KeyShare), %% #'ECPrivateKey'{}
@@ -203,11 +205,11 @@ negotiated(internal,
HandshakeSecret = tls_v1:key_schedule(handshake_secret, HKDFAlgo, IKM, EarlySecret),
%% Calculate [sender]_handshake_traffic_secret
- {Messages, _} = HHistory,
+ {Messages1, _} = HHistory1,
ClientHSTrafficSecret =
- tls_v1:client_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)),
+ tls_v1:client_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages1)),
ServerHSTrafficSecret =
- tls_v1:server_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)),
+ tls_v1:server_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages1)),
%% Calculate traffic keys
#{cipher := Cipher} = ssl_cipher_format:suite_definition(CipherSuite),
@@ -215,8 +217,8 @@ negotiated(internal,
{WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ServerHSTrafficSecret),
%% Update pending connection state
- PendingRead0 = ssl_record:pending_connection_state(ConnectionStates0, read),
- PendingWrite0 = ssl_record:pending_connection_state(ConnectionStates0, write),
+ PendingRead0 = ssl_record:pending_connection_state(ConnectionStates1, read),
+ PendingWrite0 = ssl_record:pending_connection_state(ConnectionStates1, write),
PendingRead = update_conn_state(PendingRead0, HandshakeSecret, ReadKey, ReadIV),
PendingWrite = update_conn_state(PendingWrite0, HandshakeSecret, WriteKey, WriteIV),
@@ -224,16 +226,28 @@ negotiated(internal,
%% Update pending and copy to current (activate)
%% All subsequent handshake messages are encrypted
%% ([sender]_handshake_traffic_secret)
- ConnectionStates1 = ConnectionStates0#{current_read => PendingRead,
+ ConnectionStates2 = ConnectionStates1#{current_read => PendingRead,
current_write => PendingWrite,
pending_read => PendingRead,
pending_write => PendingWrite},
- %% Create Certificate, CertificateVerify
+ %% Create Certificate
Certificate = tls_handshake_1_3:certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server),
- io:format("### Certificate: ~p~n", [Certificate]),
- %% CertificateVerify = tls_handshake_1_3:certificate_verify(),
+ %% Encode Certificate
+ {_CertificateBin, _ConnectionStates2, HHistory2} =
+ tls_connection:encode_handshake(ServerHello, {3,4}, ConnectionStates1, HHistory1),
+
+ %% Create CertificateVerify
+
+ {Messages2, _} = HHistory2,
+
+ %% Use selected signature_alg from here, HKDF only used for key_schedule
+ CertificateVerify = tls_handshake_1_3:certificate_verify(OwnCert, CertPrivateKey, SignatureScheme,
+ Messages2, server),
+ io:format("### CertificateVerify: ~p~n", [CertificateVerify]),
+
+ %% Encode CertificateVerify
%% Send Certificate, CertifricateVerify
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 8c302d8d57..cbe9550e70 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -39,6 +39,7 @@
%% Create handshake messages
-export([certificate/5,
+ certificate_verify/5,
server_hello/4]).
%%====================================================================
@@ -78,6 +79,23 @@ certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, server) ->
?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {server_has_no_suitable_certificates, Error})
end.
+%% TODO: use maybe monad for error handling!
+certificate_verify(OwnCert, PrivateKey, SignatureScheme, Messages, server) ->
+ {HashAlgo, _, _} =
+ ssl_cipher:scheme_to_components(SignatureScheme),
+
+ %% Transcript-Hash(Handshake Context, Certificate)
+ Context = [Messages, OwnCert],
+ THash = tls_v1:transcript_hash(Context, HashAlgo),
+
+ Signature = digitally_sign(THash, <<"TLS 1.3, server CertificateVerify">>,
+ HashAlgo, PrivateKey),
+
+ #certificate_verify_1_3{
+ algorithm = SignatureScheme,
+ signature = Signature
+ }.
+
%%====================================================================
%% Encode handshake
%%====================================================================
@@ -223,6 +241,38 @@ certificate_entry(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
+digitally_sign(THash, Context, HashAlgo, PrivateKey = #'RSAPrivateKey'{}) ->
+ Content = build_content(Context, THash),
+
+ %% The length of the Salt MUST be equal to the length of the output
+ %% of the digest algorithm.
+ PadLen = ssl_cipher:hash_size(HashAlgo),
+
+ public_key:sign(Content, HashAlgo, PrivateKey,
+ [{rsa_padding, rsa_pkcs1_pss_padding},
+ {rsa_pss_saltlen, PadLen}]).
+
+
+build_content(Context, THash) ->
+ <<" ",
+ " ",
+ Context/binary,?BYTE(0),THash/binary>>.
%%====================================================================
%% Handle handshake messages
@@ -263,15 +313,15 @@ handle_client_hello(#client_hello{cipher_suites = ClientCiphers,
Cipher = Maybe(select_cipher_suite(ClientCiphers, ServerCiphers)),
Group = Maybe(select_server_group(ServerGroups, ClientGroups)),
Maybe(validate_key_share(ClientGroups, ClientShares)),
+
ClientPubKey = Maybe(get_client_public_key(Group, ClientShares)),
- %% Handle certificate
- {PublicKeyAlgo, SignAlgo} = get_certificate_params(Cert),
+ {PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert),
%% Check if client supports signature algorithm of server certificate
- Maybe(check_cert_sign_algo(SignAlgo, ClientSignAlgs, ClientSignAlgsCert)),
+ Maybe(check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, ClientSignAlgsCert)),
- %% Check if server supports
+ %% Select signature algorithm (used in CertificateVerify message).
SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs)),
%% Generate server_share
@@ -294,9 +344,9 @@ handle_client_hello(#client_hello{cipher_suites = ClientCiphers,
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups);
{Ref, illegal_parameter} ->
?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
- {Ref, {client_hello_retry_request, _Group0}} ->
+ {Ref, {hello_retry_request, _Group0}} ->
%% TODO
- exit({client_hello_retry_request, not_implemented});
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, "hello_retry_request not implemented");
{Ref, no_suitable_cipher} ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher);
{Ref, {insufficient_security, no_suitable_signature_algorithm}} ->
@@ -353,8 +403,13 @@ get_client_public_key(Group, ClientShares) ->
{value, {_, _, ClientPublicKey}} ->
{ok, ClientPublicKey};
false ->
- %% ClientHelloRetryRequest
- {error, {client_hello_retry_request, Group}}
+ %% 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.
+ {error, {hello_retry_request, Group}}
end.
select_cipher_suite([], _) ->
@@ -379,22 +434,28 @@ select_cipher_suite([Cipher|ClientCiphers], ServerCiphers) ->
%% If no "signature_algorithms_cert" extension is
%% present, then the "signature_algorithms" extension also applies to
%% signatures appearing in certificates.
-check_cert_sign_algo(SignAlgo, ClientSignAlgs, undefined) ->
- maybe_lists_member(SignAlgo, ClientSignAlgs,
- {insufficient_security, no_suitable_signature_algorithm});
-check_cert_sign_algo(SignAlgo, _, ClientSignAlgsCert) ->
- maybe_lists_member(SignAlgo, ClientSignAlgsCert,
- {insufficient_security, no_suitable_signature_algorithm}).
+
+%% 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}};
-%% TODO: Implement check for ellipctic curves!
+%% 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),
- case PublicKeyAlgo =:= rsa andalso
- ((S =:= rsa_pkcs1) orelse (S =:= rsa_pss_rsae) orelse (S =:= rsa_pss_pss)) andalso
+ %% RSASSA-PKCS1-v1_5 and Legacy algorithms are not defined for use in signed
+ %% TLS handshake messages: filter sha-1 and rsa_pkcs1.
+ case ((PublicKeyAlgo =:= rsa andalso S =:= rsa_pss_rsae)
+ orelse (PublicKeyAlgo =:= rsa_pss andalso S =:= rsa_pss_rsae))
+ andalso
lists:member(C, ServerSignAlgs) of
true ->
{ok, C};
@@ -403,51 +464,51 @@ select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) ->
end.
-maybe_lists_member(Elem, List, Error) ->
- case lists:member(Elem, List) of
+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;
- false ->
- {error, Error}
+ _Else ->
+ do_check_cert_sign_algo(SignAlgo, SignHash, T)
end.
-%% TODO: test with ecdsa, rsa_pss_rsae, rsa_pss_pss
+
+%% 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),
- SignAlgo = public_key:pkix_sign_types(SignAlgo0),
+ {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),
- Scheme = sign_algo_to_scheme(SignAlgo),
- {PublicKeyAlgo, Scheme}.
-
-sign_algo_to_scheme({Hash0, Sign0}) ->
- SupportedSchemes = tls_v1:default_signature_schemes({3,4}),
- Hash = case Hash0 of
- sha ->
- sha1;
- H ->
- H
- end,
- Sign = case Sign0 of
- rsa ->
- rsa_pkcs1;
- S ->
- S
- end,
- sign_algo_to_scheme(Hash, Sign, SupportedSchemes).
-%%
-sign_algo_to_scheme(_, _, []) ->
- not_found;
-sign_algo_to_scheme(H, S, [Scheme|T]) ->
- {Hash, Sign, _Curve} = ssl_cipher:scheme_to_components(Scheme),
- case H =:= Hash andalso S =:= Sign of
- true ->
- Scheme;
- false ->
- sign_algo_to_scheme(H, S, T)
- end.
+ {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') ->
diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl
index 42f4766f1a..afe985755c 100644
--- a/lib/ssl/src/tls_handshake_1_3.hrl
+++ b/lib/ssl/src/tls_handshake_1_3.hrl
@@ -203,6 +203,11 @@
certificate_list % CertificateEntry certificate_list<0..2^24-1>;
}).
+-record(certificate_verify_1_3, {
+ algorithm, % SignatureScheme
+ signature % signature<0..2^16-1>
+ }).
+
%% RFC 8446 B.3.4. Ticket Establishment
-record(new_session_ticket, {
ticket_lifetime, %unit32