From a9541beba58c494300e55dcfbda288e60a2fb54a Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 17 Oct 2018 11:44:29 +0200 Subject: ssl: Refactor AEAD ciphers TLS-1.3 needs to handle AEAD inputs diffrently than previous versions. Refactor code to facilitate TLS-1.3 additions. Change CHACHA20_POLY1305 NONCE to match RFC 7905. This will be important later when we fix interop with TLS compatible crypto invocation. --- lib/ssl/src/dtls_record.erl | 22 ++++++------ lib/ssl/src/ssl_cipher.erl | 88 ++++++++++----------------------------------- lib/ssl/src/ssl_cipher.hrl | 3 +- lib/ssl/src/ssl_record.erl | 79 +++++++++++++++++++++++++++++++++++----- lib/ssl/src/tls_record.erl | 24 ++++++++----- 5 files changed, 117 insertions(+), 99 deletions(-) (limited to 'lib/ssl/src') diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index 9eb0d8e2d7..b7346d3ec8 100644 --- a/lib/ssl/src/dtls_record.erl +++ b/lib/ssl/src/dtls_record.erl @@ -499,23 +499,22 @@ encode_dtls_cipher_text(Type, {MajVer, MinVer}, Fragment, WriteState#{sequence_number => Seq + 1}}. encode_plain_text(Type, Version, Data, #{compression_state := CompS0, + cipher_state := CipherS0, epoch := Epoch, sequence_number := Seq, - cipher_state := CipherS0, security_parameters := #security_parameters{ cipher_type = ?AEAD, - bulk_cipher_algorithm = - BulkCipherAlgo, + bulk_cipher_algorithm = BCAlg, compression_algorithm = CompAlg} } = WriteState0) -> {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), - AAD = calc_aad(Type, Version, Epoch, Seq), + AAD = start_additional_data(Type, Version, Epoch, Seq), + CipherS = ssl_record:nonce_seed(BCAlg, <>, CipherS0), + WriteState = WriteState0#{compression_state => CompS1, + cipher_state => CipherS}, TLSVersion = dtls_v1:corresponding_tls_version(Version), - {CipherFragment, CipherS1} = - ssl_cipher:cipher_aead(BulkCipherAlgo, CipherS0, Seq, AAD, Comp, TLSVersion), - {CipherFragment, WriteState0#{compression_state => CompS1, - cipher_state => CipherS1}}; + ssl_record:cipher_aead(TLSVersion, Comp, WriteState, AAD); encode_plain_text(Type, Version, Fragment, #{compression_state := CompS0, epoch := Epoch, sequence_number := Seq, @@ -547,9 +546,10 @@ decode_cipher_text(#ssl_tls{type = Type, version = Version, BulkCipherAlgo, compression_algorithm = CompAlg}} = ReadState0, ConnnectionStates0) -> - AAD = calc_aad(Type, Version, Epoch, Seq), + AAD = start_additional_data(Type, Version, Epoch, Seq), + CipherS1 = ssl_record:nonce_seed(BulkCipherAlgo, <>, CipherS0), TLSVersion = dtls_v1:corresponding_tls_version(Version), - case ssl_cipher:decipher_aead(BulkCipherAlgo, CipherS0, Seq, AAD, CipherFragment, TLSVersion) of + case ssl_record:decipher_aead(BulkCipherAlgo, CipherS1, AAD, CipherFragment, TLSVersion) of {PlainFragment, CipherState} -> {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, PlainFragment, CompressionS0), @@ -600,7 +600,7 @@ mac_hash({Major, Minor}, MacAlg, MacSecret, Epoch, SeqNo, Type, Length, Fragment Fragment], dtls_v1:hmac_hash(MacAlg, MacSecret, Value). -calc_aad(Type, {MajVer, MinVer}, Epoch, SeqNo) -> +start_additional_data(Type, {MajVer, MinVer}, Epoch, SeqNo) -> <>. %%-------------------------------------------------------------------- diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index b23129dcdd..0e22d63b4b 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -34,7 +34,7 @@ -include_lib("public_key/include/public_key.hrl"). -export([security_parameters/2, security_parameters/3, - cipher_init/3, decipher/6, cipher/5, decipher_aead/6, cipher_aead/6, + 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, srp_suites/0, srp_suites_anon/0, @@ -48,6 +48,8 @@ -type cipher_enum() :: integer(). +-export_type([cipher_enum/0]). + %%-------------------------------------------------------------------- -spec security_parameters(ssl_cipher_format:cipher_suite(), #security_parameters{}) -> #security_parameters{}. @@ -91,10 +93,15 @@ cipher_init(?RC4, IV, Key) -> #cipher_state{iv = IV, key = Key, state = State}; cipher_init(?AES_GCM, IV, Key) -> <> = random_bytes(8), - #cipher_state{iv = IV, key = Key, nonce = Nonce}; + #cipher_state{iv = IV, key = Key, nonce = Nonce, tag_len = 16}; +cipher_init(?CHACHA20_POLY1305, IV, Key) -> + #cipher_state{iv = IV, key = Key, tag_len = 16}; cipher_init(_BCA, IV, Key) -> #cipher_state{iv = IV, key = Key}. +nonce_seed(Seed, CipherState) -> + CipherState#cipher_state{nonce = Seed}. + %%-------------------------------------------------------------------- -spec cipher(cipher_enum(), #cipher_state{}, binary(), iodata(), ssl_record:ssl_version()) -> {binary(), #cipher_state{}}. @@ -126,32 +133,16 @@ cipher(?AES_CBC, CipherState, Mac, Fragment, Version) -> crypto:block_encrypt(aes_cbc256, Key, IV, T) end, block_size(aes_128_cbc), CipherState, Mac, Fragment, Version). -%%-------------------------------------------------------------------- --spec cipher_aead(cipher_enum(), #cipher_state{}, integer(), binary(), iodata(), ssl_record:ssl_version()) -> - {binary(), #cipher_state{}}. -%% -%% Description: Encrypts the data and protects associated data (AAD) using chipher -%% described by cipher_enum() and updating the cipher state -%% Use for suites that use authenticated encryption with associated data (AEAD) -%%------------------------------------------------------------------- -cipher_aead(?AES_GCM, CipherState, SeqNo, AAD, Fragment, Version) -> - aead_cipher(aes_gcm, CipherState, SeqNo, AAD, Fragment, Version); -cipher_aead(?CHACHA20_POLY1305, CipherState, SeqNo, AAD, Fragment, Version) -> - aead_cipher(chacha20_poly1305, CipherState, SeqNo, AAD, Fragment, Version). - -aead_cipher(chacha20_poly1305, #cipher_state{key=Key} = CipherState, SeqNo, AAD0, Fragment, _Version) -> - CipherLen = erlang:iolist_size(Fragment), - AAD = <>, - Nonce = ?uint64(SeqNo), - {Content, CipherTag} = crypto:block_encrypt(chacha20_poly1305, Key, Nonce, {AAD, Fragment}), - {<>, CipherState}; -aead_cipher(Type, #cipher_state{key=Key, iv = IV0, nonce = Nonce} = CipherState, _SeqNo, AAD0, Fragment, _Version) -> - CipherLen = erlang:iolist_size(Fragment), - AAD = <>, - <> = IV0, - IV = <>, - {Content, CipherTag} = crypto:block_encrypt(Type, Key, IV, {AAD, Fragment}), - {<>, CipherState#cipher_state{nonce = Nonce + 1}}. +aead_encrypt(Type, Key, Nonce, Fragment, AdditionalData) -> + crypto:block_encrypt(aead_type(Type), Key, Nonce, {AdditionalData, Fragment}). + +aead_decrypt(Type, Key, Nonce, CipherText, CipherTag, AdditionalData) -> + crypto:block_decrypt(aead_type(Type), Key, Nonce, {AdditionalData, CipherText, CipherTag}). + +aead_type(?AES_GCM) -> + aes_gcm; +aead_type(?CHACHA20_POLY1305) -> + chacha20_poly1305. build_cipher_block(BlockSz, Mac, Fragment) -> TotSz = byte_size(Mac) + erlang:iolist_size(Fragment) + 1, @@ -218,19 +209,6 @@ decipher(?AES_CBC, HashSz, CipherState, Fragment, Version, PaddingCheck) -> crypto:block_decrypt(aes_cbc256, Key, IV, T) end, CipherState, HashSz, Fragment, Version, PaddingCheck). -%%-------------------------------------------------------------------- --spec decipher_aead(cipher_enum(), #cipher_state{}, integer(), binary(), binary(), ssl_record:ssl_version()) -> - {binary(), #cipher_state{}} | #alert{}. -%% -%% Description: Decrypts the data and checks the associated data (AAD) MAC using -%% cipher described by cipher_enum() and updating the cipher state. -%% Use for suites that use authenticated encryption with associated data (AEAD) -%%------------------------------------------------------------------- -decipher_aead(?AES_GCM, CipherState, SeqNo, AAD, Fragment, Version) -> - aead_decipher(aes_gcm, CipherState, SeqNo, AAD, Fragment, Version); -decipher_aead(?CHACHA20_POLY1305, CipherState, SeqNo, AAD, Fragment, Version) -> - aead_decipher(chacha20_poly1305, CipherState, SeqNo, AAD, Fragment, Version). - block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0, HashSz, Fragment, Version, PaddingCheck) -> try @@ -261,34 +239,6 @@ block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0, ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) end. -aead_ciphertext_to_state(chacha20_poly1305, SeqNo, _IV, AAD0, Fragment, _Version) -> - CipherLen = size(Fragment) - 16, - <> = Fragment, - AAD = <>, - Nonce = ?uint64(SeqNo), - {Nonce, AAD, CipherText, CipherTag}; -aead_ciphertext_to_state(_, _SeqNo, <>, AAD0, Fragment, _Version) -> - CipherLen = size(Fragment) - 24, - <> = Fragment, - AAD = <>, - Nonce = <>, - {Nonce, AAD, CipherText, CipherTag}. - -aead_decipher(Type, #cipher_state{key = Key, iv = IV} = CipherState, - SeqNo, AAD0, Fragment, Version) -> - try - {Nonce, AAD, CipherText, CipherTag} = aead_ciphertext_to_state(Type, SeqNo, IV, AAD0, Fragment, Version), - case crypto:block_decrypt(Type, Key, Nonce, {AAD, CipherText, CipherTag}) of - Content when is_binary(Content) -> - {Content, CipherState}; - _ -> - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) - end - catch - _:_ -> - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) - end. - %%-------------------------------------------------------------------- -spec suites(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()]. %% diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl index ba6a98b92a..2371e8bd32 100644 --- a/lib/ssl/src/ssl_cipher.hrl +++ b/lib/ssl/src/ssl_cipher.hrl @@ -48,7 +48,8 @@ iv, key, state, - nonce + nonce, + tag_len }). %%% TLS_NULL_WITH_NULL_NULL is specified and is the initial state of a diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index 659e1485ac..d31e429283 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -45,7 +45,7 @@ -export([compress/3, uncompress/3, compressions/0]). %% Payload encryption/decryption --export([cipher/4, decipher/4, cipher_aead/4, is_correct_mac/2]). +-export([cipher/4, decipher/4, cipher_aead/4, decipher_aead/5, is_correct_mac/2, nonce_seed/3]). -export_type([ssl_version/0, ssl_atom_version/0, connection_states/0, connection_state/0]). @@ -306,22 +306,20 @@ cipher(Version, Fragment, {CipherFragment, CipherS1} = ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MacHash, Fragment, Version), {CipherFragment, WriteState0#{cipher_state => CipherS1}}. -%% %%-------------------------------------------------------------------- -%% -spec cipher_aead(ssl_version(), iodata(), connection_state(), MacHash::binary()) -> -%% {CipherFragment::binary(), connection_state()}. -%% %% -%% %% Description: Payload encryption +%%-------------------------------------------------------------------- +-spec cipher_aead(ssl_version(), iodata(), connection_state(), AAD::binary()) -> + {CipherFragment::binary(), connection_state()}. + +%% Description: Payload encryption %% %%-------------------------------------------------------------------- cipher_aead(Version, Fragment, #{cipher_state := CipherS0, - sequence_number := SeqNo, security_parameters := #security_parameters{bulk_cipher_algorithm = BulkCipherAlgo} } = WriteState0, AAD) -> - {CipherFragment, CipherS1} = - ssl_cipher:cipher_aead(BulkCipherAlgo, CipherS0, SeqNo, AAD, Fragment, Version), + cipher_aead(BulkCipherAlgo, CipherS0, AAD, Fragment, Version), {CipherFragment, WriteState0#{cipher_state => CipherS1}}. %%-------------------------------------------------------------------- @@ -344,10 +342,39 @@ decipher(Version, CipherFragment, #alert{} = Alert -> Alert end. +%%-------------------------------------------------------------------- +-spec decipher_aead(ssl_cipher:cipher_enum(), #cipher_state{}, + binary(), binary(), ssl_record:ssl_version()) -> + {binary(), #cipher_state{}} | #alert{}. +%% +%% Description: Decrypts the data and checks the associated data (AAD) MAC using +%% cipher described by cipher_enum() and updating the cipher state. +%% Use for suites that use authenticated encryption with associated data (AEAD) +%%------------------------------------------------------------------- +decipher_aead(Type, #cipher_state{key = Key} = CipherState, AAD0, CipherFragment, _) -> + try + Nonce = decrypt_nonce(Type, CipherState, CipherFragment), + {AAD, CipherText, CipherTag} = aead_ciphertext_split(Type, CipherState, CipherFragment, AAD0), + case ssl_cipher:aead_decrypt(Type, Key, Nonce, CipherText, CipherTag, AAD) of + Content when is_binary(Content) -> + {Content, CipherState}; + _ -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) + end + catch + _:_ -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) + end. + +nonce_seed(?CHACHA20_POLY1305, Seed, CipherState) -> + ssl_cipher:nonce_seed(Seed, CipherState); +nonce_seed(_,_, CipherState) -> + CipherState. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- + empty_connection_state(ConnectionEnd, BeastMitigation) -> SecParams = empty_security_params(ConnectionEnd), #{security_parameters => SecParams, @@ -400,3 +427,37 @@ initial_security_params(ConnectionEnd) -> compression_algorithm = ?NULL}, ssl_cipher:security_parameters(?TLS_NULL_WITH_NULL_NULL, SecParams). +cipher_aead(?CHACHA20_POLY1305 = Type, #cipher_state{key=Key} = CipherState, AAD0, Fragment, _Version) -> + AAD = end_additional_data(AAD0, erlang:iolist_size(Fragment)), + Nonce = encrypt_nonce(Type, CipherState), + {Content, CipherTag} = ssl_cipher:aead_encrypt(Type, Key, Nonce, Fragment, AAD), + {<>, CipherState}; +cipher_aead(Type, #cipher_state{key=Key, nonce = ExplicitNonce} = CipherState, AAD0, Fragment, _Version) -> + AAD = end_additional_data(AAD0, erlang:iolist_size(Fragment)), + Nonce = encrypt_nonce(Type, CipherState), + {Content, CipherTag} = ssl_cipher:aead_encrypt(Type, Key, Nonce, Fragment, AAD), + {<>, CipherState#cipher_state{nonce = ExplicitNonce + 1}}. + +encrypt_nonce(?CHACHA20_POLY1305, #cipher_state{nonce = Nonce, iv = IV}) -> + crypto:exor(<>, IV); +encrypt_nonce(?AES_GCM, #cipher_state{iv = IV, nonce = ExplicitNonce}) -> + <> = IV, + <>. + +decrypt_nonce(?CHACHA20_POLY1305, #cipher_state{nonce = Nonce, iv = IV}, _) -> + crypto:exor(<>, IV); +decrypt_nonce(?AES_GCM, #cipher_state{iv = <>}, <>) -> + <>. + +aead_ciphertext_split(?CHACHA20_POLY1305, #cipher_state{tag_len = Len}, CipherTextFragment, AAD) -> + CipherLen = size(CipherTextFragment) - Len, + <> = CipherTextFragment, + {end_additional_data(AAD, Len), CipherText, CipherTag}; +aead_ciphertext_split(?AES_GCM, #cipher_state{tag_len = Len}, CipherTextFragment, AAD) -> + CipherLen = size(CipherTextFragment) - (Len + 8), %% 8 is length of explicit Nonce + << _:8/bytes, CipherText:CipherLen/bytes, CipherTag:Len/bytes>> = CipherTextFragment, + {end_additional_data(AAD, CipherLen), CipherText, CipherTag}. + +end_additional_data(AAD, Len) -> + <>. + diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl index f1aca8c801..ce7edc9dcd 100644 --- a/lib/ssl/src/tls_record.erl +++ b/lib/ssl/src/tls_record.erl @@ -176,14 +176,15 @@ decode_cipher_text(#ssl_tls{type = Type, version = Version, BulkCipherAlgo, compression_algorithm = CompAlg} } = ReadState0} = ConnnectionStates0, _) -> - AAD = calc_aad(Type, Version, ReadState0), - case ssl_cipher:decipher_aead(BulkCipherAlgo, CipherS0, Seq, AAD, CipherFragment, Version) of - {PlainFragment, CipherS1} -> + AAD = start_additional_data(Type, Version, ReadState0), + CipherS1 = ssl_record:nonce_seed(BulkCipherAlgo, <>, CipherS0), + case ssl_record:decipher_aead(BulkCipherAlgo, CipherS1, AAD, CipherFragment, Version) of + {PlainFragment, CipherState} -> {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, PlainFragment, CompressionS0), ConnnectionStates = ConnnectionStates0#{ current_read => ReadState0#{ - cipher_state => CipherS1, + cipher_state => CipherState, sequence_number => Seq + 1, compression_state => CompressionS1}}, {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; @@ -453,15 +454,20 @@ encode_iolist(Type, Data, Version, ConnectionStates0) -> {lists:reverse(EncodedMsg), ConnectionStates}. %%-------------------------------------------------------------------- do_encode_plain_text(Type, Version, Data, #{compression_state := CompS0, - security_parameters := + cipher_state := CipherS0, + sequence_number := Seq, + security_parameters := #security_parameters{ cipher_type = ?AEAD, + bulk_cipher_algorithm = BCAlg, compression_algorithm = CompAlg} } = WriteState0) -> {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), - WriteState1 = WriteState0#{compression_state => CompS1}, - AAD = calc_aad(Type, Version, WriteState1), - ssl_record:cipher_aead(Version, Comp, WriteState1, AAD); + CipherS = ssl_record:nonce_seed(BCAlg, <>, CipherS0), + WriteState = WriteState0#{compression_state => CompS1, + cipher_state => CipherS}, + AAD = start_additional_data(Type, Version, WriteState), + ssl_record:cipher_aead(Version, Comp, WriteState, AAD); do_encode_plain_text(Type, Version, Data, #{compression_state := CompS0, security_parameters := #security_parameters{compression_algorithm = CompAlg} @@ -473,7 +479,7 @@ do_encode_plain_text(Type, Version, Data, #{compression_state := CompS0, do_encode_plain_text(_,_,_,CS) -> exit({cs, CS}). %%-------------------------------------------------------------------- -calc_aad(Type, {MajVer, MinVer}, +start_additional_data(Type, {MajVer, MinVer}, #{sequence_number := SeqNo}) -> <>. -- cgit v1.2.3