aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIngela Anderton Andin <[email protected]>2017-11-10 14:48:42 +0100
committerIngela Anderton Andin <[email protected]>2017-11-10 14:48:42 +0100
commite21f3eb30a384fab1db839ac6a21a6f2055cfb6f (patch)
tree0ff2f3894d3e8e0b372c9eb22b05fc2c23199788
parent4d9d6776aadee9e0d27b34460fa5269a6b6988aa (diff)
parent7a0a2e9fa132cba32f4a287d03c04e9ff78a44ec (diff)
downloadotp-e21f3eb30a384fab1db839ac6a21a6f2055cfb6f.tar.gz
otp-e21f3eb30a384fab1db839ac6a21a6f2055cfb6f.tar.bz2
otp-e21f3eb30a384fab1db839ac6a21a6f2055cfb6f.zip
Merge branch 'ingela/ssl/engine-API/OTP-14448' into maint
* ingela/ssl/engine-API/OTP-14448: ssl: Add private key configuration for crypto engine
-rw-r--r--lib/ssl/doc/src/ssl.xml12
-rw-r--r--lib/ssl/src/ssl.erl3
-rw-r--r--lib/ssl/src/ssl_config.erl10
-rw-r--r--lib/ssl/src/ssl_handshake.erl12
-rw-r--r--lib/ssl/src/ssl_internal.hrl13
-rw-r--r--lib/ssl/test/Makefile1
-rw-r--r--lib/ssl/test/ssl_engine_SUITE.erl142
7 files changed, 183 insertions, 10 deletions
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index e80fd59a7f..bda8e8ec7d 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -69,7 +69,9 @@
<p><c>| {cert, public_key:der_encoded()}</c></p>
<p><c>| {certfile, path()}</c></p>
<p><c>| {key, {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey'
- | 'PrivateKeyInfo', public_key:der_encoded()}}</c></p>
+ | 'PrivateKeyInfo', public_key:der_encoded()} |
+ #{algorithm := rsa | dss | ecdsa,
+ engine := crypto:engine_ref(), key_id := crypto:key_id(), password => crypto:password()}</c></p>
<p><c>| {keyfile, path()}</c></p>
<p><c>| {password, string()}</c></p>
<p><c>| {cacerts, [public_key:der_encoded()]}</c></p>
@@ -202,8 +204,12 @@
<item><p>Path to a file containing the user certificate.</p></item>
<tag><c>{key, {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey'
- |'PrivateKeyInfo', public_key:der_encoded()}}</c></tag>
- <item><p>The DER-encoded user's private key. If this option
+ |'PrivateKeyInfo', public_key:der_encoded()} | #{algorithm := rsa | dss | ecdsa,
+ engine := crypto:engine_ref(), key_id := crypto:key_id(), password => crypto:password()}</c></tag>
+ <item><p>The DER-encoded user's private key or a map refering to a crypto
+ engine and its key reference that optionally can be password protected,
+ seealso <seealso marker="crypto:engine_load-4"> crypto:engine_load/4
+ </seealso> and <seealso marker="crypto:engine_load"> Crypto's Users Guide</seealso>. If this option
is supplied, it overrides option <c>keyfile</c>.</p></item>
<tag><c>{keyfile, path()}</c></tag>
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 60118549e4..4007e44a83 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -896,7 +896,8 @@ validate_option(key, {KeyType, Value}) when is_binary(Value),
KeyType == 'ECPrivateKey';
KeyType == 'PrivateKeyInfo' ->
{KeyType, Value};
-
+validate_option(key, #{algorithm := _} = Value) ->
+ Value;
validate_option(keyfile, undefined) ->
<<>>;
validate_option(keyfile, Value) when is_binary(Value) ->
diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl
index e4611995ec..022fb7eac0 100644
--- a/lib/ssl/src/ssl_config.erl
+++ b/lib/ssl/src/ssl_config.erl
@@ -91,7 +91,15 @@ init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, server
end;
init_certificates(Cert, Config, _, _) ->
{ok, Config#{own_certificate => Cert}}.
-
+init_private_key(_, #{algorithm := Alg} = Key, <<>>, _Password, _Client) when Alg == ecdsa;
+ Alg == rsa;
+ Alg == dss ->
+ case maps:is_key(engine, Key) andalso maps:is_key(key_id, Key) of
+ true ->
+ Key;
+ false ->
+ throw({key, {invalid_key_id, Key}})
+ end;
init_private_key(_, undefined, <<>>, _Password, _Client) ->
undefined;
init_private_key(DbHandle, undefined, KeyFile, Password, _) ->
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 0ee9ee3322..b47a11dc0d 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -1646,14 +1646,20 @@ digitally_signed(Version, Hashes, HashAlgo, PrivateKey) ->
error:badkey->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, bad_key(PrivateKey)))
end.
-
+do_digitally_signed({3, Minor}, Hash, HashAlgo, #{algorithm := Alg} = Engine)
+ when Minor >= 3 ->
+ crypto:sign(Alg, HashAlgo, {digest, Hash}, maps:remove(algorithm, Engine));
do_digitally_signed({3, Minor}, Hash, HashAlgo, Key) when Minor >= 3 ->
public_key:sign({digest, Hash}, HashAlgo, Key);
-do_digitally_signed(_Version, Hash, HashAlgo, #'DSAPrivateKey'{} = Key) ->
- public_key:sign({digest, Hash}, HashAlgo, Key);
do_digitally_signed(_Version, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key) ->
public_key:encrypt_private(Hash, Key,
[{rsa_pad, rsa_pkcs1_padding}]);
+do_digitally_signed({3, _}, Hash, _,
+ #{algorithm := rsa} = Engine) ->
+ crypto:private_encrypt(rsa, Hash, maps:remove(algorithm, Engine),
+ rsa_pkcs1_padding);
+do_digitally_signed({3, _}, Hash, HashAlgo, #{algorithm := Alg} = Engine) ->
+ crypto:sign(Alg, HashAlgo, {digest, Hash}, maps:remove(algorithm, Engine));
do_digitally_signed(_Version, Hash, HashAlgo, Key) ->
public_key:sign({digest, Hash}, HashAlgo, Key).
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index 24ac34653e..9bb1cbaeb0 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -95,7 +95,8 @@
certfile :: binary(),
cert :: public_key:der_encoded() | secret_printout() | 'undefined',
keyfile :: binary(),
- key :: {'RSAPrivateKey' | 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo', public_key:der_encoded()} | secret_printout() | 'undefined',
+ key :: {'RSAPrivateKey' | 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo',
+ public_key:der_encoded()} | key_map() | secret_printout() | 'undefined',
password :: string() | secret_printout() | 'undefined',
cacerts :: [public_key:der_encoded()] | secret_printout() | 'undefined',
cacertfile :: binary(),
@@ -164,7 +165,15 @@
connection_cb
}).
-
+-type key_map() :: #{algorithm := rsa | dss | ecdsa,
+ %% engine and key_id ought to
+ %% be :=, but putting it in
+ %% the spec gives dialyzer warning
+ %% of correct code!
+ engine => crypto:engine_ref(),
+ key_id => crypto:key_id(),
+ password => crypto:password()
+ }.
-type state_name() :: hello | abbreviated | certify | cipher | connection.
-type gen_fsm_state_return() :: {next_state, state_name(), term()} |
{next_state, state_name(), term(), timeout()} |
diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile
index c7e2f402af..aa01552c39 100644
--- a/lib/ssl/test/Makefile
+++ b/lib/ssl/test/Makefile
@@ -44,6 +44,7 @@ MODULES = \
ssl_certificate_verify_SUITE\
ssl_crl_SUITE\
ssl_dist_SUITE \
+ ssl_engine_SUITE\
ssl_handshake_SUITE \
ssl_npn_hello_SUITE \
ssl_npn_handshake_SUITE \
diff --git a/lib/ssl/test/ssl_engine_SUITE.erl b/lib/ssl/test/ssl_engine_SUITE.erl
new file mode 100644
index 0000000000..bc221d35fd
--- /dev/null
+++ b/lib/ssl/test/ssl_engine_SUITE.erl
@@ -0,0 +1,142 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2017-2017. 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%
+%%
+
+%%
+-module(ssl_engine_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+all() ->
+ [
+ private_key
+ ].
+
+init_per_suite(Config) ->
+ catch crypto:stop(),
+ try crypto:start() of
+ ok ->
+ ssl_test_lib:clean_start(),
+ case crypto:get_test_engine() of
+ {ok, EngineName} ->
+ try crypto:engine_load(<<"dynamic">>,
+ [{<<"SO_PATH">>, EngineName},
+ <<"LOAD">>],
+ []) of
+ {ok, Engine} ->
+ [{engine, Engine} |Config];
+ {error, Reason} ->
+ ct:pal("Reason ~p", [Reason]),
+ {skip, "No dynamic engine support"}
+ catch error:notsup ->
+ {skip, "No engine support in OpenSSL"}
+ end;
+ {error, notexist} ->
+ {skip, "Test engine not found"}
+ end
+ catch _:_ ->
+ {skip, "Crypto did not start"}
+ end.
+
+end_per_suite(Config) ->
+ Engine = proplists:get_value(engine, Config),
+ crypto:engine_unload(Engine),
+ ssl:stop(),
+ application:stop(crypto).
+
+
+init_per_testcase(_TestCase, Config) ->
+ ssl:stop(),
+ ssl:start(),
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
+ ct:timetrap({seconds, 10}),
+ Config.
+
+end_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+
+private_key(Config) when is_list(Config) ->
+ ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "client_engine"]),
+ ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "server_engine"]),
+ #{server_config := ServerConf,
+ client_config := ClientConf} = GenCertData =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}
+ ]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
+ [{server_config, FileServerConf},
+ {client_config, FileClientConf}] =
+ x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
+
+ Engine = proplists:get_value(engine, Config),
+
+ ClientKey = engine_key(FileClientConf),
+ ServerKey = engine_key(FileServerConf),
+
+ EngineClientConf = [{key, #{algorithm => rsa,
+ engine => Engine,
+ key_id => ClientKey}} | proplists:delete(key, ClientConf)],
+
+ EngineServerConf = [{key, #{algorithm => rsa,
+ engine => Engine,
+ key_id => ServerKey}} | proplists:delete(key, ServerConf)],
+ %% Test with engine
+ test_tls_connection(EngineServerConf, EngineClientConf, Config),
+ %% Test that sofware fallback is available
+ test_tls_connection(ServerConf, [{reuse_sessions, false} |ClientConf], Config).
+
+engine_key(Conf) ->
+ FileStr = proplists:get_value(keyfile, Conf),
+ list_to_binary(FileStr).
+
+
+test_tls_connection(ServerConf, ClientConf, Config) ->
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, [{verify, verify_peer}
+ | ServerConf]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, [{verify, verify_peer} | ClientConf]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).