From e59bb68f003f9af76cbe9036c7c3b718bdee1303 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Wed, 14 Feb 2024 12:15:31 -0300 Subject: [PATCH 1/7] First round of changes --- russh-keys/Cargo.toml | 3 + russh-keys/src/agent/client.rs | 24 +++++-- russh-keys/src/format/pkcs8.rs | 8 ++- russh-keys/src/key.rs | 125 ++++++++++++++++++++++++++------- russh-keys/src/lib.rs | 53 +++++++------- russh/src/client/encrypted.rs | 3 +- russh/src/client/mod.rs | 5 +- russh/src/key.rs | 37 ++++++++-- russh/src/negotiation.rs | 6 +- 9 files changed, 190 insertions(+), 74 deletions(-) diff --git a/russh-keys/Cargo.toml b/russh-keys/Cargo.toml index 3c20f143..e4064a3e 100644 --- a/russh-keys/Cargo.toml +++ b/russh-keys/Cargo.toml @@ -47,7 +47,9 @@ log = "0.4" md5 = "0.7" num-bigint = "0.4" num-integer = "0.1" +# You must select either openssl or rsa openssl = { version = "0.10", optional = true } +rsa = { version = "0.9.6", features = ["sha1", "sha2"], optional = true} p256 = "0.13" p521 = "0.13" pbkdf2 = "0.11" @@ -68,6 +70,7 @@ tokio-stream = { version = "0.1", features = ["net"] } yasna = { version = "0.5.0", features = ["bit-vec", "num-bigint"] } [features] +default = ["rsa"] vendored-openssl = ["openssl", "openssl/vendored"] [dev-dependencies] diff --git a/russh-keys/src/agent/client.rs b/russh-keys/src/agent/client.rs index 36075c35..b1b3e635 100644 --- a/russh-keys/src/agent/client.rs +++ b/russh-keys/src/agent/client.rs @@ -2,6 +2,8 @@ use std::convert::TryFrom; use byteorder::{BigEndian, ByteOrder}; use log::{debug, info}; +#[cfg(not(feature = "openssl"))] +use rsa::traits::PublicKeyParts; use russh_cryptovec::CryptoVec; use tokio; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; @@ -138,6 +140,10 @@ impl AgentClient { self.buf.extend_ssh_mpint(&key.q().unwrap().to_vec()); self.buf.extend_ssh_string(b""); } + #[cfg(not(feature = "openssl"))] + key::KeyPair::RSA { ref key, .. } => { + todo!(); + } } if !constraints.is_empty() { for cons in constraints { @@ -346,7 +352,6 @@ impl AgentClient { self.buf.extend_ssh_string(data); debug!("public = {:?}", public); let hash = match public { - #[cfg(feature = "openssl")] PublicKey::RSA { hash, .. } => match hash { SignatureHash::SHA2_256 => 2, SignatureHash::SHA2_512 => 4, @@ -529,14 +534,23 @@ impl AgentClient { fn key_blob(public: &key::PublicKey, buf: &mut CryptoVec) -> Result<(), Error> { match *public { - #[cfg(feature = "openssl")] PublicKey::RSA { ref key, .. } => { buf.extend(&[0, 0, 0, 0]); let len0 = buf.len(); buf.extend_ssh_string(b"ssh-rsa"); - let rsa = key.0.rsa()?; - buf.extend_ssh_mpint(&rsa.e().to_vec()); - buf.extend_ssh_mpint(&rsa.n().to_vec()); + #[cfg(feature = "openssl")] + { + let rsa = key.0.rsa()?; + buf.extend_ssh_mpint(&rsa.e().to_vec()); + buf.extend_ssh_mpint(&rsa.n().to_vec()); + } + #[cfg(not(feature = "openssl"))] + { + let rsa = key.clone(); + buf.extend_ssh_mpint(&rsa.e().to_bytes_be()); + buf.extend_ssh_mpint(&rsa.n().to_bytes_be()); + } + let len1 = buf.len(); #[allow(clippy::indexing_slicing)] // length is known BigEndian::write_u32(&mut buf[5..], (len1 - len0) as u32); diff --git a/russh-keys/src/format/pkcs8.rs b/russh-keys/src/format/pkcs8.rs index 53ec0d7d..99ccec2c 100644 --- a/russh-keys/src/format/pkcs8.rs +++ b/russh-keys/src/format/pkcs8.rs @@ -10,6 +10,8 @@ use openssl::pkey::Private; use openssl::rsa::Rsa; #[cfg(test)] use rand_core::OsRng; +#[cfg(not(feature = "openssl"))] +use rsa::RsaPrivateKey; use yasna::BERReaderSeq; use {std, yasna}; @@ -164,6 +166,11 @@ fn read_key_v1(reader: &mut BERReaderSeq) -> Result { } } +#[cfg(not(feature = "openssl"))] +fn write_key_v0(writer: &mut yasna::DERWriterSeq, key: &RsaPrivateKey) { + todo!() +} + #[cfg(feature = "openssl")] fn write_key_v0(writer: &mut yasna::DERWriterSeq, key: &Rsa) { writer.next().write_u32(0); @@ -316,7 +323,6 @@ pub fn encode_pkcs8(key: &key::KeyPair) -> Vec { yasna::construct_der(|writer| { writer.write_sequence(|writer| match *key { key::KeyPair::Ed25519(ref pair) => write_key_v1(writer, pair), - #[cfg(feature = "openssl")] key::KeyPair::RSA { ref key, .. } => write_key_v0(writer, key), }) }) diff --git a/russh-keys/src/key.rs b/russh-keys/src/key.rs index 4c336a58..98a2d594 100644 --- a/russh-keys/src/key.rs +++ b/russh-keys/src/key.rs @@ -15,13 +15,28 @@ use std::convert::TryFrom; use ed25519_dalek::{Signer, Verifier}; -#[cfg(feature = "openssl")] -use openssl::pkey::{Private, Public}; use p256::elliptic_curve::generic_array::typenum::Unsigned; use p521::elliptic_curve::generic_array::GenericArray; use rand_core::OsRng; use russh_cryptovec::CryptoVec; use serde::{Deserialize, Serialize}; +#[cfg(feature = "openssl")] +use { + openssl::pkey::{Private, Public}, + sha2::{Digest, Sha256}, +}; +#[cfg(not(feature = "openssl"))] +use { + rsa::{ + pss::SigningKey, + sha2::Sha512, + sha2::{Digest, Sha256}, + signature::{RandomizedSigner, SignatureEncoding}, + traits::PublicKeyParts, + BigUint, RsaPrivateKey, RsaPublicKey, + }, + sha1::Sha1, +}; use crate::encoding::{Encoding, Reader}; pub use crate::signature::*; @@ -124,6 +139,11 @@ pub enum PublicKey { key: OpenSSLPKey, hash: SignatureHash, }, + #[cfg(not(feature = "openssl"))] + RSA { + key: RsaPublicKey, + hash: SignatureHash, + }, #[doc(hidden)] P256(p256::PublicKey), #[doc(hidden)] @@ -133,7 +153,6 @@ pub enum PublicKey { impl PartialEq for PublicKey { fn eq(&self, other: &Self) -> bool { match (self, other) { - #[cfg(feature = "openssl")] (Self::RSA { key: a, .. }, Self::RSA { key: b, .. }) => a == b, (Self::Ed25519(a), Self::Ed25519(b)) => a == b, (Self::P256(a), Self::P256(b)) => a == b, @@ -214,7 +233,31 @@ impl PublicKey { } #[cfg(not(feature = "openssl"))] { - unreachable!() + // Assuming that `pubkey` is a reader that provides access to the key components + let mut p = pubkey.reader(0); + + // Read the key algorithm + let key_algo = p.read_string()?; + if key_algo != b"ssh-rsa" + && key_algo != b"rsa-sha2-256" + && key_algo != b"rsa-sha2-512" + { + return Err(Error::CouldNotReadKey); + } + + // Read the key components + let key_e = p.read_string()?; + let key_n = p.read_string()?; + + // Convert byte slices to BigUints + let e = BigUint::from_bytes_be(key_e); + let n = BigUint::from_bytes_be(key_n); + + Ok(PublicKey::RSA { + key: RsaPublicKey::new(n, e).unwrap(), + hash: SignatureHash::from_rsa_hostkey_algo(algo) + .unwrap_or(SignatureHash::SHA1), + }) } } b"ecdsa-sha2-nistp256" => { @@ -249,7 +292,6 @@ impl PublicKey { pub fn name(&self) -> &'static str { match *self { PublicKey::Ed25519(_) => ED25519.0, - #[cfg(feature = "openssl")] PublicKey::RSA { ref hash, .. } => hash.name().0, PublicKey::P256(_) => ECDSA_SHA2_NISTP256.0, PublicKey::P521(_) => ECDSA_SHA2_NISTP521.0, @@ -277,6 +319,10 @@ impl PublicKey { }; verify().unwrap_or(false) } + #[cfg(not(feature = "openssl"))] + PublicKey::RSA { ref key, ref hash } => { + todo!() + } PublicKey::P256(ref public) => { const FIELD_LEN: usize = ::FieldBytesSize::USIZE; @@ -336,13 +382,11 @@ impl PublicKey { pub fn fingerprint(&self) -> String { use super::PublicKeyBase64; let key = self.public_key_bytes(); - use sha2::{Digest, Sha256}; let mut hasher = Sha256::new(); hasher.update(&key[..]); data_encoding::BASE64_NOPAD.encode(&hasher.finalize()) } - #[cfg(feature = "openssl")] pub fn set_algorithm(&mut self, algorithm: &[u8]) { if let PublicKey::RSA { ref mut hash, .. } = self { if algorithm == b"rsa-sha2-512" { @@ -354,9 +398,6 @@ impl PublicKey { } } } - - #[cfg(not(feature = "openssl"))] - pub fn set_algorithm(&mut self, _: &[u8]) {} } impl Verify for PublicKey { @@ -377,6 +418,11 @@ pub enum KeyPair { key: openssl::rsa::Rsa, hash: SignatureHash, }, + #[cfg(not(feature = "openssl"))] + RSA { + key: RsaPrivateKey, + hash: SignatureHash, + }, } impl Clone for KeyPair { @@ -386,7 +432,6 @@ impl Clone for KeyPair { Self::Ed25519(kp) => { Self::Ed25519(ed25519_dalek::SigningKey::from_bytes(&kp.to_bytes())) } - #[cfg(feature = "openssl")] Self::RSA { key, hash } => Self::RSA { key: key.clone(), hash: *hash, @@ -403,7 +448,6 @@ impl std::fmt::Debug for KeyPair { "Ed25519 {{ public: {:?}, secret: (hidden) }}", key.verifying_key().as_bytes() ), - #[cfg(feature = "openssl")] KeyPair::RSA { .. } => write!(f, "RSA {{ (hidden) }}"), } } @@ -430,6 +474,14 @@ impl KeyPair { hash: *hash, } } + #[cfg(not(feature = "openssl"))] + KeyPair::RSA { ref key, ref hash } => { + let cloned_key = RsaPublicKey::new(key.n().clone(), key.e().clone()); + PublicKey::RSA { + key: cloned_key.unwrap(), + hash: *hash, + } + } }) } @@ -437,7 +489,6 @@ impl KeyPair { pub fn name(&self) -> &'static str { match *self { KeyPair::Ed25519(_) => ED25519.0, - #[cfg(feature = "openssl")] KeyPair::RSA { ref hash, .. } => hash.name().0, } } @@ -452,9 +503,11 @@ impl KeyPair { Some(KeyPair::Ed25519(keypair)) } - #[cfg(feature = "openssl")] pub fn generate_rsa(bits: usize, hash: SignatureHash) -> Option { + #[cfg(feature = "openssl")] let key = openssl::rsa::Rsa::generate(bits as u32).ok()?; + #[cfg(not(feature = "openssl"))] + let key = RsaPrivateKey::new(&mut OsRng, bits).ok()?; Some(KeyPair::RSA { key, hash }) } @@ -465,7 +518,6 @@ impl KeyPair { KeyPair::Ed25519(ref secret) => Ok(Signature::Ed25519(SignatureBytes( secret.sign(to_sign).to_bytes(), ))), - #[cfg(feature = "openssl")] KeyPair::RSA { ref key, ref hash } => Ok(Signature::RSA { bytes: rsa_signature(hash, key, to_sign)?, hash: *hash, @@ -490,7 +542,6 @@ impl KeyPair { buffer.extend_ssh_string(ED25519.0.as_bytes()); buffer.extend_ssh_string(signature.to_bytes().as_slice()); } - #[cfg(feature = "openssl")] KeyPair::RSA { ref key, ref hash } => { // https://tools.ietf.org/html/draft-rsa-dsa-sha2-256-02#section-2.2 let signature = rsa_signature(hash, key, to_sign.as_ref())?; @@ -515,7 +566,6 @@ impl KeyPair { buffer.extend_ssh_string(ED25519.0.as_bytes()); buffer.extend_ssh_string(signature.to_bytes().as_slice()); } - #[cfg(feature = "openssl")] KeyPair::RSA { ref key, ref hash } => { // https://tools.ietf.org/html/draft-rsa-dsa-sha2-256-02#section-2.2 let signature = rsa_signature(hash, key, buffer)?; @@ -529,11 +579,9 @@ impl KeyPair { } /// Create a copy of an RSA key with a specified hash algorithm. - #[cfg(feature = "openssl")] pub fn with_signature_hash(&self, hash: SignatureHash) -> Option { match self { KeyPair::Ed25519(_) => None, - #[cfg(feature = "openssl")] KeyPair::RSA { key, .. } => Some(KeyPair::RSA { key: key.clone(), hash, @@ -566,11 +614,26 @@ fn rsa_signature( Ok(signer.sign_to_vec()?) } +#[cfg(not(feature = "openssl"))] +fn rsa_signature(hash: &SignatureHash, key: &RsaPrivateKey, b: &[u8]) -> Result, Error> { + let private_key = key.clone(); + let mut rng = OsRng.clone(); + + let signing_key = match hash { + SignatureHash::SHA2_256 => { + SigningKey::::new(private_key).sign_with_rng(&mut rng, b) + } + SignatureHash::SHA2_512 => { + SigningKey::::new(private_key).sign_with_rng(&mut rng, b) + } + SignatureHash::SHA1 => SigningKey::::new(private_key).sign_with_rng(&mut rng, b), + }; + + Ok(signing_key.to_vec()) +} + /// Parse a public key from a byte slice. -pub fn parse_public_key( - p: &[u8], - #[cfg(feature = "openssl")] prefer_hash: Option, -) -> Result { +pub fn parse_public_key(p: &[u8], refer_hash: Option) -> Result { let mut pos = p.reader(0); let t = pos.read_string()?; if t == b"ssh-ed25519" { @@ -595,7 +658,21 @@ pub fn parse_public_key( BigNum::from_slice(n)?, BigNum::from_slice(e)?, )?)?), - hash: prefer_hash.unwrap_or(SignatureHash::SHA2_256), + hash: refer_hash.unwrap_or(SignatureHash::SHA2_256), + }); + } + #[cfg(not(feature = "openssl"))] + { + let e = pos.read_string()?; + let n = pos.read_string()?; + + // Convert byte slices to BigUints + let e = BigUint::from_bytes_be(e); + let n = BigUint::from_bytes_be(n); + + return Ok(PublicKey::RSA { + key: RsaPublicKey::new(n, e).unwrap(), + hash: refer_hash.unwrap_or(SignatureHash::SHA2_256), }); } } diff --git a/russh-keys/src/lib.rs b/russh-keys/src/lib.rs index 9d211edb..158b866b 100644 --- a/russh-keys/src/lib.rs +++ b/russh-keys/src/lib.rs @@ -74,6 +74,8 @@ use byteorder::{BigEndian, WriteBytesExt}; use data_encoding::BASE64_MIME; use hmac::{Hmac, Mac}; use log::debug; +#[cfg(not(feature = "openssl"))] +pub use rsa::traits::PublicKeyParts; use sha1::Sha1; use thiserror::Error; @@ -192,11 +194,7 @@ pub fn load_public_key>(path: P) -> Result /// ``` pub fn parse_public_key_base64(key: &str) -> Result { let base = BASE64_MIME.decode(key.as_bytes())?; - key::parse_public_key( - &base, - #[cfg(feature = "openssl")] - None, - ) + key::parse_public_key(&base, None) } pub trait PublicKeyBase64 { @@ -224,17 +222,25 @@ impl PublicKeyBase64 for key::PublicKey { .unwrap(); s.extend_from_slice(publickey.as_bytes()); } - #[cfg(feature = "openssl")] key::PublicKey::RSA { ref key, .. } => { use encoding::Encoding; let name = b"ssh-rsa"; #[allow(clippy::unwrap_used)] // Vec<>.write_all can't fail s.write_u32::(name.len() as u32).unwrap(); s.extend_from_slice(name); - #[allow(clippy::unwrap_used)] // TODO check - s.extend_ssh_mpint(&key.0.rsa().unwrap().e().to_vec()); - #[allow(clippy::unwrap_used)] // TODO check - s.extend_ssh_mpint(&key.0.rsa().unwrap().n().to_vec()); + #[cfg(feature = "openssl")] + { + #[allow(clippy::unwrap_used)] // TODO check + s.extend_ssh_mpint(&key.0.rsa().unwrap().e().to_vec()); + #[allow(clippy::unwrap_used)] // TODO check + s.extend_ssh_mpint(&key.0.rsa().unwrap().n().to_vec()); + } + #[cfg(not(feature = "openssl"))] + { + let kk = key.clone(); + s.extend_ssh_mpint(&(kk.e().to_bytes_be())); + s.extend_ssh_mpint(&(kk.n().to_bytes_be())); + } } key::PublicKey::P256(ref publickey) => { use encoding::Encoding; @@ -267,11 +273,18 @@ impl PublicKeyBase64 for key::KeyPair { s.write_u32::(public.len() as u32).unwrap(); s.extend_from_slice(public.as_slice()); } - #[cfg(feature = "openssl")] key::KeyPair::RSA { ref key, .. } => { use encoding::Encoding; - s.extend_ssh_mpint(&key.e().to_vec()); - s.extend_ssh_mpint(&key.n().to_vec()); + #[cfg(feature = "openssl")] + { + s.extend_ssh_mpint(&key.e().to_vec()); + s.extend_ssh_mpint(&key.n().to_vec()); + } + #[cfg(not(feature = "openssl"))] + { + s.extend_ssh_mpint(&key.e().to_bytes_be()); + s.extend_ssh_mpint(&key.n().to_bytes_be()); + } } } s @@ -494,7 +507,6 @@ dP3jryYgvsCIBAA5jMWSjrmnOTXhidqcOy4xYCrAttzSnZ/cUadfBenL+DQq6neffw7j8r sJWR7W+cGvJ/vLsw== -----END OPENSSH PRIVATE KEY-----"; - #[cfg(feature = "openssl")] const RSA_KEY: &str = "-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn NhAAAAAwEAAQAAAQEAuSvQ9m76zhRB4m0BUKPf17lwccj7KQ1Qtse63AOqP/VYItqEH8un @@ -536,14 +548,12 @@ QR+u0AypRPmzHnOPAAAAEXJvb3RAMTQwOTExNTQ5NDBkAQ== } #[test] - #[cfg(feature = "openssl")] fn test_decode_rsa_secret_key() { env_logger::try_init().unwrap_or(()); decode_secret_key(RSA_KEY, None).unwrap(); } #[test] - #[cfg(feature = "openssl")] fn test_fingerprint() { let key = parse_public_key_base64( "AAAAC3NzaC1lZDI1NTE5AAAAILagOJFgwaMNhBWQINinKOXmqS4Gh5NgxgriXwdOoINJ", @@ -622,7 +632,6 @@ QR+u0AypRPmzHnOPAAAAEXJvb3RAMTQwOTExNTQ5NDBkAQ== } #[test] - #[cfg(feature = "openssl")] fn test_srhb() { env_logger::try_init().unwrap_or(()); let key = "AAAAB3NzaC1yc2EAAAADAQABAAACAQC0Xtz3tSNgbUQAXem4d+d6hMx7S8Nwm/DOO2AWyWCru+n/+jQ7wz2b5+3oG2+7GbWZNGj8HCc6wJSA3jUsgv1N6PImIWclD14qvoqY3Dea1J0CJgXnnM1xKzBz9C6pDHGvdtySg+yzEO41Xt4u7HFn4Zx5SGuI2NBsF5mtMLZXSi33jCIWVIkrJVd7sZaY8jiqeVZBB/UvkLPWewGVuSXZHT84pNw4+S0Rh6P6zdNutK+JbeuO+5Bav4h9iw4t2sdRkEiWg/AdMoSKmo97Gigq2mKdW12ivnXxz3VfxrCgYJj9WwaUUWSfnAju5SiNly0cTEAN4dJ7yB0mfLKope1kRhPsNaOuUmMUqlu/hBDM/luOCzNjyVJ+0LLB7SV5vOiV7xkVd4KbEGKou8eeCR3yjFazUe/D1pjYPssPL8cJhTSuMc+/UC9zD8yeEZhB9V+vW4NMUR+lh5+XeOzenl65lWYd/nBZXLBbpUMf1AOfbz65xluwCxr2D2lj46iApSIpvE63i3LzFkbGl9GdUiuZJLMFJzOWdhGGc97cB5OVyf8umZLqMHjaImxHEHrnPh1MOVpv87HYJtSBEsN4/omINCMZrk++CRYAIRKRpPKFWV7NQHcvw3m7XLR3KaTYe+0/MINIZwGdou9fLUU3zSd521vDjA/weasH0CyDHq7sZw=="; @@ -631,7 +640,6 @@ QR+u0AypRPmzHnOPAAAAEXJvb3RAMTQwOTExNTQ5NDBkAQ== } #[test] - #[cfg(feature = "openssl")] fn test_nikao() { env_logger::try_init().unwrap_or(()); let key = "-----BEGIN RSA PRIVATE KEY----- @@ -665,7 +673,6 @@ QaChXiDsryJZwsRnruvMRX9nedtqHrgnIsJLTXjppIhGhq5Kg4RQfOU= decode_secret_key(key, None).unwrap(); } - #[cfg(feature = "openssl")] pub const PKCS8_RSA: &str = "-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAwBGetHjW+3bDQpVktdemnk7JXgu1NBWUM+ysifYLDBvJ9ttX GNZSyQKA4v/dNr0FhAJ8I9BuOTjYCy1YfKylhl5D/DiSSXFPsQzERMmGgAlYvU2U @@ -696,7 +703,6 @@ xV/JrzLAwPoKk3bkqys3bUmgo6DxVC/6RmMwPQ0rmpw78kOgEej90g== "; #[test] - #[cfg(feature = "openssl")] fn test_loewenheim() -> Result<(), Error> { env_logger::try_init().unwrap_or(()); let key = "-----BEGIN RSA PRIVATE KEY----- @@ -739,7 +745,6 @@ KJaj7gc0n6gmKY6r0/Ddufy1JZ6eihBCSJ64RARBXeg2rZpyT+xxhMEZLK5meOeR } #[test] - #[cfg(feature = "openssl")] fn test_o01eg() { env_logger::try_init().unwrap_or(()); @@ -777,14 +782,12 @@ br8gXU8KyiY9sZVbmplRPF+ar462zcI2kt0a18mr0vbrdqp2eMjb37QDbVBJ+rPE decode_secret_key(key, Some("12345")).unwrap(); } #[test] - #[cfg(feature = "openssl")] fn test_pkcs8() { env_logger::try_init().unwrap_or(()); println!("test"); decode_secret_key(PKCS8_RSA, Some("blabla")).unwrap(); } - #[cfg(feature = "openssl")] const PKCS8_ENCRYPTED: &str = "-----BEGIN ENCRYPTED PRIVATE KEY----- MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQITo1O0b8YrS0CAggA MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBtLH4T1KOfo1GGr7salhR8BIIE @@ -817,7 +820,6 @@ Cog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux -----END ENCRYPTED PRIVATE KEY-----"; #[test] - #[cfg(feature = "openssl")] fn test_gpg() { env_logger::try_init().unwrap_or(()); let algo = [115, 115, 104, 45, 114, 115, 97]; @@ -850,7 +852,6 @@ Cog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux } #[test] - #[cfg(feature = "openssl")] fn test_pkcs8_encrypted() { env_logger::try_init().unwrap_or(()); println!("test"); @@ -892,7 +893,6 @@ Cog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux let sig = &b[b.len() - 64..]; assert!(public.verify_detached(a, sig)); } - #[cfg(feature = "openssl")] _ => {} } @@ -925,7 +925,6 @@ Cog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux #[test] #[cfg(unix)] - #[cfg(feature = "openssl")] fn test_agent() { env_logger::try_init().unwrap_or(()); let dir = tempdir::TempDir::new("russh").unwrap(); diff --git a/russh/src/client/encrypted.rs b/russh/src/client/encrypted.rs index e91f09c8..e8b70e09 100644 --- a/russh/src/client/encrypted.rs +++ b/russh/src/client/encrypted.rs @@ -673,7 +673,8 @@ impl Session { Ok(key) => { let key2 = <&[u8]>::clone(&key); #[cfg(not(feature = "openssl"))] - let key = parse_public_key(key).map_err(crate::Error::from); + let key = + parse_public_key(key, None).map_err(crate::Error::from); #[cfg(feature = "openssl")] let key = parse_public_key(key, None).map_err(crate::Error::from); diff --git a/russh/src/client/mod.rs b/russh/src/client/mod.rs index edeb9f61..4c3b8e44 100644 --- a/russh/src/client/mod.rs +++ b/russh/src/client/mod.rs @@ -46,9 +46,7 @@ use futures::Future; use log::{debug, error, info, trace}; use russh_cryptovec::CryptoVec; use russh_keys::encoding::Reader; -#[cfg(feature = "openssl")] -use russh_keys::key::SignatureHash; -use russh_keys::key::{self, parse_public_key, PublicKey}; +use russh_keys::key::{self, parse_public_key, PublicKey, SignatureHash}; use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; use tokio::net::{TcpStream, ToSocketAddrs}; use tokio::pin; @@ -1120,7 +1118,6 @@ impl KexDhDone { let pubkey = reader.read_string().map_err(crate::Error::from)?; // server public key. let pubkey = parse_public_key( pubkey, - #[cfg(feature = "openssl")] SignatureHash::from_rsa_hostkey_algo(self.names.key.0.as_bytes()), ) .map_err(crate::Error::from)?; diff --git a/russh/src/key.rs b/russh/src/key.rs index 804c619e..f1c1f61c 100644 --- a/russh/src/key.rs +++ b/russh/src/key.rs @@ -33,12 +33,24 @@ impl PubKey for PublicKey { PublicKey::P256(_) | PublicKey::P521(_) => { buffer.extend_ssh_string(&self.public_key_bytes()); } - #[cfg(feature = "openssl")] PublicKey::RSA { ref key, .. } => { + let mut e = Vec::new(); + let mut n = Vec::new(); + + #[cfg(feature = "openssl")] #[allow(clippy::unwrap_used)] // type known - let rsa = key.0.rsa().unwrap(); - let e = rsa.e().to_vec(); - let n = rsa.n().to_vec(); + { + let rsa = key.0.rsa().unwrap(); + e = rsa.e().to_vec(); + n = rsa.n().to_vec(); + } + #[cfg(not(feature = "openssl"))] + { + use russh_keys::PublicKeyParts; + let rsa = key.clone(); + e = rsa.e().to_bytes_be(); + n = rsa.n().to_bytes_be(); + } buffer.push_u32_be((4 + SSH_RSA.0.len() + mpint_len(&n) + mpint_len(&e)) as u32); buffer.extend_ssh_string(SSH_RSA.0.as_bytes()); buffer.extend_ssh_mpint(&e); @@ -57,10 +69,21 @@ impl PubKey for KeyPair { buffer.extend_ssh_string(ED25519.0.as_bytes()); buffer.extend_ssh_string(public.as_slice()); } - #[cfg(feature = "openssl")] KeyPair::RSA { ref key, .. } => { - let e = key.e().to_vec(); - let n = key.n().to_vec(); + let mut e = Vec::new(); + let mut n = Vec::new(); + #[cfg(feature = "openssl")] + { + e = key.e().to_vec(); + n = key.n().to_vec(); + } + + #[cfg(not(feature = "openssl"))] + { + use russh_keys::PublicKeyParts; + e = key.e().to_bytes_be(); + n = key.n().to_bytes_be(); + } buffer.push_u32_be((4 + SSH_RSA.0.len() + mpint_len(&n) + mpint_len(&e)) as u32); buffer.extend_ssh_string(SSH_RSA.0.as_bytes()); buffer.extend_ssh_mpint(&e); diff --git a/russh/src/negotiation.rs b/russh/src/negotiation.rs index 8800dda0..6b09c1ed 100644 --- a/russh/src/negotiation.rs +++ b/russh/src/negotiation.rs @@ -126,9 +126,7 @@ impl Named for () { } } -#[cfg(feature = "openssl")] -use russh_keys::key::SSH_RSA; -use russh_keys::key::{ECDSA_SHA2_NISTP256, ECDSA_SHA2_NISTP521, ED25519}; +use russh_keys::key::{ECDSA_SHA2_NISTP256, ECDSA_SHA2_NISTP521, ED25519, SSH_RSA}; impl Named for PublicKey { fn name(&self) -> &'static str { @@ -136,7 +134,6 @@ impl Named for PublicKey { PublicKey::Ed25519(_) => ED25519.0, PublicKey::P256(_) => ECDSA_SHA2_NISTP256.0, PublicKey::P521(_) => ECDSA_SHA2_NISTP521.0, - #[cfg(feature = "openssl")] PublicKey::RSA { .. } => SSH_RSA.0, } } @@ -146,7 +143,6 @@ impl Named for KeyPair { fn name(&self) -> &'static str { match self { KeyPair::Ed25519 { .. } => ED25519.0, - #[cfg(feature = "openssl")] KeyPair::RSA { ref hash, .. } => hash.name().0, } } From 288ecaa764753ea29ab6a392242b5abf8af3fe72 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Thu, 15 Feb 2024 16:02:43 -0300 Subject: [PATCH 2/7] Second round --- russh-keys/Cargo.toml | 5 ++- russh-keys/src/agent/client.rs | 21 ++++++++++-- russh-keys/src/format/pkcs8.rs | 62 +++++++++++++++++++++++++++++----- russh-keys/src/key.rs | 17 ++++++---- russh-keys/src/lib.rs | 4 +++ russh/src/key.rs | 2 ++ 6 files changed, 91 insertions(+), 20 deletions(-) diff --git a/russh-keys/Cargo.toml b/russh-keys/Cargo.toml index e4064a3e..6c75648a 100644 --- a/russh-keys/Cargo.toml +++ b/russh-keys/Cargo.toml @@ -46,10 +46,10 @@ inout = { version = "0.1", features = ["std"] } log = "0.4" md5 = "0.7" num-bigint = "0.4" +num-bigint-dig = { version = "0.8.2", default-features = false, package = "num-bigint-dig" } num-integer = "0.1" -# You must select either openssl or rsa openssl = { version = "0.10", optional = true } -rsa = { version = "0.9.6", features = ["sha1", "sha2"], optional = true} +rsa = { version = "0.9.6", features = ["sha1", "sha2"]} p256 = "0.13" p521 = "0.13" pbkdf2 = "0.11" @@ -70,7 +70,6 @@ tokio-stream = { version = "0.1", features = ["net"] } yasna = { version = "0.5.0", features = ["bit-vec", "num-bigint"] } [features] -default = ["rsa"] vendored-openssl = ["openssl", "openssl/vendored"] [dev-dependencies] diff --git a/russh-keys/src/agent/client.rs b/russh-keys/src/agent/client.rs index b1b3e635..7d2d09e4 100644 --- a/russh-keys/src/agent/client.rs +++ b/russh-keys/src/agent/client.rs @@ -3,7 +3,7 @@ use std::convert::TryFrom; use byteorder::{BigEndian, ByteOrder}; use log::{debug, info}; #[cfg(not(feature = "openssl"))] -use rsa::traits::PublicKeyParts; +use rsa::traits::{PrivateKeyParts, PublicKeyParts}; use russh_cryptovec::CryptoVec; use tokio; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; @@ -142,7 +142,24 @@ impl AgentClient { } #[cfg(not(feature = "openssl"))] key::KeyPair::RSA { ref key, .. } => { - todo!(); + use num_bigint_dig::traits::ModInverse; + + self.buf.extend_ssh_string(b"ssh-rsa"); + self.buf.extend_ssh_mpint(&key.n().to_bytes_be()); + self.buf.extend_ssh_mpint(&key.e().to_bytes_be()); + self.buf.extend_ssh_mpint(&key.d().to_bytes_be()); + let primes = key.primes(); + if let Some(iqmp) = key.crt_coefficient() { + self.buf.extend_ssh_mpint(&iqmp.to_bytes_be()); + } else { + if let Some(iqmp) = (&primes[0]).mod_inverse(&primes[1]) { + let (_, val) = &iqmp.to_bytes_be(); + self.buf.extend_ssh_mpint(val); + } + } + self.buf.extend_ssh_mpint(&primes[0].to_bytes_be()); + self.buf.extend_ssh_mpint(&primes[1].to_bytes_be()); + self.buf.extend_ssh_string(b""); } } if !constraints.is_empty() { diff --git a/russh-keys/src/format/pkcs8.rs b/russh-keys/src/format/pkcs8.rs index 99ccec2c..a179a4f8 100644 --- a/russh-keys/src/format/pkcs8.rs +++ b/russh-keys/src/format/pkcs8.rs @@ -4,19 +4,19 @@ use std::convert::TryFrom; use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit}; use bit_vec::BitVec; use block_padding::{NoPadding, Pkcs7}; -#[cfg(feature = "openssl")] -use openssl::pkey::Private; -#[cfg(feature = "openssl")] -use openssl::rsa::Rsa; #[cfg(test)] use rand_core::OsRng; #[cfg(not(feature = "openssl"))] -use rsa::RsaPrivateKey; +use rsa::{ + traits::{PrivateKeyParts, PublicKeyParts}, + RsaPrivateKey, +}; use yasna::BERReaderSeq; +#[cfg(feature = "openssl")] +use {openssl::pkey::Private, openssl::rsa::Rsa}; use {std, yasna}; use super::Encryption; -#[cfg(feature = "openssl")] use crate::key::SignatureHash; use crate::{key, Error}; @@ -25,7 +25,6 @@ const PBKDF2: &[u64] = &[1, 2, 840, 113549, 1, 5, 12]; const HMAC_SHA256: &[u64] = &[1, 2, 840, 113549, 2, 9]; const AES256CBC: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 1, 42]; const ED25519: &[u64] = &[1, 3, 101, 112]; -#[cfg(feature = "openssl")] const RSA: &[u64] = &[1, 2, 840, 113549, 1, 1, 1]; /// Decode a PKCS#8-encoded private key. @@ -168,7 +167,53 @@ fn read_key_v1(reader: &mut BERReaderSeq) -> Result { #[cfg(not(feature = "openssl"))] fn write_key_v0(writer: &mut yasna::DERWriterSeq, key: &RsaPrivateKey) { - todo!() + // Construct DER sequence + writer.next().write_u32(0); // Version + writer.next().write_sequence(|writer| { + writer.next().write_oid(&ObjectIdentifier::from_slice(RSA)); + writer.next().write_null(); + }); + + // Encode key data + let bytes = yasna::construct_der(|writer| { + writer.write_sequence(|writer| { + writer.next().write_u32(0); // Version + + use num_bigint::BigUint; + writer + .next() + .write_biguint(&BigUint::from_bytes_be(&key.n().to_bytes_be())); + writer + .next() + .write_biguint(&BigUint::from_bytes_be(&key.e().to_bytes_be())); + writer + .next() + .write_biguint(&BigUint::from_bytes_be(&key.d().to_bytes_be())); + + let primes = key.primes(); + writer + .next() + .write_biguint(&BigUint::from_bytes_be(&primes[0].to_bytes_be())); + writer + .next() + .write_biguint(&BigUint::from_bytes_be(&primes[1].to_bytes_be())); + + if let (Some(dp), Some(dq), Some(iqmp)) = (key.dp(), key.dq(), key.crt_coefficient()) { + writer + .next() + .write_biguint(&BigUint::from_bytes_be(&dp.to_bytes_be())); + writer + .next() + .write_biguint(&BigUint::from_bytes_be(&dq.to_bytes_be())); + writer + .next() + .write_biguint(&BigUint::from_bytes_be(&iqmp.to_bytes_be())); + } + }) + }); + + // Write encoded bytes + writer.next().write_bytes(&bytes); } #[cfg(feature = "openssl")] @@ -271,7 +316,6 @@ fn test_read_write_pkcs8() { let key = decode_pkcs8(&ciphertext, Some(password)).unwrap(); match key { key::KeyPair::Ed25519 { .. } => println!("Ed25519"), - #[cfg(feature = "openssl")] key::KeyPair::RSA { .. } => println!("RSA"), } } diff --git a/russh-keys/src/key.rs b/russh-keys/src/key.rs index 98a2d594..2fb49a9c 100644 --- a/russh-keys/src/key.rs +++ b/russh-keys/src/key.rs @@ -28,7 +28,7 @@ use { #[cfg(not(feature = "openssl"))] use { rsa::{ - pss::SigningKey, + pss::{Pss, SigningKey}, sha2::Sha512, sha2::{Digest, Sha256}, signature::{RandomizedSigner, SignatureEncoding}, @@ -254,7 +254,7 @@ impl PublicKey { let n = BigUint::from_bytes_be(key_n); Ok(PublicKey::RSA { - key: RsaPublicKey::new(n, e).unwrap(), + key: RsaPublicKey::new(n, e)?, hash: SignatureHash::from_rsa_hostkey_algo(algo) .unwrap_or(SignatureHash::SHA1), }) @@ -321,7 +321,12 @@ impl PublicKey { } #[cfg(not(feature = "openssl"))] PublicKey::RSA { ref key, ref hash } => { - todo!() + let pss = match hash { + SignatureHash::SHA1 => Pss::new::(), + SignatureHash::SHA2_256 => Pss::new::(), + SignatureHash::SHA2_512 => Pss::new::(), + }; + key.verify(pss, buffer, sig).is_ok() } PublicKey::P256(ref public) => { const FIELD_LEN: usize = @@ -476,9 +481,9 @@ impl KeyPair { } #[cfg(not(feature = "openssl"))] KeyPair::RSA { ref key, ref hash } => { - let cloned_key = RsaPublicKey::new(key.n().clone(), key.e().clone()); + let cloned_key = RsaPublicKey::new(key.n().clone(), key.e().clone())?; PublicKey::RSA { - key: cloned_key.unwrap(), + key: cloned_key, hash: *hash, } } @@ -671,7 +676,7 @@ pub fn parse_public_key(p: &[u8], refer_hash: Option) -> Result

