diff options
Diffstat (limited to 'lib/ssl/src/ssl_connection.erl')
-rw-r--r-- | lib/ssl/src/ssl_connection.erl | 645 |
1 files changed, 599 insertions, 46 deletions
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 8f4fd88d42..54eed03d3c 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -34,6 +34,8 @@ -include("ssl_record.hrl"). -include("ssl_cipher.hrl"). -include("ssl_internal.hrl"). +-include("ssl_srp.hrl"). +-include("ssl_srp_primes.hrl"). -include_lib("public_key/include/public_key.hrl"). %% Internal application API @@ -73,7 +75,6 @@ session_cache, % session_cache_cb, % negotiated_version, % tls_version() - supported_protocol_versions, % [atom()] client_certificate_requested = false, key_algorithm, % atom as defined by cipher_suite hashsign_algorithm, % atom as defined by cipher_suite @@ -81,6 +82,9 @@ private_key, % PKIX: #'RSAPrivateKey'{} diffie_hellman_params, % PKIX: #'DHParameter'{} relevant for server side diffie_hellman_keys, % {PublicKey, PrivateKey} + psk_identity, % binary() - server psk identity hint + srp_params, % #srp_user{} + srp_keys, % {PublicKey, PrivateKey} premaster_secret, % file_ref_db, % ets() cert_db_ref, % ref() @@ -94,7 +98,8 @@ terminated = false, % allow_renegotiate = true, expecting_next_protocol_negotiation = false :: boolean(), - next_protocol = undefined :: undefined | binary() + next_protocol = undefined :: undefined | binary(), + client_ecc % {Curves, PointFmt} }). -define(DEFAULT_DIFFIE_HELLMAN_PARAMS, @@ -412,11 +417,14 @@ hello(Hello = #client_hello{client_version = ClientVersion}, ssl_options = SslOpts}) -> case ssl_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) of - {Version, {Type, Session}, ConnectionStates, ProtocolsToAdvertise} -> - do_server_hello(Type, ProtocolsToAdvertise, State#state{connection_states = - ConnectionStates, - negotiated_version = Version, - session = Session}); + {Version, {Type, Session}, ConnectionStates, ProtocolsToAdvertise, + EcPointFormats, EllipticCurves} -> + do_server_hello(Type, ProtocolsToAdvertise, + EcPointFormats, EllipticCurves, + State#state{connection_states = ConnectionStates, + negotiated_version = Version, + session = Session, + client_ecc = {EllipticCurves, EcPointFormats}}); #alert{} = Alert -> handle_own_alert(Alert, ClientVersion, hello, State) end; @@ -472,6 +480,13 @@ abbreviated(#finished{verify_data = Data} = Finished, handle_own_alert(Alert, Version, abbreviated, State) end; +%% only allowed to send next_protocol message after change cipher spec +%% & before finished message and it is not allowed during renegotiation +abbreviated(#next_protocol{selected_protocol = SelectedProtocol}, + #state{role = server, expecting_next_protocol_negotiation = true} = State0) -> + {Record, State} = next_record(State0#state{next_protocol = SelectedProtocol}), + next_state(abbreviated, abbreviated, Record, State); + abbreviated(timeout, State) -> { next_state, abbreviated, State, hibernate }; @@ -522,7 +537,11 @@ certify(#certificate{} = Cert, certify(#server_key_exchange{} = KeyExchangeMsg, #state{role = client, negotiated_version = Version, key_algorithm = Alg} = State0) - when Alg == dhe_dss; Alg == dhe_rsa; Alg == dh_anon -> + when Alg == dhe_dss; Alg == dhe_rsa; + Alg == ecdhe_rsa; Alg == ecdhe_ecdsa; + Alg == dh_anon; Alg == ecdh_anon; + Alg == psk; Alg == dhe_psk; Alg == rsa_psk; + Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon -> case handle_server_key(KeyExchangeMsg, State0) of #state{} = State1 -> {Record, State} = next_record(State1), @@ -539,6 +558,45 @@ certify(#certificate_request{}, State0) -> {Record, State} = next_record(State0#state{client_certificate_requested = true}), next_state(certify, certify, Record, State); +%% PSK and RSA_PSK might bypass the Server-Key-Exchange +certify(#server_hello_done{}, + #state{session = #session{master_secret = undefined}, + negotiated_version = Version, + psk_identity = PSKIdentity, + premaster_secret = undefined, + role = client, + key_algorithm = Alg} = State0) + when Alg == psk -> + case server_psk_master_secret(PSKIdentity, State0) of + #state{} = State -> + client_certify_and_key_exchange(State); + #alert{} = Alert -> + handle_own_alert(Alert, Version, certify, State0) + end; + +certify(#server_hello_done{}, + #state{session = #session{master_secret = undefined}, + ssl_options = SslOpts, + negotiated_version = Version, + psk_identity = PSKIdentity, + premaster_secret = undefined, + role = client, + key_algorithm = Alg} = State0) + when Alg == rsa_psk -> + case handle_psk_identity(PSKIdentity, SslOpts#ssl_options.user_lookup_fun) of + {ok, PSK} when is_binary(PSK) -> + PremasterSecret = make_premaster_secret(Version, rsa), + Len = byte_size(PSK), + RealPMS = <<?UINT16(48), PremasterSecret/binary, ?UINT16(Len), PSK/binary>>, + State1 = State0#state{premaster_secret = PremasterSecret}, + State = master_from_premaster_secret(RealPMS, State1), + client_certify_and_key_exchange(State); + #alert{} = Alert -> + Alert; + _ -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) + end; + %% Master secret was determined with help of server-key exchange msg certify(#server_hello_done{}, #state{session = #session{master_secret = MasterSecret} = Session, @@ -617,9 +675,74 @@ certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey}, #state{negotiated_version = Version, diffie_hellman_params = #'DHParameter'{prime = P, + base = G} = Params, + diffie_hellman_keys = {_, ServerDhPrivateKey}} = State0) -> + case dh_master_secret(Params, ClientPublicDhKey, ServerDhPrivateKey, State0) of + #state{} = State1 -> + {Record, State} = next_record(State1), + next_state(certify, cipher, Record, State); + #alert{} = Alert -> + handle_own_alert(Alert, Version, certify, State0) + end; + +certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint}, + #state{negotiated_version = Version, + diffie_hellman_keys = ECDHKey} = State0) -> + case ec_dh_master_secret(ECDHKey, #'ECPoint'{point = ClientPublicEcDhPoint}, State0) of + #state{} = State1 -> + {Record, State} = next_record(State1), + next_state(certify, cipher, Record, State); + #alert{} = Alert -> + handle_own_alert(Alert, Version, certify, State0) + end; + +certify_client_key_exchange(#client_psk_identity{identity = ClientPSKIdentity}, + #state{negotiated_version = Version} = State0) -> + case server_psk_master_secret(ClientPSKIdentity, State0) of + #state{} = State1 -> + {Record, State} = next_record(State1), + next_state(certify, cipher, Record, State); + #alert{} = Alert -> + handle_own_alert(Alert, Version, certify, State0) + end; + +certify_client_key_exchange(#client_dhe_psk_identity{ + identity = ClientPSKIdentity, + dh_public = ClientPublicDhKey}, + #state{negotiated_version = Version, + diffie_hellman_params = #'DHParameter'{prime = P, base = G}, diffie_hellman_keys = {_, ServerDhPrivateKey}} = State0) -> - case dh_master_secret(crypto:mpint(P), crypto:mpint(G), ClientPublicDhKey, ServerDhPrivateKey, State0) of + case dhe_psk_master_secret(ClientPSKIdentity, P, G, ClientPublicDhKey, ServerDhPrivateKey, State0) of + #state{} = State1 -> + {Record, State} = next_record(State1), + next_state(certify, cipher, Record, State); + #alert{} = Alert -> + handle_own_alert(Alert, Version, certify, State0) + end; + +certify_client_key_exchange(#client_rsa_psk_identity{ + identity = PskIdentity, + exchange_keys = + #encrypted_premaster_secret{premaster_secret= EncPMS}}, + #state{negotiated_version = Version, + private_key = Key} = State0) -> + PremasterSecret = ssl_handshake:decrypt_premaster_secret(EncPMS, Key), + case server_rsa_psk_master_secret(PskIdentity, PremasterSecret, State0) of + #state{} = State1 -> + {Record, State} = next_record(State1), + next_state(certify, cipher, Record, State); + #alert{} = Alert -> + handle_own_alert(Alert, Version, certify, State0) + end; + +certify_client_key_exchange(#client_srp_public{srp_a = ClientPublicKey}, + #state{negotiated_version = Version, + srp_params = + #srp_user{prime = Prime, + verifier = Verifier} + } = State0) -> + case server_srp_master_secret(Verifier, Prime, ClientPublicKey, State0) of #state{} = State1 -> {Record, State} = next_record(State1), next_state(certify, cipher, Record, State); @@ -656,11 +779,10 @@ cipher(#certificate_verify{signature = Signature, hashsign_algorithm = CertHashS handle_own_alert(Alert, Version, cipher, State0) end; -% client must send a next protocol message if we are expecting it +%% client must send a next protocol message if we are expecting it cipher(#finished{}, #state{role = server, expecting_next_protocol_negotiation = true, next_protocol = undefined, negotiated_version = Version} = State0) -> - handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, cipher, State0), - {stop, normal, State0}; + handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, cipher, State0); cipher(#finished{verify_data = Data} = Finished, #state{negotiated_version = Version, @@ -682,8 +804,8 @@ cipher(#finished{verify_data = Data} = Finished, handle_own_alert(Alert, Version, cipher, State) end; -% only allowed to send next_protocol message after change cipher spec -% & before finished message and it is not allowed during renegotiation +%% only allowed to send next_protocol message after change cipher spec +%% & before finished message and it is not allowed during renegotiation cipher(#next_protocol{selected_protocol = SelectedProtocol}, #state{role = server, expecting_next_protocol_negotiation = true} = State0) -> {Record, State} = next_record(State0#state{next_protocol = SelectedProtocol}), @@ -1146,7 +1268,9 @@ init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHan init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, CertFile, client) -> try - [OwnCert] = ssl_certificate:file_to_certificats(CertFile, PemCacheHandle), + %% Ignoring potential proxy-certificates see: + %% http://dev.globus.org/wiki/Security/ProxyFileFormat + [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCacheHandle), {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, OwnCert} catch _Error:_Reason -> {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, undefined} @@ -1154,7 +1278,7 @@ init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHan init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, CertFile, server) -> try - [OwnCert] = ssl_certificate:file_to_certificats(CertFile, PemCacheHandle), + [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCacheHandle), {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, OwnCert} catch _:Reason -> @@ -1171,6 +1295,7 @@ init_private_key(DbHandle, undefined, KeyFile, Password, _) -> [PemEntry] = [PemEntry || PemEntry = {PKey, _ , _} <- List, PKey =:= 'RSAPrivateKey' orelse PKey =:= 'DSAPrivateKey' orelse + PKey =:= 'ECPrivateKey' orelse PKey =:= 'PrivateKeyInfo' ], private_key(public_key:pem_entry_decode(PemEntry, Password)) @@ -1184,6 +1309,8 @@ init_private_key(_,{rsa, PrivateKey}, _, _,_) -> init_private_key('RSAPrivateKey', PrivateKey); init_private_key(_,{dsa, PrivateKey},_,_,_) -> init_private_key('DSAPrivateKey', PrivateKey); +init_private_key(_,{ec, PrivateKey},_,_,_) -> + init_private_key('ECPrivateKey', PrivateKey); init_private_key(_,{Asn1Type, PrivateKey},_,_,_) -> private_key(init_private_key(Asn1Type, PrivateKey)). @@ -1199,6 +1326,7 @@ private_key(#'PrivateKeyInfo'{privateKeyAlgorithm = #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-dsa'}, privateKey = Key}) -> public_key:der_decode('DSAPrivateKey', iolist_to_binary(Key)); + private_key(Key) -> Key. @@ -1250,7 +1378,15 @@ handle_peer_cert(PeerCert, PublicKeyInfo, State1 = State0#state{session = Session#session{peer_certificate = PeerCert}, public_key_info = PublicKeyInfo}, - {Record, State} = next_record(State1), + State2 = case PublicKeyInfo of + {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey, PublicKeyParams} -> + ECDHKey = public_key:generate_key(PublicKeyParams), + State3 = State1#state{diffie_hellman_keys = ECDHKey}, + ec_dh_master_secret(ECDHKey, PublicKey, State3); + + _ -> State1 + end, + {Record, State} = next_record(State2), next_state(certify, certify, Record, State). certify_client(#state{client_certificate_requested = true, role = client, @@ -1300,15 +1436,18 @@ verify_client_cert(#state{client_certificate_requested = true, role = client, verify_client_cert(#state{client_certificate_requested = false} = State) -> State. -do_server_hello(Type, NextProtocolsToSend, #state{negotiated_version = Version, - session = #session{session_id = SessId}, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}} +do_server_hello(Type, NextProtocolsToSend, + EcPointFormats, EllipticCurves, + #state{negotiated_version = Version, + session = #session{session_id = SessId}, + connection_states = ConnectionStates0, + renegotiation = {Renegotiation, _}} = State0) when is_atom(Type) -> ServerHello = ssl_handshake:server_hello(SessId, Version, - ConnectionStates0, Renegotiation, NextProtocolsToSend), + ConnectionStates0, Renegotiation, + NextProtocolsToSend, EcPointFormats, EllipticCurves), State = server_hello(ServerHello, State0#state{expecting_next_protocol_negotiation = NextProtocolsToSend =/= undefined}), @@ -1439,7 +1578,8 @@ server_hello_done(#state{transport_cb = Transport, State#state{connection_states = ConnectionStates, tls_handshake_history = Handshake}. -certify_server(#state{key_algorithm = dh_anon} = State) -> +certify_server(#state{key_algorithm = Algo} = State) + when Algo == dh_anon; Algo == ecdh_anon; Algo == psk; Algo == dhe_psk; Algo == srp_anon -> State; certify_server(#state{transport_cb = Transport, @@ -1466,7 +1606,7 @@ key_exchange(#state{role = server, key_algorithm = rsa} = State) -> State; key_exchange(#state{role = server, key_algorithm = Algo, hashsign_algorithm = HashSignAlgo, - diffie_hellman_params = #'DHParameter'{prime = P, base = G} = Params, + diffie_hellman_params = #'DHParameter'{} = Params, private_key = PrivateKey, connection_states = ConnectionStates0, negotiated_version = Version, @@ -1477,13 +1617,13 @@ key_exchange(#state{role = server, key_algorithm = Algo, when Algo == dhe_dss; Algo == dhe_rsa; Algo == dh_anon -> - Keys = crypto:dh_generate_key([crypto:mpint(P), crypto:mpint(G)]), + DHKeys = public_key:generate_key(Params), ConnectionState = ssl_record:pending_connection_state(ConnectionStates0, read), SecParams = ConnectionState#connection_state.security_parameters, #security_parameters{client_random = ClientRandom, server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, Version, {dh, Keys, Params, + Msg = ssl_handshake:key_exchange(server, Version, {dh, DHKeys, Params, HashSignAlgo, ClientRandom, ServerRandom, PrivateKey}), @@ -1491,7 +1631,161 @@ key_exchange(#state{role = server, key_algorithm = Algo, encode_handshake(Msg, Version, ConnectionStates0, Handshake0), Transport:send(Socket, BinMsg), State#state{connection_states = ConnectionStates, - diffie_hellman_keys = Keys, + diffie_hellman_keys = DHKeys, + tls_handshake_history = Handshake}; + +key_exchange(#state{role = server, private_key = Key, key_algorithm = Algo} = State) + when Algo == ecdh_ecdsa; Algo == ecdh_rsa -> + State#state{diffie_hellman_keys = Key}; +key_exchange(#state{role = server, key_algorithm = Algo, + hashsign_algorithm = HashSignAlgo, + private_key = PrivateKey, + connection_states = ConnectionStates0, + negotiated_version = Version, + tls_handshake_history = Handshake0, + socket = Socket, + transport_cb = Transport + } = State) + when Algo == ecdhe_ecdsa; Algo == ecdhe_rsa; + Algo == ecdh_anon -> + + ECDHKeys = public_key:generate_key(select_curve(State)), + ConnectionState = + ssl_record:pending_connection_state(ConnectionStates0, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, Version, {ecdh, ECDHKeys, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + {BinMsg, ConnectionStates, Handshake1} = + encode_handshake(Msg, Version, ConnectionStates0, Handshake0), + Transport:send(Socket, BinMsg), + State#state{connection_states = ConnectionStates, + diffie_hellman_keys = ECDHKeys, + tls_handshake_history = Handshake1}; + +key_exchange(#state{role = server, key_algorithm = psk, + ssl_options = #ssl_options{psk_identity = undefined}} = State) -> + State; +key_exchange(#state{role = server, key_algorithm = psk, + ssl_options = #ssl_options{psk_identity = PskIdentityHint}, + hashsign_algorithm = HashSignAlgo, + private_key = PrivateKey, + connection_states = ConnectionStates0, + negotiated_version = Version, + tls_handshake_history = Handshake0, + socket = Socket, + transport_cb = Transport + } = State) -> + ConnectionState = + ssl_record:pending_connection_state(ConnectionStates0, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + {BinMsg, ConnectionStates, Handshake} = + encode_handshake(Msg, Version, ConnectionStates0, Handshake0), + Transport:send(Socket, BinMsg), + State#state{connection_states = ConnectionStates, + tls_handshake_history = Handshake}; + +key_exchange(#state{role = server, key_algorithm = dhe_psk, + ssl_options = #ssl_options{psk_identity = PskIdentityHint}, + hashsign_algorithm = HashSignAlgo, + diffie_hellman_params = #'DHParameter'{} = Params, + private_key = PrivateKey, + connection_states = ConnectionStates0, + negotiated_version = Version, + tls_handshake_history = Handshake0, + socket = Socket, + transport_cb = Transport + } = State) -> + DHKeys = public_key:generate_key(Params), + ConnectionState = + ssl_record:pending_connection_state(ConnectionStates0, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, Version, {dhe_psk, PskIdentityHint, DHKeys, Params, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + {BinMsg, ConnectionStates, Handshake} = + encode_handshake(Msg, Version, ConnectionStates0, Handshake0), + Transport:send(Socket, BinMsg), + State#state{connection_states = ConnectionStates, + diffie_hellman_keys = DHKeys, + tls_handshake_history = Handshake}; + +key_exchange(#state{role = server, key_algorithm = rsa_psk, + ssl_options = #ssl_options{psk_identity = undefined}} = State) -> + State; +key_exchange(#state{role = server, key_algorithm = rsa_psk, + ssl_options = #ssl_options{psk_identity = PskIdentityHint}, + hashsign_algorithm = HashSignAlgo, + private_key = PrivateKey, + connection_states = ConnectionStates0, + negotiated_version = Version, + tls_handshake_history = Handshake0, + socket = Socket, + transport_cb = Transport + } = State) -> + ConnectionState = + ssl_record:pending_connection_state(ConnectionStates0, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + {BinMsg, ConnectionStates, Handshake} = + encode_handshake(Msg, Version, ConnectionStates0, Handshake0), + Transport:send(Socket, BinMsg), + State#state{connection_states = ConnectionStates, + tls_handshake_history = Handshake}; + +key_exchange(#state{role = server, key_algorithm = Algo, + ssl_options = #ssl_options{user_lookup_fun = LookupFun}, + hashsign_algorithm = HashSignAlgo, + session = #session{srp_username = Username}, + private_key = PrivateKey, + connection_states = ConnectionStates0, + negotiated_version = Version, + tls_handshake_history = Handshake0, + socket = Socket, + transport_cb = Transport + } = State) + when Algo == srp_dss; + Algo == srp_rsa; + Algo == srp_anon -> + SrpParams = handle_srp_identity(Username, LookupFun), + Keys = case generate_srp_server_keys(SrpParams, 0) of + Alert = #alert{} -> + throw(Alert); + Keys0 = {_,_} -> + Keys0 + end, + ConnectionState = + ssl_record:pending_connection_state(ConnectionStates0, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, Version, {srp, Keys, SrpParams, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + {BinMsg, ConnectionStates, Handshake} = + encode_handshake(Msg, Version, ConnectionStates0, Handshake0), + Transport:send(Socket, BinMsg), + State#state{connection_states = ConnectionStates, + srp_params = SrpParams, + srp_keys = Keys, tls_handshake_history = Handshake}; key_exchange(#state{role = client, @@ -1523,6 +1817,85 @@ key_exchange(#state{role = client, encode_handshake(Msg, Version, ConnectionStates0, Handshake0), Transport:send(Socket, BinMsg), State#state{connection_states = ConnectionStates, + tls_handshake_history = Handshake}; + +key_exchange(#state{role = client, + connection_states = ConnectionStates0, + key_algorithm = Algorithm, + negotiated_version = Version, + diffie_hellman_keys = Keys, + socket = Socket, transport_cb = Transport, + tls_handshake_history = Handshake0} = State) + when Algorithm == ecdhe_ecdsa; Algorithm == ecdhe_rsa; + Algorithm == ecdh_ecdsa; Algorithm == ecdh_rsa; + Algorithm == ecdh_anon -> + Msg = ssl_handshake:key_exchange(client, Version, {ecdh, Keys}), + {BinMsg, ConnectionStates, Handshake} = + encode_handshake(Msg, Version, ConnectionStates0, Handshake0), + Transport:send(Socket, BinMsg), + State#state{connection_states = ConnectionStates, + tls_handshake_history = Handshake}; + +key_exchange(#state{role = client, + ssl_options = SslOpts, + connection_states = ConnectionStates0, + key_algorithm = psk, + negotiated_version = Version, + socket = Socket, transport_cb = Transport, + tls_handshake_history = Handshake0} = State) -> + Msg = ssl_handshake:key_exchange(client, Version, {psk, SslOpts#ssl_options.psk_identity}), + {BinMsg, ConnectionStates, Handshake} = + encode_handshake(Msg, Version, ConnectionStates0, Handshake0), + Transport:send(Socket, BinMsg), + State#state{connection_states = ConnectionStates, + tls_handshake_history = Handshake}; + +key_exchange(#state{role = client, + ssl_options = SslOpts, + connection_states = ConnectionStates0, + key_algorithm = dhe_psk, + negotiated_version = Version, + diffie_hellman_keys = {DhPubKey, _}, + socket = Socket, transport_cb = Transport, + tls_handshake_history = Handshake0} = State) -> + Msg = ssl_handshake:key_exchange(client, Version, {dhe_psk, SslOpts#ssl_options.psk_identity, DhPubKey}), + {BinMsg, ConnectionStates, Handshake} = + encode_handshake(Msg, Version, ConnectionStates0, Handshake0), + Transport:send(Socket, BinMsg), + State#state{connection_states = ConnectionStates, + tls_handshake_history = Handshake}; + +key_exchange(#state{role = client, + ssl_options = SslOpts, + connection_states = ConnectionStates0, + key_algorithm = rsa_psk, + public_key_info = PublicKeyInfo, + negotiated_version = Version, + premaster_secret = PremasterSecret, + socket = Socket, transport_cb = Transport, + tls_handshake_history = Handshake0} = State) -> + Msg = rsa_psk_key_exchange(Version, SslOpts#ssl_options.psk_identity, PremasterSecret, PublicKeyInfo), + {BinMsg, ConnectionStates, Handshake} = + encode_handshake(Msg, Version, ConnectionStates0, Handshake0), + Transport:send(Socket, BinMsg), + State#state{connection_states = ConnectionStates, + tls_handshake_history = Handshake}; + +key_exchange(#state{role = client, + connection_states = ConnectionStates0, + key_algorithm = Algorithm, + negotiated_version = Version, + srp_keys = {ClientPubKey, _}, + socket = Socket, transport_cb = Transport, + tls_handshake_history = Handshake0} = State) + when Algorithm == srp_dss; + Algorithm == srp_rsa; + Algorithm == srp_anon -> + Msg = ssl_handshake:key_exchange(client, Version, {srp, ClientPubKey}), + {BinMsg, ConnectionStates, Handshake} = + encode_handshake(Msg, Version, ConnectionStates0, Handshake0), + Transport:send(Socket, BinMsg), + State#state{connection_states = ConnectionStates, tls_handshake_history = Handshake}. rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) @@ -1541,6 +1914,22 @@ rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) rsa_key_exchange(_, _, _) -> throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)). +rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) + when Algorithm == ?rsaEncryption; + Algorithm == ?md2WithRSAEncryption; + Algorithm == ?md5WithRSAEncryption; + Algorithm == ?sha1WithRSAEncryption; + Algorithm == ?sha224WithRSAEncryption; + Algorithm == ?sha256WithRSAEncryption; + Algorithm == ?sha384WithRSAEncryption; + Algorithm == ?sha512WithRSAEncryption + -> + ssl_handshake:key_exchange(client, Version, + {psk_premaster_secret, PskIdentity, PremasterSecret, + PublicKeyInfo}); +rsa_psk_key_exchange(_, _, _, _) -> + throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)). + request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer}, connection_states = ConnectionStates0, cert_db = CertDbHandle, @@ -1628,7 +2017,7 @@ handle_server_key(#server_key_exchange{exchange_keys = Keys}, Params = ssl_handshake:decode_server_key(Keys, KeyAlg, Version), HashSign = connection_hashsign(Params#server_key_params.hashsign, State), case HashSign of - {_, anon} -> + {_, SignAlgo} when SignAlgo == anon; SignAlgo == ecdh_anon -> server_master_secret(Params#server_key_params.params, State); _ -> verify_server_key(Params, HashSign, State) @@ -1659,7 +2048,28 @@ verify_server_key(#server_key_params{params = Params, server_master_secret(#server_dh_params{dh_p = P, dh_g = G, dh_y = ServerPublicDhKey}, State) -> - dh_master_secret(P, G, ServerPublicDhKey, undefined, State). + dh_master_secret(P, G, ServerPublicDhKey, undefined, State); + +server_master_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey}, + State) -> + ECDHKeys = public_key:generate_key(ECCurve), + ec_dh_master_secret(ECDHKeys, #'ECPoint'{point = ECServerPubKey}, State#state{diffie_hellman_keys = ECDHKeys}); + +server_master_secret(#server_psk_params{ + hint = IdentityHint}, + State) -> + %% store for later use + State#state{psk_identity = IdentityHint}; + +server_master_secret(#server_dhe_psk_params{ + hint = IdentityHint, + dh_params = #server_dh_params{dh_p = P, dh_g = G, dh_y = ServerPublicDhKey}}, + State) -> + dhe_psk_master_secret(IdentityHint, P, G, ServerPublicDhKey, undefined, State); + +server_master_secret(#server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}, + State) -> + client_srp_master_secret(G, N, S, B, undefined, State). master_from_premaster_secret(PremasterSecret, #state{session = Session, @@ -1676,19 +2086,147 @@ master_from_premaster_secret(PremasterSecret, Alert end. +dh_master_secret(#'DHParameter'{} = Params, OtherPublicDhKey, MyPrivateKey, State) -> + PremasterSecret = + public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params), + master_from_premaster_secret(PremasterSecret, State). + dh_master_secret(Prime, Base, PublicDhKey, undefined, State) -> - PMpint = mpint_binary(Prime), - GMpint = mpint_binary(Base), - Keys = {_, PrivateDhKey} = - crypto:dh_generate_key([PMpint,GMpint]), - dh_master_secret(PMpint, GMpint, PublicDhKey, PrivateDhKey, State#state{diffie_hellman_keys = Keys}); + Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]), + dh_master_secret(Prime, Base, PublicDhKey, PrivateDhKey, State#state{diffie_hellman_keys = Keys}); -dh_master_secret(PMpint, GMpint, PublicDhKey, PrivateDhKey, State) -> +dh_master_secret(Prime, Base, PublicDhKey, PrivateDhKey, State) -> PremasterSecret = - crypto:dh_compute_key(mpint_binary(PublicDhKey), PrivateDhKey, - [PMpint, GMpint]), + crypto:compute_key(dh, PublicDhKey, PrivateDhKey, [Prime, Base]), master_from_premaster_secret(PremasterSecret, State). +ec_dh_master_secret(ECDHKeys, ECPoint, State) -> + PremasterSecret = + public_key:compute_key(ECPoint, ECDHKeys), + master_from_premaster_secret(PremasterSecret, State). + +handle_psk_identity(_PSKIdentity, LookupFun) + when LookupFun == undefined -> + error; +handle_psk_identity(PSKIdentity, {Fun, UserState}) -> + Fun(psk, PSKIdentity, UserState). + +server_psk_master_secret(ClientPSKIdentity, + #state{ssl_options = SslOpts} = State) -> + case handle_psk_identity(ClientPSKIdentity, SslOpts#ssl_options.user_lookup_fun) of + {ok, PSK} when is_binary(PSK) -> + Len = byte_size(PSK), + PremasterSecret = <<?UINT16(Len), 0:(Len*8), ?UINT16(Len), PSK/binary>>, + master_from_premaster_secret(PremasterSecret, State); + #alert{} = Alert -> + Alert; + _ -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) + end. + +dhe_psk_master_secret(PSKIdentity, Prime, Base, PublicDhKey, undefined, State) -> + Keys = {_, PrivateDhKey} = + crypto:generate_key(dh, [Prime, Base]), + dhe_psk_master_secret(PSKIdentity, Prime, Base, PublicDhKey, PrivateDhKey, + State#state{diffie_hellman_keys = Keys}); + +dhe_psk_master_secret(PSKIdentity, Prime, Base, PublicDhKey, PrivateDhKey, + #state{ssl_options = SslOpts} = State) -> + case handle_psk_identity(PSKIdentity, SslOpts#ssl_options.user_lookup_fun) of + {ok, PSK} when is_binary(PSK) -> + DHSecret = + crypto:compute_key(dh, PublicDhKey, PrivateDhKey, + [Prime, Base]), + DHLen = erlang:byte_size(DHSecret), + Len = erlang:byte_size(PSK), + PremasterSecret = <<?UINT16(DHLen), DHSecret/binary, ?UINT16(Len), PSK/binary>>, + master_from_premaster_secret(PremasterSecret, State); + #alert{} = Alert -> + Alert; + _ -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) + end. + +server_rsa_psk_master_secret(PskIdentity, PremasterSecret, + #state{ssl_options = SslOpts} = State) -> + case handle_psk_identity(PskIdentity, SslOpts#ssl_options.user_lookup_fun) of + {ok, PSK} when is_binary(PSK) -> + Len = byte_size(PSK), + RealPMS = <<?UINT16(48), PremasterSecret/binary, ?UINT16(Len), PSK/binary>>, + master_from_premaster_secret(RealPMS, State); + #alert{} = Alert -> + Alert; + _ -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) + end. + +generate_srp_server_keys(_SrpParams, 10) -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); +generate_srp_server_keys(SrpParams = + #srp_user{generator = Generator, prime = Prime, + verifier = Verifier}, N) -> + case crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of + error -> + generate_srp_server_keys(SrpParams, N+1); + Keys -> + Keys + end. + +generate_srp_client_keys(_Generator, _Prime, 10) -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); +generate_srp_client_keys(Generator, Prime, N) -> + + case crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of + error -> + generate_srp_client_keys(Generator, Prime, N+1); + Keys -> + Keys + end. + +handle_srp_identity(Username, {Fun, UserState}) -> + case Fun(srp, Username, UserState) of + {ok, {SRPParams, Salt, DerivedKey}} + when is_atom(SRPParams), is_binary(Salt), is_binary(DerivedKey) -> + {Generator, Prime} = ssl_srp_primes:get_srp_params(SRPParams), + Verifier = crypto:mod_pow(Generator, DerivedKey, Prime), + #srp_user{generator = Generator, prime = Prime, + salt = Salt, verifier = Verifier}; + #alert{} = Alert -> + throw(Alert); + _ -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end. + +server_srp_master_secret(Verifier, Prime, ClientPub, State = #state{srp_keys = ServerKeys}) -> + case crypto:compute_key(srp, ClientPub, ServerKeys, {host, [Verifier, Prime, '6a']}) of + error -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); + PremasterSecret -> + master_from_premaster_secret(PremasterSecret, State) + end. + +client_srp_master_secret(_Generator, _Prime, _Salt, _ServerPub, #alert{} = Alert, _State) -> + Alert; +client_srp_master_secret(Generator, Prime, Salt, ServerPub, undefined, State) -> + Keys = generate_srp_client_keys(Generator, Prime, 0), + client_srp_master_secret(Generator, Prime, Salt, ServerPub, Keys, State#state{srp_keys = Keys}); + +client_srp_master_secret(Generator, Prime, Salt, ServerPub, ClientKeys, + #state{ssl_options = SslOpts} = State) -> + case ssl_srp_primes:check_srp_params(Generator, Prime) of + ok -> + {Username, Password} = SslOpts#ssl_options.srp_identity, + DerivedKey = crypto:sha([Salt, crypto:sha([Username, <<$:>>, Password])]), + case crypto:compute_key(srp, ServerPub, ClientKeys, {user, [DerivedKey, Prime, Generator, '6a']}) of + error -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); + PremasterSecret -> + master_from_premaster_secret(PremasterSecret, State) + end; + _ -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) + end. + cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State) -> ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, ConnectionStates0), next_state_connection(cipher, ack_connection(State#state{session = Session, @@ -2349,11 +2887,6 @@ make_premaster_secret({MajVer, MinVer}, rsa) -> make_premaster_secret(_, _) -> undefined. -mpint_binary(Binary) -> - Size = erlang:byte_size(Binary), - <<?UINT32(Size), Binary/binary>>. - - ack_connection(#state{renegotiation = {true, Initiater}} = State) when Initiater == internal; Initiater == peer -> @@ -2488,19 +3021,34 @@ default_hashsign(_Version = {Major, Minor}, KeyExchange) when Major == 3 andalso Minor >= 3 andalso (KeyExchange == rsa orelse KeyExchange == dhe_rsa orelse - KeyExchange == dh_rsa) -> + KeyExchange == dh_rsa orelse + KeyExchange == ecdhe_rsa orelse + KeyExchange == srp_rsa) -> {sha, rsa}; default_hashsign(_Version, KeyExchange) when KeyExchange == rsa; KeyExchange == dhe_rsa; - KeyExchange == dh_rsa -> + KeyExchange == dh_rsa; + KeyExchange == ecdhe_rsa; + KeyExchange == srp_rsa -> {md5sha, rsa}; default_hashsign(_Version, KeyExchange) + when KeyExchange == ecdhe_ecdsa; + KeyExchange == ecdh_ecdsa; + KeyExchange == ecdh_rsa -> + {sha, ecdsa}; +default_hashsign(_Version, KeyExchange) when KeyExchange == dhe_dss; - KeyExchange == dh_dss -> + KeyExchange == dh_dss; + KeyExchange == srp_dss -> {sha, dsa}; default_hashsign(_Version, KeyExchange) - when KeyExchange == dh_anon -> + when KeyExchange == dh_anon; + KeyExchange == ecdh_anon; + KeyExchange == psk; + KeyExchange == dhe_psk; + KeyExchange == rsa_psk; + KeyExchange == srp_anon -> {null, anon}. start_or_recv_cancel_timer(infinity, _RecvFrom) -> @@ -2531,3 +3079,8 @@ handle_close_alert(Data, StateName, State0) -> _ -> ok end. + +select_curve(#state{client_ecc = {[Curve|_], _}}) -> + {namedCurve, Curve}; +select_curve(_) -> + {namedCurve, ?secp256k1}. |