aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src/ssl.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src/ssl.erl')
-rw-r--r--lib/ssl/src/ssl.erl366
1 files changed, 221 insertions, 145 deletions
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 6e26f05c3d..7b1fda4cf9 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2010. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -49,10 +49,30 @@
inet_ssl, %% inet options for internal ssl socket
cb %% Callback info
}).
-
-%%--------------------------------------------------------------------
--spec start() -> ok.
--spec start(permanent | transient | temporary) -> ok.
+-type option() :: socketoption() | ssloption() | transportoption().
+-type socketoption() :: [{property(), term()}]. %% See gen_tcp and inet
+-type property() :: atom().
+-type ssloption() :: {verify, verify_type()} |
+ {verify_fun, {fun(), InitialUserState::term()}} |
+ {fail_if_no_peer_cert, boolean()} | {depth, integer()} |
+ {cert, Der::binary()} | {certfile, path()} | {key, Der::binary()} |
+ {keyfile, path()} | {password, string()} | {cacerts, [Der::binary()]} |
+ {cacertfile, path()} | {dh, Der::binary()} | {dhfile, path()} |
+ {ciphers, ciphers()} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} |
+ {reuse_session, fun()} | {hibernate_after, integer()|undefined}.
+
+-type verify_type() :: verify_none | verify_peer.
+-type path() :: string().
+-type ciphers() :: [erl_cipher_suite()] |
+ string(). % (according to old API)
+-type ssl_imp() :: new | old.
+
+-type transportoption() :: {CallbackModule::atom(), DataTag::atom(), ClosedTag::atom()}.
+
+
+%%--------------------------------------------------------------------
+-spec start() -> ok | {error, reason()}.
+-spec start(permanent | transient | temporary) -> ok | {error, reason()}.
%%
%% Description: Utility function that starts the ssl,
%% crypto and public_key applications. Default type
@@ -77,9 +97,13 @@ stop() ->
application:stop(ssl).
%%--------------------------------------------------------------------
--spec connect(host() | port(), list()) -> {ok, #sslsocket{}}.
--spec connect(host() | port(), list() | port_num(), timeout() | list()) -> {ok, #sslsocket{}}.
--spec connect(host() | port(), port_num(), list(), timeout()) -> {ok, #sslsocket{}}.
+-spec connect(host() | port(), [option()]) -> {ok, #sslsocket{}} |
+ {error, reason()}.
+-spec connect(host() | port(), [option()] | port_num(), timeout() | list()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+-spec connect(host() | port(), port_num(), list(), timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+
%%
%% Description: Connect to a ssl server.
%%--------------------------------------------------------------------
@@ -125,7 +149,7 @@ connect(Host, Port, Options0, Timeout) ->
end.
%%--------------------------------------------------------------------
--spec listen(port_num(), list()) ->{ok, #sslsocket{}} | {error, reason()}.
+-spec listen(port_num(), [option()]) ->{ok, #sslsocket{}} | {error, reason()}.
%%
%% Description: Creates a ssl listen socket.
@@ -149,8 +173,10 @@ listen(Port, Options0) ->
end.
%%--------------------------------------------------------------------
--spec transport_accept(#sslsocket{}) -> {ok, #sslsocket{}}.
--spec transport_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}}.
+-spec transport_accept(#sslsocket{}) -> {ok, #sslsocket{}} |
+ {error, reason()}.
+-spec transport_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
+ {error, reason()}.
%%
%% Description: Performs transport accept on a ssl listen socket
%%--------------------------------------------------------------------
@@ -188,8 +214,10 @@ transport_accept(#sslsocket{} = ListenSocket, Timeout) ->
ssl_broker:transport_accept(Pid, ListenSocket, Timeout).
%%--------------------------------------------------------------------
--spec ssl_accept(#sslsocket{}) -> {ok, #sslsocket{}} | {error, reason()}.
--spec ssl_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | {error, reason()}.
+-spec ssl_accept(#sslsocket{}) -> ok | {error, reason()}.
+-spec ssl_accept(#sslsocket{} | port(), timeout()| [option()]) ->
+ ok | {ok, #sslsocket{}} | {error, reason()}.
+-spec ssl_accept(port(), [option()], timeout()) -> {ok, #sslsocket{}} | {error, reason()}.
%%
%% Description: Performs accept on a ssl listen socket. e.i. performs
%% ssl handshake.
@@ -375,7 +403,7 @@ cipher_suites(openssl) ->
[ssl_cipher:openssl_suite_name(S) || S <- ssl_cipher:suites(Version)].
%%--------------------------------------------------------------------
--spec getopts(#sslsocket{}, [atom()]) -> {ok, [{atom(), term()}]}| {error, reason()}.
+-spec getopts(#sslsocket{}, [atom()]) -> {ok, [{atom(), term()}]}| {error, reason()}.
%%
%% Description:
%%--------------------------------------------------------------------
@@ -462,11 +490,102 @@ versions() ->
%%---------------------------------------------------------------
-spec renegotiate(#sslsocket{}) -> ok | {error, reason()}.
%%
-%% Description:
+%% Description: Initiates a renegotiation.
%%--------------------------------------------------------------------
renegotiate(#sslsocket{pid = Pid, fd = new_ssl}) ->
ssl_connection:renegotiation(Pid).
+%%---------------------------------------------------------------
+-spec format_error({error, term()}) -> list().
+%%
+%% Description: Creates error string.
+%%--------------------------------------------------------------------
+format_error({error, Reason}) ->
+ format_error(Reason);
+format_error(Reason) when is_list(Reason) ->
+ Reason;
+format_error(closed) ->
+ "The connection is closed";
+format_error(ecacertfile) ->
+ "Own CA certificate file is invalid.";
+format_error(ecertfile) ->
+ "Own certificate file is invalid.";
+format_error(ekeyfile) ->
+ "Own private key file is invalid.";
+format_error(esslaccept) ->
+ "Server SSL handshake procedure between client and server failed.";
+format_error(esslconnect) ->
+ "Client SSL handshake procedure between client and server failed.";
+format_error({eoptions, Options}) ->
+ lists:flatten(io_lib:format("Error in options list: ~p~n", [Options]));
+
+%%%%%%%%%%%% START OLD SSL format_error %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+format_error(ebadsocket) ->
+ "Connection not found (internal error).";
+format_error(ebadstate) ->
+ "Connection not in connect state (internal error).";
+format_error(ebrokertype) ->
+ "Wrong broker type (internal error).";
+format_error(echaintoolong) ->
+ "The chain of certificates provided by peer is too long.";
+format_error(ecipher) ->
+ "Own list of specified ciphers is invalid.";
+format_error(ekeymismatch) ->
+ "Own private key does not match own certificate.";
+format_error(enoissuercert) ->
+ "Cannot find certificate of issuer of certificate provided by peer.";
+format_error(enoservercert) ->
+ "Attempt to do accept without having set own certificate.";
+format_error(enotlistener) ->
+ "Attempt to accept on a non-listening socket.";
+format_error(enoproxysocket) ->
+ "No proxy socket found (internal error or max number of file "
+ "descriptors exceeded).";
+format_error(enooptions) ->
+ "List of options is empty.";
+format_error(enotstarted) ->
+ "The SSL application has not been started.";
+format_error(eoptions) ->
+ "Invalid list of options.";
+format_error(epeercert) ->
+ "Certificate provided by peer is in error.";
+format_error(epeercertexpired) ->
+ "Certificate provided by peer has expired.";
+format_error(epeercertinvalid) ->
+ "Certificate provided by peer is invalid.";
+format_error(eselfsignedcert) ->
+ "Certificate provided by peer is self signed.";
+format_error(esslerrssl) ->
+ "SSL protocol failure. Typically because of a fatal alert from peer.";
+format_error(ewantconnect) ->
+ "Protocol wants to connect, which is not supported in this "
+ "version of the SSL application.";
+format_error(ex509lookup) ->
+ "Protocol wants X.509 lookup, which is not supported in this "
+ "version of the SSL application.";
+format_error({badcall, _Call}) ->
+ "Call not recognized for current mode (active or passive) and state "
+ "of socket.";
+format_error({badcast, _Cast}) ->
+ "Call not recognized for current mode (active or passive) and state "
+ "of socket.";
+
+format_error({badinfo, _Info}) ->
+ "Call not recognized for current mode (active or passive) and state "
+ "of socket.";
+
+%%%%%%%%%%%%%%%%%% END OLD SSL format_error %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+format_error(Error) ->
+ case (catch inet:format_error(Error)) of
+ "unkknown POSIX" ++ _ ->
+ no_format(Error);
+ {'EXIT', _} ->
+ no_format(Error);
+ Other ->
+ Other
+ end.
+
%%%--------------------------------------------------------------
%%% Internal functions
%%%--------------------------------------------------------------------
@@ -522,59 +641,68 @@ old_listen(Port, Options) ->
{ok, Pid} = ssl_broker:start_broker(listener),
ssl_broker:listen(Pid, Port, Options).
-handle_options(Opts0, Role) ->
+handle_options(Opts0, _Role) ->
Opts = proplists:expand([{binary, [{mode, binary}]},
{list, [{mode, list}]}], Opts0),
ReuseSessionFun = fun(_, _, _, _) -> true end,
- AcceptBadCa = fun({bad_cert,unknown_ca}, Acc) -> Acc;
- (Other, Acc) -> [Other | Acc]
- end,
-
- VerifyFun =
- fun(ErrorList) ->
- case lists:foldl(AcceptBadCa, [], ErrorList) of
- [] -> true;
- [_|_] -> false
- end
- end,
+ DefaultVerifyNoneFun =
+ {fun(_,{bad_cert, _}, UserState) ->
+ {valid, UserState};
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState};
+ (_, valid, UserState) ->
+ {valid, UserState};
+ (_, valid_peer, UserState) ->
+ {valid, UserState}
+ end, []},
- UserFailIfNoPeerCert = validate_option(fail_if_no_peer_cert,
- proplists:get_value(fail_if_no_peer_cert, Opts, false)),
+ VerifyNoneFun = handle_option(verify_fun, Opts, DefaultVerifyNoneFun),
- {Verify, FailIfNoPeerCert, CaCertDefault} =
+ UserFailIfNoPeerCert = handle_option(fail_if_no_peer_cert, Opts, false),
+ UserVerifyFun = handle_option(verify_fun, Opts, undefined),
+ CaCerts = handle_option(cacerts, Opts, undefined),
+
+ {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun} =
%% Handle 0, 1, 2 for backwards compatibility
case proplists:get_value(verify, Opts, verify_none) of
0 ->
- {verify_none, false, ca_cert_default(verify_none, Role)};
+ {verify_none, false,
+ ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun};
1 ->
- {verify_peer, false, ca_cert_default(verify_peer, Role)};
+ {verify_peer, false,
+ ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
2 ->
- {verify_peer, true, ca_cert_default(verify_peer, Role)};
+ {verify_peer, true,
+ ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
verify_none ->
- {verify_none, false, ca_cert_default(verify_none, Role)};
+ {verify_none, false,
+ ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun};
verify_peer ->
- {verify_peer, UserFailIfNoPeerCert, ca_cert_default(verify_peer, Role)};
+ {verify_peer, UserFailIfNoPeerCert,
+ ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
Value ->
throw({error, {eoptions, {verify, Value}}})
- end,
+ end,
CertFile = handle_option(certfile, Opts, ""),
SSLOptions = #ssl_options{
versions = handle_option(versions, Opts, []),
verify = validate_option(verify, Verify),
- verify_fun = handle_option(verify_fun, Opts, VerifyFun),
+ verify_fun = VerifyFun,
fail_if_no_peer_cert = FailIfNoPeerCert,
verify_client_once = handle_option(verify_client_once, Opts, false),
- validate_extensions_fun = handle_option(validate_extensions_fun, Opts, undefined),
depth = handle_option(depth, Opts, 1),
+ cert = handle_option(cert, Opts, undefined),
certfile = CertFile,
- keyfile = handle_option(keyfile, Opts, CertFile),
key = handle_option(key, Opts, undefined),
+ keyfile = handle_option(keyfile, Opts, CertFile),
password = handle_option(password, Opts, ""),
+ cacerts = CaCerts,
cacertfile = handle_option(cacertfile, Opts, CaCertDefault),
+ dh = handle_option(dh, Opts, undefined),
dhfile = handle_option(dhfile, Opts, undefined),
ciphers = handle_option(ciphers, Opts, []),
%% Server side option
@@ -582,16 +710,17 @@ handle_options(Opts0, Role) ->
reuse_sessions = handle_option(reuse_sessions, Opts, true),
secure_renegotiate = handle_option(secure_renegotiate, Opts, false),
renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT),
- debug = handle_option(debug, Opts, [])
+ debug = handle_option(debug, Opts, []),
+ hibernate_after = handle_option(hibernate_after, Opts, undefined)
},
CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}),
- SslOptions = [versions, verify, verify_fun, validate_extensions_fun,
+ SslOptions = [versions, verify, verify_fun,
fail_if_no_peer_cert, verify_client_once,
- depth, certfile, keyfile,
- key, password, cacertfile, dhfile, ciphers,
+ depth, cert, certfile, key, keyfile,
+ password, cacerts, cacertfile, dh, dhfile, ciphers,
debug, reuse_session, reuse_sessions, ssl_imp,
- cb_info, renegotiate_at, secure_renegotiate],
+ cb_info, renegotiate_at, secure_renegotiate, hibernate_after],
SockOpts = lists:foldl(fun(Key, PropList) ->
proplists:delete(Key, PropList)
@@ -613,7 +742,25 @@ validate_option(ssl_imp, Value) when Value == new; Value == old ->
validate_option(verify, Value)
when Value == verify_none; Value == verify_peer ->
Value;
-validate_option(verify_fun, Value) when is_function(Value) ->
+validate_option(verify_fun, undefined) ->
+ undefined;
+%% Backwards compatibility
+validate_option(verify_fun, Fun) when is_function(Fun) ->
+ {fun(_,{bad_cert, _} = Reason, OldFun) ->
+ case OldFun([Reason]) of
+ true ->
+ {valid, OldFun};
+ false ->
+ {fail, Reason}
+ end;
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState};
+ (_, valid, UserState) ->
+ {valid, UserState};
+ (_, valid_peer, UserState) ->
+ {valid, UserState}
+ end, Fun};
+validate_option(verify_fun, {Fun, _} = Value) when is_function(Fun) ->
Value;
validate_option(fail_if_no_peer_cert, Value)
when Value == true; Value == false ->
@@ -621,29 +768,38 @@ validate_option(fail_if_no_peer_cert, Value)
validate_option(verify_client_once, Value)
when Value == true; Value == false ->
Value;
-
-validate_option(validate_extensions_fun, Value) when Value == undefined; is_function(Value) ->
- Value;
validate_option(depth, Value) when is_integer(Value),
Value >= 0, Value =< 255->
Value;
-validate_option(certfile, Value) when is_list(Value) ->
+validate_option(cert, Value) when Value == undefined;
+ is_binary(Value) ->
Value;
-validate_option(keyfile, Value) when is_list(Value) ->
+validate_option(certfile, Value) when Value == undefined; is_list(Value) ->
Value;
-validate_option(key, Value) when Value == undefined;
- is_tuple(Value) ->
- %% element(1, Value)=='RSAPrivateKey' ->
+
+validate_option(key, undefined) ->
+ undefined;
+validate_option(key, {KeyType, Value}) when is_binary(Value),
+ KeyType == rsa;
+ KeyType == dsa ->
+ {KeyType, Value};
+validate_option(keyfile, Value) when is_list(Value) ->
Value;
validate_option(password, Value) when is_list(Value) ->
Value;
+validate_option(cacerts, Value) when Value == undefined;
+ is_list(Value) ->
+ Value;
%% certfile must be present in some cases otherwhise it can be set
%% to the empty string.
validate_option(cacertfile, undefined) ->
"";
validate_option(cacertfile, Value) when is_list(Value), Value =/= "" ->
Value;
+validate_option(dh, Value) when Value == undefined;
+ is_binary(Value) ->
+ Value;
validate_option(dhfile, undefined = Value) ->
Value;
validate_option(dhfile, Value) when is_list(Value), Value =/= "" ->
@@ -671,6 +827,10 @@ validate_option(renegotiate_at, Value) when is_integer(Value) ->
validate_option(debug, Value) when is_list(Value); Value == true ->
Value;
+validate_option(hibernate_after, undefined) ->
+ undefined;
+validate_option(hibernate_after, Value) when is_integer(Value), Value >= 0 ->
+ Value;
validate_option(Opt, Value) ->
throw({error, {eoptions, {Opt, Value}}}).
@@ -701,14 +861,16 @@ validate_inet_option(active, Value)
validate_inet_option(_, _) ->
ok.
-ca_cert_default(verify_none, _) ->
+%% The option cacerts overrides cacertsfile
+ca_cert_default(_,_, [_|_]) ->
+ undefined;
+ca_cert_default(verify_none, _, _) ->
undefined;
-%% Client may leave verification up to the user
-ca_cert_default(verify_peer, client) ->
+ca_cert_default(verify_peer, {Fun,_}, _) when is_function(Fun) ->
undefined;
-%% Server that wants to verify_peer must have
+%% Server that wants to verify_peer and has no verify_fun must have
%% some trusted certs.
-ca_cert_default(verify_peer, server) ->
+ca_cert_default(verify_peer, undefined, _) ->
"".
emulated_options() ->
@@ -759,7 +921,7 @@ cipher_suites(Version, [{_,_,_}| _] = Ciphers0) ->
Ciphers = [ssl_cipher:suite(C) || C <- Ciphers0],
cipher_suites(Version, Ciphers);
cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) ->
- Supported = ssl_cipher:suites(Version),
+ Supported = ssl_cipher:suites(Version) ++ ssl_cipher:anonymous_suites(),
case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, Supported)] of
[] ->
Supported;
@@ -775,92 +937,6 @@ cipher_suites(Version, Ciphers0) ->
Ciphers = [ssl_cipher:openssl_suite(C) || C <- string:tokens(Ciphers0, ":")],
cipher_suites(Version, Ciphers).
-format_error({error, Reason}) ->
- format_error(Reason);
-format_error(Reason) when is_list(Reason) ->
- Reason;
-format_error(closed) ->
- "The connection is closed";
-format_error(ecacertfile) ->
- "Own CA certificate file is invalid.";
-format_error(ecertfile) ->
- "Own certificate file is invalid.";
-format_error(ekeyfile) ->
- "Own private key file is invalid.";
-format_error(esslaccept) ->
- "Server SSL handshake procedure between client and server failed.";
-format_error(esslconnect) ->
- "Client SSL handshake procedure between client and server failed.";
-format_error({eoptions, Options}) ->
- lists:flatten(io_lib:format("Error in options list: ~p~n", [Options]));
-
-%%%%%%%%%%%% START OLD SSL format_error %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-format_error(ebadsocket) ->
- "Connection not found (internal error).";
-format_error(ebadstate) ->
- "Connection not in connect state (internal error).";
-format_error(ebrokertype) ->
- "Wrong broker type (internal error).";
-format_error(echaintoolong) ->
- "The chain of certificates provided by peer is too long.";
-format_error(ecipher) ->
- "Own list of specified ciphers is invalid.";
-format_error(ekeymismatch) ->
- "Own private key does not match own certificate.";
-format_error(enoissuercert) ->
- "Cannot find certificate of issuer of certificate provided by peer.";
-format_error(enoservercert) ->
- "Attempt to do accept without having set own certificate.";
-format_error(enotlistener) ->
- "Attempt to accept on a non-listening socket.";
-format_error(enoproxysocket) ->
- "No proxy socket found (internal error or max number of file "
- "descriptors exceeded).";
-format_error(enooptions) ->
- "List of options is empty.";
-format_error(enotstarted) ->
- "The SSL application has not been started.";
-format_error(eoptions) ->
- "Invalid list of options.";
-format_error(epeercert) ->
- "Certificate provided by peer is in error.";
-format_error(epeercertexpired) ->
- "Certificate provided by peer has expired.";
-format_error(epeercertinvalid) ->
- "Certificate provided by peer is invalid.";
-format_error(eselfsignedcert) ->
- "Certificate provided by peer is self signed.";
-format_error(esslerrssl) ->
- "SSL protocol failure. Typically because of a fatal alert from peer.";
-format_error(ewantconnect) ->
- "Protocol wants to connect, which is not supported in this "
- "version of the SSL application.";
-format_error(ex509lookup) ->
- "Protocol wants X.509 lookup, which is not supported in this "
- "version of the SSL application.";
-format_error({badcall, _Call}) ->
- "Call not recognized for current mode (active or passive) and state "
- "of socket.";
-format_error({badcast, _Cast}) ->
- "Call not recognized for current mode (active or passive) and state "
- "of socket.";
-
-format_error({badinfo, _Info}) ->
- "Call not recognized for current mode (active or passive) and state "
- "of socket.";
-
-%%%%%%%%%%%%%%%%%% END OLD SSL format_error %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-format_error(Error) ->
- case (catch inet:format_error(Error)) of
- "unkknown POSIX" ++ _ ->
- no_format(Error);
- {'EXIT', _} ->
- no_format(Error);
- Other ->
- Other
- end.
-
no_format(Error) ->
lists:flatten(io_lib:format("No format string for error: \"~p\" available.", [Error])).