{ buffer.extend_ssh_string(&self.public_key_bytes()); } + #[allow(unused_assignments)] PublicKey::RSA { ref key, .. } => { let mut e = Vec::new(); let mut n = Vec::new(); @@ -69,6 +70,7 @@ impl PubKey for KeyPair { buffer.extend_ssh_string(ED25519.0.as_bytes()); buffer.extend_ssh_string(public.as_slice()); } + #[allow(unused_assignments)] KeyPair::RSA { ref key, .. } => { let mut e = Vec::new(); let mut n = Vec::new(); From c6136741f5f75a25df55c0df4f68a8e3c306e44e Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sat, 17 Feb 2024 11:30:23 -0300 Subject: [PATCH 3/7] Fix clippy --- russh-keys/src/agent/client.rs | 18 +++++++++++------- russh-keys/src/format/pkcs8.rs | 24 +++++++++++++----------- russh-keys/src/key.rs | 2 +- russh-keys/src/lib.rs | 2 +- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/russh-keys/src/agent/client.rs b/russh-keys/src/agent/client.rs index 7d2d09e4..3d3fe89c 100644 --- a/russh-keys/src/agent/client.rs +++ b/russh-keys/src/agent/client.rs @@ -151,14 +151,18 @@ impl AgentClient { let primes = key.primes(); if let Some(iqmp) = key.crt_coefficient() { self.buf.extend_ssh_mpint(&iqmp.to_bytes_be()); - } else { - if let Some(iqmp) = (&primes[0]).mod_inverse(&primes[1]) { - let (_, val) = &iqmp.to_bytes_be(); - self.buf.extend_ssh_mpint(val); - } + } else if let Some(iqmp) = &primes + .get(0) + .ok_or(Error::IndexOutOfBounds)? + .mod_inverse(primes.get(1).ok_or(Error::IndexOutOfBounds)?) + { + let (_, val) = &iqmp.to_bytes_be(); + self.buf.extend_ssh_mpint(val); } - self.buf.extend_ssh_mpint(&primes[0].to_bytes_be()); - self.buf.extend_ssh_mpint(&primes[1].to_bytes_be()); + self.buf + .extend_ssh_mpint(&primes.get(0).ok_or(Error::IndexOutOfBounds)?.to_bytes_be()); + self.buf + .extend_ssh_mpint(&primes.get(1).ok_or(Error::IndexOutOfBounds)?.to_bytes_be()); self.buf.extend_ssh_string(b""); } } diff --git a/russh-keys/src/format/pkcs8.rs b/russh-keys/src/format/pkcs8.rs index a179a4f8..ffde841a 100644 --- a/russh-keys/src/format/pkcs8.rs +++ b/russh-keys/src/format/pkcs8.rs @@ -17,7 +17,6 @@ use {openssl::pkey::Private, openssl::rsa::Rsa}; use {std, yasna}; use super::Encryption; -use crate::key::SignatureHash; use crate::{key, Error}; const PBES2: &[u64] = &[1, 2, 840, 113549, 1, 5, 13]; @@ -176,10 +175,10 @@ fn write_key_v0(writer: &mut yasna::DERWriterSeq, key: &RsaPrivateKey) { // Encode key data let bytes = yasna::construct_der(|writer| { - writer.write_sequence(|writer| { + let _ = writer.write_sequence(|writer| -> Result<(), Error> { writer.next().write_u32(0); // Version - use num_bigint::BigUint; + writer .next() .write_biguint(&BigUint::from_bytes_be(&key.n().to_bytes_be())); @@ -191,12 +190,13 @@ fn write_key_v0(writer: &mut yasna::DERWriterSeq, key: &RsaPrivateKey) { .write_biguint(&BigUint::from_bytes_be(&key.d().to_bytes_be())); let primes = key.primes(); - writer - .next() - .write_biguint(&BigUint::from_bytes_be(&primes[0].to_bytes_be())); - writer - .next() - .write_biguint(&BigUint::from_bytes_be(&primes[1].to_bytes_be())); + + writer.next().write_biguint(&BigUint::from_bytes_be( + &primes.get(0).ok_or(Error::IndexOutOfBounds)?.to_bytes_be(), + )); + writer.next().write_biguint(&BigUint::from_bytes_be( + &primes.get(1).ok_or(Error::IndexOutOfBounds)?.to_bytes_be(), + )); if let (Some(dp), Some(dq), Some(iqmp)) = (key.dp(), key.dq(), key.crt_coefficient()) { writer @@ -208,8 +208,9 @@ fn write_key_v0(writer: &mut yasna::DERWriterSeq, key: &RsaPrivateKey) { writer .next() .write_biguint(&BigUint::from_bytes_be(&iqmp.to_bytes_be())); - } - }) + }; + Ok(()) + }); }); // Write encoded bytes @@ -260,6 +261,7 @@ fn write_key_v0(writer: &mut yasna::DERWriterSeq, key: &Rsa) { #[cfg(feature = "openssl")] fn read_key_v0(reader: &mut BERReaderSeq) -> Result { + use crate::key::SignatureHash; let oid = reader.next().read_sequence(|reader| { let oid = reader.next().read_oid()?; reader.next().read_null()?; diff --git a/russh-keys/src/key.rs b/russh-keys/src/key.rs index 2fb49a9c..51b31770 100644 --- a/russh-keys/src/key.rs +++ b/russh-keys/src/key.rs @@ -622,7 +622,7 @@ fn rsa_signature( #[cfg(not(feature = "openssl"))] fn rsa_signature(hash: &SignatureHash, key: &RsaPrivateKey, b: &[u8]) -> Result, Error> { let private_key = key.clone(); - let mut rng = OsRng.clone(); + let mut rng = OsRng; let signing_key = match hash { SignatureHash::SHA2_256 => { diff --git a/russh-keys/src/lib.rs b/russh-keys/src/lib.rs index 77b06991..c7431e4c 100644 --- a/russh-keys/src/lib.rs +++ b/russh-keys/src/lib.rs @@ -487,7 +487,7 @@ mod test { use std::fs::File; use std::io::Write; - #[cfg(all(unix, feature = "openssl"))] + #[cfg(unix)] use futures::Future; use super::*; From 27a45a3af7dcc874b179082dc13d50e7b7613a63 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sat, 24 Feb 2024 16:03:16 -0300 Subject: [PATCH 4/7] Fix some tests. --- russh-keys/Cargo.toml | 4 +- russh-keys/src/agent/client.rs | 14 +++++++ russh-keys/src/agent/server.rs | 35 +++++++++++++++- russh-keys/src/format/mod.rs | 72 ++++++++++++-------------------- russh-keys/src/format/openssh.rs | 23 +++++++++- russh-keys/src/format/pkcs5.rs | 1 - russh-keys/src/format/pkcs8.rs | 48 ++++++++++++--------- russh-keys/src/key.rs | 2 +- russh/src/negotiation.rs | 2 - 9 files changed, 128 insertions(+), 73 deletions(-) diff --git a/russh-keys/Cargo.toml b/russh-keys/Cargo.toml index 6427d5d6..c0b6c00e 100644 --- a/russh-keys/Cargo.toml +++ b/russh-keys/Cargo.toml @@ -60,11 +60,13 @@ serde = { version = "1.0", features = ["derive"] } sha1 = "0.10" sha2 = "0.10" thiserror = "1.0" -tokio = { version = "1.17.0", features = [ +tokio = { version = "1.36.0", features = [ "io-util", "rt-multi-thread", "time", "net", + "macros", + "process" ] } tokio-stream = { version = "0.1", features = ["net"] } yasna = { version = "0.5.0", features = ["bit-vec", "num-bigint"] } diff --git a/russh-keys/src/agent/client.rs b/russh-keys/src/agent/client.rs index 3d3fe89c..67fd3bd9 100644 --- a/russh-keys/src/agent/client.rs +++ b/russh-keys/src/agent/client.rs @@ -299,6 +299,20 @@ impl AgentClient { hash: SignatureHash::SHA2_512, }) } + #[cfg(not(feature = "openssl"))] + b"ssh-rsa" => { + let e = r.read_mpint()?; + let n = r.read_mpint()?; + use rsa::{RsaPublicKey, BigUint}; + + let e = BigUint::from_bytes_be(&e); + let n = BigUint::from_bytes_be(&n); + + keys.push(PublicKey::RSA { + key: RsaPublicKey::new(n, e)?, + hash: SignatureHash::SHA2_512, + }) + } b"ssh-ed25519" => keys.push(PublicKey::Ed25519( ed25519_dalek::VerifyingKey::try_from(r.read_string()?)?, )), diff --git a/russh-keys/src/agent/server.rs b/russh-keys/src/agent/server.rs index b8078539..a85d9635 100644 --- a/russh-keys/src/agent/server.rs +++ b/russh-keys/src/agent/server.rs @@ -15,8 +15,9 @@ use {std, tokio}; use super::{msg, Constraint}; use crate::encoding::{Encoding, Position, Reader}; -#[cfg(feature = "openssl")] use crate::key::SignatureHash; +#[cfg(not(feature = "openssl"))] +use rsa::{RsaPrivateKey, BigUint}; use crate::{key, Error}; #[derive(Clone)] @@ -321,6 +322,38 @@ impl { + let n = r.read_string()?; + let e = r.read_string()?; + + let n_buint = BigUint::from_bytes_be(n); + let e_buint = BigUint::from_bytes_be(e); + let d = BigUint::from_bytes_be(r.read_string()?); + let _ = r.read_string()?; + let p = BigUint::from_bytes_be(r.read_string()?); + let q = BigUint::from_bytes_be(r.read_string()?); + + let key = RsaPrivateKey::from_components(n_buint, e_buint, d, vec![p, q])?; + + let len0 = writebuf.len(); + writebuf.extend_ssh_string(b"ssh-rsa"); + writebuf.extend_ssh_mpint(e); + writebuf.extend_ssh_mpint(n); + + #[allow(clippy::indexing_slicing)] // length is known + let blob = writebuf[len0..].to_vec(); + writebuf.resize(len0); + writebuf.push(msg::SUCCESS); + + ( + blob, + key::KeyPair::RSA { + key: key, + hash: SignatureHash::SHA2_256, // Assuming SHA2_256 is compatible with your needs + }, + ) + } _ => return Ok(false), }; let mut w = self.keys.0.write().or(Err(Error::AgentFailure))?; diff --git a/russh-keys/src/format/mod.rs b/russh-keys/src/format/mod.rs index 1463a85d..c7e00869 100644 --- a/russh-keys/src/format/mod.rs +++ b/russh-keys/src/format/mod.rs @@ -1,21 +1,17 @@ use std::io::Write; -#[cfg(not(feature = "openssl"))] -use data_encoding::BASE64_MIME; -#[cfg(feature = "openssl")] use data_encoding::{BASE64_MIME, HEXLOWER_PERMISSIVE}; +#[cfg(not(feature = "openssl"))] +use rsa::{self, RsaPrivateKey, pkcs1::DecodeRsaPrivateKey}; #[cfg(feature = "openssl")] use openssl::rsa::Rsa; use super::is_base64_char; use crate::{key, Error}; +pub mod pkcs5; pub mod openssh; pub use self::openssh::*; - -#[cfg(feature = "openssl")] -pub mod pkcs5; -#[cfg(feature = "openssl")] pub use self::pkcs5::*; pub mod pkcs8; @@ -33,20 +29,29 @@ pub enum Encryption { #[derive(Clone, Debug)] enum Format { - #[cfg(feature = "openssl")] Rsa, Openssh, - #[cfg(feature = "openssl")] Pkcs5Encrypted(Encryption), Pkcs8Encrypted, Pkcs8, } +/// Parse the header line to determine the format of the secret key. +fn parse_header(line: &str) -> Option { + match line { + "-----BEGIN OPENSSH PRIVATE KEY-----" => Some(Format::Openssh), + "-----BEGIN RSA PRIVATE KEY-----" => Some(Format::Rsa), + "-----BEGIN ENCRYPTED PRIVATE KEY-----" => Some(Format::Pkcs8Encrypted), + "-----BEGIN PRIVATE KEY-----" => Some(Format::Pkcs8), + _ => None, + } +} + /// Decode a secret key, possibly deciphering it with the supplied /// password. pub fn decode_secret_key(secret: &str, password: Option<&str>) -> Result { let mut format = None; - let secret = { + let secret: String = { let mut started = false; let mut sec = String::new(); for l in secret.lines() { @@ -57,41 +62,18 @@ pub fn decode_secret_key(secret: &str, password: Option<&str>) -> Result = HEXLOWER_PERMISSIVE - .decode(l.split_at(AES_128_CBC.len()).1.as_bytes())?; - if iv_.len() != 16 { - return Err(Error::CouldNotReadKey); - } - let mut iv = [0; 16]; - iv.clone_from_slice(&iv_); - format = Some(Format::Pkcs5Encrypted(Encryption::Aes128Cbc(iv))) + let iv_: Vec = HEXLOWER_PERMISSIVE + .decode(l.split_at(AES_128_CBC.len()).1.as_bytes())?; + if iv_.len() != 16 { + return Err(Error::CouldNotReadKey); } + let mut iv = [0; 16]; + iv.clone_from_slice(&iv_); + format = Some(Format::Pkcs5Encrypted(Encryption::Aes128Cbc(iv))) } - } - if l == "-----BEGIN OPENSSH PRIVATE KEY-----" { - started = true; - format = Some(Format::Openssh); - } else if l == "-----BEGIN RSA PRIVATE KEY-----" { - #[cfg(not(feature = "openssl"))] - { - return Err(Error::UnsupportedKeyType { - key_type_string: "rsa".to_owned(), - key_type_raw: "rsa".as_bytes().to_vec(), - }); - } - #[cfg(feature = "openssl")] - { - started = true; - format = Some(Format::Rsa); - } - } else if l == "-----BEGIN ENCRYPTED PRIVATE KEY-----" { + } else { + format = parse_header(l); started = true; - format = Some(Format::Pkcs8Encrypted); - } else if l == "-----BEGIN PRIVATE KEY-----" { - started = true; - format = Some(Format::Pkcs8); } } sec @@ -100,9 +82,7 @@ pub fn decode_secret_key(secret: &str, password: Option<&str>) -> Result decode_openssh(&secret, password), - #[cfg(feature = "openssl")] Some(Format::Rsa) => decode_rsa(&secret), - #[cfg(feature = "openssl")] Some(Format::Pkcs5Encrypted(enc)) => decode_pkcs5(&secret, password, enc), Some(Format::Pkcs8Encrypted) | Some(Format::Pkcs8) => { self::pkcs8::decode_pkcs8(&secret, password.map(|x| x.as_bytes())) @@ -132,10 +112,12 @@ pub fn encode_pkcs8_pem_encrypted( Ok(()) } -#[cfg(feature = "openssl")] fn decode_rsa(secret: &[u8]) -> Result { Ok(key::KeyPair::RSA { + #[cfg(feature = "openssl")] key: Rsa::private_key_from_der(secret)?, + #[cfg(not(feature = "openssl"))] + key: RsaPrivateKey::from_pkcs1_der(secret).map_err(rsa::Error::from)?, hash: key::SignatureHash::SHA2_256, }) } diff --git a/russh-keys/src/format/openssh.rs b/russh-keys/src/format/openssh.rs index 44821fb8..dc7bbe61 100644 --- a/russh-keys/src/format/openssh.rs +++ b/russh-keys/src/format/openssh.rs @@ -6,7 +6,11 @@ use bcrypt_pbkdf; use ctr::Ctr64BE; #[cfg(feature = "openssl")] use openssl::bn::BigNum; - +#[cfg(not(feature = "openssl"))] +use { + rsa::BigUint, + rsa::RsaPrivateKey +}; use crate::encoding::Reader; use crate::{key, Error, KEYTYPE_ED25519, KEYTYPE_RSA}; @@ -48,7 +52,7 @@ pub fn decode_openssh(secret: &[u8], password: Option<&str>) -> Result) -> Result, diff --git a/russh-keys/src/format/pkcs8.rs b/russh-keys/src/format/pkcs8.rs index ffde841a..0fe3a298 100644 --- a/russh-keys/src/format/pkcs8.rs +++ b/russh-keys/src/format/pkcs8.rs @@ -10,6 +10,7 @@ use rand_core::OsRng; use rsa::{ traits::{PrivateKeyParts, PublicKeyParts}, RsaPrivateKey, + BigUint }; use yasna::BERReaderSeq; #[cfg(feature = "openssl")] @@ -260,6 +261,31 @@ fn write_key_v0(writer: &mut yasna::DERWriterSeq, key: &Rsa) { } #[cfg(feature = "openssl")] +fn read_key(reader: &mut BERReaderSeq) -> Result, Error>{ + use openssl::bn::BigNum; + Ok(Rsa::from_private_components( + BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?, + BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?, + BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?, + BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?, + BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?, + BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?, + BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?, + BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?, + )?) +} + +#[cfg(not(feature = "openssl"))] +fn read_key(reader: &mut BERReaderSeq) -> Result { + let n = BigUint::from_bytes_be(&reader.next().read_biguint()?.to_bytes_be()); + let e = BigUint::from_bytes_be(&reader.next().read_biguint()?.to_bytes_be()); + let d = BigUint::from_bytes_be(&reader.next().read_biguint()?.to_bytes_be()); + let _ = &reader.next().read_biguint()?.to_bytes_be(); + let p = BigUint::from_bytes_be(&reader.next().read_biguint()?.to_bytes_be()); + let q = BigUint::from_bytes_be(&reader.next().read_biguint()?.to_bytes_be()); + Ok(RsaPrivateKey::from_components(n, e, d, vec![p, q])?) +} + fn read_key_v0(reader: &mut BERReaderSeq) -> Result { use crate::key::SignatureHash; let oid = reader.next().read_sequence(|reader| { @@ -269,26 +295,13 @@ fn read_key_v0(reader: &mut BERReaderSeq) -> Result { })?; if oid.components().as_slice() == RSA { let seq = &reader.next().read_bytes()?; - let rsa: Result, Error> = yasna::parse_der(seq, |reader| { + let rsa = yasna::parse_der(seq, |reader| { reader.read_sequence(|reader| { let version = reader.next().read_u32()?; if version != 0 { return Ok(Err(Error::CouldNotReadKey)); } - use openssl::bn::BigNum; - let mut read_key = || -> Result, Error> { - Ok(Rsa::from_private_components( - BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?, - BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?, - BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?, - BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?, - BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?, - BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?, - BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?, - BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?, - )?) - }; - Ok(read_key()) + Ok(read_key(reader)) }) })?; Ok(key::KeyPair::RSA { @@ -300,11 +313,6 @@ fn read_key_v0(reader: &mut BERReaderSeq) -> Result { } } -#[cfg(not(feature = "openssl"))] -fn read_key_v0(_: &mut BERReaderSeq) -> Result { - Err(Error::CouldNotReadKey) -} - #[test] fn test_read_write_pkcs8() { let secret = ed25519_dalek::SigningKey::generate(&mut OsRng {}); diff --git a/russh-keys/src/key.rs b/russh-keys/src/key.rs index 51b31770..124407e6 100644 --- a/russh-keys/src/key.rs +++ b/russh-keys/src/key.rs @@ -204,7 +204,7 @@ impl PublicKey { .map(PublicKey::Ed25519) .map_err(Error::from) } - b"ssh-rsa" | b"rsa-sha2-256" | b"rsa-sha2-512" if cfg!(feature = "openssl") => { + b"ssh-rsa" | b"rsa-sha2-256" | b"rsa-sha2-512" => { #[cfg(feature = "openssl")] { use log::debug; diff --git a/russh/src/negotiation.rs b/russh/src/negotiation.rs index 5ec21c44..19e239b2 100644 --- a/russh/src/negotiation.rs +++ b/russh/src/negotiation.rs @@ -90,9 +90,7 @@ impl Preferred { key::ED25519, key::ECDSA_SHA2_NISTP256, key::ECDSA_SHA2_NISTP521, - #[cfg(feature = "openssl")] key::RSA_SHA2_256, - #[cfg(feature = "openssl")] key::RSA_SHA2_512, ], cipher: CIPHER_ORDER, From 7a93d389d484db688855955b8947f917ba2df732 Mon Sep 17 00:00:00 2001 From: Eugene Date: Mon, 22 Apr 2024 10:13:32 -0700 Subject: [PATCH 5/7] ensure dp/dq/iqmp are precomputed --- russh-keys/src/format/pkcs8.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/russh-keys/src/format/pkcs8.rs b/russh-keys/src/format/pkcs8.rs index 0fe3a298..cba7e5d9 100644 --- a/russh-keys/src/format/pkcs8.rs +++ b/russh-keys/src/format/pkcs8.rs @@ -199,6 +199,9 @@ fn write_key_v0(writer: &mut yasna::DERWriterSeq, key: &RsaPrivateKey) { &primes.get(1).ok_or(Error::IndexOutOfBounds)?.to_bytes_be(), )); + let mut key = key.clone(); + key.precompute()?; // Compute dp/dq/crt_coefficient values + if let (Some(dp), Some(dq), Some(iqmp)) = (key.dp(), key.dq(), key.crt_coefficient()) { writer .next() From a994845943e09983390189c9aa613256cdfd01bc Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Thu, 25 Apr 2024 20:09:51 -0300 Subject: [PATCH 6/7] Fix read_key --- russh-keys/src/format/pkcs8.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/russh-keys/src/format/pkcs8.rs b/russh-keys/src/format/pkcs8.rs index 553df9f0..242d6b9c 100644 --- a/russh-keys/src/format/pkcs8.rs +++ b/russh-keys/src/format/pkcs8.rs @@ -284,14 +284,17 @@ fn read_key(reader: &mut BERReaderSeq) -> Result, Error>{ )?) } +#[allow(unused_must_use)] #[cfg(not(feature = "openssl"))] fn read_key(reader: &mut BERReaderSeq) -> Result { let n = BigUint::from_bytes_be(&reader.next().read_biguint()?.to_bytes_be()); let e = BigUint::from_bytes_be(&reader.next().read_biguint()?.to_bytes_be()); let d = BigUint::from_bytes_be(&reader.next().read_biguint()?.to_bytes_be()); - let _ = &reader.next().read_biguint()?.to_bytes_be(); let p = BigUint::from_bytes_be(&reader.next().read_biguint()?.to_bytes_be()); let q = BigUint::from_bytes_be(&reader.next().read_biguint()?.to_bytes_be()); + &reader.next().read_biguint()?.to_bytes_be(); + &reader.next().read_biguint()?.to_bytes_be(); + &reader.next().read_biguint()?.to_bytes_be(); Ok(RsaPrivateKey::from_components(n, e, d, vec![p, q])?) } From 2274deafb42e21b7dbd4883d1291ddc542570118 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Fri, 26 Apr 2024 09:41:01 -0300 Subject: [PATCH 7/7] Use pkcs1v15 instead of pss --- russh-keys/src/key.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/russh-keys/src/key.rs b/russh-keys/src/key.rs index f2b3c610..ff83b7e9 100644 --- a/russh-keys/src/key.rs +++ b/russh-keys/src/key.rs @@ -26,10 +26,10 @@ use { #[cfg(not(feature = "openssl"))] use { rsa::{ - pss::{Pss, SigningKey}, + pkcs1v15::{Pkcs1v15Sign, SigningKey}, sha2::Sha512, sha2::{Digest, Sha256}, - signature::{RandomizedSigner, SignatureEncoding}, + signature::SignatureEncoding, traits::PublicKeyParts, BigUint, RsaPrivateKey, RsaPublicKey, }, @@ -313,9 +313,9 @@ impl PublicKey { #[cfg(not(feature = "openssl"))] PublicKey::RSA { ref key, ref hash } => { let pss = match hash { - SignatureHash::SHA1 => Pss::new::(), - SignatureHash::SHA2_256 => Pss::new::(), - SignatureHash::SHA2_512 => Pss::new::(), + SignatureHash::SHA1 => Pkcs1v15Sign::new::(), + SignatureHash::SHA2_256 => Pkcs1v15Sign::new::(), + SignatureHash::SHA2_512 => Pkcs1v15Sign::new::(), }; key.verify(pss, buffer, sig).is_ok() } @@ -591,16 +591,15 @@ fn rsa_signature( #[cfg(not(feature = "openssl"))] fn rsa_signature(hash: &SignatureHash, key: &RsaPrivateKey, b: &[u8]) -> Result, Error> { let private_key = key.clone(); - let mut rng = OsRng; let signing_key = match hash { SignatureHash::SHA2_256 => { - SigningKey::::new(private_key).sign_with_rng(&mut rng, b) + SigningKey::::new(private_key).sign(b) } SignatureHash::SHA2_512 => { - SigningKey::::new(private_key).sign_with_rng(&mut rng, b) + SigningKey::::new(private_key).sign(b) } - SignatureHash::SHA1 => SigningKey::::new(private_key).sign_with_rng(&mut rng, b), + SignatureHash::SHA1 => SigningKey::::new(private_key).sign(b), }; Ok(signing_key.to_vec())