aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src/ssl_handshake.erl
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/ssl/src/ssl_handshake.erl
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/ssl/src/ssl_handshake.erl')
-rw-r--r--lib/ssl/src/ssl_handshake.erl917
1 files changed, 917 insertions, 0 deletions
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
new file mode 100644
index 0000000000..829e0c2ba6
--- /dev/null
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -0,0 +1,917 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%----------------------------------------------------------------------
+%% Purpose: Help funtions for handling the SSL-handshake protocol
+%%----------------------------------------------------------------------
+
+-module(ssl_handshake).
+
+-include("ssl_handshake.hrl").
+-include("ssl_record.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_debug.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+-export([master_secret/4, client_hello/4, server_hello/3, hello/2,
+ certify/5, certificate/3,
+ client_certificate_verify/6,
+ certificate_verify/6, certificate_request/2,
+ key_exchange/2, finished/4,
+ verify_connection/5,
+ get_tls_handshake/4,
+ server_hello_done/0, sig_alg/1,
+ encode_handshake/3, init_hashes/0,
+ update_hashes/2, decrypt_premaster_secret/2]).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: client_hello(Host, Port, ConnectionStates, SslOpts) ->
+%% #client_hello{}
+%% Host
+%% Port
+%% ConnectionStates = #connection_states{}
+%% SslOpts = #ssl_options{}
+%%
+%% Description: Creates a client hello message.
+%%--------------------------------------------------------------------
+client_hello(Host, Port, ConnectionStates, #ssl_options{versions = Versions,
+ ciphers = Ciphers}
+ = SslOpts) ->
+
+ Fun = fun(Version) ->
+ ssl_record:protocol_version(Version)
+ end,
+ Version = ssl_record:highest_protocol_version(lists:map(Fun, Versions)),
+ Pending = ssl_record:pending_connection_state(ConnectionStates, read),
+ SecParams = Pending#connection_state.security_parameters,
+
+ Id = ssl_manager:client_session_id(Host, Port, SslOpts),
+
+ #client_hello{session_id = Id,
+ client_version = Version,
+ cipher_suites = Ciphers,
+ compression_methods = ssl_record:compressions(),
+ random = SecParams#security_parameters.client_random
+ }.
+
+%%--------------------------------------------------------------------
+%% Function: server_hello(Host, Port, SessionId,
+%% Version, ConnectionStates) -> #server_hello{}
+%% SessionId
+%% Version
+%% ConnectionStates
+%%
+%%
+%% Description: Creates a server hello message.
+%%--------------------------------------------------------------------
+server_hello(SessionId, Version, ConnectionStates) ->
+ Pending = ssl_record:pending_connection_state(ConnectionStates, read),
+ SecParams = Pending#connection_state.security_parameters,
+ #server_hello{server_version = Version,
+ cipher_suite = SecParams#security_parameters.cipher_suite,
+ compression_method =
+ SecParams#security_parameters.compression_algorithm,
+ random = SecParams#security_parameters.server_random,
+ session_id = SessionId
+ }.
+
+%%--------------------------------------------------------------------
+%% Function: hello(Hello, Info) ->
+%% {Version, Id, NewConnectionStates} |
+%% #alert{}
+%%
+%% Hello = #client_hello{} | #server_hello{}
+%% Info = ConnectionStates | {Port, Session, ConnectionStates}
+%% ConnectionStates = #connection_states{}
+%%
+%% Description: Handles a recieved hello message
+%%--------------------------------------------------------------------
+hello(#server_hello{cipher_suite = CipherSuite, server_version = Version,
+ compression_method = Compression, random = Random,
+ session_id = SessionId}, ConnectionStates) ->
+ NewConnectionStates =
+ hello_pending_connection_states(client, CipherSuite, Random,
+ Compression, ConnectionStates),
+ {Version, SessionId, NewConnectionStates};
+
+hello(#client_hello{client_version = ClientVersion, random = Random} = Hello,
+ {Port, #ssl_options{versions = Versions} = SslOpts,
+ Session0, Cache, CacheCb, ConnectionStates0}) ->
+ Version = select_version(ClientVersion, Versions),
+ case ssl_record:is_acceptable_version(Version) of
+ true ->
+ {Type, #session{cipher_suite = CipherSuite,
+ compression_method = Compression} = Session}
+ = select_session(Hello, Port, Session0, Version,
+ SslOpts, Cache, CacheCb),
+ case CipherSuite of
+ no_suite ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
+ _ ->
+ ConnectionStates =
+ hello_pending_connection_states(server,
+ CipherSuite,
+ Random,
+ Compression,
+ ConnectionStates0),
+ {Version, {Type, Session}, ConnectionStates}
+ end;
+ false ->
+ ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: certify(Certs, CertDbRef, MaxPathLen) ->
+%% {PeerCert, PublicKeyInfo} | #alert{}
+%%
+%% Certs = #certificate{}
+%% CertDbRef = reference()
+%% MaxPathLen = integer() | nolimit
+%%
+%% Description: Handles a certificate handshake message
+%%--------------------------------------------------------------------
+certify(#certificate{asn1_certificates = ASN1Certs}, CertDbRef,
+ MaxPathLen, Verify, VerifyFun) ->
+ [PeerCert | _] = ASN1Certs,
+ VerifyBool = verify_bool(Verify),
+
+ try
+ %% Allow missing root_cert and check that with VerifyFun
+ ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbRef, false) of
+ {TrustedErlCert, CertPath, VerifyErrors} ->
+ Result = public_key:pkix_path_validation(TrustedErlCert,
+ CertPath,
+ [{max_path_length,
+ MaxPathLen},
+ {verify, VerifyBool},
+ {acc_errors,
+ VerifyErrors}]),
+ case Result of
+ {error, Reason} ->
+ path_validation_alert(Reason, Verify);
+ {ok, {PublicKeyInfo,_, []}} ->
+ {PeerCert, PublicKeyInfo};
+ {ok, {PublicKeyInfo,_, AccErrors = [Error | _]}} ->
+ case VerifyFun(AccErrors) of
+ true ->
+ {PeerCert, PublicKeyInfo};
+ false ->
+ path_validation_alert(Error, Verify)
+ end
+ end
+ catch
+ throw:Alert ->
+ Alert
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: certificate(OwnCert, CertDbRef, Role) -> #certificate{}
+%%
+%% OwnCert = binary()
+%% CertDbRef = term() as returned by ssl_certificate_db:create()
+%%
+%% Description: Creates a certificate message.
+%%--------------------------------------------------------------------
+certificate(OwnCert, CertDbRef, client) ->
+ Chain =
+ case ssl_certificate:certificate_chain(OwnCert, CertDbRef) of
+ {ok, CertChain} ->
+ CertChain;
+ {error, _} ->
+ %% If no suitable certificate is available, the client
+ %% SHOULD send a certificate message containing no
+ %% certificates. (chapter 7.4.6. rfc 4346)
+ []
+ end,
+ #certificate{asn1_certificates = Chain};
+
+certificate(OwnCert, CertDbRef, server) ->
+ case ssl_certificate:certificate_chain(OwnCert, CertDbRef) of
+ {ok, Chain} ->
+ #certificate{asn1_certificates = Chain};
+ {error, _} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR)
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: client_certificate_verify(Cert, ConnectionStates) ->
+%% #certificate_verify{} | ignore
+%% Cert = #'OTPcertificate'{}
+%% ConnectionStates = #connection_states{}
+%%
+%% Description: Creates a certificate_verify message, called by the client.
+%%--------------------------------------------------------------------
+client_certificate_verify(undefined, _, _, _, _, _) ->
+ ignore;
+client_certificate_verify(_, _, _, _, undefined, _) ->
+ ignore;
+client_certificate_verify(OwnCert, MasterSecret, Version, Algorithm,
+ PrivateKey, {Hashes0, _}) ->
+ case public_key:pkix_is_fixed_dh_cert(OwnCert) of
+ true ->
+ ignore;
+ false ->
+ Hashes =
+ calc_certificate_verify(Version, MasterSecret,
+ Algorithm, Hashes0),
+ Signed = digitally_signed(Hashes, PrivateKey),
+ #certificate_verify{signature = Signed}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: certificate_verify(Signature, PublicKeyInfo) -> valid | #alert{}
+%%
+%% Signature = binary()
+%% PublicKeyInfo = {Algorithm, PublicKey, PublicKeyParams}
+%%
+%% Description: Checks that the certificate_verify message is valid.
+%%--------------------------------------------------------------------
+certificate_verify(Signature, {_, PublicKey, _}, Version,
+ MasterSecret, Algorithm, {_, Hashes0})
+ when Algorithm =:= rsa; Algorithm =:= dh_rsa; Algorithm =:= dhe_rsa ->
+ Hashes = calc_certificate_verify(Version, MasterSecret,
+ Algorithm, Hashes0),
+ case public_key:decrypt_public(Signature, PublicKey,
+ [{rsa_pad, rsa_pkcs1_padding}]) of
+ Hashes ->
+ valid;
+ _ ->
+ ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE)
+ end.
+%% TODO dsa clause
+
+%%--------------------------------------------------------------------
+%% Function: certificate_request(ConnectionStates, CertDbRef) ->
+%% #certificate_request{}
+%%
+%% Description: Creates a certificate_request message, called by the server.
+%%--------------------------------------------------------------------
+certificate_request(ConnectionStates, CertDbRef) ->
+ #connection_state{security_parameters =
+ #security_parameters{cipher_suite = CipherSuite}} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ Types = certificate_types(CipherSuite),
+ Authorities = certificate_authorities(CertDbRef),
+ #certificate_request{
+ certificate_types = Types,
+ certificate_authorities = Authorities
+ }.
+
+%%--------------------------------------------------------------------
+%% Function: key_exchange(Role, Secret, Params) ->
+%% #client_key_exchange{} | #server_key_exchange{}
+%%
+%% Secret -
+%% Params -
+%%
+%% Description: Creates a keyexchange message.
+%%--------------------------------------------------------------------
+key_exchange(client, {premaster_secret, Secret, {_, PublicKey, _}}) ->
+ EncPremasterSecret =
+ encrypted_premaster_secret(Secret, PublicKey),
+ #client_key_exchange{exchange_keys = EncPremasterSecret};
+key_exchange(client, fixed_diffie_hellman) ->
+ #client_key_exchange{exchange_keys =
+ #client_diffie_hellman_public{
+ dh_public = <<>>
+ }};
+key_exchange(client, {dh, PublicKey}) ->
+ Len = byte_size(PublicKey),
+ #client_key_exchange{
+ exchange_keys = #client_diffie_hellman_public{
+ dh_public = <<?UINT16(Len), PublicKey/binary>>}
+ };
+
+%% key_exchange(server, {{?'dhpublicnumber', _PublicKey,
+%% #'DomainParameters'{p = P, g = G, y = Y},
+%% SignAlgorithm, ClientRandom, ServerRandom}}) ->
+%% ServerDHParams = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y},
+%% PLen = byte_size(P),
+%% GLen = byte_size(G),
+%% YLen = byte_size(Y),
+%% Hash = server_key_exchange_hash(SignAlgorithm, <<ClientRandom/binary,
+%% ServerRandom/binary,
+%% ?UINT16(PLen), P/binary,
+%% ?UINT16(GLen), G/binary,
+%% ?UINT16(YLen), Y/binary>>),
+%% Signed = digitally_signed(Hash, PrivateKey),
+%% #server_key_exchange{
+%% params = ServerDHParams,
+%% signed_params = Signed
+%% };
+key_exchange(_, _) ->
+ %%TODO : Real imp
+ #server_key_exchange{}.
+
+%%--------------------------------------------------------------------
+%% Function: master_secret(Version, Session/PremasterSecret,
+%% ConnectionStates, Role) ->
+%% {MasterSecret, NewConnectionStates} | #alert{}
+%% Version = #protocol_version{}
+%% Session = #session{} (session contains master secret)
+%% PremasterSecret = binary()
+%% ConnectionStates = #connection_states{}
+%% Role = client | server
+%%
+%% Description: Sets or calculates the master secret and calculate keys,
+%% updating the pending connection states. The Mastersecret and the update
+%% connection states are returned or an alert if the calculation fails.
+%%-------------------------------------------------------------------
+master_secret(Version, #session{master_secret = Mastersecret},
+ ConnectionStates, Role) ->
+ ConnectionState =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ SecParams = ConnectionState#connection_state.security_parameters,
+ try master_secret(Version, Mastersecret, SecParams,
+ ConnectionStates, Role)
+ catch
+ exit:Reason ->
+ error_logger:error_report("Key calculation failed due to ~p",
+ [Reason]),
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
+ end;
+
+master_secret(Version, PremasterSecret, ConnectionStates, Role) ->
+ ConnectionState =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ SecParams = ConnectionState#connection_state.security_parameters,
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ try master_secret(Version,
+ calc_master_secret(Version,PremasterSecret,
+ ClientRandom, ServerRandom),
+ SecParams, ConnectionStates, Role)
+ catch
+ exit:Reason ->
+ error_logger:error_report("Master secret calculation failed"
+ " due to ~p", [Reason]),
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: finished(Version, Role, MacSecret, Hashes) -> #finished{}
+%%
+%% ConnectionStates = #connection_states{}
+%%
+%% Description: Creates a handshake finished message
+%%-------------------------------------------------------------------
+finished(Version, Role, MasterSecret, {Hashes, _}) -> % use the current hashes
+ #finished{verify_data =
+ calc_finished(Version, Role, MasterSecret, Hashes)}.
+
+%%--------------------------------------------------------------------
+%% Function: verify_connection(Finished, Role,
+%% MasterSecret, Hashes) -> verified | #alert{}
+%%
+%% Finished = #finished{}
+%% Role = client | server - the role of the process that sent the finished
+%% message.
+%% MasterSecret = binary()
+%% Hashes = binary() - {md5_hash, sha_hash}
+%%
+%%
+%% Description: Checks the ssl handshake finished message to verify
+%% the connection.
+%%-------------------------------------------------------------------
+verify_connection(Version, #finished{verify_data = Data},
+ Role, MasterSecret, {_, {MD5, SHA}}) ->
+ %% use the previous hashes
+ ?DBG_HEX(crypto:md5_final(MD5)),
+ ?DBG_HEX(crypto:sha_final(SHA)),
+ case calc_finished(Version, Role, MasterSecret, {MD5, SHA}) of
+ Data ->
+ verified;
+ _E ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
+ end.
+
+server_hello_done() ->
+ #server_hello_done{}.
+
+%%--------------------------------------------------------------------
+%% Function: encode_handshake(HandshakeRec) -> BinHandshake
+%% HandshakeRec = #client_hello | #server_hello{} | server_hello_done |
+%% #certificate{} | #client_key_exchange{} | #finished{} |
+%% #client_certify_request{}
+%%
+%% encode a handshake packet to binary
+%%--------------------------------------------------------------------
+encode_handshake(Package, Version, SigAlg) ->
+ {MsgType, Bin} = enc_hs(Package, Version, SigAlg),
+ Len = byte_size(Bin),
+ [MsgType, ?uint24(Len), Bin].
+
+%%--------------------------------------------------------------------
+%% Function: get_tls_handshake(Data, Buffer) -> Result
+%% Result = {[#handshake{}], [Raw], NewBuffer}
+%% Data = Buffer = NewBuffer = Raw = binary()
+%%
+%% Description: Given buffered and new data from ssl_record, collects
+%% and returns it as a list of #handshake, also returns leftover
+%% data.
+%%--------------------------------------------------------------------
+get_tls_handshake(Data, <<>>, KeyAlg, Version) ->
+ get_tls_handshake_aux(Data, KeyAlg, Version, []);
+get_tls_handshake(Data, Buffer, KeyAlg, Version) ->
+ get_tls_handshake_aux(list_to_binary([Buffer, Data]),
+ KeyAlg, Version, []).
+
+get_tls_handshake_aux(<<?BYTE(Type), ?UINT24(Length), Body:Length/binary,Rest/binary>>,
+ KeyAlg, Version, Acc) ->
+ Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>,
+ H = dec_hs(Type, Body, KeyAlg, Version),
+ get_tls_handshake_aux(Rest, KeyAlg, Version, [{H,Raw} | Acc]);
+get_tls_handshake_aux(Data, _KeyAlg, _Version, Acc) ->
+ {lists:reverse(Acc), Data}.
+
+%%--------------------------------------------------------------------
+%% Function: sig_alg(atom()) -> integer()
+%%
+%% Description: Convert from key exchange as atom to signature
+%% algorithm as a ?SIGNATURE_... constant
+%%--------------------------------------------------------------------
+
+sig_alg(dh_anon) ->
+ ?SIGNATURE_ANONYMOUS;
+sig_alg(Alg) when Alg == dhe_rsa; Alg == rsa; Alg == dh_rsa ->
+ ?SIGNATURE_RSA;
+sig_alg(Alg) when Alg == dh_dss; Alg == dhe_dss ->
+ ?SIGNATURE_DSA;
+sig_alg(_) ->
+ ?NULL.
+
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+verify_bool(verify_peer) ->
+ true;
+verify_bool(verify_none) ->
+ false.
+
+path_validation_alert({bad_cert, cert_expired}, _) ->
+ ?ALERT_REC(?FATAL, ?CERTIFICATE_EXPIRED);
+path_validation_alert({bad_cert, invalid_issuer}, _) ->
+ ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
+path_validation_alert({bad_cert, invalid_signature} , _) ->
+ ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
+path_validation_alert({bad_cert, name_not_permitted}, _) ->
+ ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
+path_validation_alert({bad_cert, unknown_critical_extension}, _) ->
+ ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE);
+path_validation_alert({bad_cert, cert_revoked}, _) ->
+ ?ALERT_REC(?FATAL, ?CERTIFICATE_REVOKED);
+path_validation_alert(_, _) ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE).
+
+select_session(Hello, Port, Session, Version,
+ #ssl_options{ciphers = UserSuites} = SslOpts, Cache, CacheCb) ->
+ SuggestedSessionId = Hello#client_hello.session_id,
+ SessionId = ssl_manager:server_session_id(Port, SuggestedSessionId,
+ SslOpts),
+
+ Suites = case UserSuites of
+ [] ->
+ ssl_cipher:suites(Version);
+ _ ->
+ UserSuites
+ end,
+
+ case ssl_session:is_new(SuggestedSessionId, SessionId) of
+ true ->
+ CipherSuite =
+ select_cipher_suite(Hello#client_hello.cipher_suites, Suites),
+ Compressions = Hello#client_hello.compression_methods,
+ Compression = select_compression(Compressions),
+ {new, Session#session{session_id = SessionId,
+ cipher_suite = CipherSuite,
+ compression_method = Compression}};
+ false ->
+ {resumed, CacheCb:lookup(Cache, {Port, SessionId})}
+ end.
+
+%% Update pending connection states with parameters exchanged via
+%% hello messages
+%% NOTE : Role is the role of the receiver of the hello message
+%% currently being processed.
+hello_pending_connection_states(Role, CipherSuite, Random, Compression,
+ ConnectionStates) ->
+ ReadState =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ WriteState =
+ ssl_record:pending_connection_state(ConnectionStates, write),
+
+ NewReadSecParams =
+ hello_security_parameters(Role, ReadState, CipherSuite,
+ Random, Compression),
+
+ NewWriteSecParams =
+ hello_security_parameters(Role, WriteState, CipherSuite,
+ Random, Compression),
+
+ ssl_record:update_security_params(NewReadSecParams,
+ NewWriteSecParams,
+ ConnectionStates).
+
+hello_security_parameters(client, ConnectionState, CipherSuite, Random,
+ Compression) ->
+ SecParams = ConnectionState#connection_state.security_parameters,
+ NewSecParams = ssl_cipher:security_parameters(CipherSuite, SecParams),
+ NewSecParams#security_parameters{
+ server_random = Random,
+ compression_algorithm = Compression
+ };
+
+hello_security_parameters(server, ConnectionState, CipherSuite, Random,
+ Compression) ->
+ SecParams = ConnectionState#connection_state.security_parameters,
+ NewSecParams = ssl_cipher:security_parameters(CipherSuite, SecParams),
+ NewSecParams#security_parameters{
+ client_random = Random,
+ compression_algorithm = Compression
+ }.
+
+select_version(ClientVersion, Versions) ->
+ Fun = fun(Version) ->
+ ssl_record:protocol_version(Version)
+ end,
+ ServerVersion = ssl_record:highest_protocol_version(lists:map(Fun,
+ Versions)),
+ ssl_record:lowest_protocol_version(ClientVersion, ServerVersion).
+
+select_cipher_suite([], _) ->
+ no_suite;
+select_cipher_suite([Suite | ClientSuites], SupportedSuites) ->
+ case is_member(Suite, SupportedSuites) of
+ true ->
+ Suite;
+ false ->
+ select_cipher_suite(ClientSuites, SupportedSuites)
+ end.
+
+is_member(Suite, SupportedSuites) ->
+ lists:member(Suite, SupportedSuites).
+
+select_compression(_CompressionMetodes) ->
+ ?NULL.
+
+master_secret(Version, MasterSecret, #security_parameters{
+ client_random = ClientRandom,
+ server_random = ServerRandom,
+ hash_size = HashSize,
+ key_material_length = KML,
+ expanded_key_material_length = EKML,
+ iv_size = IVS,
+ exportable = Exportable},
+ ConnectionStates, Role) ->
+ {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
+ ServerWriteKey, ClientIV, ServerIV} =
+ setup_keys(Version, Exportable, MasterSecret, ServerRandom,
+ ClientRandom, HashSize, KML, EKML, IVS),
+ ?DBG_HEX(ClientWriteKey),
+ ?DBG_HEX(ClientIV),
+ ConnStates1 = ssl_record:set_master_secret(MasterSecret, ConnectionStates),
+ ConnStates2 =
+ ssl_record:set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret,
+ Role, ConnStates1),
+
+ ClientCipherState = #cipher_state{iv = ClientIV, key = ClientWriteKey},
+ ServerCipherState = #cipher_state{iv = ServerIV, key = ServerWriteKey},
+ {MasterSecret,
+ ssl_record:set_pending_cipher_state(ConnStates2, ClientCipherState,
+ ServerCipherState, Role)}.
+
+
+dec_hs(?HELLO_REQUEST, <<>>, _, _) ->
+ #hello_request{};
+
+%% Client hello v2.
+%% The server must be able to receive such messages, from clients that
+%% are willing to use ssl v3 or higher, but have ssl v2 compatibility.
+dec_hs(?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor),
+ ?UINT16(CSLength), ?UINT16(0),
+ ?UINT16(CDLength),
+ CipherSuites:CSLength/binary,
+ ChallengeData:CDLength/binary>>,
+ _, _) ->
+ ?DBG_HEX(CipherSuites),
+ ?DBG_HEX(CipherSuites),
+ #client_hello{client_version = {Major, Minor},
+ random = ssl_ssl2:client_random(ChallengeData, CDLength),
+ session_id = 0,
+ cipher_suites = from_3bytes(CipherSuites),
+ compression_methods = [?NULL]
+ };
+dec_hs(?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+ ?BYTE(SID_length), Session_ID:SID_length/binary,
+ ?UINT16(Cs_length), CipherSuites:Cs_length/binary,
+ ?BYTE(Cm_length), Comp_methods:Cm_length/binary,
+ _FutureCompatData/binary>>,
+ _, _) ->
+ #client_hello{
+ client_version = {Major,Minor},
+ random = Random,
+ session_id = Session_ID,
+ cipher_suites = from_2bytes(CipherSuites),
+ compression_methods = Comp_methods
+ };
+dec_hs(?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+ ?BYTE(SID_length), Session_ID:SID_length/binary,
+ Cipher_suite:2/binary, ?BYTE(Comp_method)>>, _, _) ->
+ #server_hello{
+ server_version = {Major,Minor},
+ random = Random,
+ session_id = Session_ID,
+ cipher_suite = Cipher_suite,
+ compression_method = Comp_method
+ };
+dec_hs(?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>, _, _) ->
+ #certificate{asn1_certificates = certs_to_list(ASN1Certs)};
+dec_hs(?SERVER_KEY_EXCHANGE, <<?UINT16(ModLen), Mod:ModLen/binary,
+ ?UINT16(ExpLen), Exp:ExpLen/binary,
+ Sig/binary>>,
+ ?KEY_EXCHANGE_RSA, _) ->
+ #server_key_exchange{params = #server_rsa_params{rsa_modulus = Mod,
+ rsa_exponent = Exp},
+ signed_params = Sig};
+dec_hs(?SERVER_KEY_EXCHANGE, <<?UINT16(PLen), P:PLen/binary,
+ ?UINT16(GLen), G:GLen/binary,
+ ?UINT16(YLen), Y:YLen/binary,
+ Sig/binary>>,
+ ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) ->
+ #server_key_exchange{params = #server_dh_params{dh_p = P,dh_g = G, dh_y = Y},
+ signed_params = Sig};
+dec_hs(?CERTIFICATE_REQUEST,
+ <<?BYTE(CertTypesLen), CertTypes:CertTypesLen/binary,
+ ?UINT16(CertAuthsLen), CertAuths:CertAuthsLen/binary>>, _, _) ->
+ %% TODO: maybe we should chop up CertAuths into a list?
+ #certificate_request{certificate_types = CertTypes,
+ certificate_authorities = CertAuths};
+dec_hs(?SERVER_HELLO_DONE, <<>>, _, _) ->
+ #server_hello_done{};
+dec_hs(?CERTIFICATE_VERIFY,<<?UINT16(_), Signature/binary>>, _, _)->
+ #certificate_verify{signature = Signature};
+dec_hs(?CLIENT_KEY_EXCHANGE, PKEPMS, rsa, {3, 0}) ->
+ PreSecret = #encrypted_premaster_secret{premaster_secret = PKEPMS},
+ #client_key_exchange{exchange_keys = PreSecret};
+dec_hs(?CLIENT_KEY_EXCHANGE, <<?UINT16(_), PKEPMS/binary>>, rsa, _) ->
+ PreSecret = #encrypted_premaster_secret{premaster_secret = PKEPMS},
+ #client_key_exchange{exchange_keys = PreSecret};
+dec_hs(?CLIENT_KEY_EXCHANGE, <<>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) ->
+ %% TODO: Should check whether the cert already contains a suitable DH-key (7.4.7.2)
+ throw(?ALERT_REC(?FATAL, implicit_public_value_encoding));
+dec_hs(?CLIENT_KEY_EXCHANGE, <<?UINT16(DH_YCLen), DH_YC:DH_YCLen/binary>>,
+ ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) ->
+ #client_diffie_hellman_public{dh_public = DH_YC};
+dec_hs(?FINISHED, VerifyData, _, _) ->
+ #finished{verify_data = VerifyData};
+dec_hs(_, _, _, _) ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)).
+
+encrypted_premaster_secret(Secret, RSAPublicKey) ->
+ try
+ PreMasterSecret = public_key:encrypt_public(Secret, RSAPublicKey,
+ [{rsa_pad,
+ rsa_pkcs1_padding}]),
+ #encrypted_premaster_secret{premaster_secret = PreMasterSecret}
+ catch
+ _:_->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE))
+ end.
+
+decrypt_premaster_secret(Secret, RSAPrivateKey) ->
+ try public_key:decrypt_private(Secret, RSAPrivateKey,
+ [{rsa_pad, rsa_pkcs1_padding}])
+ catch
+ _:_ ->
+ throw(?ALERT_REC(?FATAL, ?DECRYPTION_FAILED))
+ end.
+
+%% encode/decode stream of certificate data to/from list of certificate data
+certs_to_list(ASN1Certs) ->
+ certs_to_list(ASN1Certs, []).
+
+certs_to_list(<<?UINT24(CertLen), Cert:CertLen/binary, Rest/binary>>, Acc) ->
+ certs_to_list(Rest, [Cert | Acc]);
+certs_to_list(<<>>, Acc) ->
+ lists:reverse(Acc, []).
+
+certs_from_list(ACList) ->
+ list_to_binary([begin
+ CertLen = byte_size(Cert),
+ <<?UINT24(CertLen), Cert/binary>>
+ end || Cert <- ACList]).
+
+enc_hs(#hello_request{}, _Version, _) ->
+ {?HELLO_REQUEST, <<>>};
+enc_hs(#client_hello{
+ client_version = {Major, Minor},
+ random = Random,
+ session_id = SessionID,
+ cipher_suites = CipherSuites,
+ compression_methods = CompMethods}, _Version, _) ->
+ SIDLength = byte_size(SessionID),
+ BinCompMethods = list_to_binary(CompMethods),
+ CmLength = byte_size(BinCompMethods),
+ BinCipherSuites = list_to_binary(CipherSuites),
+ CsLength = byte_size(BinCipherSuites),
+ {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+ ?BYTE(SIDLength), SessionID/binary,
+ ?UINT16(CsLength), BinCipherSuites/binary,
+ ?BYTE(CmLength), BinCompMethods/binary>>};
+enc_hs(#server_hello{
+ server_version = {Major, Minor},
+ random = Random,
+ session_id = Session_ID,
+ cipher_suite = Cipher_suite,
+ compression_method = Comp_method}, _Version, _) ->
+ SID_length = byte_size(Session_ID),
+ {?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+ ?BYTE(SID_length), Session_ID/binary,
+ Cipher_suite/binary, ?BYTE(Comp_method)>>};
+enc_hs(#certificate{asn1_certificates = ASN1CertList}, _Version, _) ->
+ ASN1Certs = certs_from_list(ASN1CertList),
+ ACLen = erlang:iolist_size(ASN1Certs),
+ {?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>};
+enc_hs(#server_key_exchange{params = #server_rsa_params{rsa_modulus = Mod,
+ rsa_exponent = Exp},
+ signed_params = SignedParams}, _Version, _) ->
+ ModLen = byte_size(Mod),
+ ExpLen = byte_size(Exp),
+ {?SERVER_KEY_EXCHANGE, <<?UINT16(ModLen), Mod/binary,
+ ?UINT16(ExpLen), Exp/binary,
+ SignedParams/binary>>
+ };
+enc_hs(#server_key_exchange{params = #server_dh_params{
+ dh_p = P, dh_g = G, dh_y = Y},
+ signed_params = SignedParams}, _Version, _) ->
+ PLen = byte_size(P),
+ GLen = byte_size(G),
+ YLen = byte_size(Y),
+ {?SERVER_KEY_EXCHANGE, <<?UINT16(PLen), P:PLen/binary,
+ ?UINT16(GLen), G:GLen/binary,
+ ?UINT16(YLen), Y:YLen/binary,
+ SignedParams/binary>>
+ };
+enc_hs(#certificate_request{certificate_types = CertTypes,
+ certificate_authorities = CertAuths},
+ _Version, _) ->
+ CertTypesLen = byte_size(CertTypes),
+ CertAuthsLen = byte_size(CertAuths),
+ {?CERTIFICATE_REQUEST,
+ <<?BYTE(CertTypesLen), CertTypes/binary,
+ ?UINT16(CertAuthsLen), CertAuths/binary>>
+ };
+enc_hs(#server_hello_done{}, _Version, _) ->
+ {?SERVER_HELLO_DONE, <<>>};
+enc_hs(#client_key_exchange{exchange_keys = ExchangeKeys}, Version, _) ->
+ {?CLIENT_KEY_EXCHANGE, enc_cke(ExchangeKeys, Version)};
+enc_hs(#certificate_verify{signature = BinSig}, _, _) ->
+ EncSig = enc_bin_sig(BinSig),
+ {?CERTIFICATE_VERIFY, EncSig};
+enc_hs(#finished{verify_data = VerifyData}, _Version, _) ->
+ {?FINISHED, VerifyData}.
+
+enc_cke(#encrypted_premaster_secret{premaster_secret = PKEPMS},{3, 0}) ->
+ PKEPMS;
+enc_cke(#encrypted_premaster_secret{premaster_secret = PKEPMS}, _) ->
+ PKEPMSLen = byte_size(PKEPMS),
+ <<?UINT16(PKEPMSLen), PKEPMS/binary>>;
+enc_cke(#client_diffie_hellman_public{dh_public = DHPublic}, _) ->
+ Len = byte_size(DHPublic),
+ <<?UINT16(Len), DHPublic/binary>>.
+
+enc_bin_sig(BinSig) ->
+ Size = byte_size(BinSig),
+ <<?UINT16(Size), BinSig/binary>>.
+
+init_hashes() ->
+ T = {crypto:md5_init(), crypto:sha_init()},
+ {T, T}.
+
+update_hashes(Hashes, % special-case SSL2 client hello
+ <<?CLIENT_HELLO, ?UINT24(_), ?BYTE(Major), ?BYTE(Minor),
+ ?UINT16(CSLength), ?UINT16(0),
+ ?UINT16(CDLength),
+ CipherSuites:CSLength/binary,
+ ChallengeData:CDLength/binary>>) ->
+ update_hashes(Hashes,
+ <<?CLIENT_HELLO, ?BYTE(Major), ?BYTE(Minor),
+ ?UINT16(CSLength), ?UINT16(0),
+ ?UINT16(CDLength),
+ CipherSuites:CSLength/binary,
+ ChallengeData:CDLength/binary>>);
+update_hashes({{MD50, SHA0}, _Prev}, Data) ->
+ ?DBG_HEX(Data),
+ {MD51, SHA1} = {crypto:md5_update(MD50, Data),
+ crypto:sha_update(SHA0, Data)},
+ ?DBG_HEX(crypto:md5_final(MD51)),
+ ?DBG_HEX(crypto:sha_final(SHA1)),
+ {{MD51, SHA1}, {MD50, SHA0}}.
+
+from_3bytes(Bin3) ->
+ from_3bytes(Bin3, []).
+
+from_3bytes(<<>>, Acc) ->
+ lists:reverse(Acc);
+from_3bytes(<<?UINT24(N), Rest/binary>>, Acc) ->
+ from_3bytes(Rest, [?uint16(N) | Acc]).
+
+from_2bytes(Bin2) ->
+ from_2bytes(Bin2, []).
+
+from_2bytes(<<>>, Acc) ->
+ lists:reverse(Acc);
+from_2bytes(<<?UINT16(N), Rest/binary>>, Acc) ->
+ from_2bytes(Rest, [?uint16(N) | Acc]).
+
+certificate_types({KeyExchange, _, _, _})
+ when KeyExchange == rsa;
+ KeyExchange == dh_dss;
+ KeyExchange == dh_rsa;
+ KeyExchange == dhe_dss;
+ KeyExchange == dhe_rsa ->
+ <<?BYTE(?RSA_SIGN), ?BYTE(?DSS_SIGN)>>;
+
+certificate_types(_) ->
+ %%TODO: Is this a good default,
+ %% is there a case where we like to request
+ %% a RSA_FIXED_DH or DSS_FIXED_DH
+ <<?BYTE(?RSA_SIGN)>>.
+
+certificate_authorities(_) ->
+ %%TODO Make list of know CA:s
+ <<>>.
+
+digitally_signed(Hashes, #'RSAPrivateKey'{} = Key) ->
+ public_key:encrypt_private(Hashes, Key,
+ [{rsa_pad, rsa_pkcs1_padding}]);
+digitally_signed(Hashes, #'DSAPrivateKey'{} = Key) ->
+ public_key:sign(Hashes, Key).
+
+
+calc_master_secret({3,0}, PremasterSecret, ClientRandom, ServerRandom) ->
+ ssl_ssl3:master_secret(PremasterSecret, ClientRandom, ServerRandom);
+
+calc_master_secret({3,N},PremasterSecret, ClientRandom, ServerRandom)
+ when N == 1; N == 2 ->
+ ssl_tls1:master_secret(PremasterSecret, ClientRandom, ServerRandom).
+
+setup_keys({3,0}, Exportable, MasterSecret,
+ ServerRandom, ClientRandom, HashSize, KML, EKML, IVS) ->
+ ssl_ssl3:setup_keys(Exportable, MasterSecret, ServerRandom,
+ ClientRandom, HashSize, KML, EKML, IVS);
+
+setup_keys({3,1}, _Exportable, MasterSecret,
+ ServerRandom, ClientRandom, HashSize, KML, _EKML, IVS) ->
+ ssl_tls1:setup_keys(MasterSecret, ServerRandom, ClientRandom, HashSize,
+ KML, IVS);
+
+setup_keys({3,2}, _Exportable, MasterSecret,
+ ServerRandom, ClientRandom, HashSize, KML, _EKML, _IVS) ->
+ ssl_tls1:setup_keys(MasterSecret, ServerRandom,
+ ClientRandom, HashSize, KML).
+
+calc_finished({3, 0}, Role, MasterSecret, Hashes) ->
+ ssl_ssl3:finished(Role, MasterSecret, Hashes);
+calc_finished({3, N}, Role, MasterSecret, Hashes)
+ when N == 1; N == 2 ->
+ ssl_tls1:finished(Role, MasterSecret, Hashes).
+
+calc_certificate_verify({3, 0}, MasterSecret, Algorithm, Hashes) ->
+ ssl_ssl3:certificate_verify(Algorithm, MasterSecret, Hashes);
+calc_certificate_verify({3, N}, _, Algorithm, Hashes)
+ when N == 1; N == 2 ->
+ ssl_tls1:certificate_verify(Algorithm, Hashes).
+
+%% server_key_exchange_hash(Algorithm, Value) when Algorithm == rsa;
+%% Algorithm == dh_rsa;
+%% Algorithm == dhe_rsa ->
+%% MD5 = crypto:md5_final(Value),
+%% SHA = crypto:sha_final(Value),
+%% <<MD5/binary, SHA/binary>>;
+
+%% server_key_exchange_hash(Algorithm, Value) when Algorithm == dh_dss;
+%% Algorithm == dhe_dss ->
+%% crypto:sha_final(Value).