%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2013-2023. 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: Help functions for handling the SSL-handshake protocol (common %% to SSL/TLS and DTLS %%---------------------------------------------------------------------- -module(ssl_handshake). -feature(maybe_expr,enable). -include("ssl_handshake.hrl"). -include("ssl_record.hrl"). -include("ssl_cipher.hrl"). -include("ssl_alert.hrl"). -include("ssl_internal.hrl"). -include("ssl_srp.hrl"). -include("tls_handshake_1_3.hrl"). -include_lib("public_key/include/public_key.hrl"). -export_type([ssl_handshake/0, ssl_handshake_history/0, public_key_info/0, oid/0]). -type oid() :: tuple(). -type public_key_params() :: #'Dss-Parms'{} | {namedCurve, oid()} | #'ECParameters'{} | term(). -type public_key_info() :: {oid(), #'RSAPublicKey'{} | integer() | #'ECPoint'{}, public_key_params()}. -type ssl_handshake_history() :: {iodata(), iodata()}. -type ssl_handshake() :: #server_hello{} | #server_hello_done{} | #certificate{} | #certificate_request{} | #client_key_exchange{} | #finished{} | #certificate_verify{} | #hello_request{} | #next_protocol{} | #end_of_early_data{}. %% Create handshake messages -export([hello_request/0, server_hello/4, server_hello_done/0, certificate/4, client_certificate_verify/6, certificate_request/4, key_exchange/3, finished/5, next_protocol/1, digitally_signed/5, certificate_authorities/2]). %% Handle handshake messages -export([certify/9, certificate_verify/6, verify_signature/5, master_secret/4, server_key_exchange_hash/2, verify_connection/6, init_handshake_history/0, update_handshake_history/2, verify_server_key/5, select_version/3, select_supported_version/2, extension_value/1 ]). %% Encode -export([encode_handshake/2, encode_hello_extensions/1, encode_extensions/1, encode_extensions/2, encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1]). %% Decode -export([decode_handshake/3, decode_vector/1, decode_hello_extensions/4, decode_extensions/3, decode_server_key/3, decode_client_key/3, decode_suites/2 ]). %% Cipher suites handling -export([available_suites/2, available_signature_algs/2, available_signature_algs/3, cipher_suites/3, prf/6, select_session/9, premaster_secret/2, premaster_secret/3, premaster_secret/4]). %% Extensions handling -export([client_hello_extensions/10, handle_client_hello_extensions/10, %% Returns server hello extensions handle_server_hello_extensions/10, select_curve/2, select_curve/3, select_hashsign/4, select_hashsign/5, select_hashsign_algs/3, empty_extensions/2, add_server_share/3, add_alpn/2, add_selected_version/1, decode_alpn/1, max_frag_enum/1 ]). -export([get_cert_params/1, select_own_cert/1, server_name/3, path_validation/10, validation_fun_and_state/4, path_validation_alert/1]). %% Tracing -export([handle_trace/3]). %%==================================================================== %% Create handshake messages %%==================================================================== %%-------------------------------------------------------------------- -spec hello_request() -> #hello_request{}. %% %% Description: Creates a hello request message sent by server to %% trigger renegotiation. %%-------------------------------------------------------------------- hello_request() -> #hello_request{}. %%-------------------------------------------------------------------- %%-spec server_hello(binary(), ssl_record:ssl_version(), ssl_record:connection_states(), %% Extension::map()) -> #server_hello{}. %% %% Description: Creates a server hello message. %%-------------------------------------------------------------------- server_hello(SessionId, Version, ConnectionStates, Extensions) -> #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates, read), #server_hello{server_version = Version, cipher_suite = SecParams#security_parameters.cipher_suite, compression_method = SecParams#security_parameters.compression_algorithm, random = SecParams#security_parameters.server_random, session_id = SessionId, extensions = Extensions }. %%-------------------------------------------------------------------- -spec server_hello_done() -> #server_hello_done{}. %% %% Description: Creates a server hello done message. %%-------------------------------------------------------------------- server_hello_done() -> #server_hello_done{}. %%-------------------------------------------------------------------- -spec certificate([der_cert()], db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}. %% %% Description: Creates a certificate message. %%-------------------------------------------------------------------- certificate([[]], _, _, client) -> %% If no suitable certificate is available, the client %% SHOULD send a certificate message containing no %% certificates. (chapter 7.4.6. RFC 4346) #certificate{asn1_certificates = []}; certificate([OwnCert], CertDbHandle, CertDbRef, _) -> {ok, _, CertChain} = ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef), #certificate{asn1_certificates = CertChain}; certificate([_, _ |_] = Chain, _, _, _) -> #certificate{asn1_certificates = Chain}. %%-------------------------------------------------------------------- -spec client_certificate_verify([der_cert()], binary(), ssl_record:ssl_version(), term(), public_key:private_key(), ssl_handshake_history()) -> #certificate_verify{} | ignore | #alert{}. %% %% Description: Creates a certificate_verify message, called by the client. %%-------------------------------------------------------------------- client_certificate_verify([[]], _, _, _, _, _) -> ignore; client_certificate_verify(_, _, _, _, undefined, _) -> ignore; client_certificate_verify([OwnCert|_], MasterSecret, Version, {HashAlgo, SignAlgo}, PrivateKey, {Handshake, _}) -> case public_key:pkix_is_fixed_dh_cert(OwnCert) of true -> ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE, fixed_diffie_hellman_prohibited); false -> Hashes = calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake), Signed = digitally_signed(Version, Hashes, HashAlgo, PrivateKey, SignAlgo), #certificate_verify{signature = Signed, hashsign_algorithm = {HashAlgo, SignAlgo}} end. %%-------------------------------------------------------------------- -spec certificate_request(db_handle(), certdb_ref(), #hash_sign_algos{}, ssl_record:ssl_version()) -> #certificate_request{}. %% %% Description: Creates a certificate_request message, called by the server. %%-------------------------------------------------------------------- certificate_request(CertDbHandle, CertDbRef, HashSigns, Version) -> Types = certificate_types(Version), Authorities = certificate_authorities(CertDbHandle, CertDbRef), #certificate_request{ certificate_types = Types, hashsign_algorithms = HashSigns, certificate_authorities = Authorities }. %%-------------------------------------------------------------------- -spec key_exchange(client | server, ssl_record:ssl_version(), {premaster_secret, binary(), public_key_info()} | {dh, binary()} | {dh, {binary(), binary()}, #'DHParameter'{}, {HashAlgo::atom(), SignAlgo::atom()}, binary(), binary(), public_key:private_key()} | {ecdh, _, _, _, _, _} | {ecdh, #'ECPrivateKey'{}} | {psk, _, _, _, _, _} | {psk, binary()} | {dhe_psk, _, _, _, _, _, _, _} | {dhe_psk, binary(), binary()} | {ecdhe_psk, _, _, _, _, _, _} | {ecdhe_psk, binary(), #'ECPrivateKey'{}} | {srp, {binary(), binary()}, #srp_user{}, {HashAlgo::atom(), SignAlgo::atom()}, binary(), binary(), public_key:private_key()} | {srp, _} | {psk_premaster_secret, _, _, _}) -> #client_key_exchange{} | #server_key_exchange{}. %% %% Description: Creates a keyexchange message. %%-------------------------------------------------------------------- key_exchange(client, _Version, {premaster_secret, Secret, {_, PublicKey, _}}) -> EncPremasterSecret = encrypted_premaster_secret(Secret, PublicKey), #client_key_exchange{exchange_keys = EncPremasterSecret}; key_exchange(client, _Version, {dh, PublicKey}) -> #client_key_exchange{ exchange_keys = #client_diffie_hellman_public{ dh_public = PublicKey} }; key_exchange(client, _Version, {ecdh, #'ECPrivateKey'{publicKey = ECPublicKey}}) -> #client_key_exchange{ exchange_keys = #client_ec_diffie_hellman_public{ dh_public = ECPublicKey} }; key_exchange(client, _Version, {psk, Identity}) -> #client_key_exchange{ exchange_keys = #client_psk_identity{ identity = Identity} }; key_exchange(client, _Version, {dhe_psk, Identity, PublicKey}) -> #client_key_exchange{ exchange_keys = #client_dhe_psk_identity{ identity = Identity, dh_public = PublicKey} }; key_exchange(client, _Version, {ecdhe_psk, Identity, #'ECPrivateKey'{publicKey = ECPublicKey}}) -> #client_key_exchange{ exchange_keys = #client_ecdhe_psk_identity{ identity = Identity, dh_public = ECPublicKey} }; key_exchange(client, _Version, {psk_premaster_secret, PskIdentity, Secret, {_, PublicKey, _}}) -> EncPremasterSecret = encrypted_premaster_secret(Secret, PublicKey), #client_key_exchange{ exchange_keys = #client_rsa_psk_identity{ identity = PskIdentity, exchange_keys = EncPremasterSecret}}; key_exchange(client, _Version, {srp, PublicKey}) -> #client_key_exchange{ exchange_keys = #client_srp_public{ srp_a = PublicKey} }; key_exchange(server, Version, {dh, {PublicKey, _}, #'DHParameter'{prime = P, base = G}, HashSign, ClientRandom, ServerRandom, PrivateKey}) -> ServerDHParams = #server_dh_params{dh_p = int_to_bin(P), dh_g = int_to_bin(G), dh_y = PublicKey}, enc_server_key_exchange(Version, ServerDHParams, HashSign, ClientRandom, ServerRandom, PrivateKey); key_exchange(server, Version, {ecdh, #'ECPrivateKey'{publicKey = ECPublicKey, parameters = ECCurve}, HashSign, ClientRandom, ServerRandom, PrivateKey}) -> ServerECParams = #server_ecdh_params{curve = ECCurve, public = ECPublicKey}, enc_server_key_exchange(Version, ServerECParams, HashSign, ClientRandom, ServerRandom, PrivateKey); key_exchange(server, Version, {psk, PskIdentityHint, HashSign, ClientRandom, ServerRandom, PrivateKey}) -> ServerPSKParams = #server_psk_params{hint = PskIdentityHint}, enc_server_key_exchange(Version, ServerPSKParams, HashSign, ClientRandom, ServerRandom, PrivateKey); key_exchange(server, Version, {dhe_psk, PskIdentityHint, {PublicKey, _}, #'DHParameter'{prime = P, base = G}, HashSign, ClientRandom, ServerRandom, PrivateKey}) -> ServerEDHPSKParams = #server_dhe_psk_params{ hint = PskIdentityHint, dh_params = #server_dh_params{dh_p = int_to_bin(P), dh_g = int_to_bin(G), dh_y = PublicKey} }, enc_server_key_exchange(Version, ServerEDHPSKParams, HashSign, ClientRandom, ServerRandom, PrivateKey); key_exchange(server, Version, {ecdhe_psk, PskIdentityHint, #'ECPrivateKey'{publicKey = ECPublicKey, parameters = ECCurve}, HashSign, ClientRandom, ServerRandom, PrivateKey}) -> ServerECDHEPSKParams = #server_ecdhe_psk_params{ hint = PskIdentityHint, dh_params = #server_ecdh_params{curve = ECCurve, public = ECPublicKey}}, enc_server_key_exchange(Version, ServerECDHEPSKParams, HashSign, ClientRandom, ServerRandom, PrivateKey); key_exchange(server, Version, {srp, {PublicKey, _}, #srp_user{generator = Generator, prime = Prime, salt = Salt}, HashSign, ClientRandom, ServerRandom, PrivateKey}) -> ServerSRPParams = #server_srp_params{srp_n = Prime, srp_g = Generator, srp_s = Salt, srp_b = PublicKey}, enc_server_key_exchange(Version, ServerSRPParams, HashSign, ClientRandom, ServerRandom, PrivateKey). %%-------------------------------------------------------------------- -spec finished(ssl_record:ssl_version(), client | server, integer(), binary(), ssl_handshake_history()) -> #finished{}. %% %% Description: Creates a handshake finished message %%------------------------------------------------------------------- finished(Version, Role, PrfAlgo, MasterSecret, {Handshake, _}) -> % use the current handshake #finished{verify_data = calc_finished(Version, Role, PrfAlgo, MasterSecret, Handshake)}. %%-------------------------------------------------------------------- -spec next_protocol(binary()) -> #next_protocol{}. %% %% Description: Creates a next protocol message %%------------------------------------------------------------------- next_protocol(SelectedProtocol) -> #next_protocol{selected_protocol = SelectedProtocol}. %%==================================================================== %% Handle handshake messages %%==================================================================== %%-------------------------------------------------------------------- -spec certify(#certificate{}, db_handle(), certdb_ref(), ssl_options(), term(), client | server, inet:hostname() | inet:ip_address(), ssl_record:ssl_version(), map()) -> {der_cert(), public_key_info()} | #alert{}. %% %% Description: Handles a certificate handshake message %%-------------------------------------------------------------------- certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, #{partial_chain := PartialChain} = SSlOptions, CRLDbHandle, Role, Host, Version, CertExt) -> ServerName = server_name(SSlOptions, Host, Role), [PeerCert | _ChainCerts ] = ASN1Certs, try PathsAndAnchors = ssl_certificate:trusted_cert_and_paths(ASN1Certs, CertDbHandle, CertDbRef, PartialChain), case path_validate(PathsAndAnchors, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version, SSlOptions, CertExt) of {ok, {PublicKeyInfo, _}} -> {PeerCert, PublicKeyInfo}; {error, Reason} -> path_validation_alert(Reason) end catch error:{_,{error, {asn1, Asn1Reason}}} -> %% ASN-1 decode of certificate somehow failed ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason}); error:OtherReason:ST -> ?SSL_LOG(info, internal_error, [{error, OtherReason}, {stacktrace, ST}]), ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason}) end. %%-------------------------------------------------------------------- -spec certificate_verify(binary(), public_key_info(), ssl_record:ssl_version(), term(), binary(), ssl_handshake_history()) -> valid | #alert{}. %% %% Description: Checks that the certificate_verify message is valid. %%-------------------------------------------------------------------- certificate_verify(_, _, _, undefined, _, _) -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, invalid_certificate_verify_message); certificate_verify(Signature, PublicKeyInfo, Version, HashSign = {HashAlgo, _}, MasterSecret, {_, Handshake}) -> Msg = calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake), case verify_signature(Version, Msg, HashSign, Signature, PublicKeyInfo) of true -> valid; _ -> ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE) end. %%-------------------------------------------------------------------- -spec verify_signature(ssl_record:ssl_version(), binary(), {term(), term()}, binary(), public_key_info()) -> true | false. %% %% Description: Checks that a public_key signature is valid. %%-------------------------------------------------------------------- verify_signature(_, Msg, {HashAlgo, SignAlgo}, Signature, {_, PubKey, PubKeyParams}) when SignAlgo == rsa_pss_rsae; SignAlgo == rsa_pss_pss -> Options = verify_options(SignAlgo, HashAlgo, PubKeyParams), public_key:verify(Msg, HashAlgo, Signature, PubKey, Options); verify_signature(Version, Msg, {HashAlgo, SignAlgo}, Signature, {?rsaEncryption, PubKey, PubKeyParams}) when ?TLS_GTE(Version, ?TLS_1_2) -> Options = verify_options(SignAlgo, HashAlgo, PubKeyParams), public_key:verify(Msg, HashAlgo, Signature, PubKey, Options); verify_signature(Version, {digest, Digest}, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) when ?TLS_LTE(Version, ?TLS_1_1) -> case public_key:decrypt_public(Signature, PubKey, [{rsa_pad, rsa_pkcs1_padding}]) of Digest -> true; _ -> false end; verify_signature(?TLS_1_3, Msg, {_, eddsa}, Signature, {?'id-Ed25519', PubKey, PubKeyParams}) -> public_key:verify(Msg, none, Signature, {PubKey, PubKeyParams}); verify_signature(?TLS_1_3, Msg, {_, eddsa}, Signature, {?'id-Ed448', PubKey, PubKeyParams}) -> public_key:verify(Msg, none, Signature, {PubKey, PubKeyParams}); verify_signature(_, Msg, {HashAlgo, _SignAlg}, Signature, {?'id-ecPublicKey', PublicKey, PublicKeyParams}) -> public_key:verify(Msg, HashAlgo, Signature, {PublicKey, PublicKeyParams}); verify_signature(Version, _Msg, {_HashAlgo, anon}, _Signature, _) when ?TLS_1_X(Version), ?TLS_LTE(Version, ?TLS_1_2) -> true; verify_signature(Version, Msg, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) when ?TLS_1_X(Version), ?TLS_LTE(Version, ?TLS_1_2) -> public_key:verify(Msg, HashAlgo, Signature, {PublicKey, PublicKeyParams}). %%-------------------------------------------------------------------- -spec master_secret(ssl_record:ssl_version(), #session{} | binary(), ssl_record:connection_states(), client | server) -> {binary(), ssl_record:connection_states()} | #alert{}. %% %% Description: Sets or calculates the master secret and calculate keys, %% updating the pending connection states. The Mastersecret and the update %% connection states are returned or an alert if the calculation fails. %%------------------------------------------------------------------- master_secret(Version, #session{master_secret = Mastersecret}, ConnectionStates, Role) -> #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates, read), try master_secret(Version, Mastersecret, SecParams, ConnectionStates, Role) catch exit:Reason:ST -> ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, key_calculation_failure) end; master_secret(Version, PremasterSecret, ConnectionStates, Role) -> #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates, read), #security_parameters{prf_algorithm = PrfAlgo, client_random = ClientRandom, server_random = ServerRandom} = SecParams, try master_secret(Version, calc_master_secret(Version,PrfAlgo,PremasterSecret, ClientRandom, ServerRandom), SecParams, ConnectionStates, Role) catch exit:Reason:ST -> ?SSL_LOG(info, handshake_error, [{error, Reason}, {stacktrace, ST}]), ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, master_secret_calculation_failure) end. %%-------------------------------------------------------------------- -spec server_key_exchange_hash(md5sha | sha | sha224 |sha256 | sha384 | sha512, binary()) -> binary() | {digest, binary()}. %% %% Description: Calculate the digest of the server key exchange hash if it is complex %%-------------------------------------------------------------------- server_key_exchange_hash(md5sha, Value) -> MD5 = crypto:hash(md5, Value), SHA = crypto:hash(sha, Value), {digest, <>}; server_key_exchange_hash(_, Value) -> %% Optimization: Let crypto calculate the hash in sign/verify call Value. %%-------------------------------------------------------------------- -spec verify_connection(ssl_record:ssl_version(), #finished{}, client | server, integer(), binary(), ssl_handshake_history()) -> verified | #alert{}. %% %% Description: Checks the ssl handshake finished message to verify %% the connection. %%------------------------------------------------------------------- verify_connection(Version, #finished{verify_data = Data}, Role, PrfAlgo, MasterSecret, {_, Handshake}) -> %% use the previous hashes case calc_finished(Version, Role, PrfAlgo, MasterSecret, Handshake) of Data -> verified; _ -> ?ALERT_REC(?FATAL, ?DECRYPT_ERROR) end. %%-------------------------------------------------------------------- -spec init_handshake_history() -> ssl_handshake_history(). %% %% Description: Initialize the empty handshake history buffer. %%-------------------------------------------------------------------- init_handshake_history() -> {[], []}. %%-------------------------------------------------------------------- -spec update_handshake_history(ssl_handshake:ssl_handshake_history(), Data ::term()) -> ssl_handshake:ssl_handshake_history(). %% %% Description: Update the handshake history buffer with Data. %%-------------------------------------------------------------------- update_handshake_history({Handshake0, _Prev}, Data) -> {[Data|Handshake0], Handshake0}. verify_server_key(#server_key_params{params_bin = EncParams, signature = Signature}, HashSign = {HashAlgo, _}, ConnectionStates, Version, PubKeyInfo) -> #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates, read), #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, Hash = server_key_exchange_hash(HashAlgo, <>), verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo). select_version(RecordCB, ClientVersion, Versions) -> do_select_version(RecordCB, ClientVersion, Versions). %% Called by TLS 1.2/1.3 Server when "supported_versions" is present %% in ClientHello. %% Input lists are ordered (highest first) select_supported_version(ClientVersions, ServerVersions) -> Fn = fun (ClientVersion) -> lists:member(ClientVersion, ServerVersions) end, case lists:search(Fn, ClientVersions) of {value, ClientVersion} -> ClientVersion; false -> undefined end. %%==================================================================== %% Encode handshake %%==================================================================== encode_handshake(#next_protocol{selected_protocol = SelectedProtocol}, _Version) -> PaddingLength = 32 - ((byte_size(SelectedProtocol) + 2) rem 32), {?NEXT_PROTOCOL, <>}; encode_handshake(#server_hello{server_version = ServerVersion, random = Random, session_id = Session_ID, cipher_suite = CipherSuite, compression_method = Comp_method, extensions = Extensions}, _Version) -> SID_length = byte_size(Session_ID), {Major,Minor} = ServerVersion, ExtensionsBin = encode_hello_extensions(Extensions), {?SERVER_HELLO, <>}; encode_handshake(#certificate{asn1_certificates = ASN1CertList}, _Version) -> ASN1Certs = certs_from_list(ASN1CertList), ACLen = erlang:iolist_size(ASN1Certs), {?CERTIFICATE, <>}; encode_handshake(#server_key_exchange{exchange_keys = Keys}, _Version) -> {?SERVER_KEY_EXCHANGE, Keys}; encode_handshake(#server_key_params{params_bin = Keys, hashsign = HashSign, signature = Signature}, Version) -> EncSign = enc_sign(HashSign, Signature, Version), {?SERVER_KEY_EXCHANGE, <>}; encode_handshake(#certificate_request{certificate_types = CertTypes, hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSignAlgos}, certificate_authorities = CertAuths}, ?TLS_1_2) -> HashSigns = << <<(ssl_cipher:signature_scheme(SignatureScheme)):16 >> || SignatureScheme <- HashSignAlgos >>, EncCertAuths = encode_cert_auths(CertAuths), CertTypesLen = byte_size(CertTypes), HashSignsLen = byte_size(HashSigns), CertAuthsLen = byte_size(EncCertAuths), {?CERTIFICATE_REQUEST, <> }; encode_handshake(#certificate_request{certificate_types = CertTypes, certificate_authorities = CertAuths}, _Version) -> EncCertAuths = encode_cert_auths(CertAuths), CertTypesLen = byte_size(CertTypes), CertAuthsLen = byte_size(EncCertAuths), {?CERTIFICATE_REQUEST, <> }; encode_handshake(#server_hello_done{}, _Version) -> {?SERVER_HELLO_DONE, <<>>}; encode_handshake(#client_key_exchange{exchange_keys = ExchangeKeys}, _Version) -> {?CLIENT_KEY_EXCHANGE, encode_client_key(ExchangeKeys)}; encode_handshake(#certificate_verify{signature = BinSig, hashsign_algorithm = HashSign}, Version) -> EncSig = enc_sign(HashSign, BinSig, Version), {?CERTIFICATE_VERIFY, EncSig}; encode_handshake(#finished{verify_data = VerifyData}, _Version) -> {?FINISHED, VerifyData}. encode_hello_extensions(Extensions) -> encode_extensions(hello_extensions_list(Extensions), <<>>). encode_extensions(Exts) -> encode_extensions(Exts, <<>>). encode_extensions([], <<>>) -> <>; encode_extensions([], Acc) -> Size = byte_size(Acc), <>; encode_extensions([#alpn{extension_data = ExtensionData} | Rest], Acc) -> Len = byte_size(ExtensionData), ExtLen = Len + 2, encode_extensions(Rest, <>); encode_extensions([#next_protocol_negotiation{extension_data = ExtensionData} | Rest], Acc) -> Len = byte_size(ExtensionData), encode_extensions(Rest, <>); encode_extensions([#renegotiation_info{renegotiated_connection = undefined} | Rest], Acc) -> encode_extensions(Rest, Acc); encode_extensions([#renegotiation_info{renegotiated_connection = ?byte(0) = Info} | Rest], Acc) -> Len = byte_size(Info), encode_extensions(Rest, <>); encode_extensions([#renegotiation_info{renegotiated_connection = Info} | Rest], Acc) -> InfoLen = byte_size(Info), Len = InfoLen +1, encode_extensions(Rest, <>); encode_extensions([#elliptic_curves{elliptic_curve_list = EllipticCurves} | Rest], Acc) -> EllipticCurveList = << <<(tls_v1:oid_to_enum(X)):16>> || X <- EllipticCurves>>, ListLen = byte_size(EllipticCurveList), Len = ListLen + 2, encode_extensions(Rest, <>); encode_extensions([#supported_groups{supported_groups = SupportedGroups} | Rest], Acc) -> SupportedGroupList = << <<(tls_v1:group_to_enum(X)):16>> || X <- SupportedGroups>>, ListLen = byte_size(SupportedGroupList), Len = ListLen + 2, encode_extensions(Rest, <>); encode_extensions([#ec_point_formats{ec_point_format_list = ECPointFormats} | Rest], Acc) -> ECPointFormatList = list_to_binary(ECPointFormats), ListLen = byte_size(ECPointFormatList), Len = ListLen + 1, encode_extensions(Rest, <>); encode_extensions([#srp{username = UserName} | Rest], Acc) -> SRPLen = byte_size(UserName), Len = SRPLen + 1, encode_extensions(Rest, <>); encode_extensions([#hash_sign_algos{hash_sign_algos = HashSignAlgos} | Rest], Acc) -> SignAlgoList = << <<(ssl_cipher:signature_scheme(SignatureScheme)):16 >> || SignatureScheme <- HashSignAlgos >>, ListLen = byte_size(SignAlgoList), Len = ListLen + 2, encode_extensions(Rest, <>); encode_extensions([#signature_algorithms{ signature_scheme_list = SignatureSchemes} | Rest], Acc) -> SignSchemeList = << <<(ssl_cipher:signature_scheme(SignatureScheme)):16 >> || SignatureScheme <- SignatureSchemes >>, ListLen = byte_size(SignSchemeList), Len = ListLen + 2, encode_extensions(Rest, <>); encode_extensions([#signature_algorithms_cert{ signature_scheme_list = SignatureSchemes} | Rest], Acc) -> SignSchemeList = << <<(ssl_cipher:signature_scheme(SignatureScheme)):16 >> || SignatureScheme <- SignatureSchemes >>, ListLen = byte_size(SignSchemeList), Len = ListLen + 2, encode_extensions(Rest, <>); encode_extensions([#sni{hostname = ""} | Rest], Acc) -> HostnameBin = <<>>, encode_extensions(Rest, <>); encode_extensions([#sni{hostname = Hostname} | Rest], Acc) -> HostLen = length(Hostname), HostnameBin = list_to_binary(Hostname), % Hostname type (1 byte) + Hostname length (2 bytes) + Hostname (HostLen bytes) ServerNameLength = 1 + 2 + HostLen, % ServerNameListSize (2 bytes) + ServerNameLength ExtLength = 2 + ServerNameLength, encode_extensions(Rest, <>); encode_extensions([#use_srtp{protection_profiles = Profiles, mki = MKI} | Rest], Acc) -> ProfilesBin = iolist_to_binary(Profiles), ProfilesLength = byte_size(ProfilesBin), MKILength = byte_size(MKI), ExtLength = ProfilesLength + 2 + MKILength + 1, encode_extensions(Rest, <>); encode_extensions([#max_frag_enum{enum = MaxFragEnum} | Rest], Acc) -> ExtLength = 1, encode_extensions(Rest, <>); encode_extensions([#client_hello_versions{versions = Versions0} | Rest], Acc) -> Versions = encode_versions(Versions0), VerLen = byte_size(Versions), Len = VerLen + 1, encode_extensions(Rest, <>); encode_extensions([#server_hello_selected_version{selected_version = Version0} | Rest], Acc) -> Version = encode_versions([Version0]), Len = byte_size(Version), %% 2 encode_extensions(Rest, <>); encode_extensions([#key_share_client_hello{client_shares = ClientShares0} | Rest], Acc) -> ClientShares = encode_client_shares(ClientShares0), ClientSharesLen = byte_size(ClientShares), Len = ClientSharesLen + 2, encode_extensions(Rest, <>); encode_extensions([#key_share_server_hello{server_share = ServerShare0} | Rest], Acc) -> ServerShare = encode_key_share_entry(ServerShare0), Len = byte_size(ServerShare), encode_extensions(Rest, <>); encode_extensions([#key_share_hello_retry_request{selected_group = Group0} | Rest], Acc) -> Group = tls_v1:group_to_enum(Group0), encode_extensions(Rest, <>); encode_extensions([#psk_key_exchange_modes{ke_modes = KEModes0} | Rest], Acc) -> KEModes = encode_psk_key_exchange_modes(KEModes0), KEModesLen = byte_size(KEModes), ExtLen = KEModesLen + 1, encode_extensions(Rest, <>); encode_extensions([ #certificate_status_request{ status_type = StatusRequest, request = Request} | Rest], Acc) -> CertStatusReq = encode_cert_status_req(StatusRequest, Request), Len = byte_size(CertStatusReq), encode_extensions( Rest, <>); encode_extensions([#pre_shared_key_client_hello{ offered_psks = #offered_psks{ identities = Identities0, binders = Binders0}} | Rest], Acc) -> Identities = encode_psk_identities(Identities0), Binders = encode_psk_binders(Binders0), Len = byte_size(Identities) + byte_size(Binders), %% The "pre_shared_key" extension MUST be the last extension in the %% ClientHello (this facilitates implementation as described below). %% Servers MUST check that it is the last extension and otherwise fail %% the handshake with an "illegal_parameter" alert. encode_extensions(Rest, <>); encode_extensions([#pre_shared_key_server_hello{selected_identity = Identity} | Rest], Acc) -> encode_extensions(Rest, <>); encode_extensions([#cookie{cookie = Cookie} | Rest], Acc) -> CookieLen = byte_size(Cookie), Len = CookieLen + 2, encode_extensions(Rest, <>); encode_extensions([#early_data_indication{} | Rest], Acc) -> encode_extensions(Rest, <>); encode_extensions([#early_data_indication_nst{indication = MaxSize} | Rest], Acc) -> encode_extensions(Rest, <>); encode_extensions([#certificate_authorities{authorities = CertAuths}| Rest], Acc) -> EncCertAuths = encode_cert_auths(CertAuths), CertAuthsLen = byte_size(EncCertAuths), Len = CertAuthsLen + 2, encode_extensions(Rest, <>). encode_cert_status_req( StatusType, #ocsp_status_request{ responder_id_list = ResponderIDList, request_extensions = ReqExtns}) -> ResponderIDListBin = encode_responderID_list(ResponderIDList), ReqExtnsBin = encode_request_extensions(ReqExtns), <>. encode_responderID_list([]) -> <>; encode_responderID_list(List) -> do_encode_responderID_list(List, <<>>). %% ResponderID is DER-encoded ASN.1 type defined in RFC6960. do_encode_responderID_list([], Acc) -> Len = byte_size(Acc), <>; do_encode_responderID_list([Responder | Rest], Acc) when is_binary(Responder) -> Len = byte_size(Responder), do_encode_responderID_list( Rest, <>). %% Extensions are DER-encoded ASN.1 type defined in RFC6960 following %% extension model employed in X.509 version 3 certificates(RFC5280). encode_request_extensions([]) -> <>; encode_request_extensions(Extns) when is_list(Extns) -> ExtnBin = public_key:der_encode('Extensions', Extns), Len = byte_size(ExtnBin), <>. encode_client_protocol_negotiation(undefined, _) -> undefined; encode_client_protocol_negotiation(_, false) -> #next_protocol_negotiation{extension_data = <<>>}; encode_client_protocol_negotiation(_, _) -> undefined. encode_protocols_advertised_on_server(undefined) -> undefined; encode_protocols_advertised_on_server(Protocols) -> #next_protocol_negotiation{ extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}. encode_cert_auths(Auths) -> DNEncode = fun (Auth) -> DNEncodedBin = public_key:pkix_encode('Name', Auth, otp), DNEncodedLen = byte_size(DNEncodedBin), <> end, list_to_binary(lists:map(DNEncode, Auths)). %%==================================================================== %% Decode handshake %%==================================================================== decode_handshake(_, ?HELLO_REQUEST, <<>>) -> #hello_request{}; decode_handshake(_, ?NEXT_PROTOCOL, <>) -> #next_protocol{selected_protocol = SelectedProtocol}; decode_handshake(Version, ?SERVER_HELLO, <>) -> #server_hello{ server_version = {Major,Minor}, random = Random, session_id = Session_ID, cipher_suite = Cipher_suite, compression_method = Comp_method, extensions = empty_extensions(Version, server_hello)}; decode_handshake(Version, ?SERVER_HELLO, <>) -> HelloExtensions = decode_hello_extensions(Extensions, Version, {Major, Minor}, server_hello), #server_hello{ server_version = {Major,Minor}, random = Random, session_id = Session_ID, cipher_suite = Cipher_suite, compression_method = Comp_method, extensions = HelloExtensions}; decode_handshake(_Version, ?CERTIFICATE, <>) -> #certificate{asn1_certificates = certs_to_list(ASN1Certs)}; %% RFC 6066, servers return a certificate response along with their certificate %% by sending a "CertificateStatus" message immediately after the "Certificate" %% message and before any "ServerKeyExchange" or "CertificateRequest" messages. decode_handshake(_Version, ?CERTIFICATE_STATUS, <>) -> #certificate_status{ status_type = ?CERTIFICATE_STATUS_TYPE_OCSP, response = ASN1OcspResponse}; decode_handshake(_Version, ?SERVER_KEY_EXCHANGE, Keys) -> #server_key_exchange{exchange_keys = Keys}; decode_handshake(?TLS_1_2 = Version, ?CERTIFICATE_REQUEST, <>) -> HashSignAlgos = decode_sign_alg(Version, HashSigns), #certificate_request{certificate_types = CertTypes, hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSignAlgos}, certificate_authorities = decode_cert_auths(EncCertAuths, [])}; decode_handshake(_Version, ?CERTIFICATE_REQUEST, <>) -> #certificate_request{certificate_types = CertTypes, certificate_authorities = decode_cert_auths(EncCertAuths, [])}; decode_handshake(_Version, ?SERVER_HELLO_DONE, <<>>) -> #server_hello_done{}; decode_handshake(?TLS_1_2, ?CERTIFICATE_VERIFY,<>) -> #certificate_verify{hashsign_algorithm = dec_hashsign(HashSign), signature = Signature}; decode_handshake(_Version, ?CERTIFICATE_VERIFY,<>)-> #certificate_verify{signature = Signature}; decode_handshake(_Version, ?CLIENT_KEY_EXCHANGE, PKEPMS) -> #client_key_exchange{exchange_keys = PKEPMS}; decode_handshake(_Version, ?FINISHED, VerifyData) -> #finished{verify_data = VerifyData}; decode_handshake(_, MessageType, _) -> throw(?ALERT_REC(?FATAL, ?DECODE_ERROR, {unknown_or_malformed_handshake, MessageType})). %%-------------------------------------------------------------------- -spec decode_vector(binary()) -> binary(). %% %% Description: Remove length tag from TLS Vector type. Needed %% for client hello when extensions in older versions may be empty. %% %%-------------------------------------------------------------------- decode_vector(<<>>) -> <<>>; decode_vector(<>) -> Vector. %%-------------------------------------------------------------------- -spec decode_hello_extensions(binary(), ssl_record:ssl_version(), ssl_record:ssl_version(), atom()) -> map(). %% %% Description: Decodes TLS hello extensions %%-------------------------------------------------------------------- decode_hello_extensions(Extensions, LocalVersion, LegacyVersion, MessageType0) -> %% Convert legacy atoms MessageType = case MessageType0 of client -> client_hello; server -> server_hello; T -> T end, %% RFC 8446 - 4.2.1 %% Servers MUST be prepared to receive ClientHellos that include this extension but %% do not include 0x0304 in the list of versions. %% Clients MUST check for this extension prior to processing the rest of the %% ServerHello (although they will have to parse the ServerHello in order to read %% the extension). Version = process_supported_versions_extension(Extensions, LocalVersion, LegacyVersion), decode_extensions(Extensions, Version, MessageType, empty_extensions(Version, MessageType)). %%-------------------------------------------------------------------- -spec decode_extensions(binary(),tuple(), atom()) -> map(). %% %% Description: Decodes TLS hello extensions %%-------------------------------------------------------------------- decode_extensions(Extensions, Version, MessageType) -> decode_extensions(Extensions, Version, MessageType, empty_extensions()). %%-------------------------------------------------------------------- -spec decode_server_key(binary(), ssl:kex_algo(), ssl_record:ssl_version()) -> #server_key_params{}. %% %% Description: Decode server_key data and return appropriate type %%-------------------------------------------------------------------- decode_server_key(ServerKey, Type, Version) -> dec_server_key(ServerKey, key_exchange_alg(Type), Version). %%-------------------------------------------------------------------- -spec decode_client_key(binary(), ssl:kex_algo(), ssl_record:ssl_version()) -> #encrypted_premaster_secret{} | #client_diffie_hellman_public{} | #client_ec_diffie_hellman_public{} | #client_psk_identity{} | #client_dhe_psk_identity{} | #client_ecdhe_psk_identity{} | #client_rsa_psk_identity{} | #client_srp_public{}. %% %% Description: Decode client_key data and return appropriate type %%-------------------------------------------------------------------- decode_client_key(ClientKey, Type, Version) -> dec_client_key(ClientKey, key_exchange_alg(Type), Version). %%-------------------------------------------------------------------- -spec decode_suites('2_bytes'|'3_bytes', binary()) -> list(). %% %% Description: %%-------------------------------------------------------------------- decode_suites('2_bytes', Dec) -> from_2bytes(Dec); decode_suites('3_bytes', Dec) -> from_3bytes(Dec). %%==================================================================== %% Cipher suite handling %%==================================================================== available_suites(UserSuites, Version) -> VersionSuites = ssl_cipher:all_suites(Version) ++ ssl_cipher:anonymous_suites(Version), lists:filtermap(fun(Suite) -> lists:member(Suite, VersionSuites) end, UserSuites). available_suites(ServerCert, UserSuites, Version, undefined, Curve) -> Suites = ssl_cipher:filter(ServerCert, available_suites(UserSuites, Version), Version), filter_unavailable_ecc_suites(Curve, Suites); available_suites(ServerCert, UserSuites, Version, HashSigns, Curve) -> Suites = available_suites(ServerCert, UserSuites, Version, undefined, Curve), filter_hashsigns(Suites, [ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- Suites], HashSigns, Version). available_signature_algs(undefined, _) -> undefined; available_signature_algs(SupportedHashSigns, Version) when ?TLS_GTE(Version, ?TLS_1_2) -> case contains_scheme(SupportedHashSigns) of true -> case Version of ?TLS_1_2 -> #hash_sign_algos{hash_sign_algos = ssl_cipher:signature_schemes_1_2(SupportedHashSigns)}; _ -> #signature_algorithms{signature_scheme_list = SupportedHashSigns} end; false -> #hash_sign_algos{hash_sign_algos = SupportedHashSigns} end; available_signature_algs(_, _) -> undefined. available_signature_algs(undefined, SupportedHashSigns, Version) when ?TLS_GTE(Version, ?TLS_1_2) -> SupportedHashSigns; available_signature_algs(#hash_sign_algos{hash_sign_algos = ClientHashSigns}, SupportedHashSigns0, Version) when ?TLS_GTE(Version, ?TLS_1_2) -> SupportedHashSigns = case (Version == ?TLS_1_2) andalso contains_scheme(SupportedHashSigns0) of true -> ssl_cipher:signature_schemes_1_2(SupportedHashSigns0); false -> SupportedHashSigns0 end, sets:to_list(sets:intersection(sets:from_list(ClientHashSigns), sets:from_list(SupportedHashSigns))); available_signature_algs(_, _, _) -> undefined. contains_scheme(Schemes) -> lists:any(fun erlang:is_atom/1, Schemes). cipher_suites(Suites, Renegotiation, true) -> %% TLS_FALLBACK_SCSV should be placed last -RFC7507 cipher_suites(Suites, Renegotiation) ++ [?TLS_FALLBACK_SCSV]; cipher_suites(Suites, Renegotiation, false) -> cipher_suites(Suites, Renegotiation). cipher_suites(Suites, false) -> [?TLS_EMPTY_RENEGOTIATION_INFO_SCSV | Suites]; cipher_suites(Suites, true) -> Suites. %%-------------------------------------------------------------------- -spec prf(ssl_record:ssl_version(), non_neg_integer(), binary(), binary(), [binary()], non_neg_integer()) -> {ok, binary()} | {error, undefined}. %% %% Description: use the TLS PRF to generate key material %%-------------------------------------------------------------------- prf(Version, PRFAlgo, Secret, Label, Seed, WantedLength) when ?TLS_1_X(Version)-> {ok, tls_v1:prf(PRFAlgo, Secret, Label, Seed, WantedLength)}. select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, SessIdTracker, Session0, Version, SslOpts, CertKeyAlts) -> CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, Version), {SessionId, Resumed} = ssl_session:server_select_session(Version, SessIdTracker, SuggestedSessionId, SslOpts, CertKeyPairs), case Resumed of undefined -> %% Select Cert Session = new_session_parameters(SessionId, Session0, CipherSuites, SslOpts, Version, Compressions, HashSigns, CertKeyPairs), {new, Session}; _ -> {resumed, Resumed} end. new_session_parameters(SessionId, #session{ecc = ECCCurve0} = Session, CipherSuites, SslOpts, Version, Compressions, HashSigns, CertKeyPairs) -> Compression = select_compression(Compressions), {Certs, Key, {ECCCurve, CipherSuite}} = server_select_cert_key_pair_and_params(CipherSuites, CertKeyPairs, HashSigns, ECCCurve0, SslOpts, Version), Session#session{session_id = SessionId, ecc = ECCCurve, own_certificates = Certs, private_key = Key, cipher_suite = CipherSuite, compression_method = Compression}. %% Possibly support part of "trusted_ca_keys" extension that corresponds to TLS-1.3 certificate_authorities?! server_select_cert_key_pair_and_params(CipherSuites, [#{private_key := NoKey, certs := [[]] = NoCerts}], HashSigns, ECCCurve0, #{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder}, Version) -> %% This can happen if anonymous cipher suites are enabled Suites = available_suites(undefined, UserSuites, Version, HashSigns, ECCCurve0), CipherSuite0 = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder), CurveAndSuite = cert_curve(undefined, ECCCurve0, CipherSuite0), {NoCerts, NoKey, CurveAndSuite}; server_select_cert_key_pair_and_params(CipherSuites, [#{private_key := Key, certs := [Cert | _] = Certs}], HashSigns, ECCCurve0, #{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder}, Version) -> Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve0), CipherSuite0 = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder), CurveAndSuite = cert_curve(Cert, ECCCurve0, CipherSuite0), {Certs, Key, CurveAndSuite}; server_select_cert_key_pair_and_params(CipherSuites, [#{private_key := Key, certs := [Cert | _] = Certs} | Rest], HashSigns, ECCCurve0, #{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder} = Opts, Version) -> Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve0), case select_cipher_suite(CipherSuites, Suites, HonorCipherOrder) of no_suite -> server_select_cert_key_pair_and_params(CipherSuites, Rest, HashSigns, ECCCurve0, Opts, Version); CipherSuite0 -> case is_acceptable_cert(Cert, HashSigns, ssl:tls_version(Version)) of true -> CurveAndSuite = cert_curve(Cert, ECCCurve0, CipherSuite0), {Certs, Key, CurveAndSuite}; false -> server_select_cert_key_pair_and_params(CipherSuites, Rest, HashSigns, ECCCurve0, Opts, Version) end end. is_acceptable_cert(Cert, HashSigns, Version) when ?TLS_1_X(Version), ?TLS_GTE(Version, ?TLS_1_2) -> {SignAlgo0, Param, _, _, _} = get_cert_params(Cert), SignAlgo = sign_algo(SignAlgo0, Param), is_acceptable_hash_sign(SignAlgo, HashSigns); is_acceptable_cert(_,_,_) -> %% Not negotiable pre TLS-1.2. So if cert is available for version it is acceptable true. premaster_secret(OtherPublicDhKey, MyPrivateKey, #'DHParameter'{} = Params) -> try public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params) catch error:Reason:ST -> ?SSL_LOG(debug, crypto_error, [{reason, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) end; premaster_secret(PublicDhKey, PrivateDhKey, #server_dh_params{dh_p = Prime, dh_g = Base}) -> try crypto:compute_key(dh, PublicDhKey, PrivateDhKey, [Prime, Base]) catch error:Reason:ST -> ?SSL_LOG(debug, crypto_error, [{reason, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) end; premaster_secret(#client_srp_public{srp_a = ClientPublicKey}, ServerKey, #srp_user{prime = Prime, verifier = Verifier}) -> try crypto:compute_key(srp, ClientPublicKey, ServerKey, {host, [Verifier, Prime, '6a']}) catch error:Reason:ST -> ?SSL_LOG(debug, crypto_error, [{reason, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) end; premaster_secret(#server_srp_params{srp_n = Prime, srp_g = Generator, srp_s = Salt, srp_b = Public}, ClientKeys, {Username, Password}) -> case ssl_srp_primes:check_srp_params(Generator, Prime) of ok -> DerivedKey = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, Password])]), try crypto:compute_key(srp, Public, ClientKeys, {user, [DerivedKey, Prime, Generator, '6a']}) catch error:Reason:ST -> ?SSL_LOG(debug, crypto_error, [{reason, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) end; not_accepted -> throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) end; premaster_secret(#client_rsa_psk_identity{ identity = PSKIdentity, exchange_keys = #encrypted_premaster_secret{premaster_secret = EncPMS} }, #'RSAPrivateKey'{} = Key, PSKLookup) -> PremasterSecret = premaster_secret(EncPMS, Key), psk_secret(PSKIdentity, PSKLookup, PremasterSecret); premaster_secret(#server_dhe_psk_params{ hint = IdentityHint, dh_params = #server_dh_params{dh_y = PublicDhKey} = Params}, PrivateDhKey, LookupFun) -> PremasterSecret = premaster_secret(PublicDhKey, PrivateDhKey, Params), psk_secret(IdentityHint, LookupFun, PremasterSecret); premaster_secret(#server_ecdhe_psk_params{ hint = IdentityHint, dh_params = #server_ecdh_params{ public = ECServerPubKey}}, PrivateEcDhKey, LookupFun) -> PremasterSecret = premaster_secret(#'ECPoint'{point = ECServerPubKey}, PrivateEcDhKey), psk_secret(IdentityHint, LookupFun, PremasterSecret); premaster_secret({rsa_psk, PSKIdentity}, PSKLookup, RSAPremasterSecret) -> psk_secret(PSKIdentity, PSKLookup, RSAPremasterSecret); premaster_secret(#client_ecdhe_psk_identity{ identity = PSKIdentity, dh_public = PublicEcDhPoint}, PrivateEcDhKey, PSKLookup) -> PremasterSecret = premaster_secret(#'ECPoint'{point = PublicEcDhPoint}, PrivateEcDhKey), psk_secret(PSKIdentity, PSKLookup, PremasterSecret). premaster_secret(#client_dhe_psk_identity{ identity = PSKIdentity, dh_public = PublicDhKey}, PrivateKey, #'DHParameter'{} = Params, PSKLookup) -> PremasterSecret = premaster_secret(PublicDhKey, PrivateKey, Params), psk_secret(PSKIdentity, PSKLookup, PremasterSecret). premaster_secret(#client_psk_identity{identity = PSKIdentity}, PSKLookup) -> psk_secret(PSKIdentity, PSKLookup); premaster_secret({psk, PSKIdentity}, PSKLookup) -> psk_secret(PSKIdentity, PSKLookup); premaster_secret(#'ECPoint'{} = ECPoint, #'ECPrivateKey'{} = ECDHKeys) -> public_key:compute_key(ECPoint, ECDHKeys); premaster_secret(EncSecret, #'RSAPrivateKey'{} = RSAPrivateKey) -> try public_key:decrypt_private(EncSecret, RSAPrivateKey, [{rsa_pad, rsa_pkcs1_padding}]) catch _:Reason:ST -> ?SSL_LOG(debug, decrypt_error, [{reason, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) end; premaster_secret(EncSecret, #{algorithm := rsa} = Engine) -> try crypto:private_decrypt(rsa, EncSecret, maps:remove(algorithm, Engine), [{rsa_pad, rsa_pkcs1_padding}]) catch _:Reason:ST -> ?SSL_LOG(debug, decrypt_error, [{reason, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) end. %%==================================================================== %% Extensions handling %%==================================================================== client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation, KeyShare, TicketData, OcspNonce, CertDbHandle, CertDbRef) -> HelloExtensions0 = add_tls12_extensions(Version, SslOpts, ConnectionStates, Renegotiation), HelloExtensions1 = add_common_extensions(Version, HelloExtensions0, CipherSuites, SslOpts), HelloExtensions2 = maybe_add_certificate_status_request(Version, SslOpts, OcspNonce, HelloExtensions1), maybe_add_tls13_extensions(Version, HelloExtensions2, SslOpts, KeyShare, TicketData, CertDbHandle, CertDbRef). add_tls12_extensions(_Version, #{alpn_advertised_protocols := AlpnAdvertisedProtocols, max_fragment_length := MaxFragmentLength} = SslOpts, ConnectionStates, Renegotiation) -> SRP = srp_user(SslOpts), NextProtocolSelector = maps:get(next_protocol_selector, SslOpts, undefined), #{renegotiation_info => renegotiation_info(tls_record, client, ConnectionStates, Renegotiation), srp => SRP, alpn => encode_alpn(AlpnAdvertisedProtocols, Renegotiation), next_protocol_negotiation => encode_client_protocol_negotiation(NextProtocolSelector, Renegotiation), sni => sni(SslOpts), use_srtp => use_srtp_ext(SslOpts), max_frag_enum => max_frag_enum(MaxFragmentLength) }. add_common_extensions(?TLS_1_3, HelloExtensions, _CipherSuites, #{eccs := SupportedECCs, supported_groups := Groups, signature_algs := SignatureSchemes, signature_algs_cert := SignatureCertSchemes}) -> {EcPointFormats, _} = client_ecc_extensions(SupportedECCs), HelloExtensions#{ec_point_formats => EcPointFormats, elliptic_curves => Groups, signature_algs => signature_algs_ext(SignatureSchemes), signature_algs_cert => signature_algs_cert(SignatureCertSchemes)}; add_common_extensions(Version, HelloExtensions, CipherSuites, #{eccs := SupportedECCs, signature_algs := SupportedHashSigns, signature_algs_cert := SignatureCertSchemes}) -> {EcPointFormats, EllipticCurves} = case advertises_ec_ciphers( lists:map(fun ssl_cipher_format:suite_bin_to_map/1, CipherSuites)) of true -> client_ecc_extensions(SupportedECCs); false -> {undefined, undefined} end, HelloExtensions#{ec_point_formats => EcPointFormats, elliptic_curves => EllipticCurves, signature_algs => available_signature_algs(SupportedHashSigns, Version), signature_algs_cert => signature_algs_cert(SignatureCertSchemes)}. maybe_add_tls13_extensions(?TLS_1_3, HelloExtensions0, #{versions := SupportedVersions} = Opts, KeyShare, TicketData, CertDbHandle, CertDbRef) -> HelloExtensions1 = HelloExtensions0#{client_hello_versions => #client_hello_versions{versions = SupportedVersions}}, HelloExtensions2 = maybe_add_key_share(HelloExtensions1, KeyShare), HelloExtensions = maybe_add_pre_shared_key(HelloExtensions2, TicketData), AddCA = maps:get(certificate_authorities, Opts, false), maybe_add_certificate_auths(HelloExtensions, CertDbHandle, CertDbRef, AddCA); maybe_add_tls13_extensions(_, HelloExtensions, _, _, _, _,_) -> HelloExtensions. maybe_add_certificate_status_request(_Version, #{ocsp_stapling := OcspStapling}, OcspNonce, HelloExtensions) -> OcspResponderCerts = maps:get(ocsp_responder_certs, OcspStapling), OcspResponderList = get_ocsp_responder_list(OcspResponderCerts), OcspRequestExtns = public_key:ocsp_extensions(OcspNonce), Req = #ocsp_status_request{responder_id_list = OcspResponderList, request_extensions = OcspRequestExtns}, CertStatusReqExtn = #certificate_status_request{ status_type = ?CERTIFICATE_STATUS_TYPE_OCSP, request = Req }, HelloExtensions#{status_request => CertStatusReqExtn}; maybe_add_certificate_status_request(_Version, _SslOpts, _OcspNonce, HelloExtensions) -> HelloExtensions. get_ocsp_responder_list(ResponderCerts) -> lists:map(fun public_key:ocsp_responder_id/1, ResponderCerts). %% TODO: Add support for PSK key establishment %% RFC 8446 (TLS 1.3) - 4.2.8. Key Share %% %% 4.2.8.1. Diffie-Hellman Parameters %% Diffie-Hellman [DH76] parameters for both clients and servers are %% encoded in the opaque key_exchange field of a KeyShareEntry in a %% KeyShare structure. The opaque value contains the Diffie-Hellman %% public value (Y = g^X mod p) for the specified group (see [RFC7919] %% for group definitions) encoded as a big-endian integer and padded to %% the left with zeros to the size of p in bytes. %% %% 4.2.8.2. ECDHE Parameters %% %% ECDHE parameters for both clients and servers are encoded in the %% opaque key_exchange field of a KeyShareEntry in a KeyShare structure. %% %% For secp256r1, secp384r1, and secp521r1, the contents are the %% serialized value of the following struct: %% %% struct { %% uint8 legacy_form = 4; %% opaque X[coordinate_length]; %% opaque Y[coordinate_length]; %% } UncompressedPointRepresentation; %% %% X and Y, respectively, are the binary representations of the x and y %% values in network byte order. There are no internal length markers, %% so each number representation occupies as many octets as implied by %% the curve parameters. For P-256, this means that each of X and Y use %% 32 octets, padded on the left by zeros if necessary. For P-384, they %% take 48 octets each. For P-521, they take 66 octets each. maybe_add_key_share(HelloExtensions, undefined) -> HelloExtensions; maybe_add_key_share(HelloExtensions, KeyShare) -> #key_share_client_hello{client_shares = ClientShares0} = KeyShare, %% Keep only public keys ClientShares = lists:map(fun kse_remove_private_key/1, ClientShares0), HelloExtensions#{key_share => #key_share_client_hello{ client_shares = ClientShares}}. maybe_add_pre_shared_key(HelloExtensions, undefined) -> HelloExtensions; maybe_add_pre_shared_key(HelloExtensions, TicketData) -> {Identities, Binders} = get_identities_binders(TicketData), %% A client MUST provide a "psk_key_exchange_modes" extension if it %% offers a "pre_shared_key" extension. HelloExtensions#{pre_shared_key => #pre_shared_key_client_hello{ offered_psks = #offered_psks{ identities = Identities, binders = Binders}}, psk_key_exchange_modes => #psk_key_exchange_modes{ ke_modes = [psk_ke, psk_dhe_ke]}}. maybe_add_certificate_auths(HelloExtensions,_,_,false) -> HelloExtensions; maybe_add_certificate_auths(HelloExtensions, CertDbHandle, CertDbRef, true) -> Auths = certificate_authorities(CertDbHandle, CertDbRef), HelloExtensions#{certificate_authorities => #certificate_authorities{authorities = Auths}}. get_identities_binders(TicketData) -> get_identities_binders(TicketData, {[], []}, 0). %% get_identities_binders([], {Identities, Binders}, _) -> {lists:reverse(Identities), lists:reverse(Binders)}; get_identities_binders([#ticket_data{key = Key, identity = Identity, cipher_suite = {_, HKDF}}|T], {I0, B0}, N) -> %% Use dummy binder for proper calculation of packet size when creating %% the real binder value. Binder = dummy_binder(HKDF), %% Store ticket position in identities tls_client_ticket_store:update_ticket(Key, N), get_identities_binders(T, {[Identity|I0], [Binder|B0]}, N + 1). dummy_binder(HKDF) -> binary:copy(<<0>>, ssl_cipher:hash_size(HKDF)). add_server_share(server_hello, Extensions, KeyShare) -> #key_share_server_hello{server_share = ServerShare0} = KeyShare, %% Keep only public keys ServerShare = kse_remove_private_key(ServerShare0), Extensions#{key_share => #key_share_server_hello{ server_share = ServerShare}}; add_server_share(hello_retry_request, Extensions, #key_share_server_hello{ server_share = #key_share_entry{group = Group}}) -> Extensions#{key_share => #key_share_hello_retry_request{ selected_group = Group}}. add_alpn(Extensions, ALPN0) -> ALPN = encode_alpn([ALPN0], false), Extensions#{alpn => ALPN}. add_selected_version(Extensions) -> SupportedVersions = #server_hello_selected_version{selected_version = ?TLS_1_3}, Extensions#{server_hello_selected_version => SupportedVersions}. kse_remove_private_key(#key_share_entry{ group = Group, key_exchange = #'ECPrivateKey'{publicKey = PublicKey}}) -> #key_share_entry{ group = Group, key_exchange = PublicKey}; kse_remove_private_key(#key_share_entry{ group = Group, key_exchange = {PublicKey, _}}) -> #key_share_entry{ group = Group, key_exchange = PublicKey}. signature_algs_ext(undefined) -> undefined; signature_algs_ext(SignatureSchemes0) -> %% The SSL option signature_algs contains both hash-sign algorithms (tuples) and %% signature schemes (atoms) if TLS 1.3 is configured. #signature_algorithms{signature_scheme_list = SignatureSchemes0}. signature_algs_cert(undefined) -> undefined; signature_algs_cert(SignatureSchemes) -> #signature_algorithms_cert{signature_scheme_list = SignatureSchemes}. use_srtp_ext(#{use_srtp := #{protection_profiles := Profiles, mki := MKI}}) -> #use_srtp{protection_profiles = Profiles, mki = MKI}; use_srtp_ext(#{}) -> undefined. handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites, Exts, Version, #{secure_renegotiate := SecureRenegotation, alpn_preferred_protocols := ALPNPreferredProtocols} = Opts, #session{cipher_suite = NegotiatedCipherSuite, compression_method = Compression} = Session0, ConnectionStates0, Renegotiation, IsResumed) -> Session = handle_srp_extension(maps:get(srp, Exts, undefined), Session0), MaxFragEnum = handle_mfl_extension(maps:get(max_frag_enum, Exts, undefined)), ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, maps:get(renegotiation_info, Exts, undefined), Random, NegotiatedCipherSuite, ClientCipherSuites, Compression, ConnectionStates1, Renegotiation, SecureRenegotation), Empty = empty_extensions(Version, server_hello), %% RFC 6066 - server doesn't include max_fragment_length for resumed sessions ServerMaxFragEnum = if IsResumed -> undefined; true -> MaxFragEnum end, ServerHelloExtensions = Empty#{renegotiation_info => renegotiation_info(RecordCB, server, ConnectionStates, Renegotiation), ec_point_formats => server_ecc_extension(Version, maps:get(ec_point_formats, Exts, undefined)), use_srtp => use_srtp_ext(Opts), max_frag_enum => ServerMaxFragEnum }, %% If we receive an ALPN extension and have ALPN configured for this connection, %% we handle it. Otherwise we check for the NPN extension. ALPN = maps:get(alpn, Exts, undefined), if ALPN =/= undefined, ALPNPreferredProtocols =/= undefined -> Protocol = handle_alpn_extension(ALPNPreferredProtocols, decode_alpn(ALPN)), {Session, ConnectionStates, Protocol, ServerHelloExtensions#{alpn => encode_alpn([Protocol], Renegotiation)}}; true -> NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined), ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts), {Session, ConnectionStates, undefined, ServerHelloExtensions#{next_protocol_negotiation => encode_protocols_advertised_on_server(ProtocolsToAdvertise)}} end. handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression, Exts, Version, #{secure_renegotiate := SecureRenegotation} = SslOpts, ConnectionStates0, Renegotiation, IsNew) -> ConnectionStates = handle_renegotiation_extension(client, RecordCB, Version, maps:get(renegotiation_info, Exts, undefined), Random, CipherSuite, undefined, Compression, ConnectionStates0, Renegotiation, SecureRenegotation), %% RFC 6066: handle received/expected maximum fragment length if IsNew -> ServerMaxFragEnum = maps:get(max_frag_enum, Exts, undefined), #{current_write := #{max_fragment_length := ConnMaxFragLen}} = ConnectionStates, ClientMaxFragEnum = max_frag_enum(ConnMaxFragLen), if ServerMaxFragEnum == ClientMaxFragEnum -> ok; true -> throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) end; true -> ok end, case handle_ocsp_extension(SslOpts, Exts) of #alert{} = Alert -> Alert; OcspState -> %% If we receive an ALPN extension then this is the protocol selected, %% otherwise handle the NPN extension. ALPN = maps:get(alpn, Exts, undefined), case decode_alpn(ALPN) of %% ServerHello contains exactly one protocol: the one selected. %% We also ignore the ALPN extension during renegotiation (see encode_alpn/2). [Protocol] when not Renegotiation -> {ConnectionStates, alpn, Protocol, OcspState}; [_] when Renegotiation -> {ConnectionStates, alpn, undefined, OcspState}; undefined -> NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined), NextProtocolSelector = maps:get(next_protocol_selector, SslOpts, undefined), Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtocolSelector, Renegotiation), {ConnectionStates, npn, Protocol, OcspState}; {error, Reason} -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); [] -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, no_protocols_in_server_hello); [_|_] -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, too_many_protocols_in_server_hello) end end. select_curve(Client, Server) -> select_curve(Client, Server, false). select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves}, #elliptic_curves{elliptic_curve_list = ServerCurves}, ServerOrder) -> case ServerOrder of false -> select_shared_curve(ClientCurves, ServerCurves); true -> select_shared_curve(ServerCurves, ClientCurves) end; select_curve(undefined, _, _) -> %% Client did not send ECC extension use default curve if %% ECC cipher is negotiated case tls_v1:ecc_curves([secp256r1]) of [] -> %% Curve not supported by cryptolib ECC algorithms can not be negotiated no_curve; [CurveOid] -> {namedCurve, CurveOid} end; select_curve({supported_groups, Groups}, Server, HonorServerOrder) -> %% TLS-1.3 hello but lesser version chosen TLSCommonCurves = [secp256r1,secp384r1,secp521r1], Curves = [tls_v1:enum_to_oid(tls_v1:group_to_enum(Name)) || Name <- Groups, lists:member(Name, TLSCommonCurves)], case tls_v1:ecc_curves(Curves) of [] -> select_curve(undefined, Server, HonorServerOrder); [_|_] = ClientCurves -> select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves}, Server, HonorServerOrder) end. %%-------------------------------------------------------------------- -spec select_hashsign(#hash_sign_algos{} | undefined, undefined | binary(), atom(), [atom()], ssl_record:ssl_version()) -> {atom(), atom()} | undefined | #alert{}. %% %% Description: Handles signature_algorithms hello extension (server) %%-------------------------------------------------------------------- select_hashsign(_, _, KeyExAlgo, _, _Version) when KeyExAlgo == dh_anon; KeyExAlgo == ecdh_anon; KeyExAlgo == srp_anon; KeyExAlgo == psk; KeyExAlgo == dhe_psk; KeyExAlgo == ecdhe_psk -> {null, anon}; %% The signature_algorithms extension was introduced with TLS 1.2. Ignore it if we have %% negotiated a lower version. select_hashsign({ClientHashSigns, ClientSignatureSchemes}, Cert, KeyExAlgo, undefined, ?TLS_1_2 = Version) -> select_hashsign({ClientHashSigns, ClientSignatureSchemes}, Cert, KeyExAlgo, tls_v1:default_signature_algs([Version]), Version); select_hashsign({#hash_sign_algos{hash_sign_algos = ClientHashSigns}, ClientSignatureSchemes0}, Cert, KeyExAlgo, SupportedHashSigns, ?TLS_1_2) -> ClientSignatureSchemes = get_signature_scheme(ClientSignatureSchemes0), {SignAlgo0, Param, PublicKeyAlgo0, _, _} = get_cert_params(Cert), SignAlgo = sign_algo(SignAlgo0, Param), PublicKeyAlgo = ssl_certificate:public_key_type(PublicKeyAlgo0), %% RFC 5246 (TLS 1.2) %% If the client provided a "signature_algorithms" extension, then all %% certificates provided by the server MUST be signed by a %% hash/signature algorithm pair that appears in that extension. %% %% RFC 8446 (TLS 1.3) %% TLS 1.3 provides two extensions for indicating which signature %% algorithms may be used in digital signatures. The %% "signature_algorithms_cert" extension applies to signatures in %% certificates and the "signature_algorithms" extension, which %% originally appeared in TLS 1.2, applies to signatures in %% CertificateVerify messages. %% %% If no "signature_algorithms_cert" extension is %% present, then the "signature_algorithms" extension also applies to %% signatures appearing in certificates. case is_supported_sign(SignAlgo, Param, ClientHashSigns, ClientSignatureSchemes) of true -> case (KeyExAlgo == psk) orelse (KeyExAlgo == dhe_psk) orelse (KeyExAlgo == ecdhe_psk) orelse (KeyExAlgo == srp_anon) orelse (KeyExAlgo == dh_anon) orelse (KeyExAlgo == ecdhe_anon) of true -> ClientHashSigns; false -> do_select_hashsign(ClientHashSigns, PublicKeyAlgo, SupportedHashSigns) end; false -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm) end; select_hashsign(_, Cert, _, _, Version) -> #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, select_hashsign_algs(undefined, Algo, Version). %%-------------------------------------------------------------------- -spec select_hashsign(#certificate_request{}, binary(), [atom()], ssl_record:ssl_version()) -> {atom(), atom()} | #alert{}. %% %% Description: Handles signature algorithms selection for certificate requests (client) %%-------------------------------------------------------------------- select_hashsign(#certificate_request{ hashsign_algorithms = #hash_sign_algos{ hash_sign_algos = HashSigns}, certificate_types = Types}, Cert, SupportedHashSigns, ?TLS_1_2) -> {SignAlgo0, Param, PublicKeyAlgo0, _, _} = get_cert_params(Cert), SignAlgo = {_, KeyType} = sign_algo(SignAlgo0, Param), PublicKeyAlgo = ssl_certificate:public_key_type(PublicKeyAlgo0), SignatureSchemes = [Scheme || Scheme <- HashSigns, is_atom(Scheme), (KeyType == rsa_pss_pss) or (KeyType == rsa)], case is_acceptable_cert_type(PublicKeyAlgo, Types) andalso is_supported_sign(SignAlgo, Param, HashSigns, SignatureSchemes) of true -> do_select_hashsign(HashSigns, PublicKeyAlgo, SupportedHashSigns); false -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm) end; select_hashsign(#certificate_request{certificate_types = Types}, Cert, _, Version) -> {_, _, PublicKeyAlgo0, _, _} = get_cert_params(Cert), PublicKeyAlgo = ssl_certificate:public_key_type(PublicKeyAlgo0), %% Check cert even for TLS 1.0/1.1 case is_acceptable_cert_type(PublicKeyAlgo, Types) of true -> select_hashsign(undefined, Cert, undefined, [], Version); false -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm) end. do_select_hashsign(HashSigns, PublicKeyAlgo, SupportedHashSigns) -> case lists:filter(fun({H, rsa_pss_pss = S} = Algos) when S == PublicKeyAlgo -> is_acceptable_hash_sign(list_to_existing_atom(atom_to_list(S) ++ "_" ++ atom_to_list(H)), SupportedHashSigns) orelse is_acceptable_hash_sign(Algos, SupportedHashSigns); ({H, rsa_pss_rsae = S} = Algos) when PublicKeyAlgo == rsa -> is_acceptable_hash_sign(list_to_existing_atom(atom_to_list(S) ++ "_" ++ atom_to_list(H)), SupportedHashSigns) orelse is_acceptable_hash_sign(Algos, SupportedHashSigns); ({_, S} = Algos) when S == PublicKeyAlgo -> is_acceptable_hash_sign(Algos, SupportedHashSigns); (_A) -> false end, HashSigns) of [] -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm); [HashSign | _] -> HashSign end. %% Gets the relevant parameters of a certificate: %% - signature algorithm %% - parameters of the signature algorithm %% - public key algorithm (key type) %% - RSA key size in bytes %% - Elliptic Curve (public key) get_cert_params(Cert) -> #'OTPCertificate'{tbsCertificate = TBSCert, signatureAlgorithm = {_,SignAlgo, Param}} = public_key:pkix_decode_cert(Cert, otp), #'OTPSubjectPublicKeyInfo'{algorithm = {_, PublicKeyAlgo, _}, subjectPublicKey = PublicKey} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, RSAKeySize = case PublicKey of #'RSAPublicKey'{modulus = Modulus} -> %% Get RSA key size in bytes byte_size(binary:encode_unsigned(Modulus)); _ -> undefined end, Curve = get_ec_curve(TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo), {SignAlgo, Param, PublicKeyAlgo, RSAKeySize, Curve}. get_ec_curve(#'OTPSubjectPublicKeyInfo'{ algorithm = #'PublicKeyAlgorithm'{ algorithm = ?'id-ecPublicKey', parameters = {namedCurve, ?'secp256r1'}}}) -> secp256r1; get_ec_curve(#'OTPSubjectPublicKeyInfo'{ algorithm = #'PublicKeyAlgorithm'{ algorithm = ?'id-ecPublicKey', parameters = {namedCurve, ?'secp384r1'}}}) -> secp384r1; get_ec_curve(#'OTPSubjectPublicKeyInfo'{ algorithm = #'PublicKeyAlgorithm'{ algorithm = ?'id-ecPublicKey', parameters = {namedCurve, ?'secp521r1'}}}) -> secp521r1; get_ec_curve(#'OTPSubjectPublicKeyInfo'{ algorithm = #'PublicKeyAlgorithm'{ algorithm = ?'id-ecPublicKey', parameters = {ecParameters, #'ECParameters'{curve = #'Curve'{} = Curve, base = Base, order = Order, cofactor = Cofactor}}}}) -> curve_to_atom(Curve, Base, Order, Cofactor); get_ec_curve(_) -> unsupported. curve_to_atom(#'Curve'{ a = <<255,255,255,255,0,0,0,1,0,0,0,0,0,0,0,0, 0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,252>>, b = <<90,198,53,216,170,58,147,231,179,235,189,85,118,152,134,188, 101,29,6,176,204,83,176,246,59,206,60,62,39,210,96,75>>, seed = <<196,157,54,8,134,231,4,147,106,102,120,225,19,157,38,183, 129,159,126,144>>}, <<4,107,23,209,242,225,44,66,71,248,188,230,229,99,164,64, 242,119,3,125,129,45,235,51,160,244,161,57,69,216,152,194, 150,79,227,66,226,254,26,127,155,142,231,235,74,124,15,158, 22,43,206,51,87,107,49,94,206,203,182,64,104,55,191,81, 245>>, 115792089210356248762697446949407573529996955224135760342422259061068512044369, 1 ) -> secp256r1; curve_to_atom(#'Curve'{ a = <<255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254, 255,255,255,255,0,0,0,0,0,0,0,0,255,255,255,252>>, b = <<179,49,47,167,226, 62,231,228,152,142,5,107,227,248,45,25, 24,29,156,110,254,129,65,18,3,20,8,143,80,19,135,90, 198,86,57,141,138,46,209,157,42,133,200,237,211,236,42,239>>, seed = <<163,53,146,106,163,25,162,122,29,0,137,106,103,115,164,130, 122,205,172,115>>}, <<4,170,135,202,34,190,139,5,55,142,177,199,30,243,32,173, 116,110,29,59,98,139,167,155,152,89,247,65,224,130,84,42, 56,85,2,242,93,191,85,41,108,58,84,94,56,114,118,10, 183,54,23,222,74,150,38,44,111,93,158,152,191,146,146,220, 41,248,244,29,189,40,154,20,124,233,218,49,19,181,240,184, 192,10,96,177,206,29,126,129,157,122,67,29,124,144,234,14, 95>>, 39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643, 1) -> secp384r1; curve_to_atom(#'Curve'{ a = <<1,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,252>>, b = <<0,81,149,62,185,97,142,28,154,31,146,154,33,160,182,133, 64,238,162,218,114,91,153,179,21,243,184,180,137,145,142,241, 9,225,86,25,57,81,236,126,147,123,22,82,192,189,59,177, 191,7,53,115,223,136,61,44,52,241,239,69,31,212,107,80, 63,0>>, seed = <<208,158,136,0,41,28,184,83,150,204,103,23,57,50,132,170, 160,218,100,186>>}, <<4,0,198,133,142,6,183,4,4,233,205,158,62,203,102,35, 149,180,66,156,100,129,57,5,63,181,33,248,40,175,96,107, 77,61,186,161,75,94,119,239,231,89,40,254,29,193,39,162, 255,168,222,51,72,179,193,133,106,66,155,249,126,126,49,194, 229,189,102,1,24,57,41,106,120,154,59,192,4,92,138,95, 180,44,125,27,217,152,245,68,73,87,155,68,104,23,175,189, 23,39,62,102,44,151,238,114,153,94,244,38,64,197,80,185, 1,63,173,7,97,53,60,112,134,162,114,194,64,136,190,148, 118,159,209,102,80>>, 6864797660130609714981900799081393217269435300143305409394463459185543183397655394245057746333217197532963996371363321113864768612440380340372808892707005449, 1) -> secp521r1; curve_to_atom(_, _, _, _) -> unsupported. select_own_cert([OwnCert| _]) -> OwnCert; select_own_cert(undefined) -> undefined. get_signature_scheme(undefined) -> []; get_signature_scheme(#signature_algorithms_cert{ signature_scheme_list = ClientSignatureSchemes}) -> ClientSignatureSchemes. %%-------------------------------------------------------------------- -spec select_hashsign_algs({atom(), atom()}| undefined, oid(), ssl_record:ssl_version()) -> {atom(), atom()}. %% Description: For TLS 1.2 hash function and signature algorithm pairs can be %% negotiated with the signature_algorithms extension, %% for previous versions always use appropriate defaults. %% RFC 5246, Sect. 7.4.1.4.1. Signature Algorithms %% If the client does not send the signature_algorithms extension, the %% server MUST do the following: (e.i defaults for TLS 1.2) %% %% - If the negotiated key exchange algorithm is one of (RSA, DHE_RSA, %% DH_RSA, RSA_PSK, ECDH_RSA, ECDHE_RSA), behave as if client had %% sent the value {sha1,rsa}. %% %% - If the negotiated key exchange algorithm is one of (DHE_DSS, %% DH_DSS), behave as if the client had sent the value {sha1,dsa}. %% %% - If the negotiated key exchange algorithm is one of (ECDH_ECDSA, %% ECDHE_ECDSA), behave as if the client had sent value {sha1,ecdsa}. %%-------------------------------------------------------------------- select_hashsign_algs(HashSign, _, ?TLS_1_2) when HashSign =/= undefined -> HashSign; select_hashsign_algs(undefined, ?rsaEncryption, ?TLS_1_2) -> {sha, rsa}; select_hashsign_algs(undefined,?'id-ecPublicKey', _) -> {sha, ecdsa}; select_hashsign_algs(undefined, ?rsaEncryption, _) -> {md5sha, rsa}; select_hashsign_algs(undefined, ?'id-dsa', _) -> {sha, dsa}. srp_user(#{srp_identity := {UserName, _}}) -> #srp{username = UserName}; srp_user(_) -> undefined. extension_value(undefined) -> undefined; extension_value(#sni{hostname = HostName}) -> HostName; extension_value(#use_srtp{protection_profiles = ProtectionProfiles, mki = MKI}) -> #{protection_profiles => ProtectionProfiles, mki => MKI}; extension_value(#ec_point_formats{ec_point_format_list = List}) -> List; extension_value(#elliptic_curves{elliptic_curve_list = List}) -> List; extension_value(#supported_groups{supported_groups = SupportedGroups}) -> SupportedGroups; extension_value(#hash_sign_algos{hash_sign_algos = Algos}) -> Algos; extension_value(#alpn{extension_data = Data}) -> Data; extension_value(#max_frag_enum{enum = Enum}) -> Enum; extension_value(#next_protocol_negotiation{extension_data = Data}) -> Data; extension_value(#srp{username = Name}) -> Name; extension_value(#renegotiation_info{renegotiated_connection = Data}) -> Data; extension_value(#signature_algorithms{signature_scheme_list = Schemes}) -> Schemes; extension_value(#signature_algorithms_cert{signature_scheme_list = Schemes}) -> Schemes; extension_value(#key_share_client_hello{client_shares = ClientShares}) -> ClientShares; extension_value(#key_share_server_hello{server_share = ServerShare}) -> ServerShare; extension_value(#client_hello_versions{versions = Versions}) -> Versions; extension_value(#server_hello_selected_version{selected_version = SelectedVersion}) -> SelectedVersion; extension_value(#pre_shared_key_client_hello{offered_psks = PSKs}) -> PSKs; extension_value(#pre_shared_key_server_hello{selected_identity = SelectedIdentity}) -> SelectedIdentity; extension_value(#psk_key_exchange_modes{ke_modes = Modes}) -> Modes; extension_value(#cookie{cookie = Cookie}) -> Cookie. handle_ocsp_extension(#{ocsp_stapling := _OcspStapling}, Extensions) -> case maps:get(status_request, Extensions, false) of undefined -> %% status_request in server hello is empty #{ocsp_stapling => true, ocsp_expect => staple}; false -> %% status_request is missing (not negotiated) #{ocsp_stapling => true, ocsp_expect => no_staple}; _Else -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, status_request_not_empty) end; handle_ocsp_extension(_SslOpts, Extensions) -> case maps:get(status_request, Extensions, false) of false -> %% status_request is missing (not negotiated) #{ocsp_stapling => false, ocsp_expect => no_staple}; _Else -> %% unsolicited status_request ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_status_request) end. certificate_authorities(CertDbHandle, CertDbRef) -> Auths = fun(#'OTPCertificate'{tbsCertificate = TBSCert}) -> TBSCert#'OTPTBSCertificate'.subject end, [Auths(Cert) || #cert{otp = Cert} <- certificate_authorities_from_db(CertDbHandle, CertDbRef)]. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- %%------------- Create handshake messages ---------------------------- int_to_bin(I) -> L = (length(integer_to_list(I, 16)) + 1) div 2, <>. %% The end-entity certificate provided by the client MUST contain a %% key that is compatible with certificate_types. certificate_types(Version) when ?TLS_LTE(Version, ?TLS_1_2) -> ECDSA = supported_cert_type_or_empty(ecdsa, ?ECDSA_SIGN), RSA = supported_cert_type_or_empty(rsa, ?RSA_SIGN), DSS = supported_cert_type_or_empty(dss, ?DSS_SIGN), <>. %% Returns encoded certificate_type if algorithm is supported supported_cert_type_or_empty(Algo, Type) -> case proplists:get_bool( Algo, proplists:get_value(public_keys, crypto:supports())) of true -> <>; false -> <<>> end. certificate_authorities_from_db(CertDbHandle, CertDbRef) when is_reference(CertDbRef) -> ConnectionCerts = fun({{Ref, _, _}, Cert}, Acc) when Ref == CertDbRef -> [Cert | Acc]; (_, Acc) -> Acc end, ssl_pkix_db:foldl(ConnectionCerts, [], CertDbHandle); certificate_authorities_from_db(_CertDbHandle, {extracted, CertDbData}) -> %% Cache disabled, Ref contains data lists:foldl(fun({decoded, {_Key,Cert}}, Acc) -> [Cert | Acc] end, [], CertDbData). %%-------------Handle handshake messages -------------------------------- path_validate(TrustedAndPath, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version, SslOptions, CertExt) -> InitialPotentialError = {error, {bad_cert, unknown_ca}}, InitialInvalidated = [], path_validate(TrustedAndPath, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version, SslOptions, CertExt, InitialInvalidated, InitialPotentialError). validation_fun_and_state({Fun, UserState0}, VerifyState, CertPath, LogLevel) -> {fun(OtpCert, {extension, _} = Extension, {SslState, UserState}) -> case ssl_certificate:validate(OtpCert, Extension, SslState) of {valid, NewSslState} -> {valid, {NewSslState, UserState}}; {fail, Reason} -> apply_user_fun(Fun, OtpCert, Reason, UserState, SslState, CertPath, LogLevel); {unknown, _} -> apply_user_fun(Fun, OtpCert, Extension, UserState, SslState, CertPath, LogLevel) end; (OtpCert, VerifyResult, {SslState, UserState}) -> apply_user_fun(Fun, OtpCert, VerifyResult, UserState, SslState, CertPath, LogLevel) end, {VerifyState, UserState0}}; validation_fun_and_state(undefined, VerifyState, CertPath, LogLevel) -> {fun(OtpCert, {extension, _} = Extension, SslState) -> ssl_certificate:validate(OtpCert, Extension, SslState); (OtpCert, VerifyResult, SslState) when (VerifyResult == valid) or (VerifyResult == valid_peer) -> case cert_status_check(OtpCert, SslState, VerifyResult, CertPath, LogLevel) of valid -> ssl_certificate:validate(OtpCert, VerifyResult, SslState); Reason -> {fail, Reason} end; (OtpCert, VerifyResult, SslState) -> ssl_certificate:validate(OtpCert, VerifyResult, SslState) end, VerifyState}. apply_user_fun(Fun, OtpCert, VerifyResult0, UserState0, SslState, CertPath, LogLevel) when (VerifyResult0 == valid) or (VerifyResult0 == valid_peer) -> VerifyResult = maybe_check_hostname(OtpCert, VerifyResult0, SslState), case apply_fun(Fun, OtpCert, VerifyResult, UserState0, CertPath) of {Valid, UserState} when (Valid == valid) orelse (Valid == valid_peer) -> case cert_status_check(OtpCert, SslState, VerifyResult, CertPath, LogLevel) of valid -> {Valid, {SslState, UserState}}; Result -> apply_user_fun(Fun, OtpCert, Result, UserState, SslState, CertPath, LogLevel) end; {fail, _} = Fail -> Fail end; apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, CertPath, _LogLevel) -> case apply_fun(Fun, OtpCert, ExtensionOrError, UserState0, CertPath) of {Valid, UserState} when (Valid == valid) orelse (Valid == valid_peer)-> {Valid, {SslState, UserState}}; {fail, _} = Fail -> Fail; {unknown, UserState} -> {unknown, {SslState, UserState}} end. apply_fun(Fun, OtpCert, ExtensionOrError, UserState, CertPath) -> if is_function(Fun, 4) -> #cert{der=DerCert} = lists:keyfind(OtpCert, #cert.otp, CertPath), Fun(OtpCert, DerCert, ExtensionOrError, UserState); is_function(Fun, 3) -> Fun(OtpCert, ExtensionOrError, UserState) end. maybe_check_hostname(OtpCert, valid_peer, SslState) -> case ssl_certificate:validate(OtpCert, valid_peer, SslState) of {valid, _} -> valid_peer; {fail, Reason} -> Reason end; maybe_check_hostname(_, valid, _) -> valid. path_validation_alert({bad_cert, cert_expired}) -> ?ALERT_REC(?FATAL, ?CERTIFICATE_EXPIRED); path_validation_alert({bad_cert, invalid_issuer}) -> ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); path_validation_alert({bad_cert, invalid_signature}) -> ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); path_validation_alert({bad_cert, name_not_permitted}) -> ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); path_validation_alert({bad_cert, unknown_critical_extension}) -> ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE); path_validation_alert({bad_cert, {revoked, _}}) -> ?ALERT_REC(?FATAL, ?CERTIFICATE_REVOKED); path_validation_alert({bad_cert, {revocation_status_undetermined, Details}}) -> ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE, Details); path_validation_alert({bad_cert, selfsigned_peer}) -> ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); path_validation_alert({bad_cert, unknown_ca}) -> ?ALERT_REC(?FATAL, ?UNKNOWN_CA); path_validation_alert(Reason) -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason). digitally_signed(Version, Msg, HashAlgo, PrivateKey, SignAlgo) -> try do_digitally_signed(Version, Msg, HashAlgo, PrivateKey, SignAlgo) catch error:Reason:ST -> ?SSL_LOG(info, sign_error, [{error, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, bad_key(PrivateKey))) end. do_digitally_signed(Version, Msg, HashAlgo, {#'RSAPrivateKey'{} = Key, #'RSASSA-PSS-params'{}}, SignAlgo) when ?TLS_GTE(Version, ?TLS_1_2) -> Options = signature_options(SignAlgo, HashAlgo), public_key:sign(Msg, HashAlgo, Key, Options); do_digitally_signed(Version, {digest, Digest}, _HashAlgo, #'RSAPrivateKey'{} = Key, rsa) when ?TLS_LTE(Version, ?TLS_1_1) -> public_key:encrypt_private(Digest, Key, [{rsa_pad, rsa_pkcs1_padding}]); do_digitally_signed(Version, {digest, Digest}, _, #{algorithm := rsa} = Engine, rsa) when ?TLS_LTE(Version, ?TLS_1_1) -> crypto:private_encrypt(rsa, Digest, maps:remove(algorithm, Engine), rsa_pkcs1_padding); do_digitally_signed(_, Msg, HashAlgo, #{algorithm := Alg} = Engine, SignAlgo) -> Options = signature_options(SignAlgo, HashAlgo), crypto:sign(Alg, HashAlgo, Msg, maps:remove(algorithm, Engine), Options); do_digitally_signed(Version, {digest, _} = Msg , HashAlgo, Key, _) when ?TLS_LTE(Version,?TLS_1_1) -> public_key:sign(Msg, HashAlgo, Key); do_digitally_signed(_, Msg, HashAlgo, Key, SignAlgo) -> Options = signature_options(SignAlgo, HashAlgo), public_key:sign(Msg, HashAlgo, Key, Options). signature_options(SignAlgo, HashAlgo) when SignAlgo =:= rsa_pss_rsae orelse SignAlgo =:= rsa_pss_pss -> pss_options(HashAlgo); signature_options(_, _) -> []. verify_options(SignAlgo, HashAlgo, _KeyParams) when SignAlgo =:= rsa_pss_rsae orelse SignAlgo =:= rsa_pss_pss -> pss_options(HashAlgo); verify_options(_, _, _) -> []. pss_options(HashAlgo) -> %% of the digest algorithm: rsa_pss_saltlen = -1 [{rsa_padding, rsa_pkcs1_pss_padding}, {rsa_pss_saltlen, -1}, {rsa_mgf1_md, HashAlgo}]. bad_key(#'DSAPrivateKey'{}) -> unacceptable_dsa_key; bad_key(#'RSAPrivateKey'{}) -> unacceptable_rsa_key; bad_key(#'ECPrivateKey'{}) -> unacceptable_ecdsa_key; bad_key(#{algorithm := rsa}) -> unacceptable_rsa_key; bad_key(#{algorithm := ecdsa}) -> unacceptable_ecdsa_key. cert_status_check(_, #{ocsp_state := #{ocsp_stapling := true, ocsp_expect := stapled}}, _VerifyResult, _, _) -> %% OCSP staple will now be checked by %% ssl_certificate:verify_cert_extensions/2 in ssl_certificate:validate valid; cert_status_check(OtpCert, #{ocsp_state := #{ocsp_stapling := false}} = SslState, VerifyResult, CertPath, LogLevel) -> maybe_check_crl(OtpCert, SslState, VerifyResult, CertPath, LogLevel); cert_status_check(_OtpCert, #{ocsp_state := #{ocsp_stapling := true, ocsp_expect := undetermined}}, _VerifyResult, _CertPath, _LogLevel) -> {bad_cert, {revocation_status_undetermined, not_stapled}}; cert_status_check(_OtpCert, #{ocsp_state := #{ocsp_stapling := true, ocsp_expect := no_staple}}, _VerifyResult, _CertPath, _LogLevel) -> {bad_cert, {revocation_status_undetermined, not_stapled}}. maybe_check_crl(_, #{crl_check := false}, _, _, _) -> valid; maybe_check_crl(_, #{crl_check := peer}, valid, _, _) -> %% Do not check CAs with this option. valid; maybe_check_crl(OtpCert, #{crl_check := Check, certdb := CertDbHandle, certdb_ref := CertDbRef, crl_db := {Callback, CRLDbHandle}}, _, CertPath, LogLevel) -> Options = [{issuer_fun, {fun(_DP, CRL, Issuer, DBInfo) -> ssl_crl:trusted_cert_and_path(CRL, Issuer, CertPath, DBInfo) end, {CertDbHandle, CertDbRef}}}, {update_crl, fun(DP, CRL) -> case Callback:fresh_crl(DP, CRL) of {logger, LogInfo, Fresh} -> handle_log(LogLevel, LogInfo), Fresh; Fresh -> Fresh end end}, {undetermined_details, true} ], case dps_and_crls(OtpCert, Callback, CRLDbHandle, ext, LogLevel) of no_dps -> crl_check_same_issuer(OtpCert, Check, dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer, LogLevel), Options); DpsAndCRLs -> %% This DP list may be empty if relevant CRLs existed %% but could not be retrieved, will result in {bad_cert, revocation_status_undetermined} case public_key:pkix_crls_validate(OtpCert, DpsAndCRLs, Options) of {bad_cert, {revocation_status_undetermined, _}} -> crl_check_same_issuer(OtpCert, Check, dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer, LogLevel), Options); Other -> Other end end. crl_check_same_issuer(OtpCert, best_effort, Dps, Options) -> case public_key:pkix_crls_validate(OtpCert, Dps, Options) of {bad_cert, {revocation_status_undetermined, _}} -> valid; Other -> Other end; crl_check_same_issuer(OtpCert, _, Dps, Options) -> public_key:pkix_crls_validate(OtpCert, Dps, Options). dps_and_crls(OtpCert, Callback, CRLDbHandle, ext, LogLevel) -> case public_key:pkix_dist_points(OtpCert) of [] -> no_dps; DistPoints -> Issuer = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.issuer, CRLs = distpoints_lookup(DistPoints, Issuer, Callback, CRLDbHandle, LogLevel), [{DP, {CRL, public_key:der_decode('CertificateList', CRL)}} || DP <- DistPoints, CRL <- CRLs] end; dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer, LogLevel) -> DP = #'DistributionPoint'{distributionPoint = {fullName, GenNames}} = public_key:pkix_dist_point(OtpCert), CRLs = lists:flatmap(fun({directoryName, Issuer}) -> case Callback:select(Issuer, CRLDbHandle) of {logger, LogInfo, Return} -> handle_log(LogLevel, LogInfo), Return; Return -> Return end; (_) -> [] end, GenNames), [{DP, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs]. distpoints_lookup([],_, _, _, _) -> []; distpoints_lookup([DistPoint | Rest], Issuer, Callback, CRLDbHandle, LogLevel) -> Result = try Callback:lookup(DistPoint, Issuer, CRLDbHandle) catch error:undef -> %% The callback module still uses the 2-argument %% version of the lookup function. Callback:lookup(DistPoint, CRLDbHandle) end, case Result of not_available -> distpoints_lookup(Rest, Issuer, Callback, CRLDbHandle, LogLevel); {logger, LogInfo, CRLs} -> handle_log(LogLevel, LogInfo), CRLs; CRLs -> CRLs end. encrypted_premaster_secret(Secret, RSAPublicKey) -> try PreMasterSecret = public_key:encrypt_public(Secret, RSAPublicKey, [{rsa_pad, rsa_pkcs1_padding}]), #encrypted_premaster_secret{premaster_secret = PreMasterSecret} catch _:Reason:ST-> ?SSL_LOG(debug, encrypt_error, [{reason, Reason}, {stacktrace, ST}]), throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, premaster_encryption_failed)) end. -spec calc_certificate_verify(ssl_record:ssl_version(), md5sha | ssl:hash(), _, [binary()]) -> binary(). calc_certificate_verify(Version, HashAlgo, _MasterSecret, Handshake) when ?TLS_1_X(Version) -> tls_v1:certificate_verify(HashAlgo, lists:reverse(Handshake)). calc_finished(Version, Role, PrfAlgo, MasterSecret, Handshake) when ?TLS_1_X(Version) -> tls_v1:finished(Role, Version, PrfAlgo, MasterSecret, lists:reverse(Handshake)). master_secret(Version, MasterSecret, #security_parameters{ bulk_cipher_algorithm = BCA, client_random = ClientRandom, server_random = ServerRandom, hash_size = HashSize, prf_algorithm = PrfAlgo, key_material_length = KML, expanded_key_material_length = EKML, iv_size = IVS}, ConnectionStates, Role) -> {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, ServerWriteKey, ClientIV, ServerIV} = setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, KML, EKML, IVS), ConnStates1 = ssl_record:set_master_secret(MasterSecret, ConnectionStates), ConnStates2 = ssl_record:set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, Role, ConnStates1), ClientCipherState = ssl_cipher:cipher_init(BCA, ClientIV, ClientWriteKey), ServerCipherState = ssl_cipher:cipher_init(BCA, ServerIV, ServerWriteKey), {MasterSecret, ssl_record:set_pending_cipher_state(ConnStates2, ClientCipherState, ServerCipherState, Role)}. setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, KML, _EKML, IVS) when ?TLS_1_X(Version)-> tls_v1:setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, KML, IVS). calc_master_secret(Version, PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) when ?TLS_LT(Version, ?TLS_1_3) -> tls_v1:master_secret(PrfAlgo, PremasterSecret, ClientRandom, ServerRandom). %% Update pending connection states with parameters exchanged via %% hello messages %% NOTE : Role is the role of the receiver of the hello message %% currently being processed. hello_pending_connection_states(_RecordCB, Role, Version, CipherSuite, Random, Compression, ConnectionStates) -> ReadState = ssl_record:pending_connection_state(ConnectionStates, read), WriteState = ssl_record:pending_connection_state(ConnectionStates, write), NewReadSecParams = hello_security_parameters(Role, Version, ReadState, CipherSuite, Random, Compression), NewWriteSecParams = hello_security_parameters(Role, Version, WriteState, CipherSuite, Random, Compression), ssl_record:set_security_params(NewReadSecParams, NewWriteSecParams, ConnectionStates). hello_security_parameters(client, Version, #{security_parameters := SecParams}, CipherSuite, Random, Compression) -> NewSecParams = ssl_cipher:security_parameters(Version, CipherSuite, SecParams), NewSecParams#security_parameters{ server_random = Random, compression_algorithm = Compression }; hello_security_parameters(server, Version, #{security_parameters := SecParams}, CipherSuite, Random, Compression) -> NewSecParams = ssl_cipher:security_parameters(Version, CipherSuite, SecParams), NewSecParams#security_parameters{ client_random = Random, compression_algorithm = Compression }. select_compression(_CompressionMetodes) -> ?NULL. do_select_version(_, ClientVersion, []) -> ClientVersion; do_select_version(RecordCB, ClientVersion, [Version | Versions]) -> case RecordCB:is_higher(Version, ClientVersion) of true -> %% Version too high for client - keep looking do_select_version(RecordCB, ClientVersion, Versions); false -> %% Version ok for client - look for a higher do_select_version(RecordCB, ClientVersion, Versions, Version) end. %% do_select_version(_, _, [], GoodVersion) -> GoodVersion; do_select_version( RecordCB, ClientVersion, [Version | Versions], GoodVersion) -> BetterVersion = case RecordCB:is_higher(Version, ClientVersion) of true -> %% Version too high for client GoodVersion; false -> %% Version ok for client case RecordCB:is_higher(Version, GoodVersion) of true -> %% Use higher version Version; false -> GoodVersion end end, do_select_version(RecordCB, ClientVersion, Versions, BetterVersion). %%-------------Encode handshakes -------------------------------- encode_server_key(#server_dh_params{dh_p = P, dh_g = G, dh_y = Y}) -> PLen = byte_size(P), GLen = byte_size(G), YLen = byte_size(Y), <>; encode_server_key(#server_ecdh_params{curve = {namedCurve, ECCurve}, public = ECPubKey}) -> %%TODO: support arbitrary keys KLen = byte_size(ECPubKey), <>; encode_server_key(#server_psk_params{hint = PskIdentityHint}) -> Len = byte_size(PskIdentityHint), <>; encode_server_key(Params = #server_dhe_psk_params{hint = undefined}) -> encode_server_key(Params#server_dhe_psk_params{hint = <<>>}); encode_server_key(#server_dhe_psk_params{ hint = PskIdentityHint, dh_params = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}}) -> Len = byte_size(PskIdentityHint), PLen = byte_size(P), GLen = byte_size(G), YLen = byte_size(Y), <>; encode_server_key(Params = #server_ecdhe_psk_params{hint = undefined}) -> encode_server_key(Params#server_ecdhe_psk_params{hint = <<>>}); encode_server_key(#server_ecdhe_psk_params{ hint = PskIdentityHint, dh_params = #server_ecdh_params{ curve = {namedCurve, ECCurve}, public = ECPubKey}}) -> %%TODO: support arbitrary keys Len = byte_size(PskIdentityHint), KLen = byte_size(ECPubKey), <>; encode_server_key(#server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}) -> NLen = byte_size(N), GLen = byte_size(G), SLen = byte_size(S), BLen = byte_size(B), <>. encode_client_key(#encrypted_premaster_secret{premaster_secret = PKEPMS}) -> PKEPMSLen = byte_size(PKEPMS), <>; encode_client_key(#client_diffie_hellman_public{dh_public = DHPublic}) -> Len = byte_size(DHPublic), <>; encode_client_key(#client_ec_diffie_hellman_public{dh_public = DHPublic}) -> Len = byte_size(DHPublic), <>; encode_client_key(#client_psk_identity{identity = undefined}) -> Id = <<"psk_identity">>, Len = byte_size(Id), <>; encode_client_key(#client_psk_identity{identity = Id}) -> Len = byte_size(Id), <>; encode_client_key(Identity = #client_dhe_psk_identity{identity = undefined}) -> encode_client_key(Identity#client_dhe_psk_identity{identity = <<"psk_identity">>}); encode_client_key(#client_dhe_psk_identity{identity = Id, dh_public = DHPublic}) -> Len = byte_size(Id), DHLen = byte_size(DHPublic), <>; encode_client_key(Identity = #client_ecdhe_psk_identity{identity = undefined}) -> encode_client_key(Identity#client_ecdhe_psk_identity{identity = <<"psk_identity">>}); encode_client_key(#client_ecdhe_psk_identity{identity = Id, dh_public = DHPublic}) -> Len = byte_size(Id), DHLen = byte_size(DHPublic), <>; encode_client_key(Identity = #client_rsa_psk_identity{identity = undefined}) -> encode_client_key(Identity#client_rsa_psk_identity{identity = <<"psk_identity">>}); encode_client_key(#client_rsa_psk_identity{identity = Id, exchange_keys = ExchangeKeys}) -> EncPMS = encode_client_key(ExchangeKeys), Len = byte_size(Id), <>; encode_client_key(#client_srp_public{srp_a = A}) -> Len = byte_size(A), <>. enc_sign({_, anon}, _Sign, _Version) -> <<>>; enc_sign({HashAlg, SignAlg}, Signature, Version) when ?TLS_GTE(Version, ?TLS_1_2)-> SignLen = byte_size(Signature), HashSign = enc_hashsign(HashAlg, SignAlg), <>; enc_sign(_HashSign, Sign, _Version) -> SignLen = byte_size(Sign), <>. enc_hashsign(HashAlgo, SignAlgo) when SignAlgo == rsa_pss_pss; SignAlgo == rsa_pss_rsae -> Sign = ssl_cipher:signature_scheme(list_to_existing_atom(atom_to_list(SignAlgo) ++ "_" ++ atom_to_list(HashAlgo))), <>; enc_hashsign(HashAlgo, SignAlgo) -> Hash = ssl_cipher:hash_algorithm(HashAlgo), Sign = ssl_cipher:sign_algorithm(SignAlgo), <>. encode_protocol(Protocol, Acc) -> Len = byte_size(Protocol), <>. enc_server_key_exchange(Version, Params, {HashAlgo, SignAlgo}, ClientRandom, ServerRandom, PrivateKey) -> EncParams = encode_server_key(Params), case HashAlgo of null -> #server_key_params{params = Params, params_bin = EncParams, hashsign = {null, anon}, signature = <<>>}; _ -> Hash = server_key_exchange_hash(HashAlgo, <>), Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey, SignAlgo), #server_key_params{params = Params, params_bin = EncParams, hashsign = {HashAlgo, SignAlgo}, signature = Signature} end. %% While the RFC opens the door to allow ALPN during renegotiation, in practice %% this does not work and it is recommended to ignore any ALPN extension during %% renegotiation, as done here. encode_alpn(_, true) -> undefined; encode_alpn(undefined, _) -> undefined; encode_alpn(Protocols, _) -> #alpn{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}. encode_versions(Versions) -> << <> || {M,N} <- Versions>>. encode_client_shares(ClientShares) -> << << (encode_key_share_entry(KeyShareEntry0))/binary >> || KeyShareEntry0 <- ClientShares >>. encode_key_share_entry(#key_share_entry{group = Group, key_exchange = KeyExchange}) -> Len = byte_size(KeyExchange), <>. encode_psk_key_exchange_modes(KEModes) -> << <> || PskKey <- KEModes>>. % choose_psk_key(psk_ke) -> ?PSK_KE; choose_psk_key(psk_dhe_ke) -> ?PSK_DHE_KE. encode_psk_identities(Identities) -> Result = << << ?UINT16((byte_size(Identity))), Identity/binary,?UINT32(Age) >> || #psk_identity{ identity = Identity, obfuscated_ticket_age = Age} <- Identities >>, Len = byte_size(Result), <>. encode_psk_binders(Binders) -> Result = << << ?BYTE((byte_size(Binder))),Binder/binary >> || Binder <- Binders >>, Len = byte_size(Result), <>. hello_extensions_list(HelloExtensions) -> [Ext || {_, Ext} <- maps:to_list(HelloExtensions), Ext =/= undefined]. %%-------------Decode handshakes--------------------------------- dec_server_key(<> = KeyStruct, ?KEY_EXCHANGE_DIFFIE_HELLMAN, Version) -> Params = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}, {BinMsg, HashSign, Signature} = dec_server_key_params(PLen + GLen + YLen + 6, KeyStruct, Version), #server_key_params{params = Params, params_bin = BinMsg, hashsign = HashSign, signature = Signature}; %% ECParameters with named_curve %% TODO: explicit curve dec_server_key(<> = KeyStruct, ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, Version) -> Params = #server_ecdh_params{curve = {namedCurve, tls_v1:enum_to_oid(CurveID)}, public = ECPoint}, {BinMsg, HashSign, Signature} = dec_server_key_params(PointLen + 4, KeyStruct, Version), #server_key_params{params = Params, params_bin = BinMsg, hashsign = HashSign, signature = Signature}; dec_server_key(<> = KeyStruct, KeyExchange, Version) when KeyExchange == ?KEY_EXCHANGE_PSK; KeyExchange == ?KEY_EXCHANGE_RSA_PSK -> Params = #server_psk_params{ hint = PskIdentityHint}, {BinMsg, HashSign, Signature} = dec_server_key_params(Len + 2, KeyStruct, Version), #server_key_params{params = Params, params_bin = BinMsg, hashsign = HashSign, signature = Signature}; dec_server_key(<> = KeyStruct, ?KEY_EXCHANGE_DHE_PSK, Version) -> DHParams = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}, Params = #server_dhe_psk_params{ hint = IdentityHint, dh_params = DHParams}, {BinMsg, HashSign, Signature} = dec_server_key_params(Len + PLen + GLen + YLen + 8, KeyStruct, Version), #server_key_params{params = Params, params_bin = BinMsg, hashsign = HashSign, signature = Signature}; dec_server_key(<> = KeyStruct, ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN_PSK, Version) -> DHParams = #server_ecdh_params{ curve = {namedCurve, tls_v1:enum_to_oid(CurveID)}, public = ECPoint}, Params = #server_ecdhe_psk_params{ hint = IdentityHint, dh_params = DHParams}, {BinMsg, HashSign, Signature} = dec_server_key_params(Len + 2 + PointLen + 4, KeyStruct, Version), #server_key_params{params = Params, params_bin = BinMsg, hashsign = HashSign, signature = Signature}; dec_server_key(<> = KeyStruct, ?KEY_EXCHANGE_SRP, Version) -> Params = #server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}, {BinMsg, HashSign, Signature} = dec_server_key_params(NLen + GLen + SLen + BLen + 7, KeyStruct, Version), #server_key_params{params = Params, params_bin = BinMsg, hashsign = HashSign, signature = Signature}; dec_server_key(_, KeyExchange, _) -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {unknown_or_malformed_key_exchange, KeyExchange})). dec_client_key(<>, ?KEY_EXCHANGE_RSA, _) -> #encrypted_premaster_secret{premaster_secret = PKEPMS}; dec_client_key(<<>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) -> throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE, empty_dh_public)); dec_client_key(<>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) -> #client_diffie_hellman_public{dh_public = DH_Y}; dec_client_key(<<>>, ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, _) -> throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE, empty_dh_public)); dec_client_key(<>, ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, _) -> #client_ec_diffie_hellman_public{dh_public = DH_Y}; dec_client_key(<>, ?KEY_EXCHANGE_PSK, _) -> #client_psk_identity{identity = Id}; dec_client_key(<>, ?KEY_EXCHANGE_DHE_PSK, _) -> #client_dhe_psk_identity{identity = Id, dh_public = DH_Y}; dec_client_key(<>, ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN_PSK, _) -> #client_ecdhe_psk_identity{identity = Id, dh_public = DH_Y}; dec_client_key(<>, ?KEY_EXCHANGE_RSA_PSK, _) -> #client_rsa_psk_identity{identity = Id, exchange_keys = #encrypted_premaster_secret{premaster_secret = PKEPMS}}; dec_client_key(<>, ?KEY_EXCHANGE_SRP, _) -> #client_srp_public{srp_a = A}. dec_server_key_params(Len, Keys, Version) -> <> = Keys, dec_server_key_signature(Params, Signature, Version). dec_server_key_signature(Params, <>, Version) when ?TLS_GTE(Version, ?TLS_1_2) -> <> = <>, Scheme = ssl_cipher:signature_scheme(Scheme0), {Hash, Sign, _} = ssl_cipher:scheme_to_components(Scheme), {Params, {Hash, Sign}, <<>>}; dec_server_key_signature(Params, <>, Version) when ?TLS_GTE(Version, ?TLS_1_2) -> <> = <>, Scheme = ssl_cipher:signature_scheme(Scheme0), {Hash, Sign, _} = ssl_cipher:scheme_to_components(Scheme), {Params, {Hash, Sign}, Signature}; dec_server_key_signature(Params, <>, Version) when ?TLS_GTE(Version, ?TLS_1_2) -> HashSign = {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)}, {Params, HashSign, <<>>}; dec_server_key_signature(Params, <>, Version) when ?TLS_GTE(Version, ?TLS_1_2) -> HashSign = {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)}, {Params, HashSign, Signature}; dec_server_key_signature(Params, <<>>, _) -> {Params, {null, anon}, <<>>}; dec_server_key_signature(Params, <>, _) -> {Params, {null, anon}, <<>>}; dec_server_key_signature(Params, <>, _) -> {Params, undefined, Signature}; dec_server_key_signature(_, _, _) -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, failed_to_decrypt_server_key_sign)). %% Processes a ClientHello/ServerHello message and returns the version to be used %% in the decoding functions. The following rules apply: %% - IF supported_versions extension is absent: %% RETURN the lowest of (LocalVersion and LegacyVersion) %% - IF supported_versions estension is present: %% RETURN the lowest of (LocalVersion and first element of supported versions) process_supported_versions_extension(<<>>, LocalVersion, LegacyVersion) when LegacyVersion =< LocalVersion -> LegacyVersion; process_supported_versions_extension(<<>>, LocalVersion, _LegacyVersion) -> LocalVersion; process_supported_versions_extension(<>, LocalVersion, _LegacyVersion) when Len > 2 -> <> = ExtData, [Highest|_] = decode_versions(Versions0), if Highest =< LocalVersion -> Highest; true -> LocalVersion end; process_supported_versions_extension(<>, LocalVersion, _LegacyVersion) when Len =:= 2 -> SelectedVersion = {Major, Minor}, if SelectedVersion =< LocalVersion -> SelectedVersion; true -> LocalVersion end; process_supported_versions_extension(<>, LocalVersion, LegacyVersion) -> process_supported_versions_extension(Rest, LocalVersion, LegacyVersion); %% Tolerate protocol encoding errors and skip parsing the rest of the extension. process_supported_versions_extension(_, LocalVersion, LegacyVersion) when LegacyVersion =< LocalVersion -> LegacyVersion; process_supported_versions_extension(_, LocalVersion, _) -> LocalVersion. decode_extensions(<<>>, _Version, _MessageType, Acc) -> Acc; decode_extensions(<>, Version, MessageType, Acc) when Len + 2 =:= ExtLen -> ALPN = #alpn{extension_data = ExtensionData}, decode_extensions(Rest, Version, MessageType, Acc#{alpn => ALPN}); decode_extensions(<>, Version, MessageType, Acc) -> NextP = #next_protocol_negotiation{extension_data = ExtensionData}, decode_extensions(Rest, Version, MessageType, Acc#{next_protocol_negotiation => NextP}); decode_extensions(<>, Version, MessageType, Acc) -> RenegotiateInfo = case Len of 1 -> % Initial handshake Info; % should be <<0>> will be matched in handle_renegotiation_info _ -> VerifyLen = Len - 1, <> = Info, VerifyInfo end, decode_extensions(Rest, Version, MessageType, Acc#{renegotiation_info => #renegotiation_info{renegotiated_connection = RenegotiateInfo}}); decode_extensions(<>, Version, MessageType, Acc) when Len == SRPLen + 1 -> decode_extensions(Rest, Version, MessageType, Acc#{srp => #srp{username = SRP}}); decode_extensions(<>, Version, MessageType, Acc) when ?TLS_LT(Version, ?TLS_1_2) -> SignAlgoListLen = Len - 2, <> = ExtData, HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} || <> <= SignAlgoList], decode_extensions(Rest, Version, MessageType, Acc#{signature_algs => #hash_sign_algos{hash_sign_algos = HashSignAlgos}}); decode_extensions(<>, ?TLS_1_2=Version, MessageType, Acc) -> SignSchemeListLen = Len - 2, <> = ExtData, HashSigns = decode_sign_alg(Version, SignSchemeList), decode_extensions(Rest, Version, MessageType, Acc#{signature_algs => #hash_sign_algos{ hash_sign_algos = HashSigns}}); decode_extensions(<>, ?TLS_1_3=Version, MessageType, Acc) -> SignSchemeListLen = Len - 2, <> = ExtData, SignSchemes = decode_sign_alg(Version, SignSchemeList), decode_extensions(Rest, Version, MessageType, Acc#{signature_algs => #signature_algorithms{ signature_scheme_list = SignSchemes}}); decode_extensions(<>, ?TLS_1_3=Version, MessageType, Acc) -> SignSchemeListLen = Len - 2, <> = ExtData, %% Ignore unknown signature algorithms Fun = fun(Elem) -> case ssl_cipher:signature_scheme(Elem) of unassigned -> false; Value -> {true, Value} end end, SignSchemes= lists:filtermap(Fun, [SignScheme || <> <= SignSchemeList]), decode_extensions(Rest, Version, MessageType, Acc#{signature_algs => #signature_algorithms{ signature_scheme_list = SignSchemes}}); decode_extensions(<>, Version, MessageType, Acc) -> SignSchemeListLen = Len - 2, <> = ExtData, %% Ignore unknown signature algorithms Fun = fun(Elem) -> case ssl_cipher:signature_scheme(Elem) of unassigned -> false; Value -> {true, Value} end end, SignSchemes= lists:filtermap(Fun, [SignScheme || <> <= SignSchemeList]), decode_extensions(Rest, Version, MessageType, Acc#{signature_algs_cert => #signature_algorithms_cert{ signature_scheme_list = SignSchemes}}); decode_extensions(<>, Version, MessageType, Acc) -> <> = ExtData, Profiles = [P || <> <= ProfilesBin], decode_extensions(Rest, Version, MessageType, Acc#{use_srtp => #use_srtp{ protection_profiles = Profiles, mki = MKI}}); decode_extensions(<>, Version, MessageType, Acc) when ?TLS_LT(Version, ?TLS_1_3) -> <> = ExtData, %% Ignore unknown curves Pick = fun(Enum) -> case tls_v1:enum_to_oid(Enum) of undefined -> false; Oid -> {true, Oid} end end, EllipticCurves = lists:filtermap(Pick, [ECC || <> <= EllipticCurveList]), decode_extensions(Rest, Version, MessageType, Acc#{elliptic_curves => #elliptic_curves{elliptic_curve_list = EllipticCurves}}); decode_extensions(<>, ?TLS_1_3=Version, MessageType, Acc) -> <> = ExtData, %% Ignore unknown curves Pick = fun(Enum) -> case tls_v1:enum_to_group(Enum) of undefined -> false; Group -> {true, Group} end end, SupportedGroups = lists:filtermap(Pick, [Group || <> <= GroupList]), decode_extensions(Rest, Version, MessageType, Acc#{elliptic_curves => #supported_groups{supported_groups = SupportedGroups}}); decode_extensions(<>, Version, MessageType, Acc) -> <> = ExtData, ECPointFormats = binary_to_list(ECPointFormatList), decode_extensions(Rest, Version, MessageType, Acc#{ec_point_formats => #ec_point_formats{ec_point_format_list = ECPointFormats}}); decode_extensions(<>, Version, MessageType, Acc) when Len == 0 -> decode_extensions(Rest, Version, MessageType, Acc#{sni => #sni{hostname = ""}}); %% Server may send an empty SNI decode_extensions(<>, Version, MessageType, Acc) -> <> = ExtData, decode_extensions(Rest, Version, MessageType, Acc#{sni => dec_sni(NameList)}); decode_extensions(<>, Version, MessageType, Acc) -> %% RFC 6066 Section 4 decode_extensions(Rest, Version, MessageType, Acc#{max_frag_enum => #max_frag_enum{enum = MaxFragEnum}}); decode_extensions(<>, Version, MessageType, Acc) when Len > 2 -> <> = ExtData, decode_extensions(Rest, Version, MessageType, Acc#{client_hello_versions => #client_hello_versions{ versions = decode_versions(Versions)}}); decode_extensions(<>, Version, MessageType, Acc) when Len =:= 2, SelectedVersion =:= 16#0304 -> decode_extensions(Rest, Version, MessageType, Acc#{server_hello_selected_version => #server_hello_selected_version{selected_version = ?TLS_1_3}}); decode_extensions(<>, Version, MessageType = client_hello, Acc) -> <> = ExtData, decode_extensions(Rest, Version, MessageType, Acc#{key_share => #key_share_client_hello{ client_shares = decode_client_shares(ClientShares)}}); decode_extensions(<>, Version, MessageType = server_hello, Acc) -> <> = ExtData, decode_extensions(Rest, Version, MessageType, Acc#{key_share => #key_share_server_hello{ server_share = #key_share_entry{ group = tls_v1:enum_to_group(Group), key_exchange = KeyExchange}}}); decode_extensions(<>, Version, MessageType = hello_retry_request, Acc) -> <> = ExtData, decode_extensions(Rest, Version, MessageType, Acc#{key_share => #key_share_hello_retry_request{ selected_group = tls_v1:enum_to_group(Group)}}); decode_extensions(<>, Version, MessageType, Acc) -> <> = ExtData, decode_extensions(Rest, Version, MessageType, Acc#{psk_key_exchange_modes => #psk_key_exchange_modes{ ke_modes = decode_psk_key_exchange_modes(KEModes)}}); decode_extensions(<>, Version, MessageType = client_hello, Acc) -> <> = ExtData, decode_extensions(Rest, Version, MessageType, Acc#{pre_shared_key => #pre_shared_key_client_hello{ offered_psks = #offered_psks{ identities = decode_psk_identities(Identities), binders = decode_psk_binders(Binders)}}}); decode_extensions(<>, Version, MessageType = server_hello, Acc) -> <> = ExtData, decode_extensions(Rest, Version, MessageType, Acc#{pre_shared_key => #pre_shared_key_server_hello{ selected_identity = Identity}}); decode_extensions(<>, Version, MessageType, Acc) when Len == CookieLen + 2 -> decode_extensions(Rest, Version, MessageType, Acc#{cookie => #cookie{cookie = Cookie}}); %% RFC6066, if a server returns a "CertificateStatus" message, then %% the server MUST have included an extension of type "status_request" %% with empty "extension_data" in the extended server hello. decode_extensions(<>, Version, MessageType = server_hello, Acc) when Len =:= 0 -> decode_extensions(Rest, Version, MessageType, Acc#{status_request => undefined}); %% RFC8446 4.4.2.1, In TLS1.3, the body of the "status_request" extension %% from the server MUST be a CertificateStatus structure as defined in %% RFC6066. decode_extensions(<>, Version, MessageType, Acc) -> case CertStatus of <> -> decode_extensions(Rest, Version, MessageType, Acc#{status_request => #certificate_status{response = ASN1OCSPResponse}}); _Other -> decode_extensions(Rest, Version, MessageType, Acc) end; decode_extensions(<>, Version, MessageType, Acc) -> decode_extensions(Rest, Version, MessageType, Acc#{early_data => #early_data_indication{}}); decode_extensions(<>, Version, MessageType, Acc) -> decode_extensions(Rest, Version, MessageType, Acc#{early_data => #early_data_indication_nst{indication = MaxSize}}); decode_extensions(<>, Version, MessageType, Acc) -> CertAutsLen = Len - 2, <> = CertAutsExt, decode_extensions(Rest, Version, MessageType, Acc#{certificate_authorities => #certificate_authorities{authorities = decode_cert_auths(EncCertAuts, [])}}); %% Ignore data following the ClientHello (i.e., %% extensions) if not understood. decode_extensions(<>, Version, MessageType, Acc) -> decode_extensions(Rest, Version, MessageType, Acc); %% This theoretically should not happen if the protocol is followed, but if it does it is ignored. decode_extensions(_, _, _, Acc) -> Acc. decode_sign_alg(?TLS_1_2, SignSchemeList) -> %% Ignore unknown signature algorithms Fun = fun(Elem) -> case ssl_cipher:signature_scheme(Elem) of unassigned -> false; Value when is_atom(Value)-> case ssl_cipher:scheme_to_components(Value) of {Hash, rsa_pss_rsae = Sign, _} -> {true, {Hash, Sign}}; {Hash, rsa_pss_pss = Sign, _} -> {true,{Hash, Sign}}; {Hash, rsa_pkcs1, _} -> {true,{Hash, rsa}}; {Hash, ecdsa, _} -> {true,{Hash, ecdsa}}; _ -> false end; Value -> {true, Value} end end, lists:filtermap(Fun, [SignScheme || <> <= SignSchemeList]); decode_sign_alg(?TLS_1_3, SignSchemeList) -> %% Ignore unknown signature algorithms Fun = fun(Elem) -> case ssl_cipher:signature_scheme(Elem) of unassigned -> false; Value -> {true, Value} end end, lists:filtermap(Fun, [SignScheme || <> <= SignSchemeList]). dec_hashsign(Value) -> [HashSign] = decode_sign_alg(?TLS_1_2, Value), HashSign. %% Ignore unknown names (only host_name is supported) dec_sni(<>) -> #sni{hostname = binary_to_list(HostName)}; dec_sni(<>) -> dec_sni(Rest); dec_sni(_) -> undefined. decode_alpn(undefined) -> undefined; decode_alpn(#alpn{extension_data=Data}) -> decode_protocols(Data, []). decode_versions(Versions) -> decode_versions(Versions, []). %% decode_versions(<<>>, Acc) -> lists:reverse(Acc); decode_versions(<>, Acc) -> decode_versions(Rest, [{M,N}|Acc]). decode_client_shares(ClientShares) -> decode_client_shares(ClientShares, []). %% decode_client_shares(<<>>, Acc) -> lists:reverse(Acc); decode_client_shares(<>, Acc) -> case tls_v1:enum_to_group(Group0) of undefined -> %% Ignore key_share with unknown group decode_client_shares(Rest, Acc); Group -> decode_client_shares(Rest, [#key_share_entry{ group = Group, key_exchange= KeyExchange }|Acc]) end. decode_next_protocols({next_protocol_negotiation, Protocols}) -> decode_protocols(Protocols, []). decode_protocols(<<>>, Acc) -> lists:reverse(Acc); decode_protocols(<>, Acc) -> case Len of 0 -> {error, invalid_protocols}; _ -> decode_protocols(Rest, [Protocol|Acc]) end; decode_protocols(_Bytes, _Acc) -> {error, invalid_protocols}. decode_psk_key_exchange_modes(KEModes) -> decode_psk_key_exchange_modes(KEModes, []). %% decode_psk_key_exchange_modes(<<>>, Acc) -> lists:reverse(Acc); decode_psk_key_exchange_modes(<>, Acc) -> decode_psk_key_exchange_modes(Rest, [psk_ke|Acc]); decode_psk_key_exchange_modes(<>, Acc) -> decode_psk_key_exchange_modes(Rest, [psk_dhe_ke|Acc]); %% Ignore unknown PskKeyExchangeModes decode_psk_key_exchange_modes(<>, Acc) -> decode_psk_key_exchange_modes(Rest, Acc). decode_psk_identities(Identities) -> decode_psk_identities(Identities, []). %% decode_psk_identities(<<>>, Acc) -> lists:reverse(Acc); decode_psk_identities(<>, Acc) -> decode_psk_identities(Rest, [#psk_identity{ identity = Identity, obfuscated_ticket_age = Age}|Acc]). decode_psk_binders(Binders) -> decode_psk_binders(Binders, []). %% decode_psk_binders(<<>>, Acc) -> lists:reverse(Acc); decode_psk_binders(<>, Acc) -> decode_psk_binders(Rest, [Binder|Acc]). decode_cert_auths(<<>>, Acc) -> lists:reverse(Acc); decode_cert_auths(<>, Acc) -> decode_cert_auths(Rest, [public_key:pkix_normalize_name(Auth) | Acc]). %% encode/decode stream of certificate data to/from list of certificate data certs_to_list(ASN1Certs) -> certs_to_list(ASN1Certs, []). certs_to_list(<>, Acc) -> certs_to_list(Rest, [Cert | Acc]); certs_to_list(<<>>, Acc) -> lists:reverse(Acc, []). certs_from_list(ACList) -> list_to_binary([begin CertLen = byte_size(Cert), <> end || Cert <- ACList]). from_3bytes(Bin3) -> from_3bytes(Bin3, []). from_3bytes(<<>>, Acc) -> lists:reverse(Acc); from_3bytes(<>, Acc) -> from_3bytes(Rest, [?uint16(N) | Acc]). from_2bytes(Bin2) -> from_2bytes(Bin2, []). from_2bytes(<<>>, Acc) -> lists:reverse(Acc); from_2bytes(<>, Acc) -> from_2bytes(Rest, [?uint16(N) | Acc]). key_exchange_alg(rsa) -> ?KEY_EXCHANGE_RSA; key_exchange_alg(Alg) when Alg == dhe_rsa; Alg == dhe_dss; Alg == dh_dss; Alg == dh_rsa; Alg == dh_anon -> ?KEY_EXCHANGE_DIFFIE_HELLMAN; key_exchange_alg(Alg) when Alg == ecdhe_rsa; Alg == ecdh_rsa; Alg == ecdhe_ecdsa; Alg == ecdh_ecdsa; Alg == ecdh_anon -> ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN; key_exchange_alg(psk) -> ?KEY_EXCHANGE_PSK; key_exchange_alg(dhe_psk) -> ?KEY_EXCHANGE_DHE_PSK; key_exchange_alg(ecdhe_psk) -> ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN_PSK; key_exchange_alg(rsa_psk) -> ?KEY_EXCHANGE_RSA_PSK; key_exchange_alg(Alg) when Alg == srp_rsa; Alg == srp_dss; Alg == srp_anon -> ?KEY_EXCHANGE_SRP; key_exchange_alg(_) -> ?NULL. %%-------------Cipher suite handling ----------------------------- select_cipher_suite(CipherSuites, Suites, false) -> select_cipher_suite(CipherSuites, Suites); select_cipher_suite(CipherSuites, Suites, true) -> select_cipher_suite(Suites, CipherSuites). select_cipher_suite(ClientSuites, SupportedSuites) -> F = fun(Suite) -> is_member(Suite, SupportedSuites) end, case lists:search(F, ClientSuites) of {value, Suite} -> Suite; false -> no_suite end. is_member(Suite, SupportedSuites) -> lists:member(Suite, SupportedSuites). psk_secret(PSKIdentity, PSKLookup) -> case handle_psk_identity(PSKIdentity, PSKLookup) of {ok, PSK} when is_binary(PSK) -> Len = erlang:byte_size(PSK), <>; #alert{} = Alert -> Alert; _ -> throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) end. psk_secret(PSKIdentity, PSKLookup, PremasterSecret) -> case handle_psk_identity(PSKIdentity, PSKLookup) of {ok, PSK} when is_binary(PSK) -> Len = erlang:byte_size(PremasterSecret), PSKLen = erlang:byte_size(PSK), <>; #alert{} = Alert -> Alert; _ -> throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) end. handle_psk_identity(_PSKIdentity, LookupFun) when LookupFun == undefined -> error; handle_psk_identity(PSKIdentity, {Fun, UserState}) -> Fun(psk, PSKIdentity, UserState). filter_hashsigns(Suites, Algos, HashSigns, Version) -> %% HashSigns, and Version never change ZipperF = fun (Suite, #{key_exchange := KeyExchange}) -> {Suite, KeyExchange} end, SuiteAlgoPairs = lists:zipwith(ZipperF, Suites, Algos), FilterHashSign = fun ({Suite, Kex}) -> maybe true ?= filter_hashsigns_helper(Kex, HashSigns, Version), {true, Suite} end end, lists:filtermap(FilterHashSign, SuiteAlgoPairs). filter_hashsigns_helper(KeyExchange, HashSigns, _Version) when KeyExchange == dhe_ecdsa; KeyExchange == ecdhe_ecdsa -> lists:keymember(ecdsa, 2, HashSigns); filter_hashsigns_helper(KeyExchange, HashSigns, ?TLS_1_2) when KeyExchange == rsa; KeyExchange == dhe_rsa; KeyExchange == ecdhe_rsa; KeyExchange == srp_rsa; KeyExchange == rsa_psk -> lists:any(fun (H) -> lists:keymember(H, 2, HashSigns) end, [rsa, rsa_pss_rsae, rsa_pss_pss]); filter_hashsigns_helper(KeyExchange, HashSigns, _Version) when KeyExchange == rsa; KeyExchange == dhe_rsa; KeyExchange == ecdhe_rsa; KeyExchange == srp_rsa; KeyExchange == rsa_psk -> lists:keymember(rsa, 2, HashSigns); filter_hashsigns_helper(KeyExchange, HashSigns, _Version) when KeyExchange == dhe_dss; KeyExchange == srp_dss -> lists:keymember(dsa, 2, HashSigns); filter_hashsigns_helper(KeyExchange, _HashSigns, _Version) when KeyExchange == dh_dss; KeyExchange == dh_rsa; KeyExchange == dh_ecdsa; KeyExchange == ecdh_rsa; KeyExchange == ecdh_ecdsa -> %% Fixed DH certificates MAY be signed with any hash/signature %% algorithm pair appearing in the hash_sign extension. The names %% DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are historical. true; filter_hashsigns_helper(KeyExchange, _HashSigns, _Version) when KeyExchange == dh_anon; KeyExchange == ecdh_anon; KeyExchange == srp_anon; KeyExchange == psk; KeyExchange == dhe_psk; KeyExchange == ecdhe_psk -> %% In this case hashsigns is not used as the kexchange is anonaymous true. filter_unavailable_ecc_suites(no_curve, Suites) -> ECCSuites = ssl_cipher:filter_suites(Suites, #{key_exchange_filters => [fun(ecdh_ecdsa) -> true; (ecdhe_ecdsa) -> true; (ecdh_rsa) -> true; (_) -> false end], cipher_filters => [], mac_filters => [], prf_filters => []}), Suites -- ECCSuites; filter_unavailable_ecc_suites(_, Suites) -> Suites. %%-------------Extension handling -------------------------------- handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, NegotiatedCipherSuite, ClientCipherSuites, Compression, ConnectionStates0, Renegotiation, SecureRenegotation) -> {ok, ConnectionStates} = handle_renegotiation_info(Version, RecordCB, Role, Info, ConnectionStates0, Renegotiation, SecureRenegotation, ClientCipherSuites), hello_pending_connection_states(RecordCB, Role, Version, NegotiatedCipherSuite, Random, Compression, ConnectionStates). %% Receive protocols, choose one from the list, return it. handle_alpn_extension(_, {error, Reason}) -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason)); handle_alpn_extension([], _) -> throw(?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL)); handle_alpn_extension([ServerProtocol|Tail], ClientProtocols) -> case lists:member(ServerProtocol, ClientProtocols) of true -> ServerProtocol; false -> handle_alpn_extension(Tail, ClientProtocols) end. handle_mfl_extension(#max_frag_enum{enum = Enum}=MaxFragEnum) when Enum >= 1, Enum =< 4 -> MaxFragEnum; handle_mfl_extension(#max_frag_enum{}) -> throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)); handle_mfl_extension(_) -> undefined. handle_next_protocol(undefined, _NextProtocolSelector, _Renegotiating) -> undefined; handle_next_protocol(#next_protocol_negotiation{} = NextProtocols, NextProtocolSelector, Renegotiating) -> case next_protocol_extension_allowed(NextProtocolSelector, Renegotiating) of true -> select_next_protocol(decode_next_protocols(NextProtocols), NextProtocolSelector); false -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension)) end. handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, SslOpts)-> case handle_next_protocol_on_server(NextProtocolNegotiation, Renegotiation, SslOpts) of #alert{} = Alert -> throw(Alert); ProtocolsToAdvertise -> ProtocolsToAdvertise end. handle_next_protocol_on_server(undefined, _Renegotiation, _SslOpts) -> undefined; handle_next_protocol_on_server(#next_protocol_negotiation{extension_data = <<>>}, false, SslOpts) -> maps:get(next_protocols_advertised, SslOpts, undefined); handle_next_protocol_on_server(_Hello, _Renegotiation, _SSLOpts) -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension). next_protocol_extension_allowed(NextProtocolSelector, Renegotiating) -> NextProtocolSelector =/= undefined andalso not Renegotiating. select_next_protocol({error, Reason}, _NextProtocolSelector) -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); select_next_protocol(Protocols, NextProtocolSelector) -> case NextProtocolSelector(Protocols) of ?NO_PROTOCOL -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, no_next_protocol); Protocol when is_binary(Protocol) -> Protocol end. handle_srp_extension(undefined, Session) -> Session; handle_srp_extension(#srp{username = Username}, Session) -> Session#session{srp_username = Username}. is_acceptable_hash_sign(Algos, SupportedHashSigns) -> lists:member(Algos, SupportedHashSigns). is_acceptable_cert_type(Sign, Types) -> lists:member(sign_type(Sign), binary_to_list(Types)). %% signature_algorithms_cert = undefined is_supported_sign(SignAlgo, _, HashSigns, []) -> ssl_cipher:is_supported_sign(SignAlgo, HashSigns); %% {'SignatureAlgorithm',{1,2,840,113549,1,1,11},'NULL'} %% TODO: Implement validation for the curve used in the signature %% RFC 3279 - 2.2.3 ECDSA Signature Algorithm %% When the ecdsa-with-SHA1 algorithm identifier appears as the %% algorithm field in an AlgorithmIdentifier, the encoding MUST omit the %% parameters field. That is, the AlgorithmIdentifier SHALL be a %% SEQUENCE of one component: the OBJECT IDENTIFIER ecdsa-with-SHA1. %% %% The elliptic curve parameters in the subjectPublicKeyInfo field of %% the certificate of the issuer SHALL apply to the verification of the %% signature. is_supported_sign({Hash, Sign}, _Param, _, SignatureSchemes) -> Fun = fun (Scheme) -> {H, S0, _} = ssl_cipher:scheme_to_components(Scheme), S1 = case S0 of rsa_pkcs1 -> rsa; S -> S end, (Sign =:= S1) andalso (Hash =:= H) end, lists:any(Fun, SignatureSchemes). %% SupportedSignatureAlgorithms SIGNATURE-ALGORITHM-CLASS ::= { %% dsa-with-sha1 | dsaWithSHA1 | md2-with-rsa-encryption | %% md5-with-rsa-encryption | sha1-with-rsa-encryption | sha-1with-rsa-encryption | %% sha224-with-rsa-encryption | %% sha256-with-rsa-encryption | %% sha384-with-rsa-encryption | %% sha512-with-rsa-encryption | %% ecdsa-with-sha1 | %% ecdsa-with-sha224 | %% ecdsa-with-sha256 | %% ecdsa-with-sha384 | %% ecdsa-with-sha512 } sign_algo(?'id-RSASSA-PSS', #'RSASSA-PSS-params'{maskGenAlgorithm = #'MaskGenAlgorithm'{algorithm = ?'id-mgf1', parameters = #'HashAlgorithm'{algorithm = HashOid}}}) -> {public_key:pkix_hash_type(HashOid), rsa_pss_pss}; sign_algo(Alg, _) -> public_key:pkix_sign_types(Alg). sign_type(rsa_pss_pss) -> ?RSA_SIGN; sign_type(rsa) -> ?RSA_SIGN; sign_type(dsa) -> ?DSS_SIGN; sign_type(ecdsa) -> ?ECDSA_SIGN. server_name(_, _, server) -> undefined; %% Not interesting to check your own name. server_name(SSLOpts, Host, client) -> case maps:get(server_name_indication, SSLOpts, undefined) of undefined -> {fallback, Host}; %% Fallback to Host argument to connect SNI -> SNI %% If Server Name Indication is available end. client_ecc_extensions(SupportedECCs) -> CryptoSupport = proplists:get_value(public_keys, crypto:supports()), case proplists:get_bool(ecdh, CryptoSupport) of true -> %% RFC 8422 - 5.1. Client Hello Extensions %% Clients SHOULD send both the Supported Elliptic Curves Extension and the %% Supported Point Formats Extension. If the Supported Point Formats %% Extension is indeed sent, it MUST contain the value 0 (uncompressed) %% as one of the items in the list of point formats. EcPointFormats = #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}, EllipticCurves = SupportedECCs, {EcPointFormats, EllipticCurves}; _ -> {undefined, undefined} end. server_ecc_extension(_Version, EcPointFormats) -> CryptoSupport = proplists:get_value(public_keys, crypto:supports()), case proplists:get_bool(ecdh, CryptoSupport) of true -> handle_ecc_point_fmt_extension(EcPointFormats); false -> undefined end. handle_ecc_point_fmt_extension(undefined) -> undefined; handle_ecc_point_fmt_extension(_) -> #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}. advertises_ec_ciphers(ListKex) -> KeyExchanges = [ecdh_ecdsa, ecdhe_ecdsa, ecdh_rsa, ecdhe_rsa, ecdh_anon], F = fun (#{key_exchange := Kex}) -> lists:member(Kex, KeyExchanges); ({ecdhe_psk, _,_,_}) -> true end, lists:any(F, ListKex). select_shared_curve(SharedCurves, Curves) -> case lists:search(fun (Curve) -> lists:member(Curve, Curves) end, SharedCurves) of {value, SharedCurve} -> {namedCurve, SharedCurve}; false -> no_curve end. sni(SslOpts) -> case maps:get(server_name_indication, SslOpts, undefined) of undefined -> undefined; disable -> undefined; Hostname -> #sni{hostname = Hostname} end. %% convert max_fragment_length (in bytes) to the RFC 6066 ENUM max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_1) -> #max_frag_enum{enum = 1}; max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_2) -> #max_frag_enum{enum = 2}; max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_3) -> #max_frag_enum{enum = 3}; max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_4) -> #max_frag_enum{enum = 4}; max_frag_enum(undefined) -> undefined. renegotiation_info(_, client, _, false) -> #renegotiation_info{renegotiated_connection = undefined}; renegotiation_info(_RecordCB, server, ConnectionStates, false) -> ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), case maps:get(secure_renegotiation, ConnectionState) of true -> #renegotiation_info{renegotiated_connection = ?byte(0)}; false -> #renegotiation_info{renegotiated_connection = undefined} end; renegotiation_info(_RecordCB, client, ConnectionStates, true) -> ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), case maps:get(secure_renegotiation, ConnectionState) of true -> Data = maps:get(client_verify_data, ConnectionState), #renegotiation_info{renegotiated_connection = Data}; false -> #renegotiation_info{renegotiated_connection = undefined} end; renegotiation_info(_RecordCB, server, ConnectionStates, true) -> ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), case maps:get(secure_renegotiation, ConnectionState) of true -> CData = maps:get(client_verify_data, ConnectionState), SData = maps:get(server_verify_data, ConnectionState), #renegotiation_info{renegotiated_connection = <>}; false -> #renegotiation_info{renegotiated_connection = undefined} end. handle_renegotiation_info(_, _RecordCB, _, #renegotiation_info{renegotiated_connection = ?byte(0)}, ConnectionStates, false, _, _) -> {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; handle_renegotiation_info(_, _RecordCB, server, undefined, ConnectionStates, _, _, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of true -> {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; false -> {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)} end; handle_renegotiation_info(_, _RecordCB, _, undefined, ConnectionStates, false, _, _) -> {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)}; handle_renegotiation_info(_, _RecordCB, client, #renegotiation_info{renegotiated_connection = ClientServerVerify}, ConnectionStates, true, _, _) -> ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), CData = maps:get(client_verify_data, ConnectionState), SData = maps:get(server_verify_data, ConnectionState), case <> == ClientServerVerify of true -> {ok, ConnectionStates}; false -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation)) end; handle_renegotiation_info(_, _RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify}, ConnectionStates, true, _, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of true -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv})); false -> ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), Data = maps:get(client_verify_data, ConnectionState), case Data == ClientVerify of true -> {ok, ConnectionStates}; false -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation)) end end; handle_renegotiation_info(_, RecordCB, client, undefined, ConnectionStates, true, SecureRenegotation, _) -> handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation); handle_renegotiation_info(_, RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of true -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv})); false -> handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation) end. handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) -> ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), case {SecureRenegotation, maps:get(secure_renegotiation, ConnectionState)} of {_, true} -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure)); {true, false} -> throw(?ALERT_REC(?FATAL, ?NO_RENEGOTIATION)); {false, false} -> {ok, ConnectionStates} end. cert_curve(_, _, no_suite) -> {no_curve, no_suite}; cert_curve(Cert, ECCCurve0, CipherSuite) -> case ssl_cipher_format:suite_bin_to_map(CipherSuite) of #{key_exchange := Kex} when Kex == ecdh_ecdsa; Kex == ecdh_rsa -> OtpCert = public_key:pkix_decode_cert(Cert, otp), TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, #'OTPSubjectPublicKeyInfo'{algorithm = AlgInfo} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, {namedCurve, Oid} = AlgInfo#'PublicKeyAlgorithm'.parameters, {{namedCurve, Oid}, CipherSuite}; _ -> {ECCCurve0, CipherSuite} end. empty_extensions() -> #{}. empty_extensions(?TLS_1_3, client_hello) -> #{ sni => undefined, %% max_frag_enum => undefined, %% status_request => undefined, elliptic_curves => undefined, signature_algs => undefined, use_srtp => undefined, %% heartbeat => undefined, alpn => undefined, %% signed_cert_timestamp => undefined, %% client_cert_type => undefined, %% server_cert_type => undefined, %% padding => undefined, key_share => undefined, pre_shared_key => undefined, psk_key_exchange_modes => undefined, %% early_data => undefined, cookie => undefined, client_hello_versions => undefined, certificate_authorities => undefined, %% post_handshake_auth => undefined, signature_algs_cert => undefined }; empty_extensions(?TLS_1_2, client_hello) -> Ext = empty_extensions(?TLS_1_1, client_hello), Ext#{signature_algs => undefined}; empty_extensions(_, client_hello) -> #{renegotiation_info => undefined, alpn => undefined, next_protocol_negotiation => undefined, srp => undefined, ec_point_formats => undefined, elliptic_curves => undefined, sni => undefined}; empty_extensions(?TLS_1_3, server_hello) -> #{server_hello_selected_version => undefined, key_share => undefined, pre_shared_key => undefined }; empty_extensions(?TLS_1_3, hello_retry_request) -> #{server_hello_selected_version => undefined, key_share => undefined, pre_shared_key => undefined, %% TODO remove! cookie => undefined }; empty_extensions(_, server_hello) -> #{renegotiation_info => undefined, alpn => undefined, next_protocol_negotiation => undefined, ec_point_formats => undefined}. handle_log(Level, {LogLevel, ReportMap, Meta}) -> ssl_logger:log(Level, LogLevel, ReportMap, Meta). %%% Recurse over possible paths until we find a valid one %%% or run out of alternatives. path_validate([], _, _, _, _, _, _, _, _, _, {error, {bad_cert, root_cert_expired}}) -> %% ROOT cert expired and no alternative ROOT certs could be found %% to validate this path or shorter variant of this path. So fail it %% with normal certificate expire reason. {error, {bad_cert, cert_expired}}; path_validate([], _, _, _, _, _, _, _, _, _, Error) -> Error; path_validate([{TrustedCert, Path} | Rest], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version, SslOptions, CertExt, InvalidatedList, Error) -> CB = path_validation_cb(Version), case CB:path_validation(trusted_unwrap(TrustedCert), Path, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version, SslOptions, CertExt) of {error, {bad_cert, root_cert_expired}} = NewError -> NewInvalidatedList = [TrustedCert | InvalidatedList], Alt = ssl_certificate:find_cross_sign_root_paths(Path, CertDbHandle, CertDbRef, NewInvalidatedList), path_validate(Alt ++ Rest, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version, SslOptions, CertExt, NewInvalidatedList, NewError); {error, {bad_cert, unknown_ca}} = NewError -> Alt = ssl_certificate:find_cross_sign_root_paths(Path, CertDbHandle, CertDbRef, InvalidatedList), path_validate(Alt ++ Rest, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version, SslOptions, CertExt, InvalidatedList, error_to_propagate(Error, NewError)); {error, _} when Rest =/= []-> path_validate(Rest, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version, SslOptions, CertExt, InvalidatedList, Error); Result -> Result end. trusted_unwrap(#cert{otp = TrustedCert}) -> TrustedCert; trusted_unwrap(#'OTPCertificate'{} = TrustedCert) -> TrustedCert; trusted_unwrap(ErrAtom) when is_atom(ErrAtom) -> ErrAtom. %% Call basic path validation algorithm in public_key pre TLS-1.3 path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version, #{verify_fun := VerifyFun, customize_hostname_check := CustomizeHostnameCheck, crl_check := CrlCheck, log_level := Level} = Opts, #{cert_ext := CertExt, ocsp_responder_certs := OcspResponderCerts, ocsp_state := OcspState}) -> SignAlgos = maps:get(signature_algs, Opts, undefined), SignAlgosCert = maps:get(signature_algs_cert, Opts, undefined), ValidationFunAndState = validation_fun_and_state(VerifyFun, #{role => Role, certdb => CertDbHandle, certdb_ref => CertDbRef, server_name => ServerName, customize_hostname_check => CustomizeHostnameCheck, signature_algs => SignAlgos, signature_algs_cert => SignAlgosCert, version => Version, crl_check => CrlCheck, crl_db => CRLDbHandle, cert_ext => CertExt, issuer => TrustedCert, ocsp_responder_certs => OcspResponderCerts, ocsp_state => OcspState}, Path, Level), Options = [{max_path_length, maps:get(depth, Opts, ?DEFAULT_DEPTH)}, {verify_fun, ValidationFunAndState}], public_key:pkix_path_validation(TrustedCert, Path, Options). error_to_propagate({error, {bad_cert, root_cert_expired}} = Error, _) -> Error; error_to_propagate(_, Error) -> Error. path_validation_cb(?TLS_1_3) -> tls_handshake_1_3; path_validation_cb(_) -> ?MODULE. %%%################################################################ %%%# %%%# Tracing %%%# handle_trace(csp, {call, {?MODULE, maybe_add_certificate_status_request, [_Version, SslOpts, _OcspNonce, _HelloExtensions]}}, Stack) -> OcspStapling = maps:get(ocsp_stapling, SslOpts, false), {io_lib:format("#1 ADD crt status request / OcspStapling option = ~W", [OcspStapling, 10]), Stack}.