From 40a832093a95aac9bc171616b9f11adf108419c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Thu, 6 Dec 2018 15:52:53 +0100 Subject: ssl: Calculate handshake traffic keys Change-Id: Ifdf8978c58c15313e8a7973cff97dda3458f7721 --- lib/ssl/src/ssl_cipher.erl | 39 ++++++++------ lib/ssl/src/tls_connection_1_3.erl | 103 ++++++++++++++++++++++++++++++++++--- lib/ssl/src/tls_handshake_1_3.erl | 9 ++-- 3 files changed, 121 insertions(+), 30 deletions(-) (limited to 'lib/ssl/src') diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index 32a60fe5aa..0628299d98 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -34,7 +34,7 @@ -include("tls_handshake_1_3.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([security_parameters/2, security_parameters/3, security_parameters_1_3/3, +-export([security_parameters/2, security_parameters/3, security_parameters_1_3/2, cipher_init/3, nonce_seed/2, decipher/6, cipher/5, aead_encrypt/5, aead_decrypt/6, suites/1, all_suites/1, crypto_support_filters/0, chacha_suites/1, anonymous_suites/1, psk_suites/1, psk_suites_anon/1, @@ -44,10 +44,10 @@ hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1, random_bytes/1, calc_mac_hash/4, is_stream_ciphersuite/1, signature_scheme/1, - scheme_to_components/1, hash_size/1]). + scheme_to_components/1, hash_size/1, effective_key_bits/1]). %% RFC 8446 TLS 1.3 --export([generate_client_shares/1, generate_server_share/1]). +-export([generate_client_shares/1, generate_server_share/1, add_zero_padding/2]). -compile(inline). @@ -88,23 +88,13 @@ security_parameters(Version, CipherSuite, SecParams) -> prf_algorithm = prf_algorithm(PrfHashAlg, Version), hash_size = hash_size(Hash)}. -security_parameters_1_3(SecParams, ClientRandom, CipherSuite) -> - #{cipher := Cipher, - mac := Hash, - prf := PrfHashAlg} = ssl_cipher_format:suite_definition(CipherSuite), +security_parameters_1_3(SecParams, CipherSuite) -> + #{cipher := Cipher, prf := PrfHashAlg} = + ssl_cipher_format:suite_definition(CipherSuite), SecParams#security_parameters{ - client_random = ClientRandom, cipher_suite = CipherSuite, bulk_cipher_algorithm = bulk_cipher_algorithm(Cipher), - cipher_type = type(Cipher), - key_size = effective_key_bits(Cipher), - expanded_key_material_length = expanded_key_material(Cipher), - key_material_length = key_material(Cipher), - iv_size = iv_size(Cipher), - mac_algorithm = mac_algorithm(Hash), - prf_algorithm =prf_algorithm(PrfHashAlg, {3,4}), - hash_size = hash_size(Hash), - compression_algorithm = 0}. + prf_algorithm = PrfHashAlg}. %% HKDF hash algorithm %%-------------------------------------------------------------------- -spec cipher_init(cipher_enum(), binary(), binary()) -> #cipher_state{}. @@ -1243,3 +1233,18 @@ generate_key_exchange(secp521r1) -> public_key:generate_key({namedCurve, secp521r1}); generate_key_exchange(FFDHE) -> public_key:generate_key(ssl_dh_groups:dh_params(FFDHE)). + + +%% TODO: Move this functionality to crypto! +%% 7.4.1. Finite Field Diffie-Hellman +%% +%% For finite field groups, a conventional Diffie-Hellman [DH76] +%% computation is performed. The negotiated key (Z) is converted to a +%% byte string by encoding in big-endian form and left-padded with zeros +%% up to the size of the prime. This byte string is used as the shared +%% secret in the key schedule as specified above. +add_zero_padding(Bin, PrimeSize) + when byte_size (Bin) =:= PrimeSize -> + Bin; +add_zero_padding(Bin, PrimeSize) -> + add_zero_padding(<<0, Bin/binary>>, PrimeSize). diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl index 9ff84c703b..d9203de556 100644 --- a/lib/ssl/src/tls_connection_1_3.erl +++ b/lib/ssl/src/tls_connection_1_3.erl @@ -137,16 +137,15 @@ start(internal, %% TODO: move these functions update_state(#state{connection_states = ConnectionStates0, session = Session} = State, - #{client_random := ClientRandom, - cipher := Cipher, + #{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, ClientRandom, Cipher), - SecParamsW = ssl_cipher:security_parameters_1_3(SecParamsW0, ClientRandom, Cipher), + 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}}, @@ -169,10 +168,9 @@ negotiated(internal, %% 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, ConnectionStates, HHistory} = tls_connection:encode_handshake(ServerHello, {3,4}, ConnectionStates0, HHistory0), %% Send server_hello tls_connection:send(Transport, Socket, BinMsg), @@ -184,7 +182,59 @@ negotiated(internal, 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]}), - ok. + + #{security_parameters := SecParamsR} = + ssl_record:pending_connection_state(ConnectionStates, 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), + + 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 + {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_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(ConnectionStates0, read), + PendingWrite0 = ssl_record:pending_connection_state(ConnectionStates0, 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) + ConnectionStates1 = ConnectionStates0#{current_read => PendingRead, + current_write => PendingWrite, + pending_read => PendingRead, + pending_write => PendingWrite}, + + %% Create Certificate, CertificateVerify + + %% Send Certificate, CertifricateVerify + + %% Send finished + + %% Next record/Next event + + ConnectionStates1. %% K_send = handshake ??? %% (Send EncryptedExtensions) @@ -198,3 +248,42 @@ negotiated(internal, %% 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. + + +%% DH +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); +%% ECDH +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}. diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index 4c18e76ad9..0d6ebe953f 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -50,8 +50,7 @@ server_hello(SessionId, KeyShare, ConnectionStates, _Map) -> Extensions = server_hello_extensions(KeyShare), #server_hello{server_version = {3,3}, %% legacy_version cipher_suite = SecParams#security_parameters.cipher_suite, - compression_method = - SecParams#security_parameters.compression_algorithm, + compression_method = 0, %% legacy attribute random = SecParams#security_parameters.server_random, session_id = SessionId, extensions = Extensions @@ -198,7 +197,6 @@ extensions_list(HelloExtensions) -> %%==================================================================== handle_client_hello(#client_hello{cipher_suites = ClientCiphers, - random = Random, session_id = SessionId, extensions = Extensions} = _Hello, #ssl_options{ciphers = ServerCiphers, @@ -233,7 +231,7 @@ 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)), + ClientPubKey = Maybe(get_client_public_key(Group, ClientShares)), %% Handle certificate {PublicKeyAlgo, SignAlgo} = get_certificate_params(Cert), @@ -250,9 +248,8 @@ handle_client_hello(#client_hello{cipher_suites = ClientCiphers, _Ret = #{cipher => Cipher, group => Group, sign_alg => SelectedSignAlg, - %% client_share => ClientPubKey, + client_share => ClientPubKey, key_share => KeyShare, - client_random => Random, session_id => SessionId} %% TODO: -- cgit v1.2.3