diff options
Diffstat (limited to 'lib/ssl/src/ssl_connection.erl')
-rw-r--r-- | lib/ssl/src/ssl_connection.erl | 269 |
1 files changed, 147 insertions, 122 deletions
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 5b4b129e30..c94199c336 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -125,8 +125,9 @@ send(Pid, Data) -> recv(Pid, Length, Timeout) -> sync_send_all_state_event(Pid, {recv, Length}, Timeout). %%-------------------------------------------------------------------- --spec connect(host(), port_num(), port(), list(), pid(), tuple(), timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. +-spec connect(host(), port_num(), port(), {#ssl_options{}, #socket_options{}}, + pid(), tuple(), timeout()) -> + {ok, #sslsocket{}} | {error, reason()}. %% %% Description: Connect to a ssl server. %%-------------------------------------------------------------------- @@ -138,7 +139,8 @@ connect(Host, Port, Socket, Options, User, CbInfo, Timeout) -> {error, ssl_not_started} end. %%-------------------------------------------------------------------- --spec ssl_accept(port_num(), port(), list(), pid(), tuple(), timeout()) -> +-spec ssl_accept(port_num(), port(), {#ssl_options{}, #socket_options{}}, + pid(), tuple(), timeout()) -> {ok, #sslsocket{}} | {error, reason()}. %% %% Description: Performs accept on a ssl listen socket. e.i. performs @@ -253,7 +255,7 @@ session_info(ConnectionPid) -> sync_send_all_state_event(ConnectionPid, session_info). %%-------------------------------------------------------------------- --spec peer_certificate(pid()) -> {ok, binary()} | {error, reason()}. +-spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}. %% %% Description: Returns the peer cert %%-------------------------------------------------------------------- @@ -288,9 +290,10 @@ start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> %% gen_fsm callbacks %%==================================================================== %%-------------------------------------------------------------------- --spec init(list()) -> {ok, state_name(), #state{}} - | {ok, state_name(), #state{}, timeout()} | - ignore | {stop, term()}. +-spec init(list()) -> {ok, state_name(), #state{}} | {stop, term()}. +%% Possible return values not used now. +%% | {ok, state_name(), #state{}, timeout()} | +%% ignore %% 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. @@ -331,14 +334,12 @@ hello(start, #state{host = Host, port = Port, role = client, ssl_options = SslOpts, transport_cb = Transport, socket = Socket, connection_states = ConnectionStates, - own_cert = Cert, renegotiation = {Renegotiation, _}} = State0) -> Hello = ssl_handshake:client_hello(Host, Port, ConnectionStates, - SslOpts, Cert, - Renegotiation), + SslOpts, Renegotiation), Version = Hello#client_hello.client_version, Hashes0 = ssl_handshake:init_hashes(), @@ -350,7 +351,7 @@ hello(start, #state{host = Host, port = Port, role = client, session = #session{session_id = Hello#client_hello.session_id, is_resumable = false}, - tls_handshake_hashes = Hashes1}, + tls_handshake_hashes = Hashes1}, {Record, State} = next_record(State1), next_state(hello, Record, State); @@ -499,8 +500,7 @@ certify(#certificate{} = Cert, ssl_options = Opts} = State) -> case ssl_handshake:certify(Cert, CertDbRef, Opts#ssl_options.depth, Opts#ssl_options.verify, - Opts#ssl_options.verify_fun, - Opts#ssl_options.validate_extensions_fun, Role) of + Opts#ssl_options.verify_fun, Role) of {PeerCert, PublicKeyInfo} -> handle_peer_cert(PeerCert, PublicKeyInfo, State#state{client_certificate_requested = false}); @@ -576,58 +576,61 @@ certify(#client_key_exchange{} = Msg, %% We expect a certificate here handle_unexpected_message(Msg, certify_client_key_exchange, 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}, - State1 = State0#state{connection_states = ConnectionStates, - session = Session}, - {Record, State} = next_record(State1), - next_state(cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, - certify_client_key_exchange, State0), - {stop, normal, State0} - end +certify(#client_key_exchange{exchange_keys = Keys}, + State = #state{key_algorithm = KeyAlg, negotiated_version = Version}) -> + try + certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, Version), State) catch #alert{} = Alert -> - handle_own_alert(Alert, Version, certify_client_key_exchange, - State0), + handle_own_alert(Alert, Version, certify_client_key_exchange, State), + {stop, normal, State} + end; + +certify(Msg, State) -> + handle_unexpected_message(Msg, certify, State). + +certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, + #state{negotiated_version = Version, + connection_states = ConnectionStates0, + session = Session0, + private_key = Key} = State0) -> + PremasterSecret = ssl_handshake:decrypt_premaster_secret(EncPMS, Key), + case ssl_handshake:master_secret(Version, PremasterSecret, + ConnectionStates0, server) of + {MasterSecret, ConnectionStates} -> + Session = Session0#session{master_secret = MasterSecret}, + State1 = State0#state{connection_states = ConnectionStates, + session = Session}, + {Record, State} = next_record(State1), + next_state(cipher, Record, State); + #alert{} = Alert -> + handle_own_alert(Alert, Version, + certify_client_key_exchange, State0), {stop, normal, State0} end; -certify(#client_key_exchange{exchange_keys = #client_diffie_hellman_public{ - dh_public = ClientPublicDhKey}}, - #state{negotiated_version = Version, - diffie_hellman_params = #'DHParameter'{prime = P, - base = G}, - diffie_hellman_keys = {_, ServerDhPrivateKey}, - role = Role, - session = Session, - connection_states = ConnectionStates0} = State0) -> - +certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey}, + #state{negotiated_version = Version, + diffie_hellman_params = #'DHParameter'{prime = P, + base = G}, + diffie_hellman_keys = {_, ServerDhPrivateKey}, + role = Role, + session = Session, + connection_states = ConnectionStates0} = State0) -> + PMpint = crypto:mpint(P), GMpint = crypto:mpint(G), PremasterSecret = crypto:dh_compute_key(mpint_binary(ClientPublicDhKey), ServerDhPrivateKey, [PMpint, GMpint]), - + case ssl_handshake:master_secret(Version, PremasterSecret, ConnectionStates0, Role) of {MasterSecret, ConnectionStates} -> State1 = State0#state{session = - Session#session{master_secret - = MasterSecret}, - connection_states = ConnectionStates}, + Session#session{master_secret + = MasterSecret}, + connection_states = ConnectionStates}, {Record, State} = next_record(State1), next_state(cipher, Record, State); @@ -635,10 +638,7 @@ certify(#client_key_exchange{exchange_keys = #client_diffie_hellman_public{ handle_own_alert(Alert, Version, certify_client_key_exchange, State0), {stop, normal, State0} - end; - -certify(Msg, State) -> - handle_unexpected_message(Msg, certify, State). + end. %%-------------------------------------------------------------------- -spec cipher(#hello_request{} | #certificate_verify{} | #finished{} | term(), @@ -698,14 +698,13 @@ connection(#hello_request{}, #state{host = Host, port = Port, socket = Socket, ssl_options = SslOpts, negotiated_version = Version, - own_cert = Cert, transport_cb = Transport, connection_states = ConnectionStates0, renegotiation = {Renegotiation, _}, tls_handshake_hashes = Hashes0} = State0) -> - Hello = ssl_handshake:client_hello(Host, Port, - ConnectionStates0, SslOpts, Cert, Renegotiation), + Hello = ssl_handshake:client_hello(Host, Port, ConnectionStates0, + SslOpts, Renegotiation), {BinMsg, ConnectionStates1, Hashes1} = encode_handshake(Hello, Version, ConnectionStates0, Hashes0), @@ -720,7 +719,9 @@ connection(#client_hello{} = Hello, #state{role = server} = State) -> connection(Msg, State) -> handle_unexpected_message(Msg, connection, State). %%-------------------------------------------------------------------- --spec handle_event(term(), state_name(), #state{}) -> gen_fsm_state_return(). +-spec handle_event(term(), state_name(), #state{}) -> term(). +%% As it is not currently used gen_fsm_state_return() makes +%% dialyzer unhappy! %% %% Description: Whenever a gen_fsm receives an event sent using %% gen_fsm:send_all_state_event/2, this function is called to handle @@ -1033,29 +1034,40 @@ ssl_init(SslOpts, Role) -> PrivateKey = init_private_key(SslOpts#ssl_options.key, SslOpts#ssl_options.keyfile, SslOpts#ssl_options.password, Role), - DHParams = init_diffie_hellman(SslOpts#ssl_options.dhfile, Role), + DHParams = init_diffie_hellman(SslOpts#ssl_options.dh, SslOpts#ssl_options.dhfile, Role), {ok, CertDbRef, CacheRef, OwnCert, PrivateKey, DHParams}. -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, Reason} -> - handle_file_error(?LINE, error, Reason, CACertFile, ecacertfile, - erlang:get_stacktrace()) - end. +init_certificates(#ssl_options{cacerts = CaCerts, + cacertfile = CACertFile, + certfile = CertFile, + cert = Cert}, Role) -> + {ok, CertDbRef, CacheRef} = + try + Certs = case CaCerts of + undefined -> + CACertFile; + _ -> + {der, CaCerts} + end, + {ok, _, _} = ssl_manager:connection_init(Certs, Role) + catch + Error:Reason -> + handle_file_error(?LINE, Error, Reason, CACertFile, ecacertfile, + erlang:get_stacktrace()) + end, + init_certificates(Cert, CertDbRef, CacheRef, CertFile, Role). + -init_certificates(CertDbRef, CacheRef, CertFile, client) -> +init_certificates(undefined, CertDbRef, CacheRef, CertFile, client) -> try [OwnCert] = ssl_certificate:file_to_certificats(CertFile), {ok, CertDbRef, CacheRef, OwnCert} - catch _E:_R -> + catch _Error:_Reason -> {ok, CertDbRef, CacheRef, undefined} end; -init_certificates(CertDbRef, CacheRef, CertFile, server) -> +init_certificates(undefined, CertDbRef, CacheRef, CertFile, server) -> try [OwnCert] = ssl_certificate:file_to_certificats(CertFile), {ok, CertDbRef, CacheRef, OwnCert} @@ -1063,53 +1075,61 @@ init_certificates(CertDbRef, CacheRef, CertFile, server) -> Error:Reason -> handle_file_error(?LINE, Error, Reason, CertFile, ecertfile, erlang:get_stacktrace()) - end. + end; +init_certificates(Cert, CertDbRef, CacheRef, _, _) -> + {ok, CertDbRef, CacheRef, Cert}. init_private_key(undefined, "", _Password, client) -> undefined; init_private_key(undefined, KeyFile, Password, _) -> - case ssl_manager:cache_pem_file(KeyFile) of - {ok, List} -> - [Der] = [Der || Der = {PKey, _ , _} <- List, - PKey =:= rsa_private_key orelse - PKey =:= dsa_private_key], - {ok, Decoded} = public_key:decode_private_key(Der,Password), - Decoded; - {error, Reason} -> - handle_file_error(?LINE, error, Reason, KeyFile, ekeyfile, + try + {ok, List} = ssl_manager:cache_pem_file(KeyFile), + [PemEntry] = [PemEntry || PemEntry = {PKey, _ , _} <- List, + PKey =:= 'RSAPrivateKey' orelse + PKey =:= 'DSAPrivateKey'], + public_key:pem_entry_decode(PemEntry, Password) + catch + Error:Reason -> + handle_file_error(?LINE, Error, Reason, KeyFile, ekeyfile, erlang:get_stacktrace()) end; -init_private_key(PrivateKey, _, _,_) -> - PrivateKey. +init_private_key({rsa, PrivateKey}, _, _,_) -> + public_key:der_decode('RSAPrivateKey', PrivateKey); +init_private_key({dsa, PrivateKey},_,_,_) -> + public_key:der_decode('DSAPrivateKey', PrivateKey). handle_file_error(Line, Error, {badmatch, Reason}, File, Throw, Stack) -> file_error(Line, Error, Reason, File, Throw, Stack); handle_file_error(Line, Error, Reason, File, Throw, Stack) -> file_error(Line, Error, Reason, File, Throw, Stack). +-spec(file_error/6 :: (_,_,_,_,_,_) -> no_return()). file_error(Line, Error, Reason, File, Throw, Stack) -> Report = io_lib:format("SSL: ~p: ~p:~p ~s~n ~p~n", [Line, Error, Reason, File, Stack]), error_logger:error_report(Report), throw(Throw). -init_diffie_hellman(_, client) -> +init_diffie_hellman(Params, _,_) when is_binary(Params)-> + public_key:der_decode('DHParameter', Params); +init_diffie_hellman(_,_, client) -> undefined; -init_diffie_hellman(undefined, _) -> +init_diffie_hellman(_,undefined, _) -> ?DEFAULT_DIFFIE_HELLMAN_PARAMS; -init_diffie_hellman(DHParamFile, server) -> - case ssl_manager:cache_pem_file(DHParamFile) of - {ok, List} -> - case [Der || Der = {dh_params, _ , _} <- List] of - [Der] -> - {ok, Decoded} = public_key:decode_dhparams(Der), - Decoded; - [] -> - ?DEFAULT_DIFFIE_HELLMAN_PARAMS - end; - {error, Reason} -> - handle_file_error(?LINE, error, Reason, DHParamFile, edhfile, erlang:get_stacktrace()) +init_diffie_hellman(_, DHParamFile, server) -> + try + {ok, List} = ssl_manager:cache_pem_file(DHParamFile), + case [Entry || Entry = {'DHParameter', _ , _} <- List] of + [Entry] -> + public_key:pem_entry_decode(Entry); + [] -> + ?DEFAULT_DIFFIE_HELLMAN_PARAMS + end + catch + Error:Reason -> + handle_file_error(?LINE, Error, Reason, + DHParamFile, edhfile, erlang:get_stacktrace()) end. sync_send_all_state_event(FsmPid, Event) -> @@ -1178,7 +1198,7 @@ verify_client_cert(#state{client_certificate_requested = true, role = client, tls_handshake_hashes = Hashes1}; ignore -> State; - #alert{} = Alert -> + #alert{} = Alert -> handle_own_alert(Alert, Version, certify, State) end; @@ -1186,18 +1206,19 @@ verify_client_cert(#state{client_certificate_requested = false} = State) -> State. do_server_hello(Type, #state{negotiated_version = Version, - session = Session, + session = #session{session_id = SessId} = Session, connection_states = ConnectionStates0, renegotiation = {Renegotiation, _}} = State0) when is_atom(Type) -> + ServerHello = - ssl_handshake:server_hello(Session#session.session_id, Version, + ssl_handshake:server_hello(SessId, Version, ConnectionStates0, Renegotiation), State1 = server_hello(ServerHello, State0), case Type of new -> - do_server_hello(ServerHello, State1); + new_server_hello(ServerHello, State1); resumed -> ConnectionStates1 = State1#state.connection_states, case ssl_handshake:master_secret(Version, Session, @@ -1216,9 +1237,9 @@ do_server_hello(Type, #state{negotiated_version = Version, handle_own_alert(Alert, Version, hello, State1), {stop, normal, State1} end - end; + end. -do_server_hello(#server_hello{cipher_suite = CipherSuite, +new_server_hello(#server_hello{cipher_suite = CipherSuite, compression_method = Compression, session_id = SessionId}, #state{session = Session0, @@ -1343,7 +1364,7 @@ certify_server(#state{transport_cb = Transport, key_exchange(#state{role = server, key_algorithm = rsa} = State) -> State; key_exchange(#state{role = server, key_algorithm = Algo, - diffie_hellman_params = Params, + diffie_hellman_params = #'DHParameter'{prime = P, base = G} = Params, private_key = PrivateKey, connection_states = ConnectionStates0, negotiated_version = Version, @@ -1354,7 +1375,7 @@ key_exchange(#state{role = server, key_algorithm = Algo, when Algo == dhe_dss; Algo == dhe_rsa -> - Keys = public_key:gen_key(Params), + Keys = crypto:dh_generate_key([crypto:mpint(P), crypto:mpint(G)]), ConnectionState = ssl_record:pending_connection_state(ConnectionStates0, read), SecParams = ConnectionState#connection_state.security_parameters, @@ -1406,6 +1427,8 @@ key_exchange(#state{role = client, State#state{connection_states = ConnectionStates1, tls_handshake_hashes = Hashes1}. +-spec(rsa_key_exchange/2 :: (_,_) -> no_return()). + rsa_key_exchange(PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) when Algorithm == ?rsaEncryption; Algorithm == ?md2WithRSAEncryption; @@ -1536,7 +1559,7 @@ verify_dh_params(Signed, Hashes, {?rsaEncryption, PubKey, _PubKeyParams}) -> false end; verify_dh_params(Signed, Hash, {?'id-dsa', PublicKey, PublicKeyParams}) -> - public_key:verify_signature(Hash, none, Signed, PublicKey, PublicKeyParams). + public_key:verify(Hash, none, Signed, {PublicKey, PublicKeyParams}). cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State) -> @@ -1563,7 +1586,7 @@ encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) -> ssl_record:encode_change_cipher_spec(Version, ConnectionStates). encode_handshake(HandshakeRec, Version, ConnectionStates, Hashes) -> - encode_handshake(HandshakeRec, undefined, Version, + encode_handshake(HandshakeRec, null, Version, ConnectionStates, Hashes). encode_handshake(HandshakeRec, SigAlg, Version, ConnectionStates0, Hashes0) -> @@ -1626,8 +1649,6 @@ application_data(Data, #state{user_application = {_Mon, Pid}, true -> <<Buffer0/binary, Data/binary>> end, case get_data(SOpts, BytesToRead, Buffer1) of - {ok, <<>>, Buffer} -> % no reply, we need more data - next_record(State0#state{user_data_buffer = Buffer}); {ok, ClientData, Buffer} -> % Send data SocketOpt = deliver_app_data(SOpts, ClientData, Pid, From), State = State0#state{user_data_buffer = Buffer, @@ -1643,12 +1664,16 @@ application_data(Data, #state{user_application = {_Mon, Pid}, true -> %% We have more data application_data(<<>>, State) end; + {more, Buffer} -> % no reply, we need more data + next_record(State0#state{user_data_buffer = Buffer}); {error,_Reason} -> %% Invalid packet in packet mode deliver_packet_error(SOpts, Buffer1, Pid, From), {stop, normal, State0} end. %% Picks ClientData +get_data(_, _, <<>>) -> + {more, <<>>}; get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer) when Raw =:= raw; Raw =:= 0 -> %% Raw Mode if @@ -1661,13 +1686,13 @@ get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer) {ok, Data, Rest}; true -> %% Passive Mode not enough data - {ok, <<>>, Buffer} + {more, Buffer} end; get_data(#socket_options{packet=Type, packet_size=Size}, _, Buffer) -> PacketOpts = [{packet_size, Size}], case decode_packet(Type, Buffer, PacketOpts) of {more, _} -> - {ok, <<>>, Buffer}; + {more, Buffer}; Decoded -> Decoded end. @@ -1726,11 +1751,13 @@ format_packet_error(#socket_options{active = _, mode = Mode}, Data) -> format_reply(binary, _, N, Data) when N > 0 -> % Header mode header(N, Data); -format_reply(binary, _, _, Data) -> Data; -format_reply(list, Packet, _, Data) when is_integer(Packet); Packet == raw -> - binary_to_list(Data); +format_reply(binary, _, _, Data) -> + Data; +format_reply(list, Packet, _, Data) + when Packet == http; Packet == {http, headers}; Packet == http_bin; Packet == {http_bin, headers} -> + Data; format_reply(list, _,_, Data) -> - Data. + binary_to_list(Data). header(0, <<>>) -> <<>>; @@ -1780,9 +1807,7 @@ next_state(Next, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, State) -> handle_alerts(Alerts, {next_state, Next, State}); next_state(StateName, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, - State0 = #state{key_algorithm = KeyAlg, - tls_handshake_buffer = Buf0, - negotiated_version = Version}) -> + State0 = #state{tls_handshake_buffer = Buf0, negotiated_version = Version}) -> Handle = fun({#hello_request{} = Packet, _}, {next_state, connection = SName, State}) -> %% This message should not be included in handshake @@ -1805,7 +1830,7 @@ next_state(StateName, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, (_, StopState) -> StopState end, try - {Packets, Buf} = ssl_handshake:get_tls_handshake(Data,Buf0, KeyAlg,Version), + {Packets, Buf} = ssl_handshake:get_tls_handshake(Data,Buf0), State = State0#state{tls_packets = Packets, tls_handshake_buffer = Buf}, handle_tls_handshake(Handle, StateName, State) catch throw:#alert{} = Alert -> @@ -1817,7 +1842,7 @@ next_state(StateName, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, State case application_data(Data, State0) of Stop = {stop,_,_} -> Stop; - {Record, State} -> + {Record, State} -> next_state(StateName, Record, State) end; next_state(StateName, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} = @@ -2154,7 +2179,7 @@ renegotiate(#state{role = server, negotiated_version = Version, connection_states = ConnectionStates0} = State0) -> HelloRequest = ssl_handshake:hello_request(), - Frag = ssl_handshake:encode_handshake(HelloRequest, Version, undefined), + Frag = ssl_handshake:encode_handshake(HelloRequest, Version, null), Hs0 = ssl_handshake:init_hashes(), {BinMsg, ConnectionStates} = ssl_record:encode_handshake(Frag, Version, ConnectionStates0), |