From 84adefa331c4159d432d22840663c38f155cd4c1 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Fri, 20 Nov 2009 14:54:40 +0000 Subject: The R13B03 release. --- lib/ssl/src/Makefile | 142 +++ lib/ssl/src/inet_ssl_dist.erl | 449 +++++++++ lib/ssl/src/ssl.app.src | 41 + lib/ssl/src/ssl.appup.src | 21 + lib/ssl/src/ssl.erl | 841 ++++++++++++++++ lib/ssl/src/ssl_alert.erl | 107 +++ lib/ssl/src/ssl_alert.hrl | 97 ++ lib/ssl/src/ssl_app.erl | 41 + lib/ssl/src/ssl_base64.erl | 129 +++ lib/ssl/src/ssl_broker.erl | 1188 +++++++++++++++++++++++ lib/ssl/src/ssl_broker_int.hrl | 38 + lib/ssl/src/ssl_broker_sup.erl | 46 + lib/ssl/src/ssl_certificate.erl | 156 +++ lib/ssl/src/ssl_certificate_db.erl | 219 +++++ lib/ssl/src/ssl_cipher.erl | 784 +++++++++++++++ lib/ssl/src/ssl_cipher.hrl | 253 +++++ lib/ssl/src/ssl_connection.erl | 1704 +++++++++++++++++++++++++++++++++ lib/ssl/src/ssl_connection_sup.erl | 60 ++ lib/ssl/src/ssl_debug.erl | 99 ++ lib/ssl/src/ssl_debug.hrl | 39 + lib/ssl/src/ssl_handshake.erl | 917 ++++++++++++++++++ lib/ssl/src/ssl_handshake.hrl | 200 ++++ lib/ssl/src/ssl_int.hrl | 99 ++ lib/ssl/src/ssl_internal.hrl | 91 ++ lib/ssl/src/ssl_manager.erl | 340 +++++++ lib/ssl/src/ssl_pem.erl | 147 +++ lib/ssl/src/ssl_pkix.erl | 307 ++++++ lib/ssl/src/ssl_pkix.hrl | 81 ++ lib/ssl/src/ssl_prim.erl | 173 ++++ lib/ssl/src/ssl_record.erl | 577 +++++++++++ lib/ssl/src/ssl_record.hrl | 170 ++++ lib/ssl/src/ssl_server.erl | 1378 ++++++++++++++++++++++++++ lib/ssl/src/ssl_session.erl | 172 ++++ lib/ssl/src/ssl_session_cache.erl | 124 +++ lib/ssl/src/ssl_session_cache_api.erl | 37 + lib/ssl/src/ssl_ssl2.erl | 37 + lib/ssl/src/ssl_ssl3.erl | 286 ++++++ lib/ssl/src/ssl_sup.erl | 100 ++ lib/ssl/src/ssl_tls1.erl | 251 +++++ 39 files changed, 11941 insertions(+) create mode 100644 lib/ssl/src/Makefile create mode 100644 lib/ssl/src/inet_ssl_dist.erl create mode 100644 lib/ssl/src/ssl.app.src create mode 100644 lib/ssl/src/ssl.appup.src create mode 100644 lib/ssl/src/ssl.erl create mode 100644 lib/ssl/src/ssl_alert.erl create mode 100644 lib/ssl/src/ssl_alert.hrl create mode 100644 lib/ssl/src/ssl_app.erl create mode 100644 lib/ssl/src/ssl_base64.erl create mode 100644 lib/ssl/src/ssl_broker.erl create mode 100644 lib/ssl/src/ssl_broker_int.hrl create mode 100644 lib/ssl/src/ssl_broker_sup.erl create mode 100644 lib/ssl/src/ssl_certificate.erl create mode 100644 lib/ssl/src/ssl_certificate_db.erl create mode 100644 lib/ssl/src/ssl_cipher.erl create mode 100644 lib/ssl/src/ssl_cipher.hrl create mode 100644 lib/ssl/src/ssl_connection.erl create mode 100644 lib/ssl/src/ssl_connection_sup.erl create mode 100644 lib/ssl/src/ssl_debug.erl create mode 100644 lib/ssl/src/ssl_debug.hrl create mode 100644 lib/ssl/src/ssl_handshake.erl create mode 100644 lib/ssl/src/ssl_handshake.hrl create mode 100644 lib/ssl/src/ssl_int.hrl create mode 100644 lib/ssl/src/ssl_internal.hrl create mode 100644 lib/ssl/src/ssl_manager.erl create mode 100644 lib/ssl/src/ssl_pem.erl create mode 100644 lib/ssl/src/ssl_pkix.erl create mode 100644 lib/ssl/src/ssl_pkix.hrl create mode 100644 lib/ssl/src/ssl_prim.erl create mode 100644 lib/ssl/src/ssl_record.erl create mode 100644 lib/ssl/src/ssl_record.hrl create mode 100644 lib/ssl/src/ssl_server.erl create mode 100644 lib/ssl/src/ssl_session.erl create mode 100644 lib/ssl/src/ssl_session_cache.erl create mode 100644 lib/ssl/src/ssl_session_cache_api.erl create mode 100644 lib/ssl/src/ssl_ssl2.erl create mode 100644 lib/ssl/src/ssl_ssl3.erl create mode 100644 lib/ssl/src/ssl_sup.erl create mode 100644 lib/ssl/src/ssl_tls1.erl (limited to 'lib/ssl/src') 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 @@ +%% +%% 2000-2008 +%% Ericsson AB, 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. +%% +%% The Initial Developer of the Original Code is Ericsson AB. +%% +%% +-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, + [enc(C1), enc(C2), enc(C3), enc(C4)| encode(Bs)]; +encode(<>) -> + <> = <>, + [enc(C1), enc(C2), enc(C3), $=]; +encode(<>) -> + <> = <>, + [enc(C1), enc(C2), $=, $=]; +encode(<<>>) -> + []. + +%% +%% encode_split(Bytes|Binary) -> Lines +%% +%% The encoding is divided into lines separated by , and each line +%% is precisely 64 characters long (excluding the characters, +%% except the last line which 64 characters long or shorter. may +%% follow the last line. +%% +encode_split(Bs) -> + split(encode(Bs)). + +%% +%% decode(Chars) -> Binary +%% +decode(Cs) -> + list_to_binary(decode1(Cs)). + +decode1([C1, C2, $=, $=]) -> + <> = <<(dec(C1)):6, (dec(C2)):6, 0:12>>, + [B1]; +decode1([C1, C2, C3, $=]) -> + <> = <<(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 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 +%% . 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 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 +%%------------------------------------------------------------------- +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(<>, 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(<>, 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 +%% 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, + <> = 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, + <> = 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, <>). + +%%% 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, <>). + +%% TLS_RSA_WITH_NULL_SHA = { 0x00,0x02 }; +-define(TLS_RSA_WITH_NULL_SHA, <>). + +%% TLS_RSA_EXPORT_WITH_RC4_40_MD5 = { 0x00,0x03 }; +-define(TLS_RSA_EXPORT_WITH_RC4_40_MD5, <>). + +%% TLS_RSA_WITH_RC4_128_MD5 = { 0x00,0x04 }; +-define(TLS_RSA_WITH_RC4_128_MD5, <>). + +%% TLS_RSA_WITH_RC4_128_SHA = { 0x00,0x05 }; +-define(TLS_RSA_WITH_RC4_128_SHA, <>). + +%% TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = { 0x00,0x06 }; +-define(TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5, <>). + +%% TLS_RSA_WITH_IDEA_CBC_SHA = { 0x00,0x07 }; +-define(TLS_RSA_WITH_IDEA_CBC_SHA, <>). + +%% TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x08 }; +-define(TLS_RSA_EXPORT_WITH_DES40_CBC_SHA, <>). + +%% TLS_RSA_WITH_DES_CBC_SHA = { 0x00,0x09 }; +-define(TLS_RSA_WITH_DES_CBC_SHA, <>). + +%% TLS_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x0A }; +-define(TLS_RSA_WITH_3DES_EDE_CBC_SHA, <>). + +%%% 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, <>). + +%% TLS_DH_DSS_WITH_DES_CBC_SHA = { 0x00,0x0C }; +-define(TLS_DH_DSS_WITH_DES_CBC_SHA, <>). + +%% TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00,0x0D }; +-define(TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA, <>). + +%% TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x0E }; +-define(TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA, <>). + +%% TLS_DH_RSA_WITH_DES_CBC_SHA = { 0x00,0x0F }; +-define(TLS_DH_RSA_WITH_DES_CBC_SHA, <>). + +%% TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x10 }; +-define(TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA, <>). + +%% TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x11 }; +-define(TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, <>). + +%% TLS_DHE_DSS_WITH_DES_CBC_SHA = { 0x00,0x12 }; +-define(TLS_DHE_DSS_WITH_DES_CBC_SHA, <>). + +%% TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00,0x13 }; +-define(TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, <>). + +%% TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x14 }; +-define(TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, <>). + +%% TLS_DHE_RSA_WITH_DES_CBC_SHA = { 0x00,0x15 }; +-define(TLS_DHE_RSA_WITH_DES_CBC_SHA, <>). + +%% TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x16 }; +-define(TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, <>). + +%% TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = { 0x00,0x17 }; +-define(TLS_DH_anon_EXPORT_WITH_RC4_40_MD5, <>). + +%% TLS_DH_anon_WITH_RC4_128_MD5 = { 0x00,0x18 }; +-define(TLS_DH_anon_WITH_RC4_128_MD5, <>). + +%% TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x19 }; +-define(TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA, <>). + +%% TLS_DH_anon_WITH_DES_CBC_SHA = { 0x00,0x1A }; +-define(TLS_DH_anon_WITH_DES_CBC_SHA, <>). + +%% TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = { 0x00,0x1B }; +-define(TLS_DH_anon_WITH_3DES_EDE_CBC_SHA, <>). + + +%%% AES Cipher Suites RFC 3268 + +%% TLS_RSA_WITH_AES_128_CBC_SHA = { 0x00, 0x2F }; +-define(TLS_RSA_WITH_AES_128_CBC_SHA, <>). + +%% TLS_DH_DSS_WITH_AES_128_CBC_SHA = { 0x00, 0x30 }; +-define(TLS_DH_DSS_WITH_AES_128_CBC_SHA, <>). + +%% TLS_DH_RSA_WITH_AES_128_CBC_SHA = { 0x00, 0x31 }; +-define(TLS_DH_RSA_WITH_AES_128_CBC_SHA, <>). + +%% TLS_DHE_DSS_WITH_AES_128_CBC_SHA = { 0x00, 0x32 }; +-define(TLS_DHE_DSS_WITH_AES_128_CBC_SHA, <>). + +%% TLS_DHE_RSA_WITH_AES_128_CBC_SHA = { 0x00, 0x33 }; +-define(TLS_DHE_RSA_WITH_AES_128_CBC_SHA, <>). + +%% TLS_DH_anon_WITH_AES_128_CBC_SHA = { 0x00, 0x34 }; +-define(TLS_DH_anon_WITH_AES_128_CBC_SHA, <>). + +%% TLS_RSA_WITH_AES_256_CBC_SHA = { 0x00, 0x35 }; +-define(TLS_RSA_WITH_AES_256_CBC_SHA, <>). + +%% TLS_DH_DSS_WITH_AES_256_CBC_SHA = { 0x00, 0x36 }; +-define(TLS_DH_DSS_WITH_AES_256_CBC_SHA, <>). + +%% TLS_DH_RSA_WITH_AES_256_CBC_SHA = { 0x00, 0x37 }; +-define(TLS_DH_RSA_WITH_AES_256_CBC_SHA, <>). + +%% TLS_DHE_DSS_WITH_AES_256_CBC_SHA = { 0x00, 0x38 }; +-define(TLS_DHE_DSS_WITH_AES_256_CBC_SHA, <>). + +%% TLS_DHE_RSA_WITH_AES_256_CBC_SHA = { 0x00, 0x39 }; +-define(TLS_DHE_RSA_WITH_AES_256_CBC_SHA, <>). + +%% TLS_DH_anon_WITH_AES_256_CBC_SHA = { 0x00, 0x3A }; +-define(TLS_DH_anon_WITH_AES_256_CBC_SHA, <>). + +%%% Kerberos Cipher Suites + +%% TLS_KRB5_WITH_DES_CBC_SHA = { 0x00,0x1E }; +-define(TLS_KRB5_WITH_DES_CBC_SHA, <>). + +%% TLS_KRB5_WITH_3DES_EDE_CBC_SHA = { 0x00,0x1F }; +-define(TLS_KRB5_WITH_3DES_EDE_CBC_SHA, <>). + +%% TLS_KRB5_WITH_RC4_128_SHA = { 0x00,0x20 }; +-define(TLS_KRB5_WITH_RC4_128_SHA, <>). + +%% TLS_KRB5_WITH_IDEA_CBC_SHA = { 0x00,0x21 }; +-define(TLS_KRB5_WITH_IDEA_CBC_SHA, <>). + +%% TLS_KRB5_WITH_DES_CBC_MD5 = { 0x00,0x22 }; +-define(TLS_KRB5_WITH_DES_CBC_MD5, <>). + +%% TLS_KRB5_WITH_3DES_EDE_CBC_MD5 = { 0x00,0x23 }; +-define(TLS_KRB5_WITH_3DES_EDE_CBC_MD5, <>). + +%% TLS_KRB5_WITH_RC4_128_MD5 = { 0x00,0x24 }; +-define(TLS_KRB5_WITH_RC4_128_MD5, <>). + +%% TLS_KRB5_WITH_IDEA_CBC_MD5 = { 0x00,0x25 }; +-define(TLS_KRB5_WITH_IDEA_CBC_MD5, <>). + +%% TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA = { 0x00,0x26 }; +-define(TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA, <>). + +%% TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA = { 0x00,0x27 }; +-define(TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA, <>). + +%% TLS_KRB5_EXPORT_WITH_RC4_40_SHA = { 0x00,0x28 }; +-define(TLS_KRB5_EXPORT_WITH_RC4_40_SHA, <>). + +%% TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 = { 0x00,0x29 }; +-define(TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5, <>). + +%% TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 = { 0x00,0x2A }; +-define(TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5, <>). + +%% TLS_KRB5_EXPORT_WITH_RC4_40_MD5 = { 0x00,0x2B }; +-define(TLS_KRB5_EXPORT_WITH_RC4_40_MD5, <>). + +%% Additional TLS ciphersuites from draft-ietf-tls-56-bit-ciphersuites-00.txt + +-define(TLS_RSA_EXPORT1024_WITH_RC4_56_MD5, <>). +-define(TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5, <>). +-define(TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA, <>). +-define(TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA, <>). +-define(TLS_RSA_EXPORT1024_WITH_RC4_56_SHA, <>). +-define(TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA, <>). +-define(TLS_DHE_DSS_WITH_RC4_128_SHA, <>). + +-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(<>, 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 -> <> + 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) + <> = 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 + <> = 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), + <>. 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(<>, 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 = <>} + }; + +%% 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, <>), +%% 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(<>, + KeyAlg, Version, Acc) -> + Raw = <>, + 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, <>, + _, _) -> + ?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, <>, + _, _) -> + #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, <>, _, _) -> + #server_hello{ + server_version = {Major,Minor}, + random = Random, + session_id = Session_ID, + cipher_suite = Cipher_suite, + compression_method = Comp_method + }; +dec_hs(?CERTIFICATE, <>, _, _) -> + #certificate{asn1_certificates = certs_to_list(ASN1Certs)}; +dec_hs(?SERVER_KEY_EXCHANGE, <>, + ?KEY_EXCHANGE_RSA, _) -> + #server_key_exchange{params = #server_rsa_params{rsa_modulus = Mod, + rsa_exponent = Exp}, + signed_params = Sig}; +dec_hs(?SERVER_KEY_EXCHANGE, <>, + ?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, + <>, _, _) -> + %% 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,<>, _, _)-> + #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, <>, 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, <>, + ?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(<>, 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), + <> + 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, <>}; +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, <>}; +enc_hs(#certificate{asn1_certificates = ASN1CertList}, _Version, _) -> + ASN1Certs = certs_from_list(ASN1CertList), + ACLen = erlang:iolist_size(ASN1Certs), + {?CERTIFICATE, <>}; +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, <> + }; +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, <> + }; +enc_hs(#certificate_request{certificate_types = CertTypes, + certificate_authorities = CertAuths}, + _Version, _) -> + CertTypesLen = byte_size(CertTypes), + CertAuthsLen = byte_size(CertAuths), + {?CERTIFICATE_REQUEST, + <> + }; +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), + <>; +enc_cke(#client_diffie_hellman_public{dh_public = DHPublic}, _) -> + Len = byte_size(DHPublic), + <>. + +enc_bin_sig(BinSig) -> + Size = byte_size(BinSig), + <>. + +init_hashes() -> + T = {crypto:md5_init(), crypto:sha_init()}, + {T, T}. + +update_hashes(Hashes, % special-case SSL2 client hello + <>) -> + update_hashes(Hashes, + <>); +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(<>, Acc) -> + from_3bytes(Rest, [?uint16(N) | Acc]). + +from_2bytes(Bin2) -> + from_2bytes(Bin2, []). + +from_2bytes(<<>>, Acc) -> + lists:reverse(Acc); +from_2bytes(<>, 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 -> + <>; + +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 + <>. + +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), +%% <>; + +%% 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: +%% +%% +%% -----BEGIN SOMETHING----- +%% +%% +%% ... +%% -----END SOMETHING----- +%% +%% +%% 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, + <> = 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) -> + <> = + 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), <>). 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, ) -> {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 . +%% 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, ) -> {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 . +%%-------------------------------------------------------------------- +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 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(<>, + Acc) -> + get_tls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA, + version = {MajVer, MinVer}, + fragment = Data} | Acc]); +get_tls_records_aux(<>, Acc) -> + get_tls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE, + version = {MajVer, MinVer}, + fragment = Data} | Acc]); +get_tls_records_aux(<>, Acc) -> + get_tls_records_aux(Rest, [#ssl_tls{type = ?ALERT, + version = {MajVer, MinVer}, + fragment = Data} | Acc]); +get_tls_records_aux(<>, + 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 + <> -> + Length = Length0-1, + <> = Data0, + Data = <>, + 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), + <>. + +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 + <> -> + 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, <>, + 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), + [<>, 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]), + <> = 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]. + <>. + +int32(I) -> + %% [(I bsr 24) band 255, + %% (I bsr 16) band 255, + %% (I bsr 8) band 255, + %% I band 255]. + <>. + +%% 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| dec(F, Bin)]; +dec([int32| F], <>) -> + [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], <>) -> +%% {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(<>, RCs) -> + dec_string(Bin, [C| RCs]). + +dec_bin(<>) -> + <> = 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 -> + <> = ChallengeData, + NewChallengeData; +client_random(ChallengeData, N) -> + Pad = list_to_binary(lists:duplicate(N, 0)), + <>. 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), + <>. + +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), + <>; + +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, + [<>, 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 */ + <> = 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); + + <> = KeyBlock, + <> = + hash(?MD5, [ClientRandom, ServerRandom]), + <> = + hash(?MD5, [ServerRandom, ClientRandom]), + <> = + hash(?MD5, [ClientWriteKey, ClientRandom, ServerRandom]), + <> = + 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 -> + <> = 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), + <>; + +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), + <> = 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), + <> + = 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, + [<>, + 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, + <> = 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, + <> = 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). + + + + -- cgit v1.2.3