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.