diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml index 5fc2420fab3c..bb1c5c4630bd 100644 --- a/lib/public_key/doc/src/public_key.xml +++ b/lib/public_key/doc/src/public_key.xml @@ -145,6 +145,14 @@ + + + +

Can be provided together with a custom private key, that specifies a key fun, + to provide additional options understood by the fun.

+
+
+ @@ -390,9 +398,14 @@ Public-key encryption using the private key. -

Public-key encryption using the private key. - See also crypto:private_encrypt/4.

+

Public-key encryption using the private key. See also crypto:private_encrypt/4. + The key, can besides a standard RSA key, be a map specifing the + key algorithm rsa and a fun to handle the encryption + operation. This may be used for customized the encryption + operation with for instance hardware security modules (HSM) or + trusted platform modules (TPM). +

@@ -1006,7 +1019,11 @@ end

Creates a digital signature.

The Msg is either the binary "plain text" data to be signed or it is the hashed value of "plain text", that is, the - digest.

+ digest. The key, can besides a standard key, be a map specifing + a key algorithm and a fun that should handle the signing. This may + be used for customized signing with for instance hardware security + modules (HSM) or trusted platform modules (TPM). +

diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index 858860e29c17..2cd045bd24b1 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -112,10 +112,22 @@ test_root_cert/0 ]). --type public_key() :: rsa_public_key() | rsa_pss_public_key() | dsa_public_key() | ec_public_key() | ed_public_key() . --type private_key() :: rsa_private_key() | rsa_pss_private_key() | dsa_private_key() | ec_private_key() | ed_private_key() . +-type public_key() :: rsa_public_key() | + rsa_pss_public_key() | + dsa_public_key() | + ec_public_key() | + ed_public_key() . +-type private_key() :: rsa_private_key() | + rsa_pss_private_key() | + dsa_private_key() | + ec_private_key() | + ed_private_key() | + #{algorithm := eddsa | rsa_pss_pss | ecdsa | rsa | dsa, + sign_fun => fun()} . +-type custom_key_opts() :: [term()]. -type rsa_public_key() :: #'RSAPublicKey'{}. --type rsa_private_key() :: #'RSAPrivateKey'{}. +-type rsa_private_key() :: #'RSAPrivateKey'{} | #{algorithm := rsa, + encrypt_fun => fun()}. -type dss_public_key() :: integer(). -type rsa_pss_public_key() :: {rsa_pss_public_key(), #'RSASSA-PSS-params'{}}. -type rsa_pss_private_key() :: { #'RSAPrivateKey'{}, #'RSASSA-PSS-params'{}}. @@ -635,16 +647,16 @@ encrypt_private(PlainText, Key) -> CipherText when PlainText :: binary(), Key :: rsa_private_key(), - Options :: crypto:pk_encrypt_decrypt_opts(), + Options :: crypto:pk_encrypt_decrypt_opts() | custom_key_opts(), CipherText :: binary() . -encrypt_private(PlainText, - #'RSAPrivateKey'{modulus = N, publicExponent = E, - privateExponent = D} = Key, - Options) +encrypt_private(PlainText, Key, Options) when is_binary(PlainText), - is_integer(N), is_integer(E), is_integer(D), is_list(Options) -> - crypto:private_encrypt(rsa, PlainText, format_rsa_private_key(Key), default_options(Options)). + Opts = default_options(Options), + case format_sign_key(Key) of + {extern, Fun} -> Fun(PlainText, Opts); + {rsa, CryptoKey} -> crypto:private_encrypt(rsa, PlainText, CryptoKey, Opts) + end. %%-------------------------------------------------------------------- %% Description: List available group sizes among the pre-computed dh groups @@ -831,7 +843,7 @@ sign(DigestOrPlainText, DigestType, Key) -> Signature when Msg :: binary() | {digest,binary()}, DigestType :: digest_type(), Key :: private_key(), - Options :: crypto:pk_sign_verify_opts(), + Options :: crypto:pk_sign_verify_opts() | custom_key_opts(), Signature :: binary() . sign(Digest, none, Key = #'DSAPrivateKey'{}, Options) when is_binary(Digest) -> %% Backwards compatible @@ -840,6 +852,8 @@ sign(DigestOrPlainText, DigestType, Key, Options) -> case format_sign_key(Key) of badarg -> erlang:error(badarg, [DigestOrPlainText, DigestType, Key, Options]); + {extern, Fun} when is_function(Fun) -> + Fun(DigestOrPlainText, DigestType, Options); {Algorithm, CryptoKey} -> try crypto:sign(Algorithm, DigestType, DigestOrPlainText, CryptoKey, Options) catch %% Compatible with old error schema @@ -1505,8 +1519,17 @@ format_pkix_sign_key({#'RSAPrivateKey'{} = Key, _}) -> Key; format_pkix_sign_key(Key) -> Key. + +format_sign_key(#{encrypt_fun := KeyFun}) -> + {extern, KeyFun}; +format_sign_key(#{sign_fun := KeyFun}) -> + {extern, KeyFun}; format_sign_key(Key = #'RSAPrivateKey'{}) -> {rsa, format_rsa_private_key(Key)}; +format_sign_key({#'RSAPrivateKey'{} = Key, _}) -> + %% Params are handled in options arg + %% provided by caller. + {rsa, format_rsa_private_key(Key)}; format_sign_key(#'DSAPrivateKey'{p = P, q = Q, g = G, x = X}) -> {dss, [P, Q, G, X]}; format_sign_key(#'ECPrivateKey'{privateKey = PrivKey, parameters = {namedCurve, Curve} = Param}) diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl index 0c8cf07fb5a2..ee1ed7b56749 100644 --- a/lib/public_key/test/public_key_SUITE.erl +++ b/lib/public_key/test/public_key_SUITE.erl @@ -77,12 +77,16 @@ cert_pem/1, encrypt_decrypt/0, encrypt_decrypt/1, + encrypt_decrypt_sign_fun/0, + encrypt_decrypt_sign_fun/1, rsa_sign_verify/0, rsa_sign_verify/1, rsa_pss_sign_verify/0, rsa_pss_sign_verify/1, dsa_sign_verify/0, dsa_sign_verify/1, + custom_sign_fun_verify/0, + custom_sign_fun_verify/1, pkix/0, pkix/1, pkix_countryname/0, @@ -153,6 +157,7 @@ all() -> appup, {group, pem_decode_encode}, encrypt_decrypt, + encrypt_decrypt_sign_fun, {group, sign_verify}, pkix, pkix_countryname, @@ -190,7 +195,7 @@ groups() -> ec_pem_encode_generated, gen_ec_param_prime_field, gen_ec_param_char_2_field]}, {sign_verify, [], [rsa_sign_verify, rsa_pss_sign_verify, dsa_sign_verify, - eddsa_sign_verify_24_compat]} + eddsa_sign_verify_24_compat, custom_sign_fun_verify]} ]. %%------------------------------------------------------------------- init_per_suite(Config) -> @@ -655,6 +660,22 @@ encrypt_decrypt(Config) when is_list(Config) -> RsaEncrypted2 = public_key:encrypt_public(Msg, PublicKey), Msg = public_key:decrypt_private(RsaEncrypted2, PrivateKey), ok. + +%%-------------------------------------------------------------------- +encrypt_decrypt_sign_fun() -> + [{doc, "Test public_key:encrypt_private with user provided sign_fun"}]. +encrypt_decrypt_sign_fun(Config) when is_list(Config) -> + {PrivateKey, _DerKey} = erl_make_certs:gen_rsa(64), + #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} = PrivateKey, + EncryptFun = fun (PlainText, Options) -> + public_key:encrypt_private(PlainText, PrivateKey, Options) + end, + CustomPrivKey = #{encrypt_fun => EncryptFun}, + PublicKey = #'RSAPublicKey'{modulus=Mod, publicExponent=Exp}, + Msg = list_to_binary(lists:duplicate(5, "Foo bar 100")), + RsaEncrypted = public_key:encrypt_private(Msg, CustomPrivKey), + Msg = public_key:decrypt_public(RsaEncrypted, PublicKey), + ok. %%-------------------------------------------------------------------- rsa_sign_verify() -> @@ -731,6 +752,28 @@ dsa_sign_verify(Config) when is_list(Config) -> {DSAPublicKey, DSAParams}), false = public_key:verify(Digest, none, <<1:8, DigestSign/binary>>, {DSAPublicKey, DSAParams}). +%%-------------------------------------------------------------------- + +custom_sign_fun_verify() -> + [{doc, "Checks that public_key:sign correctly calls the `sign_fun`"}]. +custom_sign_fun_verify(Config) when is_list(Config) -> + {_, CaKey} = erl_make_certs:make_cert([{key, rsa}]), + PrivateRSA = public_key:pem_entry_decode(CaKey), + #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} = PrivateRSA, + PublicRSA = #'RSAPublicKey'{modulus=Mod, publicExponent=Exp}, + SignFun = fun (Msg, HashAlgo, Options) -> + public_key:sign(Msg, HashAlgo, PrivateRSA, Options) + end, + CustomKey = #{algorithm => rsa, sign_fun => SignFun}, + + Msg = list_to_binary(lists:duplicate(5, "Foo bar 100")), + RSASign = public_key:sign(Msg, sha, CustomKey), + true = public_key:verify(Msg, sha, RSASign, PublicRSA), + false = public_key:verify(<<1:8, Msg/binary>>, sha, RSASign, PublicRSA), + false = public_key:verify(Msg, sha, <<1:8, RSASign/binary>>, PublicRSA), + + RSASign1 = public_key:sign(Msg, md5, CustomKey), + true = public_key:verify(Msg, md5, RSASign1, PublicRSA). %%-------------------------------------------------------------------- pkix() -> diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index f719c55c32f3..403872dff00f 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -343,11 +343,35 @@ -

