diff --git a/bin/cluelessh-key/src/main.rs b/bin/cluelessh-key/src/main.rs index ee66277..b9ba0af 100644 --- a/bin/cluelessh-key/src/main.rs +++ b/bin/cluelessh-key/src/main.rs @@ -137,7 +137,7 @@ fn info(id_file: &Path, decrypt: bool, show_private: bool) -> eyre::Result<()> { PrivateKey::Ed25519 { private_key, .. } => { println!( " private key: {}", - base64::prelude::BASE64_STANDARD.encode(private_key) + base64::prelude::BASE64_STANDARD.encode(private_key.as_bytes()) ) } PrivateKey::EcdsaSha2NistP256 { private_key, .. } => { diff --git a/lib/cluelessh-keys/src/crypto.rs b/lib/cluelessh-keys/src/crypto.rs index b09cef9..dbc4ef6 100644 --- a/lib/cluelessh-keys/src/crypto.rs +++ b/lib/cluelessh-keys/src/crypto.rs @@ -157,7 +157,7 @@ pub(crate) fn generate_private_key(params: KeyGenerationParams) -> PrivateKey { PrivateKey::Ed25519 { public_key: private_key.verifying_key(), - private_key: private_key.to_bytes(), + private_key, } } KeyType::Ecdsa => { diff --git a/lib/cluelessh-keys/src/private.rs b/lib/cluelessh-keys/src/private.rs index c09bcc3..5df7614 100644 --- a/lib/cluelessh-keys/src/private.rs +++ b/lib/cluelessh-keys/src/private.rs @@ -36,7 +36,7 @@ impl Debug for PlaintextPrivateKey { pub enum PrivateKey { Ed25519 { public_key: ed25519_dalek::VerifyingKey, - private_key: [u8; 32], // TODO: store a signing key! + private_key: ed25519_dalek::SigningKey, }, EcdsaSha2NistP256 { public_key: p256::ecdsa::VerifyingKey, @@ -326,9 +326,9 @@ impl PlaintextPrivateKey { // enc.string(b"ssh-ed25519"); enc.string(public_key); - let combined = private_key.len() + public_key.as_bytes().len(); + let combined = private_key.as_bytes().len() + public_key.as_bytes().len(); enc.u32(combined as u32); - enc.raw(private_key); + enc.raw(private_key.as_bytes()); enc.raw(public_key.as_bytes()); } PrivateKey::EcdsaSha2NistP256 { diff --git a/lib/cluelessh-keys/src/signature.rs b/lib/cluelessh-keys/src/signature.rs index 9b72d75..c140e96 100644 --- a/lib/cluelessh-keys/src/signature.rs +++ b/lib/cluelessh-keys/src/signature.rs @@ -1,6 +1,6 @@ use cluelessh_format::Writer; -use crate::public::PublicKey; +use crate::{private::PrivateKey, public::PublicKey}; // TODO SessionId newtype pub fn signature_data(session_id: [u8; 32], username: &str, pubkey: &PublicKey) -> Vec { @@ -17,3 +17,58 @@ pub fn signature_data(session_id: [u8; 32], username: &str, pubkey: &PublicKey) s.finish() } + +pub enum Signature { + Ed25519 { signature: ed25519_dalek::Signature }, + EcdsaSha2NistP256 { signature: p256::ecdsa::Signature }, +} + +impl Signature { + pub fn to_wire_encoding(&self) -> Vec { + let mut data = Writer::new(); + data.string(self.algorithm_name()); + match self { + Self::Ed25519 { signature } => { + // + data.string(signature.to_bytes()); + } + Self::EcdsaSha2NistP256 { signature } => { + // + + let (r, s) = signature.split_scalars(); + + let mut signature_blob = Writer::new(); + signature_blob.mpint(p256::U256::from(r.as_ref())); + signature_blob.mpint(p256::U256::from(s.as_ref())); + data.string(signature_blob.finish()); + } + } + data.finish() + } + + pub fn algorithm_name(&self) -> &'static str { + match self { + Self::Ed25519 { .. } => "ssh-ed25519", + Self::EcdsaSha2NistP256 { .. } => "ecdsa-sha2-nistp256", + } + } +} + +impl PrivateKey { + pub fn sign(&self, data: &[u8]) -> Signature { + match self { + Self::Ed25519 { private_key, .. } => { + use ed25519_dalek::Signer; + + let sig = private_key.sign(data); + Signature::Ed25519 { signature: sig } + } + Self::EcdsaSha2NistP256 { private_key, .. } => { + use p256::ecdsa::signature::Signer; + + let sig = private_key.sign(data); + Signature::EcdsaSha2NistP256 { signature: sig } + } + } + } +} diff --git a/lib/cluelessh-transport/src/client.rs b/lib/cluelessh-transport/src/client.rs index 68e3afc..6ddada5 100644 --- a/lib/cluelessh-transport/src/client.rs +++ b/lib/cluelessh-transport/src/client.rs @@ -4,7 +4,7 @@ use tracing::{debug, info, trace}; use crate::{ crypto::{ - self, AlgorithmName, EncodedSshSignature, EncryptionAlgorithm, HostKeySigningAlgorithm, + self, AlgorithmName, EncodedSshSignature, EncryptionAlgorithm, HostKeyVerifyAlgorithm, KeyExchangeSecret, SupportedAlgorithms, }, packet::{Packet, PacketTransport, ProtocolIdentParser}, @@ -36,7 +36,7 @@ enum ClientState { client_ident: Vec, server_ident: Vec, kex_secret: Option, - server_hostkey_algorithm: HostKeySigningAlgorithm, + server_hostkey_algorithm: HostKeyVerifyAlgorithm, encryption_client_to_server: EncryptionAlgorithm, encryption_server_to_client: EncryptionAlgorithm, client_kexinit: Vec, @@ -165,7 +165,7 @@ impl ClientConnection { let server_hostkey_algorithm = kexinit.name_list()?; let server_hostkey_algorithm = - sup_algs.hostkey.find(server_hostkey_algorithm.0)?; + sup_algs.hostkey_verify.find(server_hostkey_algorithm.0)?; debug!(name = %server_hostkey_algorithm.name(), "Using host key algorithm"); let encryption_algorithms_client_to_server = kexinit.name_list()?; diff --git a/lib/cluelessh-transport/src/crypto.rs b/lib/cluelessh-transport/src/crypto.rs index 1ba4375..53887cb 100644 --- a/lib/cluelessh-transport/src/crypto.rs +++ b/lib/cluelessh-transport/src/crypto.rs @@ -1,11 +1,11 @@ pub mod encrypt; -use cluelessh_format::{Reader, Writer}; +use cluelessh_format::Reader; use cluelessh_keys::{ private::{PlaintextPrivateKey, PrivateKey}, public::PublicKey, + signature::Signature, }; -use p256::ecdsa::signature::Signer; use sha2::Digest; use crate::{ @@ -110,108 +110,73 @@ impl AlgorithmName for EncryptionAlgorithm { pub struct EncodedSshSignature(pub Vec); pub struct HostKeySigningAlgorithm { + private_key: PrivateKey, +} + +impl AlgorithmName for HostKeySigningAlgorithm { + fn name(&self) -> &'static str { + self.private_key.algorithm_name() + } +} + +impl HostKeySigningAlgorithm { + pub fn new(private_key: PrivateKey) -> Self { + Self { private_key } + } + pub fn sign(&self, data: &[u8]) -> Signature { + self.private_key.sign(data) + } + pub fn public_key(&self) -> PublicKey { + self.private_key.public_key() + } +} + +pub struct HostKeyVerifyAlgorithm { name: &'static str, - hostkey_private: Vec, - public_key: fn(private_key: &[u8]) -> PublicKey, - sign: fn(private_key: &[u8], data: &[u8]) -> EncodedSshSignature, pub verify: fn(public_key: &[u8], message: &[u8], signature: &EncodedSshSignature) -> Result<()>, } -impl AlgorithmName for HostKeySigningAlgorithm { +impl AlgorithmName for HostKeyVerifyAlgorithm { fn name(&self) -> &'static str { self.name } } -impl HostKeySigningAlgorithm { - pub fn sign(&self, data: &[u8]) -> EncodedSshSignature { - (self.sign)(&self.hostkey_private, data) - } - pub fn public_key(&self) -> PublicKey { - (self.public_key)(&self.hostkey_private) - } -} +const HOSTKEY_VERIFY_ED25519: HostKeyVerifyAlgorithm = HostKeyVerifyAlgorithm { + name: "ssh-ed25519", + verify: |public_key, message, signature| { + // Parse out public key + let mut public_key = Reader::new(public_key); + let public_key_alg = public_key.string()?; + if public_key_alg != b"ssh-ed25519" { + return Err(peer_error!("incorrect algorithm public host key")); + } + let public_key = public_key.string()?; + let Ok(public_key) = public_key.try_into() else { + return Err(peer_error!("incorrect length for public host key")); + }; + let public_key = ed25519_dalek::VerifyingKey::from_bytes(public_key) + .map_err(|err| peer_error!("incorrect public host key: {err}"))?; -pub fn hostkey_ed25519(hostkey_private: Vec) -> HostKeySigningAlgorithm { - HostKeySigningAlgorithm { - name: "ssh-ed25519", - hostkey_private, - public_key: |key| { - let key = ed25519_dalek::SigningKey::from_bytes(key.try_into().unwrap()); - let public_key = key.verifying_key(); - PublicKey::Ed25519 { public_key } - }, - sign: |key, data| { - let key = ed25519_dalek::SigningKey::from_bytes(key.try_into().unwrap()); - let signature = key.sign(data); + // Parse out signature + let mut signature = Reader::new(&signature.0); + let alg = signature.string()?; + if alg != b"ssh-ed25519" { + return Err(peer_error!("incorrect algorithm for signature")); + } + let signature = signature.string()?; + let Ok(signature) = signature.try_into() else { + return Err(peer_error!("incorrect length for signature")); + }; + let signature = ed25519_dalek::Signature::from_bytes(signature); - // - let mut data = Writer::new(); - data.string(b"ssh-ed25519"); - data.string(signature.to_bytes()); - EncodedSshSignature(data.finish()) - }, - verify: |public_key, message, signature| { - // Parse out public key - let mut public_key = Reader::new(public_key); - let public_key_alg = public_key.string()?; - if public_key_alg != b"ssh-ed25519" { - return Err(peer_error!("incorrect algorithm public host key")); - } - let public_key = public_key.string()?; - let Ok(public_key) = public_key.try_into() else { - return Err(peer_error!("incorrect length for public host key")); - }; - let public_key = ed25519_dalek::VerifyingKey::from_bytes(public_key) - .map_err(|err| peer_error!("incorrect public host key: {err}"))?; - - // Parse out signature - let mut signature = Reader::new(&signature.0); - let alg = signature.string()?; - if alg != b"ssh-ed25519" { - return Err(peer_error!("incorrect algorithm for signature")); - } - let signature = signature.string()?; - let Ok(signature) = signature.try_into() else { - return Err(peer_error!("incorrect length for signature")); - }; - let signature = ed25519_dalek::Signature::from_bytes(signature); - - // Verify - public_key - .verify_strict(message, &signature) - .map_err(|err| peer_error!("incorrect signature: {err}")) - }, - } -} -pub fn hostkey_ecdsa_sha2_p256(hostkey_private: Vec) -> HostKeySigningAlgorithm { - HostKeySigningAlgorithm { - name: "ecdsa-sha2-nistp256", - hostkey_private, - public_key: |key| { - let key = p256::ecdsa::SigningKey::from_slice(key).unwrap(); - PublicKey::EcdsaSha2NistP256 { - public_key: *key.verifying_key(), - } - }, - sign: |key, data| { - let key = p256::ecdsa::SigningKey::from_slice(key).unwrap(); - let signature: p256::ecdsa::Signature = key.sign(data); - let (r, s) = signature.split_scalars(); - - // - let mut data = Writer::new(); - data.string(b"ecdsa-sha2-nistp256"); - let mut signature_blob = Writer::new(); - signature_blob.mpint(p256::U256::from(r.as_ref())); - signature_blob.mpint(p256::U256::from(s.as_ref())); - data.string(signature_blob.finish()); - EncodedSshSignature(data.finish()) - }, - verify: |_public_key, _message, _signature| todo!("ecdsa p256 verification"), - } -} + // Verify + public_key + .verify_strict(message, &signature) + .map_err(|err| peer_error!("incorrect signature: {err}")) + }, +}; pub struct AlgorithmNegotiation { pub supported: Vec, @@ -244,7 +209,8 @@ impl AlgorithmNegotiation { pub struct SupportedAlgorithms { pub key_exchange: AlgorithmNegotiation, - pub hostkey: AlgorithmNegotiation, + pub hostkey_sign: AlgorithmNegotiation, + pub hostkey_verify: AlgorithmNegotiation, pub encryption_to_peer: AlgorithmNegotiation, pub encryption_from_peer: AlgorithmNegotiation, pub mac_to_peer: AlgorithmNegotiation<&'static str>, @@ -258,21 +224,19 @@ impl SupportedAlgorithms { pub fn secure(host_keys: &[PlaintextPrivateKey]) -> Self { let supported_host_keys = host_keys .iter() - .map(|key| match &key.private_key { - PrivateKey::Ed25519 { private_key, .. } => hostkey_ed25519(private_key.to_vec()), - PrivateKey::EcdsaSha2NistP256 { private_key, .. } => { - hostkey_ecdsa_sha2_p256(private_key.to_bytes().to_vec()) - } - }) + .map(|key| HostKeySigningAlgorithm::new(key.private_key.clone())) .collect(); Self { key_exchange: AlgorithmNegotiation { supported: vec![KEX_CURVE_25519_SHA256, KEX_ECDH_SHA2_NISTP256], }, - hostkey: AlgorithmNegotiation { + hostkey_sign: AlgorithmNegotiation { supported: supported_host_keys, }, + hostkey_verify: AlgorithmNegotiation { + supported: vec![HOSTKEY_VERIFY_ED25519], // TODO: p256 + }, encryption_to_peer: AlgorithmNegotiation { supported: vec![encrypt::CHACHA20POLY1305, encrypt::AES256_GCM], }, diff --git a/lib/cluelessh-transport/src/server.rs b/lib/cluelessh-transport/src/server.rs index bd03b97..7f040bc 100644 --- a/lib/cluelessh-transport/src/server.rs +++ b/lib/cluelessh-transport/src/server.rs @@ -146,7 +146,7 @@ impl ServerConnection { debug!(name = %kex_algorithm.name(), "Using KEX algorithm"); let server_host_key_algorithm = - sup_algs.hostkey.find(kex.server_host_key_algorithms.0)?; + sup_algs.hostkey_sign.find(kex.server_host_key_algorithms.0)?; debug!(name = %server_host_key_algorithm.name(), "Using host key algorithm"); // TODO: Implement aes128-ctr @@ -268,7 +268,7 @@ impl ServerConnection { let packet = Packet::new_msg_kex_ecdh_reply( &pub_hostkey.to_wire_encoding(), &server_public_key, - &signature.0, + &signature.to_wire_encoding(), ); self.packet_transport.queue_packet(packet);