Skip to content

Commit 090e489

Browse files
authored
Merge pull request #67 from hannesm/more-kex
More kex
2 parents db63313 + f82537a commit 090e489

File tree

4 files changed

+131
-53
lines changed

4 files changed

+131
-53
lines changed

lib/client.ml

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ let make ?(authenticator = `No_authentication) ~user auth_method =
138138
| `Key Hostkey.Ed25519_pub _ -> Hostkey.algs_of_typ `Ed25519
139139
| `Fingerprint (typ, _) -> Hostkey.algs_of_typ typ
140140
in
141-
let client_kexinit = Kex.make_kexinit hostkey_algs Kex.client_supported () in
141+
let client_kexinit = Kex.make_kexinit hostkey_algs Kex.supported () in
142142
let banner_msg = Ssh.Msg_version version_banner in
143143
let kex_msg = Ssh.Msg_kexinit client_kexinit in
144144
let t = { state = Init (version_banner, client_kexinit);

lib/kex.ml

+1-5
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,13 @@ let hash_of_alg = function
105105
| Ecdh_sha2_nistp384 -> Mirage_crypto.Hash.module_of `SHA384
106106
| Ecdh_sha2_nistp521 -> Mirage_crypto.Hash.module_of `SHA512
107107

108-
let client_supported =
108+
let supported =
109109
[ Curve25519_sha256 ;
110110
Ecdh_sha2_nistp256 ; Ecdh_sha2_nistp384 ; Ecdh_sha2_nistp521 ;
111111
Diffie_hellman_group14_sha256 ; Diffie_hellman_group_exchange_sha256 ;
112112
Diffie_hellman_group14_sha1 ; Diffie_hellman_group1_sha1 ;
113113
Diffie_hellman_group_exchange_sha1 ]
114114

115-
let server_supported =
116-
[ Diffie_hellman_group14_sha256 ; Diffie_hellman_group14_sha1 ;
117-
Diffie_hellman_group1_sha1 ]
118-
119115
let make_kexinit ?ext_info host_key_algs algs () =
120116
let k =
121117
{ cookie = Mirage_crypto_rng.generate 16;

lib/server.ml

+128-46
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type t = {
5050
user_db : Auth.db; (* username database *)
5151
channels : Channel.db; (* Ssh channels *)
5252
ignore_next_packet : bool; (* Ignore the next packet from the wire *)
53+
dh_group : (Mirage_crypto_pk.Dh.group * int32 * int32 * int32) option; (* used for GEX (RFC 4419) *)
5354
}
5455

5556
let guard_msg t msg =
@@ -70,7 +71,7 @@ let make host_key user_db =
7071
let open Ssh in
7172
let server_kexinit =
7273
let algs = host_key_algs host_key in
73-
Kex.make_kexinit algs Kex.server_supported ()
74+
Kex.make_kexinit algs Kex.supported ()
7475
in
7576
let banner_msg = Ssh.Msg_version version_banner in
7677
let kex_msg = Ssh.Msg_kexinit server_kexinit in
@@ -92,7 +93,9 @@ let make host_key user_db =
9293
auth_state = Auth.Preauth;
9394
user_db;
9495
channels = Channel.empty_db;
95-
ignore_next_packet = false },
96+
ignore_next_packet = false;
97+
dh_group = None;
98+
},
9699
[ banner_msg; kex_msg ]
97100

98101
(* t with updated keys from new_keys_ctos *)
@@ -116,7 +119,7 @@ let rekey t =
116119
| false, true -> (* can't be keying and must be keyed *)
117120
let server_kexinit =
118121
let algs = host_key_algs t.host_key in
119-
Kex.make_kexinit algs Kex.server_supported ()
122+
Kex.make_kexinit algs Kex.supported ()
120123
in
121124
let t = { t with server_kexinit; keying = true } in
122125
Some (t, Ssh.Msg_kexinit server_kexinit)
@@ -341,60 +344,139 @@ let input_msg t msg now =
341344
kex.first_kex_packet_follows &&
342345
not (Kex.guessed_right ~s:t.server_kexinit ~c:kex)
343346
in
347+
let expect =
348+
Some (if Kex.is_rfc4419 neg.kex_alg then MSG_KEX_4 else MSG_KEX_0)
349+
in
344350
let t = { t with client_kexinit = Some kex;
345351
neg_kex = Some neg;
346-
expect = Some MSG_KEX_0; (* TODO needs fix *)
352+
expect;
347353
ignore_next_packet;
348354
ext_info = kex.ext_info = Some `Ext_info_c; }
349355
in
350356
(match rekey t with
351357
| None -> make_noreply t (* either already rekeying or not keyed *)
352358
| Some (t, kexinit) -> make_reply t kexinit)
353359
| Msg_kex (id, data) ->
354-
begin
355-
let* m = Wire.dh_kexdh_of_kex id data in
356-
match m with
357-
| Msg_kexdh_init e ->
358-
let* neg = guard_some t.neg_kex "No negotiated kex" in
359-
let* client_version = guard_some t.client_version "No client version" in
360-
let* () = guard_none t.new_keys_stoc "Already got new_keys_stoc" in
361-
let* () = guard_none t.new_keys_ctos "Already got new_keys_ctos" in
362-
let* c = guard_some t.client_kexinit "No client kex" in
363-
let* f, k = Kex.(Dh.generate neg.kex_alg e) in
364-
let pub_host_key = Hostkey.pub_of_priv t.host_key in
365-
let h = Kex.Dh.compute_hash ~signed:true neg
366-
~v_c:client_version
367-
~v_s:t.server_version
368-
~i_c:c.rawkex
369-
~i_s:(Wire.blob_of_kexinit t.server_kexinit)
370-
~k_s:pub_host_key
371-
~e ~f ~k
360+
let exts =
361+
if t.ext_info then
362+
let algs =
363+
String.concat ","
364+
(List.map Hostkey.alg_to_string (host_key_algs t.host_key));
372365
in
373-
let signature = Hostkey.sign neg.server_host_key_alg t.host_key h in
374-
let session_id = match t.session_id with None -> h | Some x -> x in
375-
let* new_keys_ctos, new_keys_stoc, key_eol =
376-
Kex.Dh.derive_keys k h session_id neg now
366+
let extensions =
367+
[Extension { name = "server-sig-algs"; value = algs; }]
377368
in
378-
let signature = neg.server_host_key_alg, signature in
379-
make_replies { t with session_id = Some session_id;
380-
new_keys_ctos = Some new_keys_ctos;
381-
new_keys_stoc = Some new_keys_stoc;
382-
key_eol = Some key_eol;
383-
expect = Some MSG_NEWKEYS }
384-
([ Msg_kexdh_reply (pub_host_key, f, signature); Msg_newkeys ] @ (
385-
if t.ext_info then
386-
let algs =
387-
String.concat ","
388-
(List.map Hostkey.alg_to_string (host_key_algs t.host_key));
389-
in
390-
let extensions =
391-
[Extension { name = "server-sig-algs";
392-
value = algs; }]
393-
in
394-
[ Msg_ext_info extensions ]
395-
else []))
396-
| _ ->
397-
Error "unexpected KEX message"
369+
[ Msg_ext_info extensions ]
370+
else []
371+
in
372+
let sign_rekey t neg ~h ~f ~k =
373+
let signature = Hostkey.sign neg.Kex.server_host_key_alg t.host_key h in
374+
Log.debug (fun m -> m "shared is %a signature is %a (hash %a)"
375+
Cstruct.hexdump_pp (Mirage_crypto_pk.Z_extra.to_cstruct_be f)
376+
Cstruct.hexdump_pp signature Cstruct.hexdump_pp h);
377+
let session_id = match t.session_id with None -> h | Some x -> x in
378+
let* new_keys_ctos, new_keys_stoc, key_eol =
379+
Kex.Dh.derive_keys k h session_id neg now
380+
in
381+
let signature = neg.server_host_key_alg, signature in
382+
Ok ({ t with session_id = Some session_id;
383+
new_keys_ctos = Some new_keys_ctos;
384+
new_keys_stoc = Some new_keys_stoc;
385+
key_eol = Some key_eol;
386+
expect = Some MSG_NEWKEYS },
387+
signature,
388+
Msg_newkeys :: exts)
389+
in
390+
let cv_ckex t =
391+
let* client_version = guard_some t.client_version "No client version" in
392+
let* () = guard_none t.new_keys_stoc "Already got new_keys_stoc" in
393+
let* () = guard_none t.new_keys_ctos "Already got new_keys_ctos" in
394+
let* c = guard_some t.client_kexinit "No client kex" in
395+
Ok (client_version, c.rawkex)
396+
in
397+
let dh ~ec t neg ~e ~f ~k =
398+
let* client_version, i_c = cv_ckex t in
399+
let pub_host_key = Hostkey.pub_of_priv t.host_key in
400+
let h = Kex.Dh.compute_hash ~signed:(not ec) neg
401+
~v_c:client_version
402+
~v_s:t.server_version
403+
~i_c
404+
~i_s:(Wire.blob_of_kexinit t.server_kexinit)
405+
~k_s:pub_host_key
406+
~e ~f ~k
407+
in
408+
let* t, signature, msgs = sign_rekey t neg ~h ~f ~k in
409+
Ok (t, (pub_host_key, signature), msgs)
410+
in
411+
begin
412+
match t.neg_kex with
413+
| None -> Error "No negotiated kex"
414+
| Some neg ->
415+
if Kex.is_rfc4419 neg.kex_alg then
416+
let* m = Wire.dh_kexdh_gex_of_kex id data in
417+
match t.dh_group, m with
418+
| None, Msg_kexdh_gex_request (min, n, max) ->
419+
let* group =
420+
if max < 2048l then
421+
Error "maximum group size too small"
422+
else if min > 8192l then
423+
Error "minimum group size too big"
424+
else if min > n || n > max then
425+
Error "group size limits wrong (min <= n <= max)"
426+
else if n < 3072l then
427+
Ok Mirage_crypto_pk.Dh.Group.ffdhe2048
428+
else if n < 4096l then
429+
Ok Mirage_crypto_pk.Dh.Group.ffdhe3072
430+
else if n < 6144l then
431+
Ok Mirage_crypto_pk.Dh.Group.ffdhe4096
432+
else if n < 8192l then
433+
Ok Mirage_crypto_pk.Dh.Group.ffdhe6144
434+
else
435+
Ok Mirage_crypto_pk.Dh.Group.ffdhe8192
436+
in
437+
make_replies { t with
438+
dh_group = Some (group, min, n, max);
439+
expect = Some MSG_KEX_2 }
440+
[ Msg_kexdh_gex_group (group.p, group.gg) ]
441+
| Some (group, min, n, max), Msg_kexdh_gex_init theirs ->
442+
let secret, my_share = Mirage_crypto_pk.Dh.gen_key group in
443+
let* client_version, i_c = cv_ckex t in
444+
let* k = Kex.Dh.shared secret theirs in
445+
let pub_host_key = Hostkey.pub_of_priv t.host_key in
446+
let f = Mirage_crypto_pk.Z_extra.of_cstruct_be my_share in
447+
let h =
448+
Kex.Dh.compute_hash_gex neg
449+
~v_c:client_version ~v_s:t.server_version
450+
~i_c ~i_s:(Wire.blob_of_kexinit t.server_kexinit)
451+
~k_s:pub_host_key
452+
~min ~n ~max
453+
~p:group.p ~g:group.gg
454+
~e:theirs ~f
455+
~k
456+
in
457+
let* t, sig_, msgs = sign_rekey t neg ~h ~f ~k in
458+
make_replies t
459+
(Msg_kexdh_gex_reply (pub_host_key, f, sig_) :: msgs)
460+
| _ -> Error "unexpected KEX message"
461+
else if Kex.is_finite_field neg.kex_alg then
462+
let* m = Wire.dh_kexdh_of_kex id data in
463+
match m with
464+
| Msg_kexdh_init e ->
465+
let* f, k = Kex.(Dh.generate neg.kex_alg e) in
466+
let* (t, (key, sig_), msgs) = dh ~ec:false t neg ~e ~f ~k in
467+
make_replies t
468+
(Msg_kexdh_reply (key, f, sig_) :: msgs)
469+
| _ -> Error "unexpected KEX message"
470+
else (* EC *)
471+
let* m = Wire.dh_kexecdh_of_kex id data in
472+
match m with
473+
| Msg_kexecdh_init e ->
474+
let secret, f = Kex.Dh.ec_secret_pub neg.kex_alg in
475+
let* k = Kex.Dh.ec_shared secret e in
476+
let* (t, (key, sig_), msgs) = dh ~ec:true t neg ~e ~f ~k in
477+
make_replies t
478+
(Msg_kexecdh_reply (key, f, sig_) :: msgs)
479+
| _ -> Error "unexpected KEX message"
398480
end
399481
| Msg_newkeys ->
400482
(* If this is the first time we keyed, we must take a service request *)

test/test.ml

+1-1
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ let t_ignore_next_packet () =
412412
let t = Server.{ t with client_version = Some "SSH-2.0-client";
413413
expect = Some(Ssh.MSG_KEXINIT) }
414414
in
415-
let kexinit = Ssh.{ (Kex.make_kexinit Hostkey.preferred_algs Kex.client_supported ()) with
415+
let kexinit = Ssh.{ (Kex.make_kexinit Hostkey.preferred_algs Kex.supported ()) with
416416
encryption_algs_ctos = ["aes256-cbc"];
417417
first_kex_packet_follows = true }
418418
in

0 commit comments

Comments
 (0)