The DER-encoded user's private key or a map referring to a crypto - engine and its key reference that optionally can be password protected, - see also crypto:engine_load/3 - and Crypto's Users Guide. If this option - is supplied, it overrides option keyfile.

+ +

The user's private key. Either the key can be provided + directly as DER encoded entity, or indirectly using a crypto + engine/provider (with key reference information) or an Erlang + fun (with possible custom options). The latter two options + can both be used for customized signing with for instance + hardware security modules (HSM) or trusted platform modules + (TPM).

+ + +

A DER encoded key will need to specify the ASN-1 type used to + create the encoding.

+ +

An engine/provider needs to specify specific + information to support this concept and can optionally be + password protected, see also crypto:engine_load/3 + and + Crypto's Users Guide.

+ +

A fun option should include a fun that mimics public_key:sign/4 + and possibly public_key:private_encrypt/4 + if legacy versions TLS-1.0 and TLS-1.1 should be supported.

+
+ +

If this option is supplied, it overrides option keyfile. +

@@ -616,7 +640,7 @@ version. ROOT-CA, and so on. The default value is 10.

- + diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index b9f69af6a3bb..4708f7d611e5 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -88,6 +88,6 @@ {applications, [crypto, public_key, kernel, stdlib]}, {env, []}, {mod, {ssl_app, []}}, - {runtime_dependencies, ["stdlib-4.1","public_key-1.11.3","kernel-9.0", + {runtime_dependencies, ["stdlib-4.1","public_key-@OTP-18876@","kernel-9.0", "erts-14.0","crypto-5.0", "inets-5.10.7", "runtime_tools-1.15.1"]}]}. diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 03a9f9b0c279..43c5ae4ac3a9 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -84,7 +84,7 @@ signature_algs/2, eccs/0, eccs/1, - versions/0, + versions/0, groups/0, groups/1, format_error/1, @@ -111,9 +111,9 @@ -removed({cipher_suites, 1, "use cipher_suites/2,3 instead"}). -removed([{negotiated_next_protocol,1, - "use ssl:negotiated_protocol/1 instead"}]). + "use ssl:negotiated_protocol/1 instead"}]). -removed([{connection_info,1, - "use ssl:connection_information/[1,2] instead"}]). + "use ssl:connection_information/[1,2] instead"}]). -export_type([socket/0, sslsocket/0, @@ -193,7 +193,7 @@ -type legacy_hash() :: sha224 | sha | md5. --type sign_algo() :: rsa | dsa | ecdsa | eddsa. % exported +-type sign_algo() :: rsa | rsa_pss_pss | dsa | ecdsa | eddsa. % exported -type sign_schemes() :: [sign_scheme()]. @@ -233,8 +233,8 @@ }. -type old_cipher_suite() :: {kex_algo(), cipher(), hash()} % Pre TLS 1.2 - %% TLS 1.2, internally PRE TLS 1.2 will use default_prf - | {kex_algo(), cipher(), hash() | aead, hash()}. + %% TLS 1.2, internally PRE TLS 1.2 will use default_prf + | {kex_algo(), cipher(), hash() | aead, hash()}. -type named_curve() :: sect571r1 | sect571k1 | @@ -350,11 +350,17 @@ -type cert() :: public_key:der_encoded(). -type cert_pem() :: file:filename(). -type key() :: {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey' |'PrivateKeyInfo', - public_key:der_encoded()} | - #{algorithm := rsa | dss | ecdsa, + public_key:der_encoded()} | + #{algorithm := sign_algo(), engine := crypto:engine_ref(), key_id := crypto:key_id(), - password => crypto:password()}. % exported + password => crypto:password()} | + #{algorithm := sign_algo(), + sign_fun := fun(), + sign_opts => list(), + encrypt_fun => fun(), %% Only TLS-1.0, TLS-1.1 and rsa-key + encrypt_opts => list() + }. % exported -type key_pem() :: file:filename(). -type key_pem_password() :: iodata() | fun(() -> iodata()). -type certs_keys() :: [cert_key_conf()]. @@ -367,12 +373,11 @@ -type ciphers() :: [erl_cipher_suite()] | string(). % (according to old API) exported -type cipher_filters() :: list({key_exchange | cipher | mac | prf, - algo_filter()}). % exported + algo_filter()}). % exported -type algo_filter() :: fun((kex_algo()|cipher()|hash()|aead|default_prf) -> true | false). -type keep_secrets() :: boolean(). -type secure_renegotiation() :: boolean(). -type allowed_cert_chain_length() :: integer(). - -type custom_verify() :: {Verifyfun :: fun(), InitialUserState :: any()}. -type crl_check() :: boolean() | peer | best_effort. -type crl_cache_opts() :: {Module :: atom(), @@ -430,9 +435,9 @@ {use_ticket, use_ticket()} | {early_data, client_early_data()} | {use_srtp, use_srtp()}. - %% {ocsp_stapling, ocsp_stapling()} | - %% {ocsp_responder_certs, ocsp_responder_certs()} | - %% {ocsp_nonce, ocsp_nonce()}. +%% {ocsp_stapling, ocsp_stapling()} | +%% {ocsp_responder_certs, ocsp_responder_certs()} | +%% {ocsp_nonce, ocsp_nonce()}. -type client_verify_type() :: verify_type(). -type client_reuse_session() :: session_id() | {session_id(), SessionData::binary()}. @@ -578,9 +583,9 @@ stop() -> %%-------------------------------------------------------------------- -spec connect(TCPSocket, TLSOptions) -> - {ok, sslsocket()} | - {error, reason()} | - {option_not_a_key_value_tuple, any()} when + {ok, sslsocket()} | + {error, reason()} | + {option_not_a_key_value_tuple, any()} when TCPSocket :: socket(), TLSOptions :: [tls_client_option()]. @@ -588,22 +593,22 @@ connect(Socket, SslOptions) -> connect(Socket, SslOptions, infinity). -spec connect(TCPSocket, TLSOptions, Timeout) -> - {ok, sslsocket()} | {error, reason()} when + {ok, sslsocket()} | {error, reason()} when TCPSocket :: socket(), TLSOptions :: [tls_client_option()], Timeout :: timeout(); (Host, Port, TLSOptions) -> - {ok, sslsocket()} | - {ok, sslsocket(),Ext :: protocol_extensions()} | - {error, reason()} | - {option_not_a_key_value_tuple, any()} when + {ok, sslsocket()} | + {ok, sslsocket(),Ext :: protocol_extensions()} | + {error, reason()} | + {option_not_a_key_value_tuple, any()} when Host :: host(), Port :: inet:port_number(), TLSOptions :: [tls_client_option()]. connect(Socket, SslOptions0, Timeout) when is_list(SslOptions0) andalso (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> - + try CbInfo = handle_option_cb_info(SslOptions0, tls), Transport = element(1, CbInfo), @@ -617,10 +622,10 @@ connect(Host, Port, Options) -> connect(Host, Port, Options, infinity). -spec connect(Host, Port, TLSOptions, Timeout) -> - {ok, sslsocket()} | - {ok, sslsocket(),Ext :: protocol_extensions()} | - {error, reason()} | - {option_not_a_key_value_tuple, any()} when + {ok, sslsocket()} | + {ok, sslsocket(),Ext :: protocol_extensions()} | + {error, reason()} | + {option_not_a_key_value_tuple, any()} when Host :: host(), Port :: inet:port_number(), TLSOptions :: [tls_client_option()], @@ -664,7 +669,7 @@ listen(Port, Options0) -> %% Description: Performs transport accept on an ssl listen socket %%-------------------------------------------------------------------- -spec transport_accept(ListenSocket) -> {ok, SslSocket} | - {error, reason()} when + {error, reason()} when ListenSocket :: sslsocket(), SslSocket :: sslsocket(). @@ -672,7 +677,7 @@ transport_accept(ListenSocket) -> transport_accept(ListenSocket, infinity). -spec transport_accept(ListenSocket, Timeout) -> {ok, SslSocket} | - {error, reason()} when + {error, reason()} when ListenSocket :: sslsocket(), Timeout :: timeout(), SslSocket :: sslsocket(). @@ -686,7 +691,7 @@ transport_accept(#sslsocket{pid = {ListenSocket, dtls_gen_connection -> dtls_socket:accept(ListenSocket, Config, Timeout) end. - + %%-------------------------------------------------------------------- %% %% Description: Performs accept on an ssl listen socket. e.i. performs @@ -729,9 +734,9 @@ handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Tim handshake(ListenSocket, SslOptions) -> handshake(ListenSocket, SslOptions, infinity). -spec handshake(Socket, Options, Timeout) -> - {ok, SslSocket} | - {ok, SslSocket, Ext} | - {error, Reason} when + {ok, SslSocket} | + {ok, SslSocket, Ext} | + {error, Reason} when Socket :: socket() | sslsocket(), SslSocket :: sslsocket(), Options :: [server_option()], @@ -782,7 +787,7 @@ handshake(Socket, SslOptions, Timeout) when (is_integer(Timeout) andalso Timeout %%-------------------------------------------------------------------- -spec handshake_continue(HsSocket, Options) -> - {ok, SslSocket} | {error, Reason} when + {ok, SslSocket} | {error, Reason} when HsSocket :: sslsocket(), Options :: [tls_client_option() | tls_server_option()], SslSocket :: sslsocket(), @@ -795,7 +800,7 @@ handshake_continue(Socket, SSLOptions) -> handshake_continue(Socket, SSLOptions, infinity). %%-------------------------------------------------------------------- -spec handshake_continue(HsSocket, Options, Timeout) -> - {ok, SslSocket} | {error, Reason} when + {ok, SslSocket} | {error, Reason} when HsSocket :: sslsocket(), Options :: [tls_client_option() | tls_server_option()], Timeout :: timeout(), @@ -850,7 +855,7 @@ close(#sslsocket{pid = [TLSPid|_]}, Other end; close(#sslsocket{pid = [TLSPid|_]}, Timeout) when is_pid(TLSPid), - (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> + (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> ssl_gen_statem:close(TLSPid, {close, Timeout}); close(#sslsocket{pid = {dtls = ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}, _) -> dtls_socket:close(Transport, ListenSocket); @@ -963,7 +968,7 @@ connection_information(#sslsocket{pid = [Pid|_]}, Items) when is_pid(Pid) -> %%-------------------------------------------------------------------- -spec peername(SslSocket) -> {ok, {Address, Port}} | - {error, reason()} when + {error, reason()} when SslSocket :: sslsocket(), Address :: inet:ip_address(), Port :: inet:port_number(). @@ -1021,12 +1026,12 @@ negotiated_protocol(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> %% TLS/DTLS version %%-------------------------------------------------------------------- cipher_suites(Description, Version) when Version == 'tlsv1.3'; - Version == 'tlsv1.2'; - Version == 'tlsv1.1'; - Version == tlsv1 -> + Version == 'tlsv1.2'; + Version == 'tlsv1.1'; + Version == tlsv1 -> cipher_suites(Description, tls_record:protocol_version_name(Version)); cipher_suites(Description, Version) when Version == 'dtlsv1.2'; - Version == 'dtlsv1'-> + Version == 'dtlsv1'-> cipher_suites(Description, dtls_record:protocol_version_name(Version)); cipher_suites(Description, Version) -> [ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- supported_suites(Description, Version)]. @@ -1040,12 +1045,12 @@ cipher_suites(Description, Version) -> %% TLS/DTLS version %%-------------------------------------------------------------------- cipher_suites(Description, Version, StringType) when Version == 'tlsv1.3'; - Version == 'tlsv1.2'; - Version == 'tlsv1.1'; - Version == tlsv1 -> + Version == 'tlsv1.2'; + Version == 'tlsv1.1'; + Version == tlsv1 -> cipher_suites(Description, tls_record:protocol_version_name(Version), StringType); cipher_suites(Description, Version, StringType) when Version == 'dtlsv1.2'; - Version == 'dtlsv1'-> + Version == 'dtlsv1'-> cipher_suites(Description, dtls_record:protocol_version_name(Version), StringType); cipher_suites(Description, Version, rfc) -> [ssl_cipher_format:suite_map_to_str(ssl_cipher_format:suite_bin_to_map(Suite)) @@ -1125,8 +1130,8 @@ signature_algs(default, 'tlsv1.3') -> signature_algs(default, 'tlsv1.2') -> tls_v1:default_signature_algs([tls_record:protocol_version_name('tlsv1.2')]); signature_algs(all, 'tlsv1.3') -> - tls_v1:default_signature_algs([tls_record:protocol_version_name('tlsv1.3'), - tls_record:protocol_version_name('tlsv1.2')]) ++ + tls_v1:default_signature_algs([tls_record:protocol_version_name('tlsv1.3'), + tls_record:protocol_version_name('tlsv1.2')]) ++ tls_v1:legacy_signature_algs_pre_13(); signature_algs(all, 'tlsv1.2') -> tls_v1:default_signature_algs([tls_record:protocol_version_name('tlsv1.2')]) ++ @@ -1194,7 +1199,7 @@ groups(default) -> %%-------------------------------------------------------------------- -spec getopts(SslSocket, OptionNames) -> - {ok, [gen_tcp:option()]} | {error, reason()} when + {ok, [gen_tcp:option()]} | {error, reason()} when SslSocket :: sslsocket(), OptionNames :: [gen_tcp:option_name()]. %% @@ -1286,18 +1291,18 @@ setopts(#sslsocket{}, Options) -> %%--------------------------------------------------------------- -spec getstat(SslSocket) -> - {ok, OptionValues} | {error, inet:posix()} when + {ok, OptionValues} | {error, inet:posix()} when SslSocket :: sslsocket(), OptionValues :: [{inet:stat_option(), integer()}]. %% %% Description: Get all statistic options for a socket. %%-------------------------------------------------------------------- getstat(Socket) -> - getstat(Socket, inet:stats()). + getstat(Socket, inet:stats()). %%--------------------------------------------------------------- -spec getstat(SslSocket, Options) -> - {ok, OptionValues} | {error, inet:posix()} when + {ok, OptionValues} | {error, inet:posix()} when SslSocket :: sslsocket(), Options :: [inet:stat_option()], OptionValues :: [{inet:stat_option(), integer()}]. @@ -1350,7 +1355,7 @@ shutdown(#sslsocket{pid = [Pid|_]}, How) when is_pid(Pid) -> %%-------------------------------------------------------------------- -spec sockname(SslSocket) -> - {ok, {Address, Port}} | {error, reason()} when + {ok, {Address, Port}} | {error, reason()} when SslSocket :: sslsocket(), Address :: inet:ip_address(), Port :: inet:port_number(). @@ -1381,18 +1386,18 @@ versions() -> ImplementedTLSVsns = ?ALL_AVAILABLE_VERSIONS, ImplementedDTLSVsns = ?ALL_AVAILABLE_DATAGRAM_VERSIONS, - TLSCryptoSupported = fun(Vsn) -> - tls_record:sufficient_crypto_support(Vsn) + TLSCryptoSupported = fun(Vsn) -> + tls_record:sufficient_crypto_support(Vsn) + end, + DTLSCryptoSupported = fun(Vsn) -> + tls_record:sufficient_crypto_support(dtls_v1:corresponding_tls_version(Vsn)) end, - DTLSCryptoSupported = fun(Vsn) -> - tls_record:sufficient_crypto_support(dtls_v1:corresponding_tls_version(Vsn)) - end, SupportedTLSVsns = [tls_record:protocol_version(Vsn) || Vsn <- ConfTLSVsns, TLSCryptoSupported(Vsn)], SupportedDTLSVsns = [dtls_record:protocol_version(Vsn) || Vsn <- ConfDTLSVsns, DTLSCryptoSupported(Vsn)], AvailableTLSVsns = [Vsn || Vsn <- ImplementedTLSVsns, TLSCryptoSupported(tls_record:protocol_version_name(Vsn))], AvailableDTLSVsns = [Vsn || Vsn <- ImplementedDTLSVsns, DTLSCryptoSupported(dtls_record:protocol_version_name(Vsn))], - + [{ssl_app, ?VSN}, {supported, SupportedTLSVsns}, {supported_dtls, SupportedDTLSVsns}, @@ -1409,7 +1414,7 @@ versions() -> %% Description: Initiates a renegotiation. %%-------------------------------------------------------------------- renegotiate(#sslsocket{pid = [Pid, Sender |_]} = Socket) when is_pid(Pid), - is_pid(Sender) -> + is_pid(Sender) -> case ssl:connection_information(Socket, [protocol]) of {ok, [{protocol, 'tlsv1.3'}]} -> {error, notsup}; @@ -1451,7 +1456,7 @@ update_keys(_, Type) -> %%-------------------------------------------------------------------- -spec prf(SslSocket, Secret, Label, Seed, WantedLength) -> - {ok, binary()} | {error, reason()} when + {ok, binary()} | {error, reason()} when SslSocket :: sslsocket(), Secret :: binary() | 'master_secret', Label::binary(), @@ -1533,7 +1538,7 @@ str_to_suite(CipherSuiteName) -> _:_ -> {error, {not_recognized, CipherSuiteName}} end. - + %%%-------------------------------------------------------------- %%% Internal functions %%%-------------------------------------------------------------------- @@ -1814,21 +1819,21 @@ opt_verify_fun(UserOpts, Opts, _Env) -> Opts#{verify_fun => VerifyFun}. none_verify_fun() -> - fun(_, {bad_cert, _}, UserState) -> - {valid, UserState}; - (_, {extension, #'Extension'{critical = true}}, UserState) -> - %% This extension is marked as critical, so - %% certificate verification should fail if we don't - %% understand the extension. However, this is - %% `verify_none', so let's accept it anyway. - {valid, UserState}; - (_, {extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, UserState) -> + fun(_, {bad_cert, _}, UserState) -> + {valid, UserState}; + (_, {extension, #'Extension'{critical = true}}, UserState) -> + %% This extension is marked as critical, so + %% certificate verification should fail if we don't + %% understand the extension. However, this is + %% `verify_none', so let's accept it anyway. + {valid, UserState}; + (_, {extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, UserState) -> {valid, UserState}; - (_, valid_peer, UserState) -> - {valid, UserState} - end. + (_, valid_peer, UserState) -> + {valid, UserState} + end. convert_verify_fun() -> fun(_,{bad_cert, _} = Reason, OldFun) -> @@ -1844,7 +1849,7 @@ convert_verify_fun() -> {valid, UserState} end. -opt_certs(UserOpts, #{log_level := LogLevel} = Opts0, Env) -> +opt_certs(UserOpts, #{log_level := LogLevel, versions := Versions} = Opts0, Env) -> case get_opt_list(certs_keys, [], UserOpts, Opts0) of {Where, []} when Where =/= new -> opt_old_certs(UserOpts, #{}, Opts0, Env); @@ -1852,11 +1857,11 @@ opt_certs(UserOpts, #{log_level := LogLevel} = Opts0, Env) -> opt_old_certs(UserOpts, CertKey, Opts0, Env); {Where, CKs} when is_list(CKs) -> warn_override(Where, UserOpts, certs_keys, [cert,certfile,key,keyfile,password], LogLevel), - Opts0#{certs_keys => [check_cert_key(CK, #{}, LogLevel) || CK <- CKs]} + Opts0#{certs_keys => [check_cert_key(Versions, CK, #{}, LogLevel) || CK <- CKs]} end. -opt_old_certs(UserOpts, CertKeys, #{log_level := LogLevel}=SSLOpts, _Env) -> - CK = check_cert_key(UserOpts, CertKeys, LogLevel), +opt_old_certs(UserOpts, CertKeys, #{log_level := LogLevel, versions := Versions}=SSLOpts, _Env) -> + CK = check_cert_key(Versions, UserOpts, CertKeys, LogLevel), case maps:keys(CK) =:= [] of true -> SSLOpts#{certs_keys => []}; @@ -1864,7 +1869,7 @@ opt_old_certs(UserOpts, CertKeys, #{log_level := LogLevel}=SSLOpts, _Env) -> SSLOpts#{certs_keys => [CK]} end. -check_cert_key(UserOpts, CertKeys, LogLevel) -> +check_cert_key(Versions, UserOpts, CertKeys, LogLevel) -> CertKeys0 = case get_opt(cert, undefined, UserOpts, CertKeys) of {Where, Cert} when is_binary(Cert) -> warn_override(Where, UserOpts, cert, [certfile], LogLevel), @@ -1899,7 +1904,15 @@ check_cert_key(UserOpts, CertKeys, LogLevel) -> KF == 'RSAPrivateKey'; KF == 'DSAPrivateKey'; KF == 'ECPrivateKey'; KF == 'PrivateKeyInfo' -> CertKeys0#{key => Key}; - {_, #{engine := _, key_id := _, algorithm := _} = Key} -> + {_, #{engine := _, key_id := _, algorithm := Algo} = Key} -> + check_key_algo_version_dep(Versions, Algo), + CertKeys0#{key => Key}; + {_, #{sign_fun := _, algorithm := Algo} = Key} -> + check_key_algo_version_dep(Versions, Algo), + check_key_legacy_version_dep(Versions, Key, Algo), + CertKeys0#{key => Key}; + {_, #{encrypt_fun := _, algorithm := rsa} = Key} -> + check_key_legacy_version_dep(Versions, Key), CertKeys0#{key => Key}; {new, Err1} -> option_error(key, Err1) @@ -1916,6 +1929,29 @@ check_cert_key(UserOpts, CertKeys, LogLevel) -> end, CertKeys2. +check_key_algo_version_dep(Versions, eddsa) -> + assert_version_dep(key, Versions, ['tlsv1.3']); +check_key_algo_version_dep(Versions, rsa_pss_pss) -> + assert_version_dep(key, Versions, ['tlsv1.3', 'tlsv1.2']); +check_key_algo_version_dep(Versions, dsa) -> + assert_version_dep(key, Versions, ['tlsv1.2', 'tlsv1.1', 'tlsv1']); +check_key_algo_version_dep(_,_) -> + true. + +check_key_legacy_version_dep(Versions, Key, rsa) -> + check_key_legacy_version_dep(Versions, Key); +check_key_legacy_version_dep(_,_,_) -> + true. + +check_key_legacy_version_dep(Versions, Key) -> + EncryptFun = maps:get(encrypt_fun, Key, undefined), + case EncryptFun of + undefined -> + assert_version_dep(key, Versions, ['tlsv1.3', 'tlsv1.2']); + _ -> + assert_version_dep(key, Versions, ['tlsv1.1', 'tlsv1']) + end. + opt_cacerts(UserOpts, #{verify := Verify, log_level := LogLevel, versions := Versions} = Opts, #{role := Role}) -> {_, CaCerts} = get_opt_list(cacerts, undefined, UserOpts, Opts), @@ -2248,7 +2284,7 @@ opt_identity(UserOpts, #{versions := Versions} = Opts, _Env) -> PSKSize = byte_size(PSK1), assert_version_dep(psk_identity, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), option_error(not (0 < PSKSize andalso PSKSize < 65536), - psk_identity, {psk_identity, PSK0}), + psk_identity, {psk_identity, PSK0}), PSK1; {_, PSK0} -> PSK0 @@ -2260,7 +2296,7 @@ opt_identity(UserOpts, #{versions := Versions} = Opts, _Env) -> UserSize = byte_size(User), assert_version_dep(srp_identity, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), option_error(not (0 < UserSize andalso UserSize < 65536), - srp_identity, {srp_identity, PSK0}), + srp_identity, {srp_identity, PSK0}), {User, unicode:characters_to_binary(S2)}; {new, Err} -> option_error(srp_identity, Err); @@ -2736,7 +2772,7 @@ all_suites([?TLS_1_3, Version1 |_]) -> ssl_cipher:all_suites(Version1) ++ ssl_cipher:anonymous_suites(Version1); all_suites([Version|_]) -> - ssl_cipher:all_suites(Version) ++ + ssl_cipher:all_suites(Version) ++ ssl_cipher:anonymous_suites(Version). tuple_to_map({Kex, Cipher, Mac}) -> @@ -2902,7 +2938,7 @@ connection_cb(tls) -> connection_cb(dtls) -> dtls_gen_connection; connection_cb(Opts) -> - connection_cb(proplists:get_value(protocol, Opts, tls)). + connection_cb(proplists:get_value(protocol, Opts, tls)). %% Assert that basic options are on the format {Key, Value} @@ -2986,4 +3022,4 @@ format_ocsp_params(Map) -> Nonce = maps:get(ocsp_nonce, Map, '?'), Certs = maps:get(ocsp_responder_certs, Map, '?'), io_lib:format("Stapling = ~W Nonce = ~W Certs = ~W", - [Stapling, 5, Nonce, 5, Certs, 5]). + [Stapling, 5, Nonce, 5, Certs, 5]). diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl index 761a4f431561..fc6a305c32c0 100644 --- a/lib/ssl/src/ssl_config.erl +++ b/lib/ssl/src/ssl_config.erl @@ -87,6 +87,9 @@ group_pairs([#{private_key := #'DSAPrivateKey'{}} = Pair | Rest], #{dsa := DSA} group_pairs([#{private_key := #{algorithm := dss, engine := _}} = Pair | Rest], Group) -> Pairs = maps:get(dsa, Group), group_pairs(Rest, Group#{dsa => [Pair | Pairs]}); +group_pairs([#{private_key := #{algorithm := Alg, sign_fun := _}} = Pair | Rest], Group) -> + Pairs = maps:get(Alg, Group), + group_pairs(Rest, Group#{Alg => [Pair | Pairs]}); group_pairs([#{private_key := #{algorithm := Alg, engine := _}} = Pair | Rest], Group) -> Pairs = maps:get(Alg, Group), group_pairs(Rest, Group#{Alg => [Pair | Pairs]}); @@ -107,16 +110,23 @@ prioritize_groups(#{eddsa := EDDSA, prio_eddsa(EDDSA) -> %% Engine not supported yet - using_curve({namedCurve, ?'id-Ed25519'}, EDDSA, []) ++ using_curve({namedCurve, ?'id-Ed448'}, EDDSA, []). + SignFunPairs = [Pair || Pair = #{private_key := #{sign_fun := _}} <- EDDSA], + SignFunPairs + ++ using_curve({namedCurve, ?'id-Ed25519'}, EDDSA, []) + ++ using_curve({namedCurve, ?'id-Ed448'}, EDDSA, []). prio_ecdsa(ECDSA) -> EnginePairs = [Pair || Pair = #{private_key := #{engine := _}} <- ECDSA], + SignFunPairs = [Pair || Pair = #{private_key := #{sign_fun := _}} <- ECDSA], Curves = tls_v1:ecc_curves(all), - EnginePairs ++ lists:foldr(fun(Curve, AccIn) -> - CurveOid = pubkey_cert_records:namedCurves(Curve), - Pairs = using_curve({namedCurve, CurveOid}, ECDSA -- EnginePairs, []), - Pairs ++ AccIn - end, [], Curves). + EnginePairs + ++ SignFunPairs + ++ lists:foldr( + fun(Curve, AccIn) -> + CurveOid = pubkey_cert_records:namedCurves(Curve), + Pairs = using_curve({namedCurve, CurveOid}, ECDSA -- EnginePairs -- SignFunPairs, []), + Pairs ++ AccIn + end, [], Curves). using_curve(_, [], Acc) -> lists:reverse(Acc); using_curve(Curve, [#{private_key := #'ECPrivateKey'{parameters = Curve}} = Pair | Rest], Acc) -> @@ -265,6 +275,8 @@ init_certificate_file(CertFile, PemCache, Role) -> file_error(CertFile, {certfile, Reason}) end. +init_private_key(#{algorithm := _, sign_fun := _SignFun} = Key, _, _) -> + Key; init_private_key(#{algorithm := Alg} = Key, _, _PemCache) when Alg =:= ecdsa; Alg =:= rsa; Alg =:= dss -> case maps:is_key(engine, Key) andalso maps:is_key(key_id, Key) of diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 505b1a50be27..85c89178f5ad 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -386,15 +386,15 @@ certificate_verify(Signature, PublicKeyInfo, Version, %% 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), + {_, PubKey, _}) when SignAlgo == rsa_pss_rsae; + SignAlgo == rsa_pss_pss -> + Options = verify_options(SignAlgo, HashAlgo), public_key:verify(Msg, HashAlgo, Signature, PubKey, Options); -verify_signature(Version, Msg, {HashAlgo, SignAlgo}, Signature, {?rsaEncryption, PubKey, PubKeyParams}) +verify_signature(Version, Msg, {HashAlgo, SignAlgo}, Signature, {?rsaEncryption, PubKey, _}) when ?TLS_GTE(Version, ?TLS_1_2) -> - Options = verify_options(SignAlgo, HashAlgo, PubKeyParams), + Options = verify_options(SignAlgo, HashAlgo), public_key:verify(Msg, HashAlgo, Signature, PubKey, Options); -verify_signature(Version, {digest, Digest}, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) +verify_signature(Version, {digest, Digest}, _HashAlgo, Signature, {?rsaEncryption, PubKey, _}) when ?TLS_LTE(Version, ?TLS_1_1) -> case public_key:decrypt_public(Signature, PubKey, [{rsa_pad, rsa_pkcs1_padding}]) of @@ -2139,42 +2139,59 @@ 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) -> +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}, _HashAlgo, #{algorithm := rsa, encrypt_fun := Fun} = Key0, rsa) + when ?TLS_LTE(Version, ?TLS_1_1) -> + CustomOpts = maps:get(encrypt_opts, Key0, []), + Key = #{algorithm => rsa, encrypt_fun => Fun}, + public_key:encrypt_private(Digest, Key, CustomOpts ++ [{rsa_pad, rsa_pkcs1_padding}]); do_digitally_signed(Version, {digest, Digest}, _, - #{algorithm := rsa} = Engine, rsa) when ?TLS_LTE(Version, ?TLS_1_1) -> + #{algorithm := rsa, engine := _} = 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) -> +do_digitally_signed(_, Msg, HashAlgo, #{algorithm := Alg, engine := _} = 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, #{algorithm := SignAlgo, sign_fun := Fun} = Key0, SignAlgo) -> + CustomOpts = maps:get(sign_opts, Key0, []), + Options = signature_options(SignAlgo, HashAlgo), + Key = #{algorithm => SignAlgo, sign_fun => Fun}, + public_key:sign(Msg, HashAlgo, Key, CustomOpts ++ Options); 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(rsa_pss_rsae, HashAlgo) -> + pss_options(HashAlgo, hash_algo_byte_size(HashAlgo)); +signature_options(rsa_pss_pss, HashAlgo) -> + pss_options(HashAlgo, hash_algo_byte_size(HashAlgo)); signature_options(_, _) -> []. -verify_options(SignAlgo, HashAlgo, _KeyParams) - when SignAlgo =:= rsa_pss_rsae orelse - SignAlgo =:= rsa_pss_pss -> - pss_options(HashAlgo); -verify_options(_, _, _) -> +verify_options(rsa_pss_rsae, HashAlgo) -> + pss_options(HashAlgo, hash_algo_byte_size(HashAlgo)); +verify_options(rsa_pss_pss, HashAlgo) -> + pss_options(HashAlgo, hash_algo_byte_size(HashAlgo)); +verify_options(_, _) -> []. -pss_options(HashAlgo) -> - %% of the digest algorithm: rsa_pss_saltlen = -1 +pss_options(HashAlgo, SaltLen) -> [{rsa_padding, rsa_pkcs1_pss_padding}, - {rsa_pss_saltlen, -1}, + {rsa_pss_saltlen, SaltLen}, {rsa_mgf1_md, HashAlgo}]. +hash_algo_byte_size(sha256) -> + 32; +hash_algo_byte_size(sha384) -> + 48; +hash_algo_byte_size(sha512) -> + 64. + bad_key(#'DSAPrivateKey'{}) -> unacceptable_dsa_key; bad_key(#'RSAPrivateKey'{}) -> @@ -2183,6 +2200,10 @@ bad_key(#'ECPrivateKey'{}) -> unacceptable_ecdsa_key; bad_key(#{algorithm := rsa}) -> unacceptable_rsa_key; +bad_key(#{algorithm := rsa_pss_pss}) -> + unacceptable_rsa_pss_pss_key; +bad_key(#{algorithm := eddsa}) -> + unacceptable_eddsa_key; bad_key(#{algorithm := ecdsa}) -> unacceptable_ecdsa_key. diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl index b737b2880eb4..aed938eeaaf0 100644 --- a/lib/ssl/test/ssl_api_SUITE.erl +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -2556,7 +2556,17 @@ options_cert(Config) -> %% cert[file] cert_keys keys password [{key, {rsa, <<>>}}], client, Old), ?OK(#{certs_keys := [#{key := #{}}]}, [{key, #{engine => foo, algorithm => foo, key_id => foo}}], client, Old), - + ?OK(#{certs_keys := [#{key := #{}}]}, + [{key, #{algorithm => eddsa, sign_fun => fun(_,_,_,_) -> << "dummy signature">> end}}, + {versions, ['tlsv1.3']}], client, Old), + ?OK(#{certs_keys := [#{key := #{}}]}, + [{key, #{algorithm => ecdsa, sign_fun => fun(_,_,_,_) -> << "dummy signature">> end}}], + client, Old), + ?OK(#{certs_keys := [#{key := #{}}]}, + [{key, #{algorithm => rsa, + sign_fun => fun(_,_,_,_) -> << "dummy signature">> end, + encrypt_fun => fun(_,_,_) -> << "dummy encrypt">> end}}, + {versions, ['tlsv1.3', 'tlsv1.2', 'tlsv1.1']}], client, Old), ?OK(#{certs_keys := [#{password := _}]}, [{password, "foobar"}], client, Old), ?OK(#{certs_keys := [#{password := _}]}, [{password, <<"foobar">>}], client, Old), Pwd = fun() -> "foobar" end, @@ -2570,6 +2580,14 @@ options_cert(Config) -> %% cert[file] cert_keys keys password client, Old), %% Errors + ?ERR({options,incompatible,[key,{versions,['tlsv1.2']}]}, + [{key, #{algorithm => eddsa, sign_fun => fun(_,_,_,_) -> << "dummy signature">> end}}, + {versions, ['tlsv1.2']}], client), + ?ERR({options,incompatible,[key,{versions,['tlsv1.3','tlsv1.2']}]}, + [{key, #{algorithm => rsa, + sign_fun => fun(_,_,_,_) -> << "dummy signature">> end, + encrypt_fun => fun(_,_,_) -> << "dummy encrypt">> end + }}], client), ?ERR({cert, #{}}, [{cert, #{}}], client), ?ERR({certfile, cert}, [{certfile, cert}], client), ?ERR({certs_keys, #{}}, [{certs_keys, #{}}], client), diff --git a/lib/ssl/test/ssl_cert_SUITE.erl b/lib/ssl/test/ssl_cert_SUITE.erl index 28f8d06aa717..5f6007a1a05c 100644 --- a/lib/ssl/test/ssl_cert_SUITE.erl +++ b/lib/ssl/test/ssl_cert_SUITE.erl @@ -43,6 +43,8 @@ no_auth/1, auth/0, auth/1, + client_auth_custom_key/0, + client_auth_custom_key/1, client_auth_empty_cert_accepted/0, client_auth_empty_cert_accepted/1, client_auth_empty_cert_rejected/0, @@ -228,6 +230,7 @@ all_version_tests() -> [ no_auth, auth, + client_auth_custom_key, client_auth_empty_cert_accepted, client_auth_empty_cert_rejected, client_auth_use_partial_chain, @@ -459,6 +462,11 @@ auth() -> auth(Config) -> ssl_cert_tests:auth(Config). %%-------------------------------------------------------------------- +client_auth_custom_key() -> + ssl_cert_tests:client_auth_custom_key(). +client_auth_custom_key(Config) -> + ssl_cert_tests:client_auth_custom_key(Config). +%%-------------------------------------------------------------------- client_auth_empty_cert_accepted() -> ssl_cert_tests:client_auth_empty_cert_accepted(). client_auth_empty_cert_accepted(Config) -> diff --git a/lib/ssl/test/ssl_cert_tests.erl b/lib/ssl/test/ssl_cert_tests.erl index 2749d161943e..d2e5e76e27ad 100644 --- a/lib/ssl/test/ssl_cert_tests.erl +++ b/lib/ssl/test/ssl_cert_tests.erl @@ -29,6 +29,8 @@ no_auth/1, auth/0, auth/1, + client_auth_custom_key/0, + client_auth_custom_key/1, client_auth_empty_cert_accepted/0, client_auth_empty_cert_accepted/1, client_auth_empty_cert_rejected/0, @@ -87,18 +89,46 @@ auth() -> auth(Config) -> Version = proplists:get_value(version,Config), + CommonClientOpts = [{verify, verify_peer} | ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config)], ClientOpts = case Version of 'tlsv1.3' -> - [{verify, verify_peer}, - {certificate_authorities, true} | - ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config)]; + [{certificate_authorities, true} | CommonClientOpts]; _ -> - [{verify, verify_peer} | ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config)] + CommonClientOpts end, ServerOpts = [{verify, verify_peer} | ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config)], ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). %%-------------------------------------------------------------------- +client_auth_custom_key() -> + [{doc,"Test that client and server can connect using their own signature function"}]. + +client_auth_custom_key(Config) when is_list(Config) -> + Version = proplists:get_value(version,Config), + CommonClientOpts = [{verify, verify_peer} | ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config)], + ClientOpts0 = case Version of + 'tlsv1.3' -> + [{certificate_authorities, true} | CommonClientOpts]; + _ -> + CommonClientOpts + end, + ClientKeyFilePath = proplists:get_value(keyfile, ClientOpts0), + [ClientKeyEntry] = ssl_test_lib:pem_to_der(ClientKeyFilePath), + ClientKey = ssl_test_lib:public_key(public_key:pem_entry_decode(ClientKeyEntry)), + ClientCustomKey = choose_custom_key(ClientKey, Version), + + ClientOpts = [ ClientCustomKey | proplists:delete(key, proplists:delete(keyfile, ClientOpts0))], + + ServerOpts0 = ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config), + ServerKeyFilePath = proplists:get_value(keyfile, ServerOpts0), + [ServerKeyEntry] = ssl_test_lib:pem_to_der(ServerKeyFilePath), + ServerKey = ssl_test_lib:public_key(public_key:pem_entry_decode(ServerKeyEntry)), + ServerCustomKey = choose_custom_key(ServerKey, Version), + + ServerOpts = [ ServerCustomKey, {verify, verify_peer} | ServerOpts0], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- client_auth_empty_cert_accepted() -> [{doc,"Client sends empty cert chain as no cert is configured and server allows it"}]. @@ -517,3 +547,30 @@ group_config(Config, ServerOpts, ClientOpts) -> {[{supported_groups, [x448, x25519]} | ServerOpts], [{groups,"P-256:X25519"} | ClientOpts]} end. + +choose_custom_key(#'RSAPrivateKey'{} = Key, Version) + when (Version == 'dtlsv1') or (Version == 'tlsv1') or (Version == 'tlsv1.1') -> + EFun = fun (PlainText, Options) -> + public_key:encrypt_private(PlainText, Key, Options) + end, + SFun = fun (Msg, HashAlgo, Options) -> + public_key:sign(Msg, HashAlgo, Key, Options) + end, + {key, #{algorithm => rsa, sign_fun => SFun, encrypt_fun => EFun}}; +choose_custom_key(Key, _) -> + Fun = fun (Msg, HashAlgo, Options) -> + public_key:sign(Msg, HashAlgo, Key, Options) + end, + {key, #{algorithm => alg_key(Key), sign_fun => Fun}}. + +alg_key(#'RSAPrivateKey'{}) -> + rsa; +alg_key({#'RSAPrivateKey'{}, #'RSASSA-PSS-params'{}}) -> + rsa_pss_pss; +alg_key(#'DSAPrivateKey'{}) -> + dsa; +alg_key(#'ECPrivateKey'{parameters = {namedCurve, CurveOId}}) when CurveOId == ?'id-Ed25519' orelse + CurveOId == ?'id-Ed448' -> + eddsa; +alg_key(#'ECPrivateKey'{}) -> + ecdsa.