%%
%% %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: Handles tls1 encryption.
%%----------------------------------------------------------------------
-module(tls_v1).
-include("ssl_cipher.hrl").
-include("ssl_internal.hrl").
-include("ssl_record.hrl").
-export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7, hmac_hash/3,
setup_keys/8, suites/1, prf/5,
ecc_curves/1, ecc_curves/2, oid_to_enum/1, enum_to_oid/1,
default_signature_algs/1, signature_algs/2,
default_signature_schemes/1, signature_schemes/2,
groups/1, groups/2, group_to_enum/1, enum_to_group/1, default_groups/1]).
-export([derive_secret/4, hkdf_expand_label/5, hkdf_extract/3, hkdf_expand/4,
key_schedule/3, key_schedule/4, create_info/3,
external_binder_key/2, resumption_binder_key/2,
client_early_traffic_secret/3, early_exporter_master_secret/3,
client_handshake_traffic_secret/3, server_handshake_traffic_secret/3,
client_application_traffic_secret_0/3, server_application_traffic_secret_0/3,
exporter_master_secret/3, resumption_master_secret/3,
update_traffic_secret/2, calculate_traffic_keys/3,
transcript_hash/2, finished_key/2, finished_verify_data/3]).
-type named_curve() :: sect571r1 | sect571k1 | secp521r1 | brainpoolP512r1 |
sect409k1 | sect409r1 | brainpoolP384r1 | secp384r1 |
sect283k1 | sect283r1 | brainpoolP256r1 | secp256k1 | secp256r1 |
sect239k1 | sect233k1 | sect233r1 | secp224k1 | secp224r1 |
sect193r1 | sect193r2 | secp192k1 | secp192r1 | sect163k1 |
sect163r1 | sect163r2 | secp160k1 | secp160r1 | secp160r2.
-type curves() :: [named_curve()].
-type group() :: secp256r1 | secp384r1 | secp521r1 | ffdhe2048 |
ffdhe3072 | ffdhe4096 | ffdhe6144 | ffdhe8192.
-type supported_groups() :: [group()].
-export_type([curves/0, named_curve/0, group/0, supported_groups/0]).
%%====================================================================
%% Internal application API
%%====================================================================
%% TLS 1.3 ---------------------------------------------------
-spec derive_secret(Secret::binary(), Label::binary(),
Messages::iodata(), Algo::ssl_cipher_format:hash()) -> Key::binary().
derive_secret(Secret, Label, Messages, Algo) ->
Hash = crypto:hash(mac_algo(Algo), Messages),
hkdf_expand_label(Secret, Label,
Hash, ssl_cipher:hash_size(Algo), Algo).
-spec hkdf_expand_label(Secret::binary(), Label0::binary(),
Context::binary(), Length::integer(),
Algo::ssl_cipher_format:hash()) -> KeyingMaterial::binary().
hkdf_expand_label(Secret, Label0, Context, Length, Algo) ->
HkdfLabel = create_info(Label0, Context, Length),
hkdf_expand(Secret, HkdfLabel, Length, Algo).
%% Create info parameter for HKDF-Expand:
%% HKDF-Expand(PRK, info, L) -> OKM
create_info(Label0, Context0, Length) ->
%% struct {
%% uint16 length = Length;
%% opaque label<7..255> = "tls13 " + Label;
%% opaque context<0..255> = Context;
%% } HkdfLabel;
Label1 = << <<"tls13 ">>/binary, Label0/binary>>,
LabelLen = size(Label1),
Label = <<?BYTE(LabelLen), Label1/binary>>,
ContextLen = size(Context0),
Context = <<?BYTE(ContextLen),Context0/binary>>,
Content = <<Label/binary, Context/binary>>,
<<?UINT16(Length), Content/binary>>.
-spec hkdf_extract(MacAlg::ssl_cipher_format:hash(), Salt::binary(),
KeyingMaterial::binary()) -> PseudoRandKey::binary().
hkdf_extract(MacAlg, Salt, KeyingMaterial) ->
hmac_hash(MacAlg, Salt, KeyingMaterial).
-spec hkdf_expand(PseudoRandKey::binary(), ContextInfo::binary(),
Length::integer(), Algo::ssl_cipher_format:hash()) -> KeyingMaterial::binary().
hkdf_expand(PseudoRandKey, ContextInfo, Length, Algo) ->
Iterations = erlang:ceil(Length / ssl_cipher:hash_size(Algo)),
hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, 1, Iterations, <<>>, <<>>).
-spec transcript_hash(Messages::iodata(), Algo::ssl_cipher_format:hash()) -> Hash::binary().
transcript_hash(Messages, Algo) ->
crypto:hash(mac_algo(Algo), Messages).
%% TLS 1.3 ---------------------------------------------------
%% TLS 1.0 -1.2 ---------------------------------------------------
-spec master_secret(integer(), binary(), binary(), binary()) -> binary().
master_secret(PrfAlgo, PreMasterSecret, ClientRandom, ServerRandom) ->
%% RFC 2246 & 4346 && RFC 5246 - 8.1 %% master_secret = PRF(pre_master_secret,
%% "master secret", ClientHello.random +
%% ServerHello.random)[0..47];
prf(PrfAlgo, PreMasterSecret, <<"master secret">>,
[ClientRandom, ServerRandom], 48).
%% TLS 1.0 -1.2 ---------------------------------------------------
-spec finished(client | server, integer(), integer(), binary(), [binary()]) -> binary().
%% TLS 1.0 -1.1 ---------------------------------------------------
finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
when Version == 1; Version == 2; PrfAlgo == ?MD5SHA ->
%% RFC 2246 & 4346 - 7.4.9. Finished
%% struct {
%% opaque verify_data[12];
%% } Finished;
%%
%% verify_data
%% PRF(master_secret, finished_label, MD5(handshake_messages) +
%% SHA-1(handshake_messages)) [0..11];
MD5 = crypto:hash(md5, Handshake),
SHA = crypto:hash(sha, Handshake),
prf(?MD5SHA, MasterSecret, finished_label(Role), [MD5, SHA], 12);
%% TLS 1.0 -1.1 ---------------------------------------------------
%% TLS 1.2 ---------------------------------------------------
finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
when Version == 3 ->
%% RFC 5246 - 7.4.9. Finished
%% struct {
%% opaque verify_data[12];
%% } Finished;
%%
%% verify_data
%% PRF(master_secret, finished_label, Hash(handshake_messages)) [0..11];
Hash = crypto:hash(mac_algo(PrfAlgo), Handshake),
prf(PrfAlgo, MasterSecret, finished_label(Role), Hash, 12).
%% TLS 1.2 ---------------------------------------------------
%% TODO 1.3 finished
-spec certificate_verify(md5sha | sha, integer(), [binary()]) -> binary().
%% TLS 1.0 -1.1 ---------------------------------------------------
certificate_verify(md5sha, _Version, Handshake) ->
MD5 = crypto:hash(md5, Handshake),
SHA = crypto:hash(sha, Handshake),
<<MD5/binary, SHA/binary>>;
%% TLS 1.0 -1.1 ---------------------------------------------------
%% TLS 1.2 ---------------------------------------------------
certificate_verify(HashAlgo, _Version, Handshake) ->
crypto:hash(HashAlgo, Handshake).
%% TLS 1.2 ---------------------------------------------------
-spec setup_keys(integer(), integer(), binary(), binary(), binary(), integer(),
integer(), integer()) -> {binary(), binary(), binary(),
binary(), binary(), binary()}.
%% TLS v1.0 ---------------------------------------------------
setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
KeyMatLen, IVSize)
when Version == 1 ->
%% RFC 2246 - 6.3. Key calculation
%% key_block = PRF(SecurityParameters.master_secret,
%% "key expansion",
%% SecurityParameters.server_random +
%% SecurityParameters.client_random);
%% Then the key_block is partitioned as follows:
%% client_write_MAC_secret[SecurityParameters.hash_size]
%% server_write_MAC_secret[SecurityParameters.hash_size]
%% client_write_key[SecurityParameters.key_material_length]
%% server_write_key[SecurityParameters.key_material_length]
%% client_write_IV[SecurityParameters.IV_size]
%% server_write_IV[SecurityParameters.IV_size]
WantedLength = 2 * (HashSize + KeyMatLen + IVSize),
KeyBlock = prf(?MD5SHA, MasterSecret, "key expansion",
[ServerRandom, ClientRandom], WantedLength),
<<ClientWriteMacSecret:HashSize/binary,
ServerWriteMacSecret:HashSize/binary,
ClientWriteKey:KeyMatLen/binary, ServerWriteKey:KeyMatLen/binary,
ClientIV:IVSize/binary, ServerIV:IVSize/binary>> = KeyBlock,
{ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
ServerWriteKey, ClientIV, ServerIV};
%% TLS v1.0 ---------------------------------------------------
%% TLS v1.1 ---------------------------------------------------
setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
KeyMatLen, IVSize)
when Version == 2 ->
%% RFC 4346 - 6.3. Key calculation
%% key_block = PRF(SecurityParameters.master_secret,
%% "key expansion",
%% SecurityParameters.server_random +
%% SecurityParameters.client_random);
%% Then the key_block is partitioned as follows:
%% client_write_MAC_secret[SecurityParameters.hash_size]
%% server_write_MAC_secret[SecurityParameters.hash_size]
%% client_write_key[SecurityParameters.key_material_length]
%% server_write_key[SecurityParameters.key_material_length]
%%
%% RFC 4346 is incomplete, the client and server IVs have to
%% be generated just like for TLS 1.0
WantedLength = 2 * (HashSize + KeyMatLen + IVSize),
KeyBlock = prf(?MD5SHA, MasterSecret, "key expansion",
[ServerRandom, ClientRandom], WantedLength),
<<ClientWriteMacSecret:HashSize/binary,
ServerWriteMacSecret:HashSize/binary,
ClientWriteKey:KeyMatLen/binary, ServerWriteKey:KeyMatLen/binary,
ClientIV:IVSize/binary, ServerIV:IVSize/binary>> = KeyBlock,
{ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
ServerWriteKey, ClientIV, ServerIV};
%% TLS v1.1 ---------------------------------------------------
%% TLS v1.2 ---------------------------------------------------
setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
KeyMatLen, IVSize)
when Version == 3; Version == 4 ->
%% RFC 5246 - 6.3. Key calculation
%% key_block = PRF(SecurityParameters.master_secret,
%% "key expansion",
%% SecurityParameters.server_random +
%% SecurityParameters.client_random);
%% Then the key_block is partitioned as follows:
%% client_write_MAC_secret[SecurityParameters.hash_size]
%% server_write_MAC_secret[SecurityParameters.hash_size]
%% client_write_key[SecurityParameters.key_material_length]
%% server_write_key[SecurityParameters.key_material_length]
%% client_write_IV[SecurityParameters.fixed_iv_length]
%% server_write_IV[SecurityParameters.fixed_iv_length]
WantedLength = 2 * (HashSize + KeyMatLen + IVSize),
KeyBlock = prf(PrfAlgo, MasterSecret, "key expansion",
[ServerRandom, ClientRandom], WantedLength),
<<ClientWriteMacSecret:HashSize/binary,
ServerWriteMacSecret:HashSize/binary,
ClientWriteKey:KeyMatLen/binary, ServerWriteKey:KeyMatLen/binary,
ClientIV:IVSize/binary, ServerIV:IVSize/binary>> = KeyBlock,
{ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
ServerWriteKey, ClientIV, ServerIV}.
%% TLS v1.2 ---------------------------------------------------
%% TLS v1.3 ---------------------------------------------------
%% RFC 8446 - 7.1. Key Schedule
%%
%% 0
%% |
%% v
%% PSK -> HKDF-Extract = Early Secret
%% |
%% +-----> Derive-Secret(., "ext binder" | "res binder", "")
%% | = binder_key
%% |
%% +-----> Derive-Secret(., "c e traffic", ClientHello)
%% | = client_early_traffic_secret
%% |
%% +-----> Derive-Secret(., "e exp master", ClientHello)
%% | = early_exporter_master_secret
%% v
%% Derive-Secret(., "derived", "")
%% |
%% v
%% (EC)DHE -> HKDF-Extract = Handshake Secret
%% |
%% +-----> Derive-Secret(., "c hs traffic",
%% | ClientHello...ServerHello)
%% | = client_handshake_traffic_secret
%% |
%% +-----> Derive-Secret(., "s hs traffic",
%% | ClientHello...ServerHello)
%% | = server_handshake_traffic_secret
%% v
%% Derive-Secret(., "derived", "")
%% |
%% v
%% 0 -> HKDF-Extract = Master Secret
%% |
%% +-----> Derive-Secret(., "c ap traffic",
%% | ClientHello...server Finished)
%% | = client_application_traffic_secret_0
%% |
%% +-----> Derive-Secret(., "s ap traffic",
%% | ClientHello...server Finished)
%% | = server_application_traffic_secret_0
%% |
%% +-----> Derive-Secret(., "exp master",
%% | ClientHello...server Finished)
%% | = exporter_master_secret
%% |
%% +-----> Derive-Secret(., "res master",
%% ClientHello...client Finished)
%% = resumption_master_secret
-spec key_schedule(early_secret | handshake_secret | master_secret,
atom(), {psk | early_secret | handshake_secret, binary()}) ->
{early_secret | handshake_secret | master_secret, binary()}.
key_schedule(early_secret, Algo, {psk, PSK}) ->
Len = ssl_cipher:hash_size(Algo),
Salt = binary:copy(<<?BYTE(0)>>, Len),
{early_secret, hkdf_extract(Algo, Salt, PSK)};
key_schedule(master_secret, Algo, {handshake_secret, Secret}) ->
Len = ssl_cipher:hash_size(Algo),
IKM = binary:copy(<<?BYTE(0)>>, Len),
Salt = derive_secret(Secret, <<"derived">>, <<>>, Algo),
{master_secret, hkdf_extract(Algo, Salt, IKM)}.
%%
key_schedule(handshake_secret, Algo, IKM, {early_secret, Secret}) ->
Salt = derive_secret(Secret, <<"derived">>, <<>>, Algo),
{handshake_secret, hkdf_extract(Algo, Salt, IKM)}.
-spec external_binder_key(atom(), {early_secret, binary()}) -> binary().
external_binder_key(Algo, {early_secret, Secret}) ->
derive_secret(Secret, <<"ext binder">>, <<>>, Algo).
-spec resumption_binder_key(atom(), {early_secret, binary()}) -> binary().
resumption_binder_key(Algo, {early_secret, Secret}) ->
derive_secret(Secret, <<"res binder">>, <<>>, Algo).
-spec client_early_traffic_secret(atom(), {early_secret, binary()}, iodata()) -> binary().
%% M = ClientHello
client_early_traffic_secret(Algo, {early_secret, Secret}, M) ->
derive_secret(Secret, <<"c e traffic">>, M, Algo).
-spec early_exporter_master_secret(atom(), {early_secret, binary()}, iodata()) -> binary().
%% M = ClientHello
early_exporter_master_secret(Algo, {early_secret, Secret}, M) ->
derive_secret(Secret, <<"e exp master">>, M, Algo).
-spec client_handshake_traffic_secret(atom(), {handshake_secret, binary()}, iodata()) -> binary().
%% M = ClientHello...ServerHello
client_handshake_traffic_secret(Algo, {handshake_secret, Secret}, M) ->
derive_secret(Secret, <<"c hs traffic">>, M, Algo).
-spec server_handshake_traffic_secret(atom(), {handshake_secret, binary()}, iodata()) -> binary().
%% M = ClientHello...ServerHello
server_handshake_traffic_secret(Algo, {handshake_secret, Secret}, M) ->
derive_secret(Secret, <<"s hs traffic">>, M, Algo).
-spec client_application_traffic_secret_0(atom(), {master_secret, binary()}, iodata()) -> binary().
%% M = ClientHello...server Finished
client_application_traffic_secret_0(Algo, {master_secret, Secret}, M) ->
derive_secret(Secret, <<"c ap traffic">>, M, Algo).
-spec server_application_traffic_secret_0(atom(), {master_secret, binary()}, iodata()) -> binary().
%% M = ClientHello...server Finished
server_application_traffic_secret_0(Algo, {master_secret, Secret}, M) ->
derive_secret(Secret, <<"s ap traffic">>, M, Algo).
-spec exporter_master_secret(atom(), {master_secret, binary()}, iodata()) -> binary().
%% M = ClientHello...server Finished
exporter_master_secret(Algo, {master_secret, Secret}, M) ->
derive_secret(Secret, <<"exp master">>, M, Algo).
-spec resumption_master_secret(atom(), {master_secret, binary()}, iodata()) -> binary().
%% M = ClientHello...client Finished
resumption_master_secret(Algo, {master_secret, Secret}, M) ->
derive_secret(Secret, <<"res master">>, M, Algo).
-spec finished_key(binary(), atom()) -> binary().
finished_key(BaseKey, Algo) ->
%% finished_key =
%% HKDF-Expand-Label(BaseKey, "finished", "", Hash.length)
ssl_cipher:hash_size(Algo),
hkdf_expand_label(BaseKey, <<"finished">>, <<>>, ssl_cipher:hash_size(Algo), Algo).
-spec finished_verify_data(binary(), atom(), iodata()) -> binary().
finished_verify_data(FinishedKey, HKDFAlgo, Messages) ->
%% The verify_data value is computed as follows:
%%
%% verify_data =
%% HMAC(finished_key,
%% Transcript-Hash(Handshake Context,
%% Certificate*, CertificateVerify*))
Context = lists:reverse(Messages),
THash = tls_v1:transcript_hash(Context, HKDFAlgo),
tls_v1:hmac_hash(HKDFAlgo, FinishedKey, THash).
%% The next-generation application_traffic_secret is computed as:
%%
%% application_traffic_secret_N+1 =
%% HKDF-Expand-Label(application_traffic_secret_N,
%% "traffic upd", "", Hash.length)
-spec update_traffic_secret(atom(), binary()) -> binary().
update_traffic_secret(Algo, Secret) ->
hkdf_expand_label(Secret, <<"traffic upd">>, <<>>, ssl_cipher:hash_size(Algo), Algo).
%% The traffic keying material is generated from the following input
%% values:
%%
%% - A secret value
%%
%% - A purpose value indicating the specific value being generated
%%
%% - The length of the key being generated
%%
%% The traffic keying material is generated from an input traffic secret
%% value using:
%%
%% [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length)
%% [sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length)
-spec calculate_traffic_keys(atom(), atom(), binary()) -> {binary(), binary()}.
calculate_traffic_keys(HKDFAlgo, Cipher, Secret) ->
Key = hkdf_expand_label(Secret, <<"key">>, <<>>, ssl_cipher:key_material(Cipher), HKDFAlgo),
%% TODO: remove hard coded IV size
IV = hkdf_expand_label(Secret, <<"iv">>, <<>>, 12, HKDFAlgo),
{Key, IV}.
%% TLS v1.3 ---------------------------------------------------
%% TLS 1.0 -1.2 ---------------------------------------------------
-spec mac_hash(integer() | atom(), binary(), integer(), integer(), tls_record:tls_version(),
integer(), binary()) -> binary().
mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor},
Length, Fragment) ->
%% RFC 2246 & 4346 - 6.2.3.1.
%% HMAC_hash(MAC_write_secret, seq_num + TLSCompressed.type +
%% TLSCompressed.version + TLSCompressed.length +
%% TLSCompressed.fragment));
Mac = hmac_hash(Method, Mac_write_secret,
[<<?UINT64(Seq_num), ?BYTE(Type),
?BYTE(Major), ?BYTE(Minor), ?UINT16(Length)>>,
Fragment]),
Mac.
%% TLS 1.0 -1.2 ---------------------------------------------------
%% TODO 1.3 same as above?
-spec suites(1|2|3|4|'TLS_v1.3') -> [ssl_cipher_format:cipher_suite()].
suites(Minor) when Minor == 1; Minor == 2 ->
[
?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
?TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
];
suites(3) ->
[?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,
?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,
?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,
?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,
?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,
?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
%% not supported
%% ?TLS_DH_RSA_WITH_AES_256_GCM_SHA384,
%% ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384,
%% ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256,
%% ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256
] ++ suites(2);
suites(4) ->
[?TLS_AES_256_GCM_SHA384,
?TLS_AES_128_GCM_SHA256,
?TLS_CHACHA20_POLY1305_SHA256
%% Not supported
%% ?TLS_AES_128_CCM_SHA256,
%% ?TLS_AES_128_CCM_8_SHA256
] ++ suites(3);
suites('TLS_v1.3') ->
[?TLS_AES_256_GCM_SHA384,
?TLS_AES_128_GCM_SHA256,
?TLS_CHACHA20_POLY1305_SHA256
%% Not supported
%% ?TLS_AES_128_CCM_SHA256,
%% ?TLS_AES_128_CCM_8_SHA256
].
signature_algs({3, 4}, HashSigns) ->
signature_algs({3, 3}, HashSigns);
signature_algs({3, 3}, HashSigns) ->
CryptoSupports = crypto:supports(),
Hashes = proplists:get_value(hashs, CryptoSupports),
PubKeys = proplists:get_value(public_keys, CryptoSupports),
Supported = lists:foldl(fun({Hash, dsa = Sign} = Alg, Acc) ->
case proplists:get_bool(dss, PubKeys)
andalso proplists:get_bool(Hash, Hashes)
andalso is_pair(Hash, Sign, Hashes)
of
true ->
[Alg | Acc];
false ->
Acc
end;
({Hash, Sign} = Alg, Acc) ->
case proplists:get_bool(Sign, PubKeys)
andalso proplists:get_bool(Hash, Hashes)
andalso is_pair(Hash, Sign, Hashes)
of
true ->
[Alg | Acc];
false ->
Acc
end
end, [], HashSigns),
lists:reverse(Supported).
default_signature_algs({3, 4} = Version) ->
%% TLS 1.3 servers shall be prepared to process TLS 1.2 ClientHellos
%% containing legacy hash-sign tuples.
default_signature_schemes(Version) ++ default_signature_algs({3,3});
default_signature_algs({3, 3} = Version) ->
Default = [%% SHA2
{sha512, ecdsa},
{sha512, rsa},
{sha384, ecdsa},
{sha384, rsa},
{sha256, ecdsa},
{sha256, rsa},
{sha224, ecdsa},
{sha224, rsa},
%% SHA
{sha, ecdsa},
{sha, rsa},
{sha, dsa}],
signature_algs(Version, Default);
default_signature_algs(_) ->
undefined.
signature_schemes(Version, SignatureSchemes) when is_tuple(Version)
andalso Version >= {3, 3} ->
CryptoSupports = crypto:supports(),
Hashes = proplists:get_value(hashs, CryptoSupports),
PubKeys = proplists:get_value(public_keys, CryptoSupports),
Curves = proplists:get_value(curves, CryptoSupports),
RSAPSSSupported = lists:member(rsa_pkcs1_pss_padding,
proplists:get_value(rsa_opts, CryptoSupports)),
Fun = fun (Scheme, Acc) when is_atom(Scheme) ->
{Hash0, Sign0, Curve} =
ssl_cipher:scheme_to_components(Scheme),
Sign = case Sign0 of
rsa_pkcs1 ->
rsa;
rsa_pss_rsae when RSAPSSSupported ->
rsa;
rsa_pss_pss when RSAPSSSupported ->
rsa;
S -> S
end,
Hash = case Hash0 of
sha1 ->
sha;
H -> H
end,
case proplists:get_bool(Sign, PubKeys)
andalso proplists:get_bool(Hash, Hashes)
andalso (Curve =:= undefined orelse
proplists:get_bool(Curve, Curves))
andalso is_pair(Hash, Sign, Hashes)
of
true ->
[Scheme | Acc];
false ->
Acc
end;
%% Special clause for filtering out the legacy hash-sign tuples.
(_ , Acc) ->
Acc
end,
Supported = lists:foldl(Fun, [], SignatureSchemes),
lists:reverse(Supported);
signature_schemes(_, _) ->
[].
default_signature_schemes(Version) ->
Default = [
ecdsa_secp521r1_sha512,
ecdsa_secp384r1_sha384,
ecdsa_secp256r1_sha256,
rsa_pss_pss_sha512,
rsa_pss_pss_sha384,
rsa_pss_pss_sha256,
rsa_pss_rsae_sha512,
rsa_pss_rsae_sha384,
rsa_pss_rsae_sha256,
%% ed25519,
%% ed448,
%% These values refer solely to signatures
%% which appear in certificates (see Section 4.4.2.2) and are not
%% defined for use in signed TLS handshake messages, although they
%% MAY appear in "signature_algorithms" and
%% "signature_algorithms_cert" for backward compatibility with
%% TLS 1.2.
rsa_pkcs1_sha512,
rsa_pkcs1_sha384,
rsa_pkcs1_sha256,
ecdsa_sha1,
rsa_pkcs1_sha1
],
signature_schemes(Version, Default).
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, N, N, Prev, Acc) ->
Keyingmaterial = hmac_hash(Algo, PseudoRandKey, <<Prev/binary, ContextInfo/binary, ?BYTE(N)>>),
binary:part(<<Acc/binary, Keyingmaterial/binary>>, {0, Length});
hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, M, N, Prev, Acc) ->
Keyingmaterial = hmac_hash(Algo, PseudoRandKey, <<Prev/binary, ContextInfo/binary, ?BYTE(M)>>),
hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, M + 1, N, Keyingmaterial, <<Acc/binary, Keyingmaterial/binary>>).
%%%% HMAC and the Pseudorandom Functions RFC 2246 & 4346 - 5.%%%%
hmac_hash(?NULL, _, _) ->
<<>>;
hmac_hash(Alg, Key, Value) ->
crypto:hmac(mac_algo(Alg), Key, Value).
mac_algo(Alg) when is_atom(Alg) ->
Alg;
mac_algo(?MD5) -> md5;
mac_algo(?SHA) -> sha;
mac_algo(?SHA256) -> sha256;
mac_algo(?SHA384) -> sha384;
mac_algo(?SHA512) -> sha512.
% First, we define a data expansion function, P_hash(secret, data) that
% uses a single hash function to expand a secret and seed into an
% arbitrary quantity of output:
%% P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
%% HMAC_hash(secret, A(2) + seed) +
%% HMAC_hash(secret, A(3) + seed) + ...
p_hash(Secret, Seed, WantedLength, Method) ->
p_hash(Secret, Seed, WantedLength, Method, 0, []).
p_hash(_Secret, _Seed, WantedLength, _Method, _N, [])
when WantedLength =< 0 ->
[];
p_hash(_Secret, _Seed, WantedLength, _Method, _N, [Last | Acc])
when WantedLength =< 0 ->
Keep = byte_size(Last) + WantedLength,
<<B:Keep/binary, _/binary>> = Last,
list_to_binary(lists:reverse(Acc, [B]));
p_hash(Secret, Seed, WantedLength, Method, N, Acc) ->
N1 = N+1,
Bin = hmac_hash(Method, Secret, [a(N1, Secret, Seed, Method), Seed]),
p_hash(Secret, Seed, WantedLength - byte_size(Bin), Method, N1, [Bin|Acc]).
%% ... Where A(0) = seed
%% A(i) = HMAC_hash(secret, A(i-1))
%% a(0, _Secret, Seed, _Method) ->
%% Seed.
%% a(N, Secret, Seed, Method) ->
%% hmac_hash(Method, Secret, a(N-1, Secret, Seed, Method)).
a(0, _Secret, Seed, _Method) ->
Seed;
a(N, Secret, Seed0, Method) ->
Seed = hmac_hash(Method, Secret, Seed0),
a(N-1, Secret, Seed, Method).
split_secret(BinSecret) ->
%% L_S = length in bytes of secret;
%% L_S1 = L_S2 = ceil(L_S / 2);
%% The secret is partitioned into two halves (with the possibility of
%% one shared byte) as described above, S1 taking the first L_S1 bytes,
%% and S2 the last L_S2 bytes.
Length = byte_size(BinSecret),
Div = Length div 2,
EvenLength = Length - Div,
<<Secret1:EvenLength/binary, _/binary>> = BinSecret,
<<_:Div/binary, Secret2:EvenLength/binary>> = BinSecret,
{Secret1, Secret2}.
prf(?MD5SHA, Secret, Label, Seed, WantedLength) ->
%% PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR
%% P_SHA-1(S2, label + seed);
{S1, S2} = split_secret(Secret),
LS = list_to_binary([Label, Seed]),
crypto:exor(p_hash(S1, LS, WantedLength, ?MD5),
p_hash(S2, LS, WantedLength, ?SHA));
prf(MAC, Secret, Label, Seed, WantedLength) ->
%% PRF(secret, label, seed) = P_SHA256(secret, label + seed);
LS = list_to_binary([Label, Seed]),
p_hash(Secret, LS, WantedLength, MAC).
%%%% Misc help functions %%%%
finished_label(client) ->
<<"client finished">>;
finished_label(server) ->
<<"server finished">>.
is_pair(sha, dsa, _) ->
true;
is_pair(_, dsa, _) ->
false;
is_pair(Hash, ecdsa, Hashs) ->
AtLeastSha = Hashs -- [md2,md4,md5],
lists:member(Hash, AtLeastSha);
is_pair(Hash, rsa, Hashs) ->
AtLeastMd5 = Hashs -- [md2,md4],
lists:member(Hash, AtLeastMd5).
%% list ECC curves in preferred order
-spec ecc_curves(1..3 | all) -> [named_curve()].
ecc_curves(all) ->
[sect571r1,sect571k1,secp521r1,brainpoolP512r1,
sect409k1,sect409r1,brainpoolP384r1,secp384r1,
sect283k1,sect283r1,brainpoolP256r1,secp256k1,secp256r1,
sect239k1,sect233k1,sect233r1,secp224k1,secp224r1,
sect193r1,sect193r2,secp192k1,secp192r1,sect163k1,
sect163r1,sect163r2,secp160k1,secp160r1,secp160r2];
ecc_curves(Minor) ->
TLSCurves = ecc_curves(all),
ecc_curves(Minor, TLSCurves).
-spec ecc_curves(1..3, [named_curve()]) -> [named_curve()].
ecc_curves(_Minor, TLSCurves) ->
CryptoCurves = crypto:ec_curves(),
lists:foldr(fun(Curve, Curves) ->
case proplists:get_bool(Curve, CryptoCurves) of
true -> [pubkey_cert_records:namedCurves(Curve)|Curves];
false -> Curves
end
end, [], TLSCurves).
-spec groups(4 | all | default) -> [group()].
groups(all) ->
[x25519,
x448,
secp256r1,
secp384r1,
secp521r1,
ffdhe2048,
ffdhe3072,
ffdhe4096,
ffdhe6144,
ffdhe8192];
groups(default) ->
[x25519,
x448,
secp256r1,
secp384r1];
groups(Minor) ->
TLSGroups = groups(all),
groups(Minor, TLSGroups).
%%
-spec groups(4, [group()]) -> [group()].
groups(_Minor, TLSGroups) ->
CryptoGroups = supported_groups(),
lists:filter(fun(Group) -> proplists:get_bool(Group, CryptoGroups) end, TLSGroups).
default_groups(Minor) ->
TLSGroups = groups(default),
groups(Minor, TLSGroups).
supported_groups() ->
%% TODO: Add new function to crypto?
proplists:get_value(curves, crypto:supports()) ++
[ffdhe2048,ffdhe3072,ffdhe4096,ffdhe6144,ffdhe8192].
group_to_enum(secp256r1) -> 23;
group_to_enum(secp384r1) -> 24;
group_to_enum(secp521r1) -> 25;
group_to_enum(x25519) -> 29;
group_to_enum(x448) -> 30;
group_to_enum(ffdhe2048) -> 256;
group_to_enum(ffdhe3072) -> 257;
group_to_enum(ffdhe4096) -> 258;
group_to_enum(ffdhe6144) -> 259;
group_to_enum(ffdhe8192) -> 260.
enum_to_group(23) -> secp256r1;
enum_to_group(24) -> secp384r1;
enum_to_group(25) -> secp521r1;
enum_to_group(29) -> x25519;
enum_to_group(30) -> x448;
enum_to_group(256) -> ffdhe2048;
enum_to_group(257) -> ffdhe3072;
enum_to_group(258) -> ffdhe4096;
enum_to_group(259) -> ffdhe6144;
enum_to_group(260) -> ffdhe8192;
enum_to_group(_) -> undefined.
%% ECC curves from draft-ietf-tls-ecc-12.txt (Oct. 17, 2005)
oid_to_enum(?sect163k1) -> 1;
oid_to_enum(?sect163r1) -> 2;
oid_to_enum(?sect163r2) -> 3;
oid_to_enum(?sect193r1) -> 4;
oid_to_enum(?sect193r2) -> 5;
oid_to_enum(?sect233k1) -> 6;
oid_to_enum(?sect233r1) -> 7;
oid_to_enum(?sect239k1) -> 8;
oid_to_enum(?sect283k1) -> 9;
oid_to_enum(?sect283r1) -> 10;
oid_to_enum(?sect409k1) -> 11;
oid_to_enum(?sect409r1) -> 12;
oid_to_enum(?sect571k1) -> 13;
oid_to_enum(?sect571r1) -> 14;
oid_to_enum(?secp160k1) -> 15;
oid_to_enum(?secp160r1) -> 16;
oid_to_enum(?secp160r2) -> 17;
oid_to_enum(?secp192k1) -> 18;
oid_to_enum(?secp192r1) -> 19;
oid_to_enum(?secp224k1) -> 20;
oid_to_enum(?secp224r1) -> 21;
oid_to_enum(?secp256k1) -> 22;
oid_to_enum(?secp256r1) -> 23;
oid_to_enum(?secp384r1) -> 24;
oid_to_enum(?secp521r1) -> 25;
oid_to_enum(?brainpoolP256r1) -> 26;
oid_to_enum(?brainpoolP384r1) -> 27;
oid_to_enum(?brainpoolP512r1) -> 28.
enum_to_oid(1) -> ?sect163k1;
enum_to_oid(2) -> ?sect163r1;
enum_to_oid(3) -> ?sect163r2;
enum_to_oid(4) -> ?sect193r1;
enum_to_oid(5) -> ?sect193r2;
enum_to_oid(6) -> ?sect233k1;
enum_to_oid(7) -> ?sect233r1;
enum_to_oid(8) -> ?sect239k1;
enum_to_oid(9) -> ?sect283k1;
enum_to_oid(10) -> ?sect283r1;
enum_to_oid(11) -> ?sect409k1;
enum_to_oid(12) -> ?sect409r1;
enum_to_oid(13) -> ?sect571k1;
enum_to_oid(14) -> ?sect571r1;
enum_to_oid(15) -> ?secp160k1;
enum_to_oid(16) -> ?secp160r1;
enum_to_oid(17) -> ?secp160r2;
enum_to_oid(18) -> ?secp192k1;
enum_to_oid(19) -> ?secp192r1;
enum_to_oid(20) -> ?secp224k1;
enum_to_oid(21) -> ?secp224r1;
enum_to_oid(22) -> ?secp256k1;
enum_to_oid(23) -> ?secp256r1;
enum_to_oid(24) -> ?secp384r1;
enum_to_oid(25) -> ?secp521r1;
enum_to_oid(26) -> ?brainpoolP256r1;
enum_to_oid(27) -> ?brainpoolP384r1;
enum_to_oid(28) -> ?brainpoolP512r1;
enum_to_oid(_) ->
undefined.