%%
%% %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: TODO
%%----------------------------------------------------------------------
%% RFC 8446
%% A.1. Client
%%
%% START <----+
%% Send ClientHello | | Recv HelloRetryRequest
%% [K_send = early data] | |
%% v |
%% / WAIT_SH ----+
%% | | Recv ServerHello
%% | | K_recv = handshake
%% Can | V
%% send | WAIT_EE
%% early | | Recv EncryptedExtensions
%% data | +--------+--------+
%% | Using | | Using certificate
%% | PSK | v
%% | | WAIT_CERT_CR
%% | | Recv | | Recv CertificateRequest
%% | | Certificate | v
%% | | | WAIT_CERT
%% | | | | Recv Certificate
%% | | v v
%% | | WAIT_CV
%% | | | Recv CertificateVerify
%% | +> WAIT_FINISHED <+
%% | | Recv Finished
%% \ | [Send EndOfEarlyData]
%% | K_send = handshake
%% | [Send Certificate [+ CertificateVerify]]
%% Can send | Send Finished
%% app data --> | K_send = K_recv = application
%% after here v
%% CONNECTED
%%
%% A.2. Server
%%
%% START <-----+
%% Recv ClientHello | | Send HelloRetryRequest
%% v |
%% RECVD_CH ----+
%% | Select parameters
%% v
%% NEGOTIATED
%% | Send ServerHello
%% | K_send = handshake
%% | Send EncryptedExtensions
%% | [Send CertificateRequest]
%% Can send | [Send Certificate + CertificateVerify]
%% app data | Send Finished
%% after --> | K_send = application
%% here +--------+--------+
%% No 0-RTT | | 0-RTT
%% | |
%% K_recv = handshake | | K_recv = early data
%% [Skip decrypt errors] | +------> WAIT_EOED -+
%% | | Recv | | Recv EndOfEarlyData
%% | | early data | | K_recv = handshake
%% | +------------+ |
%% | |
%% +> WAIT_FLIGHT2 <--------+
%% |
%% +--------+--------+
%% No auth | | Client auth
%% | |
%% | v
%% | WAIT_CERT
%% | Recv | | Recv Certificate
%% | empty | v
%% | Certificate | WAIT_CV
%% | | | Recv
%% | v | CertificateVerify
%% +-> WAIT_FINISHED <---+
%% | Recv Finished
%% | K_recv = application
%% v
%% CONNECTED
-module(tls_connection_1_3).
-include("ssl_alert.hrl").
-include("ssl_connection.hrl").
-include("tls_handshake.hrl").
-include("tls_handshake_1_3.hrl").
%% gen_statem helper functions
-export([start/4,
negotiated/4
]).
start(internal,
#client_hello{} = Hello,
#state{connection_states = _ConnectionStates0,
ssl_options = #ssl_options{ciphers = _ServerCiphers,
signature_algs = _ServerSignAlgs,
signature_algs_cert = _SignatureSchemes, %% TODO: Check??
supported_groups = _ServerGroups0,
versions = _Versions} = SslOpts,
session = #session{own_certificate = Cert}} = State0,
_Module) ->
Env = #{cert => Cert},
case tls_handshake_1_3:handle_client_hello(Hello, SslOpts, Env) of
#alert{} = Alert ->
ssl_connection:handle_own_alert(Alert, {3,4}, start, State0);
M ->
%% update connection_states with cipher
State = update_state(State0, M),
{next_state, negotiated, State, [{next_event, internal, M}]}
end.
%% TODO: move these functions
update_state(#state{connection_states = ConnectionStates0,
session = Session} = State,
#{cipher := Cipher,
key_share := KeyShare,
session_id := SessionId}) ->
#{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,
key_share = KeyShare,
session = Session#session{session_id = SessionId}}.
negotiated(internal,
Map,
#state{connection_states = ConnectionStates0,
session = #session{session_id = SessionId,
own_certificate = OwnCert},
ssl_options = #ssl_options{} = SslOpts,
key_share = KeyShare,
tls_handshake_history = HHistory0,
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, ConnectionStates1, HHistory1} =
tls_connection:encode_handshake(ServerHello, {3,4}, ConnectionStates0, HHistory0),
%% Send server_hello
tls_connection:send(Transport, Socket, BinMsg),
Report = #{direction => outbound,
protocol => 'tls_record',
message => BinMsg},
Msg = #{direction => outbound,
protocol => 'handshake',
message => ServerHello},
ssl_logger:debug(SslOpts#ssl_options.log_level, Msg, #{domain => [otp,ssl,handshake]}),
ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}),
#{security_parameters := SecParamsR} =
ssl_record:pending_connection_state(ConnectionStates1, read),
#security_parameters{prf_algorithm = HKDFAlgo,
cipher_suite = CipherSuite} = SecParamsR,
%% Calculate handshake_secret
EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, <<>>}),
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'{}
IKM = calculate_shared_secret(ClientKey, PrivateKey, SelectedGroup),
HandshakeSecret = tls_v1:key_schedule(handshake_secret, HKDFAlgo, IKM, EarlySecret),
%% Calculate [sender]_handshake_traffic_secret
{Messages1, _} = HHistory1,
ClientHSTrafficSecret =
tls_v1:client_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages1)),
ServerHSTrafficSecret =
tls_v1:server_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages1)),
%% Calculate traffic keys
#{cipher := Cipher} = ssl_cipher_format:suite_definition(CipherSuite),
{ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ClientHSTrafficSecret),
{WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ServerHSTrafficSecret),
%% Update pending connection state
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),
%% Update pending and copy to current (activate)
%% All subsequent handshake messages are encrypted
%% ([sender]_handshake_traffic_secret)
ConnectionStates2 = ConnectionStates1#{current_read => PendingRead,
current_write => PendingWrite,
pending_read => PendingRead,
pending_write => PendingWrite},
%% Create Certificate
Certificate = tls_handshake_1_3:certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server),
%% 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
%% Send finished
%% Next record/Next event
ConnectionStates1.
%% K_send = handshake ???
%% (Send EncryptedExtensions)
%% ([Send CertificateRequest])
%% [Send Certificate + CertificateVerify]
%% Send Finished
%% K_send = application ???
%% Will be called implicitly
%% {Record, State} = Connection:next_record(State2#state{session = Session}),
%% Connection:next_event(wait_flight2, Record, State, Actions),
%% OR
%% Connection:next_event(WAIT_EOED, Record, State, Actions)
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_conn_state(ConnectionState = #{security_parameters := SecurityParameters0},
HandshakeSecret, Key, IV) ->
%% Store secret
SecurityParameters = SecurityParameters0#security_parameters{
master_secret = HandshakeSecret},
ConnectionState#{security_parameters => SecurityParameters,
cipher_state => cipher_init(Key, IV)}.
cipher_init(Key, IV) ->
#cipher_state{key = Key, iv = IV, tag_len = 16}.