From a8d25d856f93bd2bd312a6c3a183dc94ce29b982 Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Tue, 13 Aug 2024 17:46:24 +0200 Subject: [PATCH] ECDSA! --- Cargo.lock | 1 + ssh-transport/Cargo.toml | 1 + ssh-transport/src/crypto.rs | 37 +++++++++++++--------- ssh-transport/src/lib.rs | 7 ++--- ssh-transport/src/packet.rs | 63 ------------------------------------- ssh-transport/src/parse.rs | 24 ++++++++++++-- 6 files changed, 49 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ccc5e72..1432bfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -928,6 +928,7 @@ version = "0.1.0" dependencies = [ "aes-gcm", "chacha20", + "crypto-bigint", "ed25519-dalek", "eyre", "hex-literal", diff --git a/ssh-transport/Cargo.toml b/ssh-transport/Cargo.toml index 9118844..dbc0bac 100644 --- a/ssh-transport/Cargo.toml +++ b/ssh-transport/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] aes-gcm = "0.10.3" chacha20 = "0.9.1" +crypto-bigint = "0.5.5" ed25519-dalek = { version = "2.1.1" } eyre = "0.6.12" p256 = { version = "0.13.2", features = ["ecdh", "ecdsa"] } diff --git a/ssh-transport/src/crypto.rs b/ssh-transport/src/crypto.rs index ae7cb03..b25d8e8 100644 --- a/ssh-transport/src/crypto.rs +++ b/ssh-transport/src/crypto.rs @@ -7,7 +7,7 @@ use subtle::ConstantTimeEq; use crate::{ client_error, packet::{EncryptedPacket, MsgKind, Packet, RawPacket}, - parse::Writer, + parse::{self, Writer}, Msg, Result, SshRng, }; @@ -187,17 +187,30 @@ pub fn hostkey_ecdsa_sha2_p256(hostkey_private: Vec) -> HostKeySigningAlgori hostkey_private, public_key: |key| { let key = p256::ecdsa::SigningKey::from_slice(key).unwrap(); - key.verifying_key() - .to_encoded_point(true) - .as_bytes() - .to_vec(); - todo!() + let public_key = key.verifying_key(); + let mut data = Writer::new(); + + // + data.string(b"ecdsa-sha2-nistp256"); + data.string(b"nistp256"); + // > point compression MAY be used. + // But OpenSSH does not appear to support that, so let's NOT use it. + data.string(public_key.to_encoded_point(false).as_bytes()); + EncodedSshPublicHostKey(data.finish()) }, sign: |key, data| { let key = p256::ecdsa::SigningKey::from_slice(key).unwrap(); let signature: p256::ecdsa::Signature = key.sign(data); - signature.to_vec(); - todo!() + 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()) }, } } @@ -404,12 +417,8 @@ fn derive_key( output } -pub(crate) fn encode_mpint_for_hash(mut key: &[u8], mut add_to_hash: impl FnMut(&[u8])) { - while key[0] == 0 { - key = &key[1..]; - } - // If the first high bit is set, pad it with a zero. - let pad_zero = (key[0] & 0b10000000) > 1; +pub(crate) fn encode_mpint_for_hash(key: &[u8], mut add_to_hash: impl FnMut(&[u8])) { + let (key, pad_zero) = parse::fixup_mpint(key); add_to_hash(&u32::to_be_bytes((key.len() + (pad_zero as usize)) as u32)); if pad_zero { add_to_hash(&[0]); diff --git a/ssh-transport/src/lib.rs b/ssh-transport/src/lib.rs index 810831f..415a85f 100644 --- a/ssh-transport/src/lib.rs +++ b/ssh-transport/src/lib.rs @@ -7,10 +7,7 @@ use core::str; use std::{collections::VecDeque, mem::take}; use crypto::{AlgorithmName, AlgorithmNegotiation, EncryptionAlgorithm, HostKeySigningAlgorithm}; -use packet::{ - KeyExchangeEcDhInitPacket, KeyExchangeInitPacket, Packet, PacketTransport, SshPublicKey, - SshSignature, -}; +use packet::{KeyExchangeEcDhInitPacket, KeyExchangeInitPacket, Packet, PacketTransport}; use parse::{NameList, Parser, Writer}; use rand::RngCore; use sha2::Digest; @@ -199,7 +196,7 @@ impl ServerConnection { let hostkey_algorithms = AlgorithmNegotiation { supported: vec![ - //crypto::hostkey_ed25519(ED25519_PRIVKEY_BYTES.to_vec()), + crypto::hostkey_ed25519(ED25519_PRIVKEY_BYTES.to_vec()), crypto::hostkey_ecdsa_sha2_p256(ECDSA_P256_PRIVKEY_BYTES.to_vec()), ], }; diff --git a/ssh-transport/src/packet.rs b/ssh-transport/src/packet.rs index de3757f..c891e2b 100644 --- a/ssh-transport/src/packet.rs +++ b/ssh-transport/src/packet.rs @@ -318,69 +318,6 @@ impl<'a> KeyExchangeEcDhInitPacket<'a> { } } -#[derive(Debug)] -pub(crate) struct SshPublicKey<'a> { - pub(crate) format: &'a [u8], - pub(crate) data: &'a [u8], -} -impl SshPublicKey<'_> { - pub(crate) fn to_bytes(&self) -> Vec { - let mut data = Writer::new(); - // ed25519-specific! - // - data.string(self.format); - data.string(self.data); - data.finish() - } -} -#[derive(Debug)] -pub(crate) struct SshPublicKeyEcdsa<'a> { - pub(crate) format: &'a [u8], - pub(crate) ident: &'a [u8], - pub(crate) data: &'a [u8], -} -impl SshPublicKeyEcdsa<'_> { - pub(crate) fn to_bytes(&self) -> Vec { - let mut data = Writer::new(); - // ECDSA-specific! - // - data.string(self.format); - data.string(self.ident); - data.string(self.data); - data.finish() - } -} - -#[derive(Debug)] -pub(crate) struct SshSignature<'a> { - pub(crate) format: &'a [u8], - pub(crate) data: &'a [u8], -} - -impl SshSignature<'_> { - pub(crate) fn to_bytes(&self) -> Vec { - let mut data = Writer::new(); - // - data.string(self.format); - data.string(self.data); - data.finish() - } -} - -pub(crate) struct SshSignatureEcdsa<'a> { - pub(crate) format: &'a [u8], - pub(crate) data: &'a [u8], -} - -impl SshSignatureEcdsa<'_> { - pub(crate) fn to_bytes(&self) -> Vec { - let mut data = Writer::new(); - // - data.string(self.format); - data.finish() - } -} - pub(crate) struct RawPacket { pub mac_len: usize, pub raw: Vec, diff --git a/ssh-transport/src/parse.rs b/ssh-transport/src/parse.rs index 44dae6e..626bf1b 100644 --- a/ssh-transport/src/parse.rs +++ b/ssh-transport/src/parse.rs @@ -104,8 +104,18 @@ impl Writer { self.string(list.0.as_bytes()); } - pub fn mpint(&mut self, _mpint: MpInt<'_>) { - todo!("implement correctly?") + pub fn mpint(&mut self, uint: crypto_bigint::Uint) + where + crypto_bigint::Uint: crypto_bigint::ArrayEncoding, + { + let bytes = crypto_bigint::ArrayEncoding::to_be_byte_array(&uint); + let (bytes, pad_zero) = fixup_mpint(&bytes); + let len = bytes.len() + (pad_zero as usize); + self.u32(len as u32); + if pad_zero { + self.u8(0); + } + self.write(bytes); } pub fn string(&mut self, data: &[u8]) { @@ -122,6 +132,16 @@ impl Writer { } } +/// Returns an array of significant bits for the mpint, +/// and whether a leading 0 needs to be added for padding. +pub fn fixup_mpint(mut int_encoded: &[u8]) -> (&[u8], bool) { + while int_encoded[0] == 0 { + int_encoded = &int_encoded[1..]; + } + // If the first high bit is set, pad it with a zero. + (int_encoded, (int_encoded[0] & 0b10000000) > 1) +} + #[derive(Clone, Copy)] pub struct NameList<'a>(pub &'a str);