From a698c64fe1daaad88d447f5ac4378dbe3068bc84 Mon Sep 17 00:00:00 2001
From: Matt Johnston <matt@ucc.asn.au>
Date: Sun, 21 Aug 2022 11:33:37 +0800
Subject: [PATCH] work in progress on make_kexdhreply

---
 async/src/async_door.rs |  3 +++
 sshproto/src/conn.rs    |  2 +-
 sshproto/src/kex.rs     | 27 +++++++++++++++++----------
 sshproto/src/sign.rs    | 11 +++++++++--
 4 files changed, 30 insertions(+), 13 deletions(-)

diff --git a/async/src/async_door.rs b/async/src/async_door.rs
index df4419f..c3bc33f 100644
--- a/async/src/async_door.rs
+++ b/async/src/async_door.rs
@@ -59,6 +59,9 @@ impl<'a> AsyncDoor<'a> {
         AsyncDoorSocket::new(self)
     }
 
+    /// The `f` closure should return `Some` if the result should be returned
+    /// from `progress()`, or `None` to not do that.
+    /// XXX better docs, perhaps it won't take a closure anyway
     pub async fn progress<F, R>(&mut self,
         b: &mut Behaviour<'_>,
         f: F)
diff --git a/sshproto/src/conn.rs b/sshproto/src/conn.rs
index da464d5..556f8e9 100644
--- a/sshproto/src/conn.rs
+++ b/sshproto/src/conn.rs
@@ -242,7 +242,7 @@ impl<'a> Conn<'a> {
                             let kex =
                                 core::mem::replace(&mut self.kex, kex::Kex::new()?);
                             *output = Some(kex.handle_kexdhinit(&p, &self.sess_id)?);
-                            let reply = output.as_ref().trap()?.make_kexdhreply(&b.server()?)?;
+                            let reply = output.as_mut().trap()?.make_kexdhreply(&mut b.server()?).await?;
                             resp.push(reply.into()).trap()?;
                         }
                     }
diff --git a/sshproto/src/kex.rs b/sshproto/src/kex.rs
index 59b9e18..cc6db09 100644
--- a/sshproto/src/kex.rs
+++ b/sshproto/src/kex.rs
@@ -445,7 +445,7 @@ impl SharedSecret {
             SharedSecret::KexCurve25519(ref k) => {
                 let pubkey: salty::agreement::PublicKey = k.ours.as_ref().trap()?.into();
                 let mut kex_out = KexCurve25519::secret(&mut algos, p.q_c.0, kex_hash, sess_id)?;
-                kex_out.pubkey = Some(pubkey.to_bytes());
+                kex_out.kex_pub = Some(pubkey.to_bytes());
                 kex_out
             }
         };
@@ -467,14 +467,17 @@ pub(crate) struct KexOutput {
     // storage for kex packet reply content that outlives Kex
     // in make_kexdhreply().
 
-    // TODO: this should become generic for other kex methods
-    pubkey: Option<[u8; 32]>,
+    /// ephemeral public key octet string
+    kex_pub: Option<[u8; 32]>,
+    // the negotiated signature type
+    sig_type: SigType,
+    sig: Option<sign::OwnedSig>,
 }
 
 impl fmt::Debug for KexOutput {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         f.debug_struct("KexOutput")
-            .field("pubkey", &self.pubkey.is_some())
+            .field("kex_pub", &self.kex_pub.is_some())
             .finish_non_exhaustive()
     }
 }
@@ -488,15 +491,19 @@ impl<'a> KexOutput {
         let sess_id = sess_id.as_ref().unwrap_or(&h);
         let keys = Keys::new_from(k, &h, &sess_id, algos)?;
 
-        Ok(KexOutput { h, keys, pubkey: None })
+        Ok(KexOutput { h, keys, kex_pub: None, sig_type: algos.hostsig, sig: None })
     }
 
     // server only
-    pub fn make_kexdhreply(&'a self, b: &ServBehaviour) -> Result<Packet<'a>> {
-        let q_s = BinString(self.pubkey.as_ref().trap()?);
-        // TODO real signature
-        let k_s = Blob(PubKey::Ed25519(packets::Ed25519PubKey{ key: BinString(&[]) }));
-        let sig = Blob(Signature::Ed25519(packets::Ed25519Sig{ sig: BinString(&[]) }));
+    pub async fn make_kexdhreply<'b>(&'a mut self, b: &'a mut ServBehaviour<'_>) -> Result<Packet<'a>> {
+        let q_s = BinString(self.kex_pub.as_ref().trap()?);
+
+        // hostkeys list must contain the signature type
+        let key = b.hostkeys().await?.iter().find(|k| k.can_sign(&self.sig_type)).trap()?;
+        let k_s = Blob(key.pubkey());
+        self.sig = Some(key.sign(&self.h.as_slice(), None)?);
+        let sig: Signature = self.sig.as_ref().unwrap().into();
+        let sig = Blob(sig);
         Ok(packets::KexDHReply { k_s, q_s, sig }.into())
     }
 }
diff --git a/sshproto/src/sign.rs b/sshproto/src/sign.rs
index 638499f..7ec846c 100644
--- a/sshproto/src/sign.rs
+++ b/sshproto/src/sign.rs
@@ -20,7 +20,7 @@ use core::mem::discriminant;
 
 // RSA requires alloc.
 
-#[derive(Debug)]
+#[derive(Debug, Clone, Copy)]
 pub enum SigType {
     Ed25519,
     RSA256,
@@ -137,7 +137,14 @@ impl SignKey {
         k.try_into()
     }
 
-    pub(crate) fn sign_encode<'s>(&self, msg: &'s impl SSHEncode, parse_ctx: Option<&ParseContext>) -> Result<OwnedSig> {
+    /// Returns whether this `SignKey` can create a given signature type
+    pub(crate) fn can_sign(&self, sig_type: &SigType) -> bool {
+        match self {
+            SignKey::Ed25519(_) => matches!(sig_type, SigType::Ed25519),
+        }
+    }
+
+    pub(crate) fn sign<'s>(&self, msg: &'s impl SSHEncode, parse_ctx: Option<&ParseContext>) -> Result<OwnedSig> {
         match self {
             SignKey::Ed25519(k) => {
                 k.sign_parts(|h| {
-- 
GitLab