aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl/src')
-rw-r--r--lib/ssl/src/Makefile142
-rw-r--r--lib/ssl/src/inet_ssl_dist.erl449
-rw-r--r--lib/ssl/src/ssl.app.src41
-rw-r--r--lib/ssl/src/ssl.appup.src21
-rw-r--r--lib/ssl/src/ssl.erl841
-rw-r--r--lib/ssl/src/ssl_alert.erl107
-rw-r--r--lib/ssl/src/ssl_alert.hrl97
-rw-r--r--lib/ssl/src/ssl_app.erl41
-rw-r--r--lib/ssl/src/ssl_base64.erl129
-rw-r--r--lib/ssl/src/ssl_broker.erl1188
-rw-r--r--lib/ssl/src/ssl_broker_int.hrl38
-rw-r--r--lib/ssl/src/ssl_broker_sup.erl46
-rw-r--r--lib/ssl/src/ssl_certificate.erl156
-rw-r--r--lib/ssl/src/ssl_certificate_db.erl219
-rw-r--r--lib/ssl/src/ssl_cipher.erl784
-rw-r--r--lib/ssl/src/ssl_cipher.hrl253
-rw-r--r--lib/ssl/src/ssl_connection.erl1704
-rw-r--r--lib/ssl/src/ssl_connection_sup.erl60
-rw-r--r--lib/ssl/src/ssl_debug.erl99
-rw-r--r--lib/ssl/src/ssl_debug.hrl39
-rw-r--r--lib/ssl/src/ssl_handshake.erl917
-rw-r--r--lib/ssl/src/ssl_handshake.hrl200
-rw-r--r--lib/ssl/src/ssl_int.hrl99
-rw-r--r--lib/ssl/src/ssl_internal.hrl91
-rw-r--r--lib/ssl/src/ssl_manager.erl340
-rw-r--r--lib/ssl/src/ssl_pem.erl147
-rw-r--r--lib/ssl/src/ssl_pkix.erl307
-rw-r--r--lib/ssl/src/ssl_pkix.hrl81
-rw-r--r--lib/ssl/src/ssl_prim.erl173
-rw-r--r--lib/ssl/src/ssl_record.erl577
-rw-r--r--lib/ssl/src/ssl_record.hrl170
-rw-r--r--lib/ssl/src/ssl_server.erl1378
-rw-r--r--lib/ssl/src/ssl_session.erl172
-rw-r--r--lib/ssl/src/ssl_session_cache.erl124
-rw-r--r--lib/ssl/src/ssl_session_cache_api.erl37
-rw-r--r--lib/ssl/src/ssl_ssl2.erl37
-rw-r--r--lib/ssl/src/ssl_ssl3.erl286
-rw-r--r--lib/ssl/src/ssl_sup.erl100
-rw-r--r--lib/ssl/src/ssl_tls1.erl251
39 files changed, 11941 insertions, 0 deletions
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
new file mode 100644
index 0000000000..fabf8a4e0d
--- /dev/null
+++ b/lib/ssl/src/Makefile
@@ -0,0 +1,142 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1999-2009. 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
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+
+#
+
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Application version
+# ----------------------------------------------------
+include ../vsn.mk
+VSN=$(SSL_VSN)
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/ssl-$(VSN)
+
+# ----------------------------------------------------
+# Common Macros
+# ----------------------------------------------------
+
+MODULES= \
+ ssl \
+ ssl_alert \
+ ssl_app \
+ ssl_broker \
+ ssl_broker_sup \
+ ssl_server \
+ ssl_sup \
+ ssl_prim \
+ ssl_pkix \
+ ssl_pem \
+ ssl_base64 \
+ inet_ssl_dist \
+ ssl_certificate\
+ ssl_certificate_db\
+ ssl_cipher \
+ ssl_connection \
+ ssl_connection_sup \
+ ssl_debug \
+ ssl_handshake \
+ ssl_manager \
+ ssl_session \
+ ssl_session_cache_api \
+ ssl_session_cache \
+ ssl_record \
+ ssl_ssl2 \
+ ssl_ssl3 \
+ ssl_tls1 \
+
+INTERNAL_HRL_FILES = \
+ ssl_int.hrl ssl_broker_int.hrl ssl_debug.hrl \
+ ssl_alert.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_internal.hrl \
+ ssl_record.hrl
+
+PUBLIC_HRL_FILES = ssl_pkix.hrl
+
+ERL_FILES= $(MODULES:%=%.erl)
+
+TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+
+APP_FILE= ssl.app
+APPUP_FILE= ssl.appup
+
+APP_SRC= $(APP_FILE).src
+APP_TARGET= $(EBIN)/$(APP_FILE)
+APPUP_SRC= $(APPUP_FILE).src
+APPUP_TARGET= $(EBIN)/$(APPUP_FILE)
+
+INCLUDE = ../include
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+EXTRA_ERLC_FLAGS = +warn_unused_vars
+ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \
+ -pz $(ERL_TOP)/lib/public_key/ebin \
+ -I$(INCLUDE) \
+ $(EXTRA_ERLC_FLAGS) -DVSN=\"$(VSN)\"
+
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(PUBLIC_HRL_FILES)
+
+clean:
+ rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET)
+ rm -f errs core *~
+
+$(APP_TARGET): $(APP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+$(PUBLIC_HRL_FILES):
+ cp -f $(PUBLIC_HRL_FILES) $(INCLUDE)
+
+docs:
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)/src
+ $(INSTALL_DATA) $(ERL_FILES) $(INTERNAL_HRL_FILES) $(RELSYSDIR)/src
+ $(INSTALL_DIR) $(RELSYSDIR)/include
+ $(INSTALL_DATA) $(PUBLIC_HRL_FILES) $(RELSYSDIR)/include
+ $(INSTALL_DIR) $(RELSYSDIR)/ebin
+ $(INSTALL_DATA) $(TARGET_FILES) $(APP_TARGET) \
+ $(APPUP_TARGET) $(RELSYSDIR)/ebin
+
+release_docs_spec:
+
+
+
+
+
+
+
diff --git a/lib/ssl/src/inet_ssl_dist.erl b/lib/ssl/src/inet_ssl_dist.erl
new file mode 100644
index 0000000000..f62aefd35a
--- /dev/null
+++ b/lib/ssl/src/inet_ssl_dist.erl
@@ -0,0 +1,449 @@
+%%<copyright>
+%% <year>2000-2008</year>
+%% <holder>Ericsson AB, All Rights Reserved</holder>
+%%</copyright>
+%%<legalnotice>
+%% 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% The Initial Developer of the Original Code is Ericsson AB.
+%%</legalnotice>
+%%
+-module(inet_ssl_dist).
+
+%% Handles the connection setup phase with other Erlang nodes.
+
+-export([childspecs/0, listen/1, accept/1, accept_connection/5,
+ setup/5, close/1, select/1, is_node_name/1]).
+
+%% internal exports
+
+-export([accept_loop/2,do_accept/6,do_setup/6, getstat/1,tick/1]).
+
+-import(error_logger,[error_msg/2]).
+
+-include("net_address.hrl").
+
+
+
+-define(to_port(Socket, Data, Opts),
+ case ssl_prim:send(Socket, Data, Opts) of
+ {error, closed} ->
+ self() ! {ssl_closed, Socket},
+ {error, closed};
+ R ->
+ R
+ end).
+
+
+-include("dist.hrl").
+-include("dist_util.hrl").
+
+%% -------------------------------------------------------------
+%% This function should return a valid childspec, so that
+%% the primitive ssl_server gets supervised
+%% -------------------------------------------------------------
+childspecs() ->
+ {ok, [{ssl_server_prim,{ssl_server, start_link_prim, []},
+ permanent, 2000, worker, [ssl_server]}]}.
+
+
+%% ------------------------------------------------------------
+%% Select this protocol based on node name
+%% select(Node) => Bool
+%% ------------------------------------------------------------
+
+select(Node) ->
+ case split_node(atom_to_list(Node), $@, []) of
+ [_,_Host] -> true;
+ _ -> false
+ end.
+
+%% ------------------------------------------------------------
+%% Create the listen socket, i.e. the port that this erlang
+%% node is accessible through.
+%% ------------------------------------------------------------
+
+listen(Name) ->
+ case ssl_prim:listen(0, [{active, false}, {packet,4}] ++
+ get_ssl_options(server)) of
+ {ok, Socket} ->
+ TcpAddress = get_tcp_address(Socket),
+ {_,Port} = TcpAddress#net_address.address,
+ {ok, Creation} = erl_epmd:register_node(Name, Port),
+ {ok, {Socket, TcpAddress, Creation}};
+ Error ->
+ Error
+ end.
+
+%% ------------------------------------------------------------
+%% Accepts new connection attempts from other Erlang nodes.
+%% ------------------------------------------------------------
+
+accept(Listen) ->
+ spawn_link(?MODULE, accept_loop, [self(), Listen]).
+
+accept_loop(Kernel, Listen) ->
+ process_flag(priority, max),
+ case ssl_prim:accept(Listen) of
+ {ok, Socket} ->
+ Kernel ! {accept,self(),Socket,inet,ssl},
+ controller(Kernel, Socket),
+ accept_loop(Kernel, Listen);
+ Error ->
+ exit(Error)
+ end.
+
+controller(Kernel, Socket) ->
+ receive
+ {Kernel, controller, Pid} ->
+ flush_controller(Pid, Socket),
+ ssl_prim:controlling_process(Socket, Pid),
+ flush_controller(Pid, Socket),
+ Pid ! {self(), controller};
+ {Kernel, unsupported_protocol} ->
+ exit(unsupported_protocol)
+ end.
+
+flush_controller(Pid, Socket) ->
+ receive
+ {ssl, Socket, Data} ->
+ Pid ! {ssl, Socket, Data},
+ flush_controller(Pid, Socket);
+ {ssl_closed, Socket} ->
+ Pid ! {ssl_closed, Socket},
+ flush_controller(Pid, Socket)
+ after 0 ->
+ ok
+ end.
+
+%% ------------------------------------------------------------
+%% Accepts a new connection attempt from another Erlang node.
+%% Performs the handshake with the other side.
+%% ------------------------------------------------------------
+
+accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime) ->
+ spawn_link(?MODULE, do_accept,
+ [self(), AcceptPid, Socket, MyNode,
+ Allowed, SetupTime]).
+
+do_accept(Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) ->
+ process_flag(priority, max),
+ receive
+ {AcceptPid, controller} ->
+ Timer = dist_util:start_timer(SetupTime),
+ case check_ip(Socket) of
+ true ->
+ HSData = #hs_data{
+ kernel_pid = Kernel,
+ this_node = MyNode,
+ socket = Socket,
+ timer = Timer,
+ this_flags = 0,
+ allowed = Allowed,
+ f_send = fun(S,D) -> ssl_prim:send(S,D) end,
+ f_recv = fun(S,N,T) -> ssl_prim:recv(S,N,T)
+ end,
+ f_setopts_pre_nodeup =
+ fun(S) ->
+ ssl_prim:setopts(S,
+ [{active, false}])
+ end,
+ f_setopts_post_nodeup =
+ fun(S) ->
+ ssl_prim:setopts(S,
+ [{deliver, port},
+ {active, true}])
+ end,
+ f_getll = fun(S) ->
+ ssl_prim:getll(S)
+ end,
+ f_address = fun get_remote_id/2,
+ mf_tick = {?MODULE, tick},
+ mf_getstat = {?MODULE,getstat}
+ },
+ dist_util:handshake_other_started(HSData);
+ {false,IP} ->
+ error_msg("** Connection attempt from "
+ "disallowed IP ~w ** ~n", [IP]),
+ ?shutdown(no_node)
+ end
+ end.
+
+%% ------------------------------------------------------------
+%% Get remote information about a Socket.
+%% ------------------------------------------------------------
+
+get_remote_id(Socket, Node) ->
+ {ok, Address} = ssl_prim:peername(Socket),
+ [_, Host] = split_node(atom_to_list(Node), $@, []),
+ #net_address {
+ address = Address,
+ host = Host,
+ protocol = ssl,
+ family = inet }.
+
+%% ------------------------------------------------------------
+%% Setup a new connection to another Erlang node.
+%% Performs the handshake with the other side.
+%% ------------------------------------------------------------
+
+setup(Node, Type, MyNode, LongOrShortNames,SetupTime) ->
+ spawn_link(?MODULE, do_setup, [self(),
+ Node,
+ Type,
+ MyNode,
+ LongOrShortNames,
+ SetupTime]).
+
+do_setup(Kernel, Node, Type, MyNode, LongOrShortNames,SetupTime) ->
+ process_flag(priority, max),
+ ?trace("~p~n",[{inet_ssl_dist,self(),setup,Node}]),
+ [Name, Address] = splitnode(Node, LongOrShortNames),
+ case inet:getaddr(Address, inet) of
+ {ok, Ip} ->
+ Timer = dist_util:start_timer(SetupTime),
+ case erl_epmd:port_please(Name, Ip) of
+ {port, TcpPort, Version} ->
+ ?trace("port_please(~p) -> version ~p~n",
+ [Node,Version]),
+ dist_util:reset_timer(Timer),
+ case ssl_prim:connect(Ip, TcpPort,
+ [{active, false},
+ {packet,4}] ++
+ get_ssl_options(client)) of
+ {ok, Socket} ->
+ HSData = #hs_data{
+ kernel_pid = Kernel,
+ other_node = Node,
+ this_node = MyNode,
+ socket = Socket,
+ timer = Timer,
+ this_flags = 0,
+ other_version = Version,
+ f_send = fun(S,D) ->
+ ssl_prim:send(S,D)
+ end,
+ f_recv = fun(S,N,T) ->
+ ssl_prim:recv(S,N,T)
+ end,
+ f_setopts_pre_nodeup =
+ fun(S) ->
+ ssl_prim:setopts
+ (S,
+ [{active, false}])
+ end,
+ f_setopts_post_nodeup =
+ fun(S) ->
+ ssl_prim:setopts
+ (S,
+ [{deliver, port},{active, true}])
+ end,
+ f_getll = fun(S) ->
+ ssl_prim:getll(S)
+ end,
+ f_address =
+ fun(_,_) ->
+ #net_address {
+ address = {Ip,TcpPort},
+ host = Address,
+ protocol = ssl,
+ family = inet}
+ end,
+ mf_tick = {?MODULE, tick},
+ mf_getstat = {?MODULE,getstat},
+ request_type = Type
+ },
+ dist_util:handshake_we_started(HSData);
+ _ ->
+ %% Other Node may have closed since
+ %% port_please !
+ ?trace("other node (~p) "
+ "closed since port_please.~n",
+ [Node]),
+ ?shutdown(Node)
+ end;
+ _ ->
+ ?trace("port_please (~p) "
+ "failed.~n", [Node]),
+ ?shutdown(Node)
+ end;
+ _Other ->
+ ?trace("inet_getaddr(~p) "
+ "failed (~p).~n", [Node,Other]),
+ ?shutdown(Node)
+ end.
+
+%%
+%% Close a socket.
+%%
+close(Socket) ->
+ ssl_prim:close(Socket).
+
+
+%% If Node is illegal terminate the connection setup!!
+splitnode(Node, LongOrShortNames) ->
+ case split_node(atom_to_list(Node), $@, []) of
+ [Name|Tail] when Tail =/= [] ->
+ Host = lists:append(Tail),
+ case split_node(Host, $., []) of
+ [_] when LongOrShortNames == longnames ->
+ error_msg("** System running to use "
+ "fully qualified "
+ "hostnames **~n"
+ "** Hostname ~s is illegal **~n",
+ [Host]),
+ ?shutdown(Node);
+ [_, _ | _] when LongOrShortNames == shortnames ->
+ error_msg("** System NOT running to use fully qualified "
+ "hostnames **~n"
+ "** Hostname ~s is illegal **~n",
+ [Host]),
+ ?shutdown(Node);
+ _ ->
+ [Name, Host]
+ end;
+ [_] ->
+ error_msg("** Nodename ~p illegal, no '@' character **~n",
+ [Node]),
+ ?shutdown(Node);
+ _ ->
+ error_msg("** Nodename ~p illegal **~n", [Node]),
+ ?shutdown(Node)
+ end.
+
+split_node([Chr|T], Chr, Ack) -> [lists:reverse(Ack)|split_node(T, Chr, [])];
+split_node([H|T], Chr, Ack) -> split_node(T, Chr, [H|Ack]);
+split_node([], _, Ack) -> [lists:reverse(Ack)].
+
+%% ------------------------------------------------------------
+%% Fetch local information about a Socket.
+%% ------------------------------------------------------------
+get_tcp_address(Socket) ->
+ {ok, Address} = ssl_prim:sockname(Socket),
+ {ok, Host} = inet:gethostname(),
+ #net_address {
+ address = Address,
+ host = Host,
+ protocol = ssl,
+ family = inet
+ }.
+
+%% ------------------------------------------------------------
+%% Do only accept new connection attempts from nodes at our
+%% own LAN, if the check_ip environment parameter is true.
+%% ------------------------------------------------------------
+check_ip(Socket) ->
+ case application:get_env(check_ip) of
+ {ok, true} ->
+ case get_ifs(Socket) of
+ {ok, IFs, IP} ->
+ check_ip(IFs, IP);
+ _ ->
+ ?shutdown(no_node)
+ end;
+ _ ->
+ true
+ end.
+
+get_ifs(Socket) ->
+ case ssl_prim:peername(Socket) of
+ {ok, {IP, _}} ->
+ case ssl_prim:getif(Socket) of
+ {ok, IFs} -> {ok, IFs, IP};
+ Error -> Error
+ end;
+ Error ->
+ Error
+ end.
+
+check_ip([{OwnIP, _, Netmask}|IFs], PeerIP) ->
+ case {mask(Netmask, PeerIP), mask(Netmask, OwnIP)} of
+ {M, M} -> true;
+ _ -> check_ip(IFs, PeerIP)
+ end;
+check_ip([], PeerIP) ->
+ {false, PeerIP}.
+
+mask({M1,M2,M3,M4}, {IP1,IP2,IP3,IP4}) ->
+ {M1 band IP1,
+ M2 band IP2,
+ M3 band IP3,
+ M4 band IP4}.
+
+is_node_name(Node) when is_atom(Node) ->
+ case split_node(atom_to_list(Node), $@, []) of
+ [_, _Host] -> true;
+ _ -> false
+ end;
+is_node_name(_Node) ->
+ false.
+tick(Sock) ->
+ ?to_port(Sock,[],[force]).
+getstat(Socket) ->
+ case ssl_prim:getstat(Socket, [recv_cnt, send_cnt, send_pend]) of
+ {ok, Stat} ->
+ split_stat(Stat,0,0,0);
+ Error ->
+ Error
+ end.
+
+split_stat([{recv_cnt, R}|Stat], _, W, P) ->
+ split_stat(Stat, R, W, P);
+split_stat([{send_cnt, W}|Stat], R, _, P) ->
+ split_stat(Stat, R, W, P);
+split_stat([{send_pend, P}|Stat], R, W, _) ->
+ split_stat(Stat, R, W, P);
+split_stat([], R, W, P) ->
+ {ok, R, W, P}.
+
+
+get_ssl_options(Type) ->
+ case init:get_argument(ssl_dist_opt) of
+ {ok, Args} ->
+ ssl_options(Type, Args);
+ _ ->
+ []
+ end.
+
+ssl_options(_,[]) ->
+ [];
+ssl_options(server, [["server_certfile", Value]|T]) ->
+ [{certfile, Value} | ssl_options(server,T)];
+ssl_options(client, [["client_certfile", Value]|T]) ->
+ [{certfile, Value} | ssl_options(client,T)];
+ssl_options(server, [["server_cacertfile", Value]|T]) ->
+ [{cacertfile, Value} | ssl_options(server,T)];
+ssl_options(server, [["server_keyfile", Value]|T]) ->
+ [{keyfile, Value} | ssl_options(server,T)];
+ssl_options(Type, [["client_certfile", _Value]|T]) ->
+ ssl_options(Type,T);
+ssl_options(Type, [["server_certfile", _Value]|T]) ->
+ ssl_options(Type,T);
+ssl_options(Type, [[Item, Value]|T]) ->
+ [{atomize(Item),fixup(Value)} | ssl_options(Type,T)];
+ssl_options(Type, [[Item,Value |T1]|T2]) ->
+ ssl_options(atomize(Type),[[Item,Value],T1|T2]);
+ssl_options(_,_) ->
+ exit(malformed_ssl_dist_opt).
+
+fixup(Value) ->
+ case catch list_to_integer(Value) of
+ {'EXIT',_} ->
+ Value;
+ Int ->
+ Int
+ end.
+
+atomize(List) when is_list(List) ->
+ list_to_atom(List);
+atomize(Atom) when is_atom(Atom) ->
+ Atom.
diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src
new file mode 100644
index 0000000000..2a7d451341
--- /dev/null
+++ b/lib/ssl/src/ssl.app.src
@@ -0,0 +1,41 @@
+{application, ssl,
+ [{description, "Erlang/OTP SSL application"},
+ {vsn, "%VSN%"},
+ {modules, [ssl,
+ ssl_app,
+ ssl_sup,
+ ssl_server,
+ ssl_broker,
+ ssl_broker_sup,
+ ssl_base64,
+ ssl_pem,
+ ssl_pkix,
+ ssl_pkix_oid,
+ ssl_prim,
+ inet_ssl_dist,
+ ssl_tls1,
+ ssl_ssl3,
+ ssl_ssl2,
+ ssl_session,
+ ssl_session_cache_api,
+ ssl_session_cache,
+ ssl_record,
+ ssl_manager,
+ ssl_handshake,
+ ssl_debug,
+ ssl_connection_sup,
+ ssl_connection,
+ ssl_cipher,
+ ssl_certificate_db,
+ ssl_certificate,
+ ssl_alert,
+ 'OTP-PKIX'
+ ]},
+ {registered, [ssl_sup, ssl_server, ssl_broker_sup]},
+ {applications, [kernel, stdlib]},
+ {env, []},
+ {mod, {ssl_app, []}}]}.
+
+
+
+
diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src
new file mode 100644
index 0000000000..755c3b51b5
--- /dev/null
+++ b/lib/ssl/src/ssl.appup.src
@@ -0,0 +1,21 @@
+%% -*- erlang -*-
+{"%VSN%",
+ [
+ {"3.10", [{restart_application, ssl}]},
+ {"3.10.1", [{restart_application, ssl}]},
+ {"3.10.2", [{restart_application, ssl}]},
+ {"3.10.3", [{restart_application, ssl}]},
+ {"3.10.4", [{restart_application, ssl}]},
+ {"3.10.5", [{restart_application, ssl}]},
+ {"3.10.6", [{restart_application, ssl}]}
+ ],
+ [
+ {"3.10", [{restart_application, ssl}]},
+ {"3.10.1", [{restart_application, ssl}]},
+ {"3.10.2", [{restart_application, ssl}]},
+ {"3.10.3", [{restart_application, ssl}]},
+ {"3.10.4", [{restart_application, ssl}]},
+ {"3.10.5", [{restart_application, ssl}]},
+ {"3.10.6", [{restart_application, ssl}]}
+ ]}.
+
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
new file mode 100644
index 0000000000..1222fe97fd
--- /dev/null
+++ b/lib/ssl/src/ssl.erl
@@ -0,0 +1,841 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+%%% Purpose : Main API module for SSL.
+
+-module(ssl).
+
+-export([start/0, start/1, stop/0, transport_accept/1,
+ transport_accept/2, ssl_accept/1, ssl_accept/2, ssl_accept/3,
+ ciphers/0, cipher_suites/0, cipher_suites/1, close/1, shutdown/2,
+ connect/3, connect/2, connect/4, connection_info/1,
+ controlling_process/2, listen/2, pid/1, peername/1, recv/2, recv/3,
+ send/2, getopts/2, setopts/2, seed/1, sockname/1, peercert/1,
+ peercert/2, version/0, versions/0, session_info/1, format_error/1]).
+
+%% Should be deprecated as soon as old ssl is removed
+%%-deprecated({pid, 1, next_major_release}).
+
+-include("ssl_int.hrl").
+-include("ssl_internal.hrl").
+
+-record(config, {ssl, %% SSL parameters
+ inet_user, %% User set inet options
+ emulated, %% #socket_option{} emulated
+ inet_ssl, %% inet options for internal ssl socket
+ cb %% Callback info
+ }).
+
+%%--------------------------------------------------------------------
+%% Function: start([, Type]) -> ok
+%%
+%% Type = permanent | transient | temporary
+%% Vsns = [Vsn]
+%% Vsn = ssl3 | tlsv1 | 'tlsv1.1'
+%%
+%% Description: Starts the ssl application. Default type
+%% is temporary. see application(3)
+%%--------------------------------------------------------------------
+start() ->
+ application:start(ssl).
+start(Type) ->
+ application:start(ssl, Type).
+
+%%--------------------------------------------------------------------
+%% Function: stop() -> ok
+%%
+%% Description: Stops the ssl application.
+%%--------------------------------------------------------------------
+stop() ->
+ application:stop(ssl).
+
+%%--------------------------------------------------------------------
+%% Function: connect(Address, Port, Options[, Timeout]) -> {ok, Socket}
+%%
+%% Description: Connect to a ssl server.
+%%--------------------------------------------------------------------
+connect(Socket, SslOptions) when is_port(Socket) ->
+ connect(Socket, SslOptions, infinity).
+
+connect(Socket, SslOptions0, Timeout) when is_port(Socket) ->
+ EmulatedOptions = emulated_options(),
+ {ok, InetValues} = inet:getopts(Socket, EmulatedOptions),
+ inet:setopts(Socket, internal_inet_values()),
+ try handle_options(SslOptions0 ++ InetValues, client) of
+ {ok, #config{cb=CbInfo, ssl=SslOptions, emulated=EmOpts}} ->
+ case inet:peername(Socket) of
+ {ok, {Address, Port}} ->
+ ssl_connection:connect(Address, Port, Socket,
+ {SslOptions, EmOpts},
+ self(), CbInfo, Timeout);
+ {error, Error} ->
+ {error, Error}
+ end
+ catch
+ _:{error, Reason} ->
+ {error, Reason}
+ end;
+
+connect(Address, Port, Options) ->
+ connect(Address, Port, Options, infinity).
+
+connect(Address, Port, Options0, Timeout) ->
+ case proplists:get_value(ssl_imp, Options0, old) of
+ new ->
+ new_connect(Address, Port, Options0, Timeout);
+ old ->
+ %% Allow the option reuseaddr to be present
+ %% so that new and old ssl can be run by the same
+ %% code, however the option will be ignored by old ssl
+ %% that hardcodes reuseaddr to true in its portprogram.
+ Options1 = proplists:delete(reuseaddr, Options0),
+ Options = proplists:delete(ssl_imp, Options1),
+ old_connect(Address, Port, Options, Timeout);
+ Value ->
+ {error, {eoptions, {ssl_imp, Value}}}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: listen(Port, Options) -> {ok, ListenSock} | {error, Reason}
+%%
+%% Description: Creates a ssl listen socket.
+%%--------------------------------------------------------------------
+listen(_Port, []) ->
+ {error, enooptions};
+listen(Port, Options0) ->
+ case proplists:get_value(ssl_imp, Options0, old) of
+ new ->
+ new_listen(Port, Options0);
+ old ->
+ %% Allow the option reuseaddr to be present
+ %% so that new and old ssl can be run by the same
+ %% code, however the option will be ignored by old ssl
+ %% that hardcodes reuseaddr to true in its portprogram.
+ Options = proplists:delete(reuseaddr, Options0),
+ old_listen(Port, Options);
+ Value ->
+ {error, {eoptions, {ssl_imp, Value}}}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: transport_accept(ListenSocket[, Timeout]) -> {ok, Socket}.
+%%
+%% Description: Performs transport accept on a ssl listen socket
+%%--------------------------------------------------------------------
+transport_accept(ListenSocket) ->
+ transport_accept(ListenSocket, infinity).
+
+transport_accept(#sslsocket{pid = {ListenSocket, #config{cb=CbInfo, ssl=SslOpts}},
+ fd = new_ssl} = SslSocket, Timeout) ->
+
+ %% The setopt could have been invoked on the listen socket
+ %% and options should be inherited.
+ EmOptions = emulated_options(),
+ {ok, InetValues} = inet:getopts(ListenSocket, EmOptions),
+ {CbModule,_,_} = CbInfo,
+ {ok, Socket} = CbModule:accept(ListenSocket, Timeout),
+ inet:setopts(Socket, internal_inet_values()),
+ {ok, Port} = inet:port(Socket),
+ case ssl_connection_sup:start_child([server, "localhost", Port, Socket,
+ {SslOpts, socket_options(InetValues)}, self(),
+ CbInfo]) of
+ {ok, Pid} ->
+ CbModule:controlling_process(Socket, Pid),
+ {ok, SslSocket#sslsocket{pid = Pid}};
+ {error, Reason} ->
+ {error, Reason}
+ end;
+
+transport_accept(#sslsocket{} = ListenSocket, Timeout) ->
+ ensure_old_ssl_started(),
+ {ok, Pid} = ssl_broker:start_broker(acceptor),
+ ssl_broker:transport_accept(Pid, ListenSocket, Timeout).
+
+%%--------------------------------------------------------------------
+%% Function: ssl_accept(ListenSocket[, Timeout]) -> {ok, Socket} |
+%% {error, Reason}
+%%
+%% Description: Performs accept on a ssl listen socket. e.i. performs
+%% ssl handshake.
+%%--------------------------------------------------------------------
+ssl_accept(ListenSocket) ->
+ ssl_accept(ListenSocket, infinity).
+
+ssl_accept(#sslsocket{pid = Pid, fd = new_ssl}, Timeout) ->
+ gen_fsm:send_event(Pid, socket_control),
+ try gen_fsm:sync_send_all_state_event(Pid, started, Timeout) of
+ connected ->
+ ok;
+ {error, _} = Error ->
+ Error
+ catch
+ exit:{noproc, _} ->
+ {error, closed};
+ exit:{timeout, _} ->
+ {error, timeout};
+ exit:{normal, _} ->
+ {error, closed}
+ end;
+
+ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) ->
+ ssl_accept(ListenSocket, SslOptions, infinity);
+
+%% Old ssl
+ssl_accept(#sslsocket{} = Socket, Timeout) ->
+ ensure_old_ssl_started(),
+ ssl_broker:ssl_accept(Socket, Timeout).
+
+ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) ->
+ EmulatedOptions = emulated_options(),
+ {ok, InetValues} = inet:getopts(Socket, EmulatedOptions),
+ inet:setopts(Socket, internal_inet_values()),
+ try handle_options(SslOptions ++ InetValues, server) of
+ {ok, #config{cb=CbInfo,ssl=SslOpts, emulated=EmOpts}} ->
+ {ok, Port} = inet:port(Socket),
+ ssl_connection:accept(Port, Socket,
+ {SslOpts, EmOpts},
+ self(), CbInfo, Timeout)
+ catch
+ Error = {error, _Reason} -> Error
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: close() -> ok
+%%
+%% Description: Close a ssl connection
+%%--------------------------------------------------------------------
+close(#sslsocket{pid = {ListenSocket, #config{cb={CbMod,_, _}}}, fd = new_ssl}) ->
+ CbMod:close(ListenSocket);
+close(#sslsocket{pid = Pid, fd = new_ssl}) ->
+ ssl_connection:close(Pid);
+close(Socket = #sslsocket{}) ->
+ ensure_old_ssl_started(),
+ ssl_broker:close(Socket).
+
+%%--------------------------------------------------------------------
+%% Function: send(Socket, Data) -> ok
+%%
+%% Description: Sends data over the ssl connection
+%%--------------------------------------------------------------------
+send(#sslsocket{pid = Pid, fd = new_ssl}, Data) ->
+ ssl_connection:send(Pid, Data);
+
+send(#sslsocket{} = Socket, Data) ->
+ ensure_old_ssl_started(),
+ ssl_broker:send(Socket, Data).
+
+%%--------------------------------------------------------------------
+%% Function: recv(Socket, Length [,Timeout]) -> {ok, Data} | {error, reason}
+%%
+%% Description: Receives data when active = false
+%%--------------------------------------------------------------------
+recv(Socket, Length) ->
+ recv(Socket, Length, infinity).
+recv(#sslsocket{pid = Pid, fd = new_ssl}, Length, Timeout) ->
+ ssl_connection:recv(Pid, Length, Timeout);
+
+recv(Socket = #sslsocket{}, Length, Timeout) ->
+ ensure_old_ssl_started(),
+ ssl_broker:recv(Socket, Length, Timeout).
+
+%%--------------------------------------------------------------------
+%% Function: controlling_process(Socket, NewOwner) -> ok | {error, Reason}
+%%
+%% Description: Changes process that receives the messages when active = true
+%% or once.
+%%--------------------------------------------------------------------
+controlling_process(#sslsocket{pid = Pid, fd = new_ssl}, NewOwner)
+ when is_pid(Pid) ->
+ ssl_connection:new_user(Pid, NewOwner);
+
+controlling_process(Socket, NewOwner) when is_pid(NewOwner) ->
+ ensure_old_ssl_started(),
+ ssl_broker:controlling_process(Socket, NewOwner).
+
+%%--------------------------------------------------------------------
+%% Function: connection_info(Socket) -> {ok, {Protocol, CipherSuite}} |
+%% {error, Reason}
+%% Protocol = sslv3 | tlsv1 | tlsv1.1
+%% CipherSuite = {KeyExchange, Chipher, Hash, Exportable}
+%%
+%%
+%% Description: Returns ssl protocol and cipher used for the connection
+%%--------------------------------------------------------------------
+connection_info(#sslsocket{pid = Pid, fd = new_ssl}) ->
+ ssl_connection:info(Pid);
+
+connection_info(#sslsocket{} = Socket) ->
+ ensure_old_ssl_started(),
+ ssl_broker:connection_info(Socket).
+
+%%--------------------------------------------------------------------
+%% Function: peercert(Socket[, Opts]) -> {ok, Cert} | {error, Reason}
+%%
+%% Description:
+%%--------------------------------------------------------------------
+peercert(Socket) ->
+ peercert(Socket, []).
+
+peercert(#sslsocket{pid = Pid, fd = new_ssl}, Opts) ->
+ case ssl_connection:peer_certificate(Pid) of
+ {ok, undefined} ->
+ {error, no_peercert};
+ {ok, BinCert} ->
+ PKOpts = [case Opt of ssl -> otp; pkix -> plain end ||
+ Opt <- Opts, Opt =:= ssl orelse Opt =:= pkix],
+ case PKOpts of
+ [Opt] ->
+ public_key:pkix_decode_cert(BinCert, Opt);
+ [] ->
+ {ok, BinCert}
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end;
+
+peercert(#sslsocket{} = Socket, Opts) ->
+ ensure_old_ssl_started(),
+ case ssl_broker:peercert(Socket) of
+ {ok, Bin} ->
+ ssl_pkix:decode_cert(Bin, Opts);
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: peername(Socket) -> {ok, {Address, Port}} | {error, Reason}
+%%
+%% Description:
+%%--------------------------------------------------------------------
+peername(#sslsocket{fd = new_ssl, pid = Pid}) ->
+ ssl_connection:peername(Pid);
+
+peername(#sslsocket{} = Socket) ->
+ ensure_old_ssl_started(),
+ ssl_broker:peername(Socket).
+
+%%--------------------------------------------------------------------
+%% Function: cipher_suites() ->
+%%
+%% Description:
+%%--------------------------------------------------------------------
+cipher_suites() ->
+ cipher_suites(erlang).
+
+cipher_suites(erlang) ->
+ Version = ssl_record:highest_protocol_version([]),
+ [ssl_cipher:suite_definition(S) || S <- ssl_cipher:suites(Version)];
+
+cipher_suites(openssl) ->
+ Version = ssl_record:highest_protocol_version([]),
+ [ssl_cipher:openssl_suite_name(S) || S <- ssl_cipher:suites(Version)].
+
+%%--------------------------------------------------------------------
+%% Function: getopts(Socket, OptTags) -> {ok, Options} | {error, Reason}
+%%
+%% Description:
+%%--------------------------------------------------------------------
+getopts(#sslsocket{fd = new_ssl, pid = Pid}, OptTags) when is_pid(Pid) ->
+ ssl_connection:get_opts(Pid, OptTags);
+getopts(#sslsocket{fd = new_ssl, pid = {ListenSocket, _}}, OptTags) ->
+ inet:getopts(ListenSocket, OptTags);
+getopts(#sslsocket{} = Socket, Options) ->
+ ensure_old_ssl_started(),
+ ssl_broker:getopts(Socket, Options).
+
+%%--------------------------------------------------------------------
+%% Function: setopts(Socket, Options) -> ok | {error, Reason}
+%%
+%% Description:
+%%--------------------------------------------------------------------
+setopts(#sslsocket{fd = new_ssl, pid = Pid}, Options) when is_pid(Pid) ->
+ ssl_connection:set_opts(Pid, Options);
+setopts(#sslsocket{fd = new_ssl, pid = {ListenSocket, _}}, OptTags) ->
+ inet:setopts(ListenSocket, OptTags);
+setopts(#sslsocket{} = Socket, Options) ->
+ ensure_old_ssl_started(),
+ ssl_broker:setopts(Socket, Options).
+
+%%---------------------------------------------------------------
+%% Function: shutdown(Socket, How) -> ok | {error, Reason}
+%%
+%% Description: Same as gen_tcp:shutdown/2
+%%--------------------------------------------------------------------
+shutdown(#sslsocket{pid = {ListenSocket, #config{cb={CbMod,_, _}}}, fd = new_ssl}, How) ->
+ CbMod:shutdown(ListenSocket, How);
+shutdown(#sslsocket{pid = Pid, fd = new_ssl}, How) ->
+ ssl_connection:shutdown(Pid, How).
+
+%%--------------------------------------------------------------------
+%% Function: sockname(Socket) -> {ok, {Address, Port}} | {error, Reason}
+%%
+%% Description: Same as inet:sockname/1
+%%--------------------------------------------------------------------
+sockname(#sslsocket{fd = new_ssl, pid = {ListenSocket, _}}) ->
+ inet:sockname(ListenSocket);
+
+sockname(#sslsocket{fd = new_ssl, pid = Pid}) ->
+ ssl_connection:sockname(Pid);
+
+sockname(#sslsocket{} = Socket) ->
+ ensure_old_ssl_started(),
+ ssl_broker:sockname(Socket).
+
+%%---------------------------------------------------------------
+%% Function: seed(Data) -> ok | {error, edata}
+%%
+%% Description:
+%%--------------------------------------------------------------------
+%% TODO: crypto:seed ?
+seed(Data) ->
+ ensure_old_ssl_started(),
+ ssl_server:seed(Data).
+
+%%---------------------------------------------------------------
+%% Function: session_id(Socket) -> {ok, PropList} | {error, Reason}
+%%
+%% Description:
+%%--------------------------------------------------------------------
+session_info(#sslsocket{pid = Pid, fd = new_ssl}) ->
+ ssl_connection:session_info(Pid).
+
+%%---------------------------------------------------------------
+%% Function: versions() -> [{SslAppVer, SupportedSslVer, AvailableSslVsn}]
+%%
+%% SslAppVer = string() - t.ex: ssl-4.0
+%% SupportedSslVer = [SslVer]
+%% AvailableSslVsn = [SSLVer]
+%% SSLVer = sslv3 | tlsv1 | 'tlsv1.1'
+%%
+%% Description: Returns a list of relevant versions.
+%%--------------------------------------------------------------------
+versions() ->
+ Vsns = ssl_record:supported_protocol_versions(),
+ SupportedVsns = [ssl_record:protocol_version(Vsn) || Vsn <- Vsns],
+ AvailableVsns = ?DEFAULT_SUPPORTED_VERSIONS,
+ [{ssl_app, ?VSN}, {supported, SupportedVsns}, {available, AvailableVsns}].
+
+%%%--------------------------------------------------------------
+%%% Internal functions
+%%%--------------------------------------------------------------------
+new_connect(Address, Port, Options, Timeout) when is_list(Options) ->
+ try handle_options(Options, client) of
+ {ok, Config} ->
+ do_new_connect(Address,Port,Config,Timeout)
+ catch
+ throw:Error ->
+ Error
+ end.
+
+do_new_connect(Address, Port,
+ #config{cb=CbInfo, inet_user=UserOpts, ssl=SslOpts,
+ emulated=EmOpts,inet_ssl=SocketOpts},
+ Timeout) ->
+ {CbModule, _, _} = CbInfo,
+ try CbModule:connect(Address, Port, SocketOpts, Timeout) of
+ {ok, Socket} ->
+ ssl_connection:connect(Address, Port, Socket, {SslOpts,EmOpts},
+ self(), CbInfo, Timeout);
+ {error, Reason} ->
+ {error, Reason}
+ catch
+ exit:{function_clause, _} ->
+ {error, {eoptions, {cb_info, CbInfo}}};
+ exit:{badarg, _} ->
+ {error,{eoptions, {inet_options, UserOpts}}}
+ end.
+
+old_connect(Address, Port, Options, Timeout) ->
+ ensure_old_ssl_started(),
+ {ok, Pid} = ssl_broker:start_broker(connector),
+ ssl_broker:connect(Pid, Address, Port, Options, Timeout).
+
+new_listen(Port, Options0) ->
+ try
+ {ok, Config} = handle_options(Options0, server),
+ #config{cb={CbModule, _, _},inet_user=Options} = Config,
+ case CbModule:listen(Port, Options) of
+ {ok, ListenSocket} ->
+ {ok, #sslsocket{pid = {ListenSocket, Config}, fd = new_ssl}};
+ Err = {error, _} ->
+ Err
+ end
+ catch
+ Error = {error, _} ->
+ Error
+ end.
+
+old_listen(Port, Options) ->
+ ensure_old_ssl_started(),
+ {ok, Pid} = ssl_broker:start_broker(listener),
+ ssl_broker:listen(Pid, Port, Options).
+
+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,
+
+ {Verify, FailIfNoPeerCert, CaCertDefault} =
+ %% 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)};
+ 1 ->
+ {verify_peer, false, ca_cert_default(verify_peer, Role)};
+ 2 ->
+ {verify_peer, true, ca_cert_default(verify_peer, Role)};
+ verify_none ->
+ {verify_none, false, ca_cert_default(verify_none, Role)};
+ verify_peer ->
+ {verify_peer, proplists:get_value(fail_if_no_peer_cert,
+ Opts, false),
+ ca_cert_default(verify_peer, Role)};
+ Value ->
+ throw({error, {eoptions, {verify, Value}}})
+ 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),
+ fail_if_no_peer_cert = validate_option(fail_if_no_peer_cert,
+ FailIfNoPeerCert),
+ verify_client_once = handle_option(verify_client_once, Opts, false),
+ depth = handle_option(depth, Opts, 1),
+ certfile = CertFile,
+ keyfile = handle_option(keyfile, Opts, CertFile),
+ key = handle_option(key, Opts, undefined),
+ password = handle_option(password, Opts, ""),
+ cacertfile = handle_option(cacertfile, Opts, CaCertDefault),
+ ciphers = handle_option(ciphers, Opts, []),
+ %% Server side option
+ reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun),
+ reuse_sessions = handle_option(reuse_sessions, Opts, true),
+ debug = handle_option(debug, Opts, [])
+ },
+
+ CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed}),
+ SslOptions = [versions, verify, verify_fun,
+ depth, certfile, keyfile,
+ key, password, cacertfile, ciphers,
+ debug, reuse_session, reuse_sessions, ssl_imp,
+ cd_info],
+
+ SockOpts = lists:foldl(fun(Key, PropList) ->
+ proplists:delete(Key, PropList)
+ end, Opts, SslOptions),
+
+ {SSLsock, Emulated} = emulated_options(SockOpts),
+ {ok, #config{ssl=SSLOptions, emulated=Emulated, inet_ssl=SSLsock,
+ inet_user=SockOpts, cb=CbInfo}}.
+
+handle_option(OptionName, Opts, Default) ->
+ validate_option(OptionName,
+ proplists:get_value(OptionName, Opts, Default)).
+
+
+validate_option(versions, Versions) ->
+ validate_versions(Versions, Versions);
+validate_option(ssl_imp, Value) when Value == new; Value == old ->
+ Value;
+validate_option(verify, Value)
+ when Value == verify_none; Value == verify_peer ->
+ Value;
+validate_option(verify_fun, Value) when is_function(Value) ->
+ Value;
+validate_option(fail_if_no_peer_cert, Value)
+ when Value == true; Value == false ->
+ Value;
+validate_option(verify_client_once, Value)
+ when Value == true; Value == false ->
+ Value;
+validate_option(depth, Value) when is_integer(Value),
+ Value >= 0, Value =< 255->
+ Value;
+validate_option(certfile, Value) when is_list(Value) ->
+ Value;
+validate_option(keyfile, Value) when is_list(Value) ->
+ Value;
+validate_option(key, Value) when Value == undefined;
+ is_tuple(Value) ->
+ %% element(1, Value)=='RSAPrivateKey' ->
+ Value;
+validate_option(password, Value) when 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(ciphers, Value) when is_list(Value) ->
+ Version = ssl_record:highest_protocol_version([]),
+ try cipher_suites(Version, Value)
+ catch
+ exit:_ ->
+ throw({error, {eoptions, {ciphers, Value}}})
+ end;
+validate_option(reuse_session, Value) when is_function(Value) ->
+ Value;
+validate_option(reuse_sessions, Value) when Value == true;
+ Value == false ->
+ Value;
+validate_option(debug, Value) when is_list(Value); Value == true ->
+ Value;
+validate_option(Opt, Value) ->
+ throw({error, {eoptions, {Opt, Value}}}).
+
+validate_versions([], Versions) ->
+ Versions;
+validate_versions([Version | Rest], Versions) when Version == 'tlsv1.1';
+ Version == tlsv1;
+ Version == sslv3 ->
+ validate_versions(Rest, Versions);
+validate_versions(Ver, Versions) ->
+ throw({error, {eoptions, {Ver, {versions, Versions}}}}).
+
+validate_inet_option(mode, Value)
+ when Value =/= list, Value =/= binary ->
+ throw({error, {eoptions, {mode,Value}}});
+validate_inet_option(packet, Value)
+ when not (is_atom(Value) orelse is_integer(Value)) ->
+ throw({error, {eoptions, {packet,Value}}});
+validate_inet_option(packet_size, Value)
+ when not is_integer(Value) ->
+ throw({error, {eoptions, {packet_size,Value}}});
+validate_inet_option(header, Value)
+ when not is_integer(Value) ->
+ throw({error, {eoptions, {header,Value}}});
+validate_inet_option(active, Value)
+ when Value =/= true, Value =/= false, Value =/= once ->
+ throw({error, {eoptions, {active,Value}}});
+validate_inet_option(_, _) ->
+ ok.
+
+ca_cert_default(verify_none, _) ->
+ undefined;
+%% Client may leave verification up to the user
+ca_cert_default(verify_peer, client) ->
+ undefined;
+%% Server that wants to verify_peer must have
+%% some trusted certs.
+ca_cert_default(verify_peer, server) ->
+ "".
+
+emulated_options() ->
+ [mode, packet, active, header, packet_size].
+
+internal_inet_values() ->
+ [{packet_size,0},{packet, 0},{header, 0},{active, false},{mode,binary}].
+ %%[{packet, ssl},{header, 0},{active, false},{mode,binary}].
+
+socket_options(InetValues) ->
+ #socket_options{
+ mode = proplists:get_value(mode, InetValues),
+ header = proplists:get_value(header, InetValues),
+ active = proplists:get_value(active, InetValues),
+ packet = proplists:get_value(packet, InetValues),
+ packet_size = proplists:get_value(packet_size, InetValues)
+ }.
+
+emulated_options(Opts) ->
+ emulated_options(Opts, internal_inet_values(), #socket_options{}).
+
+emulated_options([{mode,Opt}|Opts], Inet, Emulated) ->
+ validate_inet_option(mode,Opt),
+ emulated_options(Opts, Inet, Emulated#socket_options{mode=Opt});
+emulated_options([{header,Opt}|Opts], Inet, Emulated) ->
+ validate_inet_option(header,Opt),
+ emulated_options(Opts, Inet, Emulated#socket_options{header=Opt});
+emulated_options([{active,Opt}|Opts], Inet, Emulated) ->
+ validate_inet_option(active,Opt),
+ emulated_options(Opts, Inet, Emulated#socket_options{active=Opt});
+emulated_options([{packet,Opt}|Opts], Inet, Emulated) ->
+ validate_inet_option(packet,Opt),
+ emulated_options(Opts, Inet, Emulated#socket_options{packet=Opt});
+emulated_options([{packet_size,Opt}|Opts], Inet, Emulated) ->
+ validate_inet_option(packet_size,Opt),
+ emulated_options(Opts, Inet, Emulated#socket_options{packet_size=Opt});
+emulated_options([Opt|Opts], Inet, Emulated) ->
+ emulated_options(Opts, [Opt|Inet], Emulated);
+emulated_options([], Inet,Emulated) ->
+ {Inet, Emulated}.
+
+cipher_suites(Version, []) ->
+ ssl_cipher:suites(Version);
+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),
+ case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, Supported)] of
+ [] ->
+ Supported;
+ Ciphers ->
+ Ciphers
+ end;
+cipher_suites(Version, [Head | _] = Ciphers0) when is_list(Head) ->
+ %% Format: ["RC4-SHA","RC4-MD5"]
+ Ciphers = [ssl_cipher:openssl_suite(C) || C <- Ciphers0],
+ cipher_suites(Version, Ciphers);
+cipher_suites(Version, Ciphers0) ->
+ %% Format: "RC4-SHA:RC4-MD5"
+ Ciphers = [ssl_cipher:openssl_suite(C) || C <- string:tokens(Ciphers0, ":")],
+ cipher_suites(Version, Ciphers).
+
+format_error({error, Reason}) ->
+ format_error(Reason);
+format_error(closed) ->
+ "Connection closed for the operation in question.";
+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(ecacertfile) ->
+ "Own CA certificate file is invalid.";
+format_error(ecertfile) ->
+ "Own certificate file is invalid.";
+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(ekeyfile) ->
+ "Own private key file 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(esslaccept) ->
+ "Server SSL handshake procedure between client and server failed.";
+format_error(esslconnect) ->
+ "Client SSL handshake procedure between client and server failed.";
+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.";
+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) ->
+ io_lib:format("No format string for error: \"~p\" available.", [Error]).
+
+%% Start old ssl port program if needed.
+ensure_old_ssl_started() ->
+ case whereis(ssl_server) of
+ undefined ->
+ (catch supervisor:start_child(ssl_sup,
+ {ssl_server, {ssl_server, start_link, []},
+ permanent, 2000, worker, [ssl_server]}));
+ _ ->
+ ok
+ end.
+
+%%%%%%%%%%%%%%%% Deprecated %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ciphers() ->
+ ensure_old_ssl_started(),
+ case (catch ssl_server:ciphers()) of
+ {'EXIT', _} ->
+ {error, enotstarted};
+ Res = {ok, _} ->
+ Res
+ end.
+
+version() ->
+ ensure_old_ssl_started(),
+ SSLVsn = ?VSN,
+ {CompVsn, LibVsn} = case (catch ssl_server:version()) of
+ {'EXIT', _} ->
+ {"", ""};
+ {ok, Vsns} ->
+ Vsns
+ end,
+ {ok, {SSLVsn, CompVsn, LibVsn}}.
+
+%% Only used to remove exit messages from old ssl
+%% First is a nonsense clause to provide some
+%% backward compability for orber that uses this
+%% function in a none recommended way, but will
+%% work correctly if a valid pid is returned.
+pid(#sslsocket{fd = new_ssl}) ->
+ whereis(ssl_connection_sup);
+pid(#sslsocket{pid = Pid}) ->
+ Pid.
diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl
new file mode 100644
index 0000000000..d3f9c833f1
--- /dev/null
+++ b/lib/ssl/src/ssl_alert.erl
@@ -0,0 +1,107 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Handles an ssl connection, e.i. both the setup
+%% e.i. SSL-Handshake, SSL-Alert and SSL-Cipher protocols and delivering
+%% data to the application. All data on the connectinon is received and
+%% sent according to the SSL-record protocol.
+%% %%----------------------------------------------------------------------
+
+-module(ssl_alert).
+
+-include("ssl_alert.hrl").
+-include("ssl_record.hrl").
+
+-export([alert_txt/1, reason_code/2]).
+
+reason_code(#alert{description = ?CLOSE_NOTIFY}, _) ->
+ closed;
+reason_code(#alert{description = ?HANDSHAKE_FAILURE}, client) ->
+ esslconnect;
+reason_code(#alert{description = ?HANDSHAKE_FAILURE}, server) ->
+ esslaccept;
+reason_code(#alert{description = ?CERTIFICATE_EXPIRED}, _) ->
+ epeercertexpired;
+reason_code(#alert{level = ?FATAL}, _) ->
+ esslerrssl;
+reason_code(#alert{description = Description}, _) ->
+ description_txt(Description).
+
+alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}}) ->
+ Mod ++ ":" ++ integer_to_list(Line) ++ ":" ++
+ level_txt(Level) ++" "++ description_txt(Description).
+
+level_txt(?WARNING) ->
+ "Warning:";
+level_txt(?FATAL) ->
+ "Fatal error:".
+
+description_txt(?CLOSE_NOTIFY) ->
+ "close_notify";
+description_txt(?UNEXPECTED_MESSAGE) ->
+ "unexpected_message";
+description_txt(?BAD_RECORD_MAC) ->
+ "bad_record_mac";
+description_txt(?DECRYPTION_FAILED) ->
+ "decryption_failed";
+description_txt(?RECORD_OVERFLOW) ->
+ "record_overflow";
+description_txt(?DECOMPRESSION_FAILURE) ->
+ "decompression_failure";
+description_txt(?HANDSHAKE_FAILURE) ->
+ "handshake_failure";
+description_txt(?BAD_CERTIFICATE) ->
+ "bad_certificate";
+description_txt(?UNSUPPORTED_CERTIFICATE) ->
+ "unsupported_certificate";
+description_txt(?CERTIFICATE_REVOKED) ->
+ "certificate_revoked";
+description_txt(?CERTIFICATE_EXPIRED) ->
+ "certificate_expired";
+description_txt(?CERTIFICATE_UNKNOWN) ->
+ "certificate_unknown";
+description_txt(?ILLEGAL_PARAMETER) ->
+ "illegal_parameter";
+description_txt(?UNKNOWN_CA) ->
+ "unknown_ca";
+description_txt(?ACCESS_DENIED) ->
+ "access_denied";
+description_txt(?DECODE_ERROR) ->
+ "decode_error";
+description_txt(?DECRYPT_ERROR) ->
+ "decrypt_error";
+description_txt(?EXPORT_RESTRICTION) ->
+ "export_restriction";
+description_txt(?PROTOCOL_VERSION) ->
+ "protocol_version";
+description_txt(?INSUFFICIENT_SECURITY) ->
+ "insufficient_security";
+description_txt(?INTERNAL_ERROR) ->
+ "internal_error";
+description_txt(?USER_CANCELED) ->
+ "user_canceled";
+description_txt(?NO_RENEGOTIATION) ->
+ "no_renegotiation".
+
+
+
+
+
diff --git a/lib/ssl/src/ssl_alert.hrl b/lib/ssl/src/ssl_alert.hrl
new file mode 100644
index 0000000000..6470b82d50
--- /dev/null
+++ b/lib/ssl/src/ssl_alert.hrl
@@ -0,0 +1,97 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Record and constant defenitions for the SSL-alert protocol
+%% see RFC 2246
+%%----------------------------------------------------------------------
+
+-ifndef(ssl_alert).
+-define(ssl_alert, true).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Alert protocol - RFC 2246 section 7.2
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% AlertLevel
+-define(WARNING, 1).
+-define(FATAL, 2).
+
+%% {AlertDescription
+%% enum {
+%% close_notify(0),
+%% unexpected_message(10),
+%% bad_record_mac(20),
+%% decryption_failed(21),
+%% record_overflow(22),
+%% decompression_failure(30),
+%% handshake_failure(40),
+%% bad_certificate(42),
+%% unsupported_certificate(43),
+%% certificate_revoked(44),
+%% certificate_expired(45),
+ %% certificate_unknown(46),
+%% illegal_parameter(47),
+%% unknown_ca(48),
+%% access_denied(49),
+%% decode_error(50),
+%% decrypt_error(51),
+%% export_restriction(60),
+%% protocol_version(70),
+%% insufficient_security(71),
+%% internal_error(80),
+%% user_canceled(90),
+%% no_renegotiation(100),
+%% (255)
+%% } AlertDescription;
+
+-define(CLOSE_NOTIFY, 0).
+-define(UNEXPECTED_MESSAGE, 10).
+-define(BAD_RECORD_MAC, 20).
+-define(DECRYPTION_FAILED, 21).
+-define(RECORD_OVERFLOW, 22).
+-define(DECOMPRESSION_FAILURE, 30).
+-define(HANDSHAKE_FAILURE, 40).
+-define(BAD_CERTIFICATE, 42).
+-define(UNSUPPORTED_CERTIFICATE, 43).
+-define(CERTIFICATE_REVOKED, 44).
+-define(CERTIFICATE_EXPIRED, 45).
+-define(CERTIFICATE_UNKNOWN, 46).
+-define(ILLEGAL_PARAMETER, 47).
+-define(UNKNOWN_CA, 48).
+-define(ACCESS_DENIED, 49).
+-define(DECODE_ERROR, 50).
+-define(DECRYPT_ERROR, 51).
+-define(EXPORT_RESTRICTION, 60).
+-define(PROTOCOL_VERSION, 70).
+-define(INSUFFICIENT_SECURITY, 71).
+-define(INTERNAL_ERROR, 80).
+-define(USER_CANCELED, 90).
+-define(NO_RENEGOTIATION, 100).
+
+-define(ALERT_REC(Level,Desc), #alert{level=Level,description=Desc,where={?FILE, ?LINE}}).
+
+%% Alert
+-record(alert, {
+ level,
+ description,
+ where = {?FILE, ?LINE}
+ }).
+-endif. % -ifdef(ssl_alert).
diff --git a/lib/ssl/src/ssl_app.erl b/lib/ssl/src/ssl_app.erl
new file mode 100644
index 0000000000..6ca1c42631
--- /dev/null
+++ b/lib/ssl/src/ssl_app.erl
@@ -0,0 +1,41 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+%%% Purpose : Application master for SSL.
+
+-module(ssl_app).
+
+-behaviour(application).
+
+-export([start/2, stop/1]).
+
+%% start/2(Type, StartArgs) -> {ok, Pid} | {ok, Pid, State} |
+%% {error, Reason}
+%%
+start(_Type, _StartArgs) ->
+ ssl_sup:start_link().
+
+%% stop(State) -> void()
+%%
+stop(_State) ->
+ ok.
+
+
diff --git a/lib/ssl/src/ssl_base64.erl b/lib/ssl/src/ssl_base64.erl
new file mode 100644
index 0000000000..cfc42407e8
--- /dev/null
+++ b/lib/ssl/src/ssl_base64.erl
@@ -0,0 +1,129 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2003-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+%%% Purpose : Base 64 encoding and decoding.
+
+-module(ssl_base64).
+
+-export([encode/1, encode_split/1, decode/1, join_decode/1]).
+
+-define(st(X,A), ((X-A+256) div 256)).
+-define(CHARS, 64).
+
+%% A PEM encoding consists of characters A-Z, a-z, 0-9, +, / and
+%% =. Each character encodes a 6 bits value from 0 to 63 (A = 0, / =
+%% 63); = is a padding character.
+%%
+
+%%
+%% encode(Bytes|Binary) -> Chars
+%%
+%% Take 3 bytes a time (3 x 8 = 24 bits), and make 4 characters out of
+%% them (4 x 6 = 24 bits).
+%%
+encode(Bs) when is_list(Bs) ->
+ encode(list_to_binary(Bs));
+encode(<<B:3/binary, Bs/binary>>) ->
+ <<C1:6, C2:6, C3:6, C4:6>> = B,
+ [enc(C1), enc(C2), enc(C3), enc(C4)| encode(Bs)];
+encode(<<B:2/binary>>) ->
+ <<C1:6, C2:6, C3:6, _:6>> = <<B/binary, 0>>,
+ [enc(C1), enc(C2), enc(C3), $=];
+encode(<<B:1/binary>>) ->
+ <<C1:6, C2:6, _:12>> = <<B/binary, 0, 0>>,
+ [enc(C1), enc(C2), $=, $=];
+encode(<<>>) ->
+ [].
+
+%%
+%% encode_split(Bytes|Binary) -> Lines
+%%
+%% The encoding is divided into lines separated by <NL>, and each line
+%% is precisely 64 characters long (excluding the <NL> characters,
+%% except the last line which 64 characters long or shorter. <NL> may
+%% follow the last line.
+%%
+encode_split(Bs) ->
+ split(encode(Bs)).
+
+%%
+%% decode(Chars) -> Binary
+%%
+decode(Cs) ->
+ list_to_binary(decode1(Cs)).
+
+decode1([C1, C2, $=, $=]) ->
+ <<B1, _:16>> = <<(dec(C1)):6, (dec(C2)):6, 0:12>>,
+ [B1];
+decode1([C1, C2, C3, $=]) ->
+ <<B1, B2, _:8>> = <<(dec(C1)):6, (dec(C2)):6, (dec(C3)):6, (dec(0)):6>>,
+ [B1, B2];
+decode1([C1, C2, C3, C4| Cs]) ->
+ Bin = <<(dec(C1)):6, (dec(C2)):6, (dec(C3)):6, (dec(C4)):6>>,
+ [Bin| decode1(Cs)];
+decode1([]) ->
+ [].
+
+%%
+%% join_decode(Lines) -> Binary
+%%
+%% Remove <NL> before decoding.
+%%
+join_decode(Cs) ->
+ decode(join(Cs)).
+
+%%
+%% Locals
+%%
+
+%% enc/1 and dec/1
+%%
+%% Mapping: 0-25 -> A-Z, 26-51 -> a-z, 52-61 -> 0-9, 62 -> +, 63 -> /
+%%
+enc(C) ->
+ 65 + C + 6*?st(C,26) - 75*?st(C,52) -15*?st(C,62) + 3*?st(C,63).
+
+dec(C) ->
+ 62*?st(C,43) + ?st(C,47) + (C-59)*?st(C,48) - 69*?st(C,65) - 6*?st(C,97).
+
+%% split encoding into lines
+%%
+split(Cs) ->
+ split(Cs, ?CHARS).
+
+split([], _N) ->
+ [$\n];
+split(Cs, 0) ->
+ [$\n| split(Cs, ?CHARS)];
+split([C| Cs], N) ->
+ [C| split(Cs, N-1)].
+
+%% join lines of encodings
+%%
+join([$\r, $\n| Cs]) ->
+ join(Cs);
+join([$\n| Cs]) ->
+ join(Cs);
+join([C| Cs]) ->
+ [C| join(Cs)];
+join([]) ->
+ [].
+
diff --git a/lib/ssl/src/ssl_broker.erl b/lib/ssl/src/ssl_broker.erl
new file mode 100644
index 0000000000..178fb5fcb9
--- /dev/null
+++ b/lib/ssl/src/ssl_broker.erl
@@ -0,0 +1,1188 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+%%% Purpose : SSL broker
+
+-module(ssl_broker).
+-behaviour(gen_server).
+
+%% This module implements brokers for ssl. A broker is either a connector,
+%% an acceptor, or a listener. All brokers are children to ssl_broker_sup,
+%% to which they are linked. Each broker is also linked to ssl_server, and
+%% to its client.
+%%
+%% The purpose of the broker is to set up SSL connections through calls to
+%% ssl_server and gen_tcp. All control information goes to the server,
+%% while all data is exchanged directly between gen_tcp and the port program
+%% of the ssl_server.
+%%
+%% A broker is created by a call to start_broker/3 (do *not* use start_link/4
+%% - it is for ssl_broker_sup to call that one), and then call listen/3,
+%% accept/4, or connect/5.
+%%
+%% The following table shows all functions dependency on status, active
+%% mode etc.
+%%
+%% Permitted status transitions:
+%%
+%% nil -> open
+%% open -> closing | closed (termination)
+%% closing -> closed (termination)
+%%
+%% We are rather sloppy about nil, and consider open/closing == !closed,
+%% open/closing/closed === any etc.
+%%
+%%
+%% function/ valid mode new
+%% message status state
+%%
+%% calls
+%% -----
+%% recv open passive ditto
+%% send open any ditto
+%% transport_accept nil any open
+%% ssl_accept nil any open
+%% connect nil any open
+%% listen nil any open
+%% peername open/closing any ditto
+%% setopts open/closing any ditto
+%% getopts open/closing any ditto
+%% sockname open/closing any ditto
+%% peercert open/closing any ditto
+%% inhibit any any ditto
+%% release any any ditto
+%% close any any closed (1)
+%%
+%% info
+%% ----
+%% tcp open active ditto
+%% tcp_closed open | closing active closing
+%% tcp_error open | closing active closing
+%%
+%% (1) We just terminate.
+%%
+%% TODO
+%%
+%% XXX Timeouts are not checked (integer or infinity).
+%%
+%% XXX The collector thing is not gen_server compliant.
+%%
+%% NOTE: There are three different "modes": (a) passive or active mode,
+%% specified as {active, bool()}, and (b) list or binary mode, specified
+%% as {mode, list | binary}, and (c) encrypted or clear mode
+%%
+
+-include("ssl_int.hrl").
+
+%% External exports
+
+-export([start_broker/1, start_broker/2, start_link/3,
+ transport_accept/3, ssl_accept/2,
+ close/1, connect/5, connection_info/1, controlling_process/2,
+ listen/3, recv/3, send/2, getopts/2, getopts/3, setopts/2,
+ sockname/1, peername/1, peercert/1]).
+
+-export([listen_prim/5, connect_prim/8,
+ transport_accept_prim/5, ssl_accept_prim/6]).
+
+%% Internal exports
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ code_change/3, terminate/2, collector_init/1]).
+
+-include("ssl_broker_int.hrl").
+
+%% start_broker(Type) -> {ok, Pid} | {error, Reason}
+%% start_broker(Type, GenOpts) -> {ok, Pid} | {error, Reason}
+%% Type = accept | connect | listen
+%% GenOpts = /standard gen_server options/
+%%
+%% This is the function to be called from the interface module ssl.erl.
+%% Links to the caller.
+%%
+start_broker(Type) ->
+ start_broker(Type, []).
+
+start_broker(Type, GenOpts) ->
+ case lists:member(Type, [listener, acceptor, connector]) of
+ true ->
+ case supervisor:start_child(ssl_broker_sup,
+ [self(), Type, GenOpts]) of
+ {ok, Pid} ->
+ link(Pid),
+ {ok, Pid};
+ {error, Reason} ->
+ {error, Reason}
+ end;
+ false ->
+ {error, ebrokertype}
+ end.
+
+%% start_link(Client, Type, GenOpts) -> {ok, Pid} | {error, Reason}
+%%
+%% Type = accept | connect | listen
+%% GenOpts = /standard gen_server options/
+%%
+%% This function is called by ssl_broker_sup and must *not* be called
+%% from an interface module (ssl.erl).
+
+start_link(Client, Type, GenOpts) ->
+ gen_server:start_link(?MODULE, [Client, Type], GenOpts).
+
+
+%% accept(Pid, ListenSocket, Timeout) -> {ok, Socket} | {error, Reason}
+%%
+%% Types: Pid = pid() of acceptor
+%% ListenSocket = Socket = sslsocket()
+%% Timeout = timeout()
+%%
+%% accept(Pid, ListenSocket, Timeout)
+%% when is_pid(Pid), is_record(ListenSocket, sslsocket) ->
+%% Req = {accept, self(), ListenSocket, Timeout},
+%% gen_server:call(Pid, Req, infinity).
+
+%% transport_accept(Pid, ListenSocket, Timeout) -> {ok, Socket} |
+%% {error, Reason}
+%%
+%% Types: Pid = pid() of acceptor
+%% ListenSocket = Socket = sslsocket()
+%% Timeout = timeout()
+%%
+transport_accept(Pid, #sslsocket{} = ListenSocket, Timeout) when is_pid(Pid) ->
+ Req = {transport_accept, self(), ListenSocket, Timeout},
+ gen_server:call(Pid, Req, infinity).
+
+%% ssl_accept(Pid, Socket, Timeout) -> {ok, Socket} | {error, Reason}
+%%
+%% Types: Pid = pid() of acceptor
+%% ListenSocket = Socket = sslsocket()
+%% Timeout = timeout()
+%%
+ssl_accept(#sslsocket{pid = Pid} = Socket, Timeout) ->
+ Req = {ssl_accept, self(), Socket, Timeout},
+ gen_server:call(Pid, Req, infinity).
+
+%% close(Socket) -> ok | {error, Reason}
+%%
+%% Types: Socket = sslsocket() | pid()
+%%
+close(#sslsocket{pid = Pid}) ->
+ close(Pid);
+close(Pid) when is_pid(Pid) ->
+ gen_server:call(Pid, {close, self()}, infinity).
+
+%% connect(Pid, Address, Port, Opts, Timeout) -> {ok, Socket} | {error, Reason}
+%%
+%% Types: Pid = pid() of connector
+%% Address = string() | {byte(), byte(), byte(), byte()}
+%% Port = int()
+%% Opts = options()
+%% Timeout = timeout()
+%% Socket = sslsocket()
+%%
+connect(Pid, Address, Port, Opts, Timeout) when is_pid(Pid), is_list(Opts) ->
+ case are_connect_opts(Opts) of
+ true ->
+ Req = {connect, self(), Address, Port, Opts, Timeout},
+ gen_server:call(Pid, Req, infinity);
+ false ->
+ {error, eoptions}
+ end.
+
+%%
+%% connection_info(Socket) -> {ok, {Protocol, Cipher} | {error, Reason}
+%%
+connection_info(#sslsocket{pid = Pid}) ->
+ Req = {connection_info, self()},
+ gen_server:call(Pid, Req, infinity).
+
+%% controlling_process(Socket, NewOwner) -> ok | {error, Reason}
+
+controlling_process(#sslsocket{pid = Pid}, NewOwner) when is_pid(NewOwner) ->
+ case gen_server:call(Pid, {inhibit_msgs, self()}, infinity) of
+ ok ->
+ transfer_messages(Pid, NewOwner),
+ gen_server:call(Pid, {release_msgs, self(), NewOwner}, infinity);
+ Error ->
+ Error
+ end.
+
+%% listen(Pid, Port, Opts) -> {ok, ListenSocket} | {error, Reason}
+%%
+%% Types: Pid = pid() of listener
+%% Port = int()
+%% Opts = options()
+%% ListenSocket = sslsocket()
+%%
+listen(Pid, Port, Opts) when is_pid(Pid) ->
+ case are_listen_opts(Opts) of
+ true ->
+ Req = {listen, self(), Port, Opts},
+ gen_server:call(Pid, Req, infinity);
+ false ->
+ {error, eoptions}
+ end.
+
+
+%%
+%% peername(Socket) -> {ok, {Address, Port}} | {error, Reason}
+%%
+peername(#sslsocket{pid = Pid}) ->
+ Req = {peername, self()},
+ gen_server:call(Pid, Req, infinity).
+
+
+%% recv(Socket, Length, Timeout) -> {ok, Data} | {error, Reason}
+%%
+%% Types: Socket = sslsocket()
+%% Length = Timeout = integer()
+%% Data = bytes() | binary()
+%%
+recv(#sslsocket{pid = Pid}, Length, Timeout) ->
+ Req = {recv, self(), Length, Timeout},
+ gen_server:call(Pid, Req, infinity).
+
+
+%% send(Socket, Data) -> ok | {error, Reason}
+%%
+%% Types: Socket = sslsocket()
+%%
+send(#sslsocket{pid = Pid}, Data) ->
+ gen_server:call(Pid, {send, self(), Data}, infinity).
+
+
+%% getopts(Socket, OptTags) -> {ok, Opts} | {error, einval}
+%%
+%% Types: Pid = pid() of broker
+%% Timeout = timeout()
+%% OptTags = option_tags()
+%% Opts = options()
+%%
+getopts(Socket, OptTags) ->
+ getopts(Socket, OptTags, infinity).
+
+getopts(#sslsocket{pid = Pid}, OptTags, Timeout) when is_list(OptTags) ->
+ Req = {getopts, self(), OptTags},
+ gen_server:call(Pid, Req, Timeout).
+
+
+%%
+%% setopts(Socket, Opts) -> ok | {error, Reason}
+%%
+setopts(#sslsocket{pid = Pid}, Opts) ->
+ Req = {setopts, self(), Opts},
+ gen_server:call(Pid, Req, infinity).
+
+%%
+%% sockname(Socket) -> {ok, {Address, Port}} | {error, Reason}
+%%
+sockname(#sslsocket{pid = Pid}) ->
+ Req = {sockname, self()},
+ gen_server:call(Pid, Req, infinity).
+
+
+%%
+%% peercert(Socket) -> {ok, Cert} | {error, Reason}
+%%
+peercert(#sslsocket{pid = Pid}) ->
+ Req = {peercert, self()},
+ gen_server:call(Pid, Req, infinity).
+
+%%
+%% INIT
+%%
+
+%% init
+%%
+init([Client, Type]) ->
+ process_flag(trap_exit, true),
+ link(Client),
+ Debug = case application:get_env(ssl, edebug) of
+ {ok, true} ->
+ true;
+ _ ->
+ case application:get_env(ssl, debug) of
+ {ok, true} ->
+ true;
+ _ ->
+ os:getenv("ERL_SSL_DEBUG") =/= false
+ end
+ end,
+ Server = whereis(ssl_server),
+ if
+ is_pid(Server) ->
+ link(Server),
+ debug1(Debug, Type, "in start, client = ~w", [Client]),
+ {ok, #st{brokertype = Type, server = Server, client = Client,
+ collector = Client, debug = Debug}};
+ true ->
+ {stop, no_ssl_server}
+ end.
+
+
+%%
+%% HANDLE CALL
+%%
+
+%% recv - passive mode
+%%
+handle_call({recv, Client, Length, Timeout}, _From,
+ #st{active = false, proxysock = Proxysock, status = Status} = St) ->
+ debug(St, "recv: client = ~w~n", [Client]),
+ if
+ Status =/= open ->
+ {reply, {error, closed}, St};
+ true ->
+ case gen_tcp:recv(Proxysock, Length, Timeout) of
+ {ok, Data} ->
+ {reply, {ok, Data}, St};
+ {error, timeout} ->
+ {reply, {error, timeout}, St};
+ {error, Reason} ->
+ {reply, {error, Reason}, St#st{status = closing}}
+ end
+ end;
+
+%% send
+%%
+handle_call({send, Client, Data}, _From, St) ->
+ debug(St, "send: client = ~w~n", [Client]),
+ if
+ St#st.status =/= open ->
+ {reply, {error, closed}, St};
+ true ->
+ case gen_tcp:send(St#st.proxysock, Data) of
+ ok ->
+ {reply, ok, St};
+ {error, _Reason} ->
+ {reply, {error, closed}, St#st{status = closing}}
+ end
+ end;
+
+%% transport_accept
+%%
+%% Client = pid of client
+%% ListenSocket = sslsocket()
+%%
+handle_call({transport_accept, Client, ListenSocket, Timeout}, _From, St) ->
+ debug(St, "transport_accept: client = ~w, listensocket = ~w~n",
+ [Client, ListenSocket]),
+ case getopts(ListenSocket, tcp_listen_opt_tags(), ?DEF_TIMEOUT) of
+ {ok, LOpts} ->
+ case transport_accept_prim(
+ ssl_server, ListenSocket#sslsocket.fd, LOpts, Timeout, St) of
+ {ok, ThisSocket, NSt} ->
+ {reply, {ok, ThisSocket}, NSt};
+ {error, Reason, St} ->
+ What = what(Reason),
+ {stop, normal, {error, What}, St}
+ end;
+ {error, Reason} ->
+ What = what(Reason),
+ {stop, normal, {error, What}, St}
+ end;
+
+%% ssl_accept
+%%
+%% Client = pid of client
+%% ListenSocket = sslsocket()
+%%
+handle_call({ssl_accept, Client, Socket, Timeout}, _From, St) ->
+ debug(St, "ssl_accept: client = ~w, socket = ~w~n", [Client, Socket]),
+ case ssl_accept_prim(ssl_server, gen_tcp, Client, St#st.opts, Timeout, St#st{thissock=Socket}) of
+ {ok, Socket, NSt} ->
+ {reply, ok, NSt};
+ {error, Reason, St} ->
+ What = what(Reason),
+ {stop, normal, {error, What}, St}
+ end;
+
+%% connect
+%%
+%% Client = client pid
+%% Address = hostname | ipstring | IP
+%% Port = integer()
+%% Opts = options()
+%%
+handle_call({connect, Client, Address, Port, Opts, Timeout}, _From, St) ->
+ debug(St, "connect: client = ~w, address = ~p, port = ~w~n",
+ [Client, Address, Port]),
+ case connect_prim(ssl_server, gen_tcp, Client, Address, Port, Opts,
+ Timeout, St) of
+ {ok, Res, NSt} ->
+ {reply, {ok, Res}, NSt};
+ {error, Reason, NSt} ->
+ What = what(Reason),
+ {stop, normal, {error, What}, NSt}
+ end;
+
+%% connection_info
+%%
+handle_call({connection_info, Client}, _From, St) ->
+ debug(St, "connection_info: client = ~w~n", [Client]),
+ Reply = ssl_server:connection_info(St#st.fd),
+ {reply, Reply, St};
+
+%% close from client
+%%
+handle_call({close, Client}, _From, St) ->
+ debug(St, "close: client = ~w~n", [Client]),
+ %% Terminate
+ {stop, normal, ok, St#st{status = closed}};
+
+%% listen
+%%
+%% Client = pid of client
+%% Port = int()
+%% Opts = options()
+%%
+handle_call({listen, Client, Port, Opts}, _From, St) ->
+ debug(St, "listen: client = ~w, port = ~w~n",
+ [Client, Port]),
+ case listen_prim(ssl_server, Client, Port, Opts, St) of
+ {ok, Res, NSt} ->
+ {reply, {ok, Res}, NSt};
+ {error, Reason, NSt} ->
+ What = what(Reason),
+ {stop, normal, {error, What}, NSt}
+ end;
+
+%% peername
+%%
+handle_call({peername, Client}, _From, St) ->
+ debug(St, "peername: client = ~w~n", [Client]),
+ Reply = case ssl_server:peername(St#st.fd) of
+ {ok, {Address, Port}} ->
+ {ok, At} = inet_parse:ipv4_address(Address),
+ {ok, {At, Port}};
+ Error ->
+ Error
+ end,
+ {reply, Reply, St};
+
+%% setopts
+%%
+handle_call({setopts, Client, Opts0}, _From, St0) ->
+ debug(St0, "setopts: client = ~w~n", [Client]),
+ OptsOK = case St0#st.brokertype of
+ listener ->
+ are_opts(fun is_tcp_listen_opt/1, Opts0);
+ acceptor ->
+ are_opts(fun is_tcp_accept_opt/1, Opts0);
+ connector ->
+ are_opts(fun is_tcp_connect_opt/1, Opts0)
+ end,
+ if
+ OptsOK =:= false ->
+ {reply, {error, eoptions}, St0};
+ true ->
+ Opts1 = lists:keydelete(nodelay, 1, Opts0),
+ case inet:setopts(St0#st.proxysock, Opts1) of
+ ok ->
+ Opts2 = replace_opts(Opts1, St0#st.opts),
+ Active = get_active(Opts2),
+ St2 = St0#st{opts = Opts2,
+ active = Active},
+ case get_nodelay(Opts0) of
+ empty ->
+ {reply, ok, St2};
+ Bool ->
+ case setnodelay(ssl_server, St0, Bool) of
+ ok ->
+ Opts3 = replace_opts([{nodelay, Bool}],
+ Opts2),
+ St3 = St0#st{opts = Opts3,
+ active = Active},
+ {reply, ok, St3};
+ {error, Reason} ->
+ {reply, {error, Reason}, St2}
+ end
+ end;
+ {error, Reason} ->
+ {reply, {error, Reason}, St0}
+ end
+ end;
+
+%% sockname
+%%
+handle_call({sockname, Client}, _From, St) ->
+ debug(St, "sockname: client = ~w~n", [Client]),
+ Reply = case ssl_server:sockname(St#st.fd) of
+ {ok, {Address, Port}} ->
+ {ok, At} = inet_parse:ipv4_address(Address),
+ {ok, {At, Port}};
+ Error ->
+ Error
+ end,
+ {reply, Reply, St};
+
+%% peercert
+%%
+handle_call({peercert, Client}, _From, St) ->
+ debug(St, "peercert: client = ~w~n", [Client]),
+ Reply = ssl_server:peercert(St#st.fd),
+ {reply, Reply, St};
+
+%% inhibit msgs
+%%
+handle_call({inhibit_msgs, Client}, _From, #st{client = Client} = St) ->
+ debug(St, "inhibit_msgs: client = ~w~n", [Client]),
+ {ok, Collector} = start_collector(),
+ {reply, ok, St#st{collector = Collector}};
+
+%% release msgs
+%%
+handle_call({release_msgs, Client, NewClient}, _From,
+ #st{client = Client, collector = Collector} = St) ->
+ debug(St, "release_msgs: client = ~w~n", [Client]),
+ unlink(Client),
+ link(NewClient),
+ release_collector(Collector, NewClient),
+ NSt = St#st{client = NewClient, collector = NewClient},
+ {reply, ok, NSt};
+
+%% getopts
+%%
+handle_call({getopts, Client, OptTags}, _From, St) ->
+ debug(St, "getopts: client = ~w~n", [Client]),
+ Reply = case are_opt_tags(St#st.brokertype, OptTags) of
+ true ->
+ {ok, extract_opts(OptTags, St#st.opts)};
+ _ ->
+ {error, einval}
+ end,
+ {reply, Reply, St};
+
+%% bad call
+%%
+handle_call(Request, _From, St) ->
+ debug(St, "++++ ssl_broker: bad call: ~w~n", [Request]),
+ {reply, {error, {badcall, Request}}, St}.
+
+%%
+%% HANDLE CAST
+%%
+
+handle_cast(Request, St) ->
+ debug(St, "++++ ssl_broker: bad cast: ~w~n", [Request]),
+ {stop, {error, {badcast, Request}}, St}.
+
+%%
+%% HANDLE INFO
+%%
+
+%% tcp - active mode
+%%
+%% The collector is different from client only during change of
+%% controlling process.
+%%
+handle_info({tcp, Socket, Data},
+ #st{active = Active, collector = Collector, status = open,
+ proxysock = Socket, thissock = Thissock} = St)
+ when Active =/= false ->
+ debug(St, "tcp: socket = ~w~n", [Socket]),
+ Msg = {ssl, Thissock, Data},
+ Collector ! Msg,
+ if
+ Active =:= once ->
+ {noreply, St#st{active = false}};
+ true ->
+ {noreply, St}
+ end;
+
+%% tcp_closed - from proxy socket, active mode
+%%
+%%
+handle_info({tcp_closed, Socket},
+ #st{active = Active, collector = Collector,
+ proxysock = Socket, thissock = Thissock} = St)
+ when Active =/= false ->
+ debug(St, "tcp_closed: socket = ~w~n", [Socket]),
+ Msg = {ssl_closed, Thissock},
+ Collector ! Msg,
+ if
+ Active =:= once ->
+ {noreply, St#st{status = closing, active = false}};
+ true ->
+ {noreply, St#st{status = closing}}
+ end;
+
+%% tcp_error - from proxy socket, active mode
+%%
+%%
+handle_info({tcp_error, Socket, Reason},
+ #st{active = Active, collector = Collector,
+ proxysock = Socket} = St)
+ when Active =/= false ->
+ debug(St, "tcp_error: socket = ~w, reason = ~w~n", [Socket, Reason]),
+ Msg = {ssl_error, Socket, Reason},
+ Collector ! Msg,
+ if
+ Active =:= once ->
+ {noreply, St#st{status = closing, active = false}};
+ true ->
+ {noreply, St#st{status = closing}}
+ end;
+
+%% EXIT - from client
+%%
+%%
+handle_info({'EXIT', Client, Reason}, #st{client = Client} = St) ->
+ debug(St, "exit client: client = ~w, reason = ~w~n", [Client, Reason]),
+ {stop, normal, St#st{status = closed}}; % do not make noise
+
+%% EXIT - from server
+%%
+%%
+handle_info({'EXIT', Server, Reason}, #st{server = Server} = St) ->
+ debug(St, "exit server: reason = ~w~n", [Reason]),
+ {stop, Reason, St};
+
+%% handle info catch all
+%%
+handle_info(Info, St) ->
+ debug(St, " bad info: ~w~n", [Info]),
+ {stop, {error, {badinfo, Info}}, St}.
+
+
+%% terminate
+%%
+%%
+terminate(Reason, St) ->
+ debug(St, "in terminate reason: ~w, state: ~w~n", [Reason, St]),
+ ok.
+
+%% code_change
+%%
+%%
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%
+%% Primitive interface
+%%
+listen_prim(ServerName, Client, Port, Opts, St) ->
+ LOpts = get_tcp_listen_opts(Opts),
+ SSLOpts = get_ssl_opts(Opts),
+ FlagStr =mk_ssl_optstr(SSLOpts),
+ BackLog = get_backlog(LOpts),
+ IP = get_ip(LOpts),
+ case ssl_server:listen_prim(ServerName, IP, Port, FlagStr, BackLog) of
+ {ok, ListenFd, _Port0} ->
+ ThisSocket = #sslsocket{fd = ListenFd, pid = self()},
+ StOpts = add_default_tcp_listen_opts(LOpts) ++
+ add_default_ssl_opts(SSLOpts),
+ NSt = St#st{fd = ListenFd,
+ active = get_active(LOpts), % irrelevant for listen
+ opts = StOpts,
+ thissock = ThisSocket,
+ status = open},
+ debug(St, "listen: ok: client = ~w, listenfd = ~w~n",
+ [Client, ListenFd]),
+ {ok, ThisSocket, NSt};
+ {error, Reason} ->
+ {error, Reason, St}
+ end.
+
+connect_prim(ServerName, TcpModule, Client, FAddress, FPort, Opts,
+ Timeout, St) ->
+ COpts = get_tcp_connect_opts(Opts),
+ SSLOpts = get_ssl_opts(Opts),
+ FlagStr = mk_ssl_optstr(SSLOpts),
+ case inet:getaddr(FAddress, inet) of
+ {ok, FIP} ->
+ %% Timeout is gen_server timeout - hence catch
+ LIP = get_ip(COpts),
+ LPort = get_port(COpts),
+ case (catch ssl_server:connect_prim(ServerName,
+ LIP, LPort, FIP, FPort,
+ FlagStr, Timeout)) of
+ {ok, Fd, ProxyPort} ->
+ case connect_proxy(ServerName, TcpModule, Fd,
+ ProxyPort, COpts, Timeout) of
+ {ok, Socket} ->
+ ThisSocket = #sslsocket{fd = Fd, pid = self()},
+ StOpts = add_default_tcp_connect_opts(COpts) ++
+ add_default_ssl_opts(SSLOpts),
+ NSt = St#st{fd = Fd,
+ active = get_active(COpts),
+ opts = StOpts,
+ thissock = ThisSocket,
+ proxysock = Socket,
+ status = open},
+ case get_nodelay(COpts) of
+ true -> setnodelay(ServerName, NSt, true);
+ _ -> ok
+ end,
+ debug(St, "connect: ok: client = ~w, fd = ~w~n",
+ [Client, Fd]),
+ {ok, ThisSocket, NSt};
+ {error, Reason} ->
+ {error, Reason, St}
+ end;
+ {'EXIT', Reason} ->
+ {error, Reason, St};
+ {error, Reason} ->
+ {error, Reason, St}
+ end;
+ {error, Reason} ->
+ {error, Reason, St}
+ end.
+
+transport_accept_prim(ServerName, ListenFd, LOpts, Timeout, St) ->
+ AOpts = get_tcp_accept_opts(LOpts),
+ FlagStr = "",
+ %% Timeout is gen_server timeout - hence catch.
+ case (catch ssl_server:transport_accept_prim(ServerName, ListenFd,
+ FlagStr, Timeout)) of
+ {ok, Fd, ProxyPort} ->
+ ThisSocket = #sslsocket{fd = Fd, pid = self()},
+ NSt = St#st{fd = Fd,
+ active = get_active(AOpts),
+ opts = AOpts,
+ thissock = ThisSocket,
+ proxyport = ProxyPort,
+ encrypted = false},
+ debug(St, "transport_accept: ok: fd = ~w~n", [Fd]),
+ {ok, ThisSocket, NSt};
+ {'EXIT', Reason} ->
+ debug(St, "transport_accept: EXIT: Reason = ~w~n", [Reason]),
+ {error, Reason, St};
+ {error, Reason} ->
+ debug(St, "transport_accept: error: Reason = ~w~n", [Reason]),
+ {error, Reason, St}
+ end.
+
+ssl_accept_prim(ServerName, TcpModule, Client, LOpts, Timeout, St) ->
+ FlagStr = [],
+ SSLOpts = [],
+ AOpts = get_tcp_accept_opts(LOpts),
+ %% Timeout is gen_server timeout - hence catch.
+ debug(St, "ssl_accept_prim: self() ~w Client ~w~n", [self(), Client]),
+ Socket = St#st.thissock,
+ Fd = Socket#sslsocket.fd,
+ A = (catch ssl_server:ssl_accept_prim(ServerName, Fd, FlagStr, Timeout)),
+ debug(St, "ssl_accept_prim: ~w~n", [A]),
+ case A of
+ ok ->
+ B = connect_proxy(ServerName, TcpModule, Fd,
+ St#st.proxyport, AOpts, Timeout),
+ debug(St, "ssl_accept_prim: connect_proxy ~w~n", [B]),
+ case B of
+ {ok, Socket2} ->
+ StOpts = add_default_tcp_accept_opts(AOpts) ++
+ add_default_ssl_opts(SSLOpts),
+ NSt = St#st{opts = StOpts,
+ proxysock = Socket2,
+ encrypted = true,
+ status = open},
+ case get_nodelay(AOpts) of
+ true -> setnodelay(ServerName, NSt, true);
+ _ -> ok
+ end,
+ debug(St, "transport_accept: ok: client = ~w, fd = ~w~n",
+ [Client, Fd]),
+ {ok, St#st.thissock, NSt};
+ {error, Reason} ->
+ {error, Reason, St}
+ end;
+ {'EXIT', Reason} ->
+ {error, Reason, St};
+ {error, Reason} ->
+ {error, Reason, St}
+ end.
+
+
+%%
+%% LOCAL FUNCTIONS
+%%
+
+%%
+%% connect_proxy(Fd, ProxyPort, TOpts, Timeout) -> {ok, Socket} |
+%% {error, Reason}
+%%
+connect_proxy(ServerName, TcpModule, Fd, ProxyPort, TOpts, Timeout) ->
+ case TcpModule:connect({127, 0, 0, 1}, ProxyPort, TOpts, Timeout) of
+ {ok, Socket} ->
+ {ok, Port} = inet:port(Socket),
+ A = ssl_server:proxy_join_prim(ServerName, Fd, Port),
+ case A of
+ ok ->
+ {ok, Socket};
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+
+setnodelay(ServerName, St, Bool) ->
+ case ssl_server:setnodelay_prim(ServerName, St#st.fd, Bool) of
+ ok ->
+ case inet:setopts(St#st.proxysock, [{nodelay, Bool}]) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ {error, Reason}
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+%%
+%% start_collector()
+%%
+%% A collector is a little process that keeps messages during change of
+%% controlling process.
+%% XXX This is not gen_server compliant :-(.
+%%
+start_collector() ->
+ Pid = spawn_link(?MODULE, collector_init, [self()]),
+ {ok, Pid}.
+
+%%
+%% release_collector(Collector, NewOwner)
+%%
+release_collector(Collector, NewOwner) ->
+ Collector ! {release, self(), NewOwner},
+ receive
+ %% Reap collector
+ {'EXIT', Collector, normal} ->
+ ok
+ end.
+
+%%
+%% collector_init(Broker) -> void()
+%%
+collector_init(Broker) ->
+ receive
+ {release, Broker, NewOwner} ->
+ transfer_messages(Broker, NewOwner)
+ end.
+
+%%
+%% transfer_messages(Pid, NewOwner) -> void()
+%%
+transfer_messages(Pid, NewOwner) ->
+ receive
+ {ssl, Sock, Data} ->
+ NewOwner ! {ssl, Sock, Data},
+ transfer_messages(Pid, NewOwner);
+ {ssl_closed, Sock} ->
+ NewOwner ! {ssl_closed, Sock},
+ transfer_messages(Pid, NewOwner);
+ {ssl_error, Sock, Reason} ->
+ NewOwner ! {ssl_error, Sock, Reason},
+ transfer_messages(Pid, NewOwner)
+ after 0 ->
+ ok
+ end.
+
+%%
+%% debug(St, Format, Args) -> void() - printouts
+%%
+debug(St, Format, Args) ->
+ debug1(St#st.debug, St#st.brokertype, Format, Args).
+
+debug1(true, Type, Format0, Args) ->
+ {_MS, S, MiS} = erlang:now(),
+ Secs = S rem 100,
+ MiSecs = MiS div 1000,
+ Format = "++++ ~3..0w:~3..0w ssl_broker (~w)[~w]: " ++ Format0,
+ io:format(Format, [Secs, MiSecs, self(), Type| Args]);
+debug1(_, _, _, _) ->
+ ok.
+
+%%
+%% what(Reason) -> What
+%%
+what(Reason) when is_atom(Reason) ->
+ Reason;
+what({'EXIT', Reason}) ->
+ what(Reason);
+what({What, _Where}) when is_atom(What) ->
+ What;
+what(Reason) ->
+ Reason.
+
+
+%%
+%% OPTIONS
+%%
+%% Note that `accept' has no options when invoked, but get all its options
+%% by inheritance from `listen'.
+%%
+
+are_opt_tags(listener, OptTags) ->
+ is_subset(OptTags, listen_opt_tags());
+are_opt_tags(acceptor, OptTags) ->
+ is_subset(OptTags, accept_opt_tags());
+are_opt_tags(connector, OptTags) ->
+ is_subset(OptTags, connect_opt_tags()).
+
+listen_opt_tags() ->
+ tcp_listen_opt_tags() ++ ssl_opt_tags().
+
+accept_opt_tags() ->
+ tcp_gen_opt_tags().
+
+connect_opt_tags() ->
+ tcp_gen_opt_tags() ++ ssl_opt_tags().
+
+tcp_listen_opt_tags() ->
+ tcp_gen_opt_tags() ++ tcp_listen_only_opt_tags().
+
+tcp_gen_opt_tags() ->
+ %% All except `reuseaddr' and `deliver'.
+ [nodelay, active, packet, mode, header].
+
+tcp_listen_only_opt_tags() ->
+ [ip, backlog].
+
+ssl_opt_tags() ->
+ %% XXX Should remove cachetimeout.
+ [verify, depth, certfile, password, cacertfile, ciphers, cachetimeout].
+
+%% Options
+
+%%
+%% are_*_opts(Opts) -> boolean()
+%%
+are_connect_opts(Opts) ->
+ are_opts(fun is_connect_opt/1, Opts).
+
+are_listen_opts(Opts) ->
+ are_opts(fun is_listen_opt/1, Opts).
+
+are_opts(F, Opts) ->
+ lists:all(F, transform_opts(Opts)).
+
+%%
+%% get_*_opts(Opts) -> Value
+%%
+get_tcp_accept_opts(Opts) ->
+ [O || O <- transform_opts(Opts), is_tcp_accept_opt(O)].
+
+get_tcp_connect_opts(Opts) ->
+ [O || O <- transform_opts(Opts), is_tcp_connect_opt(O)].
+
+get_tcp_listen_opts(Opts) ->
+ [O || O <- transform_opts(Opts), is_tcp_listen_opt(O)].
+
+get_ssl_opts(Opts) ->
+ [O || O <- transform_opts(Opts), is_ssl_opt(O)].
+
+get_active(Opts) ->
+ get_tagged_opt(active, Opts, true).
+
+get_backlog(Opts) ->
+ get_tagged_opt(backlog, Opts, ?DEF_BACKLOG).
+
+get_ip(Opts) ->
+ get_tagged_opt(ip, Opts, {0, 0, 0, 0}).
+
+get_port(Opts) ->
+ get_tagged_opt(port, Opts, 0).
+
+get_nodelay(Opts) ->
+ get_tagged_opt(nodelay, Opts, empty).
+
+%%
+%% add_default_*_opts(Opts) -> NOpts
+%%
+
+add_default_tcp_accept_opts(Opts) ->
+ add_default_opts(Opts, default_tcp_accept_opts()).
+
+add_default_tcp_connect_opts(Opts) ->
+ add_default_opts(Opts, default_tcp_connect_opts()).
+
+add_default_tcp_listen_opts(Opts) ->
+ add_default_opts(Opts, default_tcp_listen_opts()).
+
+add_default_ssl_opts(Opts) ->
+ add_default_opts(Opts, default_ssl_opts()).
+
+add_default_opts(Opts, DefOpts) ->
+ TOpts = transform_opts(Opts),
+ TOpts ++ [DP || {DTag, _DVal} = DP <- DefOpts,
+ not lists:keymember(DTag, 1, TOpts)].
+
+default_tcp_accept_opts() ->
+ [O || O <- default_opts(), is_tcp_accept_opt(O)].
+
+default_tcp_connect_opts() ->
+ [O || O <- default_opts(), is_tcp_connect_opt(O)].
+
+default_tcp_listen_opts() ->
+ [O || O <- default_opts(), is_tcp_listen_opt(O)].
+
+default_ssl_opts() ->
+ [O || O <- default_opts(), is_ssl_opt(O)].
+
+default_opts() ->
+ [{mode, list}, {packet, 0}, {nodelay, false}, {active, true},
+ {backlog, ?DEF_BACKLOG}, {ip, {0, 0, 0, 0}},
+ {verify, 0}, {depth, 1}].
+
+
+%% Transform from old to new options, and also from old gen_tcp
+%% options to new ones. All returned options are tagged options.
+%%
+transform_opts(Opts) ->
+ lists:flatmap(fun transform_opt/1, Opts).
+
+transform_opt(binary) -> [{mode, binary}];
+transform_opt(list) -> [{mode, list}];
+transform_opt({packet, raw}) -> [{packet, 0}];
+transform_opt(raw) -> [];
+transform_opt(Opt) -> [Opt].
+
+%% NOTE: The is_*_opt/1 functions must be applied on transformed options
+%% only.
+
+is_connect_opt(Opt) ->
+ is_tcp_connect_opt(Opt) or is_ssl_opt(Opt).
+
+is_listen_opt(Opt) ->
+ is_tcp_listen_opt(Opt) or is_ssl_opt(Opt).
+
+is_tcp_accept_opt(Opt) ->
+ is_tcp_gen_opt(Opt).
+
+is_tcp_connect_opt(Opt) ->
+ is_tcp_gen_opt(Opt) or is_tcp_connect_only_opt(Opt).
+
+is_tcp_listen_opt(Opt) ->
+ is_tcp_gen_opt(Opt) or is_tcp_listen_only_opt(Opt).
+
+%% General options supported by gen_tcp: All except `reuseaddr' and
+%% `deliver'.
+is_tcp_gen_opt({mode, list}) -> true;
+is_tcp_gen_opt({mode, binary}) -> true;
+is_tcp_gen_opt({header, Sz}) when is_integer(Sz), 0 =< Sz -> true;
+is_tcp_gen_opt({packet, Sz}) when is_integer(Sz), 0 =< Sz, Sz =< 4-> true;
+is_tcp_gen_opt({packet, sunrm}) -> true;
+is_tcp_gen_opt({packet, asn1}) -> true;
+is_tcp_gen_opt({packet, cdr}) -> true;
+is_tcp_gen_opt({packet, fcgi}) -> true;
+is_tcp_gen_opt({packet, line}) -> true;
+is_tcp_gen_opt({packet, tpkt}) -> true;
+is_tcp_gen_opt({packet, http}) -> true;
+is_tcp_gen_opt({packet, httph}) -> true;
+is_tcp_gen_opt({nodelay, true}) -> true;
+is_tcp_gen_opt({nodelay, false}) -> true;
+is_tcp_gen_opt({active, true}) -> true;
+is_tcp_gen_opt({active, false}) -> true;
+is_tcp_gen_opt({active, once}) -> true;
+is_tcp_gen_opt({keepalive, true}) -> true;
+is_tcp_gen_opt({keepalive, false}) -> true;
+is_tcp_gen_opt({ip, Addr}) -> is_ip_address(Addr);
+is_tcp_gen_opt(_Opt) -> false.
+
+is_tcp_listen_only_opt({backlog, Size}) when is_integer(Size), 0 =< Size ->
+ true;
+is_tcp_listen_only_opt({reuseaddr, Bool}) when is_boolean(Bool) ->
+ true;
+is_tcp_listen_only_opt(_Opt) -> false.
+
+is_tcp_connect_only_opt({port, Port}) when is_integer(Port), 0 =< Port -> true;
+is_tcp_connect_only_opt(_Opt) -> false.
+
+%% SSL options
+
+is_ssl_opt({verify, Code}) when 0 =< Code, Code =< 2 -> true;
+is_ssl_opt({depth, Depth}) when 0 =< Depth -> true;
+is_ssl_opt({certfile, String}) -> is_string(String);
+is_ssl_opt({keyfile, String}) -> is_string(String);
+is_ssl_opt({password, String}) -> is_string(String);
+is_ssl_opt({cacertfile, String}) -> is_string(String);
+is_ssl_opt({ciphers, String}) -> is_string(String);
+is_ssl_opt({cachetimeout, Timeout}) when Timeout >= 0 -> true;
+is_ssl_opt(_Opt) -> false.
+
+%% Various types
+is_string(String) when is_list(String) ->
+ lists:all(fun (C) when is_integer(C), 0 =< C, C =< 255 -> true;
+ (_C) -> false end,
+ String);
+is_string(_) ->
+ false.
+
+is_ip_address(Addr) when tuple_size(Addr) =:= 4 ->
+ is_string(tuple_to_list(Addr));
+is_ip_address(Addr) when is_list(Addr) ->
+ is_string(Addr);
+is_ip_address(_) ->
+ false.
+
+get_tagged_opt(Tag, Opts, Default) ->
+ case lists:keysearch(Tag, 1, Opts) of
+ {value, {_, Value}} ->
+ Value;
+ _Other ->
+ Default
+ end.
+
+%%
+%% mk_ssl_optstr(Opts) -> string()
+%%
+%% Makes a "command line" string of SSL options
+%%
+mk_ssl_optstr(Opts) ->
+ lists:flatten([mk_one_ssl_optstr(O) || O <- Opts]).
+
+mk_one_ssl_optstr({verify, Code}) ->
+ [" -verify ", integer_to_list(Code)];
+mk_one_ssl_optstr({depth, Depth}) ->
+ [" -depth ", integer_to_list(Depth)];
+mk_one_ssl_optstr({certfile, String}) ->
+ [" -certfile ", String];
+mk_one_ssl_optstr({keyfile, String}) ->
+ [" -keyfile ", String];
+mk_one_ssl_optstr({password, String}) ->
+ [" -password ", String];
+mk_one_ssl_optstr({cacertfile, String}) ->
+ [" -cacertfile ", String];
+mk_one_ssl_optstr({ciphers, String}) ->
+ [" -ciphers ", String];
+mk_one_ssl_optstr({cachetimeout, Timeout}) ->
+ [" -cachetimeout ", integer_to_list(Timeout)];
+mk_one_ssl_optstr(_) ->
+ "".
+
+extract_opts(OptTags, Opts) ->
+ [O || O = {Tag,_} <- Opts, lists:member(Tag, OptTags)].
+
+replace_opts(NOpts, Opts) ->
+ lists:foldl(fun({Key, Val}, Acc) ->
+ lists:keyreplace(Key, 1, Acc, {Key, Val});
+ %% XXX Check. Patch from Chandrashekhar Mullaparthi.
+ (binary, Acc) ->
+ lists:keyreplace(mode, 1, Acc, {mode, binary})
+ end,
+ Opts, NOpts).
+
+%% Misc
+
+is_subset(A, B) ->
+ [] =:= A -- B.
diff --git a/lib/ssl/src/ssl_broker_int.hrl b/lib/ssl/src/ssl_broker_int.hrl
new file mode 100644
index 0000000000..b791485725
--- /dev/null
+++ b/lib/ssl/src/ssl_broker_int.hrl
@@ -0,0 +1,38 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+%% Purpose: record definitions shared between ssl_prim.erl and ssl_broker.erl
+
+-record(st, {brokertype = nil, % connector | listener | acceptor
+ server = nil, % pid of ssl_server
+ client = nil, % client pid
+ collector = nil, % client pid, or collector during change of
+ % controlling process
+ fd = nil, % fd of "external" socket in port program
+ active = true, % true | false | once
+ opts = [], % options
+ thissock = nil, % this sslsocket
+ proxysock = nil, % local proxy socket within Erlang
+ proxyport = nil, % local port for proxy within Erlang
+ status = nil, % open | closing | closed
+ encrypted = false, %
+ debug = false %
+ }).
diff --git a/lib/ssl/src/ssl_broker_sup.erl b/lib/ssl/src/ssl_broker_sup.erl
new file mode 100644
index 0000000000..6d56a5fcf6
--- /dev/null
+++ b/lib/ssl/src/ssl_broker_sup.erl
@@ -0,0 +1,46 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+%%% Purpose : Supervisor for brokers
+
+-module(ssl_broker_sup).
+
+-behaviour(supervisor).
+
+-export([start_link/0]).
+
+%% supervisor callbacks
+-export([init/1]).
+
+start_link() ->
+ supervisor:start_link({local, ssl_broker_sup}, ssl_broker_sup,
+ []).
+
+init([]) ->
+ {ok, {{simple_one_for_one, 10, 3600},
+ [{ssl_broker,
+ {ssl_broker, start_link, []},
+ temporary,
+ 100,
+ worker,
+ [ssl_broker]}
+ ]}}.
+
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
new file mode 100644
index 0000000000..d97b61a5ce
--- /dev/null
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -0,0 +1,156 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%----------------------------------------------------------------------
+%% Purpose: Help funtions for handling certificat verification.
+%% The path validation defined in ssl_handshake.erl that mainly
+%% calls functions in this module is described in RFC 3280.
+%%----------------------------------------------------------------------
+
+-module(ssl_certificate).
+
+-include("ssl_handshake.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_debug.hrl").
+
+-export([trusted_cert_and_path/3,
+ certificate_chain/2,
+ file_to_certificats/1]).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+
+trusted_cert_and_path(CertChain, CertDbRef, Verify) ->
+ [Cert | RestPath] = lists:reverse(CertChain),
+ {ok, OtpCert} = public_key:pkix_decode_cert(Cert, otp),
+ IssuerAnPath =
+ case public_key:pkix_is_self_signed(OtpCert) of
+ true ->
+ {ok, IssuerId} = public_key:pkix_issuer_id(OtpCert, self),
+ {IssuerId, RestPath};
+ false ->
+ case public_key:pkix_issuer_id(OtpCert, other) of
+ {ok, IssuerId} ->
+ {IssuerId, [Cert | RestPath]};
+ {error, issuer_not_found} ->
+ case find_issuer(OtpCert, no_candidate) of
+ {ok, IssuerId} ->
+ {IssuerId, [Cert | RestPath]};
+ Other ->
+ {Other, RestPath}
+ end
+ end
+ end,
+
+ case IssuerAnPath of
+ {{error, issuer_not_found}, _ } ->
+ %% The root CA was not sent and can not be found, we fail if verify = true
+ not_valid(?ALERT_REC(?FATAL, ?UNKNOWN_CA), Verify, {Cert, RestPath});
+ {{SerialNr, Issuer}, Path} ->
+ case ssl_certificate_db:lookup_trusted_cert(CertDbRef,
+ SerialNr, Issuer) of
+ {ok, {BinCert,_}} ->
+ {BinCert, Path, []};
+ _ ->
+ %% Fail if verify = true
+ not_valid(?ALERT_REC(?FATAL, ?UNKNOWN_CA),
+ Verify, {Cert, RestPath})
+ end
+ end.
+
+
+certificate_chain(undefined, _CertsDbRef) ->
+ {error, no_cert};
+certificate_chain(OwnCert, CertsDbRef) ->
+ {ok, ErlCert} = public_key:pkix_decode_cert(OwnCert, otp),
+ certificate_chain(ErlCert, OwnCert, CertsDbRef, [OwnCert]).
+
+file_to_certificats(File) ->
+ {ok, List} = ssl_manager:cache_pem_file(File),
+ [Bin || {cert, Bin, not_encrypted} <- List].
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+certificate_chain(OtpCert, _Cert, CertsDbRef, Chain) ->
+ IssuerAndSelfSigned =
+ case public_key:pkix_is_self_signed(OtpCert) of
+ true ->
+ {public_key:pkix_issuer_id(OtpCert, self), true};
+ false ->
+ {public_key:pkix_issuer_id(OtpCert, other), false}
+ end,
+
+ case IssuerAndSelfSigned of
+ {_, true = SelfSigned} ->
+ certificate_chain(CertsDbRef, Chain, ignore, ignore, SelfSigned);
+ {{error, issuer_not_found}, SelfSigned} ->
+ case find_issuer(OtpCert, no_candidate) of
+ {ok, {SerialNr, Issuer}} ->
+ certificate_chain(CertsDbRef, Chain,
+ SerialNr, Issuer, SelfSigned);
+ _ ->
+ %% Guess the the issuer must be the root
+ %% certificate. The verification of the
+ %% cert chain will fail if guess is
+ %% incorrect.
+ {ok, lists:reverse(Chain)}
+ end;
+ {{ok, {SerialNr, Issuer}}, SelfSigned} ->
+ certificate_chain(CertsDbRef, Chain, SerialNr, Issuer, SelfSigned)
+ end.
+
+certificate_chain(_CertsDbRef, Chain, _SerialNr, _Issuer, true) ->
+ {ok, lists:reverse(Chain)};
+
+certificate_chain(CertsDbRef, Chain, SerialNr, Issuer, _SelfSigned) ->
+ case ssl_certificate_db:lookup_trusted_cert(CertsDbRef,
+ SerialNr, Issuer) of
+ {ok, {IssuerCert, ErlCert}} ->
+ {ok, ErlCert} = public_key:pkix_decode_cert(IssuerCert, otp),
+ certificate_chain(ErlCert, IssuerCert,
+ CertsDbRef, [IssuerCert | Chain]);
+ _ ->
+ %% The trusted cert may be obmitted from the chain as the
+ %% counter part needs to have it anyway to be able to
+ %% verify it. This will be the normal case for servers
+ %% that does not verify the clients and hence have not
+ %% specified the cacertfile.
+ {ok, lists:reverse(Chain)}
+ end.
+
+find_issuer(OtpCert, PrevCandidateKey) ->
+ case ssl_certificate_db:issuer_candidate(PrevCandidateKey) of
+ no_more_candidates ->
+ {error, issuer_not_found};
+ {Key, {_Cert, ErlCertCandidate}} ->
+ case public_key:pkix_is_issuer(OtpCert, ErlCertCandidate) of
+ true ->
+ public_key:pkix_issuer_id(ErlCertCandidate, self);
+ false ->
+ find_issuer(OtpCert, Key)
+ end
+ end.
+
+not_valid(Alert, true, _) ->
+ throw(Alert);
+not_valid(_, false, {ErlCert, Path}) ->
+ {ErlCert, Path, [{bad_cert, unknown_ca}]}.
diff --git a/lib/ssl/src/ssl_certificate_db.erl b/lib/ssl/src/ssl_certificate_db.erl
new file mode 100644
index 0000000000..decc6c9fea
--- /dev/null
+++ b/lib/ssl/src/ssl_certificate_db.erl
@@ -0,0 +1,219 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%----------------------------------------------------------------------
+%% Purpose: Storage for trused certificats
+%%----------------------------------------------------------------------
+
+-module(ssl_certificate_db).
+
+-include_lib("public_key/include/public_key.hrl").
+
+-export([create/0, remove/1, add_trusted_certs/3,
+ remove_trusted_certs/2, lookup_trusted_cert/3, issuer_candidate/1,
+ cache_pem_file/3]).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: create() -> Db
+%% Db = term() - Reference to the crated database
+%%
+%% Description: Creates a new certificate db.
+%% Note: lookup_trusted_cert/3 may be called from any process but only
+%% the process that called create may call the other functions.
+%%--------------------------------------------------------------------
+create() ->
+ [ets:new(certificate_db_name(), [named_table, set, protected]),
+ ets:new(ssl_file_to_ref, [named_table, set, protected]),
+ ets:new(ssl_pid_to_file, [bag, private])].
+
+%%--------------------------------------------------------------------
+%% Function: delete(Db) -> _
+%% Db = Database refererence as returned by create/0
+%%
+%% Description: Removes database db
+%%--------------------------------------------------------------------
+remove(Dbs) ->
+ lists:foreach(fun(Db) -> true = ets:delete(Db) end, Dbs).
+
+%%--------------------------------------------------------------------
+%% Function: lookup_trusted_cert(Ref, SerialNumber, Issuer) -> {BinCert,DecodedCert}
+%% Ref = ref()
+%% SerialNumber = integer()
+%% Issuer = {rdnSequence, IssuerAttrs}
+%% BinCert = binary()
+%%
+%% Description: Retrives the trusted certificate identified by
+%% <SerialNumber, Issuer>. Ref is used as it is specified
+%% for each connection which certificates are trusted.
+%%--------------------------------------------------------------------
+lookup_trusted_cert(Ref, SerialNumber, Issuer) ->
+ case lookup({Ref, SerialNumber, Issuer}, certificate_db_name()) of
+ undefined ->
+ undefined;
+ [Certs] ->
+ {ok, Certs}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: add_trusted_certs(Pid, File, Db) -> {ok, Ref}
+%% Pid = pid()
+%% File = string()
+%% Db = Database refererence as returned by create/0
+%% Ref = ref()
+%%
+%% Description: Adds the trusted certificates from file <File> to the
+%% runtime database. Returns Ref that should be handed to lookup_trusted_cert
+%% together with the cert serialnumber and issuer.
+%%--------------------------------------------------------------------
+add_trusted_certs(Pid, File, [CertsDb, FileToRefDb, PidToFileDb]) ->
+ Ref = case lookup(File, FileToRefDb) of
+ undefined ->
+ NewRef = make_ref(),
+ add_certs_from_file(File, NewRef, CertsDb),
+ insert(File, NewRef, 1, FileToRefDb),
+ NewRef;
+ [OldRef] ->
+ ref_count(File,FileToRefDb,1),
+ OldRef
+ end,
+ insert(Pid, File, PidToFileDb),
+ {ok, Ref}.
+
+%%--------------------------------------------------------------------
+%% Function: cache_pem_file(Pid, File, Db) -> FileContent
+%%
+%% Description: Cache file as binary in DB
+%%--------------------------------------------------------------------
+cache_pem_file(Pid, File, [_CertsDb, FileToRefDb, PidToFileDb]) ->
+ try ref_count(File, FileToRefDb,1)
+ catch _:_ ->
+ {ok, Content} = public_key:pem_to_der(File),
+ insert(File,Content,1,FileToRefDb)
+ end,
+ insert(Pid, File, PidToFileDb),
+ {ok, FileToRefDb}.
+
+%%--------------------------------------------------------------------
+%% Function: remove_trusted_certs(Pid, Db) -> _
+%%
+%% Description: Removes trusted certs originating from
+%% the file associated to Pid from the runtime database.
+%%--------------------------------------------------------------------
+remove_trusted_certs(Pid, [CertsDb, FileToRefDb, PidToFileDb]) ->
+ Files = lookup(Pid, PidToFileDb),
+ delete(Pid, PidToFileDb),
+ Clear = fun(File) ->
+ case ref_count(File, FileToRefDb, -1) of
+ 0 ->
+ case lookup(File, FileToRefDb) of
+ [Ref] when is_reference(Ref) ->
+ remove_certs(Ref, CertsDb);
+ _ -> ok
+ end,
+ delete(File, FileToRefDb);
+ _ ->
+ ok
+ end
+ end,
+ case Files of
+ undefined -> ok;
+ _ ->
+ [Clear(File) || File <- Files],
+ ok
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: issuer_candidate() -> {Key, Candidate} | no_more_candidates
+%%
+%% Candidate
+%%
+%%
+%% Description: If a certificat does not define its issuer through
+%% the extension 'ce-authorityKeyIdentifier' we can
+%% try to find the issuer in the database over known
+%% certificates.
+%%--------------------------------------------------------------------
+issuer_candidate(no_candidate) ->
+ Db = certificate_db_name(),
+ case ets:first(Db) of
+ '$end_of_table' ->
+ no_more_candidates;
+ Key ->
+ [Cert] = lookup(Key, Db),
+ {Key, Cert}
+ end;
+
+issuer_candidate(PrevCandidateKey) ->
+ Db = certificate_db_name(),
+ case ets:next(Db, PrevCandidateKey) of
+ '$end_of_table' ->
+ no_more_candidates;
+ Key ->
+ [Cert] = lookup(Key, Db),
+ {Key, Cert}
+ end.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+certificate_db_name() ->
+ ssl_otp_certificate_db.
+
+insert(Key, Data, Db) ->
+ true = ets:insert(Db, {Key, Data}).
+
+insert(Key, Data, Count, Db) ->
+ true = ets:insert(Db, {Key, Count, Data}).
+
+ref_count(Key, Db,N) ->
+ ets:update_counter(Db,Key,N).
+
+delete(Key, Db) ->
+ true = ets:delete(Db, Key).
+
+lookup(Key, Db) ->
+ case ets:lookup(Db, Key) of
+ [] ->
+ undefined;
+ Contents ->
+ Pick = fun({_, Data}) -> Data;
+ ({_,_,Data}) -> Data
+ end,
+ [Pick(Data) || Data <- Contents]
+ end.
+
+remove_certs(Ref, CertsDb) ->
+ ets:match_delete(CertsDb, {{Ref, '_', '_'}, '_'}).
+
+add_certs_from_file(File, Ref, CertsDb) ->
+ Decode = fun(Cert) ->
+ {ok, ErlCert} = public_key:pkix_decode_cert(Cert, otp),
+ TBSCertificate = ErlCert#'OTPCertificate'.tbsCertificate,
+ SerialNumber = TBSCertificate#'OTPTBSCertificate'.serialNumber,
+ Issuer = public_key:pkix_normalize_general_name(
+ TBSCertificate#'OTPTBSCertificate'.issuer),
+ insert({Ref, SerialNumber, Issuer}, {Cert,ErlCert}, CertsDb)
+ end,
+ {ok,Der} = public_key:pem_to_der(File),
+ [Decode(Cert) || {cert, Cert, not_encrypted} <- Der].
+
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
new file mode 100644
index 0000000000..3d3d11b7f3
--- /dev/null
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -0,0 +1,784 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Help functions for handling the SSL ciphers
+%%
+%%----------------------------------------------------------------------
+
+-module(ssl_cipher).
+
+-include("ssl_internal.hrl").
+-include("ssl_record.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_debug.hrl").
+
+-export([security_parameters/2, suite_definition/1,
+ decipher/4, cipher/4,
+ suite/1, suites/1,
+ openssl_suite/1, openssl_suite_name/1]).
+
+-compile(inline).
+
+%%--------------------------------------------------------------------
+%% Function: security_parameters(CipherSuite, SecParams) ->
+%% #security_parameters{}
+%%
+%% CipherSuite - as defined in ssl_cipher.hrl
+%% SecParams - #security_parameters{}
+%%
+%% Description: Returns a security parameters record where the
+%% cipher values has been updated according to <CipherSuite>
+%%-------------------------------------------------------------------
+security_parameters(CipherSuite, SecParams) ->
+ { _, Cipher, Hash, Exportable} = suite_definition(CipherSuite),
+ SecParams#security_parameters{
+ cipher_suite = CipherSuite,
+ bulk_cipher_algorithm = bulk_cipher_algorithm(Cipher),
+ cipher_type = type(Cipher),
+ key_size = effective_key_bits(Cipher),
+ expanded_key_material_length = expanded_key_material(Cipher),
+ key_material_length = key_material(Cipher),
+ iv_size = iv_size(Cipher),
+ mac_algorithm = mac_algorithm(Hash),
+ hash_size = hash_size(Hash),
+ exportable = Exportable}.
+
+%%--------------------------------------------------------------------
+%% Function: cipher(Method, CipherState, Mac, Data) ->
+%% {Encrypted, UpdateCipherState}
+%%
+%% Method - integer() (as defined in ssl_cipher.hrl)
+%% CipherState, UpdatedCipherState - #cipher_state{}
+%% Data, Encrypted - binary()
+%%
+%% Description: Encrypts the data and the mac using method, updating
+%% the cipher state
+%%-------------------------------------------------------------------
+cipher(?NULL, CipherState, <<>>, Fragment) ->
+ GenStreamCipherList = [Fragment, <<>>],
+ {GenStreamCipherList, CipherState};
+cipher(?RC4, CipherState, Mac, Fragment) ->
+ State0 = case CipherState#cipher_state.state of
+ undefined -> crypto:rc4_set_key(CipherState#cipher_state.key);
+ S -> S
+ end,
+ GenStreamCipherList = [Fragment, Mac],
+
+ ?DBG_HEX(GenStreamCipherList),
+ ?DBG_HEX(State0),
+ {State1, T} = crypto:rc4_encrypt_with_state(State0, GenStreamCipherList),
+ ?DBG_HEX(T),
+ {T, CipherState#cipher_state{state = State1}};
+cipher(?DES, CipherState, Mac, Fragment) ->
+ block_cipher(fun(Key, IV, T) ->
+ crypto:des_cbc_encrypt(Key, IV, T)
+ end, block_size(des_cbc), CipherState, Mac, Fragment);
+cipher(?DES40, CipherState, Mac, Fragment) ->
+ block_cipher(fun(Key, IV, T) ->
+ crypto:des_cbc_encrypt(Key, IV, T)
+ end, block_size(des_cbc), CipherState, Mac, Fragment);
+cipher(?'3DES', CipherState, Mac, Fragment) ->
+ block_cipher(fun(<<K1:8/binary, K2:8/binary, K3:8/binary>>, IV, T) ->
+ crypto:des3_cbc_encrypt(K1, K2, K3, IV, T)
+ end, block_size(des_cbc), CipherState, Mac, Fragment);
+cipher(?AES, CipherState, Mac, Fragment) ->
+ block_cipher(fun(Key, IV, T) when byte_size(Key) =:= 16 ->
+ crypto:aes_cbc_128_encrypt(Key, IV, T);
+ (Key, IV, T) when byte_size(Key) =:= 32 ->
+ crypto:aes_cbc_256_encrypt(Key, IV, T)
+ end, block_size(aes_128_cbc), CipherState, Mac, Fragment);
+%% cipher(?IDEA, CipherState, Mac, Fragment) ->
+%% block_cipher(fun(Key, IV, T) ->
+%% crypto:idea_cbc_encrypt(Key, IV, T)
+%% end, block_size(idea_cbc), CipherState, Mac, Fragment);
+cipher(?RC2, CipherState, Mac, Fragment) ->
+ block_cipher(fun(Key, IV, T) ->
+ crypto:rc2_40_cbc_encrypt(Key, IV, T)
+ end, block_size(rc2_cbc_40), CipherState, Mac, Fragment).
+
+block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0,
+ Mac, Fragment) ->
+ TotSz = byte_size(Mac) + erlang:iolist_size(Fragment) + 1,
+ {PaddingLength, Padding} = get_padding(TotSz, BlockSz),
+ L = [Fragment, Mac, PaddingLength, Padding],
+ ?DBG_HEX(Key),
+ ?DBG_HEX(IV),
+ ?DBG_HEX(L),
+ T = Fun(Key, IV, L),
+ ?DBG_HEX(T),
+ NextIV = next_iv(T, IV),
+ {T, CS0#cipher_state{iv=NextIV}}.
+
+%%--------------------------------------------------------------------
+%% Function: decipher(Method, CipherState, Mac, Data) ->
+%% {Decrypted, UpdateCipherState}
+%%
+%% Method - integer() (as defined in ssl_cipher.hrl)
+%% CipherState, UpdatedCipherState - #cipher_state{}
+%% Data, Encrypted - binary()
+%%
+%% Description: Decrypts the data and the mac using method, updating
+%% the cipher state
+%%-------------------------------------------------------------------
+decipher(?NULL, _HashSz, CipherState, Fragment) ->
+ {Fragment, <<>>, CipherState};
+decipher(?RC4, HashSz, CipherState, Fragment) ->
+ ?DBG_TERM(CipherState#cipher_state.key),
+ State0 = case CipherState#cipher_state.state of
+ undefined -> crypto:rc4_set_key(CipherState#cipher_state.key);
+ S -> S
+ end,
+ ?DBG_HEX(State0),
+ ?DBG_HEX(Fragment),
+ {State1, T} = crypto:rc4_encrypt_with_state(State0, Fragment),
+ ?DBG_HEX(T),
+ GSC = generic_stream_cipher_from_bin(T, HashSz),
+ #generic_stream_cipher{content=Content, mac=Mac} = GSC,
+ {Content, Mac, CipherState#cipher_state{state=State1}};
+decipher(?DES, HashSz, CipherState, Fragment) ->
+ block_decipher(fun(Key, IV, T) ->
+ crypto:des_cbc_decrypt(Key, IV, T)
+ end, CipherState, HashSz, Fragment);
+decipher(?DES40, HashSz, CipherState, Fragment) ->
+ block_decipher(fun(Key, IV, T) ->
+ crypto:des_cbc_decrypt(Key, IV, T)
+ end, CipherState, HashSz, Fragment);
+decipher(?'3DES', HashSz, CipherState, Fragment) ->
+ block_decipher(fun(<<K1:8/binary, K2:8/binary, K3:8/binary>>, IV, T) ->
+ crypto:des3_cbc_decrypt(K1, K2, K3, IV, T)
+ end, CipherState, HashSz, Fragment);
+decipher(?AES, HashSz, CipherState, Fragment) ->
+ block_decipher(fun(Key, IV, T) when byte_size(Key) =:= 16 ->
+ crypto:aes_cbc_128_decrypt(Key, IV, T);
+ (Key, IV, T) when byte_size(Key) =:= 32 ->
+ crypto:aes_cbc_256_decrypt(Key, IV, T)
+ end, CipherState, HashSz, Fragment);
+%% decipher(?IDEA, HashSz, CipherState, Fragment) ->
+%% block_decipher(fun(Key, IV, T) ->
+%% crypto:idea_cbc_decrypt(Key, IV, T)
+%% end, CipherState, HashSz, Fragment);
+decipher(?RC2, HashSz, CipherState, Fragment) ->
+ block_decipher(fun(Key, IV, T) ->
+ crypto:rc2_40_cbc_decrypt(Key, IV, T)
+ end, CipherState, HashSz, Fragment).
+
+block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0,
+ HashSz, Fragment) ->
+ ?DBG_HEX(Key),
+ ?DBG_HEX(IV),
+ ?DBG_HEX(Fragment),
+ T = Fun(Key, IV, Fragment),
+ ?DBG_HEX(T),
+ GBC = generic_block_cipher_from_bin(T, HashSz),
+ ok = check_padding(GBC), %% TODO kolla ocks�...
+ Content = GBC#generic_block_cipher.content,
+ Mac = GBC#generic_block_cipher.mac,
+ CipherState1 = CipherState0#cipher_state{iv=next_iv(Fragment, IV)},
+ {Content, Mac, CipherState1}.
+
+%%--------------------------------------------------------------------
+%% Function: suites(Version) -> [Suite]
+%%
+%% Version = version()
+%% Suite = binary() from ssl_cipher.hrl
+%%
+%% Description: Returns a list of supported cipher suites.
+%%--------------------------------------------------------------------
+suites({3, 0}) ->
+ ssl_ssl3:suites();
+suites({3, N}) when N == 1; N == 2 ->
+ ssl_tls1:suites().
+
+%%--------------------------------------------------------------------
+%% Function: suite_definition(CipherSuite) ->
+%% {KeyExchange, Cipher, Hash, Exportable}
+%%
+%%
+%% CipherSuite - as defined in ssl_cipher.hrl
+%% KeyExchange - rsa | dh_dss | dh_rsa | dh_anon | dhe_dss | dhe_rsa
+%% krb5 | *_export (old ssl)
+%% Cipher - null | rc4_128 | idea_cbc | des_cbc | '3des_ede_cbc'
+%% des40_cbc | dh_dss | aes_128_cbc | aes_256_cbc |
+%% rc2_cbc_40 | rc4_40
+%% Hash - null | md5 | sha
+%% Exportable - export | no_export | ignore(?)
+%%
+%% Description: Returns a security parameters record where the
+%% cipher values has been updated according to <CipherSuite>
+%% Note: since idea is unsupported on the openssl version used by
+%% crypto (as of OTP R12B), we've commented away the idea stuff
+%%-------------------------------------------------------------------
+%% TLS v1.1 suites
+suite_definition(?TLS_NULL_WITH_NULL_NULL) ->
+ {null, null, null, ignore};
+suite_definition(?TLS_RSA_WITH_NULL_MD5) ->
+ {rsa, null, md5, ignore};
+suite_definition(?TLS_RSA_WITH_NULL_SHA) ->
+ {rsa, null, sha, ignore};
+suite_definition(?TLS_RSA_WITH_RC4_128_MD5) -> % ok
+ {rsa, rc4_128, md5, no_export};
+suite_definition(?TLS_RSA_WITH_RC4_128_SHA) -> % ok
+ {rsa, rc4_128, sha, no_export};
+%% suite_definition(?TLS_RSA_WITH_IDEA_CBC_SHA) -> % unsupported
+%% {rsa, idea_cbc, sha, no_export};
+suite_definition(?TLS_RSA_WITH_DES_CBC_SHA) -> % ok
+ {rsa, des_cbc, sha, no_export};
+suite_definition(?TLS_RSA_WITH_3DES_EDE_CBC_SHA) ->
+ {rsa, '3des_ede_cbc', sha, no_export};
+suite_definition(?TLS_DH_DSS_WITH_DES_CBC_SHA) ->
+ {dh_dss, des_cbc, sha, no_export};
+suite_definition(?TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA) ->
+ {dh_dss, '3des_ede_cbc', sha, no_export};
+suite_definition(?TLS_DH_RSA_WITH_DES_CBC_SHA) ->
+ {dh_rsa, des_cbc, sha, no_export};
+suite_definition(?TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA) ->
+ {dh_rsa, '3des_ede_cbc', sha, no_export};
+suite_definition(?TLS_DHE_DSS_WITH_DES_CBC_SHA) ->
+ {dhe_dss, des_cbc, sha, no_export};
+suite_definition(?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA) ->
+ {dhe_dss, '3des_ede_cbc', sha, no_export};
+suite_definition(?TLS_DHE_RSA_WITH_DES_CBC_SHA) ->
+ {dhe_rsa, des_cbc, sha, no_export};
+suite_definition(?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) ->
+ {dhe_rsa, '3des_ede_cbc', sha, no_export};
+suite_definition(?TLS_DH_anon_WITH_RC4_128_MD5) ->
+ {dh_anon, rc4_128, md5, no_export};
+suite_definition(?TLS_DH_anon_WITH_DES_CBC_SHA) ->
+ {dh_anon, des40_cbc, sha, no_export};
+suite_definition(?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA) ->
+ {dh_anon, '3des_ede_cbc', sha, no_export};
+
+%%% TSL V1.1 AES suites
+suite_definition(?TLS_RSA_WITH_AES_128_CBC_SHA) -> % ok
+ {rsa, aes_128_cbc, sha, ignore};
+suite_definition(?TLS_DH_DSS_WITH_AES_128_CBC_SHA) ->
+ {dh_dss, aes_128_cbc, sha, ignore};
+suite_definition(?TLS_DH_RSA_WITH_AES_128_CBC_SHA) ->
+ {dh_rsa, aes_128_cbc, sha, ignore};
+suite_definition(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA) ->
+ {dhe_dss, aes_128_cbc, sha, ignore};
+suite_definition(?TLS_DHE_RSA_WITH_AES_128_CBC_SHA) ->
+ {dhe_rsa, aes_128_cbc, sha, ignore};
+suite_definition(?TLS_DH_anon_WITH_AES_128_CBC_SHA) ->
+ {dh_anon, aes_128_cbc, sha, ignore};
+suite_definition(?TLS_RSA_WITH_AES_256_CBC_SHA) -> % ok
+ {rsa, aes_256_cbc, sha, ignore};
+suite_definition(?TLS_DH_DSS_WITH_AES_256_CBC_SHA) ->
+ {dh_dss, aes_256_cbc, sha, ignore};
+suite_definition(?TLS_DH_RSA_WITH_AES_256_CBC_SHA) ->
+ {dh_rsa, aes_256_cbc, sha, ignore};
+suite_definition(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA) ->
+ {dhe_dss, aes_256_cbc, sha, ignore};
+suite_definition(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA) ->
+ {dhe_rsa, aes_256_cbc, sha, ignore};
+suite_definition(?TLS_DH_anon_WITH_AES_256_CBC_SHA) ->
+ {dh_anon, aes_256_cbc, sha, ignore};
+
+%% TSL V1.1 KRB SUITES
+suite_definition(?TLS_KRB5_WITH_DES_CBC_SHA) ->
+ {krb5, des_cbc, sha, ignore};
+suite_definition(?TLS_KRB5_WITH_3DES_EDE_CBC_SHA) ->
+ {krb5, '3des_ede_cbc', sha, ignore};
+suite_definition(?TLS_KRB5_WITH_RC4_128_SHA) ->
+ {krb5, rc4_128, sha, ignore};
+%% suite_definition(?TLS_KRB5_WITH_IDEA_CBC_SHA) ->
+%% {krb5, idea_cbc, sha, ignore};
+suite_definition(?TLS_KRB5_WITH_DES_CBC_MD5) ->
+ {krb5, des_cbc, md5, ignore};
+suite_definition(?TLS_KRB5_WITH_3DES_EDE_CBC_MD5) ->
+ {krb5, '3des_ede_cbc', md5, ignore};
+suite_definition(?TLS_KRB5_WITH_RC4_128_MD5) ->
+ {krb5, rc4_128, md5, ignore};
+%% suite_definition(?TLS_KRB5_WITH_IDEA_CBC_MD5) ->
+%% {krb5, idea_cbc, md5, ignore};
+
+suite_definition(?TLS_RSA_EXPORT1024_WITH_RC4_56_MD5) ->
+ {rsa, rc4_56, md5, export};
+suite_definition(?TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5) ->
+ {rsa, rc2_cbc_56, md5, export};
+suite_definition(?TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA) ->
+ {rsa, des_cbc, sha, export};
+suite_definition(?TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA) ->
+ {dhe_dss, des_cbc, sha, export};
+suite_definition(?TLS_RSA_EXPORT1024_WITH_RC4_56_SHA) ->
+ {rsa, rc4_56, sha, export};
+suite_definition(?TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA) ->
+ {dhe_dss, rc4_56, sha, export};
+suite_definition(?TLS_DHE_DSS_WITH_RC4_128_SHA) ->
+ {dhe_dss, rc4_128, sha, export};
+
+%% Export suites TLS 1.0 OR SSLv3-only servers.
+suite_definition(?TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA) ->
+ {krb5_export, des40_cbc, sha, export};
+suite_definition(?TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA) ->
+ {krb5_export, rc2_cbc_40, sha, export};
+suite_definition(?TLS_KRB5_EXPORT_WITH_RC4_40_SHA) ->
+ {krb5_export, des40_cbc, sha, export};
+suite_definition(?TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5) ->
+ {krb5_export, des40_cbc, md5, export};
+suite_definition(?TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5) ->
+ {krb5_export, rc2_cbc_40, md5, export};
+suite_definition(?TLS_KRB5_EXPORT_WITH_RC4_40_MD5) ->
+ {krb5_export, rc2_cbc_40, md5, export};
+suite_definition(?TLS_RSA_EXPORT_WITH_RC4_40_MD5) -> % ok
+ {rsa, rc4_40, md5, export};
+suite_definition(?TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5) -> % ok
+ {rsa, rc2_cbc_40, md5, export};
+suite_definition(?TLS_RSA_EXPORT_WITH_DES40_CBC_SHA) ->
+ {rsa, des40_cbc, sha, export};
+suite_definition(?TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA) ->
+ {dh_dss, des40_cbc, sha, export};
+suite_definition(?TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA) ->
+ {dh_rsa, des40_cbc, sha, export};
+suite_definition(?TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA) ->
+ {dhe_dss, des40_cbc, sha, export};
+suite_definition(?TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA) ->
+ {dhe_rsa, des40_cbc, sha, export};
+suite_definition(?TLS_DH_anon_EXPORT_WITH_RC4_40_MD5) ->
+ {dh_anon, rc4_40, md5, export};
+suite_definition(?TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA) ->
+ {dh_anon, des40_cbc, sha, export}.
+
+%% TLS v1.1 suites
+suite({rsa, null, md5, ignore}) ->
+ ?TLS_RSA_WITH_NULL_MD5;
+suite({rsa, null, sha, ignore}) ->
+ ?TLS_RSA_WITH_NULL_SHA;
+suite({rsa, rc4_128, md5, no_export}) ->
+ ?TLS_RSA_WITH_RC4_128_MD5;
+suite({rsa, rc4_128, sha, no_export}) ->
+ ?TLS_RSA_WITH_RC4_128_SHA;
+%% suite({rsa, idea_cbc, sha, no_export}) ->
+%% ?TLS_RSA_WITH_IDEA_CBC_SHA;
+suite({rsa, des_cbc, sha, no_export}) ->
+ ?TLS_RSA_WITH_DES_CBC_SHA;
+suite({rsa, '3des_ede_cbc', sha, no_export}) ->
+ ?TLS_RSA_WITH_3DES_EDE_CBC_SHA;
+suite({dh_dss, des_cbc, sha, no_export}) ->
+ ?TLS_DH_DSS_WITH_DES_CBC_SHA;
+suite({dh_dss, '3des_ede_cbc', sha, no_export}) ->
+ ?TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA;
+suite({dh_rsa, des_cbc, sha, no_export}) ->
+ ?TLS_DH_RSA_WITH_DES_CBC_SHA;
+suite({dh_rsa, '3des_ede_cbc', sha, no_export}) ->
+ ?TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA;
+suite({dhe_dss, des_cbc, sha, no_export}) ->
+ ?TLS_DHE_DSS_WITH_DES_CBC_SHA;
+suite({dhe_dss, '3des_ede_cbc', sha, no_export}) ->
+ ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA;
+suite({dhe_rsa, des_cbc, sha, no_export}) ->
+ ?TLS_DHE_RSA_WITH_DES_CBC_SHA;
+suite({dhe_rsa, '3des_ede_cbc', sha, no_export}) ->
+ ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA;
+suite({dh_anon, rc4_128, md5, no_export}) ->
+ ?TLS_DH_anon_WITH_RC4_128_MD5;
+suite({dh_anon, des40_cbc, sha, no_export}) ->
+ ?TLS_DH_anon_WITH_DES_CBC_SHA;
+suite({dh_anon, '3des_ede_cbc', sha, no_export}) ->
+ ?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA;
+
+%%% TSL V1.1 AES suites
+suite({rsa, aes_128_cbc, sha, ignore}) ->
+ ?TLS_RSA_WITH_AES_128_CBC_SHA;
+suite({dh_dss, aes_128_cbc, sha, ignore}) ->
+ ?TLS_DH_DSS_WITH_AES_128_CBC_SHA;
+suite({dh_rsa, aes_128_cbc, sha, ignore}) ->
+ ?TLS_DH_RSA_WITH_AES_128_CBC_SHA;
+suite({dhe_dss, aes_128_cbc, sha, ignore}) ->
+ ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA;
+suite({dhe_rsa, aes_128_cbc, sha, ignore}) ->
+ ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA;
+suite({dh_anon, aes_128_cbc, sha, ignore}) ->
+ ?TLS_DH_anon_WITH_AES_128_CBC_SHA;
+suite({rsa, aes_256_cbc, sha, ignore}) ->
+ ?TLS_RSA_WITH_AES_256_CBC_SHA;
+suite({dh_dss, aes_256_cbc, sha, ignore}) ->
+ ?TLS_DH_DSS_WITH_AES_256_CBC_SHA;
+suite({dh_rsa, aes_256_cbc, sha, ignore}) ->
+ ?TLS_DH_RSA_WITH_AES_256_CBC_SHA;
+suite({dhe_dss, aes_256_cbc, sha, ignore}) ->
+ ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA;
+suite({dhe_rsa, aes_256_cbc, sha, ignore}) ->
+ ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA;
+suite({dh_anon, aes_256_cbc, sha, ignore}) ->
+ ?TLS_DH_anon_WITH_AES_256_CBC_SHA;
+
+%% TSL V1.1 KRB SUITES
+suite({krb5, des_cbc, sha, ignore}) ->
+ ?TLS_KRB5_WITH_DES_CBC_SHA;
+suite({krb5_cbc, '3des_ede_cbc', sha, ignore}) ->
+ ?TLS_KRB5_WITH_3DES_EDE_CBC_SHA;
+suite({krb5, rc4_128, sha, ignore}) ->
+ ?TLS_KRB5_WITH_RC4_128_SHA;
+%% suite({krb5_cbc, idea_cbc, sha, ignore}) ->
+%% ?TLS_KRB5_WITH_IDEA_CBC_SHA;
+suite({krb5_cbc, md5, ignore}) ->
+ ?TLS_KRB5_WITH_DES_CBC_MD5;
+suite({krb5_ede_cbc, des_cbc, md5, ignore}) ->
+ ?TLS_KRB5_WITH_3DES_EDE_CBC_MD5;
+suite({krb5_128, rc4_128, md5, ignore}) ->
+ ?TLS_KRB5_WITH_RC4_128_MD5;
+%% suite({krb5, idea_cbc, md5, ignore}) ->
+%% ?TLS_KRB5_WITH_IDEA_CBC_MD5;
+
+%% Export suites TLS 1.0 OR SSLv3-only servers.
+suite({rsa, rc4_40, md5, export}) ->
+ ?TLS_RSA_EXPORT_WITH_RC4_40_MD5;
+suite({rsa, rc2_cbc_40, md5, export}) ->
+ ?TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5;
+suite({rsa, des40_cbc, sha, export}) ->
+ ?TLS_RSA_EXPORT_WITH_DES40_CBC_SHA;
+suite({rsa, rc4_56, md5, export}) ->
+ ?TLS_RSA_EXPORT1024_WITH_RC4_56_MD5;
+suite({rsa, rc2_cbc_56, md5, export}) ->
+ ?TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5;
+suite({rsa, des_cbc, sha, export}) ->
+ ?TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA;
+suite({dhe_dss, des_cbc, sha, export}) ->
+ ?TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA;
+suite({rsa, rc4_56, sha, export}) ->
+ ?TLS_RSA_EXPORT1024_WITH_RC4_56_SHA;
+suite({dhe_dss, rc4_56, sha, export}) ->
+ ?TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA;
+suite({dhe_dss, rc4_128, sha, export}) ->
+ ?TLS_DHE_DSS_WITH_RC4_128_SHA;
+suite({krb5_export, des40_cbc, sha, export}) ->
+ ?TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA;
+suite({krb5_export, rc2_cbc_40, sha, export}) ->
+ ?TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA;
+suite({krb5_export, rc4_cbc_40, sha, export}) ->
+ ?TLS_KRB5_EXPORT_WITH_RC4_40_SHA;
+suite({krb5_export, des40_cbc, md5, export}) ->
+ ?TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5;
+suite({krb5_export, rc2_cbc_40, md5, export}) ->
+ ?TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5;
+suite({krb5_export, rc4_cbc_40, md5, export}) ->
+ ?TLS_KRB5_EXPORT_WITH_RC4_40_MD5;
+suite({rsa_export, rc4_cbc_40, md5, export}) ->
+ ?TLS_RSA_EXPORT_WITH_RC4_40_MD5;
+suite({rsa_export, rc2_cbc_40, md5, export}) ->
+ ?TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5;
+suite({rsa_export, des40_cbc, sha, export}) ->
+ ?TLS_RSA_EXPORT_WITH_DES40_CBC_SHA;
+suite({dh_dss_export, des40_cbc, sha, export}) ->
+ ?TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA;
+suite({dh_rsa_export, des40_cbc, sha, export}) ->
+ ?TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA;
+suite({dhe_dss_export, des40_cbc, sha, export}) ->
+ ?TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA;
+suite({dhe_rsa_export, des40_cbc, sha, export}) ->
+ ?TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA;
+suite({dh_anon_export, rc4_40, md5, export}) ->
+ ?TLS_DH_anon_EXPORT_WITH_RC4_40_MD5;
+suite({dh_anon_export, des40_cbc, sha, export}) ->
+ ?TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA.
+
+
+%% translate constants <-> openssl-strings
+%% TODO: Is there a pattern in the nameing
+%% that is useable to make a nicer function defention?
+
+openssl_suite("DHE-RSA-AES256-SHA") ->
+ ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA;
+openssl_suite("DHE-DSS-AES256-SHA") ->
+ ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA;
+openssl_suite("AES256-SHA") ->
+ ?TLS_RSA_WITH_AES_256_CBC_SHA;
+openssl_suite("EDH-RSA-DES-CBC3-SHA") ->
+ ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA;
+openssl_suite("EDH-DSS-DES-CBC3-SHA") ->
+ ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA;
+openssl_suite("DES-CBC3-SHA") ->
+ ?TLS_RSA_WITH_3DES_EDE_CBC_SHA;
+openssl_suite("DHE-RSA-AES128-SHA") ->
+ ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA;
+openssl_suite("DHE-DSS-AES128-SHA") ->
+ ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA;
+openssl_suite("AES128-SHA") ->
+ ?TLS_RSA_WITH_AES_128_CBC_SHA;
+%% TODO: Do we want to support this?
+%% openssl_suite("DHE-DSS-RC4-SHA") ->
+%% ?TLS_DHE_DSS_WITH_RC4_128_SHA;
+%%openssl_suite("IDEA-CBC-SHA") ->
+%% ?TLS_RSA_WITH_IDEA_CBC_SHA;
+openssl_suite("RC4-SHA") ->
+ ?TLS_RSA_WITH_RC4_128_SHA;
+openssl_suite("RC4-MD5") ->
+ ?TLS_RSA_WITH_RC4_128_MD5;
+%% TODO: Do we want to support this?
+openssl_suite("EXP1024-RC4-MD5") ->
+ ?TLS_RSA_EXPORT1024_WITH_RC4_56_MD5;
+openssl_suite("EXP1024-RC2-CBC-MD5") ->
+ ?TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5;
+openssl_suite("EXP1024-DES-CBC-SHA") ->
+ ?TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA;
+openssl_suite("EXP1024-DHE-DSS-DES-CBC-SHA") ->
+ ?TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA;
+openssl_suite("EXP1024-RC4-SHA") ->
+ ?TLS_RSA_EXPORT1024_WITH_RC4_56_SHA;
+openssl_suite("EXP1024-DHE-DSS-RC4-SHA") ->
+ ?TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA;
+openssl_suite("DHE-DSS-RC4-SHA") ->
+ ?TLS_DHE_DSS_WITH_RC4_128_SHA;
+
+openssl_suite("EDH-RSA-DES-CBC-SHA") ->
+ ?TLS_DHE_RSA_WITH_DES_CBC_SHA;
+openssl_suite("DES-CBC-SHA") ->
+ ?TLS_RSA_WITH_DES_CBC_SHA;
+openssl_suite("EXP-EDH-RSA-DES-CBC-SHA") ->
+ ?TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA;
+openssl_suite("EXP-EDH-DSS-DES-CBC-SHA") ->
+ ?TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA;
+openssl_suite("EXP-DES-CBC-SHA") ->
+ ?TLS_RSA_EXPORT_WITH_DES40_CBC_SHA;
+openssl_suite("EXP-RC2-CBC-MD5") ->
+ ?TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5;
+openssl_suite("EXP-RC4-MD5") ->
+ ?TLS_RSA_EXPORT_WITH_RC4_40_MD5.
+
+openssl_suite_name(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA) ->
+ "DHE-RSA-AES256-SHA";
+openssl_suite_name(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA) ->
+ "DHE-DSS-AES256-SHA";
+openssl_suite_name(?TLS_RSA_WITH_AES_256_CBC_SHA) ->
+ "AES256-SHA";
+openssl_suite_name(?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) ->
+ "EDH-RSA-DES-CBC3-SHA";
+openssl_suite_name(?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA) ->
+ "EDH-DSS-DES-CBC3-SHA";
+openssl_suite_name(?TLS_RSA_WITH_3DES_EDE_CBC_SHA) ->
+ "DES-CBC3-SHA";
+openssl_suite_name( ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA) ->
+ "DHE-RSA-AES128-SHA";
+openssl_suite_name(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA) ->
+ "DHE-DSS-AES128-SHA";
+openssl_suite_name(?TLS_RSA_WITH_AES_128_CBC_SHA) ->
+ "AES128-SHA";
+%% openssl_suite_name(?TLS_RSA_WITH_IDEA_CBC_SHA) ->
+%% "IDEA-CBC-SHA";
+openssl_suite_name(?TLS_RSA_WITH_RC4_128_SHA) ->
+ "RC4-SHA";
+openssl_suite_name(?TLS_RSA_WITH_RC4_128_MD5) ->
+ "RC4-MD5";
+openssl_suite_name(?TLS_DHE_RSA_WITH_DES_CBC_SHA) ->
+ "EDH-RSA-DES-CBC-SHA";
+openssl_suite_name(?TLS_RSA_WITH_DES_CBC_SHA) ->
+ "DES-CBC-SHA";
+openssl_suite_name(?TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA) ->
+ "EXP-EDH-RSA-DES-CBC-SHA";
+openssl_suite_name(?TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA) ->
+ "EXP-EDH-DSS-DES-CBC-SHA";
+openssl_suite_name(?TLS_RSA_EXPORT_WITH_DES40_CBC_SHA) ->
+ "EXP-DES-CBC-SHA";
+openssl_suite_name(?TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5) ->
+ "EXP-RC2-CBC-MD5";
+openssl_suite_name(?TLS_RSA_EXPORT_WITH_RC4_40_MD5) ->
+ "EXP-RC4-MD5";
+
+openssl_suite_name(?TLS_RSA_EXPORT1024_WITH_RC4_56_MD5) ->
+ "EXP1024-RC4-MD5";
+openssl_suite_name(?TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5) ->
+ "EXP1024-RC2-CBC-MD5";
+openssl_suite_name(?TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA) ->
+ "EXP1024-DES-CBC-SHA";
+openssl_suite_name(?TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA) ->
+ "EXP1024-DHE-DSS-DES-CBC-SHA";
+openssl_suite_name(?TLS_RSA_EXPORT1024_WITH_RC4_56_SHA) ->
+ "EXP1024-RC4-SHA";
+openssl_suite_name(?TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA) ->
+ "EXP1024-DHE-DSS-RC4-SHA";
+openssl_suite_name(?TLS_DHE_DSS_WITH_RC4_128_SHA) ->
+ "DHE-DSS-RC4-SHA";
+
+%% No oppenssl name
+openssl_suite_name(Cipher) ->
+ suite_definition(Cipher).
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+bulk_cipher_algorithm(null) ->
+ ?NULL;
+%% Not supported yet
+%% bulk_cipher_algorithm(idea_cbc) ->
+%% ?IDEA;
+bulk_cipher_algorithm(Cipher) when Cipher == rc2_cbc_40;
+ Cipher == rc2_cbc_56 ->
+ ?RC2;
+bulk_cipher_algorithm(Cipher) when Cipher == rc4_40;
+ Cipher == rc4_56;
+ Cipher == rc4_128 ->
+ ?RC4;
+bulk_cipher_algorithm(des40_cbc) ->
+ ?DES40;
+bulk_cipher_algorithm(des_cbc) ->
+ ?DES;
+bulk_cipher_algorithm('3des_ede_cbc') ->
+ ?'3DES';
+bulk_cipher_algorithm(Cipher) when Cipher == aes_128_cbc;
+ Cipher == aes_256_cbc ->
+ ?AES.
+
+type(Cipher) when Cipher == null;
+ Cipher == rc4_40;
+ Cipher == rc4_56;
+ Cipher == rc4_128 ->
+ ?STREAM;
+
+type(Cipher) when Cipher == idea_cbc;
+ Cipher == rc2_cbc_40;
+ Cipher == rc2_cbc_56;
+ Cipher == des40_cbc;
+ Cipher == des_cbc;
+ Cipher == '3des_ede_cbc';
+ Cipher == aes_128_cbc;
+ Cipher == aes_256_cbc ->
+ ?BLOCK.
+
+key_material(null) ->
+ 0;
+key_material(Cipher) when Cipher == idea_cbc;
+ Cipher == rc4_128 ->
+ 16;
+key_material(Cipher) when Cipher == rc2_cbc_56;
+ Cipher == rc4_56 ->
+ 7;
+key_material(Cipher) when Cipher == rc2_cbc_40;
+ Cipher == rc4_40;
+ Cipher == des40_cbc ->
+ 5;
+key_material(des_cbc) ->
+ 8;
+key_material('3des_ede_cbc') ->
+ 24;
+key_material(aes_128_cbc) ->
+ 16;
+key_material(aes_256_cbc) ->
+ 32.
+
+expanded_key_material(null) ->
+ 0;
+expanded_key_material(Cipher) when Cipher == idea_cbc;
+ Cipher == rc2_cbc_40;
+ Cipher == rc2_cbc_56;
+ Cipher == rc4_40;
+ Cipher == rc4_56;
+ Cipher == rc4_128 ->
+ 16;
+expanded_key_material(Cipher) when Cipher == des_cbc;
+ Cipher == des40_cbc ->
+ 8;
+expanded_key_material('3des_ede_cbc') ->
+ 24;
+expanded_key_material(Cipher) when Cipher == aes_128_cbc;
+ Cipher == aes_256_cbc ->
+ unknown.
+
+
+effective_key_bits(null) ->
+ 0;
+effective_key_bits(Cipher) when Cipher == rc2_cbc_40;
+ Cipher == rc4_40;
+ Cipher == des40_cbc ->
+ 40;
+effective_key_bits(Cipher) when Cipher == rc2_cbc_56;
+ Cipher == rc4_56;
+ Cipher == des_cbc ->
+ 56;
+effective_key_bits(Cipher) when Cipher == idea_cbc;
+ Cipher == rc4_128;
+ Cipher == aes_128_cbc ->
+ 128;
+effective_key_bits('3des_ede_cbc') ->
+ 168;
+effective_key_bits(aes_256_cbc) ->
+ 256.
+
+iv_size(Cipher) when Cipher == null;
+ Cipher == rc4_40;
+ Cipher == rc4_56;
+ Cipher == rc4_128 ->
+ 0;
+iv_size(Cipher) ->
+ block_size(Cipher).
+
+block_size(Cipher) when Cipher == idea_cbc;
+ Cipher == rc2_cbc_40;
+ Cipher == rc2_cbc_56;
+ Cipher == des40_cbc;
+ Cipher == des_cbc;
+ Cipher == '3des_ede_cbc' ->
+ 8;
+
+block_size(Cipher) when Cipher == aes_128_cbc;
+ Cipher == aes_256_cbc ->
+ 16.
+
+mac_algorithm(null) ->
+ ?NULL;
+mac_algorithm(md5) ->
+ ?MD5;
+mac_algorithm(sha) ->
+ ?SHA.
+
+hash_size(null) ->
+ 0;
+hash_size(md5) ->
+ 16;
+hash_size(sha) ->
+ 20.
+
+generic_block_cipher_from_bin(T, HashSize) ->
+ Sz1 = byte_size(T) - 1,
+ <<_:Sz1/binary, ?BYTE(PadLength)>> = T,
+ CompressedLength = byte_size(T) - PadLength - 1 - HashSize,
+ <<Content:CompressedLength/binary, Mac:HashSize/binary,
+ Padding:PadLength/binary, ?BYTE(PadLength)>> = T,
+ #generic_block_cipher{content=Content, mac=Mac,
+ padding=Padding, padding_length=PadLength}.
+
+generic_stream_cipher_from_bin(T, HashSz) ->
+ Sz = byte_size(T),
+ CompressedLength = Sz - HashSz,
+ <<Content:CompressedLength/binary, Mac:HashSz/binary>> = T,
+ #generic_stream_cipher{content=Content,
+ mac=Mac}.
+
+check_padding(_GBC) ->
+ ok.
+
+get_padding(Length, BlockSize) ->
+ get_padding_aux(BlockSize, Length rem BlockSize).
+
+get_padding_aux(_, 0) ->
+ {0, <<>>};
+get_padding_aux(BlockSize, PadLength) ->
+ N = BlockSize - PadLength,
+ {N, list_to_binary(lists:duplicate(N, N))}.
+
+next_iv(Bin, IV) ->
+ BinSz = byte_size(Bin),
+ IVSz = byte_size(IV),
+ FirstPart = BinSz - IVSz,
+ <<_:FirstPart/binary, NextIV:IVSz/binary>> = Bin,
+ NextIV.
+
diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl
new file mode 100644
index 0000000000..4304c501b7
--- /dev/null
+++ b/lib/ssl/src/ssl_cipher.hrl
@@ -0,0 +1,253 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Record and constant defenitions for the SSL ciphers and
+%% the SSL-cipher protocol see RFC 4346, RFC 3268
+%%----------------------------------------------------------------------
+
+-ifndef(ssl_cipher).
+-define(ssl_cipher, true).
+
+%%% SSL cipher protocol %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-define(CHANGE_CIPHER_SPEC_PROTO, 1). % _PROTO to not clash with
+ % SSL record protocol
+
+-record(change_cipher_spec, {
+ type = 1
+ }).
+
+%%% SSL cipher suites %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% -record(cipher_state,
+%% {
+%% suite,
+%% name,
+%% state
+%% }).
+
+-record(cipher_state, {
+ iv,
+ key,
+ state
+ }).
+
+%%% TLS_NULL_WITH_NULL_NULL is specified and is the initial state of a
+%%% TLS connection during the first handshake on that channel, but
+%%% must not be negotiated, as it provides no more protection than an
+%%% unsecured connection.
+
+%% TLS_NULL_WITH_NULL_NULL = { 0x00,0x00 };
+-define(TLS_NULL_WITH_NULL_NULL, <<?BYTE(16#00), ?BYTE(16#00)>>).
+
+%%% The following CipherSuite definitions require that the server
+%%% provide an RSA certificate that can be used for key exchange. The
+%%% server may request either an RSA or a DSS signature-capable
+%%% certificate in the certificate request message.
+
+%% TLS_RSA_WITH_NULL_MD5 = { 0x00,0x01 };
+-define(TLS_RSA_WITH_NULL_MD5, <<?BYTE(16#00), ?BYTE(16#01)>>).
+
+%% TLS_RSA_WITH_NULL_SHA = { 0x00,0x02 };
+-define(TLS_RSA_WITH_NULL_SHA, <<?BYTE(16#00), ?BYTE(16#02)>>).
+
+%% TLS_RSA_EXPORT_WITH_RC4_40_MD5 = { 0x00,0x03 };
+-define(TLS_RSA_EXPORT_WITH_RC4_40_MD5, <<?BYTE(16#00), ?BYTE(16#03)>>).
+
+%% TLS_RSA_WITH_RC4_128_MD5 = { 0x00,0x04 };
+-define(TLS_RSA_WITH_RC4_128_MD5, <<?BYTE(16#00), ?BYTE(16#04)>>).
+
+%% TLS_RSA_WITH_RC4_128_SHA = { 0x00,0x05 };
+-define(TLS_RSA_WITH_RC4_128_SHA, <<?BYTE(16#00), ?BYTE(16#05)>>).
+
+%% TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = { 0x00,0x06 };
+-define(TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5, <<?BYTE(16#00), ?BYTE(16#06)>>).
+
+%% TLS_RSA_WITH_IDEA_CBC_SHA = { 0x00,0x07 };
+-define(TLS_RSA_WITH_IDEA_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#07)>>).
+
+%% TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x08 };
+-define(TLS_RSA_EXPORT_WITH_DES40_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#08)>>).
+
+%% TLS_RSA_WITH_DES_CBC_SHA = { 0x00,0x09 };
+-define(TLS_RSA_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#09)>>).
+
+%% TLS_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x0A };
+-define(TLS_RSA_WITH_3DES_EDE_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#0A)>>).
+
+%%% The following CipherSuite definitions are used for server-
+%%% authenticated (and optionally client-authenticated)
+%%% Diffie-Hellman. DH denotes cipher suites in which the server's
+%%% certificate contains the Diffie-Hellman parameters signed by the
+%%% certificate authority (CA). DHE denotes ephemeral Diffie-Hellman,
+%%% where the Diffie-Hellman parameters are signed by a DSS or RSA
+%%% certificate, which has been signed by the CA. The signing
+%%% algorithm used is specified after the DH or DHE parameter. The
+%%% server can request an RSA or DSS signature- capable certificate
+%%% from the client for client authentication or it may request a
+%%% Diffie-Hellman certificate. Any Diffie-Hellman certificate
+%%% provided by the client must use the parameters (group and
+%%% generator) described by the server.
+
+%% TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x0B };
+-define(TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#0B)>>).
+
+%% TLS_DH_DSS_WITH_DES_CBC_SHA = { 0x00,0x0C };
+-define(TLS_DH_DSS_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#0C)>>).
+
+%% TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00,0x0D };
+-define(TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#0D)>>).
+
+%% TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x0E };
+-define(TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#0E)>>).
+
+%% TLS_DH_RSA_WITH_DES_CBC_SHA = { 0x00,0x0F };
+-define(TLS_DH_RSA_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#0F)>>).
+
+%% TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x10 };
+-define(TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#10)>>).
+
+%% TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x11 };
+-define(TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#11)>>).
+
+%% TLS_DHE_DSS_WITH_DES_CBC_SHA = { 0x00,0x12 };
+-define(TLS_DHE_DSS_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#12)>>).
+
+%% TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00,0x13 };
+-define(TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#13)>>).
+
+%% TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x14 };
+-define(TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#14)>>).
+
+%% TLS_DHE_RSA_WITH_DES_CBC_SHA = { 0x00,0x15 };
+-define(TLS_DHE_RSA_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#15)>>).
+
+%% TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x16 };
+-define(TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#16)>>).
+
+%% TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = { 0x00,0x17 };
+-define(TLS_DH_anon_EXPORT_WITH_RC4_40_MD5, <<?BYTE(16#00), ?BYTE(16#17)>>).
+
+%% TLS_DH_anon_WITH_RC4_128_MD5 = { 0x00,0x18 };
+-define(TLS_DH_anon_WITH_RC4_128_MD5, <<?BYTE(16#00),?BYTE(16#18)>>).
+
+%% TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x19 };
+-define(TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#19)>>).
+
+%% TLS_DH_anon_WITH_DES_CBC_SHA = { 0x00,0x1A };
+-define(TLS_DH_anon_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#1A)>>).
+
+%% TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = { 0x00,0x1B };
+-define(TLS_DH_anon_WITH_3DES_EDE_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#1B)>>).
+
+
+%%% AES Cipher Suites RFC 3268
+
+%% TLS_RSA_WITH_AES_128_CBC_SHA = { 0x00, 0x2F };
+-define(TLS_RSA_WITH_AES_128_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#2F)>>).
+
+%% TLS_DH_DSS_WITH_AES_128_CBC_SHA = { 0x00, 0x30 };
+-define(TLS_DH_DSS_WITH_AES_128_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#30)>>).
+
+%% TLS_DH_RSA_WITH_AES_128_CBC_SHA = { 0x00, 0x31 };
+-define(TLS_DH_RSA_WITH_AES_128_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#31)>>).
+
+%% TLS_DHE_DSS_WITH_AES_128_CBC_SHA = { 0x00, 0x32 };
+-define(TLS_DHE_DSS_WITH_AES_128_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#32)>>).
+
+%% TLS_DHE_RSA_WITH_AES_128_CBC_SHA = { 0x00, 0x33 };
+-define(TLS_DHE_RSA_WITH_AES_128_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#33)>>).
+
+%% TLS_DH_anon_WITH_AES_128_CBC_SHA = { 0x00, 0x34 };
+-define(TLS_DH_anon_WITH_AES_128_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#34)>>).
+
+%% TLS_RSA_WITH_AES_256_CBC_SHA = { 0x00, 0x35 };
+-define(TLS_RSA_WITH_AES_256_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#35)>>).
+
+%% TLS_DH_DSS_WITH_AES_256_CBC_SHA = { 0x00, 0x36 };
+-define(TLS_DH_DSS_WITH_AES_256_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#36)>>).
+
+%% TLS_DH_RSA_WITH_AES_256_CBC_SHA = { 0x00, 0x37 };
+-define(TLS_DH_RSA_WITH_AES_256_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#37)>>).
+
+%% TLS_DHE_DSS_WITH_AES_256_CBC_SHA = { 0x00, 0x38 };
+-define(TLS_DHE_DSS_WITH_AES_256_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#38)>>).
+
+%% TLS_DHE_RSA_WITH_AES_256_CBC_SHA = { 0x00, 0x39 };
+-define(TLS_DHE_RSA_WITH_AES_256_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#39)>>).
+
+%% TLS_DH_anon_WITH_AES_256_CBC_SHA = { 0x00, 0x3A };
+-define(TLS_DH_anon_WITH_AES_256_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#3A)>>).
+
+%%% Kerberos Cipher Suites
+
+%% TLS_KRB5_WITH_DES_CBC_SHA = { 0x00,0x1E };
+-define(TLS_KRB5_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#1E)>>).
+
+%% TLS_KRB5_WITH_3DES_EDE_CBC_SHA = { 0x00,0x1F };
+-define(TLS_KRB5_WITH_3DES_EDE_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#1F)>>).
+
+%% TLS_KRB5_WITH_RC4_128_SHA = { 0x00,0x20 };
+-define(TLS_KRB5_WITH_RC4_128_SHA, <<?BYTE(16#00), ?BYTE(16#20)>>).
+
+%% TLS_KRB5_WITH_IDEA_CBC_SHA = { 0x00,0x21 };
+-define(TLS_KRB5_WITH_IDEA_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#21)>>).
+
+%% TLS_KRB5_WITH_DES_CBC_MD5 = { 0x00,0x22 };
+-define(TLS_KRB5_WITH_DES_CBC_MD5, <<?BYTE(16#00), ?BYTE(16#22)>>).
+
+%% TLS_KRB5_WITH_3DES_EDE_CBC_MD5 = { 0x00,0x23 };
+-define(TLS_KRB5_WITH_3DES_EDE_CBC_MD5, <<?BYTE(16#00), ?BYTE(16#23)>>).
+
+%% TLS_KRB5_WITH_RC4_128_MD5 = { 0x00,0x24 };
+-define(TLS_KRB5_WITH_RC4_128_MD5, <<?BYTE(16#00), ?BYTE(16#24)>>).
+
+%% TLS_KRB5_WITH_IDEA_CBC_MD5 = { 0x00,0x25 };
+-define(TLS_KRB5_WITH_IDEA_CBC_MD5, <<?BYTE(16#00), ?BYTE(16#25)>>).
+
+%% TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA = { 0x00,0x26 };
+-define(TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA, <<?BYTE(16#00), ?BYTE(16#26)>>).
+
+%% TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA = { 0x00,0x27 };
+-define(TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA, <<?BYTE(16#00), ?BYTE(16#27)>>).
+
+%% TLS_KRB5_EXPORT_WITH_RC4_40_SHA = { 0x00,0x28 };
+-define(TLS_KRB5_EXPORT_WITH_RC4_40_SHA, <<?BYTE(16#00), ?BYTE(16#28)>>).
+
+%% TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 = { 0x00,0x29 };
+-define(TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5, <<?BYTE(16#00), ?BYTE(16#29)>>).
+
+%% TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 = { 0x00,0x2A };
+-define(TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5, <<?BYTE(16#00), ?BYTE(16#2A)>>).
+
+%% TLS_KRB5_EXPORT_WITH_RC4_40_MD5 = { 0x00,0x2B };
+-define(TLS_KRB5_EXPORT_WITH_RC4_40_MD5, <<?BYTE(16#00), ?BYTE(16#2B)>>).
+
+%% Additional TLS ciphersuites from draft-ietf-tls-56-bit-ciphersuites-00.txt
+
+-define(TLS_RSA_EXPORT1024_WITH_RC4_56_MD5, <<?BYTE(16#00), ?BYTE(16#60)>>).
+-define(TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5, <<?BYTE(16#00), ?BYTE(16#61)>>).
+-define(TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#62)>>).
+-define(TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#63)>>).
+-define(TLS_RSA_EXPORT1024_WITH_RC4_56_SHA, <<?BYTE(16#00), ?BYTE(16#64)>>).
+-define(TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA, <<?BYTE(16#00), ?BYTE(16#65)>>).
+-define(TLS_DHE_DSS_WITH_RC4_128_SHA, <<?BYTE(16#00), ?BYTE(16#66)>>).
+
+-endif. % -ifdef(ssl_cipher).
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
new file mode 100644
index 0000000000..178c055cdf
--- /dev/null
+++ b/lib/ssl/src/ssl_connection.erl
@@ -0,0 +1,1704 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Handles an ssl connection, e.i. both the setup
+%% e.i. SSL-Handshake, SSL-Alert and SSL-Cipher protocols and delivering
+%% data to the application. All data on the connectinon is received and
+%% sent according to the SSL-record protocol.
+%%----------------------------------------------------------------------
+
+-module(ssl_connection).
+
+-behaviour(gen_fsm).
+
+-include("ssl_debug.hrl").
+-include("ssl_handshake.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_record.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_int.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+%% Internal application API
+-export([send/2, send/3, recv/3, connect/7, accept/6, close/1, shutdown/2,
+ new_user/2, get_opts/2, set_opts/2, info/1, session_info/1,
+ peer_certificate/1,
+ sockname/1, peername/1]).
+
+%% Called by ssl_connection_sup
+-export([start_link/7]).
+
+%% gen_fsm callbacks
+-export([init/1, hello/2, certify/2, cipher/2, connection/2, connection/3, abbreviated/2,
+ handle_event/3,
+ handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
+
+-record(state, {
+ role, % client | server
+ user_application, % {MonitorRef, pid()}
+ transport_cb, % atom() - callback module
+ data_tag, % atom() - ex tcp.
+ close_tag, % atom() - ex tcp_closed
+ host, % string() | ipadress()
+ port, % integer()
+ socket, % socket()
+ ssl_options, % #ssl_options{}
+ socket_options, % #socket_options{}
+ connection_states, % #connection_states{} from ssl_record.hrl
+ tls_record_buffer, % binary() buffer of incomplete records
+ tls_handshake_buffer, % binary() buffer of incomplete handshakes
+ %% {{md5_hash, sha_hash}, {prev_md5, prev_sha}} (binary())
+ tls_handshake_hashes, % see above
+ tls_cipher_texts, % list() received but not deciphered yet
+ own_cert, % binary()
+ session, % #session{} from ssl_handshake.erl
+ session_cache, %
+ session_cache_cb, %
+ negotiated_version, % #protocol_version{}
+ supported_protocol_versions, % [atom()]
+ client_certificate_requested = false,
+ key_algorithm, % atom as defined by cipher_suite
+ public_key_info, % PKIX: {Algorithm, PublicKey, PublicKeyParams}
+ private_key, % PKIX: 'RSAPrivateKey'
+ diffie_hellman_params, %
+ premaster_secret, %
+ cert_db_ref, % ets_table()
+ from, % term(), where to reply
+ bytes_to_read, % integer(), # bytes to read in passive mode
+ user_data_buffer, % binary()
+%% tls_buffer, % Keeps a lookahead one packet if available
+ log_alert % boolan()
+ }).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+send(Pid, Data) ->
+ sync_send_event(Pid, {application_data, erlang:iolist_to_binary(Data)}, infinity).
+send(Pid, Data, Timeout) ->
+ sync_send_event(Pid, {application_data, erlang:iolist_to_binary(Data)}, Timeout).
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+recv(Pid, Length, Timeout) -> % TODO: Prio with renegotiate?
+ sync_send_all_state_event(Pid, {recv, Length}, Timeout).
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+connect(Host, Port, Socket, Options, User, CbInfo, Timeout) ->
+ start_fsm(client, Host, Port, Socket, Options, User, CbInfo,
+ Timeout).
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+accept(Port, Socket, Opts, User, CbInfo, Timeout) ->
+ start_fsm(server, "localhost", Port, Socket, Opts, User,
+ CbInfo, Timeout).
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+close(ConnectionPid) ->
+ case sync_send_all_state_event(ConnectionPid, close) of
+ {error, closed} ->
+ ok;
+ Other ->
+ Other
+ end.
+
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+shutdown(ConnectionPid, How) ->
+ sync_send_all_state_event(ConnectionPid, {shutdown, How}).
+
+
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+new_user(ConnectionPid, User) ->
+ sync_send_all_state_event(ConnectionPid, {new_user, User}).
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+sockname(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, sockname).
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+peername(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, peername).
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+get_opts({ListenSocket, {_SslOpts, SockOpts}, _}, OptTags) ->
+ get_socket_opts(ListenSocket, OptTags, SockOpts, []);
+get_opts(ConnectionPid, OptTags) ->
+ sync_send_all_state_event(ConnectionPid, {get_opts, OptTags}).
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+set_opts(ConnectionPid, Options) ->
+ sync_send_all_state_event(ConnectionPid, {set_opts, Options}).
+
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+info(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, info).
+
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+session_info(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, session_info).
+
+%%--------------------------------------------------------------------
+%% Function:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+peer_certificate(ConnectionPid) ->
+ sync_send_all_state_event(ConnectionPid, peer_certificate).
+
+%%====================================================================
+%% ssl_connection_sup API
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
+%%
+%% Description: Creates a gen_fsm process which calls Module:init/1 to
+%% initialize. To ensure a synchronized start-up procedure, this function
+%% does not return until Module:init/1 has returned.
+%%--------------------------------------------------------------------
+start_link(Role, Host, Port, Socket, Options, User, CbInfo) ->
+ gen_fsm:start_link(?MODULE, [Role, Host, Port, Socket, Options,
+ User, CbInfo], []).
+
+
+%%====================================================================
+%% gen_fsm callbacks
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, StateName, State} |
+%% {ok, StateName, State, Timeout} |
+%% ignore |
+%% {stop, StopReason}
+%% Description:Whenever a gen_fsm is started using gen_fsm:start/[3,4] or
+%% gen_fsm:start_link/3,4, this function is called by the new process to
+%% initialize.
+%%--------------------------------------------------------------------
+init([Role, Host, Port, Socket, {SSLOpts, _} = Options,
+ User, CbInfo]) ->
+ State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
+ Hashes0 = ssl_handshake:init_hashes(),
+
+ try ssl_init(SSLOpts, Role) of
+ {ok, Ref, CacheRef, OwnCert, Key} ->
+ State = State0#state{tls_handshake_hashes = Hashes0,
+ own_cert = OwnCert,
+ cert_db_ref = Ref,
+ session_cache = CacheRef,
+ private_key = Key},
+ {ok, hello, State}
+ catch
+ throw:Error ->
+ {stop, Error}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function:
+%% state_name(Event, State) -> {next_state, NextStateName, NextState}|
+%% {next_state, NextStateName,
+%% NextState, Timeout} |
+%% {stop, Reason, NewState}
+%% Description:There should be one instance of this function for each possible
+%% state name. Whenever a gen_fsm receives an event sent using
+%% gen_fsm:send_event/2, the instance of this function with the same name as
+%% the current state name StateName is called to handle the event. It is also
+%% called if a timeout occurs.
+%%--------------------------------------------------------------------
+hello(socket_control, #state{host = Host, port = Port, role = client,
+ ssl_options = SslOpts,
+ transport_cb = Transport, socket = Socket,
+ connection_states = ConnectionStates}
+ = State0) ->
+ Hello = ssl_handshake:client_hello(Host, Port, ConnectionStates, SslOpts),
+ Version = Hello#client_hello.client_version,
+ Hashes0 = ssl_handshake:init_hashes(),
+ {BinMsg, CS2, Hashes1} =
+ encode_handshake(Hello, Version, ConnectionStates, Hashes0),
+ Transport:send(Socket, BinMsg),
+ State = State0#state{connection_states = CS2,
+ negotiated_version = Version, %% Requested version
+ session =
+ #session{session_id = Hello#client_hello.session_id,
+ is_resumable = false},
+ tls_handshake_hashes = Hashes1},
+ {next_state, hello, next_record(State)};
+
+hello(socket_control, #state{role = server} = State) ->
+ {next_state, hello, next_record(State)};
+
+hello(hello, #state{role = client} = State) ->
+ {next_state, hello, State};
+
+hello(#server_hello{cipher_suite = CipherSuite,
+ compression_method = Compression} = Hello,
+ #state{session = Session0 = #session{session_id = OldId},
+ connection_states = ConnectionStates0,
+ role = client,
+ negotiated_version = ReqVersion,
+ host = Host, port = Port,
+ session_cache = Cache,
+ session_cache_cb = CacheCb} = State0) ->
+ {Version, NewId, ConnectionStates1} =
+ ssl_handshake:hello(Hello, ConnectionStates0),
+
+ {KeyAlgorithm, _, _, _} =
+ ssl_cipher:suite_definition(CipherSuite),
+
+ PremasterSecret = make_premaster_secret(ReqVersion),
+
+ State = State0#state{key_algorithm = KeyAlgorithm,
+ negotiated_version = Version,
+ connection_states = ConnectionStates1,
+ premaster_secret = PremasterSecret},
+
+ case ssl_session:is_new(OldId, NewId) of
+ true ->
+ Session = Session0#session{session_id = NewId,
+ cipher_suite = CipherSuite,
+ compression_method = Compression},
+ {next_state, certify,
+ next_record(State#state{session = Session})};
+ false ->
+ Session = CacheCb:lookup(Cache, {{Host, Port}, NewId}),
+ case ssl_handshake:master_secret(Version, Session,
+ ConnectionStates1, client) of
+ {_, ConnectionStates2} ->
+ {next_state, abbreviated,
+ next_record(State#state{
+ connection_states = ConnectionStates2,
+ session = Session})};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, hello, State),
+ {stop, normal, State}
+ end
+ end;
+
+hello(Hello = #client_hello{client_version = ClientVersion},
+ State = #state{connection_states = ConnectionStates0,
+ port = Port, session = Session0,
+ session_cache = Cache,
+ session_cache_cb = CacheCb,
+ ssl_options = SslOpts}) ->
+
+ case ssl_handshake:hello(Hello, {Port, SslOpts,
+ Session0, Cache, CacheCb,
+ ConnectionStates0}) of
+ {Version, {Type, Session}, ConnectionStates} ->
+ do_server_hello(Type, State#state{connection_states =
+ ConnectionStates,
+ negotiated_version = Version,
+ session = Session});
+ #alert{} = Alert ->
+ handle_own_alert(Alert, ClientVersion, hello, State),
+ {stop, normal, State}
+ end.
+
+abbreviated(socket_control, #state{role = server} = State) ->
+ {next_state, abbreviated, State};
+abbreviated(hello, State) ->
+ {next_state, certify, State};
+
+abbreviated(Finished = #finished{},
+ #state{role = server,
+ negotiated_version = Version,
+ tls_handshake_hashes = Hashes,
+ session = #session{master_secret = MasterSecret},
+ from = From} = State) ->
+ case ssl_handshake:verify_connection(Version, Finished, client,
+ MasterSecret, Hashes) of
+ verified ->
+ gen_fsm:reply(From, connected),
+ {next_state, connection, next_record_if_active(State)};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, abbreviated, State),
+ {stop, normal, State}
+ end;
+
+abbreviated(Finished = #finished{},
+ #state{role = client, tls_handshake_hashes = Hashes0,
+ session = #session{master_secret = MasterSecret},
+ from = From,
+ negotiated_version = Version} = State) ->
+ case ssl_handshake:verify_connection(Version, Finished, server,
+ MasterSecret, Hashes0) of
+ verified ->
+ {ConnectionStates, Hashes} = finalize_client_handshake(State),
+ gen_fsm:reply(From, connected),
+ {next_state, connection,
+ next_record_if_active(State#state{tls_handshake_hashes = Hashes,
+ connection_states =
+ ConnectionStates})};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, abbreviated, State),
+ {stop, normal, State}
+ end.
+
+certify(socket_control, #state{role = server} = State) ->
+ {next_state, certify, State};
+certify(hello, State) ->
+ {next_state, certify, State};
+
+certify(#certificate{asn1_certificates = []},
+ #state{role = server, negotiated_version = Version,
+ ssl_options = #ssl_options{verify = verify_peer,
+ fail_if_no_peer_cert = true}} =
+ State) ->
+ Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE),
+ handle_own_alert(Alert, Version, certify_certificate, State),
+ {stop, normal, State};
+
+certify(#certificate{asn1_certificates = []},
+ #state{role = server,
+ ssl_options = #ssl_options{verify = verify_peer,
+ fail_if_no_peer_cert = false}} =
+ State) ->
+ {next_state, certify, next_record(State#state{client_certificate_requested = false})};
+
+certify(#certificate{} = Cert,
+ #state{session = Session,
+ negotiated_version = Version,
+ cert_db_ref = CertDbRef,
+ ssl_options = Opts} = State0) ->
+ case ssl_handshake:certify(Cert, CertDbRef, Opts#ssl_options.depth,
+ Opts#ssl_options.verify,
+ Opts#ssl_options.verify_fun) of
+ {PeerCert, PublicKeyInfo} ->
+ State = State0#state{session =
+ Session#session{peer_certificate = PeerCert},
+ public_key_info = PublicKeyInfo,
+ client_certificate_requested = false
+ },
+ {next_state, certify, next_record(State)};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, certify_certificate, State0),
+ {stop, normal, State0}
+ end;
+
+certify(#server_key_exchange{} = KeyExchangeMsg,
+ #state{role = client,
+ key_algorithm = Alg} = State)
+ when Alg == dhe_dss; Alg == dhe_rsa; Alg == dh_anon; Alg == krb5 ->
+ NewState = handle_server_key(KeyExchangeMsg, State),
+ {next_state, certify, NewState};
+
+certify(#server_key_exchange{},
+ State = #state{role = client, negotiated_version = Version,
+ key_algorithm = Alg})
+ when Alg == rsa; Alg == dh_dss; Alg == dh_rsa ->
+ Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE),
+ handle_own_alert(Alert, Version, certify_server_key_exchange, State),
+ {stop, normal, State};
+
+certify(KeyExchangeMsg = #server_key_exchange{}, State =
+ #state{role = server}) ->
+ NewState = handle_clinet_key(KeyExchangeMsg, State),
+ {next_state, cipher, NewState};
+
+certify(#certificate_request{}, State) ->
+ NewState = State#state{client_certificate_requested = true},
+ {next_state, certify, next_record(NewState)};
+
+certify(#server_hello_done{},
+ #state{session = Session0,
+ connection_states = ConnectionStates0,
+ negotiated_version = Version,
+ premaster_secret = PremasterSecret,
+ role = client} = State0) ->
+ case ssl_handshake:master_secret(Version, PremasterSecret,
+ ConnectionStates0, client) of
+ {MasterSecret, ConnectionStates1} ->
+ Session = Session0#session{master_secret = MasterSecret},
+ State = State0#state{connection_states = ConnectionStates1,
+ session = Session},
+ client_certify_and_key_exchange(State);
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version,
+ certify_server_hello_done, State0),
+ {stop, normal, State0}
+ end;
+
+certify(#client_key_exchange{},
+ State = #state{role = server,
+ client_certificate_requested = true,
+ ssl_options = #ssl_options{fail_if_no_peer_cert = true},
+ negotiated_version = Version}) ->
+ %% We expect a certificate here
+ Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE),
+ handle_own_alert(Alert, Version, certify_server_waiting_certificate, State),
+ {stop, normal, State};
+
+
+certify(#client_key_exchange{exchange_keys
+ = #encrypted_premaster_secret{premaster_secret
+ = EncPMS}},
+ #state{negotiated_version = Version,
+ connection_states = ConnectionStates0,
+ session = Session0,
+ private_key = Key} = State0) ->
+ try ssl_handshake:decrypt_premaster_secret(EncPMS, Key) of
+ PremasterSecret ->
+ case ssl_handshake:master_secret(Version, PremasterSecret,
+ ConnectionStates0, server) of
+ {MasterSecret, ConnectionStates} ->
+ Session = Session0#session{master_secret = MasterSecret},
+ State = State0#state{connection_states = ConnectionStates,
+ session = Session},
+ {next_state, cipher, next_record(State)};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version,
+ certify_client_key_exchange, State0),
+ {stop, normal, State0}
+ end
+ catch
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, certify_client_key_exchange,
+ State0),
+ {stop, normal, State0}
+ end.
+
+cipher(socket_control, #state{role = server} = State) ->
+ {next_state, cipher, State};
+cipher(hello, State) ->
+ {next_state, cipher, State};
+
+cipher(#certificate_verify{signature = Signature},
+ #state{role = server,
+ public_key_info = PublicKeyInfo,
+ negotiated_version = Version,
+ session = #session{master_secret = MasterSecret},
+ key_algorithm = Algorithm,
+ tls_handshake_hashes = Hashes
+ } = State) ->
+ case ssl_handshake:certificate_verify(Signature, PublicKeyInfo,
+ Version, MasterSecret,
+ Algorithm, Hashes) of
+ valid ->
+ {next_state, cipher, next_record(State)};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, cipher, State),
+ {stop, normal, State}
+ end;
+
+cipher(#finished{} = Finished,
+ State = #state{from = From,
+ negotiated_version = Version,
+ host = Host,
+ port = Port,
+ role = Role,
+ session = #session{master_secret = MasterSecret}
+ = Session0,
+ tls_handshake_hashes = Hashes}) ->
+
+ case ssl_handshake:verify_connection(Version, Finished,
+ opposite_role(Role),
+ MasterSecret, Hashes) of
+ verified ->
+ gen_fsm:reply(From, connected),
+ Session = register_session(Role, Host, Port, Session0),
+ case Role of
+ client ->
+ {next_state, connection,
+ next_record_if_active(State#state{session = Session})};
+ server ->
+ {NewConnectionStates, NewHashes} =
+ finalize_server_handshake(State#state{
+ session = Session}),
+ NewState =
+ State#state{connection_states = NewConnectionStates,
+ session = Session,
+ tls_handshake_hashes = NewHashes},
+ {next_state, connection, next_record_if_active(NewState)}
+ end;
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, cipher, State),
+ {stop, normal, State}
+ end.
+
+connection(socket_control, #state{role = server} = State) ->
+ {next_state, connection, State};
+connection(hello, State = #state{host = Host, port = Port,
+ socket = Socket,
+ ssl_options = SslOpts,
+ negotiated_version = Version,
+ transport_cb = Transport,
+ connection_states = ConnectionStates0,
+ tls_handshake_hashes = Hashes0}) ->
+
+ Hello = ssl_handshake:client_hello(Host, Port,
+ ConnectionStates0, SslOpts),
+ {BinMsg, ConnectionStates1, Hashes1} =
+ encode_handshake(Hello, Version, ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinMsg),
+ {next_state, hello, State#state{connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1}}.
+
+%%--------------------------------------------------------------------
+%% Function:
+%% state_name(Event, From, State) -> {next_state, NextStateName, NextState} |
+%% {next_state, NextStateName,
+%% NextState, Timeout} |
+%% {reply, Reply, NextStateName, NextState}|
+%% {reply, Reply, NextStateName,
+%% NextState, Timeout} |
+%% {stop, Reason, NewState}|
+%% {stop, Reason, Reply, NewState}
+%% Description: There should be one instance of this function for each
+%% possible state name. Whenever a gen_fsm receives an event sent using
+%% gen_fsm:sync_send_event/2,3, the instance of this function with the same
+%% name as the current state name StateName is called to handle the event.
+%%--------------------------------------------------------------------
+connection({application_data, Data}, _From,
+ State = #state{socket = Socket,
+ negotiated_version = Version,
+ transport_cb = Transport,
+ connection_states = ConnectionStates0}) ->
+ %% We should look into having a worker process to do this to
+ %% parallize send and receive decoding and not block the receiver
+ %% if sending is overloading the socket.
+ {Msgs, ConnectionStates1} = encode_data(Data, Version, ConnectionStates0),
+ Result = Transport:send(Socket, Msgs),
+ {reply, Result,
+ connection, State#state{connection_states = ConnectionStates1}}.
+
+%%--------------------------------------------------------------------
+%% Function:
+%% handle_event(Event, StateName, State) -> {next_state, NextStateName,
+%% NextState} |
+%% {next_state, NextStateName,
+%% NextState, Timeout} |
+%% {stop, Reason, NewState}
+%% Description: Whenever a gen_fsm receives an event sent using
+%% gen_fsm:send_all_state_event/2, this function is called to handle
+%% the event.
+%%--------------------------------------------------------------------
+handle_event(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
+ StateName,
+ State = #state{key_algorithm = KeyAlg,
+ tls_handshake_buffer = Buf0,
+ negotiated_version = Version}) ->
+ Handle =
+ fun({Packet, Raw}, {next_state, SName, AS=#state{tls_handshake_hashes=Hs0}}) ->
+ Hs1 = ssl_handshake:update_hashes(Hs0, Raw),
+ ?MODULE:SName(Packet, AS#state{tls_handshake_hashes=Hs1});
+ (_, StopState) -> StopState
+ end,
+ try
+ {Packets, Buf} = ssl_handshake:get_tls_handshake(Data,Buf0, KeyAlg,Version),
+ Start = {next_state, StateName, State#state{tls_handshake_buffer = Buf}},
+ lists:foldl(Handle, Start, Packets)
+ catch throw:#alert{} = Alert ->
+ handle_own_alert(Alert, Version, StateName, State),
+ {stop, normal, State}
+ end;
+
+handle_event(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data},
+ StateName, State0) ->
+ case application_data(Data, State0) of
+ Stop = {stop,_,_} ->
+ Stop;
+ State ->
+ {next_state, StateName, State}
+ end;
+
+handle_event(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} =
+ _ChangeCipher,
+ StateName,
+ State = #state{connection_states = ConnectionStates0}) ->
+ ?DBG_TERM(_ChangeCipher),
+ ConnectionStates1 =
+ ssl_record:activate_pending_connection_state(ConnectionStates0, read),
+ {next_state, StateName,
+ next_record(State#state{connection_states = ConnectionStates1})};
+
+handle_event(#ssl_tls{type = ?ALERT, fragment = Data}, StateName, State) ->
+ Alerts = decode_alerts(Data),
+ ?DBG_TERM(Alerts),
+ [alert_event(A) || A <- Alerts],
+ {next_state, StateName, State};
+
+handle_event(#alert{level = ?FATAL} = Alert, connection,
+ #state{from = From, user_application = {_Mon, Pid}, log_alert = Log,
+ host = Host, port = Port, session = Session,
+ role = Role, socket_options = Opts} = State) ->
+ invalidate_session(Role, Host, Port, Session),
+ log_alert(Log, connection, Alert),
+ alert_user(Opts#socket_options.active, Pid, From, Alert, Role),
+ {stop, normal, State};
+handle_event(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
+ connection, #state{from = From,
+ role = Role,
+ user_application = {_Mon, Pid},
+ socket_options = Opts} = State) ->
+ alert_user(Opts#socket_options.active, Pid, From, Alert, Role),
+ {stop, normal, State};
+
+handle_event(#alert{level = ?FATAL} = Alert, StateName,
+ #state{from = From, host = Host, port = Port, session = Session,
+ log_alert = Log, role = Role} = State) ->
+ invalidate_session(Role, Host, Port, Session),
+ log_alert(Log, StateName, Alert),
+ alert_user(From, Alert, Role),
+ {stop, normal, State};
+handle_event(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
+ _, #state{from = From, role = Role} = State) ->
+ alert_user(From, Alert, Role),
+ {stop, normal, State};
+handle_event(#alert{level = ?WARNING} = Alert, StateName,
+ #state{log_alert = Log} = State) ->
+ log_alert(Log, StateName, Alert),
+%%TODO: Could be user_canceled or no_negotiation should the latter be
+ %% treated as fatal?!
+ {next_state, StateName, next_record(State)}.
+
+%%--------------------------------------------------------------------
+%% Function:
+%% handle_sync_event(Event, From, StateName,
+%% State) -> {next_state, NextStateName, NextState} |
+%% {next_state, NextStateName, NextState,
+%% Timeout} |
+%% {reply, Reply, NextStateName, NextState}|
+%% {reply, Reply, NextStateName, NextState,
+%% Timeout} |
+%% {stop, Reason, NewState} |
+%% {stop, Reason, Reply, NewState}
+%% Description: Whenever a gen_fsm receives an event sent using
+%% gen_fsm:sync_send_all_state_event/2,3, this function is called to handle
+%% the event.
+%%--------------------------------------------------------------------
+handle_sync_event(started, From, StateName, State) ->
+ {next_state, StateName, State#state{from = From}};
+
+handle_sync_event(close, From, _StateName, State) ->
+ {stop, normal, ok, State#state{from = From}};
+
+handle_sync_event({shutdown, How}, From, StateName,
+ #state{transport_cb = CbModule,
+ socket = Socket} = State) ->
+ case CbModule:shutdown(Socket, How) of
+ ok ->
+ {reply, ok, StateName, State};
+ Error ->
+ {stop, normal, Error, State#state{from = From}}
+ end;
+
+%% TODO: men vad g�r next_record om det �r t.ex. renegotiate? kanske
+%% inte bra... t�l att t�nkas p�!
+handle_sync_event({recv, N}, From, StateName,
+ State0 = #state{user_data_buffer = Buffer}) ->
+ State1 = State0#state{bytes_to_read = N, from = From},
+ case Buffer of
+ <<>> ->
+ State = next_record(State1),
+ {next_state, StateName, State};
+ _ ->
+ case application_data(<<>>, State1) of
+ Stop = {stop, _, _} ->
+ Stop;
+ State ->
+ {next_state, StateName, State}
+ end
+ end;
+
+handle_sync_event({new_user, User}, _From, StateName,
+ State =#state{user_application = {OldMon, _}}) ->
+ NewMon = erlang:monitor(process, User),
+ erlang:demonitor(OldMon, [flush]),
+ {reply, ok, StateName, State#state{user_application = {NewMon,User}}};
+
+handle_sync_event({get_opts, OptTags}, _From, StateName,
+ #state{socket = Socket,
+ socket_options = SockOpts} = State) ->
+ OptsReply = get_socket_opts(Socket, OptTags, SockOpts, []),
+ {reply, OptsReply, StateName, State};
+
+handle_sync_event(sockname, _From, StateName,
+ #state{socket = Socket} = State) ->
+ SockNameReply = inet:sockname(Socket),
+ {reply, SockNameReply, StateName, State};
+
+handle_sync_event(peername, _From, StateName,
+ #state{socket = Socket} = State) ->
+ PeerNameReply = inet:peername(Socket),
+ {reply, PeerNameReply, StateName, State};
+
+handle_sync_event({set_opts, Opts0}, _From, StateName,
+ #state{socket_options = Opts1,
+ socket = Socket,
+ user_data_buffer = Buffer} = State0) ->
+ Opts = set_socket_opts(Socket, Opts0, Opts1, []),
+ State1 = State0#state{socket_options = Opts},
+ if
+ Opts#socket_options.active =:= false ->
+ {reply, ok, StateName, State1};
+ Buffer =:= <<>>, Opts1#socket_options.active =:= false ->
+ %% Need data, set active once
+ {reply, ok, StateName, next_record_if_active(State1)};
+ Buffer =:= <<>> ->
+ %% Active once already set
+ {reply, ok, StateName, State1};
+ true ->
+ case application_data(<<>>, State1) of
+ Stop = {stop,_,_} ->
+ Stop;
+ State ->
+ {reply, ok, StateName, State}
+ end
+ end;
+
+handle_sync_event(info, _, StateName,
+ #state{negotiated_version = Version,
+ session = #session{cipher_suite = Suite}} = State) ->
+
+ AtomVersion = ssl_record:protocol_version(Version),
+ {reply, {ok, {AtomVersion, ssl_cipher:suite_definition(Suite)}},
+ StateName, State};
+
+handle_sync_event(session_info, _, StateName,
+ #state{session = #session{session_id = Id,
+ cipher_suite = Suite}} = State) ->
+ {reply, [{session_id, Id},
+ {cipher_suite, ssl_cipher:suite_definition(Suite)}],
+ StateName, State};
+
+handle_sync_event(peer_certificate, _, StateName,
+ #state{session = #session{peer_certificate = Cert}}
+ = State) ->
+ {reply, {ok, Cert}, StateName, State}.
+
+
+%%--------------------------------------------------------------------
+%% Function:
+%% handle_info(Info,StateName,State)-> {next_state, NextStateName, NextState}|
+%% {next_state, NextStateName, NextState,
+%% Timeout} |
+%% {stop, Reason, NewState}
+%% Description: This function is called by a gen_fsm when it receives any
+%% other message than a synchronous or asynchronous event
+%% (or a system message).
+%%--------------------------------------------------------------------
+
+%% raw data from TCP, unpack records
+handle_info({Protocol, _, Data}, StateName, State =
+ #state{data_tag = Protocol,
+ negotiated_version = Version,
+ tls_record_buffer = Buf0,
+ tls_cipher_texts = CT0}) ->
+ case ssl_record:get_tls_records(Data, Buf0) of
+ {Records, Buf1} ->
+ CT1 = CT0 ++ Records,
+ {next_state, StateName,
+ next_record(State#state{tls_record_buffer = Buf1,
+ tls_cipher_texts = CT1})};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, StateName, State),
+ {stop, normal, State}
+ end;
+
+%% %% This is the code for {packet,ssl} removed because it was slower
+%% %% than handling it in erlang.
+%% handle_info(Data = #ssl_tls{}, StateName,
+%% State = #state{tls_buffer = Buffer,
+%% socket = Socket,
+%% connection_states = ConnectionStates0}) ->
+%% case Buffer of
+%% buffer ->
+%% {next_state, StateName, State#state{tls_buffer = [Data]}};
+%% continue ->
+%% inet:setopts(Socket, [{active,once}]),
+%% {Plain, ConnectionStates} =
+%% ssl_record:decode_cipher_text(Data, ConnectionStates0),
+%% gen_fsm:send_all_state_event(self(), Plain),
+%% {next_state, StateName,
+%% State#state{tls_buffer = buffer,
+%% connection_states = ConnectionStates}};
+%% List when is_list(List) ->
+%% {next_state, StateName,
+%% State#state{tls_buffer = Buffer ++ [Data]}}
+%% end;
+
+%% handle_info(CloseMsg = {_, Socket}, StateName0,
+%% #state{socket = Socket,tls_buffer = [Msg]} = State0) ->
+%% %% Hmm we have a ssl_tls msg buffered, handle that first
+%% %% and it proberbly is a close alert
+%% {next_state, StateName0, State0#state{tls_buffer=[Msg,{ssl_close,CloseMsg}]}};
+
+handle_info({CloseTag, Socket}, _StateName,
+ #state{socket = Socket, close_tag = CloseTag,
+ negotiated_version = Version, host = Host,
+ port = Port, socket_options = Opts,
+ user_application = {_Mon,Pid}, from = From,
+ role = Role, session = Session} = State) ->
+ %% Debug option maybe, the user do NOT want to see these in their logs
+ %% error_logger:info_report("SSL: Peer did not send close notify alert."),
+ case Version of
+ {1, N} when N >= 1 ->
+ ok;
+ _ ->
+ invalidate_session(Role, Host, Port, Session)
+ end,
+ alert_user(Opts#socket_options.active, Pid, From,
+ ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), Role),
+ {stop, normal, State};
+
+handle_info({'DOWN', MonitorRef, _, _, _}, _,
+ State = #state{user_application={MonitorRef,_Pid}}) ->
+ {stop, normal, State};
+
+handle_info(A, StateName, State) ->
+ io:format("SSL: Bad info (state ~w): ~w\n", [StateName, A]),
+ {stop, bad_info, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, StateName, State) -> void()
+%% Description:This function is called by a gen_fsm when it is about
+%% to terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_fsm terminates with
+%% Reason. The return value is ignored.
+%%--------------------------------------------------------------------
+terminate(_Reason, connection, _S=#state{negotiated_version = Version,
+ connection_states = ConnectionStates,
+ transport_cb = Transport,
+ socket = Socket}) ->
+ {BinAlert, _} = encode_alert(?ALERT_REC(?WARNING,?CLOSE_NOTIFY),
+ Version, ConnectionStates),
+ Transport:send(Socket, BinAlert),
+ Transport:close(Socket);
+terminate(_Reason, _StateName, _S=#state{transport_cb = Transport, socket = Socket}) ->
+ Transport:close(Socket),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function:
+%% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState}
+%% Description: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_OldVsn, StateName, State, _Extra) ->
+ {ok, StateName, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+start_fsm(Role, Host, Port, Socket, Opts, User, {CbModule, _,_} = CbInfo,
+ Timeout) ->
+ case ssl_connection_sup:start_child([Role, Host, Port, Socket,
+ Opts, User, CbInfo]) of
+ {ok, Pid} ->
+ CbModule:controlling_process(Socket, Pid),
+ send_event(Pid, socket_control),
+ case sync_send_all_state_event(Pid, started, Timeout) of
+ connected ->
+ {ok, sslsocket(Pid)};
+ {error, Reason} ->
+ {error, Reason}
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+ssl_init(SslOpts, Role) ->
+ {ok, CertDbRef, CacheRef, OwnCert} = init_certificates(SslOpts, Role),
+ PrivateKey =
+ init_private_key(SslOpts#ssl_options.key, SslOpts#ssl_options.keyfile,
+ SslOpts#ssl_options.password, Role),
+ ?DBG_TERM(PrivateKey),
+ {ok, CertDbRef, CacheRef, OwnCert, PrivateKey}.
+
+init_certificates(#ssl_options{cacertfile = CACertFile,
+ certfile = CertFile}, Role) ->
+
+ case ssl_manager:connection_init(CACertFile, Role) of
+ {ok, CertDbRef, CacheRef} ->
+ init_certificates(CertDbRef, CacheRef, CertFile, Role);
+ {error, _Error} ->
+ Report = io_lib:format("SSL: Error ~p ~n",[_Error]),
+ error_logger:error_report(Report),
+ throw(ecacertfile)
+ end.
+
+init_certificates(CertDbRef, CacheRef, CertFile, client) ->
+ try
+ [OwnCert] = ssl_certificate:file_to_certificats(CertFile),
+ {ok, CertDbRef, CacheRef, OwnCert}
+ catch _E:_R ->
+ {ok, CertDbRef, CacheRef, undefined}
+ end;
+
+init_certificates(CertDbRef, CacheRef, CertFile, server) ->
+ try
+ [OwnCert] = ssl_certificate:file_to_certificats(CertFile),
+ {ok, CertDbRef, CacheRef, OwnCert}
+ catch _E:_R ->
+ Report = io_lib:format("SSL: ~p: ~p:~p ~p~n",
+ [?LINE, _E,_R, erlang:get_stacktrace()]),
+ error_logger:error_report(Report),
+ throw(ecertfile)
+ end.
+
+init_private_key(undefined, "", _Password, client) ->
+ undefined;
+init_private_key(undefined, KeyFile, Password, _) ->
+ try
+ {ok, List} = ssl_manager:cache_pem_file(KeyFile),
+ [Der] = [Der || Der = {PKey, _ , _} <- List,
+ PKey =:= rsa_private_key orelse PKey =:= dsa_private_key],
+ {ok, Decoded} = public_key:decode_private_key(Der,Password),
+ Decoded
+ catch _E:_R ->
+ Report = io_lib:format("SSL: ~p: ~p:~p ~p~n",
+ [?LINE, _E,_R, erlang:get_stacktrace()]),
+ error_logger:error_report(Report),
+ throw(ekeyfile)
+ end;
+init_private_key(PrivateKey, _, _,_) ->
+ PrivateKey.
+
+send_event(FsmPid, Event) ->
+ gen_fsm:send_event(FsmPid, Event).
+
+sync_send_event(FsmPid, Event, Timeout) ->
+ try gen_fsm:sync_send_event(FsmPid, Event, Timeout) of
+ Reply ->
+ Reply
+ catch
+ exit:{noproc, _} ->
+ {error, closed};
+ exit:{timeout, _} ->
+ {error, timeout};
+ exit:{normal, _} ->
+ {error, closed}
+ end.
+
+
+
+send_all_state_event(FsmPid, Event) ->
+ gen_fsm:send_all_state_event(FsmPid, Event).
+
+sync_send_all_state_event(FsmPid, Event) ->
+ sync_send_all_state_event(FsmPid, Event, ?DEFAULT_TIMEOUT
+).
+
+sync_send_all_state_event(FsmPid, Event, Timeout) ->
+ try gen_fsm:sync_send_all_state_event(FsmPid, Event, Timeout)
+ catch
+ exit:{noproc, _} ->
+ {error, closed};
+ exit:{timeout, _} ->
+ {error, timeout};
+ exit:{normal, _} ->
+ {error, closed}
+ end.
+
+%% Events: #alert{}
+alert_event(Alert) ->
+ send_all_state_event(self(), Alert).
+
+certify_client(#state{client_certificate_requested = true, role = client,
+ connection_states = ConnectionStates0,
+ transport_cb = Transport,
+ negotiated_version = Version,
+ cert_db_ref = CertDbRef,
+ own_cert = OwnCert,
+ socket = Socket,
+ tls_handshake_hashes = Hashes0} = State) ->
+ Certificate = ssl_handshake:certificate(OwnCert, CertDbRef, client),
+ {BinCert, ConnectionStates1, Hashes1} =
+ encode_handshake(Certificate, Version, ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinCert),
+ State#state{connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1};
+certify_client(#state{client_certificate_requested = false} = State) ->
+ State.
+
+verify_client_cert(#state{client_certificate_requested = true, role = client,
+ connection_states = ConnectionStates0,
+ transport_cb = Transport,
+ negotiated_version = Version,
+ own_cert = OwnCert,
+ socket = Socket,
+ key_algorithm = KeyAlg,
+ private_key = PrivateKey,
+ session = #session{master_secret = MasterSecret},
+ tls_handshake_hashes = Hashes0} = State) ->
+ case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret,
+ Version, KeyAlg,
+ PrivateKey, Hashes0) of
+ ignore -> %% No key or cert or fixed_diffie_hellman
+ State;
+ Verified ->
+ SigAlg = ssl_handshake:sig_alg(KeyAlg),
+ {BinVerified, ConnectionStates1, Hashes1} =
+ encode_handshake(Verified, SigAlg, Version,
+ ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinVerified),
+ State#state{connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1}
+ end;
+verify_client_cert(#state{client_certificate_requested = false} = State) ->
+ State.
+
+do_server_hello(Type, #state{negotiated_version = Version,
+ session = Session,
+ connection_states = ConnectionStates0}
+ = State0) when is_atom(Type) ->
+ ServerHello =
+ ssl_handshake:server_hello(Session#session.session_id, Version,
+ ConnectionStates0),
+ State = server_hello(ServerHello, State0),
+
+ case Type of
+ new ->
+ do_server_hello(ServerHello, State);
+ resumed ->
+ case ssl_handshake:master_secret(Version, Session,
+ ConnectionStates0, server) of
+ {_, ConnectionStates1} ->
+ {ConnectionStates, Hashes} =
+ finished(State#state{connection_states =
+ ConnectionStates1}),
+ {next_state, abbreviated,
+ next_record(State#state{connection_states =
+ ConnectionStates,
+ tls_handshake_hashes = Hashes})};
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, hello, State),
+ {stop, normal, State}
+ end
+ end;
+
+do_server_hello(#server_hello{cipher_suite = CipherSuite,
+ compression_method = Compression,
+ session_id = SessionId},
+ #state{session = Session0,
+ negotiated_version = Version} = State0) ->
+ try server_certify_and_key_exchange(State0) of
+ #state{} = State1 ->
+ State = server_hello_done(State1),
+ Session =
+ Session0#session{session_id = SessionId,
+ cipher_suite = CipherSuite,
+ compression_method = Compression},
+ {next_state, certify, State#state{session = Session}}
+ catch
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, hello, State0),
+ {stop, normal, State0}
+ end.
+
+client_certify_and_key_exchange(#state{negotiated_version = Version} =
+ State0) ->
+ try do_client_certify_and_key_exchange(State0) of
+ State1 = #state{} ->
+ {ConnectionStates, Hashes} = finalize_client_handshake(State1),
+ State = State1#state{connection_states = ConnectionStates,
+ %% Reinitialize
+ client_certificate_requested = false,
+ tls_handshake_hashes = Hashes},
+ {next_state, cipher, next_record(State)}
+
+ catch
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, certify_foo, State0),
+ {stop, normal, State0}
+ end.
+
+do_client_certify_and_key_exchange(State0) ->
+ State1 = certify_client(State0),
+ State2 = key_exchange(State1),
+ verify_client_cert(State2).
+
+server_certify_and_key_exchange(State0) ->
+ State1 = certify_server(State0),
+ State2 = key_exchange(State1),
+ request_client_cert(State2).
+
+server_hello(ServerHello, #state{transport_cb = Transport,
+ socket = Socket,
+ negotiated_version = Version,
+ connection_states = ConnectionStates0,
+ tls_handshake_hashes = Hashes0} = State) ->
+ CipherSuite = ServerHello#server_hello.cipher_suite,
+ {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite),
+ %% Version = ServerHello#server_hello.server_version, TODO ska kontrolleras
+ {BinMsg, ConnectionStates1, Hashes1} =
+ encode_handshake(ServerHello, Version, ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinMsg),
+ State#state{connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1,
+ key_algorithm = KeyAlgorithm}.
+
+server_hello_done(#state{transport_cb = Transport,
+ socket = Socket,
+ negotiated_version = Version,
+ connection_states = ConnectionStates,
+ tls_handshake_hashes = Hashes} = State0) ->
+
+ HelloDone = ssl_handshake:server_hello_done(),
+
+ {BinHelloDone, NewConnectionStates, NewHashes} =
+ encode_handshake(HelloDone, Version, ConnectionStates, Hashes),
+ Transport:send(Socket, BinHelloDone),
+ State = State0#state{connection_states = NewConnectionStates,
+ tls_handshake_hashes = NewHashes},
+ next_record(State).
+
+certify_server(#state{transport_cb = Transport,
+ socket = Socket,
+ negotiated_version = Version,
+ connection_states = ConnectionStates,
+ tls_handshake_hashes = Hashes,
+ cert_db_ref = CertDbRef,
+ own_cert = OwnCert} = State) ->
+
+ case ssl_handshake:certificate(OwnCert, CertDbRef, server) of
+ CertMsg = #certificate{} ->
+ {BinCertMsg, NewConnectionStates, NewHashes} =
+ encode_handshake(CertMsg, Version, ConnectionStates, Hashes),
+ Transport:send(Socket, BinCertMsg),
+ State#state{connection_states = NewConnectionStates,
+ tls_handshake_hashes = NewHashes
+ };
+ Alert = #alert{} ->
+ throw(Alert)
+ end.
+
+key_exchange(#state{role = server, key_algorithm = Algo} = State)
+ when Algo == rsa;
+ Algo == dh_dss;
+ Algo == dh_rsa ->
+ State;
+
+key_exchange(#state{role = server, key_algorithm = rsa_export} = State) ->
+ %% TODO when the public key in the server certificate is
+ %% less than or equal to 512 bits in length dont send key_exchange
+ %% but do it otherwise
+ State;
+
+key_exchange(#state{role = server, key_algorithm = Algo,
+ diffie_hellman_params = Params,
+ connection_states = ConnectionStates0,
+ negotiated_version = Version,
+ tls_handshake_hashes = Hashes0,
+ socket = Socket,
+ transport_cb = Transport
+ } = State)
+ when Algo == dhe_dss;
+ Algo == dhe_dss_export;
+ Algo == dhe_rsa;
+ Algo == dhe_rsa_export ->
+ Msg = ssl_handshake:key_exchange(server, Params),
+ {BinMsg, ConnectionStates1, Hashes1} =
+ encode_handshake(Msg, Version, ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinMsg),
+ State#state{connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1};
+
+key_exchange(#state{role = server, key_algorithm = dh_anon,
+ connection_states = ConnectionStates0,
+ negotiated_version = Version,
+ tls_handshake_hashes = Hashes0,
+ socket = Socket,
+ transport_cb = Transport
+ } = State) ->
+ Msg = ssl_handshake:key_exchange(server, anonymous),
+ {BinMsg, ConnectionStates1, Hashes1} =
+ encode_handshake(Msg, Version, ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinMsg),
+ State#state{connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1};
+
+key_exchange(#state{role = client,
+ connection_states = ConnectionStates0,
+ key_algorithm = rsa,
+ public_key_info = PublicKeyInfo,
+ negotiated_version = Version,
+ premaster_secret = PremasterSecret,
+ socket = Socket, transport_cb = Transport,
+ tls_handshake_hashes = Hashes0} = State) ->
+ Msg = rsa_key_exchange(PremasterSecret, PublicKeyInfo),
+ {BinMsg, ConnectionStates1, Hashes1} =
+ encode_handshake(Msg, Version, ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinMsg),
+ State#state{connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1};
+
+key_exchange(#state{role = client,
+ connection_states = ConnectionStates0,
+ key_algorithm = Algorithm,
+ public_key_info = PublicKeyInfo,
+ negotiated_version = Version,
+ diffie_hellman_params = Params,
+ own_cert = Cert,
+ socket = Socket, transport_cb = Transport,
+ tls_handshake_hashes = Hashes0} = State)
+ when Algorithm == dhe_dss;
+ Algorithm == dhe_dss_export;
+ Algorithm == dhe_rsa;
+ Algorithm == dhe_rsa_export ->
+ Msg = dh_key_exchange(Cert, Params, PublicKeyInfo),
+ {BinMsg, ConnectionStates1, Hashes1} =
+ encode_handshake(Msg, Version, ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinMsg),
+ State#state{connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1}.
+
+rsa_key_exchange(PremasterSecret, PublicKeyInfo = {Algorithm, _, _})
+ when Algorithm == ?rsaEncryption;
+ Algorithm == ?md2WithRSAEncryption;
+ Algorithm == ?md5WithRSAEncryption;
+ Algorithm == ?sha1WithRSAEncryption ->
+ ssl_handshake:key_exchange(client,
+ {premaster_secret, PremasterSecret,
+ PublicKeyInfo});
+
+rsa_key_exchange(_, _) ->
+ throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)).
+
+dh_key_exchange(OwnCert, Params, PublicKeyInfo) ->
+ case public_key:pkix_is_fixed_dh_cert(OwnCert) of
+ true ->
+ ssl_handshake:key_exchange(client, fixed_diffie_hellman);
+ false ->
+ ssl_handshake:key_exchange(client, {dh, Params, PublicKeyInfo})
+ end.
+
+request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer},
+ connection_states = ConnectionStates0,
+ cert_db_ref = CertDbRef,
+ tls_handshake_hashes = Hashes0,
+ negotiated_version = Version,
+ socket = Socket,
+ transport_cb = Transport} = State) ->
+ Msg = ssl_handshake:certificate_request(ConnectionStates0, CertDbRef),
+ {BinMsg, ConnectionStates1, Hashes1} =
+ encode_handshake(Msg, Version, ConnectionStates0, Hashes0),
+ Transport:send(Socket, BinMsg),
+ State#state{client_certificate_requested = true,
+ connection_states = ConnectionStates1,
+ tls_handshake_hashes = Hashes1};
+request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} =
+ State) ->
+ State.
+
+finalize_client_handshake(#state{connection_states = ConnectionStates0}
+ = State) ->
+ ConnectionStates1 =
+ cipher_protocol(State#state{connection_states =
+ ConnectionStates0}),
+ ConnectionStates2 =
+ ssl_record:activate_pending_connection_state(ConnectionStates1,
+ write),
+ finished(State#state{connection_states = ConnectionStates2}).
+
+
+finalize_server_handshake(State) ->
+ ConnectionStates0 = cipher_protocol(State),
+ ConnectionStates =
+ ssl_record:activate_pending_connection_state(ConnectionStates0, write),
+ finished(State#state{connection_states = ConnectionStates}).
+
+cipher_protocol(#state{connection_states = ConnectionStates,
+ socket = Socket,
+ negotiated_version = Version,
+ transport_cb = Transport}) ->
+ {BinChangeCipher, NewConnectionStates} =
+ encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates),
+ Transport:send(Socket, BinChangeCipher),
+ NewConnectionStates.
+
+finished(#state{role = Role, socket = Socket, negotiated_version = Version,
+ transport_cb = Transport,
+ session = Session,
+ connection_states = ConnectionStates,
+ tls_handshake_hashes = Hashes}) ->
+ MasterSecret = Session#session.master_secret,
+ Finished = ssl_handshake:finished(Version, Role, MasterSecret, Hashes),
+ {BinFinished, NewConnectionStates, NewHashes} =
+ encode_handshake(Finished, Version, ConnectionStates, Hashes),
+ Transport:send(Socket, BinFinished),
+ {NewConnectionStates, NewHashes}.
+
+handle_server_key(_KeyExchangeMsg, State) ->
+ State.
+handle_clinet_key(_KeyExchangeMsg, State) ->
+ State.
+
+encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
+ ?DBG_TERM(Alert),
+ ssl_record:encode_alert_record(Alert, Version, ConnectionStates).
+
+encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) ->
+ ?DBG_TERM(#change_cipher_spec{}),
+ ssl_record:encode_change_cipher_spec(Version, ConnectionStates).
+
+encode_handshake(HandshakeRec, Version, ConnectionStates, Hashes) ->
+ encode_handshake(HandshakeRec, undefined, Version,
+ ConnectionStates, Hashes).
+
+encode_handshake(HandshakeRec, SigAlg, Version, ConnectionStates0, Hashes0) ->
+ ?DBG_TERM(HandshakeRec),
+ Frag = ssl_handshake:encode_handshake(HandshakeRec, Version, SigAlg),
+ Hashes1 = ssl_handshake:update_hashes(Hashes0, Frag),
+ {E, ConnectionStates1} =
+ ssl_record:encode_handshake(Frag, Version, ConnectionStates0),
+ {E, ConnectionStates1, Hashes1}.
+
+encode_data(Data, Version, ConnectionStates) ->
+ ssl_record:encode_data(Data, Version, ConnectionStates).
+
+decode_alerts(Bin) ->
+ decode_alerts(Bin, []).
+
+decode_alerts(<<?BYTE(Level), ?BYTE(Description), Rest/binary>>, Acc) ->
+ A = ?ALERT_REC(Level, Description),
+ decode_alerts(Rest, [A | Acc]);
+decode_alerts(<<>>, Acc) ->
+ lists:reverse(Acc, []).
+
+application_data(Data, #state{user_application = {_Mon, Pid},
+ socket_options = SOpts,
+ bytes_to_read = BytesToRead,
+ from = From,
+ user_data_buffer = Buffer0} = State0) ->
+ Buffer1 = if
+ Buffer0 =:= <<>> -> Data;
+ Data =:= <<>> -> Buffer0;
+ true -> <<Buffer0/binary, Data/binary>>
+ end,
+ case get_data(SOpts, BytesToRead, Buffer1) of
+ {ok, <<>>, Buffer} -> % no reply, we need more data
+ next_record(State0#state{user_data_buffer = Buffer});
+ {ok, ClientData, Buffer} -> % Send data
+ SocketOpt = deliver_app_data(SOpts, ClientData, Pid, From),
+ State = State0#state{user_data_buffer = Buffer,
+ from = undefined,
+ bytes_to_read = 0,
+ socket_options = SocketOpt
+ },
+ if
+ SocketOpt#socket_options.active =:= false ->
+ State; %% Passive mode, wait for active once or recv
+ Buffer =:= <<>> -> %% Active and empty, get more data
+ next_record(State);
+ true -> %% We have more data
+ application_data(<<>>, State)
+ end;
+ {error,_Reason} -> %% Invalid packet in packet mode
+ deliver_packet_error(SOpts, Buffer1, Pid, From),
+ {stop, normal, State0}
+ end.
+
+%% Picks ClientData
+get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer)
+ when Raw =:= raw; Raw =:= 0 -> %% Raw Mode
+ if
+ Active =/= false orelse BytesToRead =:= 0 ->
+ %% Active true or once, or passive mode recv(0)
+ {ok, Buffer, <<>>};
+ byte_size(Buffer) >= BytesToRead ->
+ %% Passive Mode, recv(Bytes)
+ <<Data:BytesToRead/binary, Rest/binary>> = Buffer,
+ {ok, Data, Rest};
+ true ->
+ %% Passive Mode not enough data
+ {ok, <<>>, Buffer}
+ end;
+get_data(#socket_options{packet=Type, packet_size=Size}, _, Buffer) ->
+ PacketOpts = [{packet_size, Size}],
+ case erlang:decode_packet(Type, Buffer, PacketOpts) of
+ {more, _} ->
+ {ok, <<>>, Buffer};
+ Decoded ->
+ Decoded
+ end.
+
+deliver_app_data(SO = #socket_options{active=once}, Data, Pid, From) ->
+ send_or_reply(once, Pid, From, format_reply(SO, Data)),
+ SO#socket_options{active=false};
+deliver_app_data(SO= #socket_options{active=Active}, Data, Pid, From) ->
+ send_or_reply(Active, Pid, From, format_reply(SO, Data)),
+ SO.
+
+format_reply(#socket_options{active=false, mode=Mode, header=Header}, Data) ->
+ {ok, format_reply(Mode, Header, Data)};
+format_reply(#socket_options{active=_, mode=Mode, header=Header}, Data) ->
+ {ssl, sslsocket(), format_reply(Mode, Header, Data)}.
+
+deliver_packet_error(SO= #socket_options{active=Active}, Data, Pid, From) ->
+ send_or_reply(Active, Pid, From, format_packet_error(SO, Data)).
+
+format_packet_error(#socket_options{active=false, mode=Mode}, Data) ->
+ {error, {invalid_packet, format_reply(Mode, raw, Data)}};
+format_packet_error(#socket_options{active=_, mode=Mode}, Data) ->
+ {ssl_error, sslsocket(), {invalid_packet, format_reply(Mode, raw, Data)}}.
+
+format_reply(list, _, Data) -> binary_to_list(Data);
+format_reply(binary, 0, Data) -> Data;
+format_reply(binary, raw, Data) -> Data;
+format_reply(binary, N, Data) -> % Header mode
+ <<Header:N/binary, Rest/binary>> = Data,
+ [binary_to_list(Header), Rest].
+
+%% tcp_closed
+send_or_reply(false, _Pid, undefined, _Data) ->
+ Report = io_lib:format("SSL(debug): Unexpected Data ~p ~n",[_Data]),
+ error_logger:error_report(Report),
+ erlang:error({badarg, _Pid, undefined, _Data}),
+ ok;
+send_or_reply(false, _Pid, From, Data) ->
+ gen_fsm:reply(From, Data);
+send_or_reply(_, Pid, _From, Data) ->
+ send_user(Pid, Data).
+
+opposite_role(client) ->
+ server;
+opposite_role(server) ->
+ client.
+
+send_user(Pid, Msg) ->
+ Pid ! Msg.
+
+%% %% This is the code for {packet,ssl} removed because it was slower
+%% %% than handling it in erlang.
+%% next_record(#state{socket = Socket,
+%% tls_buffer = [Msg|Rest],
+%% connection_states = ConnectionStates0} = State) ->
+%% Buffer =
+%% case Rest of
+%% [] ->
+%% inet:setopts(Socket, [{active,once}]),
+%% buffer;
+%% _ -> Rest
+%% end,
+%% case Msg of
+%% #ssl_tls{} ->
+%% {Plain, ConnectionStates} =
+%% ssl_record:decode_cipher_text(Msg, ConnectionStates0),
+%% gen_fsm:send_all_state_event(self(), Plain),
+%% State#state{tls_buffer=Buffer, connection_states = ConnectionStates};
+%% {ssl_close, Msg} ->
+%% self() ! Msg,
+%% State#state{tls_buffer=Buffer}
+%% end;
+%% next_record(#state{socket = Socket, tls_buffer = undefined} = State) ->
+%% inet:setopts(Socket, [{active,once}]),
+%% State#state{tls_buffer=continue};
+%% next_record(State) ->
+%% State#state{tls_buffer=continue}.
+
+next_record(#state{tls_cipher_texts = [], socket = Socket} = State) ->
+ inet:setopts(Socket, [{active,once}]),
+ State;
+next_record(#state{tls_cipher_texts = [CT | Rest],
+ connection_states = ConnStates0} = State) ->
+ {Plain, ConnStates} = ssl_record:decode_cipher_text(CT, ConnStates0),
+ gen_fsm:send_all_state_event(self(), Plain),
+ State#state{tls_cipher_texts = Rest, connection_states = ConnStates}.
+
+next_record_if_active(State =
+ #state{socket_options =
+ #socket_options{active = false}}) ->
+ State;
+next_record_if_active(State) ->
+ next_record(State).
+
+register_session(_, _, _, #session{is_resumable = true} = Session) ->
+ Session; %% Already registered
+register_session(client, Host, Port, Session0) ->
+ Session = Session0#session{is_resumable = true},
+ ssl_manager:register_session(Host, Port, Session),
+ Session;
+register_session(server, _, Port, Session0) ->
+ Session = Session0#session{is_resumable = true},
+ ssl_manager:register_session(Port, Session),
+ Session.
+
+invalidate_session(client, Host, Port, Session) ->
+ ssl_manager:invalidate_session(Host, Port, Session);
+invalidate_session(server, _, Port, Session) ->
+ ssl_manager:invalidate_session(Port, Session).
+
+initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
+ {CbModule, DataTag, CloseTag}) ->
+ ConnectionStates = ssl_record:init_connection_states(Role),
+
+ SessionCacheCb = case application:get_env(ssl, session_cb) of
+ {ok, Cb} when is_atom(Cb) ->
+ Cb;
+ _ ->
+ ssl_session_cache
+ end,
+
+ Monitor = erlang:monitor(process, User),
+
+ #state{socket_options = SocketOptions,
+ %% We do not want to save the password in the state so that
+ %% could be written in the clear into error logs.
+ ssl_options = SSLOptions#ssl_options{password = undefined},
+ session = #session{is_resumable = false},
+ transport_cb = CbModule,
+ data_tag = DataTag,
+ close_tag = CloseTag,
+ role = Role,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ connection_states = ConnectionStates,
+ tls_handshake_buffer = <<>>,
+ tls_record_buffer = <<>>,
+ tls_cipher_texts = [],
+ user_application = {Monitor, User},
+ bytes_to_read = 0,
+ user_data_buffer = <<>>,
+ log_alert = true,
+ session_cache_cb = SessionCacheCb
+ }.
+
+sslsocket(Pid) ->
+ #sslsocket{pid = Pid, fd = new_ssl}.
+
+sslsocket() ->
+ sslsocket(self()).
+
+get_socket_opts(_,[], _, Acc) ->
+ {ok, Acc};
+get_socket_opts(Socket, [mode | Tags], SockOpts, Acc) ->
+ get_socket_opts(Socket, Tags, SockOpts,
+ [{mode, SockOpts#socket_options.mode} | Acc]);
+get_socket_opts(Socket, [packet | Tags], SockOpts, Acc) ->
+ get_socket_opts(Socket, Tags, SockOpts,
+ [{packet, SockOpts#socket_options.packet} | Acc]);
+get_socket_opts(Socket, [header | Tags], SockOpts, Acc) ->
+ get_socket_opts(Socket, Tags, SockOpts,
+ [{header, SockOpts#socket_options.header} | Acc]);
+get_socket_opts(Socket, [active | Tags], SockOpts, Acc) ->
+ get_socket_opts(Socket, Tags, SockOpts,
+ [{active, SockOpts#socket_options.active} | Acc]);
+get_socket_opts(Socket, [Tag | Tags], SockOpts, Acc) ->
+ case inet:getopts(Socket, [Tag]) of
+ {ok, [Opt]} ->
+ get_socket_opts(Socket, Tags, SockOpts, [Opt | Acc]);
+ {error, Error} ->
+ {error, Error}
+ end.
+
+set_socket_opts(_, [], SockOpts, []) ->
+ SockOpts;
+set_socket_opts(Socket, [], SockOpts, Other) ->
+ %% Set non emulated options
+ inet:setopts(Socket, Other),
+ SockOpts;
+set_socket_opts(Socket, [{mode, Mode}| Opts], SockOpts, Other) ->
+ set_socket_opts(Socket, Opts, SockOpts#socket_options{mode = Mode}, Other);
+set_socket_opts(Socket, [{packet, Packet}| Opts], SockOpts, Other) ->
+ set_socket_opts(Socket, Opts,
+ SockOpts#socket_options{packet = Packet}, Other);
+set_socket_opts(Socket, [{header, Header}| Opts], SockOpts, Other) ->
+ set_socket_opts(Socket, Opts,
+ SockOpts#socket_options{header = Header}, Other);
+set_socket_opts(Socket, [{active, Active}| Opts], SockOpts, Other) ->
+ set_socket_opts(Socket, Opts,
+ SockOpts#socket_options{active = Active}, Other);
+set_socket_opts(Socket, [Opt | Opts], SockOpts, Other) ->
+ set_socket_opts(Socket, Opts, SockOpts, [Opt | Other]).
+
+alert_user(From, Alert, Role) ->
+ alert_user(false, no_pid, From, Alert, Role).
+
+alert_user(false = Active, Pid, From, Alert, Role) ->
+ ReasonCode = ssl_alert:reason_code(Alert, Role),
+ send_or_reply(Active, Pid, From, {error, ReasonCode});
+alert_user(Active, Pid, From, Alert, Role) ->
+ case ssl_alert:reason_code(Alert, Role) of
+ closed ->
+ send_or_reply(Active, Pid, From,
+ {ssl_closed, sslsocket()});
+ ReasonCode ->
+ send_or_reply(Active, Pid, From,
+ {ssl_error, sslsocket(), ReasonCode})
+ end.
+
+log_alert(true, StateName, Alert) ->
+ Txt = ssl_alert:alert_txt(Alert),
+ error_logger:format("SSL: ~p: ~s\n", [StateName, Txt]);
+log_alert(false, _, _) ->
+ ok.
+
+handle_own_alert(Alert, Version, StateName,
+ #state{transport_cb = Transport,
+ socket = Socket,
+ from = User,
+ role = Role,
+ connection_states = ConnectionStates,
+ log_alert = Log}) ->
+ {BinMsg, _} =
+ encode_alert(Alert, Version, ConnectionStates),
+ Transport:send(Socket, BinMsg),
+ log_alert(Log, StateName, Alert),
+ alert_user(User, Alert, Role).
+
+make_premaster_secret({MajVer, MinVer}) ->
+ Rand = crypto:rand_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
+ <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>.
diff --git a/lib/ssl/src/ssl_connection_sup.erl b/lib/ssl/src/ssl_connection_sup.erl
new file mode 100644
index 0000000000..e9328d5f7c
--- /dev/null
+++ b/lib/ssl/src/ssl_connection_sup.erl
@@ -0,0 +1,60 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: The top supervisor for the ftp hangs under inets_sup.
+%%----------------------------------------------------------------------
+-module(ssl_connection_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+-export([start_child/1]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+start_child(Args) ->
+ supervisor:start_child(?MODULE, Args).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init(_O) ->
+ RestartStrategy = simple_one_for_one,
+ MaxR = 0,
+ MaxT = 3600,
+
+ Name = undefined, % As simple_one_for_one is used.
+ StartFunc = {ssl_connection, start_link, []},
+ Restart = temporary, % E.g. should not be restarted
+ Shutdown = 4000,
+ Modules = [ssl_connection],
+ Type = worker,
+
+ ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
+ {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}.
diff --git a/lib/ssl/src/ssl_debug.erl b/lib/ssl/src/ssl_debug.erl
new file mode 100644
index 0000000000..625889c43b
--- /dev/null
+++ b/lib/ssl/src/ssl_debug.erl
@@ -0,0 +1,99 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+%%% Purpose : some debug utilities
+
+-module(ssl_debug).
+
+-export([unhex/1, hexd/1, hex_data/2, term_data/2, hex_data/4, term_data/4, make_binary/1]).
+
+%% external
+
+hex_data(Name, Data) ->
+ io:format("~s\n~s", [Name, hex(Data)]).
+
+term_data(Name, Term) ->
+ io:format("~s\n~p\n", [Name, Term]).
+
+hex_data(Name, Data, Mod, Line) ->
+ io:format("~w:~p ~s\n~s", [Mod, Line, Name, hex(Data)]).
+
+term_data(Name, Term, Mod, Line) ->
+ io:format("~w:~p ~s\n~p\n", [Mod, Line, Name, Term]).
+
+unhex(S) ->
+ Lines = string:tokens(S, "\n"),
+ H = [unhex(L, []) || L <- Lines],
+ list_to_binary(H).
+
+make_binary(Size) ->
+ crypto:rand_bytes(Size).
+
+%% internal
+
+is_hex_digit(C) when C >= $0, C =< $9 -> true;
+is_hex_digit(C) when C >= $A, C =< $F -> true;
+is_hex_digit(C) when C >= $a, C =< $f -> true;
+is_hex_digit(_) -> false.
+
+unhex([], Acc) ->
+ list_to_binary(lists:reverse(Acc));
+unhex([_], Acc) ->
+ unhex([], Acc);
+unhex([$ | Tl], Acc) ->
+ unhex(Tl, Acc);
+unhex([D1, D2 | Tl], Acc) ->
+ case {is_hex_digit(D1), is_hex_digit(D2)} of
+ {true, true} ->
+ unhex(Tl, [erlang:list_to_integer([D1, D2], 16) | Acc]);
+ _ ->
+ unhex([], Acc)
+ end.
+
+hexd(B) ->
+ io:format("~s\n", [hex(B)]).
+
+hex(B) -> hex(erlang:iolist_to_binary(B), []).
+
+hex_asc(B) ->
+ L = binary_to_list(B),
+ {hexify(L), asciify(L)}.
+
+hex(<<B:16/binary, Rest/binary>>, Acc) ->
+ {HS, AS} = hex_asc(B),
+ hex(Rest, ["\n", AS, " ", HS | Acc]);
+hex(<<>>, Acc) ->
+ lists:reverse(Acc);
+hex(B, Acc) ->
+ {HS, AS} = hex_asc(B),
+ L = erlang:iolist_size(HS),
+ lists:flatten(lists:reverse(Acc, [HS, lists:duplicate(3*16 - L, $ ), " ", AS, "\n"])).
+
+hexify(L) -> [[hex_byte(B), " "] || B <- L].
+
+hex_byte(B) when B < 16#10 -> ["0", erlang:integer_to_list(B, 16)];
+hex_byte(B) -> erlang:integer_to_list(B, 16).
+
+asciify(L) -> [ascii_byte(C) || C <- L].
+
+ascii_byte($") -> $.;
+ascii_byte(C) when C < 32; C >= 127 -> $.;
+ascii_byte(C) -> C.
diff --git a/lib/ssl/src/ssl_debug.hrl b/lib/ssl/src/ssl_debug.hrl
new file mode 100644
index 0000000000..e88cef441f
--- /dev/null
+++ b/lib/ssl/src/ssl_debug.hrl
@@ -0,0 +1,39 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+
+-ifndef(ssl_debug).
+-define(ssl_debug, true).
+
+-ifdef(SSL_DEBUG).
+-define(DBG_HEX(V), ssl_debug:hex_data(??V, V, ?MODULE, ?LINE)).
+-define(DBG_TERM(T), ssl_debug:term_data(??T, T, ?MODULE, ?LINE)).
+-else.
+-define(DBG_HEX(V), ok).
+-define(DBG_TERM(T), ok).
+-endif.
+
+-endif. % -ifdef(ssl_debug).
+
+
+
+
+
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
new file mode 100644
index 0000000000..829e0c2ba6
--- /dev/null
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -0,0 +1,917 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%----------------------------------------------------------------------
+%% Purpose: Help funtions for handling the SSL-handshake protocol
+%%----------------------------------------------------------------------
+
+-module(ssl_handshake).
+
+-include("ssl_handshake.hrl").
+-include("ssl_record.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_debug.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+-export([master_secret/4, client_hello/4, server_hello/3, hello/2,
+ certify/5, certificate/3,
+ client_certificate_verify/6,
+ certificate_verify/6, certificate_request/2,
+ key_exchange/2, finished/4,
+ verify_connection/5,
+ get_tls_handshake/4,
+ server_hello_done/0, sig_alg/1,
+ encode_handshake/3, init_hashes/0,
+ update_hashes/2, decrypt_premaster_secret/2]).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: client_hello(Host, Port, ConnectionStates, SslOpts) ->
+%% #client_hello{}
+%% Host
+%% Port
+%% ConnectionStates = #connection_states{}
+%% SslOpts = #ssl_options{}
+%%
+%% Description: Creates a client hello message.
+%%--------------------------------------------------------------------
+client_hello(Host, Port, ConnectionStates, #ssl_options{versions = Versions,
+ ciphers = Ciphers}
+ = SslOpts) ->
+
+ Fun = fun(Version) ->
+ ssl_record:protocol_version(Version)
+ end,
+ Version = ssl_record:highest_protocol_version(lists:map(Fun, Versions)),
+ Pending = ssl_record:pending_connection_state(ConnectionStates, read),
+ SecParams = Pending#connection_state.security_parameters,
+
+ Id = ssl_manager:client_session_id(Host, Port, SslOpts),
+
+ #client_hello{session_id = Id,
+ client_version = Version,
+ cipher_suites = Ciphers,
+ compression_methods = ssl_record:compressions(),
+ random = SecParams#security_parameters.client_random
+ }.
+
+%%--------------------------------------------------------------------
+%% Function: server_hello(Host, Port, SessionId,
+%% Version, ConnectionStates) -> #server_hello{}
+%% SessionId
+%% Version
+%% ConnectionStates
+%%
+%%
+%% Description: Creates a server hello message.
+%%--------------------------------------------------------------------
+server_hello(SessionId, Version, ConnectionStates) ->
+ Pending = ssl_record:pending_connection_state(ConnectionStates, read),
+ SecParams = Pending#connection_state.security_parameters,
+ #server_hello{server_version = Version,
+ cipher_suite = SecParams#security_parameters.cipher_suite,
+ compression_method =
+ SecParams#security_parameters.compression_algorithm,
+ random = SecParams#security_parameters.server_random,
+ session_id = SessionId
+ }.
+
+%%--------------------------------------------------------------------
+%% Function: hello(Hello, Info) ->
+%% {Version, Id, NewConnectionStates} |
+%% #alert{}
+%%
+%% Hello = #client_hello{} | #server_hello{}
+%% Info = ConnectionStates | {Port, Session, ConnectionStates}
+%% ConnectionStates = #connection_states{}
+%%
+%% Description: Handles a recieved hello message
+%%--------------------------------------------------------------------
+hello(#server_hello{cipher_suite = CipherSuite, server_version = Version,
+ compression_method = Compression, random = Random,
+ session_id = SessionId}, ConnectionStates) ->
+ NewConnectionStates =
+ hello_pending_connection_states(client, CipherSuite, Random,
+ Compression, ConnectionStates),
+ {Version, SessionId, NewConnectionStates};
+
+hello(#client_hello{client_version = ClientVersion, random = Random} = Hello,
+ {Port, #ssl_options{versions = Versions} = SslOpts,
+ Session0, Cache, CacheCb, ConnectionStates0}) ->
+ Version = select_version(ClientVersion, Versions),
+ case ssl_record:is_acceptable_version(Version) of
+ true ->
+ {Type, #session{cipher_suite = CipherSuite,
+ compression_method = Compression} = Session}
+ = select_session(Hello, Port, Session0, Version,
+ SslOpts, Cache, CacheCb),
+ case CipherSuite of
+ no_suite ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
+ _ ->
+ ConnectionStates =
+ hello_pending_connection_states(server,
+ CipherSuite,
+ Random,
+ Compression,
+ ConnectionStates0),
+ {Version, {Type, Session}, ConnectionStates}
+ end;
+ false ->
+ ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: certify(Certs, CertDbRef, MaxPathLen) ->
+%% {PeerCert, PublicKeyInfo} | #alert{}
+%%
+%% Certs = #certificate{}
+%% CertDbRef = reference()
+%% MaxPathLen = integer() | nolimit
+%%
+%% Description: Handles a certificate handshake message
+%%--------------------------------------------------------------------
+certify(#certificate{asn1_certificates = ASN1Certs}, CertDbRef,
+ MaxPathLen, Verify, VerifyFun) ->
+ [PeerCert | _] = ASN1Certs,
+ VerifyBool = verify_bool(Verify),
+
+ try
+ %% Allow missing root_cert and check that with VerifyFun
+ ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbRef, false) of
+ {TrustedErlCert, CertPath, VerifyErrors} ->
+ Result = public_key:pkix_path_validation(TrustedErlCert,
+ CertPath,
+ [{max_path_length,
+ MaxPathLen},
+ {verify, VerifyBool},
+ {acc_errors,
+ VerifyErrors}]),
+ case Result of
+ {error, Reason} ->
+ path_validation_alert(Reason, Verify);
+ {ok, {PublicKeyInfo,_, []}} ->
+ {PeerCert, PublicKeyInfo};
+ {ok, {PublicKeyInfo,_, AccErrors = [Error | _]}} ->
+ case VerifyFun(AccErrors) of
+ true ->
+ {PeerCert, PublicKeyInfo};
+ false ->
+ path_validation_alert(Error, Verify)
+ end
+ end
+ catch
+ throw:Alert ->
+ Alert
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: certificate(OwnCert, CertDbRef, Role) -> #certificate{}
+%%
+%% OwnCert = binary()
+%% CertDbRef = term() as returned by ssl_certificate_db:create()
+%%
+%% Description: Creates a certificate message.
+%%--------------------------------------------------------------------
+certificate(OwnCert, CertDbRef, client) ->
+ Chain =
+ case ssl_certificate:certificate_chain(OwnCert, CertDbRef) of
+ {ok, CertChain} ->
+ CertChain;
+ {error, _} ->
+ %% If no suitable certificate is available, the client
+ %% SHOULD send a certificate message containing no
+ %% certificates. (chapter 7.4.6. rfc 4346)
+ []
+ end,
+ #certificate{asn1_certificates = Chain};
+
+certificate(OwnCert, CertDbRef, server) ->
+ case ssl_certificate:certificate_chain(OwnCert, CertDbRef) of
+ {ok, Chain} ->
+ #certificate{asn1_certificates = Chain};
+ {error, _} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR)
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: client_certificate_verify(Cert, ConnectionStates) ->
+%% #certificate_verify{} | ignore
+%% Cert = #'OTPcertificate'{}
+%% ConnectionStates = #connection_states{}
+%%
+%% Description: Creates a certificate_verify message, called by the client.
+%%--------------------------------------------------------------------
+client_certificate_verify(undefined, _, _, _, _, _) ->
+ ignore;
+client_certificate_verify(_, _, _, _, undefined, _) ->
+ ignore;
+client_certificate_verify(OwnCert, MasterSecret, Version, Algorithm,
+ PrivateKey, {Hashes0, _}) ->
+ case public_key:pkix_is_fixed_dh_cert(OwnCert) of
+ true ->
+ ignore;
+ false ->
+ Hashes =
+ calc_certificate_verify(Version, MasterSecret,
+ Algorithm, Hashes0),
+ Signed = digitally_signed(Hashes, PrivateKey),
+ #certificate_verify{signature = Signed}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: certificate_verify(Signature, PublicKeyInfo) -> valid | #alert{}
+%%
+%% Signature = binary()
+%% PublicKeyInfo = {Algorithm, PublicKey, PublicKeyParams}
+%%
+%% Description: Checks that the certificate_verify message is valid.
+%%--------------------------------------------------------------------
+certificate_verify(Signature, {_, PublicKey, _}, Version,
+ MasterSecret, Algorithm, {_, Hashes0})
+ when Algorithm =:= rsa; Algorithm =:= dh_rsa; Algorithm =:= dhe_rsa ->
+ Hashes = calc_certificate_verify(Version, MasterSecret,
+ Algorithm, Hashes0),
+ case public_key:decrypt_public(Signature, PublicKey,
+ [{rsa_pad, rsa_pkcs1_padding}]) of
+ Hashes ->
+ valid;
+ _ ->
+ ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE)
+ end.
+%% TODO dsa clause
+
+%%--------------------------------------------------------------------
+%% Function: certificate_request(ConnectionStates, CertDbRef) ->
+%% #certificate_request{}
+%%
+%% Description: Creates a certificate_request message, called by the server.
+%%--------------------------------------------------------------------
+certificate_request(ConnectionStates, CertDbRef) ->
+ #connection_state{security_parameters =
+ #security_parameters{cipher_suite = CipherSuite}} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ Types = certificate_types(CipherSuite),
+ Authorities = certificate_authorities(CertDbRef),
+ #certificate_request{
+ certificate_types = Types,
+ certificate_authorities = Authorities
+ }.
+
+%%--------------------------------------------------------------------
+%% Function: key_exchange(Role, Secret, Params) ->
+%% #client_key_exchange{} | #server_key_exchange{}
+%%
+%% Secret -
+%% Params -
+%%
+%% Description: Creates a keyexchange message.
+%%--------------------------------------------------------------------
+key_exchange(client, {premaster_secret, Secret, {_, PublicKey, _}}) ->
+ EncPremasterSecret =
+ encrypted_premaster_secret(Secret, PublicKey),
+ #client_key_exchange{exchange_keys = EncPremasterSecret};
+key_exchange(client, fixed_diffie_hellman) ->
+ #client_key_exchange{exchange_keys =
+ #client_diffie_hellman_public{
+ dh_public = <<>>
+ }};
+key_exchange(client, {dh, PublicKey}) ->
+ Len = byte_size(PublicKey),
+ #client_key_exchange{
+ exchange_keys = #client_diffie_hellman_public{
+ dh_public = <<?UINT16(Len), PublicKey/binary>>}
+ };
+
+%% key_exchange(server, {{?'dhpublicnumber', _PublicKey,
+%% #'DomainParameters'{p = P, g = G, y = Y},
+%% SignAlgorithm, ClientRandom, ServerRandom}}) ->
+%% ServerDHParams = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y},
+%% PLen = byte_size(P),
+%% GLen = byte_size(G),
+%% YLen = byte_size(Y),
+%% Hash = server_key_exchange_hash(SignAlgorithm, <<ClientRandom/binary,
+%% ServerRandom/binary,
+%% ?UINT16(PLen), P/binary,
+%% ?UINT16(GLen), G/binary,
+%% ?UINT16(YLen), Y/binary>>),
+%% Signed = digitally_signed(Hash, PrivateKey),
+%% #server_key_exchange{
+%% params = ServerDHParams,
+%% signed_params = Signed
+%% };
+key_exchange(_, _) ->
+ %%TODO : Real imp
+ #server_key_exchange{}.
+
+%%--------------------------------------------------------------------
+%% Function: master_secret(Version, Session/PremasterSecret,
+%% ConnectionStates, Role) ->
+%% {MasterSecret, NewConnectionStates} | #alert{}
+%% Version = #protocol_version{}
+%% Session = #session{} (session contains master secret)
+%% PremasterSecret = binary()
+%% ConnectionStates = #connection_states{}
+%% Role = client | server
+%%
+%% Description: Sets or calculates the master secret and calculate keys,
+%% updating the pending connection states. The Mastersecret and the update
+%% connection states are returned or an alert if the calculation fails.
+%%-------------------------------------------------------------------
+master_secret(Version, #session{master_secret = Mastersecret},
+ ConnectionStates, Role) ->
+ ConnectionState =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ SecParams = ConnectionState#connection_state.security_parameters,
+ try master_secret(Version, Mastersecret, SecParams,
+ ConnectionStates, Role)
+ catch
+ exit:Reason ->
+ error_logger:error_report("Key calculation failed due to ~p",
+ [Reason]),
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
+ end;
+
+master_secret(Version, PremasterSecret, ConnectionStates, Role) ->
+ ConnectionState =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ SecParams = ConnectionState#connection_state.security_parameters,
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ try master_secret(Version,
+ calc_master_secret(Version,PremasterSecret,
+ ClientRandom, ServerRandom),
+ SecParams, ConnectionStates, Role)
+ catch
+ exit:Reason ->
+ error_logger:error_report("Master secret calculation failed"
+ " due to ~p", [Reason]),
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: finished(Version, Role, MacSecret, Hashes) -> #finished{}
+%%
+%% ConnectionStates = #connection_states{}
+%%
+%% Description: Creates a handshake finished message
+%%-------------------------------------------------------------------
+finished(Version, Role, MasterSecret, {Hashes, _}) -> % use the current hashes
+ #finished{verify_data =
+ calc_finished(Version, Role, MasterSecret, Hashes)}.
+
+%%--------------------------------------------------------------------
+%% Function: verify_connection(Finished, Role,
+%% MasterSecret, Hashes) -> verified | #alert{}
+%%
+%% Finished = #finished{}
+%% Role = client | server - the role of the process that sent the finished
+%% message.
+%% MasterSecret = binary()
+%% Hashes = binary() - {md5_hash, sha_hash}
+%%
+%%
+%% Description: Checks the ssl handshake finished message to verify
+%% the connection.
+%%-------------------------------------------------------------------
+verify_connection(Version, #finished{verify_data = Data},
+ Role, MasterSecret, {_, {MD5, SHA}}) ->
+ %% use the previous hashes
+ ?DBG_HEX(crypto:md5_final(MD5)),
+ ?DBG_HEX(crypto:sha_final(SHA)),
+ case calc_finished(Version, Role, MasterSecret, {MD5, SHA}) of
+ Data ->
+ verified;
+ _E ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
+ end.
+
+server_hello_done() ->
+ #server_hello_done{}.
+
+%%--------------------------------------------------------------------
+%% Function: encode_handshake(HandshakeRec) -> BinHandshake
+%% HandshakeRec = #client_hello | #server_hello{} | server_hello_done |
+%% #certificate{} | #client_key_exchange{} | #finished{} |
+%% #client_certify_request{}
+%%
+%% encode a handshake packet to binary
+%%--------------------------------------------------------------------
+encode_handshake(Package, Version, SigAlg) ->
+ {MsgType, Bin} = enc_hs(Package, Version, SigAlg),
+ Len = byte_size(Bin),
+ [MsgType, ?uint24(Len), Bin].
+
+%%--------------------------------------------------------------------
+%% Function: get_tls_handshake(Data, Buffer) -> Result
+%% Result = {[#handshake{}], [Raw], NewBuffer}
+%% Data = Buffer = NewBuffer = Raw = binary()
+%%
+%% Description: Given buffered and new data from ssl_record, collects
+%% and returns it as a list of #handshake, also returns leftover
+%% data.
+%%--------------------------------------------------------------------
+get_tls_handshake(Data, <<>>, KeyAlg, Version) ->
+ get_tls_handshake_aux(Data, KeyAlg, Version, []);
+get_tls_handshake(Data, Buffer, KeyAlg, Version) ->
+ get_tls_handshake_aux(list_to_binary([Buffer, Data]),
+ KeyAlg, Version, []).
+
+get_tls_handshake_aux(<<?BYTE(Type), ?UINT24(Length), Body:Length/binary,Rest/binary>>,
+ KeyAlg, Version, Acc) ->
+ Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>,
+ H = dec_hs(Type, Body, KeyAlg, Version),
+ get_tls_handshake_aux(Rest, KeyAlg, Version, [{H,Raw} | Acc]);
+get_tls_handshake_aux(Data, _KeyAlg, _Version, Acc) ->
+ {lists:reverse(Acc), Data}.
+
+%%--------------------------------------------------------------------
+%% Function: sig_alg(atom()) -> integer()
+%%
+%% Description: Convert from key exchange as atom to signature
+%% algorithm as a ?SIGNATURE_... constant
+%%--------------------------------------------------------------------
+
+sig_alg(dh_anon) ->
+ ?SIGNATURE_ANONYMOUS;
+sig_alg(Alg) when Alg == dhe_rsa; Alg == rsa; Alg == dh_rsa ->
+ ?SIGNATURE_RSA;
+sig_alg(Alg) when Alg == dh_dss; Alg == dhe_dss ->
+ ?SIGNATURE_DSA;
+sig_alg(_) ->
+ ?NULL.
+
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+verify_bool(verify_peer) ->
+ true;
+verify_bool(verify_none) ->
+ false.
+
+path_validation_alert({bad_cert, cert_expired}, _) ->
+ ?ALERT_REC(?FATAL, ?CERTIFICATE_EXPIRED);
+path_validation_alert({bad_cert, invalid_issuer}, _) ->
+ ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
+path_validation_alert({bad_cert, invalid_signature} , _) ->
+ ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
+path_validation_alert({bad_cert, name_not_permitted}, _) ->
+ ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
+path_validation_alert({bad_cert, unknown_critical_extension}, _) ->
+ ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE);
+path_validation_alert({bad_cert, cert_revoked}, _) ->
+ ?ALERT_REC(?FATAL, ?CERTIFICATE_REVOKED);
+path_validation_alert(_, _) ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE).
+
+select_session(Hello, Port, Session, Version,
+ #ssl_options{ciphers = UserSuites} = SslOpts, Cache, CacheCb) ->
+ SuggestedSessionId = Hello#client_hello.session_id,
+ SessionId = ssl_manager:server_session_id(Port, SuggestedSessionId,
+ SslOpts),
+
+ Suites = case UserSuites of
+ [] ->
+ ssl_cipher:suites(Version);
+ _ ->
+ UserSuites
+ end,
+
+ case ssl_session:is_new(SuggestedSessionId, SessionId) of
+ true ->
+ CipherSuite =
+ select_cipher_suite(Hello#client_hello.cipher_suites, Suites),
+ Compressions = Hello#client_hello.compression_methods,
+ Compression = select_compression(Compressions),
+ {new, Session#session{session_id = SessionId,
+ cipher_suite = CipherSuite,
+ compression_method = Compression}};
+ false ->
+ {resumed, CacheCb:lookup(Cache, {Port, SessionId})}
+ end.
+
+%% Update pending connection states with parameters exchanged via
+%% hello messages
+%% NOTE : Role is the role of the receiver of the hello message
+%% currently being processed.
+hello_pending_connection_states(Role, CipherSuite, Random, Compression,
+ ConnectionStates) ->
+ ReadState =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ WriteState =
+ ssl_record:pending_connection_state(ConnectionStates, write),
+
+ NewReadSecParams =
+ hello_security_parameters(Role, ReadState, CipherSuite,
+ Random, Compression),
+
+ NewWriteSecParams =
+ hello_security_parameters(Role, WriteState, CipherSuite,
+ Random, Compression),
+
+ ssl_record:update_security_params(NewReadSecParams,
+ NewWriteSecParams,
+ ConnectionStates).
+
+hello_security_parameters(client, ConnectionState, CipherSuite, Random,
+ Compression) ->
+ SecParams = ConnectionState#connection_state.security_parameters,
+ NewSecParams = ssl_cipher:security_parameters(CipherSuite, SecParams),
+ NewSecParams#security_parameters{
+ server_random = Random,
+ compression_algorithm = Compression
+ };
+
+hello_security_parameters(server, ConnectionState, CipherSuite, Random,
+ Compression) ->
+ SecParams = ConnectionState#connection_state.security_parameters,
+ NewSecParams = ssl_cipher:security_parameters(CipherSuite, SecParams),
+ NewSecParams#security_parameters{
+ client_random = Random,
+ compression_algorithm = Compression
+ }.
+
+select_version(ClientVersion, Versions) ->
+ Fun = fun(Version) ->
+ ssl_record:protocol_version(Version)
+ end,
+ ServerVersion = ssl_record:highest_protocol_version(lists:map(Fun,
+ Versions)),
+ ssl_record:lowest_protocol_version(ClientVersion, ServerVersion).
+
+select_cipher_suite([], _) ->
+ no_suite;
+select_cipher_suite([Suite | ClientSuites], SupportedSuites) ->
+ case is_member(Suite, SupportedSuites) of
+ true ->
+ Suite;
+ false ->
+ select_cipher_suite(ClientSuites, SupportedSuites)
+ end.
+
+is_member(Suite, SupportedSuites) ->
+ lists:member(Suite, SupportedSuites).
+
+select_compression(_CompressionMetodes) ->
+ ?NULL.
+
+master_secret(Version, MasterSecret, #security_parameters{
+ client_random = ClientRandom,
+ server_random = ServerRandom,
+ hash_size = HashSize,
+ key_material_length = KML,
+ expanded_key_material_length = EKML,
+ iv_size = IVS,
+ exportable = Exportable},
+ ConnectionStates, Role) ->
+ {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
+ ServerWriteKey, ClientIV, ServerIV} =
+ setup_keys(Version, Exportable, MasterSecret, ServerRandom,
+ ClientRandom, HashSize, KML, EKML, IVS),
+ ?DBG_HEX(ClientWriteKey),
+ ?DBG_HEX(ClientIV),
+ ConnStates1 = ssl_record:set_master_secret(MasterSecret, ConnectionStates),
+ ConnStates2 =
+ ssl_record:set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret,
+ Role, ConnStates1),
+
+ ClientCipherState = #cipher_state{iv = ClientIV, key = ClientWriteKey},
+ ServerCipherState = #cipher_state{iv = ServerIV, key = ServerWriteKey},
+ {MasterSecret,
+ ssl_record:set_pending_cipher_state(ConnStates2, ClientCipherState,
+ ServerCipherState, Role)}.
+
+
+dec_hs(?HELLO_REQUEST, <<>>, _, _) ->
+ #hello_request{};
+
+%% Client hello v2.
+%% The server must be able to receive such messages, from clients that
+%% are willing to use ssl v3 or higher, but have ssl v2 compatibility.
+dec_hs(?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor),
+ ?UINT16(CSLength), ?UINT16(0),
+ ?UINT16(CDLength),
+ CipherSuites:CSLength/binary,
+ ChallengeData:CDLength/binary>>,
+ _, _) ->
+ ?DBG_HEX(CipherSuites),
+ ?DBG_HEX(CipherSuites),
+ #client_hello{client_version = {Major, Minor},
+ random = ssl_ssl2:client_random(ChallengeData, CDLength),
+ session_id = 0,
+ cipher_suites = from_3bytes(CipherSuites),
+ compression_methods = [?NULL]
+ };
+dec_hs(?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+ ?BYTE(SID_length), Session_ID:SID_length/binary,
+ ?UINT16(Cs_length), CipherSuites:Cs_length/binary,
+ ?BYTE(Cm_length), Comp_methods:Cm_length/binary,
+ _FutureCompatData/binary>>,
+ _, _) ->
+ #client_hello{
+ client_version = {Major,Minor},
+ random = Random,
+ session_id = Session_ID,
+ cipher_suites = from_2bytes(CipherSuites),
+ compression_methods = Comp_methods
+ };
+dec_hs(?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+ ?BYTE(SID_length), Session_ID:SID_length/binary,
+ Cipher_suite:2/binary, ?BYTE(Comp_method)>>, _, _) ->
+ #server_hello{
+ server_version = {Major,Minor},
+ random = Random,
+ session_id = Session_ID,
+ cipher_suite = Cipher_suite,
+ compression_method = Comp_method
+ };
+dec_hs(?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>, _, _) ->
+ #certificate{asn1_certificates = certs_to_list(ASN1Certs)};
+dec_hs(?SERVER_KEY_EXCHANGE, <<?UINT16(ModLen), Mod:ModLen/binary,
+ ?UINT16(ExpLen), Exp:ExpLen/binary,
+ Sig/binary>>,
+ ?KEY_EXCHANGE_RSA, _) ->
+ #server_key_exchange{params = #server_rsa_params{rsa_modulus = Mod,
+ rsa_exponent = Exp},
+ signed_params = Sig};
+dec_hs(?SERVER_KEY_EXCHANGE, <<?UINT16(PLen), P:PLen/binary,
+ ?UINT16(GLen), G:GLen/binary,
+ ?UINT16(YLen), Y:YLen/binary,
+ Sig/binary>>,
+ ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) ->
+ #server_key_exchange{params = #server_dh_params{dh_p = P,dh_g = G, dh_y = Y},
+ signed_params = Sig};
+dec_hs(?CERTIFICATE_REQUEST,
+ <<?BYTE(CertTypesLen), CertTypes:CertTypesLen/binary,
+ ?UINT16(CertAuthsLen), CertAuths:CertAuthsLen/binary>>, _, _) ->
+ %% TODO: maybe we should chop up CertAuths into a list?
+ #certificate_request{certificate_types = CertTypes,
+ certificate_authorities = CertAuths};
+dec_hs(?SERVER_HELLO_DONE, <<>>, _, _) ->
+ #server_hello_done{};
+dec_hs(?CERTIFICATE_VERIFY,<<?UINT16(_), Signature/binary>>, _, _)->
+ #certificate_verify{signature = Signature};
+dec_hs(?CLIENT_KEY_EXCHANGE, PKEPMS, rsa, {3, 0}) ->
+ PreSecret = #encrypted_premaster_secret{premaster_secret = PKEPMS},
+ #client_key_exchange{exchange_keys = PreSecret};
+dec_hs(?CLIENT_KEY_EXCHANGE, <<?UINT16(_), PKEPMS/binary>>, rsa, _) ->
+ PreSecret = #encrypted_premaster_secret{premaster_secret = PKEPMS},
+ #client_key_exchange{exchange_keys = PreSecret};
+dec_hs(?CLIENT_KEY_EXCHANGE, <<>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) ->
+ %% TODO: Should check whether the cert already contains a suitable DH-key (7.4.7.2)
+ throw(?ALERT_REC(?FATAL, implicit_public_value_encoding));
+dec_hs(?CLIENT_KEY_EXCHANGE, <<?UINT16(DH_YCLen), DH_YC:DH_YCLen/binary>>,
+ ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) ->
+ #client_diffie_hellman_public{dh_public = DH_YC};
+dec_hs(?FINISHED, VerifyData, _, _) ->
+ #finished{verify_data = VerifyData};
+dec_hs(_, _, _, _) ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)).
+
+encrypted_premaster_secret(Secret, RSAPublicKey) ->
+ try
+ PreMasterSecret = public_key:encrypt_public(Secret, RSAPublicKey,
+ [{rsa_pad,
+ rsa_pkcs1_padding}]),
+ #encrypted_premaster_secret{premaster_secret = PreMasterSecret}
+ catch
+ _:_->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE))
+ end.
+
+decrypt_premaster_secret(Secret, RSAPrivateKey) ->
+ try public_key:decrypt_private(Secret, RSAPrivateKey,
+ [{rsa_pad, rsa_pkcs1_padding}])
+ catch
+ _:_ ->
+ throw(?ALERT_REC(?FATAL, ?DECRYPTION_FAILED))
+ end.
+
+%% encode/decode stream of certificate data to/from list of certificate data
+certs_to_list(ASN1Certs) ->
+ certs_to_list(ASN1Certs, []).
+
+certs_to_list(<<?UINT24(CertLen), Cert:CertLen/binary, Rest/binary>>, Acc) ->
+ certs_to_list(Rest, [Cert | Acc]);
+certs_to_list(<<>>, Acc) ->
+ lists:reverse(Acc, []).
+
+certs_from_list(ACList) ->
+ list_to_binary([begin
+ CertLen = byte_size(Cert),
+ <<?UINT24(CertLen), Cert/binary>>
+ end || Cert <- ACList]).
+
+enc_hs(#hello_request{}, _Version, _) ->
+ {?HELLO_REQUEST, <<>>};
+enc_hs(#client_hello{
+ client_version = {Major, Minor},
+ random = Random,
+ session_id = SessionID,
+ cipher_suites = CipherSuites,
+ compression_methods = CompMethods}, _Version, _) ->
+ SIDLength = byte_size(SessionID),
+ BinCompMethods = list_to_binary(CompMethods),
+ CmLength = byte_size(BinCompMethods),
+ BinCipherSuites = list_to_binary(CipherSuites),
+ CsLength = byte_size(BinCipherSuites),
+ {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+ ?BYTE(SIDLength), SessionID/binary,
+ ?UINT16(CsLength), BinCipherSuites/binary,
+ ?BYTE(CmLength), BinCompMethods/binary>>};
+enc_hs(#server_hello{
+ server_version = {Major, Minor},
+ random = Random,
+ session_id = Session_ID,
+ cipher_suite = Cipher_suite,
+ compression_method = Comp_method}, _Version, _) ->
+ SID_length = byte_size(Session_ID),
+ {?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+ ?BYTE(SID_length), Session_ID/binary,
+ Cipher_suite/binary, ?BYTE(Comp_method)>>};
+enc_hs(#certificate{asn1_certificates = ASN1CertList}, _Version, _) ->
+ ASN1Certs = certs_from_list(ASN1CertList),
+ ACLen = erlang:iolist_size(ASN1Certs),
+ {?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>};
+enc_hs(#server_key_exchange{params = #server_rsa_params{rsa_modulus = Mod,
+ rsa_exponent = Exp},
+ signed_params = SignedParams}, _Version, _) ->
+ ModLen = byte_size(Mod),
+ ExpLen = byte_size(Exp),
+ {?SERVER_KEY_EXCHANGE, <<?UINT16(ModLen), Mod/binary,
+ ?UINT16(ExpLen), Exp/binary,
+ SignedParams/binary>>
+ };
+enc_hs(#server_key_exchange{params = #server_dh_params{
+ dh_p = P, dh_g = G, dh_y = Y},
+ signed_params = SignedParams}, _Version, _) ->
+ PLen = byte_size(P),
+ GLen = byte_size(G),
+ YLen = byte_size(Y),
+ {?SERVER_KEY_EXCHANGE, <<?UINT16(PLen), P:PLen/binary,
+ ?UINT16(GLen), G:GLen/binary,
+ ?UINT16(YLen), Y:YLen/binary,
+ SignedParams/binary>>
+ };
+enc_hs(#certificate_request{certificate_types = CertTypes,
+ certificate_authorities = CertAuths},
+ _Version, _) ->
+ CertTypesLen = byte_size(CertTypes),
+ CertAuthsLen = byte_size(CertAuths),
+ {?CERTIFICATE_REQUEST,
+ <<?BYTE(CertTypesLen), CertTypes/binary,
+ ?UINT16(CertAuthsLen), CertAuths/binary>>
+ };
+enc_hs(#server_hello_done{}, _Version, _) ->
+ {?SERVER_HELLO_DONE, <<>>};
+enc_hs(#client_key_exchange{exchange_keys = ExchangeKeys}, Version, _) ->
+ {?CLIENT_KEY_EXCHANGE, enc_cke(ExchangeKeys, Version)};
+enc_hs(#certificate_verify{signature = BinSig}, _, _) ->
+ EncSig = enc_bin_sig(BinSig),
+ {?CERTIFICATE_VERIFY, EncSig};
+enc_hs(#finished{verify_data = VerifyData}, _Version, _) ->
+ {?FINISHED, VerifyData}.
+
+enc_cke(#encrypted_premaster_secret{premaster_secret = PKEPMS},{3, 0}) ->
+ PKEPMS;
+enc_cke(#encrypted_premaster_secret{premaster_secret = PKEPMS}, _) ->
+ PKEPMSLen = byte_size(PKEPMS),
+ <<?UINT16(PKEPMSLen), PKEPMS/binary>>;
+enc_cke(#client_diffie_hellman_public{dh_public = DHPublic}, _) ->
+ Len = byte_size(DHPublic),
+ <<?UINT16(Len), DHPublic/binary>>.
+
+enc_bin_sig(BinSig) ->
+ Size = byte_size(BinSig),
+ <<?UINT16(Size), BinSig/binary>>.
+
+init_hashes() ->
+ T = {crypto:md5_init(), crypto:sha_init()},
+ {T, T}.
+
+update_hashes(Hashes, % special-case SSL2 client hello
+ <<?CLIENT_HELLO, ?UINT24(_), ?BYTE(Major), ?BYTE(Minor),
+ ?UINT16(CSLength), ?UINT16(0),
+ ?UINT16(CDLength),
+ CipherSuites:CSLength/binary,
+ ChallengeData:CDLength/binary>>) ->
+ update_hashes(Hashes,
+ <<?CLIENT_HELLO, ?BYTE(Major), ?BYTE(Minor),
+ ?UINT16(CSLength), ?UINT16(0),
+ ?UINT16(CDLength),
+ CipherSuites:CSLength/binary,
+ ChallengeData:CDLength/binary>>);
+update_hashes({{MD50, SHA0}, _Prev}, Data) ->
+ ?DBG_HEX(Data),
+ {MD51, SHA1} = {crypto:md5_update(MD50, Data),
+ crypto:sha_update(SHA0, Data)},
+ ?DBG_HEX(crypto:md5_final(MD51)),
+ ?DBG_HEX(crypto:sha_final(SHA1)),
+ {{MD51, SHA1}, {MD50, SHA0}}.
+
+from_3bytes(Bin3) ->
+ from_3bytes(Bin3, []).
+
+from_3bytes(<<>>, Acc) ->
+ lists:reverse(Acc);
+from_3bytes(<<?UINT24(N), Rest/binary>>, Acc) ->
+ from_3bytes(Rest, [?uint16(N) | Acc]).
+
+from_2bytes(Bin2) ->
+ from_2bytes(Bin2, []).
+
+from_2bytes(<<>>, Acc) ->
+ lists:reverse(Acc);
+from_2bytes(<<?UINT16(N), Rest/binary>>, Acc) ->
+ from_2bytes(Rest, [?uint16(N) | Acc]).
+
+certificate_types({KeyExchange, _, _, _})
+ when KeyExchange == rsa;
+ KeyExchange == dh_dss;
+ KeyExchange == dh_rsa;
+ KeyExchange == dhe_dss;
+ KeyExchange == dhe_rsa ->
+ <<?BYTE(?RSA_SIGN), ?BYTE(?DSS_SIGN)>>;
+
+certificate_types(_) ->
+ %%TODO: Is this a good default,
+ %% is there a case where we like to request
+ %% a RSA_FIXED_DH or DSS_FIXED_DH
+ <<?BYTE(?RSA_SIGN)>>.
+
+certificate_authorities(_) ->
+ %%TODO Make list of know CA:s
+ <<>>.
+
+digitally_signed(Hashes, #'RSAPrivateKey'{} = Key) ->
+ public_key:encrypt_private(Hashes, Key,
+ [{rsa_pad, rsa_pkcs1_padding}]);
+digitally_signed(Hashes, #'DSAPrivateKey'{} = Key) ->
+ public_key:sign(Hashes, Key).
+
+
+calc_master_secret({3,0}, PremasterSecret, ClientRandom, ServerRandom) ->
+ ssl_ssl3:master_secret(PremasterSecret, ClientRandom, ServerRandom);
+
+calc_master_secret({3,N},PremasterSecret, ClientRandom, ServerRandom)
+ when N == 1; N == 2 ->
+ ssl_tls1:master_secret(PremasterSecret, ClientRandom, ServerRandom).
+
+setup_keys({3,0}, Exportable, MasterSecret,
+ ServerRandom, ClientRandom, HashSize, KML, EKML, IVS) ->
+ ssl_ssl3:setup_keys(Exportable, MasterSecret, ServerRandom,
+ ClientRandom, HashSize, KML, EKML, IVS);
+
+setup_keys({3,1}, _Exportable, MasterSecret,
+ ServerRandom, ClientRandom, HashSize, KML, _EKML, IVS) ->
+ ssl_tls1:setup_keys(MasterSecret, ServerRandom, ClientRandom, HashSize,
+ KML, IVS);
+
+setup_keys({3,2}, _Exportable, MasterSecret,
+ ServerRandom, ClientRandom, HashSize, KML, _EKML, _IVS) ->
+ ssl_tls1:setup_keys(MasterSecret, ServerRandom,
+ ClientRandom, HashSize, KML).
+
+calc_finished({3, 0}, Role, MasterSecret, Hashes) ->
+ ssl_ssl3:finished(Role, MasterSecret, Hashes);
+calc_finished({3, N}, Role, MasterSecret, Hashes)
+ when N == 1; N == 2 ->
+ ssl_tls1:finished(Role, MasterSecret, Hashes).
+
+calc_certificate_verify({3, 0}, MasterSecret, Algorithm, Hashes) ->
+ ssl_ssl3:certificate_verify(Algorithm, MasterSecret, Hashes);
+calc_certificate_verify({3, N}, _, Algorithm, Hashes)
+ when N == 1; N == 2 ->
+ ssl_tls1:certificate_verify(Algorithm, Hashes).
+
+%% server_key_exchange_hash(Algorithm, Value) when Algorithm == rsa;
+%% Algorithm == dh_rsa;
+%% Algorithm == dhe_rsa ->
+%% MD5 = crypto:md5_final(Value),
+%% SHA = crypto:sha_final(Value),
+%% <<MD5/binary, SHA/binary>>;
+
+%% server_key_exchange_hash(Algorithm, Value) when Algorithm == dh_dss;
+%% Algorithm == dhe_dss ->
+%% crypto:sha_final(Value).
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
new file mode 100644
index 0000000000..b2bdfa0934
--- /dev/null
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -0,0 +1,200 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Record and constant defenitions for the SSL-handshake protocol
+%% see RFC 4346
+%%----------------------------------------------------------------------
+
+-ifndef(ssl_handshake).
+-define(ssl_handshake, true).
+
+-record(session, {
+ session_id,
+ peer_certificate,
+ compression_method,
+ cipher_suite,
+ master_secret,
+ is_resumable,
+ time_stamp
+ }).
+
+-define(NUM_OF_SESSION_ID_BYTES, 32). % TSL 1.1 & SSL 3
+-define(NUM_OF_PREMASTERSECRET_BYTES, 48).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Handsake protocol - RFC 4346 section 7.4
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% enum {
+%% hello_request(0), client_hello(1), server_hello(2),
+%% certificate(11), server_key_exchange (12),
+%% certificate_request(13), server_hello_done(14),
+%% certificate_verify(15), client_key_exchange(16),
+%% finished(20), (255)
+%% } HandshakeType;
+
+-define(HELLO_REQUEST, 0).
+-define(CLIENT_HELLO, 1).
+-define(CLIENT_HELLO_V2, 3).
+-define(SERVER_HELLO, 2).
+-define(CERTIFICATE, 11).
+-define(SERVER_KEY_EXCHANGE, 12).
+-define(CERTIFICATE_REQUEST, 13).
+-define(SERVER_HELLO_DONE, 14).
+-define(CERTIFICATE_VERIFY, 15).
+-define(CLIENT_KEY_EXCHANGE, 16).
+-define(FINISHED, 20).
+
+-record(random, {
+ gmt_unix_time, % uint32
+ random_bytes % opaque random_bytes[28]
+ }).
+
+%% enum { null(0), (255) } CompressionMethod;
+% -define(NULL, 0). %% Already defined by ssl_internal.hrl
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Hello messages - RFC 4346 section 7.4.2
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-record(client_hello, {
+ client_version,
+ random,
+ session_id, % opaque SessionID<0..32>
+ cipher_suites, % cipher_suites<2..2^16-1>
+ compression_methods % compression_methods<1..2^8-1>
+ }).
+
+-record(server_hello, {
+ server_version,
+ random,
+ session_id, % opaque SessionID<0..32>
+ cipher_suite, % cipher_suites
+ compression_method % compression_method
+ }).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Server authentication and key exchange messages - RFC 4346 section 7.4.3
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% opaque ASN.1Cert<2^24-1>;
+
+-record(certificate, {
+ asn1_certificates %% certificate_list<1..2^24-1>
+ }).
+
+%% enum { rsa, diffie_hellman } KeyExchangeAlgorithm;
+
+-define(KEY_EXCHANGE_RSA, 0).
+-define(KEY_EXCHANGE_DIFFIE_HELLMAN, 1).
+
+-record(server_rsa_params, {
+ rsa_modulus, %% opaque RSA_modulus<1..2^16-1>
+ rsa_exponent %% opaque RSA_exponent<1..2^16-1>
+ }).
+
+-record(server_dh_params, {
+ dh_p, %% opaque DH_p<1..2^16-1>
+ dh_g, %% opaque DH_g<1..2^16-1>
+ dh_y %% opaque DH_Ys<1..2^16-1>
+ }).
+
+-record(server_key_exchange, {
+ params, %% #server_rsa_params{} | #server_dh_params{}
+ signed_params %% #signature{}
+ }).
+
+%% enum { anonymous, rsa, dsa } SignatureAlgorithm;
+
+-define(SIGNATURE_ANONYMOUS, 0).
+-define(SIGNATURE_RSA, 1).
+-define(SIGNATURE_DSA, 2).
+
+-record(hello_request, {}).
+-record(server_hello_done, {}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Certificate request - RFC 4346 section 7.4.4
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% enum {
+%% rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4),
+%% (255)
+%% } ClientCertificateType;
+
+-define(RSA_SIGN, 1).
+-define(DSS_SIGN, 2).
+-define(RSA_FIXED_DH, 3).
+-define(DSS_FIXED_DH, 4).
+
+% opaque DistinguishedName<1..2^16-1>;
+
+-record(certificate_request, {
+ certificate_types, %ClientCertificateType <1..2^8-1>
+ certificate_authorities %DistinguishedName <0..2^16-1>
+ }).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Client authentication and key exchange messages - RFC 4346 section 7.4.7
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-record(client_key_exchange, {
+ exchange_keys %% #encrypted_premaster_secret{} (rsa ) |
+ %% DiffieHellmanClientPublicValue
+ }).
+
+-record(pre_master_secret, {
+ client_version, % ProtocolVersion client_version
+ random % opaque random[46];
+ }).
+
+-record(encrypted_premaster_secret, {
+ premaster_secret
+ }).
+
+%% enum { implicit, explicit } PublicValueEncoding;
+
+-define(IMPLICIT, 0).
+-define(EXPLICIT, 1).
+
+-record(client_diffie_hellman_public, {
+ dh_public
+ }).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Certificate verify - RFC 4346 section 7.4.8
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-record(certificate_verify, {
+ signature % binary()
+ }).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Handshake finalization message RFC 4346 section 7.4.9
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-record(finished, {
+ verify_data %opaque verify_data[12]
+ }).
+
+-endif. % -ifdef(ssl_handshake).
+
+
+
diff --git a/lib/ssl/src/ssl_int.hrl b/lib/ssl/src/ssl_int.hrl
new file mode 100644
index 0000000000..3686deffce
--- /dev/null
+++ b/lib/ssl/src/ssl_int.hrl
@@ -0,0 +1,99 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+%% op codes commands are in capital and reply codes in lower case
+
+-define(CONNECT, 1).
+-define(CONNECT_WAIT, 2).
+-define(CONNECT_REP, 3).
+-define(CONNECT_ERR, 4).
+
+-define(TERMINATE, 5).
+-define(CLOSE, 6).
+
+-define(LISTEN, 7).
+-define(LISTEN_REP, 8).
+-define(LISTEN_ERR, 9).
+
+-define(TRANSPORT_ACCEPT, 10).
+-define(NOACCEPT, 11).
+-define(TRANSPORT_ACCEPT_REP, 12).
+-define(TRANSPORT_ACCEPT_ERR, 13).
+
+-define(FROMNET_CLOSE, 14).
+
+-define(CONNECT_SYNC_ERR, 15).
+-define(LISTEN_SYNC_ERR, 16).
+
+-define(PROXY_PORT, 23).
+-define(PROXY_JOIN, 24).
+-define(PROXY_JOIN_REP, 25).
+-define(PROXY_JOIN_ERR, 26).
+
+-define(SET_SOCK_OPT, 27).
+-define(IOCTL_OK, 28).
+-define(IOCTL_ERR, 29).
+
+-define(GETPEERNAME, 30).
+-define(GETPEERNAME_REP, 31).
+-define(GETPEERNAME_ERR, 32).
+
+-define(GETSOCKNAME, 33).
+-define(GETSOCKNAME_REP, 34).
+-define(GETSOCKNAME_ERR, 35).
+
+-define(GETPEERCERT, 36).
+-define(GETPEERCERT_REP, 37).
+-define(GETPEERCERT_ERR, 38).
+
+-define(GETVERSION, 39).
+-define(GETVERSION_REP, 40).
+
+-define(SET_SEED, 41).
+
+-define(GETCONNINFO, 42).
+-define(GETCONNINFO_REP, 43).
+-define(GETCONNINFO_ERR, 44).
+
+-define(SSL_ACCEPT, 45).
+-define(SSL_ACCEPT_REP, 46).
+-define(SSL_ACCEPT_ERR, 47).
+
+-define(DUMP_CMD, 48).
+-define(DEBUG_CMD, 49).
+-define(DEBUGMSG_CMD, 50).
+
+%% --------------
+
+-define(SSLv2, 1).
+-define(SSLv3, 2).
+-define(TLSv1, 4).
+
+
+%% Set socket options codes 'SET_SOCK_OPT'
+-define(SET_TCP_NODELAY, 1).
+
+-define(DEF_BACKLOG, 128).
+
+-define(DEF_TIMEOUT, 10000).
+
+-record(sslsocket, { fd = nil, pid = nil}).
+
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
new file mode 100644
index 0000000000..23a5c93452
--- /dev/null
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -0,0 +1,91 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+
+-ifndef(ssl_internal).
+-define(ssl_internal, true).
+
+%% basic binary constructors
+-define(BOOLEAN(X), X:8/unsigned-big-integer).
+-define(BYTE(X), X:8/unsigned-big-integer).
+-define(UINT16(X), X:16/unsigned-big-integer).
+-define(UINT24(X), X:24/unsigned-big-integer).
+-define(UINT32(X), X:32/unsigned-big-integer).
+-define(UINT64(X), X:64/unsigned-big-integer).
+-define(STRING(X), ?UINT32((size(X))), (X)/binary).
+
+-define(byte(X), << ?BYTE(X) >> ).
+-define(uint16(X), << ?UINT16(X) >> ).
+-define(uint24(X), << ?UINT24(X) >> ).
+-define(uint32(X), << ?UINT32(X) >> ).
+-define(uint64(X), << ?UINT64(X) >> ).
+
+-define(CDR_MAGIC, "GIOP").
+-define(CDR_HDR_SIZE, 12).
+
+-define(DEFAULT_TIMEOUT, 5000).
+
+%% Common enumerate values in for SSL-protocols
+-define(NULL, 0).
+-define(TRUE, 0).
+-define(FALSE, 1).
+
+-define(DEFAULT_SUPPORTED_VERSIONS, [tlsv1, sslv3]). % TODO: This is temporary
+%-define(DEFAULT_SUPPORTED_VERSIONS, ['tlsv1.1', tlsv1, sslv3]).
+
+-record(ssl_options, {
+ versions, % 'tlsv1.1' | tlsv1 | sslv3
+ verify, % verify_none | verify_peer
+ verify_fun, % fun(CertVerifyErrors) -> boolean()
+ fail_if_no_peer_cert, % boolean()
+ verify_client_once, % boolean()
+ depth, % integer()
+ certfile, % file()
+ keyfile, % file()
+ key, %
+ password, %
+ cacertfile, % file()
+ ciphers, %
+ %% Local policy for the server if it want's to reuse the session
+ %% or not. Defaluts to allways returning true.
+ %% fun(SessionId, PeerCert, Compression, CipherSuite) -> boolean()
+ reuse_session,
+ %% If false sessions will never be reused, if true they
+ %% will be reused if possible.
+ reuse_sessions, % boolean()
+ debug %
+ }).
+
+-record(socket_options,
+ {
+ mode = list,
+ packet = 0,
+ packet_size = 0,
+ header = 0,
+ active = true
+ }).
+
+-endif. % -ifdef(ssl_internal).
+
+
+
+
+
diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl
new file mode 100644
index 0000000000..6b83c2ea46
--- /dev/null
+++ b/lib/ssl/src/ssl_manager.erl
@@ -0,0 +1,340 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%----------------------------------------------------------------------
+%% Purpose: Manages ssl sessions and trusted certifacates
+%%----------------------------------------------------------------------
+
+-module(ssl_manager).
+-behaviour(gen_server).
+
+%% Internal application API
+-export([start_link/0, start_link/1,
+ connection_init/2, cache_pem_file/1,
+ lookup_trusted_cert/3, client_session_id/3, server_session_id/3,
+ register_session/2, register_session/3, invalidate_session/2,
+ invalidate_session/3]).
+
+% Spawn export
+-export([init_session_validator/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-include("ssl_handshake.hrl").
+-include("ssl_internal.hrl").
+
+-record(state, {
+ session_cache,
+ session_cache_cb,
+ session_lifetime,
+ certificate_db,
+ session_validation_timer
+ }).
+
+-define('24H_in_msec', 8640000).
+-define('24H_in_sec', 8640).
+-define(SESSION_VALIDATION_INTERVAL, 60000).
+-define(CERTIFICATE_CACHE_CLEANUP, 30000).
+
+%%====================================================================
+%% API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
+%% Description: Starts the server
+%%--------------------------------------------------------------------
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+start_link(Opts) ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [Opts], []).
+
+%%--------------------------------------------------------------------
+%% Function:
+%% Description:
+%%--------------------------------------------------------------------
+connection_init(TrustedcertsFile, Role) ->
+ call({connection_init, TrustedcertsFile, Role}).
+
+cache_pem_file(File) ->
+ case ets:lookup(ssl_file_to_ref,File) of
+ [{_,_,Content}] ->
+ {ok, Content};
+ [] ->
+ {ok, Db} = call({cache_pem, File}),
+ [{_,_,Content}] = ets:lookup(Db,File),
+ {ok, Content}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function:
+%% Description:
+%%--------------------------------------------------------------------
+lookup_trusted_cert(SerialNumber, Issuer, Ref) ->
+ ssl_certificate_db:lookup_trusted_cert(Ref, SerialNumber, Issuer).
+
+%%--------------------------------------------------------------------
+%% Function:
+%% Description:
+%%--------------------------------------------------------------------
+client_session_id(Host, Port, SslOpts) ->
+ call({client_session_id, Host, Port, SslOpts}).
+
+%%--------------------------------------------------------------------
+%% Function:
+%% Description:
+%%--------------------------------------------------------------------
+server_session_id(Port, SuggestedSessionId, SslOpts) ->
+ call({server_session_id, Port, SuggestedSessionId, SslOpts}).
+
+%%--------------------------------------------------------------------
+%% Function:
+%% Description:
+%%--------------------------------------------------------------------
+register_session(Host, Port, Session) ->
+ cast({register_session, Host, Port, Session}).
+
+register_session(Port, Session) ->
+ cast({register_session, Port, Session}).
+
+%%--------------------------------------------------------------------
+%% Function:
+%% Description:
+%%--------------------------------------------------------------------
+invalidate_session(Host, Port, Session) ->
+ cast({invalidate_session, Host, Port, Session}).
+
+invalidate_session(Port, Session) ->
+ cast({invalidate_session, Port, Session}).
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% Description: Initiates the server
+%%--------------------------------------------------------------------
+init(Opts) ->
+ process_flag(trap_exit, true),
+ CacheCb = proplists:get_value(session_cache, Opts, ssl_session_cache),
+ SessionLifeTime =
+ proplists:get_value(session_lifetime, Opts, ?'24H_in_sec'),
+ CertDb = ssl_certificate_db:create(),
+ SessionCache = CacheCb:init(),
+ Timer = erlang:send_after(SessionLifeTime * 1000,
+ self(), validate_sessions),
+ {ok, #state{certificate_db = CertDb,
+ session_cache = SessionCache,
+ session_cache_cb = CacheCb,
+ session_lifetime = SessionLifeTime ,
+ session_validation_timer = Timer}}.
+
+%%--------------------------------------------------------------------
+%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% {stop, Reason, State}
+%% Description: Handling call messages
+%%--------------------------------------------------------------------
+handle_call({{connection_init, "", _Role}, Pid}, _From,
+ #state{session_cache = Cache} = State) ->
+ erlang:monitor(process, Pid),
+ Result = {ok, make_ref(), Cache},
+ {reply, Result, State};
+
+handle_call({{connection_init, TrustedcertsFile, _Role}, Pid}, _From,
+ #state{certificate_db = Db,
+ session_cache = Cache} = State) ->
+ erlang:monitor(process, Pid),
+ Result =
+ case (catch ssl_certificate_db:add_trusted_certs(Pid,
+ TrustedcertsFile,
+ Db)) of
+ {ok, Ref} ->
+ {ok, Ref, Cache};
+ Error ->
+ {error, Error}
+ end,
+ {reply, Result, State};
+
+handle_call({{client_session_id, Host, Port, SslOpts}, _}, _,
+ #state{session_cache = Cache,
+ session_cache_cb = CacheCb} = State) ->
+ Id = ssl_session:id({Host, Port, SslOpts}, Cache, CacheCb),
+ {reply, Id, State};
+
+handle_call({{server_session_id, Port, SuggestedSessionId, SslOpts}, _},
+ _, #state{session_cache_cb = CacheCb,
+ session_cache = Cache,
+ session_lifetime = LifeTime} = State) ->
+ Id = ssl_session:id(Port, SuggestedSessionId, SslOpts,
+ Cache, CacheCb, LifeTime),
+ {reply, Id, State};
+
+handle_call({{cache_pem, File},Pid}, _, State = #state{certificate_db = Db}) ->
+ try ssl_certificate_db:cache_pem_file(Pid,File,Db) of
+ Result ->
+ {reply, Result, State}
+ catch _:Reason ->
+ {reply, {error, Reason}, State}
+ end;
+
+handle_call(_,_, State) ->
+ {reply, ok, State}.
+%%--------------------------------------------------------------------
+%% Function: handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% Description: Handling cast messages
+%%--------------------------------------------------------------------
+handle_cast({register_session, Host, Port, Session},
+ #state{session_cache = Cache,
+ session_cache_cb = CacheCb} = State) ->
+ TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}),
+ NewSession = Session#session{time_stamp = TimeStamp},
+ CacheCb:update(Cache, {{Host, Port},
+ NewSession#session.session_id}, NewSession),
+ {noreply, State};
+
+handle_cast({register_session, Port, Session},
+ #state{session_cache = Cache,
+ session_cache_cb = CacheCb} = State) ->
+ TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}),
+ NewSession = Session#session{time_stamp = TimeStamp},
+ CacheCb:update(Cache, {Port, NewSession#session.session_id}, NewSession),
+ {noreply, State};
+
+handle_cast({invalidate_session, Host, Port,
+ #session{session_id = ID}},
+ #state{session_cache = Cache,
+ session_cache_cb = CacheCb} = State) ->
+ CacheCb:delete(Cache, {{Host, Port}, ID}),
+ {noreply, State};
+
+handle_cast({invalidate_session, Port, #session{session_id = ID}},
+ #state{session_cache = Cache,
+ session_cache_cb = CacheCb} = State) ->
+ CacheCb:delete(Cache, {Port, ID}),
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% Description: Handling all non call/cast messages
+%%--------------------------------------------------------------------
+handle_info(validate_sessions, #state{session_cache_cb = CacheCb,
+ session_cache = Cache,
+ session_lifetime = LifeTime
+ } = State) ->
+ Timer = erlang:send_after(?SESSION_VALIDATION_INTERVAL,
+ self(), validate_sessions),
+ start_session_validator(Cache, CacheCb, LifeTime),
+ {noreply, State#state{session_validation_timer = Timer}};
+
+handle_info({'EXIT', _, _}, State) ->
+ %% Session validator died!! Do we need to take any action?
+ %% maybe error log
+ {noreply, State};
+
+handle_info({'DOWN', _Ref, _Type, _Pid, ecacertfile}, State) ->
+ {noreply, State};
+
+handle_info({'DOWN', _Ref, _Type, Pid, _Reason}, State) ->
+ erlang:send_after(?CERTIFICATE_CACHE_CLEANUP, self(),
+ {remove_trusted_certs, Pid}),
+ {noreply, State};
+handle_info({remove_trusted_certs, Pid},
+ State = #state{certificate_db = Db}) ->
+ ssl_certificate_db:remove_trusted_certs(Pid, Db),
+ {noreply, State};
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, State) -> void()
+%% Description: This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any necessary
+%% cleaning up. When it returns, the gen_server terminates with Reason.
+%% The return value is ignored.
+%%--------------------------------------------------------------------
+terminate(_Reason, #state{certificate_db = Db,
+ session_cache = SessionCache,
+ session_cache_cb = CacheCb,
+ session_validation_timer = Timer}) ->
+ erlang:cancel_timer(Timer),
+ ssl_certificate_db:remove(Db),
+ CacheCb:terminate(SessionCache),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% Description: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+call(Msg) ->
+ gen_server:call(?MODULE, {Msg, self()}, infinity).
+
+cast(Msg) ->
+ gen_server:cast(?MODULE, Msg).
+
+validate_session(Host, Port, Session, LifeTime) ->
+ case ssl_session:valid_session(Session, LifeTime) of
+ true ->
+ ok;
+ false ->
+ invalidate_session(Host, Port, Session)
+ end.
+
+validate_session(Port, Session, LifeTime) ->
+ case ssl_session:valid_session(Session, LifeTime) of
+ true ->
+ ok;
+ false ->
+ invalidate_session(Port, Session)
+ end.
+
+start_session_validator(Cache, CacheCb, LifeTime) ->
+ spawn_link(?MODULE, init_session_validator,
+ [[Cache, CacheCb, LifeTime]]).
+
+init_session_validator([Cache, CacheCb, LifeTime]) ->
+ CacheCb:foldl(fun session_validation/2,
+ LifeTime, Cache).
+
+session_validation({{Host, Port, _}, Session}, LifeTime) ->
+ validate_session(Host, Port, Session, LifeTime),
+ LifeTime;
+session_validation({{Port, _}, Session}, LifeTime) ->
+ validate_session(Port, Session, LifeTime),
+ LifeTime.
+
diff --git a/lib/ssl/src/ssl_pem.erl b/lib/ssl/src/ssl_pem.erl
new file mode 100644
index 0000000000..0a1bf0f32a
--- /dev/null
+++ b/lib/ssl/src/ssl_pem.erl
@@ -0,0 +1,147 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2003-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(ssl_pem).
+
+%%% Purpose: Reading and writing of PEM type encoded files for SSL.
+
+%% NB write_file/2 is only preliminary.
+
+%% PEM encoded files have the following structure:
+%%
+%% <text>
+%% -----BEGIN SOMETHING-----<CR><LF>
+%% <Base64 encoding line><CR><LF>
+%% <Base64 encoding line><CR><LF>
+%% ...
+%% -----END SOMETHING-----<CR><LF>
+%% <text>
+%%
+%% A file can contain several BEGIN/END blocks. Text lines between
+%% blocks are ignored.
+
+-export([read_file/1, read_file/2, write_file/2]).
+
+%% Read a PEM file and return each decoding as a binary.
+
+read_file(File) ->
+ read_file(File, no_passwd).
+
+read_file(File, Passwd) ->
+ {ok, Fd} = file:open(File, [read]),
+ Result = decode_file(Fd, Passwd),
+ file:close(Fd),
+ Result.
+
+decode_file(Fd, Passwd) ->
+ decode_file(Fd, [], [], notag, [Passwd]).
+
+decode_file(Fd, _RLs, Ens, notag, Info) ->
+ case io:get_line(Fd, "") of
+ "-----BEGIN CERTIFICATE REQUEST-----" ++ _ ->
+ decode_file(Fd, [], Ens, cert_req, Info);
+ "-----BEGIN CERTIFICATE-----" ++ _ ->
+ decode_file(Fd, [], Ens, cert, Info);
+ "-----BEGIN RSA PRIVATE KEY-----" ++ _ ->
+ decode_file(Fd, [], Ens, rsa_private_key, Info);
+ eof ->
+ {ok, lists:reverse(Ens)};
+ _ ->
+ decode_file(Fd, [], Ens, notag, Info)
+ end;
+decode_file(Fd, RLs, Ens, Tag, Info0) ->
+ case io:get_line(Fd, "") of
+ "Proc-Type: 4,ENCRYPTED"++_ ->
+ Info = dek_info(Fd, Info0),
+ decode_file(Fd, RLs, Ens, Tag, Info);
+ "-----END" ++ _ -> % XXX sloppy
+ Cs = lists:flatten(lists:reverse(RLs)),
+ Bin = ssl_base64:join_decode(Cs),
+ case Info0 of
+ [Password, Cipher, SaltHex | Info1] ->
+ Decoded = decode_key(Bin, Password, Cipher, unhex(SaltHex)),
+ decode_file(Fd, [], [{Tag, Decoded}| Ens], notag, Info1);
+ _ ->
+ decode_file(Fd, [], [{Tag, Bin}| Ens], notag, Info0)
+ end;
+ eof ->
+ {ok, lists:reverse(Ens)};
+ L ->
+ decode_file(Fd, [L|RLs], Ens, Tag, Info0)
+ end.
+
+dek_info(Fd, Info) ->
+ Line = io:get_line(Fd, ""),
+ [_, DekInfo0] = string:tokens(Line, ": "),
+ DekInfo1 = string:tokens(DekInfo0, ",\n"),
+ Info ++ DekInfo1.
+
+unhex(S) ->
+ unhex(S, []).
+
+unhex("", Acc) ->
+ lists:reverse(Acc);
+unhex([D1, D2 | Rest], Acc) ->
+ unhex(Rest, [erlang:list_to_integer([D1, D2], 16) | Acc]).
+
+decode_key(Data, Password, "DES-CBC", Salt) ->
+ Key = password_to_key(Password, Salt, 8),
+ IV = Salt,
+ crypto:des_cbc_decrypt(Key, IV, Data);
+decode_key(Data, Password, "DES-EDE3-CBC", Salt) ->
+ Key = password_to_key(Password, Salt, 24),
+ IV = Salt,
+ <<Key1:8/binary, Key2:8/binary, Key3:8/binary>> = Key,
+ crypto:des_ede3_cbc_decrypt(Key1, Key2, Key3, IV, Data).
+
+write_file(File, Ds) ->
+ file:write_file(File, encode_file(Ds)).
+
+encode_file(Ds) ->
+ [encode_file_1(D) || D <- Ds].
+
+encode_file_1({cert, Bin}) ->
+ %% PKIX (X.509)
+ ["-----BEGIN CERTIFICATE-----\n",
+ ssl_base64:encode_split(Bin),
+ "-----END CERTIFICATE-----\n\n"];
+encode_file_1({cert_req, Bin}) ->
+ %% PKCS#10
+ ["-----BEGIN CERTIFICATE REQUEST-----\n",
+ ssl_base64:encode_split(Bin),
+ "-----END CERTIFICATE REQUEST-----\n\n"];
+encode_file_1({rsa_private_key, Bin}) ->
+ %% PKCS#?
+ ["XXX Following key assumed not encrypted\n",
+ "-----BEGIN RSA PRIVATE KEY-----\n",
+ ssl_base64:encode_split(Bin),
+ "-----END RSA PRIVATE KEY-----\n\n"].
+
+password_to_key(Data, Salt, KeyLen) ->
+ <<Key:KeyLen/binary, _/binary>> =
+ password_to_key(<<>>, Data, Salt, KeyLen, <<>>),
+ Key.
+
+password_to_key(_, _, _, Len, Acc) when Len =< 0 ->
+ Acc;
+password_to_key(Prev, Data, Salt, Len, Acc) ->
+ M = crypto:md5([Prev, Data, Salt]),
+ password_to_key(M, Data, Salt, Len - byte_size(M), <<Acc/binary, M/binary>>).
diff --git a/lib/ssl/src/ssl_pkix.erl b/lib/ssl/src/ssl_pkix.erl
new file mode 100644
index 0000000000..8f540f74ad
--- /dev/null
+++ b/lib/ssl/src/ssl_pkix.erl
@@ -0,0 +1,307 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2003-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%% Purpose : API module for decoding of certificates.
+
+-module(ssl_pkix).
+
+-include("ssl_pkix.hrl").
+
+-export([decode_cert_file/1, decode_cert_file/2,
+ decode_cert/1, decode_cert/2, encode_cert/1, encoded_tbs_cert/1,
+ signature_digest/1, decode_rsa_keyfile/2]).
+
+%% The public API is dprecated by public_key and
+%% the internal application API is no longer used ssl.
+%% So this file can be compleatly removed in R14.
+-deprecated({decode_cert_file, 1, next_major_release}).
+-deprecated({decode_cert_file, 2, next_major_release}).
+-deprecated({decode_cert, 1, next_major_release}).
+-deprecated({decode_cert, 2, next_major_release}).
+
+%%====================================================================
+%% API
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: decode_cert_file(File, <Opts>) -> {ok, Cert} | {ok, [Cert]}
+%%
+%% File = string()
+%% Opts = [Opt]
+%% Opt = pem | ssl | pkix - ssl and pkix are mutual exclusive
+%% Cert = term()
+%%
+%% Description: Decodes certificats found in file <File>.
+%% If the options list is empty the certificate is
+%% returned as a DER encoded binary, i.e. {ok, Bin} is returned, where
+%% Bin> is the provided input. The options pkix and ssl imply that the
+%% certificate is returned as a parsed ASN.1 structure in the form of
+%% an Erlang term. The ssl option gives a more elaborate return
+%% structure, with more explicit information. In particular object
+%% identifiers are replaced by atoms. The option subject implies that
+%% only the subject's distinguished name part of the certificate is
+%% returned. It can only be used together with the option pkix or the
+%% option ssl.
+%%--------------------------------------------------------------------
+decode_cert_file(File) ->
+ decode_cert_file(File, []).
+
+decode_cert_file(File, Opts) ->
+ case lists:member(pem, Opts) of
+ true ->
+ {ok, List} = ssl_pem:read_file(File),
+ Certs = [Bin || {cert, Bin} <- List],
+ NewOpts = lists:delete(pem, Opts),
+ Fun = fun(Cert) ->
+ {ok, Decoded} = decode_cert(Cert, NewOpts),
+ Decoded
+ end,
+ case lists:map(Fun, Certs) of
+ [DecodedCert] ->
+ {ok, DecodedCert};
+ DecodedCerts ->
+ {ok, DecodedCerts}
+ end;
+ false ->
+ {ok, Bin} = file:read_file(File),
+ decode_cert(Bin, Opts)
+ end.
+%%--------------------------------------------------------------------
+%% Function: decode_cert(Bin, <Opts>) -> {ok, Cert}
+%% Bin - binary()
+%% Opts = [Opt]
+%% Opt = ssl | pkix | subject - ssl and pkix are mutual exclusive
+%% Cert = term()
+%%
+%% Description: If the options list is empty the certificate is
+%% returned as a DER encoded binary, i.e. {ok, Bin} is returned, where
+%% Bin> is the provided input. The options pkix and ssl imply that the
+%% certificate is returned as a parsed ASN.1 structure in the form of
+%% an Erlang term. The ssl option gives a more elaborate return
+%% structure, with more explicit information. In particular object
+%% identifiers are replaced by atoms. The option subject implies that
+%% only the subject's distinguished name part of the certificate is
+%% returned. It can only be used together with the option pkix or the
+%% option ssl.
+%%--------------------------------------------------------------------
+decode_cert(Bin) ->
+ decode_cert(Bin, []).
+
+decode_cert(Bin, []) when is_binary(Bin) ->
+ {ok, Bin};
+decode_cert(Bin, Opts) when is_binary(Bin) ->
+
+ {ok, Cert} = 'OTP-PKIX':decode('Certificate', Bin),
+
+ case {lists:member(ssl, Opts), lists:member(pkix, Opts)} of
+ {true, false} ->
+ cert_return(transform(Cert, ssl), Opts);
+ {false, true} ->
+ cert_return(transform(Cert, pkix), Opts);
+ _ ->
+ {error, eoptions}
+ end.
+
+encode_cert(#'Certificate'{} = Cert) ->
+ {ok, List} = 'OTP-PKIX':encode('Certificate', Cert),
+ list_to_binary(List).
+
+decode_rsa_keyfile(KeyFile, Password) ->
+ {ok, List} = ssl_pem:read_file(KeyFile, Password),
+ [PrivatKey] = [Bin || {rsa_private_key, Bin} <- List],
+ 'OTP-PKIX':decode('RSAPrivateKey', PrivatKey).
+
+%%====================================================================
+%% Application internal API
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: encoded_tbs_cert(Cert) -> PKXCert
+%%
+%% Cert = binary() - Der encoded
+%% PKXCert = binary() - Der encoded
+%%
+%% Description: Extracts the binary TBSCert from the binary Certificate.
+%%--------------------------------------------------------------------
+encoded_tbs_cert(Cert) ->
+ {ok, PKIXCert} =
+ 'OTP-PKIX':decode_TBSCert_exclusive(Cert),
+ {'Certificate',
+ {'Certificate_tbsCertificate', EncodedTBSCert}, _, _} = PKIXCert,
+ EncodedTBSCert.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+cert_return(Cert, Opts) ->
+ case lists:member(subject, Opts) of
+ true ->
+ {ok, get_subj(Cert)};
+ false ->
+ {ok, Cert}
+ end.
+
+
+%% Transfrom from PKIX1-Explicit88 to SSL-PKIX.
+
+transform(#'Certificate'{signature = Signature,
+ signatureAlgorithm = SignatureAlgorithm,
+ tbsCertificate = TbsCertificate} = Cert, Type) ->
+ Cert#'Certificate'{tbsCertificate = transform(TbsCertificate, Type),
+ signatureAlgorithm = transform(SignatureAlgorithm, Type),
+ signature = transform(Signature, Type)};
+
+%% -record('TBSCertificate',{
+%% version = asn1_DEFAULT, serialNumber, signature, issuer, validity, subject,
+%% subjectPublicKeyInfo, issuerUniqueID = asn1_NOVALUE,
+%% subjectUniqueID = asn1_NOVALUE, extensions = asn1_NOVALUE}).
+
+transform(#'TBSCertificate'{signature = Signature, issuer = Issuer,
+ subject = Subject, extensions = Extensions,
+ subjectPublicKeyInfo = SPKInfo} = TBSCert, Type) ->
+ TBSCert#'TBSCertificate'{signature = transform(Signature, Type),
+ issuer = transform(Issuer, Type),
+ subject = transform(Subject, Type),
+ subjectPublicKeyInfo = transform(SPKInfo, Type),
+ extensions = transform_extensions(Extensions, Type)
+ };
+
+transform(#'AlgorithmIdentifier'{algorithm = Algorithm,
+ parameters = Params}, ssl) ->
+ SignAlgAny =
+ #'SignatureAlgorithm-Any'{algorithm = Algorithm, parameters = Params},
+ {ok, AnyEnc} = 'OTP-PKIX':encode('SignatureAlgorithm-Any', SignAlgAny),
+ {ok, SignAlgCd} = 'OTP-PKIX':decode('SignatureAlgorithm',
+ list_to_binary(AnyEnc)),
+ NAlgo = ssl_pkix_oid:id2atom(SignAlgCd#'SignatureAlgorithm'.algorithm),
+ SignAlgCd#'SignatureAlgorithm'{algorithm = NAlgo};
+
+transform({rdnSequence, Lss}, Type) when is_list(Lss) ->
+ {rdnSequence, [[transform(L, Type) || L <- Ls] || Ls <- Lss]};
+transform({rdnSequence, Lss}, _) ->
+ {rdnSequence, Lss};
+
+transform(#'AttributeTypeAndValue'{} = ATAV, ssl) ->
+ {ok, ATAVEnc} =
+ 'OTP-PKIX':encode('AttributeTypeAndValue', ATAV),
+ {ok, ATAVDec} = 'OTP-PKIX':decode('SSLAttributeTypeAndValue',
+ list_to_binary(ATAVEnc)),
+ AttrType = ATAVDec#'SSLAttributeTypeAndValue'.type,
+ #'AttributeTypeAndValue'{type = ssl_pkix_oid:id2atom(AttrType),
+ value =
+ ATAVDec#'SSLAttributeTypeAndValue'.value};
+
+transform(#'AttributeTypeAndValue'{} = Att, pkix) ->
+ Att;
+
+%% -record('SubjectPublicKeyInfo',{
+%% algorithm, subjectPublicKey}).
+%%
+%% -record('SubjectPublicKeyInfo_algorithm',{
+%% algo, parameters = asn1_NOVALUE}).
+%%
+%% -record('SubjectPublicKeyInfo-Any',{
+%% algorithm, subjectPublicKey}).
+%%
+%% -record('PublicKeyAlgorithm',{
+%% algorithm, parameters = asn1_NOVALUE}).
+
+transform(#'SubjectPublicKeyInfo'{subjectPublicKey = SubjectPublicKey,
+ algorithm = Algorithm}, ssl) ->
+ %% Transform from SubjectPublicKeyInfo (PKIX1Explicit88)
+ %% to SubjectPublicKeyInfo-Any (SSL-PKIX).
+ Algo = Algorithm#'AlgorithmIdentifier'.algorithm,
+ Parameters = Algorithm#'AlgorithmIdentifier'.parameters,
+ AlgorithmAny = #'PublicKeyAlgorithm'{algorithm = Algo,
+ parameters = Parameters},
+ {0, Bin} = SubjectPublicKey,
+ SInfoAny = #'SSLSubjectPublicKeyInfo-Any'{algorithm = AlgorithmAny,
+ subjectPublicKey = Bin},
+
+ %% Encode according to SubjectPublicKeyInfo-Any, and decode according
+ %% to SubjectPublicKeyInfo.
+ {ok, AnyEnc} =
+ 'OTP-PKIX':encode('SSLSubjectPublicKeyInfo-Any', SInfoAny),
+ {ok, SInfoCd} = 'OTP-PKIX':decode('SSLSubjectPublicKeyInfo',
+ list_to_binary(AnyEnc)),
+ %% Replace object identifier by atom
+ AlgorithmCd = SInfoCd#'SSLSubjectPublicKeyInfo'.algorithm,
+ AlgoCd = AlgorithmCd#'SSLSubjectPublicKeyInfo_algorithm'.algo,
+ Params = AlgorithmCd#'SSLSubjectPublicKeyInfo_algorithm'.parameters,
+ Key = SInfoCd#'SSLSubjectPublicKeyInfo'.subjectPublicKey,
+ NAlgoCd = ssl_pkix_oid:id2atom(AlgoCd),
+ NAlgorithmCd =
+ #'SubjectPublicKeyInfo_algorithm'{algorithm = NAlgoCd,
+ parameters = Params},
+ #'SubjectPublicKeyInfo'{algorithm = NAlgorithmCd,
+ subjectPublicKey = Key
+ };
+transform(#'SubjectPublicKeyInfo'{} = SInfo, pkix) ->
+ SInfo;
+
+transform(#'Extension'{extnID = ExtnID} = Ext, ssl) ->
+ NewExtID = ssl_pkix_oid:id2atom(ExtnID),
+ ExtAny = setelement(1, Ext, 'Extension-Any'),
+ {ok, AnyEnc} = 'OTP-PKIX':encode('Extension-Any', ExtAny),
+ {ok, ExtCd} = 'OTP-PKIX':decode('SSLExtension', list_to_binary(AnyEnc)),
+
+ ExtValue = transform_extension_value(NewExtID,
+ ExtCd#'SSLExtension'.extnValue,
+ ssl),
+ #'Extension'{extnID = NewExtID,
+ critical = ExtCd#'SSLExtension'.critical,
+ extnValue = ExtValue};
+
+transform(#'Extension'{extnID = ExtnID, extnValue = ExtnValue} = Ext, pkix) ->
+ NewExtID = ssl_pkix_oid:id2atom(ExtnID),
+ ExtValue = transform_extension_value(NewExtID, ExtnValue, pkix),
+ Ext#'Extension'{extnValue = ExtValue};
+
+transform(#'AuthorityKeyIdentifier'{authorityCertIssuer = CertIssuer} = Ext,
+ Type) ->
+ Ext#'AuthorityKeyIdentifier'{authorityCertIssuer =
+ transform(CertIssuer, Type)};
+
+transform([{directoryName, Value}], Type) ->
+ [{directoryName, transform(Value, Type)}];
+
+transform(X, _) ->
+ X.
+
+transform_extension_value('ce-authorityKeyIdentifier', Value, Type) ->
+ transform(Value, Type);
+transform_extension_value(_, Value, _) ->
+ Value.
+
+transform_extensions(Exts, Type) when is_list(Exts) ->
+ [transform(Ext, Type) || Ext <- Exts];
+transform_extensions(Exts, _) ->
+ Exts.
+
+get_subj(Cert) ->
+ (Cert#'Certificate'.tbsCertificate)#'TBSCertificate'.subject.
+
+signature_digest(BinSignature) ->
+ case (catch 'OTP-PKIX':decode('DigestInfo', BinSignature)) of
+ {ok, DigestInfo} ->
+ list_to_binary(DigestInfo#'DigestInfo'.digest);
+ _ ->
+ {error, decode_error}
+ end.
diff --git a/lib/ssl/src/ssl_pkix.hrl b/lib/ssl/src/ssl_pkix.hrl
new file mode 100644
index 0000000000..a8463369f6
--- /dev/null
+++ b/lib/ssl/src/ssl_pkix.hrl
@@ -0,0 +1,81 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2003-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-ifndef(ssl_pkix).
+-define(ssl_pkix, true).
+
+-include("OTP-PKIX.hrl").
+
+%% The following commented out records are currently defined in OTP-PKIX.hrl
+%% and are considered a public interface through ssl_pkix.hrl.
+%% NOTE do not include OTP-PKIX.hrl it is an generated file
+%% and may change but the following records will still be
+%% availanble from this file.
+
+% -record('Certificate', {
+% tbsCertificate,
+% signatureAlgorithm,
+% signature}).
+
+% -record('TBSCertificate', {
+% version = asn1_DEFAULT,
+% serialNumber,
+% signature,
+% issuer,
+% validity,
+% subject,
+% subjectPublicKeyInfo,
+% issuerUniqueID = asn1_NOVALUE,
+% subjectUniqueID = asn1_NOVALUE,
+% extensions = asn1_NOVALUE}).
+
+% -record('AttributeTypeAndValue', {
+% type,
+% value}).
+
+% -record('SubjectPublicKeyInfo', {
+% algorithm,
+% subjectPublicKey}).
+
+-record('SubjectPublicKeyInfo_algorithm', {
+ algorithm,
+ parameters = asn1_NOVALUE}).
+
+% -record('FieldID', {
+% fieldType,
+% parameters}).
+
+% -record('Characteristic-two', {
+% m,
+% basis,
+% parameters}).
+
+% -record('ExtensionAttribute', {
+% extensionAttributeType,
+% extensionAttributeValue}).
+
+% -record('Extension', {
+% extnID,
+% critical = asn1_DEFAULT,
+% extnValue}).
+
+-endif. % -ifdef(ssl_pkix).
+
diff --git a/lib/ssl/src/ssl_prim.erl b/lib/ssl/src/ssl_prim.erl
new file mode 100644
index 0000000000..e3140a89d1
--- /dev/null
+++ b/lib/ssl/src/ssl_prim.erl
@@ -0,0 +1,173 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+%% Purpose: Primitive interface to SSL, without broker process (used by
+%% SSL distribution).
+
+-module(ssl_prim).
+
+-export([listen/2, connect/3, accept/1, close/1, send/2, send/3, recv/2, recv/3,
+ getll/1, getstat/2, setopts/2, controlling_process/2, peername/1,
+ sockname/1, getif/1]).
+
+-include("ssl_int.hrl").
+-include("ssl_broker_int.hrl").
+
+%-define(filter(Call), filter((catch Call))).
+-define(filter(Call), filter(Call)).
+
+listen(Port, Opts) ->
+ St = newstate(listener),
+ ?filter(ssl_broker:listen_prim(ssl_server_prim, self(), Port, nonactive(Opts), St)).
+
+connect(Address, Port, Opts) ->
+ St = newstate(connector),
+ ?filter(ssl_broker:connect_prim(ssl_server_prim, inet_tcp, self(), Address,
+ Port, nonactive(Opts), infinity, St)).
+
+accept(#st{} = ListenSt0) ->
+ case transport_accept(ListenSt0) of
+ {ok, ListenSt1} ->
+ ssl_accept(ListenSt0, ListenSt1);
+ Error ->
+ Error
+ end.
+
+transport_accept(#st{opts = ListenOpts, thissock = ListenSocket}) ->
+ NewSt = newstate(acceptor),
+ ListenFd = ListenSocket#sslsocket.fd,
+ ?filter(ssl_broker:transport_accept_prim(ssl_server_prim, ListenFd,
+ ListenOpts, infinity, NewSt)).
+
+ssl_accept(#st{opts = LOpts}, ListenSt1) ->
+ ?filter(ssl_broker:ssl_accept_prim(ssl_server_prim, gen_tcp, self(),
+ LOpts, infinity, ListenSt1)).
+
+close(#st{fd = Fd}) when is_integer(Fd) ->
+ ssl_server:close_prim(ssl_server_prim, Fd),
+ ok;
+close(_) ->
+ ok.
+
+send(St, Data) ->
+ send(St, Data, []).
+
+send(#st{proxysock = Proxysock, status = open}, Data, Opts) ->
+ case inet_tcp:send(Proxysock, Data, Opts) of
+ ok ->
+ ok;
+ {error, _} ->
+ {error, closed}
+ end;
+send(#st{}, _Data, _Opts) ->
+ {error, closed}.
+
+recv(St, Length) ->
+ recv(St, Length, infinity).
+
+recv(#st{proxysock = Proxysock, status = open}, Length, Tmo) ->
+ inet_tcp:recv(Proxysock, Length, Tmo);
+recv(#st{}, _Length, _Tmo) ->
+ {error, closed}.
+
+getll(#st{proxysock = Proxysock, status = open}) ->
+ inet:getll(Proxysock);
+getll(#st{}) ->
+ {error, closed}.
+
+getstat(#st{proxysock = Proxysock, status = open}, Opts) ->
+ inet:getstat(Proxysock, Opts);
+getstat(#st{}, _Opts) ->
+ {error, closed}.
+
+setopts(#st{proxysock = Proxysock, status = open}, Opts) ->
+ case remove_supported(Opts) of
+ [] ->
+ inet:setopts(Proxysock, Opts);
+ _ ->
+ {error, enotsup}
+ end;
+setopts(#st{}, _Opts) ->
+ {error, closed}.
+
+
+controlling_process(#st{proxysock = Proxysock, status = open}, Pid)
+ when is_pid(Pid) ->
+ inet_tcp:controlling_process(Proxysock, Pid);
+controlling_process(#st{}, Pid) when is_pid(Pid) ->
+ {error, closed}.
+
+peername(#st{fd = Fd, status = open}) ->
+ case ssl_server:peername_prim(ssl_server_prim, Fd) of
+ {ok, {Address, Port}} ->
+ {ok, At} = inet_parse:ipv4_address(Address),
+ {ok, {At, Port}};
+ Error ->
+ Error
+ end;
+peername(#st{}) ->
+ {error, closed}.
+
+sockname(#st{fd = Fd, status = open}) ->
+ case ssl_server:sockname_prim(ssl_server_prim, Fd) of
+ {ok, {Address, Port}} ->
+ {ok, At} = inet_parse:ipv4_address(Address),
+ {ok, {At, Port}};
+ Error ->
+ Error
+ end;
+sockname(#st{}) ->
+ {error, closed}.
+
+getif(#st{proxysock = Proxysock, status = open}) ->
+ inet:getif(Proxysock);
+getif(#st{}) ->
+ {error, closed}.
+
+remove_supported([{active, _}|T]) ->
+ remove_supported(T);
+remove_supported([{packet,_}|T]) ->
+ remove_supported(T);
+remove_supported([{deliver,_}|T]) ->
+ remove_supported(T);
+remove_supported([H|T]) ->
+ [H | remove_supported(T)];
+remove_supported([]) ->
+ [].
+
+filter(Result) ->
+ case Result of
+ {ok, _Sock,St} ->
+ {ok, St};
+ {error, Reason, _St} ->
+ {error,Reason}
+ end.
+
+nonactive([{active,_}|T]) ->
+ nonactive(T);
+nonactive([H|T]) ->
+ [H | nonactive(T)];
+nonactive([]) ->
+ [{active, false}].
+
+newstate(Type) ->
+ #st{brokertype = Type, server = whereis(ssl_server_prim),
+ client = undefined, collector = undefined, debug = false}.
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
new file mode 100644
index 0000000000..37a3d1b639
--- /dev/null
+++ b/lib/ssl/src/ssl_record.erl
@@ -0,0 +1,577 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Help functions for handling the SSL-Record protocol
+%%
+%%----------------------------------------------------------------------
+
+-module(ssl_record).
+
+-include("ssl_record.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_handshake.hrl").
+-include("ssl_debug.hrl").
+
+%% Connection state handling
+-export([init_connection_states/1,
+ current_connection_state/2, pending_connection_state/2,
+ update_security_params/3,
+ set_mac_secret/4,
+ set_master_secret/2,
+ activate_pending_connection_state/2,
+ set_pending_cipher_state/4]).
+
+%% Handling of incoming data
+-export([get_tls_records/2]).
+
+%% Encoding records
+-export([encode_handshake/3, encode_alert_record/3,
+ encode_change_cipher_spec/2, encode_data/3]).
+
+%% Decoding
+-export([decode_cipher_text/2]).
+
+%% Misc.
+-export([protocol_version/1, lowest_protocol_version/2,
+ highest_protocol_version/1, supported_protocol_versions/0,
+ is_acceptable_version/1]).
+
+-export([compressions/0]).
+
+-compile(inline).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: init_connection_states(Role) -> #connection_states{}
+%% Role = client | server
+%% Random = binary()
+%%
+%% Description: Creates a connection_states record with appropriate
+%% values for the initial SSL connection setup.
+%%--------------------------------------------------------------------
+init_connection_states(Role) ->
+ ConnectionEnd = record_protocol_role(Role),
+ Current = initial_connection_state(ConnectionEnd),
+ Pending = empty_connection_state(ConnectionEnd),
+ #connection_states{current_read = Current,
+ pending_read = Pending,
+ current_write = Current,
+ pending_write = Pending
+ }.
+
+%%--------------------------------------------------------------------
+%% Function: current_connection_state(States, Type) -> #connection_state{}
+%% States = #connection_states{}
+%% Type = read | write
+%%
+%% Description: Returns the instance of the connection_state record
+%% that is currently defined as the current conection state.
+%%--------------------------------------------------------------------
+current_connection_state(#connection_states{current_read = Current},
+ read) ->
+ Current;
+current_connection_state(#connection_states{current_write = Current},
+ write) ->
+ Current.
+
+%%--------------------------------------------------------------------
+%% Function: pending_connection_state(States, Type) -> #connection_state{}
+%% States = #connection_states{}
+%% Type = read | write
+%%
+%% Description: Returns the instance of the connection_state record
+%% that is currently defined as the pending conection state.
+%%--------------------------------------------------------------------
+pending_connection_state(#connection_states{pending_read = Pending},
+ read) ->
+ Pending;
+pending_connection_state(#connection_states{pending_write = Pending},
+ write) ->
+ Pending.
+
+%%--------------------------------------------------------------------
+%% Function: update_security_params(Params, States) ->
+%% #connection_states{}
+%% Params = #security_parameters{}
+%% States = #connection_states{}
+%%
+%% Description: Creates a new instance of the connection_states record
+%% where the pending states gets its security parameters
+%% updated to <Params>.
+%%--------------------------------------------------------------------
+update_security_params(ReadParams, WriteParams, States =
+ #connection_states{pending_read = Read,
+ pending_write = Write}) ->
+ States#connection_states{pending_read =
+ Read#connection_state{security_parameters =
+ ReadParams},
+ pending_write =
+ Write#connection_state{security_parameters =
+ WriteParams}
+ }.
+%%--------------------------------------------------------------------
+%% Function: set_mac_secret(ClientWriteMacSecret,
+%% ServerWriteMacSecret, Role, States) ->
+%% #connection_states{}
+%% MacSecret = binary()
+%% States = #connection_states{}
+%% Role = server | client
+%%
+%% update the mac_secret field in pending connection states
+%%--------------------------------------------------------------------
+set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, client, States) ->
+ set_mac_secret(ServerWriteMacSecret, ClientWriteMacSecret, States);
+set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, server, States) ->
+ set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, States).
+
+set_mac_secret(ReadMacSecret, WriteMacSecret,
+ States = #connection_states{pending_read = Read,
+ pending_write = Write}) ->
+ States#connection_states{
+ pending_read = Read#connection_state{mac_secret = ReadMacSecret},
+ pending_write = Write#connection_state{mac_secret = WriteMacSecret}
+ }.
+
+
+%%--------------------------------------------------------------------
+%% Function: set_master_secret(MasterSecret, States) ->
+%% #connection_states{}
+%% MacSecret =
+%% States = #connection_states{}
+%%
+%% Set master_secret in pending connection states
+%%--------------------------------------------------------------------
+set_master_secret(MasterSecret,
+ States = #connection_states{pending_read = Read,
+ pending_write = Write}) ->
+ ReadSecPar = Read#connection_state.security_parameters,
+ Read1 = Read#connection_state{
+ security_parameters = ReadSecPar#security_parameters{
+ master_secret = MasterSecret}},
+ WriteSecPar = Write#connection_state.security_parameters,
+ Write1 = Write#connection_state{
+ security_parameters = WriteSecPar#security_parameters{
+ master_secret = MasterSecret}},
+ States#connection_states{pending_read = Read1, pending_write = Write1}.
+
+
+%%--------------------------------------------------------------------
+%% Function: activate_pending_connection_state(States, Type) ->
+%% #connection_states{}
+%% States = #connection_states{}
+%% Type = read | write
+%%
+%% Description: Creates a new instance of the connection_states record
+%% where the pending state of <Type> has been activated.
+%%--------------------------------------------------------------------
+activate_pending_connection_state(States =
+ #connection_states{pending_read = Pending},
+ read) ->
+ NewCurrent = Pending#connection_state{sequence_number = 0},
+ SecParams = Pending#connection_state.security_parameters,
+ ConnectionEnd = SecParams#security_parameters.connection_end,
+ NewPending = empty_connection_state(ConnectionEnd),
+ States#connection_states{current_read = NewCurrent,
+ pending_read = NewPending
+ };
+
+activate_pending_connection_state(States =
+ #connection_states{pending_write = Pending},
+ write) ->
+ NewCurrent = Pending#connection_state{sequence_number = 0},
+ SecParams = Pending#connection_state.security_parameters,
+ ConnectionEnd = SecParams#security_parameters.connection_end,
+ NewPending = empty_connection_state(ConnectionEnd),
+ States#connection_states{current_write = NewCurrent,
+ pending_write = NewPending
+ }.
+
+%%--------------------------------------------------------------------
+%% Function: set_pending_cipher_state(States, ClientState,
+%% ServerState, Role) ->
+%% #connection_states{}
+%% ClientState = ServerState = #cipher_state{}
+%% States = #connection_states{}
+%%
+%% Description: Set the cipher state in the specified pending connection state.
+%%--------------------------------------------------------------------
+set_pending_cipher_state(#connection_states{pending_read = Read,
+ pending_write = Write} = States,
+ ClientState, ServerState, server) ->
+ States#connection_states{
+ pending_read = Read#connection_state{cipher_state = ClientState},
+ pending_write = Write#connection_state{cipher_state = ServerState}};
+
+set_pending_cipher_state(#connection_states{pending_read = Read,
+ pending_write = Write} = States,
+ ClientState, ServerState, client) ->
+ States#connection_states{
+ pending_read = Read#connection_state{cipher_state = ServerState},
+ pending_write = Write#connection_state{cipher_state = ClientState}}.
+
+%%--------------------------------------------------------------------
+%% Function: get_tls_record(Data, Buffer) -> Result
+%% Result = {[#tls_compressed{}], NewBuffer}
+%% Data = Buffer = NewBuffer = binary()
+%%
+%% Description: given old buffer and new data from TCP, packs up a records
+%% and returns it as a list of #tls_compressed, also returns leftover
+%% data
+%%--------------------------------------------------------------------
+get_tls_records(Data, <<>>) ->
+ get_tls_records_aux(Data, []);
+get_tls_records(Data, Buffer) ->
+ get_tls_records_aux(list_to_binary([Buffer, Data]), []).
+
+get_tls_records_aux(<<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer),
+ ?UINT16(Length), Data:Length/binary, Rest/binary>>,
+ Acc) ->
+ get_tls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA,
+ version = {MajVer, MinVer},
+ fragment = Data} | Acc]);
+get_tls_records_aux(<<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer),
+ ?UINT16(Length),
+ Data:Length/binary, Rest/binary>>, Acc) ->
+ get_tls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE,
+ version = {MajVer, MinVer},
+ fragment = Data} | Acc]);
+get_tls_records_aux(<<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer),
+ ?UINT16(Length), Data:Length/binary,
+ Rest/binary>>, Acc) ->
+ get_tls_records_aux(Rest, [#ssl_tls{type = ?ALERT,
+ version = {MajVer, MinVer},
+ fragment = Data} | Acc]);
+get_tls_records_aux(<<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer),
+ ?UINT16(Length), Data:Length/binary, Rest/binary>>,
+ Acc) ->
+ get_tls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
+ version = {MajVer, MinVer},
+ fragment = Data} | Acc]);
+%% Matches a ssl v2 client hello message.
+%% The server must be able to receive such messages, from clients that
+%% are willing to use ssl v3 or higher, but have ssl v2 compatibility.
+get_tls_records_aux(<<1:1, Length0:15, Data0:Length0/binary, Rest/binary>>,
+ Acc) ->
+ case Data0 of
+ <<?BYTE(?CLIENT_HELLO), ?BYTE(MajVer), ?BYTE(MinVer), _/binary>> ->
+ Length = Length0-1,
+ <<?BYTE(_), Data1:Length/binary>> = Data0,
+ Data = <<?BYTE(?CLIENT_HELLO), ?UINT24(Length), Data1/binary>>,
+ get_tls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE,
+ version = {MajVer, MinVer},
+ fragment = Data} | Acc]);
+ _ ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
+
+ end;
+
+get_tls_records_aux(<<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer),
+ ?UINT16(Length), _/binary>>,
+ _Acc) when Length > ?MAX_CIPHER_TEXT_LENGTH->
+ ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW);
+
+get_tls_records_aux(<<1:1, Length0:15, _/binary>>,_Acc)
+ when Length0 > ?MAX_CIPHER_TEXT_LENGTH->
+ ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW);
+
+get_tls_records_aux(Data, Acc) ->
+ {lists:reverse(Acc), Data}.
+
+%%--------------------------------------------------------------------
+%% Function: protocol_version(Version) -> #protocol_version{}
+%% Version = atom()
+%%
+%% Description: Creates a protocol version record from a version atom
+%% or vice versa.
+%%--------------------------------------------------------------------
+protocol_version('tlsv1.1') ->
+ {3, 2};
+protocol_version(tlsv1) ->
+ {3, 1};
+protocol_version(sslv3) ->
+ {3, 0};
+protocol_version(sslv2) ->
+ {2, 0};
+protocol_version({3, 2}) ->
+ 'tlsv1.1';
+protocol_version({3, 1}) ->
+ tlsv1;
+protocol_version({3, 0}) ->
+ sslv3;
+protocol_version({2, 0}) ->
+ sslv2.
+%%--------------------------------------------------------------------
+%% Function: protocol_version(Version1, Version2) -> #protocol_version{}
+%% Version1 = Version2 = #protocol_version{}
+%%
+%% Description: Lowes protocol version of two given versions
+%%--------------------------------------------------------------------
+lowest_protocol_version(Version = {M, N}, {M, O}) when N < O ->
+ Version;
+lowest_protocol_version({M, _},
+ Version = {M, _}) ->
+ Version;
+lowest_protocol_version(Version = {M,_},
+ {N, _}) when M < N ->
+ Version;
+lowest_protocol_version(_,Version) ->
+ Version.
+%%--------------------------------------------------------------------
+%% Function: protocol_version(Versions) -> #protocol_version{}
+%% Versions = [#protocol_version{}]
+%%
+%% Description: Highest protocol version present in a list
+%%--------------------------------------------------------------------
+highest_protocol_version([]) ->
+ highest_protocol_version();
+highest_protocol_version(Versions) ->
+ [Ver | Vers] = Versions,
+ highest_protocol_version(Ver, Vers).
+
+highest_protocol_version(Version, []) ->
+ Version;
+highest_protocol_version(Version = {N, M}, [{N, O} | Rest]) when M > O ->
+ highest_protocol_version(Version, Rest);
+highest_protocol_version({M, _}, [Version = {M, _} | Rest]) ->
+ highest_protocol_version(Version, Rest);
+highest_protocol_version(Version = {M,_}, [{N,_} | Rest]) when M > N ->
+ highest_protocol_version(Version, Rest);
+highest_protocol_version(_, [Version | Rest]) ->
+ highest_protocol_version(Version, Rest).
+
+%%--------------------------------------------------------------------
+%% Function: supported_protocol_versions() -> Versions
+%% Versions = [#protocol_version{}]
+%%
+%% Description: Protocol versions supported
+%%--------------------------------------------------------------------
+supported_protocol_versions() ->
+ Fun = fun(Version) ->
+ protocol_version(Version)
+ end,
+ case application:get_env(ssl, protocol_version) of
+ undefined ->
+ lists:map(Fun, ?DEFAULT_SUPPORTED_VERSIONS);
+ {ok, []} ->
+ lists:map(Fun, ?DEFAULT_SUPPORTED_VERSIONS);
+ {ok, Vsns} when is_list(Vsns) ->
+ lists:map(Fun, Vsns);
+ {ok, Vsn} ->
+ [Fun(Vsn)]
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: is_acceptable_version(Version) -> true | false
+%% Version = #protocol_version{}
+%%
+%% Description: ssl version 2 is not acceptable security risks are too big.
+%%--------------------------------------------------------------------
+is_acceptable_version({N,_})
+ when N >= ?LOWEST_MAJOR_SUPPORTED_VERSION ->
+ true;
+is_acceptable_version(_) ->
+ false.
+
+%%--------------------------------------------------------------------
+%% Function: compressions() -> binary()
+%%
+%% Description: return a list of compressions supported (currently none)
+%%--------------------------------------------------------------------
+compressions() ->
+ [?byte(?NULL)].
+
+%%--------------------------------------------------------------------
+%% Function: decode_cipher_text(CipherText, ConnectionStates0) ->
+%% {Plain, ConnectionStates}
+%%
+%% Description: Decode cipher text
+%%--------------------------------------------------------------------
+decode_cipher_text(CipherText, ConnnectionStates0) ->
+ ReadState0 = ConnnectionStates0#connection_states.current_read,
+ #connection_state{compression_state = CompressionS0,
+ security_parameters = SecParams} = ReadState0,
+ CompressAlg = SecParams#security_parameters.compression_algorithm,
+ {Compressed, ReadState1} = decipher(CipherText, ReadState0),
+ {Plain, CompressionS1} = uncompress(CompressAlg,
+ Compressed, CompressionS0),
+ ConnnectionStates = ConnnectionStates0#connection_states{
+ current_read = ReadState1#connection_state{
+ compression_state = CompressionS1}},
+ {Plain, ConnnectionStates}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+highest_protocol_version() ->
+ highest_protocol_version(supported_protocol_versions()).
+
+initial_connection_state(ConnectionEnd) ->
+ #connection_state{security_parameters =
+ initial_security_params(ConnectionEnd),
+ sequence_number = 0
+ }.
+
+initial_security_params(ConnectionEnd) ->
+ #security_parameters{connection_end = ConnectionEnd,
+ bulk_cipher_algorithm = ?NULL,
+ mac_algorithm = ?NULL,
+ compression_algorithm = ?NULL,
+ cipher_type = ?NULL
+ }.
+
+empty_connection_state(ConnectionEnd) ->
+ SecParams = empty_security_params(ConnectionEnd),
+ #connection_state{security_parameters = SecParams}.
+
+empty_security_params(ConnectionEnd = ?CLIENT) ->
+ #security_parameters{connection_end = ConnectionEnd,
+ client_random = random()};
+empty_security_params(ConnectionEnd = ?SERVER) ->
+ #security_parameters{connection_end = ConnectionEnd,
+ server_random = random()}.
+random() ->
+ Secs_since_1970 = calendar:datetime_to_gregorian_seconds(
+ calendar:universal_time()) - 62167219200,
+ Random_28_bytes = crypto:rand_bytes(28),
+ <<?UINT32(Secs_since_1970), Random_28_bytes/binary>>.
+
+record_protocol_role(client) ->
+ ?CLIENT;
+record_protocol_role(server) ->
+ ?SERVER.
+
+split_bin(Bin, ChunkSize) ->
+ split_bin(Bin, ChunkSize, []).
+
+split_bin(<<>>, _, Acc) ->
+ lists:reverse(Acc);
+split_bin(Bin, ChunkSize, Acc) ->
+ case Bin of
+ <<Chunk:ChunkSize/binary, Rest/binary>> ->
+ split_bin(Rest, ChunkSize, [Chunk | Acc]);
+ _ ->
+ lists:reverse(Acc, [Bin])
+ end.
+
+encode_data(Frag, Version, ConnectionStates)
+ when byte_size(Frag) < (?MAX_PLAIN_TEXT_LENGTH - 2048) ->
+ encode_plain_text(?APPLICATION_DATA,Version,Frag,ConnectionStates);
+encode_data(Frag, Version, ConnectionStates) ->
+ Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH - 2048),
+ {CS1, Acc} =
+ lists:foldl(fun(B, {CS0, Acc}) ->
+ {ET, CS1} =
+ encode_plain_text(?APPLICATION_DATA,
+ Version, B, CS0),
+ {CS1, [ET | Acc]}
+ end, {ConnectionStates, []}, Data),
+ {lists:reverse(Acc), CS1}.
+
+encode_handshake(Frag, Version, ConnectionStates) ->
+ encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates).
+
+encode_alert_record(#alert{level = Level, description = Description},
+ Version, ConnectionStates) ->
+ encode_plain_text(?ALERT, Version, <<?BYTE(Level), ?BYTE(Description)>>,
+ ConnectionStates).
+
+encode_change_cipher_spec(Version, ConnectionStates) ->
+ encode_plain_text(?CHANGE_CIPHER_SPEC, Version, <<1:8>>, ConnectionStates).
+
+encode_plain_text(Type, Version, Data, ConnectionStates) ->
+ #connection_states{current_write=#connection_state{
+ compression_state=CompS0,
+ security_parameters=
+ #security_parameters{compression_algorithm=CompAlg}
+ }=CS0} = ConnectionStates,
+ {Comp, CompS1} = compress(CompAlg, Data, CompS0),
+ CS1 = CS0#connection_state{compression_state = CompS1},
+ {CipherText, CS2} = cipher(Type, Version, Comp, CS1),
+ CTBin = encode_tls_cipher_text(Type, Version, CipherText),
+ {CTBin, ConnectionStates#connection_states{current_write = CS2}}.
+
+encode_tls_cipher_text(Type, {MajVer, MinVer}, Fragment) ->
+ Length = erlang:iolist_size(Fragment),
+ [<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Fragment].
+
+cipher(Type, Version, Fragment, CS0) ->
+ Length = erlang:iolist_size(Fragment),
+ {Hash, CS1=#connection_state{cipher_state = CipherS0,
+ security_parameters=
+ #security_parameters{bulk_cipher_algorithm =
+ BCA}
+ }} =
+ hash_and_bump_seqno(CS0, Type, Version, Length, Fragment),
+ ?DBG_HEX(Fragment),
+ {Ciphered, CipherS1} = ssl_cipher:cipher(BCA, CipherS0, Hash, Fragment),
+ ?DBG_HEX(Ciphered),
+ CS2 = CS1#connection_state{cipher_state=CipherS1},
+ {Ciphered, CS2}.
+
+decipher(TLS=#ssl_tls{type = ?CHANGE_CIPHER_SPEC}, CS) ->
+ %% These are never encrypted
+ {TLS, CS};
+decipher(TLS=#ssl_tls{type=Type, version=Version, fragment=Fragment}, CS0) ->
+ SP = CS0#connection_state.security_parameters,
+ BCA = SP#security_parameters.bulk_cipher_algorithm, % eller Cipher?
+ HashSz = SP#security_parameters.hash_size,
+ CipherS0 = CS0#connection_state.cipher_state,
+ {T, Mac, CipherS1} = ssl_cipher:decipher(BCA, HashSz, CipherS0, Fragment),
+ CS1 = CS0#connection_state{cipher_state = CipherS1},
+ TLength = size(T),
+ {Hash, CS2} = hash_and_bump_seqno(CS1, Type, Version, TLength, Fragment),
+ ok = check_hash(Hash, Mac),
+ {TLS#ssl_tls{fragment = T}, CS2}.
+
+uncompress(?NULL, Data = #ssl_tls{type = _Type,
+ version = _Version,
+ fragment = _Fragment}, CS) ->
+ {Data, CS}.
+
+compress(?NULL, Data, CS) ->
+ {Data, CS}.
+
+hash_and_bump_seqno(#connection_state{sequence_number = SeqNo,
+ mac_secret = MacSecret,
+ security_parameters =
+ SecPars} = CS0,
+ Type, Version, Length, Fragment) ->
+ Hash = mac_hash(Version,
+ SecPars#security_parameters.mac_algorithm,
+ MacSecret, SeqNo, Type,
+ Length, Fragment),
+ {Hash, CS0#connection_state{sequence_number = SeqNo+1}}.
+
+check_hash(_, _) ->
+ ok. %% TODO check this
+
+mac_hash(?NULL, {_,_}, _MacSecret, _SeqNo, _Type,
+ _Length, _Fragment) ->
+ <<>>;
+mac_hash({3, 0}, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) ->
+ ssl_ssl3:mac_hash(MacAlg, MacSecret, SeqNo, Type, Length, Fragment);
+mac_hash({3, N} = Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment)
+ when N =:= 1; N =:= 2 ->
+ ssl_tls1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version,
+ Length, Fragment).
diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl
new file mode 100644
index 0000000000..7370e0f0b3
--- /dev/null
+++ b/lib/ssl/src/ssl_record.hrl
@@ -0,0 +1,170 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Record and constant defenitions for the SSL-record protocol
+%% see RFC 2246
+%%----------------------------------------------------------------------
+
+-ifndef(ssl_record).
+-define(ssl_record, true).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Connection states - RFC 4346 section 6.1
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-record(connection_states, {
+ current_read,
+ pending_read,
+ current_write,
+ pending_write
+ }).
+
+-record(security_parameters, {
+ cipher_suite,
+ connection_end,
+ bulk_cipher_algorithm,
+ cipher_type,
+ iv_size,
+ key_size, % unit 8
+ key_material_length, % unit 8
+ expanded_key_material_length, % unit 8
+ mac_algorithm, % unit 8
+ hash_size, % unit 8
+ compression_algorithm, % unit 8
+ master_secret, % opaque 48
+ client_random, % opaque 32
+ server_random, % opaque 32
+ exportable % boolean
+ }).
+
+-record(connection_state, {
+ security_parameters,
+ compression_state,
+ cipher_state,
+ mac_secret,
+ sequence_number
+ }).
+
+%% ConnectionEnd
+-define(SERVER, 0).
+-define(CLIENT, 1).
+
+%% BulkCipherAlgorithm
+%-define(NULL, 0). %% Already defined by ssl_internal.hrl
+-define(RC4, 1).
+-define(RC2, 2).
+-define(DES, 3).
+-define('3DES', 4).
+-define(DES40, 5).
+-define(IDEA, 6).
+-define(AES, 7).
+
+%% CipherType
+-define(STREAM, 0).
+-define(BLOCK, 1).
+
+%% IsExportable
+%-define(TRUE, 0). %% Already defined by ssl_internal.hrl
+%-define(FALSE, 1). %% Already defined by ssl_internal.hrl
+
+%% MACAlgorithm
+%-define(NULL, 0). %% Already defined by ssl_internal.hrl
+-define(MD5, 1).
+-define(SHA, 2).
+
+%% CompressionMethod
+% -define(NULL, 0). %% Already defined by ssl_internal.hrl
+
+
+-record(compression_state, {
+ method,
+ state
+ }).
+
+%% See also cipher.hrl for #cipher_state{}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Record layer - RFC 2246 section 6.2
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%enum {
+%% change_cipher_spec(20), alert(21), handshake(22),
+%% application_data(23), (255)
+%% } ContentType;
+
+-define(CHANGE_CIPHER_SPEC, 20).
+-define(ALERT, 21).
+-define(HANDSHAKE, 22).
+-define(APPLICATION_DATA, 23).
+-define(MAX_PLAIN_TEXT_LENGTH, 16384).
+-define(MAX_COMPRESSED_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+1024)).
+-define(MAX_CIPHER_TEXT_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+2048)).
+
+%% -record(protocol_version, {
+%% major, % unit 8
+%% minor % unit 8
+%% }).
+
+-define(LOWEST_MAJOR_SUPPORTED_VERSION, 3).
+
+-record(ssl_tls, { %% From inet driver
+ port,
+ type,
+ version,
+ fragment
+ }).
+
+%% -record(tls_plain_text, {
+%% type,
+%% version, % #protocol_version{}
+%% length, % unit 16
+%% fragment % opaque
+%% }).
+
+%% -record(tls_compressed, {
+%% type,
+%% version,
+%% length, % unit 16
+%% fragment % opaque
+%% }).
+
+%% -record(tls_cipher_text, {
+%% type,
+%% version,
+%% length,
+%% cipher,
+%% fragment
+%% }).
+
+-record(generic_stream_cipher, {
+ content, % opaque content[TLSCompressed.length];
+ mac % opaque MAC[CipherSpec.hash_size];
+ }).
+
+-record(generic_block_cipher, {
+ iv, % opaque IV[CipherSpec.block_length];
+ content, % opaque content[TLSCompressed.length];
+ mac, % opaque MAC[CipherSpec.hash_size];
+ padding, % unit 8 padding[GenericBlockCipher.padding_length];
+ padding_length % uint8 padding_length;
+ }).
+
+-endif. % -ifdef(ssl_record).
diff --git a/lib/ssl/src/ssl_server.erl b/lib/ssl/src/ssl_server.erl
new file mode 100644
index 0000000000..b66e20a397
--- /dev/null
+++ b/lib/ssl/src/ssl_server.erl
@@ -0,0 +1,1378 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+%%% Purpose : SSL server
+
+%%
+%% TODO
+%%
+%% XXX The ip option in listen is not general enough. It is assumed
+%% to be a tuple, which is not always the case.
+
+-module(ssl_server).
+-behaviour(gen_server).
+
+%% External exports
+-export([start_link/0]).
+
+-export([transport_accept/2, transport_accept/3, ssl_accept/2, ssl_accept/3,
+ ciphers/0, connect/5, connect/6,
+ connection_info/1, close/1, listen/3, listen/4, peercert/1,
+ peername/1, proxy_join/2, seed/1, setnodelay/2, sockname/1,
+ version/0]).
+
+-export([start_link_prim/0]).
+-export([ssl_accept_prim/4, transport_accept_prim/4,
+ connect_prim/7, close_prim/2,
+ listen_prim/5, proxy_join_prim/3, peername_prim/2, setnodelay_prim/3,
+ sockname_prim/2]).
+
+-export([dump/0, dump/1]).
+-export([enable_debug/0, disable_debug/0, set_debug/1]).
+-export([enable_debugmsg/0, disable_debugmsg/0, set_debugmsg/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ code_change/3, terminate/2]).
+
+-include("ssl_int.hrl").
+
+-record(st, {
+ port = [], % port() of port program
+ progpid = [], % OS pid of port program
+ debug = false, % debug printout flag
+ cons = [], % All brokers except pending accepts
+ paccepts = [], % Pending accept brokers
+ proxylsport = [], % proxy listen socket port
+ intref = 0, % internal reference counter
+ compvsn = "", % ssl compile library version
+ libvsn = "", % ssl library version
+ ciphers = [] % available ciphers
+ }).
+
+
+%% In all functions below IP is a four tuple, e.g. {192, 236, 52, 7}.
+%% Port, Fd and ListenFd are integers; Flags is a string of characters.
+%%
+%% The prefixes F and L mean foreign and local, respectively.
+%% Example: FIP (IP address for foreign end).
+
+%%
+%% start_link() -> {ok, Pid} | {error, Reason}
+%%
+start_link() ->
+ gen_server:start_link({local, ssl_server}, ssl_server, [], []).
+
+start_link_prim() ->
+ gen_server:start_link({local, ssl_server_prim}, ssl_server, [], []).
+
+%%
+%% transport_accept(ListenFd, Flags) -> {ok, Fd, ProxyLLPort} |
+%% {error, Reason}
+%%
+transport_accept(ListenFd, Flags) ->
+ transport_accept(ListenFd, Flags, infinity).
+transport_accept(ListenFd, Flags, Timeout) ->
+ transport_accept_prim(ssl_server,ListenFd, Flags, Timeout).
+
+transport_accept_prim(ServerName, ListenFd, Flags, Timeout) ->
+ Req = {transport_accept, self(), ListenFd, Flags},
+ gen_server:call(ServerName, Req, Timeout).
+
+%%
+%% ssl_accept(ListenFd, Flags) -> {ok, Fd, ProxyLLPort} |
+%% {error, Reason}
+%%
+ssl_accept(ListenFd, Flags) ->
+ ssl_accept(ListenFd, Flags, infinity).
+ssl_accept(ListenFd, Flags, Timeout) ->
+ ssl_accept_prim(ssl_server, ListenFd, Flags, Timeout).
+
+ssl_accept_prim(ServerName, Fd, Flags, Timeout) ->
+ Req = {ssl_accept, Fd, Flags},
+ gen_server:call(ServerName, Req, Timeout).
+
+%%
+%% ciphers() -> {ok, Ciphers}
+%%
+ciphers() ->
+ gen_server:call(ssl_server, ciphers, infinity).
+
+%%
+%% close(Fd) -> ok
+%%
+close(Fd) ->
+ close_prim(ssl_server, Fd).
+close_prim(ServerName, Fd) ->
+ gen_server:call(ServerName, {close, self(), Fd}, infinity),
+ ok.
+
+%%
+%% connect(LIP, LPort, FIP, FPort, Flags) -> {ok, Fd, ProxyLFPort} |
+%% {error, Reason}
+%%
+connect(LIP, LPort, FIP, FPort, Flags) ->
+ connect(LIP, LPort, FIP, FPort, Flags, infinity).
+connect(LIP, LPort, FIP, FPort, Flags, Timeout) ->
+ connect_prim(ssl_server, LIP, LPort, FIP, FPort, Flags, Timeout).
+
+connect_prim(ServerName, LIP, LPort, FIP, FPort, Flags, Timeout) ->
+ Req = {connect, self(), LIP, LPort, FIP, FPort, Flags},
+ gen_server:call(ServerName, Req, Timeout).
+
+%%
+%% connection_info(Fd) -> {ok, {Protocol, Cipher}} | {error, Reason}
+%%
+connection_info(Fd) ->
+ Req = {connection_info, self(), Fd},
+ gen_server:call(ssl_server, Req, infinity).
+
+%%
+%% listen(IP, LPort, Flags),
+%% listen(IP, LPort, Flags, BackLog) -> {ok, ListenFd, LPort0} |
+%% {error, Reason}
+%%
+listen(IP, LPort, Flags) ->
+ listen(IP, LPort, Flags, ?DEF_BACKLOG).
+listen(IP, LPort, Flags, BackLog) ->
+ listen_prim(ssl_server, IP, LPort, Flags, BackLog).
+listen_prim(ServerName, IP, LPort, Flags, BackLog) ->
+ Req = {listen, self(), IP, LPort, Flags, BackLog},
+ gen_server:call(ServerName, Req, infinity).
+
+%%
+%% peercert(Fd) -> {ok, Cert} | {error, Reason}
+%%
+peercert(Fd) ->
+ Req = {peercert, self(), Fd},
+ gen_server:call(ssl_server, Req, infinity).
+
+%%
+%% peername(Fd) -> {ok, {Address, Port}} | {error, Reason}
+%%
+peername(Fd) ->
+ peername_prim(ssl_server, Fd).
+peername_prim(ServerName, Fd) ->
+ Req = {peername, self(), Fd},
+ gen_server:call(ServerName, Req, infinity).
+
+%%
+%% proxy_join(Fd, LPort) -> ok | {error, Reason}
+%%
+proxy_join(Fd, LPort) ->
+ proxy_join_prim(ssl_server, Fd, LPort).
+proxy_join_prim(ServerName, Fd, LPort) ->
+ Req = {proxy_join, self(), Fd, LPort},
+ gen_server:call(ServerName, Req, infinity).
+
+%%
+%% seed(Data)
+%%
+seed(Data) ->
+ Req = {seed, Data},
+ gen_server:call(ssl_server, Req, infinity).
+
+%%
+%% set_nodelay(Fd, Boolean)
+%%
+setnodelay(Fd, Boolean) ->
+ setnodelay_prim(ssl_server, Fd, Boolean).
+setnodelay_prim(ServerName, Fd, Boolean) ->
+ Req = {setnodelay, self(), Fd, Boolean},
+ gen_server:call(ServerName, Req, infinity).
+
+%%
+%% sockname(Fd) -> {ok, {Address, Port}} | {error, Reason}
+%%
+sockname(Fd) ->
+ sockname_prim(ssl_server, Fd).
+sockname_prim(ServerName, Fd) ->
+ Req = {sockname, self(), Fd},
+ gen_server:call(ServerName, Req, infinity).
+
+%%
+%% version() -> {ok, {CompVsn, LibVsn}}
+%%
+version() ->
+ gen_server:call(ssl_server, version, infinity).
+
+
+enable_debug() ->
+ set_debug(true).
+
+disable_debug() ->
+ set_debug(false).
+
+set_debug(Bool) ->
+ set_debug(Bool, infinity).
+
+set_debug(Bool, Timeout) when is_boolean(Bool) ->
+ Req = {set_debug, Bool, self()},
+ gen_server:call(ssl_server, Req, Timeout).
+
+enable_debugmsg() ->
+ set_debugmsg(true).
+
+disable_debugmsg() ->
+ set_debugmsg(false).
+
+set_debugmsg(Bool) ->
+ set_debugmsg(Bool, infinity).
+
+set_debugmsg(Bool, Timeout) when is_boolean(Bool) ->
+ Req = {set_debugmsg, Bool, self()},
+ gen_server:call(ssl_server, Req, Timeout).
+
+dump() ->
+ dump(infinity).
+
+dump(Timeout) ->
+ Req = {dump, self()},
+ gen_server:call(ssl_server, Req, Timeout).
+
+%%
+%% init
+%%
+init([]) ->
+ Debug = case application:get_env(ssl, edebug) of
+ {ok, true} ->
+ true;
+ _ ->
+ case application:get_env(ssl, debug) of
+ {ok, true} ->
+ true;
+ _ ->
+ os:getenv("ERL_SSL_DEBUG") =/= false
+ end
+ end,
+ ProgDir =
+ case init:get_argument(ssl_portprogram_dir) of
+ {ok, [[D]]} ->
+ D;
+ _ ->
+ find_priv_bin()
+ end,
+ {Program, Flags} = mk_cmd_line("ssl_esock"),
+ Cmd = filename:join(ProgDir, Program) ++ " " ++ Flags,
+ debug1(Debug, " start, Cmd = ~s~n", [Cmd]),
+ case (catch open_port({spawn, Cmd}, [binary, {packet, 4}])) of
+ Port when is_port(Port) ->
+ process_flag(trap_exit, true),
+ receive
+ {Port, {data, Bin}} ->
+ {ProxyLLPort, ProgPid, CompVsn, LibVsn, Ciphers} =
+ decode_msg(Bin, [int16, int32, string, string,
+ string]),
+ debug1(Debug, "port program pid = ~w~n",
+ [ProgPid]),
+ {ok, #st{port = Port,
+ proxylsport = ProxyLLPort,
+ progpid = ProgPid,
+ debug = Debug,
+ compvsn = CompVsn,
+ libvsn = LibVsn,
+ ciphers = Ciphers}};
+ {'EXIT', Port, Reason} ->
+ {stop, Reason}
+ end;
+ {'EXIT', Reason} ->
+ {stop, Reason}
+ end.
+
+%%
+%% transport_accept
+%%
+handle_call({transport_accept, Broker, ListenFd, Flags}, From, St) ->
+ debug(St, "transport_accept: broker = ~w, listenfd = ~w~n",
+ [Broker, ListenFd]),
+ case get_by_fd(ListenFd, St#st.cons) of
+ {ok, {ListenFd, _, _}} ->
+ send_cmd(St#st.port, ?TRANSPORT_ACCEPT, [int32(ListenFd), Flags, 0]),
+ PAccepts = add({ListenFd, Broker, From}, St#st.paccepts),
+ %%
+ %% We reply when we get TRANSPORT_ACCEPT_REP or ASYNC_ACCEPT_ERR
+ %%
+ {noreply, St#st{paccepts = PAccepts}};
+ _Other ->
+ {reply, {error, ebadf}, St}
+ end;
+
+%%
+%% ssl_accept
+%%
+handle_call({ssl_accept, Fd, Flags}, From, St) ->
+ case replace_from_by_fd(Fd, St#st.cons, From) of
+ {ok, _, Cons} = _Rep ->
+ send_cmd(St#st.port, ?SSL_ACCEPT, [int32(Fd), Flags, 0]),
+ %% We reply when we get SSL_ACCEPT_REP or ASYNC_ACCEPT_ERR
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ {reply, {error, ebadf}, St}
+ end;
+
+%%
+%% version
+%%
+handle_call(ciphers, From, St) ->
+ debug(St, "ciphers: from = ~w~n", [From]),
+ {reply, {ok, St#st.ciphers}, St};
+
+%%
+%% connect
+%%
+handle_call({connect, Broker, LIP, LPort, FIP, FPort, Flags}, From, St) ->
+ debug(St, "connect: broker = ~w, ip = ~w, "
+ "sport = ~w~n", [Broker, FIP, FPort]),
+ Port = St#st.port,
+ LIPStr = ip_to_string(LIP),
+ FIPStr = ip_to_string(FIP),
+ IntRef = new_intref(St),
+ send_cmd(Port, ?CONNECT, [int32(IntRef),
+ int16(LPort), LIPStr, 0,
+ int16(FPort), FIPStr, 0,
+ Flags, 0]),
+ Cons = add({{intref, IntRef}, Broker, From}, St#st.cons),
+ %% We reply when we have got CONNECT_SYNC_ERR, or CONNECT_WAIT
+ %% and CONNECT_REP, or CONNECT_ERR.
+ {noreply, St#st{cons = Cons, intref = IntRef}};
+
+%%
+%% connection_info
+%%
+handle_call({connection_info, Broker, Fd}, From, St) ->
+ debug(St, "connection_info: broker = ~w, fd = ~w~n",
+ [Broker, Fd]),
+ case replace_from_by_fd(Fd, St#st.cons, From) of
+ {ok, _, Cons} ->
+ send_cmd(St#st.port, ?GETCONNINFO, [int32(Fd)]),
+ %% We reply when we get GETCONNINFO_REP or GETCONNINFO_ERR.
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ {reply, {error, ebadf}, St}
+ end;
+
+%%
+%% close
+%%
+handle_call({close, Broker, Fd}, _From, St) ->
+ debug(St, "close: broker = ~w, fd = ~w~n",
+ [Broker, Fd]),
+ #st{port = Port, cons = Cons0, paccepts = PAccepts0} = St,
+ case delete_by_fd(Fd, Cons0) of
+ %% Must match Broker pid; fd may be reused already.
+ {ok, {Fd, Broker, _}, Cons} ->
+ send_cmd(Port, ?CLOSE, int32(Fd)),
+ %% If Fd is a listen socket fd, there might be pending
+ %% accepts for that fd.
+ case delete_all_by_fd(Fd, PAccepts0) of
+ {ok, DelAccepts, RemAccepts} ->
+ %% Reply {error, closed} to all pending accepts
+ lists:foreach(fun({_, _, AccFrom}) ->
+ gen_server:reply(AccFrom,
+ {error, closed})
+ end, DelAccepts),
+ {reply, ok,
+ St#st{cons = Cons, paccepts = RemAccepts}};
+ _ ->
+ {reply, ok, St#st{cons = Cons}}
+ end;
+ _ ->
+ {reply, ok, St}
+ end;
+
+%%
+%% listen
+%%
+handle_call({listen, Broker, IP, LPort, Flags, BackLog}, From, St) ->
+ debug(St, "listen: broker = ~w, IP = ~w, "
+ "sport = ~w~n", [Broker, IP, LPort]),
+ Port = St#st.port,
+ IPStr = ip_to_string(IP),
+ IntRef = new_intref(St),
+ send_cmd(Port, ?LISTEN, [int32(IntRef), int16(LPort), IPStr, 0,
+ int16(BackLog), Flags, 0]),
+ Cons = add({{intref, IntRef}, Broker, From}, St#st.cons),
+ %% We reply when we have got LISTEN_REP.
+ {noreply, St#st{cons = Cons, intref = IntRef}};
+
+%%
+%% peercert
+%%
+handle_call({peercert, Broker, Fd}, From, St) ->
+ debug(St, "peercert: broker = ~w, fd = ~w~n",
+ [Broker, Fd]),
+ case replace_from_by_fd(Fd, St#st.cons, From) of
+ {ok, _, Cons} ->
+ send_cmd(St#st.port, ?GETPEERCERT, [int32(Fd)]),
+ %% We reply when we get GETPEERCERT_REP or GETPEERCERT_ERR.
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ {reply, {error, ebadf}, St}
+ end;
+
+
+%%
+%% peername
+%%
+handle_call({peername, Broker, Fd}, From, St) ->
+ debug(St, "peername: broker = ~w, fd = ~w~n",
+ [Broker, Fd]),
+ case replace_from_by_fd(Fd, St#st.cons, From) of
+ {ok, _, Cons} ->
+ send_cmd(St#st.port, ?GETPEERNAME, [int32(Fd)]),
+ %% We reply when we get GETPEERNAME_REP or GETPEERNAME_ERR.
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ {reply, {error, ebadf}, St}
+ end;
+
+%%
+%% proxy join
+%%
+handle_call({proxy_join, Broker, Fd, LPort}, From, St) ->
+ debug(St, "proxy_join: broker = ~w, fd = ~w, "
+ "sport = ~w~n", [Broker, Fd, LPort]),
+ case replace_from_by_fd(Fd, St#st.cons, From) of
+ {ok, _, Cons} ->
+ send_cmd(St#st.port, ?PROXY_JOIN, [int32(Fd),
+ int16(LPort)]),
+ %% We reply when we get PROXY_JOIN_REP, or PROXY_JOIN_ERR.
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ {reply, {error, ebadf}, St}
+ end;
+
+%%
+%% seed
+%%
+handle_call({seed, Data}, _From, St) when is_binary(Data) ->
+ send_cmd(St#st.port, ?SET_SEED, [int32(byte_size(Data)), Data]),
+ {reply, ok, St};
+
+handle_call({seed, Data}, From, St) ->
+ case catch list_to_binary(Data) of
+ {'EXIT', _} ->
+ {reply, {error, edata}, St};
+ Bin ->
+ handle_call({seed, Bin}, From, St)
+ end;
+
+%%
+%% setnodelay
+%%
+handle_call({setnodelay, Broker, Fd, Boolean}, From, St) ->
+ debug(St, "setnodelay: broker = ~w, fd = ~w, "
+ "boolean = ~w~n", [Broker, Fd, Boolean]),
+ case replace_from_by_fd(Fd, St#st.cons, From) of
+ {ok, _, Cons} ->
+ Val = if Boolean == true -> 1; true -> 0 end,
+ send_cmd(St#st.port, ?SET_SOCK_OPT,
+ [int32(Fd), ?SET_TCP_NODELAY, Val]),
+ %% We reply when we get IOCTL_OK or IOCTL_ERR.
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ {reply, {error, ebadf}, St}
+ end;
+
+%%
+%% sockname
+%%
+handle_call({sockname, Broker, Fd}, From, St) ->
+ debug(St, "sockname: broker = ~w, fd = ~w~n",
+ [Broker, Fd]),
+ case replace_from_by_fd(Fd, St#st.cons, From) of
+ {ok, _, Cons} ->
+ send_cmd(St#st.port, ?GETSOCKNAME, [int32(Fd)]),
+ %% We reply when we get GETSOCKNAME_REP or GETSOCKNAME_ERR.
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ {reply, {error, ebadf}, St}
+ end;
+
+%%
+%% version
+%%
+handle_call(version, From, St) ->
+ debug(St, "version: from = ~w~n", [From]),
+ {reply, {ok, {St#st.compvsn, St#st.libvsn}}, St};
+
+%%
+%% dump
+%%
+handle_call({dump, Broker}, _From, St) ->
+ debug(St, "dump: broker = ~w", [Broker]),
+ Port = St#st.port,
+ send_cmd(Port, ?DUMP_CMD, []),
+ {reply, ok, St};
+
+%%
+%% set_debug
+%%
+handle_call({set_debug, Bool, Broker}, _From, St) ->
+ debug(St, "set_debug: broker = ~w", [Broker]),
+ Value = case Bool of
+ true ->
+ 1;
+ false ->
+ 0
+ end,
+ Port = St#st.port,
+ send_cmd(Port, ?DEBUG_CMD, [Value]),
+ {reply, ok, St};
+
+%%
+%% set_debugmsg
+%%
+handle_call({set_debugmsg, Bool, Broker}, _From, St) ->
+ debug(St, "set_debugmsg: broker = ~w", [Broker]),
+ Value = case Bool of
+ true ->
+ 1;
+ false ->
+ 0
+ end,
+ Port = St#st.port,
+ send_cmd(Port, ?DEBUGMSG_CMD, [Value]),
+ {reply, ok, St};
+
+handle_call(Request, _From, St) ->
+ debug(St, "unexpected call: ~w~n", [Request]),
+ Reply = {error, {badcall, Request}},
+ {reply, Reply, St}.
+
+%%
+%% handle_cast(Msg, St)
+%%
+
+
+handle_cast(Msg, St) ->
+ debug(St, "unexpected cast: ~w~n", [Msg]),
+ {noreply, St}.
+
+%%
+%% handle_info(Info, St)
+%%
+
+%% Data from port
+%%
+handle_info({Port, {data, Bin}},
+ #st{cons = StCons, paccepts = Paccepts,
+ port = Port, proxylsport = Proxylsport} = St)
+ when is_binary(Bin) ->
+ %% io:format("++++ ssl_server got from port: ~w~n", [Bin]),
+ <<OpCode:8, _/binary>> = Bin,
+ case OpCode of
+ %%
+ %% transport_accept
+ %%
+ ?TRANSPORT_ACCEPT_ERR when byte_size(Bin) >= 5 ->
+ {ListenFd, Reason} = decode_msg(Bin, [int32, atom]),
+ debug(St, "transport_accept_err: listenfd = ~w, "
+ "reason = ~w~n", [ListenFd, Reason]),
+ case delete_last_by_fd(ListenFd, Paccepts) of
+ {ok, {_, _, From}, PAccepts} ->
+ gen_server:reply(From, {error, Reason}),
+ {noreply, St#st{paccepts = PAccepts}};
+ _Other ->
+ %% Already closed
+ {noreply, St}
+ end;
+ ?TRANSPORT_ACCEPT_REP when byte_size(Bin) >= 9 ->
+ {ListenFd, Fd} = decode_msg(Bin, [int32, int32]),
+ debug(St, "transport_accept_rep: listenfd = ~w, "
+ "fd = ~w~n", [ListenFd, Fd]),
+ case delete_last_by_fd(ListenFd, Paccepts) of
+ {ok, {_, Broker, From}, PAccepts} ->
+ Reply = {ok, Fd, Proxylsport},
+ gen_server:reply(From, Reply),
+ debug(St, "transport_accept_rep: From = ~w\n", [From]),
+ Cons = add({Fd, Broker, From}, StCons),
+ {noreply, St#st{cons = Cons, paccepts = PAccepts}};
+ _Other ->
+ %% Already closed
+ {noreply, St}
+ end;
+
+ %%
+ %% ssl_accept
+ %%
+ ?SSL_ACCEPT_ERR when byte_size(Bin) >= 5 ->
+ {Fd, Reason} = decode_msg(Bin, [int32, atom]),
+ debug(St, "ssl_accept_err: listenfd = ~w, "
+ "reason = ~w~n", [Fd, Reason]),
+ %% JC: remove this?
+ case delete_last_by_fd(Fd, StCons) of
+ {ok, {_, _, From}, Cons} ->
+ gen_server:reply(From, {error, Reason}),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ %% Already closed
+ {noreply, St}
+ end;
+ ?SSL_ACCEPT_REP when byte_size(Bin) >= 5 ->
+ Fd = decode_msg(Bin, [int32]),
+ debug(St, "ssl_accept_rep: Fd = ~w\n", [Fd]),
+ case replace_from_by_fd(Fd, StCons, []) of
+ {ok, {_, _, From}, Cons} ->
+ gen_server:reply(From, ok),
+ {noreply, St#st{cons = Cons}};
+ _ ->
+ {noreply, St}
+ end;
+
+ %%
+ %% connect
+ %%
+ ?CONNECT_SYNC_ERR when byte_size(Bin) >= 5 ->
+ {IntRef, Reason} = decode_msg(Bin, [int32, atom]),
+ debug(St, "connect_sync_err: intref = ~w, "
+ "reason = ~w~n", [IntRef, Reason]),
+ case delete_by_intref(IntRef, StCons) of
+ {ok, {_, _, From}, Cons} ->
+ gen_server:reply(From, {error, Reason}),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ {noreply, St}
+ end;
+ ?CONNECT_WAIT when byte_size(Bin) >= 9 ->
+ {IntRef, Fd} = decode_msg(Bin, [int32, int32]),
+ debug(St, "connect_wait: intref = ~w, "
+ "fd = ~w~n", [IntRef, Fd]),
+ case replace_fd_by_intref(IntRef, StCons, Fd) of
+ {ok, _, Cons} ->
+ %% We reply when we get CONNECT_REP or CONNECT_ERR
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ %% We have a new Fd which must be closed
+ send_cmd(Port, ?CLOSE, int32(Fd)),
+ {noreply, St}
+ end;
+ ?CONNECT_REP when byte_size(Bin) >= 5 ->
+ %% after CONNECT_WAIT
+ Fd = decode_msg(Bin, [int32]),
+ debug(St, "connect_rep: fd = ~w~n", [Fd]),
+ case replace_from_by_fd(Fd, StCons, []) of
+ {ok, {_, _, From}, Cons} ->
+ gen_server:reply(From, {ok, Fd, Proxylsport}),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ {noreply, St}
+ end;
+ ?CONNECT_ERR when byte_size(Bin) >= 5 ->
+ {Fd, Reason} = decode_msg(Bin, [int32, atom]),
+ debug(St, "connect_err: fd = ~w, "
+ "reason = ~w~n", [Fd, Reason]),
+ case delete_by_fd(Fd, StCons) of
+ {ok, {_, _, From}, Cons} ->
+ %% Fd not yet published - hence close ourselves
+ send_cmd(Port, ?CLOSE, int32(Fd)),
+ gen_server:reply(From, {error, Reason}),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ %% Already closed
+ {noreply, St}
+ end;
+
+ %%
+ %% connection_info
+ %%
+ ?GETCONNINFO_REP when byte_size(Bin) >= 5 ->
+ {Fd, Protocol, Cipher} = decode_msg(Bin, [int32, string, string]),
+ debug(St, "connection_info_rep: fd = ~w, "
+ "protcol = ~p, ip = ~p~n", [Fd, Protocol, Cipher]),
+ case replace_from_by_fd(Fd, StCons, []) of
+ {ok, {_, _, From}, Cons} ->
+ gen_server:reply(From, {ok, {protocol_name(Protocol),
+ Cipher}}),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ %% Already closed
+ {noreply, St}
+ end;
+ ?GETCONNINFO_ERR when byte_size(Bin) >= 5 ->
+ {Fd, Reason} = decode_msg(Bin, [int32, atom]),
+ debug(St, "connection_info_err: fd = ~w, "
+ "reason = ~w~n", [Fd, Reason]),
+ case replace_from_by_fd(Fd, StCons, []) of
+ {ok, {_, _, From}, Cons} ->
+ gen_server:reply(From, {error, Reason}),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ %% Already closed
+ {noreply, St}
+ end;
+
+ %%
+ %% listen
+ %%
+ ?LISTEN_SYNC_ERR when byte_size(Bin) >= 5 ->
+ {IntRef, Reason} = decode_msg(Bin, [int32, atom]),
+ debug(St, "listen_sync_err: intref = ~w, "
+ "reason = ~w~n", [IntRef, Reason]),
+ case delete_by_intref(IntRef, StCons) of
+ {ok, {_, _, From}, Cons} ->
+ gen_server:reply(From, {error, Reason}),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ {noreply, St}
+ end;
+ ?LISTEN_REP when byte_size(Bin) >= 11 ->
+ {IntRef, ListenFd, LPort} = decode_msg(Bin, [int32, int32, int16]),
+ debug(St, "listen_rep: intref = ~w, "
+ "listenfd = ~w, sport = ~w~n", [IntRef, ListenFd, LPort]),
+ case replace_fd_from_by_intref(IntRef, StCons, ListenFd, []) of
+ {ok, {_, _, From}, Cons} ->
+ gen_server:reply(From, {ok, ListenFd, LPort}),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ %% ListenFd has to be closed.
+ send_cmd(Port, ?CLOSE, int32(ListenFd)),
+ {noreply, St}
+ end;
+
+ %%
+ %% proxy join
+ %%
+ ?PROXY_JOIN_REP when byte_size(Bin) >= 5 ->
+ Fd = decode_msg(Bin, [int32]),
+ debug(St, "proxy_join_rep: fd = ~w~n",
+ [Fd]),
+ case get_by_fd(Fd, StCons) of
+ {ok, {_, _, From}} ->
+ gen_server:reply(From, ok),
+ {noreply, St};
+ _Other ->
+ %% Already closed
+ {noreply, St}
+ end;
+ ?PROXY_JOIN_ERR when byte_size(Bin) >= 5 ->
+ {Fd, Reason} = decode_msg(Bin, [int32, atom]),
+ debug(St, "proxy_join_rep: fd = ~w, "
+ "reason = ~w~n", [Fd, Reason]),
+ case delete_by_fd(Fd, StCons) of
+ {ok, {_, _, From}, Cons} ->
+ case Reason of
+ enoproxysocket ->
+ send_cmd(Port, ?CLOSE, int32(Fd));
+ _ ->
+ ok
+ %% Must not close Fd since it is published
+ end,
+ gen_server:reply(From, {error, Reason}),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ %% Already closed
+ {noreply, St}
+ end;
+
+ %%
+ %% peername
+ %%
+ ?GETPEERNAME_REP when byte_size(Bin) >= 5 ->
+ {Fd, LPort, IPString} = decode_msg(Bin, [int32, int16, string]),
+ debug(St, "getpeername_rep: fd = ~w, "
+ "sport = ~w, ip = ~p~n", [Fd, LPort, IPString]),
+ case replace_from_by_fd(Fd, StCons, []) of
+ {ok, {_, _, From}, Cons} ->
+ gen_server:reply(From, {ok, {IPString, LPort}}),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ %% Already closed
+ {noreply, St}
+ end;
+ ?GETPEERNAME_ERR when byte_size(Bin) >= 5 ->
+ {Fd, Reason} = decode_msg(Bin, [int32, atom]),
+ debug(St, "getpeername_err: fd = ~w, "
+ "reason = ~w~n", [Fd, Reason]),
+ case replace_from_by_fd(Fd, StCons, []) of
+ {ok, {_, _, From}, Cons} ->
+ gen_server:reply(From, {error, Reason}),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ %% Already closed
+ {noreply, St}
+ end;
+
+ %%
+ %% ioctl
+ %%
+ ?IOCTL_OK when byte_size(Bin) >= 5 ->
+ Fd = decode_msg(Bin, [int32]),
+ debug(St, "ioctl_ok: fd = ~w~n",
+ [Fd]),
+ case replace_from_by_fd(Fd, StCons, []) of
+ {ok, {_, _, From}, Cons} ->
+ gen_server:reply(From, ok),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ %% Already closed
+ {noreply, St}
+ end;
+ ?IOCTL_ERR when byte_size(Bin) >= 5 ->
+ {Fd, Reason} = decode_msg(Bin, [int32, atom]),
+ debug(St, "ioctl_err: fd = ~w, "
+ "reason = ~w~n", [Fd, Reason]),
+ case replace_from_by_fd(Fd, StCons, []) of
+ {ok, {_, _, From}, Cons} ->
+ gen_server:reply(From, {error, Reason}),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ %% Already closed
+ {noreply, St}
+ end;
+
+ %%
+ %% sockname
+ %%
+ ?GETSOCKNAME_REP when byte_size(Bin) >= 5 ->
+ {Fd, LPort, IPString} = decode_msg(Bin, [int32, int16, string]),
+ debug(St, "getsockname_rep: fd = ~w, "
+ "sport = ~w, ip = ~p~n", [Fd, LPort, IPString]),
+ case replace_from_by_fd(Fd, StCons, []) of
+ {ok, {_, _, From}, Cons} ->
+ gen_server:reply(From, {ok, {IPString, LPort}}),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ %% Already closed
+ {noreply, St}
+ end;
+ ?GETSOCKNAME_ERR when byte_size(Bin) >= 5 ->
+ {Fd, Reason} = decode_msg(Bin, [int32, atom]),
+ debug(St, "getsockname_err: fd = ~w, "
+ "reason = ~w~n", [Fd, Reason]),
+ case replace_from_by_fd(Fd, StCons, []) of
+ {ok, {_, _, From}, Cons} ->
+ gen_server:reply(From, {error, Reason}),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ %% Already closed
+ {noreply, St}
+ end;
+
+ %%
+ %% peercert
+ %%
+ ?GETPEERCERT_REP when byte_size(Bin) >= 5 ->
+ {Fd, Cert} = decode_msg(Bin, [int32, bin]),
+ debug(St, "getpeercert_rep: fd = ~w~n", [Fd]),
+ case replace_from_by_fd(Fd, StCons, []) of
+ {ok, {_, _, From}, Cons} ->
+ gen_server:reply(From, {ok, Cert}),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ %% Already closed
+ {noreply, St}
+ end;
+ ?GETPEERCERT_ERR when byte_size(Bin) >= 5 ->
+ {Fd, Reason} = decode_msg(Bin, [int32, atom]),
+ debug(St, "getpeercert_err: fd = ~w, reason = ~w~n",
+ [Fd, Reason]),
+ case replace_from_by_fd(Fd, StCons, []) of
+ {ok, {_, _, From}, Cons} ->
+ gen_server:reply(From, {error, Reason}),
+ {noreply, St#st{cons = Cons}};
+ _Other ->
+ %% Already closed
+ {noreply, St}
+ end
+ end;
+
+%%
+%% EXIT
+%%
+handle_info({'EXIT', Pid, Reason}, St) when is_pid(Pid) ->
+ debug(St, "exit pid = ~w, "
+ "reason = ~w~n", [Pid, Reason]),
+ case delete_by_pid(Pid, St#st.cons) of
+ {ok, {{intref, _}, Pid, _}, Cons} ->
+ {noreply, St#st{cons = Cons}};
+ {ok, {Fd, Pid, _}, Cons} ->
+ send_cmd(St#st.port, ?CLOSE, int32(Fd)),
+ %% If Fd is a listen socket fd, there might be pending
+ %% accepts for that fd.
+ case delete_all_by_fd(Fd, St#st.paccepts) of
+ {ok, DelAccepts, RemAccepts} ->
+ %% Reply {error, closed} to all pending accepts.
+ lists:foreach(fun({_, _, From}) ->
+ gen_server:reply(From,
+ {error, closed})
+ end, DelAccepts),
+ {noreply,
+ St#st{cons = Cons, paccepts = RemAccepts}};
+ _ ->
+ {noreply, St#st{cons = Cons}}
+ end;
+ _ ->
+ case delete_by_pid(Pid, St#st.paccepts) of
+ {ok, {ListenFd, _, _}, PAccepts} ->
+ %% decrement ref count in port program
+ send_cmd(St#st.port, ?NOACCEPT, int32(ListenFd)),
+ {noreply, St#st{paccepts = PAccepts}};
+ _ ->
+ {noreply, St}
+ end
+ end;
+
+%%
+%% 'badsig' means bad message to port. Port program is unaffected.
+%%
+handle_info({'EXIT', Port, badsig}, #st{port = Port} = St) ->
+ debug(St, "badsig!!!~n", []),
+ {noreply, St};
+
+handle_info({'EXIT', Port, Reason}, #st{port = Port} = St) ->
+ {stop, Reason, St};
+
+handle_info(Info, St) ->
+ debug(St, "unexpected info: ~w~n", [Info]),
+ {noreply, St}.
+
+%%
+%% terminate(Reason, St) -> any
+%%
+terminate(_Reason, _St) ->
+ ok.
+
+%%
+%% code_change(OldVsn, St, Extra) -> {ok, NSt}
+%%
+code_change(_OldVsn, St, _Extra) ->
+ {ok, St}.
+
+%%%----------------------------------------------------------------------
+%%% Internal functions
+%%%----------------------------------------------------------------------
+
+%%
+%% Send binary command to sock
+%%
+send_cmd(Port, Cmd, Args) ->
+ Port ! {self(), {command, [Cmd| Args]}}.
+
+%%
+%% add(Descr, Cons) -> NCons
+%%
+add(D, L) ->
+ [D| L].
+
+%%
+%% get_by_fd(Fd, Cons) -> {ok, Descr} | not_found
+%%
+get_by_fd(Fd, Cons) ->
+ get_by_pos(Fd, 1, Cons).
+
+%%
+%% delete_by_fd(Fd, Cons) -> {ok, OldDesc, NewCons} | not_found.
+%%
+delete_by_fd(Fd, Cons) ->
+ delete_by_pos(Fd, 1, Cons).
+
+%%
+%% delete_all_by_fd(Fd, Cons) -> {ok, DelCons, RemCons} | not_found.
+%%
+delete_all_by_fd(Fd, Cons) ->
+ delete_all_by_pos(Fd, 1, Cons).
+
+%%
+%% delete_by_intref(IntRef, Cons) -> {ok, OldDesc, NewCons} | not_found.
+%%
+delete_by_intref(IntRef, Cons) ->
+ delete_by_pos({intref, IntRef}, 1, Cons).
+
+%%
+%% delete_by_pid(Pid, Cons) -> {ok, OldDesc, NewCons} | not_found.
+%%
+delete_by_pid(Pid, Cons) ->
+ delete_by_pos(Pid, 2, Cons).
+
+%%
+%% delete_last_by_fd(Fd, Cons) -> {ok, OldDesc, NCons} | not_found
+%%
+delete_last_by_fd(Fd, Cons) ->
+ case dlbf(Fd, Cons) of
+ {X, L} ->
+ {ok, X, L};
+ _Other ->
+ not_found
+ end.
+
+dlbf(Fd, [H]) ->
+ last_elem(Fd, H, []);
+dlbf(Fd, [H|T]) ->
+ case dlbf(Fd, T) of
+ {X, L} ->
+ {X, [H|L]};
+ L ->
+ last_elem(Fd, H, L)
+ end;
+dlbf(_Fd, []) ->
+ [].
+
+last_elem(Fd, H, L) when element(1, H) == Fd ->
+ {H, L};
+last_elem(_, H, L) ->
+ [H|L].
+
+
+%%
+%% replace_from_by_fd(Fd, Cons, From) -> {ok, OldDesc, NewList} | not_found
+%%
+replace_from_by_fd(Fd, Cons, From) ->
+ replace_posn_by_pos(Fd, 1, Cons, [{From, 3}]).
+
+%%
+%% replace_fd_by_intref(IntRef, Cons, Fd) -> {ok, OldDesc, NewList} | not_f.
+%%
+replace_fd_by_intref(IntRef, Cons, Fd) ->
+ replace_posn_by_pos({intref, IntRef}, 1, Cons, [{Fd, 1}]).
+
+%%
+%% replace_fd_from_by_intref(IntRef, Cons, NFd, From) ->
+%% {ok, OldDesc, NewList} | not_found
+%%
+replace_fd_from_by_intref(IntRef, Cons, NFd, From) ->
+ replace_posn_by_pos({intref, IntRef}, 1, Cons, [{NFd, 1}, {From, 3}]).
+
+
+%%
+%% All *_by_pos functions
+%%
+
+get_by_pos(Key, Pos, [H|_]) when element(Pos, H) == Key ->
+ {ok, H};
+get_by_pos(Key, Pos, [_|T]) ->
+ get_by_pos(Key, Pos, T);
+get_by_pos(_, _, []) ->
+ not_found.
+
+delete_by_pos(Key, Pos, Cons) ->
+ case delete_by_pos1(Key, Pos, {not_found, Cons}) of
+ {not_found, _} ->
+ not_found;
+ {ODesc, NCons} ->
+ {ok, ODesc, NCons}
+ end.
+delete_by_pos1(Key, Pos, {_R, [H|T]}) when element(Pos, H) == Key ->
+ {H, T};
+delete_by_pos1(Key, Pos, {R, [H|T]}) ->
+ {R0, T0} = delete_by_pos1(Key, Pos, {R, T}),
+ {R0, [H| T0]};
+delete_by_pos1(_, _, {R, []}) ->
+ {R, []}.
+
+delete_all_by_pos(Key, Pos, Cons) ->
+ case lists:foldl(fun(H, {Ds, Rs}) when element(Pos, H) == Key ->
+ {[H|Ds], Rs};
+ (H, {Ds, Rs}) ->
+ {Ds, [H|Rs]}
+ end, {[], []}, Cons) of
+ {[], _} ->
+ not_found;
+ {DelCons, RemCons} ->
+ {ok, DelCons, RemCons}
+ end.
+
+replace_posn_by_pos(Key, Pos, Cons, Repls) ->
+ replace_posn_by_pos1(Key, Pos, Cons, Repls, []).
+
+replace_posn_by_pos1(Key, Pos, [H0| T], Repls, Acc)
+ when element(Pos, H0) =:= Key ->
+ H = lists:foldl(fun({Val, VPos}, Tuple) ->
+ setelement(VPos, Tuple, Val)
+ end, H0, Repls),
+ {ok, H0, lists:reverse(Acc, [H| T])};
+replace_posn_by_pos1(Key, Pos, [H|T], Repls, Acc) ->
+ replace_posn_by_pos1(Key, Pos, T, Repls, [H| Acc]);
+replace_posn_by_pos1(_, _, [], _, _) ->
+ not_found.
+
+%%
+%% Binary/integer conversions
+%%
+int16(I) ->
+ %%[(I bsr 8) band 255, I band 255].
+ <<I:16>>.
+
+int32(I) ->
+ %% [(I bsr 24) band 255,
+ %% (I bsr 16) band 255,
+ %% (I bsr 8) band 255,
+ %% I band 255].
+ <<I:32>>.
+
+%% decode_msg(Bin, Format) -> Tuple | integer() | atom() | string() |
+%% list of binaries()
+%%
+%% Decode message from binary
+%% Format = [spec()]
+%% spec() = int16 | int32 | string | atom | bin | bins
+%%
+%% Notice: The first byte (op code) of the binary message is removed.
+%% Notice: bins returns a *list* of binaries.
+%%
+decode_msg(<<_, Bin/binary>>, Format) ->
+ Dec = dec(Format, Bin),
+ case Dec of
+ [Dec1] -> Dec1;
+ _ -> list_to_tuple(Dec)
+ end.
+
+dec([], _) ->
+ [];
+dec([int16| F], <<N:16, Bin/binary>>) ->
+ [N| dec(F, Bin)];
+dec([int32| F], <<N:32, Bin/binary>>) ->
+ [N| dec(F, Bin)];
+dec([string| F], Bin0) ->
+ {Cs, Bin1} = dec_string(Bin0),
+ [Cs| dec(F, Bin1)];
+dec([atom|F], Bin0) ->
+ {Cs, Bin1} = dec_string(Bin0),
+ [list_to_atom(Cs)| dec(F, Bin1)];
+
+dec([bin|F], Bin) ->
+ {Bin1, Bin2} = dec_bin(Bin),
+ [Bin1| dec(F, Bin2)].
+
+%% NOTE: This clause is not actually used yet.
+%% dec([bins|F], <<N:32, Bin0/binary>>) ->
+%% {Bins, Bin1} = dec_bins(N, Bin0),
+%% [Bins| dec(F, Bin1)].
+
+dec_string(Bin) ->
+ dec_string(Bin, []).
+
+dec_string(<<0, Bin/binary>>, RCs) ->
+ {lists:reverse(RCs), Bin};
+dec_string(<<C, Bin/binary>>, RCs) ->
+ dec_string(Bin, [C| RCs]).
+
+dec_bin(<<L:32, Bin0/binary>>) ->
+ <<Bin1:L/binary, Bin2/binary>> = Bin0,
+ {Bin1, Bin2}.
+
+%% dec_bins(N, Bin) ->
+%% dec_bins(N, Bin, []).
+
+%% dec_bins(0, Bin, Acc) ->
+%% {lists:reverse(Acc), Bin};
+%% dec_bins(N, Bin0, Acc) when N > 0 ->
+%% {Bin1, Bin2} = dec_bin(Bin0),
+%% dec_bins(N - 1, Bin2, [Bin1| Acc]).
+
+%%
+%% new_intref
+%%
+new_intref(St) ->
+ (St#st.intref + 1) band 16#ffffffff.
+
+%%
+%% {Program, Flags} = mk_cmd_line(DefaultProgram)
+%%
+mk_cmd_line(Default) ->
+ {port_program(Default),
+ lists:flatten([debug_flag(), " ", debug_port_flag(), " ",
+ debugdir_flag(), " ",
+ msgdebug_flag(), " ", proxylsport_flag(), " ",
+ proxybacklog_flag(), " ", ephemeral_rsa_flag(), " ",
+ ephemeral_dh_flag(), " ",
+ protocol_version_flag(), " "])}.
+
+port_program(Default) ->
+ case application:get_env(ssl, port_program) of
+ {ok, Program} when is_list(Program) ->
+ Program;
+ _Other ->
+ Default
+ end.
+
+%%
+%% As this server may be started by the distribution, it is not safe to assume
+%% a working code server, neither a working file server.
+%% I try to utilize the most primitive interfaces available to determine
+%% the directory of the port_program.
+%%
+find_priv_bin() ->
+ PrivDir = case (catch code:priv_dir(ssl)) of
+ {'EXIT', _} ->
+ %% Code server probably not startet yet
+ {ok, P} = erl_prim_loader:get_path(),
+ ModuleFile = atom_to_list(?MODULE) ++ extension(),
+ Pd = (catch lists:foldl
+ (fun(X,Acc) ->
+ M = filename:join([X, ModuleFile]),
+ %% The file server probably not started
+ %% either, has to use raw interface.
+ case file:raw_read_file_info(M) of
+ {ok,_} ->
+ %% Found our own module in the
+ %% path, lets bail out with
+ %% the priv_dir of this directory
+ Y = filename:split(X),
+ throw(filename:join
+ (lists:sublist
+ (Y,length(Y) - 1)
+ ++ ["priv"]));
+ _ ->
+ Acc
+ end
+ end,
+ false,P)),
+ case Pd of
+ false ->
+ exit(ssl_priv_dir_indeterminate);
+ _ ->
+ Pd
+ end;
+ Dir ->
+ Dir
+ end,
+ filename:join([PrivDir, "bin"]).
+
+extension() ->
+ %% erlang:info(machine) returns machine name as text in all uppercase
+ "." ++ string:to_lower(erlang:system_info(machine)).
+
+debug_flag() ->
+ case os:getenv("ERL_SSL_DEBUG") of
+ false ->
+ get_env(debug, "-d");
+ _ ->
+ "-d"
+ end.
+
+debug_port_flag() ->
+ case os:getenv("ERL_SSL_DEBUGPORT") of
+ false ->
+ get_env(debug, "-d");
+ _ ->
+ "-d"
+ end.
+
+msgdebug_flag() ->
+ case os:getenv("ERL_SSL_MSGDEBUG") of
+ false ->
+ get_env(msgdebug, "-dm");
+ _ ->
+ "-dm"
+ end.
+
+proxylsport_flag() ->
+ case application:get_env(ssl, proxylsport) of
+ {ok, PortNum} ->
+ "-pp " ++ integer_to_list(PortNum);
+ _Other ->
+ ""
+ end.
+
+proxybacklog_flag() ->
+ case application:get_env(ssl, proxylsbacklog) of
+ {ok, Size} ->
+ "-pb " ++ integer_to_list(Size);
+ _Other ->
+ ""
+ end.
+
+debugdir_flag() ->
+ case os:getenv("ERL_SSL_DEBUG") of
+ false ->
+ case application:get_env(ssl, debugdir) of
+ {ok, Dir} when is_list(Dir) ->
+ "-dd " ++ Dir;
+ _Other ->
+ ""
+ end;
+ _ ->
+ "-dd ./"
+ end.
+
+ephemeral_rsa_flag() ->
+ case application:get_env(ssl, ephemeral_rsa) of
+ {ok, true} ->
+ "-ersa ";
+ _Other ->
+ ""
+ end.
+
+ephemeral_dh_flag() ->
+ case application:get_env(ssl, ephemeral_dh) of
+ {ok, true} ->
+ "-edh ";
+ _Other ->
+ ""
+ end.
+
+protocol_version_flag() ->
+ case application:get_env(ssl, protocol_version) of
+ {ok, []} ->
+ "";
+ {ok, Vsns} when is_list(Vsns) ->
+ case transform_vsns(Vsns) of
+ N when (N > 0) ->
+ "-pv " ++ integer_to_list(N);
+ _ ->
+ ""
+ end;
+ _Other ->
+ ""
+ end.
+
+transform_vsns(Vsns) ->
+ transform_vsns(Vsns, 0).
+
+transform_vsns([sslv2| Vsns], I) ->
+ transform_vsns(Vsns, I bor ?SSLv2);
+transform_vsns([sslv3| Vsns], I) ->
+ transform_vsns(Vsns, I bor ?SSLv3);
+transform_vsns([tlsv1| Vsns], I) ->
+ transform_vsns(Vsns, I bor ?TLSv1);
+transform_vsns([_ | Vsns], I) ->
+ transform_vsns(Vsns, I);
+transform_vsns([], I) ->
+ I.
+
+protocol_name("SSLv2") -> sslv2;
+protocol_name("SSLv3") -> sslv3;
+protocol_name("TLSv1") -> tlsv1.
+
+get_env(Key, Val) ->
+ case application:get_env(ssl, Key) of
+ {ok, true} ->
+ Val;
+ _Other ->
+ ""
+ end.
+
+ip_to_string({A,B,C,D}) ->
+ [integer_to_list(A),$.,integer_to_list(B),$.,
+ integer_to_list(C),$.,integer_to_list(D)].
+
+debug(St, Format, Args) ->
+ debug1(St#st.debug, Format, Args).
+
+debug1(true, Format0, Args) ->
+ {_MS, S, MiS} = erlang:now(),
+ Secs = S rem 100,
+ MiSecs = MiS div 1000,
+ Format = "++++ ~3..0w:~3..0w ssl_server (~w): " ++ Format0,
+ io:format(Format, [Secs, MiSecs, self()| Args]);
+debug1(_, _, _) ->
+ ok.
diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl
new file mode 100644
index 0000000000..bcb10daf69
--- /dev/null
+++ b/lib/ssl/src/ssl_session.erl
@@ -0,0 +1,172 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Handles ssl sessions
+%%----------------------------------------------------------------------
+
+-module(ssl_session).
+
+-include("ssl_handshake.hrl").
+-include("ssl_internal.hrl").
+
+%% Internal application API
+-export([is_new/2, id/3, id/6, valid_session/2]).
+
+-define(GEN_UNIQUE_ID_MAX_TRIES, 10).
+
+%%--------------------------------------------------------------------
+%% Function: is_new(ClientSuggestedId, ServerDecidedId) -> true | false
+%%
+%% ClientSuggestedId = binary()
+%% ServerDecidedId = binary()
+%%
+%% Description: Checks if the session id decided by the server is a
+%% new or resumed sesion id.
+%%--------------------------------------------------------------------
+is_new(<<>>, _) ->
+ true;
+is_new(SessionId, SessionId) ->
+ false;
+is_new(_, _) ->
+ true.
+
+%%--------------------------------------------------------------------
+%% Function: id(ClientInfo, Cache, CacheCb) -> SessionId
+%%
+%% ClientInfo = {HostIP, Port, SslOpts}
+%% HostIP = ipadress()
+%% Port = integer()
+%% CacheCb = atom()
+%% SessionId = binary()
+%%
+%% Description: Should be called by the client side to get an id
+%% for the client hello message.
+%%--------------------------------------------------------------------
+id(ClientInfo, Cache, CacheCb) ->
+ case select_session(ClientInfo, Cache, CacheCb) of
+ no_session ->
+ <<>>;
+ SessionId ->
+ SessionId
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: id(Port, SuggestedSessionId, ReuseFun, CacheCb,
+%% SecondLifeTime) -> SessionId
+%%
+%% Port = integer()
+%% SuggestedSessionId = SessionId = binary()
+%% ReuseFun = fun(SessionId, PeerCert, Compression, CipherSuite) ->
+%% true | false
+%% CacheCb = atom()
+%%
+%% Description: Should be called by the server side to get an id
+%% for the server hello message.
+%%--------------------------------------------------------------------
+id(Port, <<>>, _, Cache, CacheCb, _) ->
+ new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb);
+
+id(Port, SuggestedSessionId, #ssl_options{reuse_sessions = ReuseEnabled,
+ reuse_session = ReuseFun},
+ Cache, CacheCb, SecondLifeTime) ->
+ case is_resumable(SuggestedSessionId, Port, ReuseEnabled,
+ ReuseFun, Cache, CacheCb, SecondLifeTime) of
+ true ->
+ SuggestedSessionId;
+ false ->
+ new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb)
+ end.
+%%--------------------------------------------------------------------
+%% Function: valid_session(Session, LifeTime) -> true | false
+%%
+%% Session = #session{}
+%% LifeTime = integer() - seconds
+%%
+%% Description: Check that the session has not expired
+%%--------------------------------------------------------------------
+valid_session(#session{time_stamp = TimeStamp}, LifeTime) ->
+ Now = calendar:datetime_to_gregorian_seconds({date(), time()}),
+ Now - TimeStamp < LifeTime.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+select_session({HostIP, Port, SslOpts}, Cache, CacheCb) ->
+ Sessions = CacheCb:select_session(Cache, {HostIP, Port}),
+ select_session(Sessions, SslOpts).
+
+select_session([], _) ->
+ no_session;
+
+select_session(Sessions, #ssl_options{ciphers = Ciphers,
+ reuse_sessions = ReuseSession}) ->
+ IsResumable =
+ fun(Session) ->
+ ReuseSession andalso (Session#session.is_resumable) andalso
+ lists:member(Session#session.cipher_suite, Ciphers)
+ end,
+ case [Id || [Id, Session] <- Sessions, IsResumable(Session)] of
+ [] ->
+ no_session;
+ List ->
+ hd(List)
+ end.
+
+%% If we can not generate a not allready in use session ID in
+%% ?GEN_UNIQUE_ID_MAX_TRIES we make the new session uncacheable The
+%% value of ?GEN_UNIQUE_ID_MAX_TRIES is stolen from open SSL which
+%% states : "If we can not find a session id in
+%% ?GEN_UNIQUE_ID_MAX_TRIES either the RAND code is broken or someone
+%% is trying to open roughly very close to 2^128 (or 2^256) SSL
+%% sessions to our server"
+new_id(_, 0, _, _) ->
+ <<>>;
+new_id(Port, Tries, Cache, CacheCb) ->
+ Id = crypto:rand_bytes(?NUM_OF_SESSION_ID_BYTES),
+ case CacheCb:lookup(Cache, {Port, Id}) of
+ undefined ->
+ Now = calendar:datetime_to_gregorian_seconds({date(), time()}),
+ %% New sessions can not be set to resumable
+ %% until handshake is compleate and the
+ %% other session values are set.
+ CacheCb:update(Cache, {Port, Id}, #session{session_id = Id,
+ is_resumable = false,
+ time_stamp = Now}),
+ Id;
+ _ ->
+ new_id(Port, Tries - 1, Cache, CacheCb)
+ end.
+
+is_resumable(SuggestedSessionId, Port, ReuseEnabled, ReuseFun, Cache,
+ CacheCb, SecondLifeTime) ->
+ case CacheCb:lookup(Cache, {Port, SuggestedSessionId}) of
+ #session{cipher_suite = CipherSuite,
+ compression_method = Compression,
+ is_resumable = Is_resumable,
+ peer_certificate = PeerCert} = Session ->
+ ReuseEnabled
+ andalso Is_resumable
+ andalso valid_session(Session, SecondLifeTime)
+ andalso ReuseFun(SuggestedSessionId, PeerCert,
+ Compression, CipherSuite);
+ undefined ->
+ false
+ end.
diff --git a/lib/ssl/src/ssl_session_cache.erl b/lib/ssl/src/ssl_session_cache.erl
new file mode 100644
index 0000000000..4a60892235
--- /dev/null
+++ b/lib/ssl/src/ssl_session_cache.erl
@@ -0,0 +1,124 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(ssl_session_cache).
+
+-behaviour(ssl_session_cache_api).
+
+-export([init/0, terminate/1, lookup/2, update/3, delete/2, foldl/3,
+ select_session/2]).
+
+%%--------------------------------------------------------------------
+%% Function: init() -> Cache
+%%
+%% Cache - Reference to the cash (opaque)
+%%
+%% Description: Return table reference. Called by ssl_manager process.
+%%--------------------------------------------------------------------
+init() ->
+ ets:new(cache_name(), [set, protected]).
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Cache) ->
+%%
+%% Cache - as returned by create/0
+%%
+%% Description: Handles cache table at termination of ssl manager.
+%%--------------------------------------------------------------------
+terminate(Cache) ->
+ ets:delete(Cache).
+
+%%--------------------------------------------------------------------
+%% Function: lookup(Cache, Key) -> Session | undefined
+%% Cache - as returned by create/0
+%% Session = #session{}
+%%
+%% Description: Looks up a cach entry. Should be callable from any
+%% process.
+%%--------------------------------------------------------------------
+lookup(Cache, Key) ->
+ case ets:lookup(Cache, Key) of
+ [{Key, Session}] ->
+ Session;
+ [] ->
+ undefined
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: update(Cache, Key, Session) -> _
+%% Cache - as returned by create/0
+%% Session = #session{}
+%%
+%% Description: Caches a new session or updates a already cached one.
+%% Will only be called from the ssl_manager process.
+%%--------------------------------------------------------------------
+update(Cache, Key, Session) ->
+ ets:insert(Cache, {Key, Session}).
+
+%%--------------------------------------------------------------------
+%% Function: delete(Cache, HostIP, Port, Id) -> _
+%% Cache - as returned by create/0
+%% HostIP = Host = string() | ipadress()
+%% Port = integer()
+%% Id =
+%%
+%% Description: Delets a cache entry.
+%% Will only be called from the ssl_manager process.
+%%--------------------------------------------------------------------
+delete(Cache, Key) ->
+ ets:delete(Cache, Key).
+
+%%--------------------------------------------------------------------
+%% Function: foldl(Fun, Acc0, Cache) -> Acc
+%%
+%% Fun - fun()
+%% Acc0 - term()
+%% Cache - cache_ref()
+%%
+%%
+%% Description: Calls Fun(Elem, AccIn) on successive elements of the
+%% cache, starting with AccIn == Acc0. Fun/2 must return a new
+%% accumulator which is passed to the next call. The function returns
+%% the final value of the accumulator. Acc0 is returned if the cache is
+%% empty.
+%% Should be callable from any process
+%%--------------------------------------------------------------------
+foldl(Fun, Acc0, Cache) ->
+ ets:foldl(Fun, Acc0, Cache).
+
+%%--------------------------------------------------------------------
+%% Function: select_session(Cache, PartialKey) -> [Sessions]
+%%
+%% Cache - as returned by create/0
+%% PartialKey - opaque Key = {PartialKey, SessionId}
+%%
+%% Description: Selects a session that could be reused. Should be callable
+%% from any process.
+%%--------------------------------------------------------------------
+select_session(Cache, PartialKey) ->
+ ets:select(Cache,
+ [{{{PartialKey,'$1'}, '$2'},[],['$$']}]).
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+cache_name() ->
+ ssl_otp_session_cache.
diff --git a/lib/ssl/src/ssl_session_cache_api.erl b/lib/ssl/src/ssl_session_cache_api.erl
new file mode 100644
index 0000000000..d2e846e9fd
--- /dev/null
+++ b/lib/ssl/src/ssl_session_cache_api.erl
@@ -0,0 +1,37 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(ssl_session_cache_api).
+
+-export([behaviour_info/1]).
+
+behaviour_info(callbacks) ->
+ [
+ {init, 0},
+ {terminate, 1},
+ {lookup, 2},
+ {update, 3},
+ {delete, 2},
+ {foldl, 3},
+ {select_session, 2}
+ ];
+behaviour_info(_) ->
+ undefined.
diff --git a/lib/ssl/src/ssl_ssl2.erl b/lib/ssl/src/ssl_ssl2.erl
new file mode 100644
index 0000000000..b1005b1acb
--- /dev/null
+++ b/lib/ssl/src/ssl_ssl2.erl
@@ -0,0 +1,37 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Handles sslv2 hello as clients supporting sslv2 and higher
+%% will send a sslv2 hello.
+%%----------------------------------------------------------------------
+
+-module(ssl_ssl2).
+
+-export([client_random/2]).
+
+client_random(ChallengeData, 32) ->
+ ChallengeData;
+client_random(ChallengeData, N) when N > 32 ->
+ <<NewChallengeData:32/binary, _/binary>> = ChallengeData,
+ NewChallengeData;
+client_random(ChallengeData, N) ->
+ Pad = list_to_binary(lists:duplicate(N, 0)),
+ <<Pad/binary, ChallengeData/binary>>.
diff --git a/lib/ssl/src/ssl_ssl3.erl b/lib/ssl/src/ssl_ssl3.erl
new file mode 100644
index 0000000000..ab29ac64df
--- /dev/null
+++ b/lib/ssl/src/ssl_ssl3.erl
@@ -0,0 +1,286 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Handles sslv3 encryption.
+%%----------------------------------------------------------------------
+
+-module(ssl_ssl3).
+
+-include("ssl_cipher.hrl").
+-include("ssl_debug.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_record.hrl"). % MD5 and SHA
+
+-export([master_secret/3, finished/3, certificate_verify/3,
+ mac_hash/6, setup_keys/8,
+ suites/0]).
+-compile(inline).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+
+master_secret(PremasterSecret, ClientRandom, ServerRandom) ->
+ ?DBG_HEX(PremasterSecret),
+ ?DBG_HEX(ClientRandom),
+ ?DBG_HEX(ServerRandom),
+ %% draft-ietf-tls-ssl-version3-00 - 6.2.2
+ %% key_block =
+ %% MD5(master_secret + SHA(`A' + master_secret +
+ %% ServerHello.random +
+ %% ClientHello.random)) +
+ %% MD5(master_secret + SHA(`BB' + master_secret +
+ %% ServerHello.random +
+ %% ClientHello.random)) +
+ %% MD5(master_secret + SHA(`CCC' + master_secret +
+ %% ServerHello.random +
+ %% ClientHello.random)) + [...];
+ B = generate_keyblock(PremasterSecret, ClientRandom, ServerRandom, 48),
+ ?DBG_HEX(B),
+ B.
+
+finished(Role, MasterSecret, {MD5Hash, SHAHash}) ->
+ %% draft-ietf-tls-ssl-version3-00 - 5.6.9 Finished
+ %% struct {
+ %% opaque md5_hash[16];
+ %% opaque sha_hash[20];
+ %% } Finished;
+ %%
+ %% md5_hash MD5(master_secret + pad2 +
+ %% MD5(handshake_messages + Sender +
+ %% master_secret + pad1));
+ %% sha_hash SHA(master_secret + pad2 +
+ %% SHA(handshake_messages + Sender +
+ %% master_secret + pad1));
+ Sender = get_sender(Role),
+ MD5 = handshake_hash(?MD5, MasterSecret, Sender, MD5Hash),
+ SHA = handshake_hash(?SHA, MasterSecret, Sender, SHAHash),
+ <<MD5/binary, SHA/binary>>.
+
+certificate_verify(Algorithm, MasterSecret, {MD5Hash, SHAHash})
+ when Algorithm == rsa; Algorithm == dh_rsa; Algorithm == dhe_rsa ->
+ %% md5_hash
+ %% MD5(master_secret + pad_2 +
+ %% MD5(handshake_messages + master_secret + pad_1));
+ %% sha_hash
+ %% SHA(master_secret + pad_2 +
+ %% SHA(handshake_messages + master_secret + pad_1));
+
+ MD5 = handshake_hash(?MD5, MasterSecret, undefined, MD5Hash),
+ SHA = handshake_hash(?SHA, MasterSecret, undefined, SHAHash),
+ <<MD5/binary, SHA/binary>>;
+
+certificate_verify(Algorithm, MasterSecret, {_, SHAHash})
+ when Algorithm == dh_dss; Algorithm == dhe_dss ->
+ %% sha_hash
+ %% SHA(master_secret + pad_2 +
+ %% SHA(handshake_messages + master_secret + pad_1));
+ handshake_hash(?SHA, MasterSecret, undefined, SHAHash).
+
+mac_hash(Method, Mac_write_secret, Seq_num, Type, Length, Fragment) ->
+ %% draft-ietf-tls-ssl-version3-00 - 5.2.3.1
+ %% hash(MAC_write_secret + pad_2 +
+ %% hash(MAC_write_secret + pad_1 + seq_num +
+ %% SSLCompressed.type + SSLCompressed.length +
+ %% SSLCompressed.fragment));
+ case Method of
+ ?NULL -> ok;
+ _ ->
+ ?DBG_HEX(Mac_write_secret),
+ ?DBG_HEX(hash(Method, Fragment)),
+ ok
+ end,
+ Mac = mac_hash(Method, Mac_write_secret,
+ [<<?UINT64(Seq_num), ?BYTE(Type),
+ ?UINT16(Length)>>, Fragment]),
+ ?DBG_HEX(Mac),
+ Mac.
+
+setup_keys(Exportable, MasterSecret, ServerRandom, ClientRandom,
+ HS, KML, _EKML, IVS)
+ when Exportable == no_export; Exportable == ignore ->
+ KeyBlock = generate_keyblock(MasterSecret, ServerRandom, ClientRandom,
+ 2*(HS+KML+IVS)),
+ %% draft-ietf-tls-ssl-version3-00 - 6.2.2
+ %% The key_block is partitioned as follows.
+ %% client_write_MAC_secret[CipherSpec.hash_size]
+ %% server_write_MAC_secret[CipherSpec.hash_size]
+ %% client_write_key[CipherSpec.key_material]
+ %% server_write_key[CipherSpec.key_material]
+ %% client_write_IV[CipherSpec.IV_size] /* non-export ciphers */
+ %% server_write_IV[CipherSpec.IV_size] /* non-export ciphers */
+ <<ClientWriteMacSecret:HS/binary, ServerWriteMacSecret:HS/binary,
+ ClientWriteKey:KML/binary, ServerWriteKey:KML/binary,
+ ClientIV:IVS/binary, ServerIV:IVS/binary>> = KeyBlock,
+ ?DBG_HEX(ClientWriteMacSecret),
+ ?DBG_HEX(ServerWriteMacSecret),
+ ?DBG_HEX(ClientWriteKey),
+ ?DBG_HEX(ServerWriteKey),
+ ?DBG_HEX(ClientIV),
+ ?DBG_HEX(ServerIV),
+ {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
+ ServerWriteKey, ClientIV, ServerIV};
+
+setup_keys(export, MasterSecret, ServerRandom, ClientRandom,
+ HS, KML, EKML, IVS) ->
+ KeyBlock = generate_keyblock(MasterSecret, ServerRandom, ClientRandom,
+ 2*(HS+KML)),
+ %% draft-ietf-tls-ssl-version3-00 - 6.2.2
+ %% Exportable encryption algorithms (for which
+ %% CipherSpec.is_exportable is true) require additional processing as
+ %% follows to derive their final write keys:
+
+ %% final_client_write_key = MD5(client_write_key +
+ %% ClientHello.random +
+ %% ServerHello.random);
+ %% final_server_write_key = MD5(server_write_key +
+ %% ServerHello.random +
+ %% ClientHello.random);
+
+ %% Exportable encryption algorithms derive their IVs from the random
+ %% messages:
+ %% client_write_IV = MD5(ClientHello.random + ServerHello.random);
+ %% server_write_IV = MD5(ServerHello.random + ClientHello.random);
+
+ <<ClientWriteMacSecret:HS/binary, ServerWriteMacSecret:HS/binary,
+ ClientWriteKey:KML/binary, ServerWriteKey:KML/binary>> = KeyBlock,
+ <<ClientIV:IVS/binary, _/binary>> =
+ hash(?MD5, [ClientRandom, ServerRandom]),
+ <<ServerIV:IVS/binary, _/binary>> =
+ hash(?MD5, [ServerRandom, ClientRandom]),
+ <<FinalClientWriteKey:EKML/binary, _/binary>> =
+ hash(?MD5, [ClientWriteKey, ClientRandom, ServerRandom]),
+ <<FinalServerWriteKey:EKML/binary, _/binary>> =
+ hash(?MD5, [ServerWriteKey, ServerRandom, ClientRandom]),
+ ?DBG_HEX(ClientWriteMacSecret),
+ ?DBG_HEX(ServerWriteMacSecret),
+ ?DBG_HEX(FinalClientWriteKey),
+ ?DBG_HEX(FinalServerWriteKey),
+ ?DBG_HEX(ClientIV),
+ ?DBG_HEX(ServerIV),
+ {ClientWriteMacSecret, ServerWriteMacSecret, FinalClientWriteKey,
+ FinalServerWriteKey, ClientIV, ServerIV}.
+
+suites() ->
+ [
+ %% TODO: uncomment when supported
+ %% ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
+ %% ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
+ %% TODO: Funkar inte, borde: ?TLS_RSA_WITH_AES_256_CBC_SHA,
+ %% ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ %% ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+ %% ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+ %% ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
+ ?TLS_RSA_WITH_AES_128_CBC_SHA,
+ %%?TLS_DHE_DSS_WITH_RC4_128_SHA, TODO: Support this?
+ %% ?TLS_RSA_WITH_IDEA_CBC_SHA, Not supported: in later openssl version than OTP requires
+
+ ?TLS_RSA_WITH_RC4_128_SHA,
+ ?TLS_RSA_WITH_RC4_128_MD5,
+ %%?TLS_RSA_EXPORT1024_WITH_RC4_56_MD5,
+ %%?TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5,
+ %%?TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA,
+ %%?TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA,
+ %%?TLS_RSA_EXPORT1024_WITH_RC4_56_SHA,
+ %%?TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA,
+ %%?TLS_DHE_DSS_WITH_RC4_128_SHA,
+
+ ?TLS_RSA_WITH_DES_CBC_SHA
+ %% ?TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA,
+ %% ?TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA,
+ %% ?TLS_RSA_EXPORT_WITH_DES40_CBC_SHA,
+ %%?TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5,
+ %%?TLS_RSA_EXPORT_WITH_RC4_40_MD5
+ ].
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+hash(?MD5, Data) ->
+ crypto:md5(Data);
+hash(?SHA, Data) ->
+ crypto:sha(Data).
+
+hash_update(?MD5, Context, Data) ->
+ crypto:md5_update(Context, Data);
+hash_update(?SHA, Context, Data) ->
+ crypto:sha_update(Context, Data).
+
+hash_final(?MD5, Context) ->
+ crypto:md5_final(Context);
+hash_final(?SHA, Context) ->
+ crypto:sha_final(Context).
+
+%%pad_1(?NULL) ->
+%% "";
+pad_1(?MD5) ->
+ <<"666666666666666666666666666666666666666666666666">>;
+pad_1(?SHA) ->
+ <<"6666666666666666666666666666666666666666">>.
+
+%%pad_2(?NULL) ->
+%% "";
+pad_2(?MD5) ->
+ <<"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"
+ "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\">>;
+pad_2(?SHA) ->
+ <<"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"
+ "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\">>.
+
+mac_hash(?NULL, _Secret, _Data) ->
+ <<>>;
+mac_hash(Method, Secret, Data) ->
+ InnerHash = hash(Method, [Secret, pad_1(Method), Data]),
+ hash(Method, [Secret, pad_2(Method), InnerHash]).
+
+handshake_hash(Method, HandshakeHash, Extra) ->
+ HSH = hash_update(Method, HandshakeHash, Extra),
+ hash_final(Method, HSH).
+
+handshake_hash(Method, MasterSecret, undefined, HandshakeHash) ->
+ InnerHash =
+ handshake_hash(Method, HandshakeHash,
+ [MasterSecret, pad_1(Method)]),
+ hash(Method, [MasterSecret, pad_2(Method), InnerHash]);
+handshake_hash(Method, MasterSecret, Sender, HandshakeHash) ->
+ InnerHash =
+ handshake_hash(Method, HandshakeHash,
+ [Sender, MasterSecret, pad_1(Method)]),
+ hash(Method, [MasterSecret, pad_2(Method), InnerHash]).
+
+get_sender(client) -> "CLNT";
+get_sender(server) -> "SRVR";
+get_sender(none) -> "".
+
+generate_keyblock(MasterSecret, ServerRandom, ClientRandom, WantedLength) ->
+ gen(MasterSecret, [MasterSecret, ServerRandom, ClientRandom],
+ WantedLength, 0, $A, 1, []).
+
+gen(_Secret, _All, Wanted, Len, _C, _N, Acc) when Wanted =< Len ->
+ <<Block:Wanted/binary, _/binary>> = list_to_binary(lists:reverse(Acc)),
+ Block;
+gen(Secret, All, Wanted, Len, C, N, Acc) ->
+ Prefix = lists:duplicate(N, C),
+ SHA = crypto:sha([Prefix, All]),
+ MD5 = crypto:md5([Secret, SHA]),
+ gen(Secret, All, Wanted, Len + 16, C+1, N+1, [MD5 | Acc]).
diff --git a/lib/ssl/src/ssl_sup.erl b/lib/ssl/src/ssl_sup.erl
new file mode 100644
index 0000000000..bd5a02417a
--- /dev/null
+++ b/lib/ssl/src/ssl_sup.erl
@@ -0,0 +1,100 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(ssl_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+%% init([]) -> {ok, {SupFlags, [ChildSpec]}}
+%%
+init([]) ->
+
+ %% OLD ssl - moved start to ssl.erl only if old
+ %% ssl is acctualy run!
+ %%Child1 = {ssl_server, {ssl_server, start_link, []},
+ %% permanent, 2000, worker, [ssl_server]},
+
+ %% Does not start any port programs so it does matter
+ %% so much if it is not used!
+ Child2 = {ssl_broker_sup, {ssl_broker_sup, start_link, []},
+ permanent, 2000, supervisor, [ssl_broker_sup]},
+
+
+ %% New ssl
+ SessionCertManager = session_and_cert_manager_child_spec(),
+ ConnetionManager = connection_manager_child_spec(),
+
+ {ok, {{one_for_all, 10, 3600}, [Child2, SessionCertManager,
+ ConnetionManager]}}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+session_and_cert_manager_child_spec() ->
+ Opts = manager_opts(),
+ Name = ssl_manager,
+ StartFunc = {ssl_manager, start_link, Opts},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [ssl_manager],
+ Type = worker,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+connection_manager_child_spec() ->
+ Name = ssl_connection,
+ StartFunc = {ssl_connection_sup, start_link, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [ssl_connection],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+
+manager_opts() ->
+ CbOpts = case application:get_env(ssl, session_cb) of
+ {ok, Cb} when is_atom(Cb) ->
+ [{session_cb, Cb}];
+ _ ->
+ []
+ end,
+ case application:get_env(ssl, session_lifetime) of
+ {ok, Time} when is_integer(Time) ->
+ [{session_lifetime, Time}| CbOpts];
+ _ ->
+ CbOpts
+ end.
+
diff --git a/lib/ssl/src/ssl_tls1.erl b/lib/ssl/src/ssl_tls1.erl
new file mode 100644
index 0000000000..e0013c48ac
--- /dev/null
+++ b/lib/ssl/src/ssl_tls1.erl
@@ -0,0 +1,251 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Handles tls1 encryption.
+%%----------------------------------------------------------------------
+
+-module(ssl_tls1).
+
+-include("ssl_cipher.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_record.hrl").
+-include("ssl_debug.hrl").
+
+-export([master_secret/3, finished/3, certificate_verify/2, mac_hash/7,
+ setup_keys/5, setup_keys/6, suites/0]).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+
+master_secret(PreMasterSecret, ClientRandom, ServerRandom) ->
+ %% RFC 2246 & 4346 - 8.1 %% master_secret = PRF(pre_master_secret,
+ %% "master secret", ClientHello.random +
+ %% ServerHello.random)[0..47];
+ prf(PreMasterSecret, <<"master secret">>,
+ [ClientRandom, ServerRandom], 48).
+
+finished(Role, MasterSecret, {MD5Hash, SHAHash}) ->
+ %% RFC 2246 & 4346 - 7.4.9. Finished
+ %% struct {
+ %% opaque verify_data[12];
+ %% } Finished;
+ %%
+ %% verify_data
+ %% PRF(master_secret, finished_label, MD5(handshake_messages) +
+ %% SHA-1(handshake_messages)) [0..11];
+ MD5 = hash_final(?MD5, MD5Hash),
+ SHA = hash_final(?SHA, SHAHash),
+ prf(MasterSecret, finished_label(Role), [MD5, SHA], 12).
+
+
+certificate_verify(Algorithm, {MD5Hash, SHAHash}) when Algorithm == rsa;
+ Algorithm == dh_rsa;
+ Algorithm == dhe_rsa ->
+ MD5 = hash_final(?MD5, MD5Hash),
+ SHA = hash_final(?SHA, SHAHash),
+ <<MD5/binary, SHA/binary>>;
+
+certificate_verify(Algorithm, {_, SHAHash}) when Algorithm == dh_dss;
+ Algorithm == dhe_dss ->
+ hash_final(?SHA, SHAHash).
+
+setup_keys(MasterSecret, ServerRandom, ClientRandom, HashSize,
+ KeyMatLen, IVSize) ->
+ %% RFC 2246 - 6.3. Key calculation
+ %% key_block = PRF(SecurityParameters.master_secret,
+ %% "key expansion",
+ %% SecurityParameters.server_random +
+ %% SecurityParameters.client_random);
+ %% Then the key_block is partitioned as follows:
+ %% client_write_MAC_secret[SecurityParameters.hash_size]
+ %% server_write_MAC_secret[SecurityParameters.hash_size]
+ %% client_write_key[SecurityParameters.key_material_length]
+ %% server_write_key[SecurityParameters.key_material_length]
+ %% client_write_IV[SecurityParameters.IV_size]
+ %% server_write_IV[SecurityParameters.IV_size]
+ WantedLength = 2 * (HashSize + KeyMatLen + IVSize),
+ KeyBlock = prf(MasterSecret, "key expansion",
+ [ServerRandom, ClientRandom], WantedLength),
+ <<ClientWriteMacSecret:HashSize/binary,
+ ServerWriteMacSecret:HashSize/binary,
+ ClientWriteKey:KeyMatLen/binary, ServerWriteKey:KeyMatLen/binary,
+ ClientIV:IVSize/binary, ServerIV:IVSize/binary>> = KeyBlock,
+ {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
+ ServerWriteKey, ClientIV, ServerIV}.
+
+setup_keys(MasterSecret, ServerRandom, ClientRandom, HashSize, KeyMatLen) ->
+ %% RFC 4346 - 6.3. Key calculation
+ %% key_block = PRF(SecurityParameters.master_secret,
+ %% "key expansion",
+ %% SecurityParameters.server_random +
+ %% SecurityParameters.client_random);
+ %% Then the key_block is partitioned as follows:
+ %% client_write_MAC_secret[SecurityParameters.hash_size]
+ %% server_write_MAC_secret[SecurityParameters.hash_size]
+ %% client_write_key[SecurityParameters.key_material_length]
+ %% server_write_key[SecurityParameters.key_material_length]
+ WantedLength = 2 * (HashSize + KeyMatLen),
+ KeyBlock = prf(MasterSecret, "key expansion",
+ [ServerRandom, ClientRandom], WantedLength),
+ <<ClientWriteMacSecret:HashSize/binary,
+ ServerWriteMacSecret:HashSize/binary,
+ ClientWriteKey:KeyMatLen/binary, ServerWriteKey:KeyMatLen/binary>>
+ = KeyBlock,
+ {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
+ ServerWriteKey, undefined, undefined}.
+
+mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor},
+ Length, Fragment) ->
+ %% RFC 2246 & 4346 - 6.2.3.1.
+ %% HMAC_hash(MAC_write_secret, seq_num + TLSCompressed.type +
+ %% TLSCompressed.version + TLSCompressed.length +
+ %% TLSCompressed.fragment));
+ case Method of
+ ?NULL -> ok;
+ _ ->
+ ?DBG_HEX(Mac_write_secret),
+ ?DBG_HEX(hash(Method, Fragment)),
+ ok
+ end,
+ Mac = hmac_hash(Method, Mac_write_secret,
+ [<<?UINT64(Seq_num), ?BYTE(Type),
+ ?BYTE(Major), ?BYTE(Minor), ?UINT16(Length)>>,
+ Fragment]),
+ ?DBG_HEX(Mac),
+ Mac.
+
+suites() ->
+ [
+ %% TODO: uncomment when supported
+ %% ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
+ %% ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
+ %% TODO: Funkar inte, borde: ?TLS_RSA_WITH_AES_256_CBC_SHA,
+ %% ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ %% ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+ %% ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+ %% ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
+ ?TLS_RSA_WITH_AES_128_CBC_SHA,
+ %%?TLS_DHE_DSS_WITH_RC4_128_SHA, TODO: Support this?
+ %% ?TLS_RSA_WITH_IDEA_CBC_SHA,
+ ?TLS_RSA_WITH_RC4_128_SHA,
+ ?TLS_RSA_WITH_RC4_128_MD5,
+ %%?TLS_RSA_EXPORT1024_WITH_RC4_56_MD5,
+ %%?TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5,
+ %%?TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA,
+ %%?TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA,
+ %%?TLS_RSA_EXPORT1024_WITH_RC4_56_SHA,
+ %%?TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA,
+ %%?TLS_DHE_DSS_WITH_RC4_128_SHA,
+ %%?TLS_DHE_RSA_WITH_DES_CBC_SHA,
+ %% EDH-DSS-DES-CBC-SHA TODO: ??
+ ?TLS_RSA_WITH_DES_CBC_SHA
+ %% ?TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA,
+ %% ?TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA,
+ %%?TLS_RSA_EXPORT_WITH_DES40_CBC_SHA,
+ %%?TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5,
+ %%?TLS_RSA_EXPORT_WITH_RC4_40_MD5
+ ].
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+%%%% HMAC and the Pseudorandom Functions RFC 2246 & 4346 - 5.%%%%
+hmac_hash(?NULL, _, _) ->
+ <<>>;
+hmac_hash(?MD5, Key, Value) ->
+ crypto:md5_mac(Key, Value);
+hmac_hash(?SHA, Key, Value) ->
+ crypto:sha_mac(Key, Value).
+
+% First, we define a data expansion function, P_hash(secret, data) that
+% uses a single hash function to expand a secret and seed into an
+% arbitrary quantity of output:
+%% P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
+%% HMAC_hash(secret, A(2) + seed) +
+%% HMAC_hash(secret, A(3) + seed) + ...
+
+p_hash(Secret, Seed, WantedLength, Method) ->
+ p_hash(Secret, Seed, WantedLength, Method, 0, []).
+
+p_hash(_Secret, _Seed, WantedLength, _Method, _N, [])
+ when WantedLength =< 0 ->
+ [];
+p_hash(_Secret, _Seed, WantedLength, _Method, _N, [Last | Acc])
+ when WantedLength =< 0 ->
+ Keep = byte_size(Last) + WantedLength,
+ <<B:Keep/binary, _/binary>> = Last,
+ lists:reverse(Acc, [B]);
+p_hash(Secret, Seed, WantedLength, Method, N, Acc) ->
+ N1 = N+1,
+ Bin = hmac_hash(Method, Secret, [a(N1, Secret, Seed, Method), Seed]),
+ p_hash(Secret, Seed, WantedLength - byte_size(Bin), Method, N1, [Bin|Acc]).
+
+
+%% ... Where A(0) = seed
+%% A(i) = HMAC_hash(secret, A(i-1))
+%% a(0, _Secret, Seed, _Method) ->
+%% Seed.
+%% a(N, Secret, Seed, Method) ->
+%% hmac_hash(Method, Secret, a(N-1, Secret, Seed, Method)).
+a(0, _Secret, Seed, _Method) ->
+ Seed;
+a(N, Secret, Seed0, Method) ->
+ Seed = hmac_hash(Method, Secret, Seed0),
+ a(N-1, Secret, Seed, Method).
+
+split_secret(BinSecret) ->
+ %% L_S = length in bytes of secret;
+ %% L_S1 = L_S2 = ceil(L_S / 2);
+ %% The secret is partitioned into two halves (with the possibility of
+ %% one shared byte) as described above, S1 taking the first L_S1 bytes,
+ %% and S2 the last L_S2 bytes.
+ Length = byte_size(BinSecret),
+ Div = Length div 2,
+ EvenLength = Length - Div,
+ <<Secret1:EvenLength/binary, _/binary>> = BinSecret,
+ <<_:Div/binary, Secret2:EvenLength/binary>> = BinSecret,
+ {Secret1, Secret2}.
+
+prf(Secret, Label, Seed, WantedLength) ->
+ %% PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR
+ %% P_SHA-1(S2, label + seed);
+ {S1, S2} = split_secret(Secret),
+ LS = list_to_binary([Label, Seed]),
+ crypto:exor(p_hash(S1, LS, WantedLength, ?MD5),
+ p_hash(S2, LS, WantedLength, ?SHA)).
+
+%%%% Misc help functions %%%%
+
+finished_label(client) ->
+ <<"client finished">>;
+finished_label(server) ->
+ <<"server finished">>.
+
+hash_final(?MD5, Conntext) ->
+ crypto:md5_final(Conntext);
+hash_final(?SHA, Conntext) ->
+ crypto:sha_final(Conntext).
+
+
+
+