diff --git a/Cargo.lock b/Cargo.lock index 54ad2f4..77917b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -220,6 +220,33 @@ dependencies = [ "typenum", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "der" version = "0.7.9" @@ -257,6 +284,27 @@ dependencies = [ "spki", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "sha2", + "subtle", +] + [[package]] name = "elliptic-curve" version = "0.13.8" @@ -310,6 +358,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38793c55593b33412e3ae40c2c9781ffaa6f438f6f8c10f24e71846fbd7ae01e" + [[package]] name = "futures" version = "0.3.30" @@ -711,6 +765,12 @@ dependencies = [ "spki", ] +[[package]] +name = "platforms" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1013,6 +1073,7 @@ dependencies = [ "rstest", "service-binding", "sha1", + "signature", "ssh-encoding", "ssh-key", "subtle", @@ -1058,6 +1119,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca9b366a80cf18bb6406f4cf4d10aebfb46140a8c0c33f666a144c5c76ecbafc" dependencies = [ + "ed25519-dalek", "num-bigint-dig", "p256", "p384", diff --git a/Cargo.toml b/Cargo.toml index 85d7aaa..db14a2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,10 +27,11 @@ tokio = { version = "1", optional = true, features = ["rt", "net", "time"] } tokio-util = { version = "0.7.1", optional = true, features = ["codec"] } service-binding = { version = "^2.1" } ssh-encoding = { version = "0.2.0" } -ssh-key = { version = "0.6.6", features = ["rsa", "alloc"] } +ssh-key = { version = "0.6.6", features = ["crypto", "alloc"] } thiserror = "1.0.58" #uuid = { version = "1.8.0", features = ["v4"] } subtle = { version = "2", default-features = false } +signature = { version = "2.2.0", features = ["alloc"] } [features] default = ["agent"] diff --git a/examples/key_storage.rs b/examples/key_storage.rs index 4a636ec..9cfb356 100644 --- a/examples/key_storage.rs +++ b/examples/key_storage.rs @@ -212,7 +212,10 @@ impl Session for KeyStorage { } "session-bind@openssh.com" => match extension.parse_message::()? { Some(bind) => { - info!("Bind: {bind:?}"); + bind.verify_signature() + .map_err(|_| AgentError::ExtensionFailure)?; + + info!("Session binding: {bind:?}"); Ok(None) } None => Err(AgentError::Failure), diff --git a/src/proto/error.rs b/src/proto/error.rs index 4f713d1..7016c48 100644 --- a/src/proto/error.rs +++ b/src/proto/error.rs @@ -23,6 +23,10 @@ pub enum ProtoError { #[error("SSH key error: {0}")] SshKey(#[from] ssh_key::Error), + /// SSH signature error. + #[error("SSH signature error: {0}")] + SshSignature(#[from] signature::Error), + /// Received command was not supported. #[error("Command not supported ({command})")] UnsupportedCommand { diff --git a/src/proto/extension/message.rs b/src/proto/extension/message.rs index e8c3e3d..aca12e2 100644 --- a/src/proto/extension/message.rs +++ b/src/proto/extension/message.rs @@ -4,6 +4,7 @@ //! - [draft-miller-ssh-agent-14](https://www.ietf.org/archive/id/draft-miller-ssh-agent-14.html) //! - [OpenSSH `PROTOCOL.agent`](https://github.com/openssh/openssh-portable/blob/cbbdf868bce431a59e2fa36ca244d5739429408d/PROTOCOL.agent) +use signature::Verifier; use ssh_encoding::{CheckedSum, Decode, Encode, Error as EncodingError, Reader, Writer}; use ssh_key::{public::KeyData, Signature}; @@ -109,6 +110,21 @@ impl Encode for SessionBind { } } +impl SessionBind { + /// Verify the server's signature of the session identifier + /// using the public `host_key`. + /// + /// > When an agent receives \[a `session-bind@openssh.com` message\], + /// > it will verify the signature. + /// + /// Described in [OpenSSH PROTOCOL.agent ยง 1](https://github.com/openssh/openssh-portable/blob/cbbdf868bce431a59e2fa36ca244d5739429408d/PROTOCOL.agent#L31) + pub fn verify_signature(&self) -> Result<(), ProtoError> { + self.host_key + .verify(self.session_id.as_slice(), &self.signature)?; + Ok(()) + } +} + impl MessageExtension for SessionBind { const NAME: &'static str = "session-bind@openssh.com"; } @@ -149,6 +165,10 @@ mod tests { let bind = SessionBind::decode(&mut buffer)?; eprintln!("Bind: {bind:#?}"); + // Check `signature` (of `session_id`) against + // server public-key `host_key` + bind.verify_signature()?; + round_trip(bind)?; Ok(())