diff options
Diffstat (limited to 'lib/ssh/src/ssh_transport.erl')
-rw-r--r-- | lib/ssh/src/ssh_transport.erl | 187 |
1 files changed, 127 insertions, 60 deletions
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index a6438e69d4..8b65806dc6 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -44,6 +44,7 @@ handle_kexdh_reply/2, handle_kex_ecdh_init/2, handle_kex_ecdh_reply/2, + extract_public_key/1, unpack/3, decompress/2, ssh_packet/2, pack/2, msg_data/1, sign/3, verify/4]). @@ -65,8 +66,8 @@ default_algorithms() -> [{K,default_algorithms(K)} || K <- algo_classes()]. algo_classes() -> [kex, public_key, cipher, mac, compression]. -default_algorithms(kex) -> - supported_algorithms(kex, []); %% Just to have a call to supported_algorithms/2 +%% default_algorithms(kex) -> % Example of how to disable an algorithm +%% supported_algorithms(kex, ['ecdh-sha2-nistp521']); default_algorithms(Alg) -> supported_algorithms(Alg). @@ -117,11 +118,11 @@ supported_algorithms(compression) -> 'zlib' ]). -supported_algorithms(Key, [{client2server,BL1},{server2client,BL2}]) -> - [{client2server,As1},{server2client,As2}] = supported_algorithms(Key), - [{client2server,As1--BL1},{server2client,As2--BL2}]; -supported_algorithms(Key, BlackList) -> - supported_algorithms(Key) -- BlackList. +%% Dialyzer complains when not called...supported_algorithms(Key, [{client2server,BL1},{server2client,BL2}]) -> +%% Dialyzer complains when not called... [{client2server,As1},{server2client,As2}] = supported_algorithms(Key), +%% Dialyzer complains when not called... [{client2server,As1--BL1},{server2client,As2--BL2}]; +%% Dialyzer complains when not called...supported_algorithms(Key, BlackList) -> +%% Dialyzer complains when not called... supported_algorithms(Key) -- BlackList. select_crypto_supported(L) -> Sup = [{ec_curve,crypto_supported_curves()} | crypto:supports()], @@ -328,9 +329,7 @@ verify_algorithm(#alg{encrypt = undefined}) -> false; verify_algorithm(#alg{decrypt = undefined}) -> false; verify_algorithm(#alg{compress = undefined}) -> false; verify_algorithm(#alg{decompress = undefined}) -> false; - -verify_algorithm(#alg{kex = Kex}) -> lists:member(Kex, supported_algorithms(kex)); -verify_algorithm(_) -> false. +verify_algorithm(#alg{kex = Kex}) -> lists:member(Kex, supported_algorithms(kex)). %%%---------------------------------------------------------------- %%% @@ -380,13 +379,15 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, 1=<E, E=<(P-1) -> {Public, Private} = generate_key(dh, [P,G]), K = compute_key(dh, E, Private, [P,G]), - Key = get_host_key(Ssh0), - H = kex_h(Ssh0, Key, E, Public, K), - H_SIG = sign_host_key(Ssh0, Key, H), - {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kexdh_reply{public_host_key = Key, - f = Public, - h_sig = H_SIG - }, Ssh0), + MyPrivHostKey = get_host_key(Ssh0), + MyPubHostKey = extract_public_key(MyPrivHostKey), + H = kex_h(Ssh0, MyPubHostKey, E, Public, K), + H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H), + {SshPacket, Ssh1} = + ssh_packet(#ssh_msg_kexdh_reply{public_host_key = MyPubHostKey, + f = Public, + h_sig = H_SIG + }, Ssh0), {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}, shared_secret = K, exchanged_hash = H, @@ -401,7 +402,7 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, }) end. -handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = HostKey, +handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey, f = F, h_sig = H_SIG}, #ssh{keyex_key = {{Private, Public}, {G, P}}} = Ssh0) -> @@ -409,9 +410,9 @@ handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = HostKey, if 1=<F, F=<(P-1)-> K = compute_key(dh, F, Private, [P,G]), - H = kex_h(Ssh0, HostKey, Public, F, K), + H = kex_h(Ssh0, PeerPubHostKey, Public, F, K), - case verify_host_key(Ssh0, HostKey, H, H_SIG) of + case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), {ok, SshPacket, Ssh#ssh{shared_secret = K, @@ -480,11 +481,12 @@ handle_kex_dh_gex_init(#ssh_msg_kex_dh_gex_init{e = E}, K = compute_key(dh, E, Private, [P,G]), if 1<K, K<(P-1) -> - HostKey = get_host_key(Ssh0), - H = kex_h(Ssh0, HostKey, Min, NBits, Max, P, G, E, Public, K), - H_SIG = sign_host_key(Ssh0, HostKey, H), + MyPrivHostKey = get_host_key(Ssh0), + MyPubHostKey = extract_public_key(MyPrivHostKey), + H = kex_h(Ssh0, MyPubHostKey, Min, NBits, Max, P, G, E, Public, K), + H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H), {SshPacket, Ssh} = - ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = HostKey, + ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = MyPubHostKey, f = Public, h_sig = H_SIG}, Ssh0), {ok, SshPacket, Ssh#ssh{shared_secret = K, @@ -508,7 +510,7 @@ handle_kex_dh_gex_init(#ssh_msg_kex_dh_gex_init{e = E}, }) end. -handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = HostKey, +handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostKey, f = F, h_sig = H_SIG}, #ssh{keyex_key = {{Private, Public}, {G, P}}, @@ -520,9 +522,9 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = HostKey, K = compute_key(dh, F, Private, [P,G]), if 1<K, K<(P-1) -> - H = kex_h(Ssh0, HostKey, Min, NBits, Max, P, G, Public, F, K), + H = kex_h(Ssh0, PeerPubHostKey, Min, NBits, Max, P, G, Public, F, K), - case verify_host_key(Ssh0, HostKey, H, H_SIG) of + case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), {ok, SshPacket, Ssh#ssh{shared_secret = K, @@ -565,11 +567,12 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, true -> {MyPublic, MyPrivate} = generate_key(ecdh, Curve), K = compute_key(ecdh, PeerPublic, MyPrivate, Curve), - HostKey = get_host_key(Ssh0), - H = kex_h(Ssh0, Curve, HostKey, PeerPublic, MyPublic, K), - H_SIG = sign_host_key(Ssh0, HostKey, H), + MyPrivHostKey = get_host_key(Ssh0), + MyPubHostKey = extract_public_key(MyPrivHostKey), + H = kex_h(Ssh0, Curve, MyPubHostKey, PeerPublic, MyPublic, K), + H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H), {SshPacket, Ssh1} = - ssh_packet(#ssh_msg_kex_ecdh_reply{public_host_key = HostKey, + ssh_packet(#ssh_msg_kex_ecdh_reply{public_host_key = MyPubHostKey, q_s = MyPublic, h_sig = H_SIG}, Ssh0), @@ -587,7 +590,7 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic}, }) end. -handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = HostKey, +handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey, q_s = PeerPublic, h_sig = H_SIG}, #ssh{keyex_key = {{MyPublic,MyPrivate}, Curve}} = Ssh0 @@ -596,8 +599,8 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = HostKey, case ecdh_validate_public_key(PeerPublic, Curve) of true -> K = compute_key(ecdh, PeerPublic, MyPrivate, Curve), - H = kex_h(Ssh0, Curve, HostKey, MyPublic, PeerPublic, K), - case verify_host_key(Ssh0, HostKey, H, H_SIG) of + H = kex_h(Ssh0, Curve, PeerPubHostKey, MyPublic, PeerPublic, K), + case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of ok -> {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), {ok, SshPacket, Ssh#ssh{shared_secret = K, @@ -622,7 +625,61 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = HostKey, end. -ecdh_validate_public_key(_, _) -> true. % FIXME: Far too many false positives :) +%%%---------------------------------------------------------------- +%%% +%%% Standards for Efficient Cryptography Group, "Elliptic Curve Cryptography", SEC 1 +%%% Section 3.2.2.1 +%%% + +ecdh_validate_public_key(Key, Curve) -> + case key_size(Curve) of + undefined -> + false; + + Sz -> + case dec_key(Key, Sz) of + {ok,Q} -> + case crypto:ec_curve(Curve) of + {{prime_field,P}, {A, B, _Seed}, + _P0Bin, _OrderBin, _CoFactorBin} -> + on_curve(Q, bin2int(A), bin2int(B), bin2int(P)) + end; + + {error,compressed_not_implemented} -> % Be a bit generous... + true; + + _Error -> + false + end + end. + + +on_curve({X,Y}, A, B, P) when 0 =< X,X =< (P-1), + 0 =< Y,Y =< (P-1) -> + %% Section 3.2.2.1, point 2 + (Y*Y) rem P == (X*X*X + A*X + B) rem P; +on_curve(_, _, _, _) -> + false. + + +bin2int(B) -> + Sz = erlang:bit_size(B), + <<I:Sz/big-unsigned-integer>> = B, + I. + +key_size(secp256r1) -> 256; +key_size(secp384r1) -> 384; +key_size(secp521r1) -> 528; % Round 521 up to closest 8-bits. +key_size(_) -> undefined. + + +dec_key(Key, NBits) -> + Size = 8 + 2*NBits, + case <<Key:Size>> of + <<4:8, X:NBits, Y:NBits>> -> {ok,{X,Y}}; + <<4:8, _/binary>> -> {error,bad_format}; + _ -> {error,compressed_not_implemented} + end. %%%---------------------------------------------------------------- handle_new_keys(#ssh_msg_newkeys{}, Ssh0) -> @@ -659,13 +716,20 @@ get_host_key(SSH) -> sign_host_key(_Ssh, PrivateKey, H) -> sign(H, sign_host_key_sha(PrivateKey), PrivateKey). -sign_host_key_sha(#'ECPrivateKey'{parameters = {namedCurve, ?'secp256r1'}}) -> sha256; -sign_host_key_sha(#'ECPrivateKey'{parameters = {namedCurve, ?'secp384r1'}}) -> sha384; -sign_host_key_sha(#'ECPrivateKey'{parameters = {namedCurve, ?'secp521r1'}}) -> sha512; +sign_host_key_sha(#'ECPrivateKey'{parameters = {namedCurve,OID}}) -> sha(OID); sign_host_key_sha(#'RSAPrivateKey'{}) -> sha; sign_host_key_sha(#'DSAPrivateKey'{}) -> sha. +extract_public_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) -> + #'RSAPublicKey'{modulus = N, publicExponent = E}; +extract_public_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) -> + {Y, #'Dss-Parms'{p=P, q=Q, g=G}}; +extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID}, + publicKey = Q}) -> + {#'ECPoint'{point=Q}, {namedCurve,OID}}. + + verify_host_key(SSH, PublicKey, Digest, Signature) -> case verify(Digest, host_key_sha(PublicKey), Signature, PublicKey) of false -> @@ -674,14 +738,16 @@ verify_host_key(SSH, PublicKey, Digest, Signature) -> known_host_key(SSH, PublicKey, public_algo(PublicKey)) end. -host_key_sha(#'RSAPublicKey'{}) -> sha; -host_key_sha({_, #'Dss-Parms'{}}) -> sha; -host_key_sha({#'ECPoint'{},Id}) -> sha(list_to_atom(binary_to_list(Id))). +host_key_sha(#'RSAPublicKey'{}) -> sha; +host_key_sha({_, #'Dss-Parms'{}}) -> sha; +host_key_sha({#'ECPoint'{},{namedCurve,OID}}) -> sha(OID). public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa'; public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss'; -public_algo({#'ECPoint'{},Id}) -> list_to_atom("ecdsa-sha2-" ++ binary_to_list(Id)). +public_algo({#'ECPoint'{},{namedCurve,OID}}) -> + Curve = public_key:oid2ssh_curvename(OID), + list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)). accepted_host(Ssh, PeerName, Opts) -> @@ -933,17 +999,12 @@ verify(PlainText, Hash, Sig, {_, #'Dss-Parms'{}} = Key) -> <<R:160/big-unsigned-integer, S:160/big-unsigned-integer>> = Sig, Signature = public_key:der_encode('Dss-Sig-Value', #'Dss-Sig-Value'{r = R, s = S}), public_key:verify(PlainText, Hash, Signature, Key); -verify(PlainText, Hash, Sig, {ECPoint=#'ECPoint'{}, Param}) -> - C = case Param of - <<"nistp256">> -> {namedCurve, ?'secp256r1'}; - <<"nistp384">> -> {namedCurve, ?'secp384r1'}; - <<"nistp521">> -> {namedCurve, ?'secp521r1'} - end, +verify(PlainText, Hash, Sig, {#'ECPoint'{},_} = Key) -> <<?UINT32(Rlen),R:Rlen/big-signed-integer-unit:8, ?UINT32(Slen),S:Slen/big-signed-integer-unit:8>> = Sig, Sval = #'ECDSA-Sig-Value'{r=R, s=S}, DerEncodedSig = public_key:der_encode('ECDSA-Sig-Value',Sval), - public_key:verify(PlainText, Hash, DerEncodedSig, {ECPoint,C}); + public_key:verify(PlainText, Hash, DerEncodedSig, Key); verify(PlainText, Hash, Sig, Key) -> public_key:verify(PlainText, Hash, Sig, Key). @@ -1336,52 +1397,58 @@ hash(K, H, Ki, N, HASH) -> hash(K, H, <<Ki/binary, Kj/binary>>, N-128, HASH). kex_h(SSH, Key, E, F, K) -> + KeyBin = public_key:ssh_encode(Key, ssh2_pubkey), L = ssh_bits:encode([SSH#ssh.c_version, SSH#ssh.s_version, SSH#ssh.c_keyinit, SSH#ssh.s_keyinit, - ssh_message:encode_host_key(Key), E,F,K], + KeyBin, E,F,K], [string,string,binary,binary,binary, mpint,mpint,mpint]), crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L). %% crypto:hash(sha,L). kex_h(SSH, Curve, Key, Q_c, Q_s, K) -> + KeyBin = public_key:ssh_encode(Key, ssh2_pubkey), L = ssh_bits:encode([SSH#ssh.c_version, SSH#ssh.s_version, SSH#ssh.c_keyinit, SSH#ssh.s_keyinit, - ssh_message:encode_host_key(Key), Q_c, Q_s, K], + KeyBin, Q_c, Q_s, K], [string,string,binary,binary,binary, mpint,mpint,mpint]), crypto:hash(sha(Curve), L). kex_h(SSH, Key, Min, NBits, Max, Prime, Gen, E, F, K) -> L = if Min==-1; Max==-1 -> + KeyBin = public_key:ssh_encode(Key, ssh2_pubkey), Ts = [string,string,binary,binary,binary, uint32, mpint,mpint,mpint,mpint,mpint], ssh_bits:encode([SSH#ssh.c_version,SSH#ssh.s_version, SSH#ssh.c_keyinit,SSH#ssh.s_keyinit, - ssh_message:encode_host_key(Key), NBits, Prime, Gen, E,F,K], + KeyBin, NBits, Prime, Gen, E,F,K], Ts); true -> + KeyBin = public_key:ssh_encode(Key, ssh2_pubkey), Ts = [string,string,binary,binary,binary, uint32,uint32,uint32, mpint,mpint,mpint,mpint,mpint], ssh_bits:encode([SSH#ssh.c_version,SSH#ssh.s_version, SSH#ssh.c_keyinit,SSH#ssh.s_keyinit, - ssh_message:encode_host_key(Key), Min, NBits, Max, + KeyBin, Min, NBits, Max, Prime, Gen, E,F,K], Ts) end, crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L). -sha('nistp256') -> sha256; -sha('secp256r1')-> sha256; -sha('nistp384') -> sha384; -sha('secp384r1')-> sha384; -sha('nistp521') -> sha512; -sha('secp521r1')-> sha512; + +sha(secp256r1) -> sha256; +sha(secp384r1) -> sha384; +sha(secp521r1) -> sha512; sha('diffie-hellman-group1-sha1') -> sha; sha('diffie-hellman-group14-sha1') -> sha; sha('diffie-hellman-group-exchange-sha1') -> sha; -sha('diffie-hellman-group-exchange-sha256') -> sha256. +sha('diffie-hellman-group-exchange-sha256') -> sha256; +sha(?'secp256r1') -> sha(secp256r1); +sha(?'secp384r1') -> sha(secp384r1); +sha(?'secp521r1') -> sha(secp521r1). + mac_key_size('hmac-sha1') -> 20*8; mac_key_size('hmac-sha1-96') -> 20*8; |