aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPéter Dimitrov <[email protected]>2019-02-27 15:38:12 +0100
committerPéter Dimitrov <[email protected]>2019-03-04 16:24:53 +0100
commit85f04feeb89d12443d12c7e233712bf8c299e187 (patch)
tree7ed84baa5f0678be30de46c291ff1d2030f713b0
parent0651ad7a32d6dabbab22993d629e4170e9952167 (diff)
downloadotp-85f04feeb89d12443d12c7e233712bf8c299e187.tar.gz
otp-85f04feeb89d12443d12c7e233712bf8c299e187.tar.bz2
otp-85f04feeb89d12443d12c7e233712bf8c299e187.zip
ssl: Implement state 'wait_cert'
Implement state 'wait_cert' with its handler function do_wait_cert/2. Send CertificateRequest if peer verification is enabled. Send Alert 'certificate required' if client answers with empty Certificate and option 'fail_if_no_peer_cert' is set to true. Change-Id: I72c73bcb6bc68ea60e6fe41cdd29ccfe40d18322
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl28
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl132
2 files changed, 145 insertions, 15 deletions
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
index 71ac6a9310..3c292a43b0 100644
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -110,6 +110,7 @@
%% gen_statem helper functions
-export([start/4,
negotiated/4,
+ wait_cert/4,
wait_finished/4
]).
@@ -140,12 +141,33 @@ negotiated(internal, Map, State0, _Module) ->
case tls_handshake_1_3:do_negotiated(Map, State0) of
#alert{} = Alert ->
ssl_connection:handle_own_alert(Alert, {3,4}, negotiated, State0);
- State ->
- {next_state, wait_finished, State, []}
-
+ {State, NextState} ->
+ {next_state, NextState, State, []}
end.
+wait_cert(internal,
+ #change_cipher_spec{} = ChangeCipherSpec, State0, _Module) ->
+ case tls_handshake_1_3:do_wait_cert(ChangeCipherSpec, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert, State0);
+ {State1, NextState} ->
+ {Record, State} = tls_connection:next_record(State1),
+ tls_connection:next_event(NextState, Record, State)
+ end;
+wait_cert(internal,
+ #certificate_1_3{} = Certificate, State0, _Module) ->
+ case tls_handshake_1_3:do_wait_cert(Certificate, State0) of
+ {#alert{} = Alert, State} ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert, State);
+ {State1, NextState} ->
+ {Record, State} = tls_connection:next_record(State1),
+ tls_connection:next_event(NextState, Record, State)
+ end;
+wait_cert(Type, Msg, State, Connection) ->
+ ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+
+
wait_finished(internal,
#change_cipher_spec{} = ChangeCipherSpec, State0, _Module) ->
case tls_handshake_1_3:do_wait_finished(ChangeCipherSpec, State0) of
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 3bc1290361..9c167e90d6 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -44,6 +44,7 @@
-export([do_start/2,
do_negotiated/2,
+ do_wait_cert/2,
do_wait_finished/2]).
%%====================================================================
@@ -87,6 +88,36 @@ encrypted_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{signature_scheme_list = SignAlgsCert}}.
+
+
+filter_tls13_algs(undefined) -> undefined;
+filter_tls13_algs(Algo) ->
+ lists:filter(fun is_atom/1, Algo).
+
+
%% TODO: use maybe monad for error handling!
%% enum {
%% X509(0),
@@ -497,7 +528,7 @@ do_negotiated(#{client_share := ClientKey,
#state{connection_states = ConnectionStates0,
session = #session{session_id = SessionId,
own_certificate = OwnCert},
- ssl_options = #ssl_options{} = _SslOpts,
+ ssl_options = #ssl_options{} = SslOpts,
key_share = KeyShare,
handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
connection_env = #connection_env{private_key = CertPrivateKey},
@@ -527,28 +558,31 @@ do_negotiated(#{client_share := ClientKey,
%% 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 = certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server),
%% Encode Certificate
- State5 = tls_connection:queue_handshake(Certificate, State4),
+ State6 = tls_connection:queue_handshake(Certificate, State5),
%% Create CertificateVerify
CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme,
- State5, server)),
+ State6, server)),
%% Encode CertificateVerify
- State6 = tls_connection:queue_handshake(CertificateVerify, State5),
+ State7 = tls_connection:queue_handshake(CertificateVerify, State6),
%% Create Finished
- Finished = finished(State6),
+ Finished = finished(State7),
%% Encode Finished
- State7 = tls_connection:queue_handshake(Finished, State6),
+ State8 = tls_connection:queue_handshake(Finished, State7),
%% Send first flight
- {State8, _} = tls_connection:send_handshake_flight(State7),
+ {State9, _} = tls_connection:send_handshake_flight(State8),
- State8
+ {State9, NextState}
catch
{Ref, {state_not_implemented, State}} ->
@@ -557,6 +591,20 @@ do_negotiated(#{client_share := ClientKey,
end.
+do_wait_cert(#change_cipher_spec{}, State0) ->
+ {State0, wait_cert};
+do_wait_cert(#certificate_1_3{} = Certificate, State0) ->
+ {Ref,Maybe} = maybe(),
+ try
+ NextState = Maybe(process_client_certificate(Certificate, State0)),
+ {State0, NextState}
+
+ catch
+ {Ref, {certificate_required, State}} ->
+ {?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required), State}
+ end.
+
+
do_wait_finished(#change_cipher_spec{},
#state{connection_states = _ConnectionStates0,
session = #session{session_id = _SessionId,
@@ -659,6 +707,42 @@ send_hello_retry_request(State0, _, _, _) ->
{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_client_certificate(#certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = []},
+ #state{ssl_options =
+ #ssl_options{
+ fail_if_no_peer_cert = false}}) ->
+ {ok, 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) ->
+
+ %% 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_client_certificate(_, _) ->
+ %% TODO: validate cert
+ {ok, wait_cv}.
+
+
%% 4.4.1. The Transcript Hash
%%
%% As an exception to this general rule, when the server responds to a
@@ -746,10 +830,8 @@ calculate_traffic_secrets(#state{connection_states = ConnectionStates,
MasterSecret =
tls_v1:key_schedule(master_secret, HKDFAlgo, HandshakeSecret),
- {Messages0, _} = HHistory,
-
- %% Drop Client Finish
- [_|Messages] = Messages0,
+ %% Get the correct list messages for the handshake context.
+ Messages = get_handshake_context(HHistory),
%% Calculate [sender]_application_traffic_secret_0
ClientAppTrafficSecret0 =
@@ -845,6 +927,32 @@ cipher_init(Key, IV, FinishedKey) ->
tag_len = 16}.
+%% Handling the case when client is authenticated
+get_handshake_context({[<<20,_/binary>>,<<11,_/binary>>|Messages], _}) ->
+ %% Handshake context messages:
+ %% ClientHello (client) (0)
+ %% ServerHello (server) (1),
+ %% 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!
+ Messages;
+%% Normal case
+get_handshake_context({[_| Messages], _}) ->
+ %% Handshake context messages:
+ %% ClientHello (client) (0)
+ %% ServerHello (server) (1),
+ %% EncryptedExtensions (server) (8)
+ %% Certificate (server) (11)
+ %% CertificateVerify (server) (15),
+ %% Finished (server) (20)
+ %% Finished (client) (20) - Drop! Not included in calculations!
+ Messages.
+
+
%% 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