From c850dbd4f342e472bf653b06afbed13ac8e4eaba Mon Sep 17 00:00:00 2001 From: Robert Wang Date: Sun, 28 Apr 2024 03:39:04 -0700 Subject: [PATCH] Add pure-rust RSA implementation (#273) * SSH encoding of RSA keys is moved into `protocol` module. * Decouple the SSH encoding into traits. * When `openssl` feature is not enabled, the pure-rust RSA impl is used. Alternative implementation for https://github.com/warp-tech/russh/issues/225 --- russh-keys/Cargo.toml | 7 +- russh-keys/src/agent/client.rs | 43 ++---- russh-keys/src/agent/server.rs | 60 ++------- russh-keys/src/backend_openssl.rs | 210 +++++++++++++++++++++++++++++ russh-keys/src/backend_rust.rs | 185 +++++++++++++++++++++++++ russh-keys/src/encoding.rs | 51 +++++++ russh-keys/src/format/mod.rs | 45 ++----- russh-keys/src/format/openssh.rs | 39 +----- russh-keys/src/format/pkcs5.rs | 1 - russh-keys/src/format/pkcs8.rs | 92 +++++++------ russh-keys/src/key.rs | 215 +++++++++--------------------- russh-keys/src/lib.rs | 66 ++++----- russh-keys/src/protocol.rs | 74 ++++++++++ russh/src/client/encrypted.rs | 3 - russh/src/client/mod.rs | 2 - russh/src/key.rs | 25 ++-- russh/src/negotiation.rs | 5 - 17 files changed, 712 insertions(+), 411 deletions(-) create mode 100644 russh-keys/src/backend_openssl.rs create mode 100644 russh-keys/src/backend_rust.rs create mode 100644 russh-keys/src/protocol.rs diff --git a/russh-keys/Cargo.toml b/russh-keys/Cargo.toml index da7f15c7..0e79a03e 100644 --- a/russh-keys/Cargo.toml +++ b/russh-keys/Cargo.toml @@ -39,6 +39,7 @@ ctr = "0.9" block-padding = { version = "0.3", features = ["std"] } byteorder = "1.4" data-encoding = "2.3" +digest = "0.10" dirs = "5.0" ecdsa = "0.16" ed25519-dalek = { version= "2.0", features = ["rand_core"] } @@ -55,12 +56,14 @@ p256 = "0.13" p384 = "0.13" p521 = "0.13" pbkdf2 = "0.11" +pkcs1 = "0.7" rand = "0.8" rand_core = { version = "0.6.4", features = ["std"] } +rsa = "0.9" russh-cryptovec = { version = "0.7.0", path = "../cryptovec" } serde = { version = "1.0", features = ["derive"] } -sha1 = "0.10" -sha2 = "0.10" +sha1 = { version = "0.10", features = ["oid"] } +sha2 = { version = "0.10", features = ["oid"] } thiserror = "1.0" tokio = { version = "1.17.0", features = ["io-util", "rt-multi-thread", "time", "net"] } tokio-stream = { version = "0.1", features = ["net"] } diff --git a/russh-keys/src/agent/client.rs b/russh-keys/src/agent/client.rs index 560c3f9b..f247e4b3 100644 --- a/russh-keys/src/agent/client.rs +++ b/russh-keys/src/agent/client.rs @@ -9,6 +9,7 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use super::{msg, Constraint}; use crate::encoding::{Encoding, Reader}; use crate::key::{PublicKey, SignatureHash}; +use crate::protocol; use crate::{key, Error, PublicKeyBase64}; /// SSH agent client. @@ -121,24 +122,11 @@ impl AgentClient { self.buf.extend(pair.verifying_key().as_bytes()); self.buf.extend_ssh_string(b""); } - #[cfg(feature = "openssl")] #[allow(clippy::unwrap_used)] // key is known to be private key::KeyPair::RSA { ref key, .. } => { self.buf.extend_ssh_string(b"ssh-rsa"); - self.buf.extend_ssh_mpint(&key.n().to_vec()); - self.buf.extend_ssh_mpint(&key.e().to_vec()); - self.buf.extend_ssh_mpint(&key.d().to_vec()); - if let Some(iqmp) = key.iqmp() { - self.buf.extend_ssh_mpint(&iqmp.to_vec()); - } else { - let mut ctx = openssl::bn::BigNumContext::new()?; - let mut iqmp = openssl::bn::BigNum::new()?; - iqmp.mod_inverse(key.p().unwrap(), key.q().unwrap(), &mut ctx)?; - self.buf.extend_ssh_mpint(&iqmp.to_vec()); - } - self.buf.extend_ssh_mpint(&key.p().unwrap().to_vec()); - self.buf.extend_ssh_mpint(&key.q().unwrap().to_vec()); - self.buf.extend_ssh_string(b""); + self.buf + .extend_ssh(&protocol::RsaPrivateKey::try_from(key)?); } key::KeyPair::EC { ref key } => { self.buf.extend_ssh_string(key.algorithm().as_bytes()); @@ -267,21 +255,10 @@ impl AgentClient { let t = r.read_string()?; debug!("t = {:?}", std::str::from_utf8(t)); match t { - #[cfg(feature = "openssl")] - b"ssh-rsa" => { - let e = r.read_mpint()?; - let n = r.read_mpint()?; - use openssl::bn::BigNum; - use openssl::pkey::PKey; - use openssl::rsa::Rsa; - keys.push(PublicKey::RSA { - key: key::OpenSSLPKey(PKey::from_rsa(Rsa::from_public_components( - BigNum::from_slice(n)?, - BigNum::from_slice(e)?, - )?)?), - hash: SignatureHash::SHA2_512, - }) - } + b"ssh-rsa" => keys.push(key::PublicKey::new_rsa_with_hash( + &r.read_ssh()?, + SignatureHash::SHA2_512, + )?), b"ssh-ed25519" => keys.push(PublicKey::Ed25519( ed25519_dalek::VerifyingKey::try_from(r.read_string()?)?, )), @@ -351,7 +328,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, @@ -534,14 +510,11 @@ 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()); + buf.extend_ssh(&protocol::RsaPublicKey::from(key)); 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/agent/server.rs b/russh-keys/src/agent/server.rs index b8078539..b9eba687 100644 --- a/russh-keys/src/agent/server.rs +++ b/russh-keys/src/agent/server.rs @@ -15,9 +15,8 @@ use {std, tokio}; use super::{msg, Constraint}; use crate::encoding::{Encoding, Position, Reader}; -#[cfg(feature = "openssl")] use crate::key::SignatureHash; -use crate::{key, Error}; +use crate::{key, protocol, Error}; #[derive(Clone)] #[allow(clippy::type_complexity)] @@ -271,55 +270,22 @@ impl { - use openssl::bn::{BigNum, BigNumContext}; - use openssl::rsa::Rsa; - let n = r.read_mpint()?; - let e = r.read_mpint()?; - let d = BigNum::from_slice(r.read_mpint()?)?; - let q_inv = r.read_mpint()?; - let p = BigNum::from_slice(r.read_mpint()?)?; - let q = BigNum::from_slice(r.read_mpint()?)?; - let (dp, dq) = { - let one = BigNum::from_u32(1)?; - let p1 = p.as_ref() - one.as_ref(); - let q1 = q.as_ref() - one.as_ref(); - let mut context = BigNumContext::new()?; - let mut dp = BigNum::new()?; - let mut dq = BigNum::new()?; - dp.checked_rem(&d, &p1, &mut context)?; - dq.checked_rem(&d, &q1, &mut context)?; - (dp, dq) - }; - let _comment = r.read_string()?; - let key = Rsa::from_private_components( - BigNum::from_slice(n)?, - BigNum::from_slice(e)?, - d, - p, - q, - dp, - dq, - BigNum::from_slice(q_inv)?, - )?; + let key = + key::KeyPair::new_rsa_with_hash(&r.read_ssh()?, None, SignatureHash::SHA2_256)?; - let len0 = writebuf.len(); - writebuf.extend_ssh_string(b"ssh-rsa"); - writebuf.extend_ssh_mpint(e); - writebuf.extend_ssh_mpint(n); + let mut blob = Vec::new(); + if let key::KeyPair::RSA { ref key, .. } = key { + let public = protocol::RsaPublicKey::from(key); + blob.extend_ssh_string(b"ssh-rsa"); + blob.extend_ssh(&public); + } else { + return Err(Error::KeyIsCorrupt); + }; - #[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, - hash: SignatureHash::SHA2_256, - }, - ) + + (blob, key) } _ => return Ok(false), }; diff --git a/russh-keys/src/backend_openssl.rs b/russh-keys/src/backend_openssl.rs new file mode 100644 index 00000000..66fb657a --- /dev/null +++ b/russh-keys/src/backend_openssl.rs @@ -0,0 +1,210 @@ +use std::convert::TryFrom; + +use crate::key::{RsaCrtExtra, SignatureHash}; +use crate::{protocol, Error}; +use openssl::{ + bn::{BigNum, BigNumContext, BigNumRef}, + hash::MessageDigest, + pkey::{PKey, Private, Public}, + rsa::Rsa, +}; + +#[derive(Clone)] +pub struct RsaPublic { + key: Rsa, + pkey: PKey, +} + +impl RsaPublic { + pub fn verify_detached(&self, hash: &SignatureHash, msg: &[u8], sig: &[u8]) -> bool { + openssl::sign::Verifier::new(message_digest_for(hash), &self.pkey) + .and_then(|mut v| v.verify_oneshot(sig, msg)) + .unwrap_or(false) + } +} + +impl TryFrom<&protocol::RsaPublicKey<'_>> for RsaPublic { + type Error = Error; + + fn try_from(pk: &protocol::RsaPublicKey<'_>) -> Result { + let key = Rsa::from_public_components( + BigNum::from_slice(&pk.modulus)?, + BigNum::from_slice(&pk.public_exponent)?, + )?; + Ok(Self { + pkey: PKey::from_rsa(key.clone())?, + key, + }) + } +} + +impl<'a> From<&RsaPublic> for protocol::RsaPublicKey<'a> { + fn from(key: &RsaPublic) -> Self { + Self { + modulus: key.key.n().to_vec().into(), + public_exponent: key.key.e().to_vec().into(), + } + } +} + +impl PartialEq for RsaPublic { + fn eq(&self, b: &RsaPublic) -> bool { + self.pkey.public_eq(&b.pkey) + } +} + +impl Eq for RsaPublic {} + +impl std::fmt::Debug for RsaPublic { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "RsaPublic {{ (hidden) }}") + } +} + +#[derive(Clone)] +pub struct RsaPrivate { + key: Rsa, + pkey: PKey, +} + +impl RsaPrivate { + pub fn new( + sk: &protocol::RsaPrivateKey<'_>, + extra: Option<&RsaCrtExtra<'_>>, + ) -> Result { + let (d, p, q) = ( + BigNum::from_slice(&sk.private_exponent)?, + BigNum::from_slice(&sk.prime1)?, + BigNum::from_slice(&sk.prime2)?, + ); + let (dp, dq) = if let Some(extra) = extra { + ( + BigNum::from_slice(&extra.dp)?, + BigNum::from_slice(&extra.dq)?, + ) + } else { + calc_dp_dq(d.as_ref(), p.as_ref(), q.as_ref())? + }; + let key = Rsa::from_private_components( + BigNum::from_slice(&sk.public_key.modulus)?, + BigNum::from_slice(&sk.public_key.public_exponent)?, + d, + p, + q, + dp, + dq, + BigNum::from_slice(&sk.coefficient)?, + )?; + key.check_key()?; + Ok(Self { + pkey: PKey::from_rsa(key.clone())?, + key, + }) + } + + pub fn new_from_der(der: &[u8]) -> Result { + let key = Rsa::private_key_from_der(der)?; + key.check_key()?; + Ok(Self { + pkey: PKey::from_rsa(key.clone())?, + key, + }) + } + + pub fn generate(bits: usize) -> Result { + let key = Rsa::generate(bits as u32)?; + Ok(Self { + pkey: PKey::from_rsa(key.clone())?, + key, + }) + } + + pub fn sign(&self, hash: &SignatureHash, msg: &[u8]) -> Result, Error> { + Ok( + openssl::sign::Signer::new(message_digest_for(hash), &self.pkey)? + .sign_oneshot_to_vec(msg)?, + ) + } +} + +impl<'a> TryFrom<&RsaPrivate> for protocol::RsaPrivateKey<'a> { + type Error = Error; + + fn try_from(key: &RsaPrivate) -> Result, Self::Error> { + let key = &key.key; + // We always set these. + if let (Some(p), Some(q), Some(iqmp)) = (key.p(), key.q(), key.iqmp()) { + Ok(protocol::RsaPrivateKey { + public_key: protocol::RsaPublicKey { + modulus: key.n().to_vec().into(), + public_exponent: key.e().to_vec().into(), + }, + private_exponent: key.d().to_vec().into(), + prime1: p.to_vec().into(), + prime2: q.to_vec().into(), + coefficient: iqmp.to_vec().into(), + comment: b"".as_slice().into(), + }) + } else { + Err(Error::KeyIsCorrupt) + } + } +} + +impl<'a> TryFrom<&RsaPrivate> for RsaCrtExtra<'a> { + type Error = Error; + + fn try_from(key: &RsaPrivate) -> Result, Self::Error> { + let key = &key.key; + // We always set these. + if let (Some(dp), Some(dq)) = (key.dmp1(), key.dmq1()) { + Ok(RsaCrtExtra { + dp: dp.to_vec().into(), + dq: dq.to_vec().into(), + }) + } else { + Err(Error::KeyIsCorrupt) + } + } +} + +impl<'a> From<&RsaPrivate> for protocol::RsaPublicKey<'a> { + fn from(key: &RsaPrivate) -> Self { + Self { + modulus: key.key.n().to_vec().into(), + public_exponent: key.key.e().to_vec().into(), + } + } +} + +impl TryFrom<&RsaPrivate> for RsaPublic { + type Error = Error; + + fn try_from(key: &RsaPrivate) -> Result { + let key = Rsa::from_public_components(key.key.n().to_owned()?, key.key.e().to_owned()?)?; + Ok(Self { + pkey: PKey::from_rsa(key.clone())?, + key, + }) + } +} + +fn message_digest_for(hash: &SignatureHash) -> MessageDigest { + match hash { + SignatureHash::SHA2_256 => MessageDigest::sha256(), + SignatureHash::SHA2_512 => MessageDigest::sha512(), + SignatureHash::SHA1 => MessageDigest::sha1(), + } +} + +fn calc_dp_dq(d: &BigNumRef, p: &BigNumRef, q: &BigNumRef) -> Result<(BigNum, BigNum), Error> { + let one = BigNum::from_u32(1)?; + let p1 = p - one.as_ref(); + let q1 = q - one.as_ref(); + let mut context = BigNumContext::new()?; + let mut dp = BigNum::new()?; + let mut dq = BigNum::new()?; + dp.checked_rem(d, &p1, &mut context)?; + dq.checked_rem(d, &q1, &mut context)?; + Ok((dp, dq)) +} diff --git a/russh-keys/src/backend_rust.rs b/russh-keys/src/backend_rust.rs new file mode 100644 index 00000000..5c44f475 --- /dev/null +++ b/russh-keys/src/backend_rust.rs @@ -0,0 +1,185 @@ +use std::convert::TryFrom; + +use crate::key::{RsaCrtExtra, SignatureHash}; +use crate::{protocol, Error}; +use rsa::{ + traits::{PrivateKeyParts, PublicKeyParts}, + BigUint, +}; + +#[derive(Clone, PartialEq, Eq)] +pub struct RsaPublic { + key: rsa::RsaPublicKey, +} + +impl RsaPublic { + pub fn verify_detached(&self, hash: &SignatureHash, msg: &[u8], sig: &[u8]) -> bool { + self.key + .verify(signature_scheme_for_hash(hash), &hash_msg(hash, msg), sig) + .is_ok() + } +} + +impl TryFrom<&protocol::RsaPublicKey<'_>> for RsaPublic { + type Error = Error; + + fn try_from(pk: &protocol::RsaPublicKey<'_>) -> Result { + Ok(Self { + key: rsa::RsaPublicKey::new( + BigUint::from_bytes_be(&pk.modulus), + BigUint::from_bytes_be(&pk.public_exponent), + )?, + }) + } +} + +impl<'a> From<&RsaPublic> for protocol::RsaPublicKey<'a> { + fn from(key: &RsaPublic) -> Self { + Self { + modulus: key.key.n().to_bytes_be().into(), + public_exponent: key.key.e().to_bytes_be().into(), + } + } +} + +impl std::fmt::Debug for RsaPublic { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "RsaPublic {{ (hidden) }}") + } +} + +#[derive(Clone)] +pub struct RsaPrivate { + key: rsa::RsaPrivateKey, +} + +impl RsaPrivate { + pub fn new( + sk: &protocol::RsaPrivateKey<'_>, + extra: Option<&RsaCrtExtra<'_>>, + ) -> Result { + let mut key = rsa::RsaPrivateKey::from_components( + BigUint::from_bytes_be(&sk.public_key.modulus), + BigUint::from_bytes_be(&sk.public_key.public_exponent), + BigUint::from_bytes_be(&sk.private_exponent), + vec![ + BigUint::from_bytes_be(&sk.prime1), + BigUint::from_bytes_be(&sk.prime2), + ], + )?; + key.validate()?; + key.precompute()?; + + if Some(BigUint::from_bytes_be(&sk.coefficient)) != key.crt_coefficient() { + return Err(Error::KeyIsCorrupt); + } + if let Some(extra) = extra { + if ( + Some(&BigUint::from_bytes_be(&extra.dp)), + Some(&BigUint::from_bytes_be(&extra.dq)), + ) != (key.dp(), key.dq()) + { + return Err(Error::KeyIsCorrupt); + } + } + + Ok(Self { key }) + } + + pub fn new_from_der(der: &[u8]) -> Result { + use pkcs1::DecodeRsaPrivateKey; + Ok(Self { + key: rsa::RsaPrivateKey::from_pkcs1_der(der)?, + }) + } + + pub fn generate(bits: usize) -> Result { + Ok(Self { + key: rsa::RsaPrivateKey::new(&mut crate::key::safe_rng(), bits)?, + }) + } + + pub fn sign(&self, hash: &SignatureHash, msg: &[u8]) -> Result, Error> { + Ok(self + .key + .sign(signature_scheme_for_hash(hash), &hash_msg(hash, msg))?) + } +} + +impl<'a> TryFrom<&RsaPrivate> for protocol::RsaPrivateKey<'a> { + type Error = Error; + + fn try_from(key: &RsaPrivate) -> Result, Self::Error> { + let key = &key.key; + // We always precompute these. + if let ([p, q], Some(iqmp)) = (key.primes(), key.crt_coefficient()) { + Ok(protocol::RsaPrivateKey { + public_key: protocol::RsaPublicKey { + modulus: key.n().to_bytes_be().into(), + public_exponent: key.e().to_bytes_be().into(), + }, + private_exponent: key.d().to_bytes_be().into(), + prime1: p.to_bytes_be().into(), + prime2: q.to_bytes_be().into(), + coefficient: iqmp.to_bytes_be().into(), + comment: b"".as_slice().into(), + }) + } else { + Err(Error::KeyIsCorrupt) + } + } +} + +impl<'a> TryFrom<&RsaPrivate> for RsaCrtExtra<'a> { + type Error = Error; + + fn try_from(key: &RsaPrivate) -> Result, Self::Error> { + let key = &key.key; + // We always precompute these. + if let (Some(dp), Some(dq)) = (key.dp(), key.dq()) { + Ok(RsaCrtExtra { + dp: dp.to_bytes_be().into(), + dq: dq.to_bytes_be().into(), + }) + } else { + Err(Error::KeyIsCorrupt) + } + } +} + +impl<'a> From<&RsaPrivate> for protocol::RsaPublicKey<'a> { + fn from(key: &RsaPrivate) -> Self { + Self { + modulus: key.key.n().to_bytes_be().into(), + public_exponent: key.key.e().to_bytes_be().into(), + } + } +} + +impl TryFrom<&RsaPrivate> for RsaPublic { + type Error = Error; + + fn try_from(key: &RsaPrivate) -> Result { + Ok(Self { + key: key.key.to_public_key(), + }) + } +} + +fn signature_scheme_for_hash(hash: &SignatureHash) -> rsa::pkcs1v15::Pkcs1v15Sign { + use rsa::pkcs1v15::Pkcs1v15Sign; + match *hash { + SignatureHash::SHA2_256 => Pkcs1v15Sign::new::(), + SignatureHash::SHA2_512 => Pkcs1v15Sign::new::(), + SignatureHash::SHA1 => Pkcs1v15Sign::new::(), + } +} + +fn hash_msg(hash: &SignatureHash, msg: &[u8]) -> Vec { + use digest::Digest; + match *hash { + SignatureHash::SHA2_256 => sha2::Sha256::digest(msg).to_vec(), + SignatureHash::SHA2_512 => sha2::Sha512::digest(msg).to_vec(), + SignatureHash::SHA1 => sha1::Sha1::digest(msg).to_vec(), + } +} diff --git a/russh-keys/src/encoding.rs b/russh-keys/src/encoding.rs index 0f64f724..2ef14e1c 100644 --- a/russh-keys/src/encoding.rs +++ b/russh-keys/src/encoding.rs @@ -41,6 +41,20 @@ pub trait Encoding { fn extend_list>(&mut self, list: I); /// Push an SSH-encoded empty list. fn write_empty_list(&mut self); + /// Push an SSH-encoded value. + fn extend_ssh(&mut self, v: &T) { + v.write_ssh(self) + } + /// Push a nested SSH-encoded value. + fn extend_wrapped(&mut self, write: F) + where + F: FnOnce(&mut Self); +} + +/// Trait for writing value in SSH-encoded format. +pub trait SshWrite { + /// Write the value. + fn write_ssh(&self, encoder: &mut E); } /// Encoding length of the given mpint. @@ -109,6 +123,20 @@ impl Encoding for Vec { fn write_empty_list(&mut self) { self.extend([0, 0, 0, 0]); } + + fn extend_wrapped(&mut self, write: F) + where + F: FnOnce(&mut Self), + { + let len_offset = self.len(); + #[allow(clippy::unwrap_used)] // writing into Vec<> can't panic + self.write_u32::(0).unwrap(); + let data_offset = self.len(); + write(self); + let data_len = self.len() - data_offset; + #[allow(clippy::indexing_slicing)] // length is known + BigEndian::write_u32(&mut self[len_offset..], data_len as u32); + } } impl Encoding for CryptoVec { @@ -163,6 +191,19 @@ impl Encoding for CryptoVec { fn write_empty_list(&mut self) { self.extend(&[0, 0, 0, 0]); } + + fn extend_wrapped(&mut self, write: F) + where + F: FnOnce(&mut Self), + { + let len_offset = self.len(); + self.push_u32_be(0); + let data_offset = self.len(); + write(self); + let data_len = self.len() - data_offset; + #[allow(clippy::indexing_slicing)] // length is known + BigEndian::write_u32(&mut self[len_offset..], data_len as u32); + } } /// A cursor-like trait to read SSH-encoded things. @@ -244,4 +285,14 @@ impl<'a> Position<'a> { Err(Error::IndexOutOfBounds) } } + + pub fn read_ssh>(&mut self) -> Result { + T::read_ssh(self) + } +} + +/// Trait for reading value in SSH-encoded format. +pub trait SshRead<'a>: Sized + 'a { + /// Read the value from a position. + fn read_ssh(pos: &mut Position<'a>) -> Result; } diff --git a/russh-keys/src/format/mod.rs b/russh-keys/src/format/mod.rs index 1463a85d..7a321590 100644 --- a/russh-keys/src/format/mod.rs +++ b/russh-keys/src/format/mod.rs @@ -1,11 +1,6 @@ use std::io::Write; -#[cfg(not(feature = "openssl"))] -use data_encoding::BASE64_MIME; -#[cfg(feature = "openssl")] use data_encoding::{BASE64_MIME, HEXLOWER_PERMISSIVE}; -#[cfg(feature = "openssl")] -use openssl::rsa::Rsa; use super::is_base64_char; use crate::{key, Error}; @@ -13,9 +8,7 @@ use crate::{key, Error}; pub mod openssh; pub use self::openssh::*; -#[cfg(feature = "openssl")] pub mod pkcs5; -#[cfg(feature = "openssl")] pub use self::pkcs5::*; pub mod pkcs8; @@ -33,10 +26,8 @@ pub enum Encryption { #[derive(Clone, Debug)] enum Format { - #[cfg(feature = "openssl")] Rsa, Openssh, - #[cfg(feature = "openssl")] Pkcs5Encrypted(Encryption), Pkcs8Encrypted, Pkcs8, @@ -57,35 +48,22 @@ 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); - } + started = true; + format = Some(Format::Rsa); } else if l == "-----BEGIN ENCRYPTED PRIVATE KEY-----" { started = true; format = Some(Format::Pkcs8Encrypted); @@ -100,9 +78,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 +108,9 @@ pub fn encode_pkcs8_pem_encrypted( Ok(()) } -#[cfg(feature = "openssl")] fn decode_rsa(secret: &[u8]) -> Result { Ok(key::KeyPair::RSA { - key: Rsa::private_key_from_der(secret)?, + key: crate::backend::RsaPrivate::new_from_der(secret)?, hash: key::SignatureHash::SHA2_256, }) } diff --git a/russh-keys/src/format/openssh.rs b/russh-keys/src/format/openssh.rs index 21fbed3e..91ccd063 100644 --- a/russh-keys/src/format/openssh.rs +++ b/russh-keys/src/format/openssh.rs @@ -4,8 +4,6 @@ use aes::cipher::block_padding::NoPadding; use aes::cipher::{BlockDecryptMut, KeyIvInit, StreamCipher}; use bcrypt_pbkdf; use ctr::Ctr64BE; -#[cfg(feature = "openssl")] -use openssl::bn::BigNum; use crate::encoding::Reader; use crate::{ec, key, Error, KEYTYPE_ED25519, KEYTYPE_RSA}; @@ -48,37 +46,12 @@ pub fn decode_openssh(secret: &[u8], password: Option<&str>) -> Result, diff --git a/russh-keys/src/format/pkcs8.rs b/russh-keys/src/format/pkcs8.rs index 4a6bec8b..e4ebe214 100644 --- a/russh-keys/src/format/pkcs8.rs +++ b/russh-keys/src/format/pkcs8.rs @@ -4,26 +4,20 @@ 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; use yasna::BERReaderSeq; use {std, yasna}; use super::Encryption; -#[cfg(feature = "openssl")] use crate::key::SignatureHash; -use crate::{key, Error}; +use crate::{key, protocol, Error}; const PBES2: &[u64] = &[1, 2, 840, 113549, 1, 5, 13]; 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]; const EC_PUBLIC_KEY: &[u64] = &[1, 2, 840, 10045, 2, 1]; const SECP256R1: &[u64] = &[1, 2, 840, 10045, 3, 1, 7]; @@ -168,43 +162,46 @@ fn read_key_v1(reader: &mut BERReaderSeq) -> Result { } } -#[cfg(feature = "openssl")] -fn write_key_v0_rsa(writer: &mut yasna::DERWriterSeq, key: &Rsa) { +fn write_key_v0_rsa(writer: &mut yasna::DERWriterSeq, key: &key::RsaPrivate) { writer.next().write_u32(0); // write OID writer.next().write_sequence(|writer| { writer.next().write_oid(&ObjectIdentifier::from_slice(RSA)); writer.next().write_null() }); + #[allow(clippy::unwrap_used)] // key is known to be private + let (sk, extra) = ( + protocol::RsaPrivateKey::try_from(key).unwrap(), + key::RsaCrtExtra::try_from(key).unwrap(), + ); let bytes = yasna::construct_der(|writer| { - #[allow(clippy::unwrap_used)] // key is known to be private writer.write_sequence(|writer| { writer.next().write_u32(0); use num_bigint::BigUint; writer .next() - .write_biguint(&BigUint::from_bytes_be(&key.n().to_vec())); + .write_biguint(&BigUint::from_bytes_be(&sk.public_key.modulus)); writer .next() - .write_biguint(&BigUint::from_bytes_be(&key.e().to_vec())); + .write_biguint(&BigUint::from_bytes_be(&sk.public_key.public_exponent)); writer .next() - .write_biguint(&BigUint::from_bytes_be(&key.d().to_vec())); + .write_biguint(&BigUint::from_bytes_be(&sk.private_exponent)); writer .next() - .write_biguint(&BigUint::from_bytes_be(&key.p().unwrap().to_vec())); + .write_biguint(&BigUint::from_bytes_be(&sk.prime1)); writer .next() - .write_biguint(&BigUint::from_bytes_be(&key.q().unwrap().to_vec())); + .write_biguint(&BigUint::from_bytes_be(&sk.prime2)); writer .next() - .write_biguint(&BigUint::from_bytes_be(&key.dmp1().unwrap().to_vec())); + .write_biguint(&BigUint::from_bytes_be(&extra.dp)); writer .next() - .write_biguint(&BigUint::from_bytes_be(&key.dmq1().unwrap().to_vec())); + .write_biguint(&BigUint::from_bytes_be(&extra.dq)); writer .next() - .write_biguint(&BigUint::from_bytes_be(&key.iqmp().unwrap().to_vec())); + .write_biguint(&BigUint::from_bytes_be(&sk.coefficient)); }) }); writer.next().write_bytes(&bytes); @@ -242,7 +239,6 @@ fn write_key_v0_ec(writer: &mut yasna::DERWriterSeq, key: &crate::ec::PrivateKey // Utility enum used for reading v0 key. enum KeyType { Unknown(ObjectIdentifier), - #[cfg(feature = "openssl")] #[allow(clippy::upper_case_acronyms)] RSA, EC(ObjectIdentifier), @@ -252,7 +248,6 @@ fn read_key_v0(reader: &mut BERReaderSeq) -> Result { let key_type = reader.next().read_sequence(|reader| { let oid = reader.next().read_oid()?; Ok(match oid.components().as_slice() { - #[cfg(feature = "openssl")] RSA => { reader.next().read_null()?; KeyType::RSA @@ -263,35 +258,48 @@ fn read_key_v0(reader: &mut BERReaderSeq) -> Result { })?; match key_type { KeyType::Unknown(_) => Err(Error::CouldNotReadKey), - #[cfg(feature = "openssl")] KeyType::RSA => { let seq = &reader.next().read_bytes()?; - let rsa: Result, Error> = yasna::parse_der(seq, |reader| { + let (sk, extra) = 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()) + let (n, e, d, p, q, dmp1, dmq1, iqmp) = ( + 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(), + 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(Ok(( + protocol::RsaPrivateKey { + public_key: protocol::RsaPublicKey { + modulus: n.into(), + public_exponent: e.into(), + }, + private_exponent: d.into(), + prime1: p.into(), + prime2: q.into(), + coefficient: iqmp.into(), + comment: Cow::Borrowed(b""), + }, + key::RsaCrtExtra { + dp: dmp1.into(), + dq: dmq1.into(), + }, + ))) }) - })?; - Ok(key::KeyPair::RSA { - key: rsa?, - hash: SignatureHash::SHA2_256, - }) + })??; + Ok(key::KeyPair::new_rsa_with_hash( + &sk, + Some(&extra), + SignatureHash::SHA2_256, + )?) } KeyType::EC(oid) => { let private_key = reader.next().read_bytes()?; @@ -351,7 +359,6 @@ fn test_read_write_pkcs8() { match key { key::KeyPair::Ed25519 { .. } => println!("Ed25519"), key::KeyPair::EC { .. } => println!("EC"), - #[cfg(feature = "openssl")] key::KeyPair::RSA { .. } => println!("RSA"), } } @@ -403,7 +410,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_rsa(writer, key), key::KeyPair::EC { ref key, .. } => write_key_v0_ec(writer, key), }) diff --git a/russh-keys/src/key.rs b/russh-keys/src/key.rs index dcb578ee..1bc0373c 100644 --- a/russh-keys/src/key.rs +++ b/russh-keys/src/key.rs @@ -12,20 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. // -use std::convert::TryFrom; +use std::borrow::Cow; +use std::convert::{TryFrom, TryInto}; use ed25519_dalek::{Signer, Verifier}; -#[cfg(feature = "openssl")] -use openssl::pkey::{Private, Public}; use rand_core::OsRng; use russh_cryptovec::CryptoVec; use serde::{Deserialize, Serialize}; +use crate::backend; use crate::ec; use crate::encoding::{Encoding, Reader}; +use crate::protocol; pub use crate::signature::*; use crate::Error; +pub use backend::{RsaPrivate, RsaPublic}; + #[derive(Debug, PartialEq, Eq, Copy, Clone)] /// Name of a public key algorithm. pub struct Name(pub &'static str); @@ -93,16 +96,6 @@ impl SignatureHash { } } - #[cfg(feature = "openssl")] - fn message_digest(&self) -> openssl::hash::MessageDigest { - use openssl::hash::MessageDigest; - match *self { - SignatureHash::SHA2_256 => MessageDigest::sha256(), - SignatureHash::SHA2_512 => MessageDigest::sha512(), - SignatureHash::SHA1 => MessageDigest::sha1(), - } - } - pub fn from_rsa_hostkey_algo(algo: &[u8]) -> Option { if algo == b"rsa-sha2-256" { Some(Self::SHA2_256) @@ -120,9 +113,8 @@ pub enum PublicKey { #[doc(hidden)] Ed25519(ed25519_dalek::VerifyingKey), #[doc(hidden)] - #[cfg(feature = "openssl")] RSA { - key: OpenSSLPKey, + key: backend::RsaPublic, hash: SignatureHash, }, #[doc(hidden)] @@ -132,7 +124,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::EC { key: a }, Self::EC { key: b }) => a == b, @@ -141,29 +132,6 @@ impl PartialEq for PublicKey { } } -/// A public key from OpenSSL. -#[cfg(feature = "openssl")] -#[derive(Clone)] -pub struct OpenSSLPKey(pub openssl::pkey::PKey); - -#[cfg(feature = "openssl")] -use std::cmp::{Eq, PartialEq}; - -#[cfg(feature = "openssl")] -impl PartialEq for OpenSSLPKey { - fn eq(&self, b: &OpenSSLPKey) -> bool { - self.0.public_eq(&b.0) - } -} -#[cfg(feature = "openssl")] -impl Eq for OpenSSLPKey {} -#[cfg(feature = "openssl")] -impl std::fmt::Debug for OpenSSLPKey { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "OpenSSLPKey {{ (hidden) }}") - } -} - impl PublicKey { /// Parse a public key in SSH format. pub fn parse(algo: &[u8], pubkey: &[u8]) -> Result { @@ -183,37 +151,21 @@ impl PublicKey { .map(PublicKey::Ed25519) .map_err(Error::from) } - b"ssh-rsa" | b"rsa-sha2-256" | b"rsa-sha2-512" if cfg!(feature = "openssl") => { - #[cfg(feature = "openssl")] - { - use log::debug; - let mut p = pubkey.reader(0); - let key_algo = p.read_string()?; - debug!("{:?}", std::str::from_utf8(key_algo)); - if key_algo != b"ssh-rsa" - && key_algo != b"rsa-sha2-256" - && key_algo != b"rsa-sha2-512" - { - return Err(Error::CouldNotReadKey); - } - let key_e = p.read_string()?; - let key_n = p.read_string()?; - use openssl::bn::BigNum; - use openssl::pkey::PKey; - use openssl::rsa::Rsa; - Ok(PublicKey::RSA { - key: OpenSSLPKey(PKey::from_rsa(Rsa::from_public_components( - BigNum::from_slice(key_n)?, - BigNum::from_slice(key_e)?, - )?)?), - hash: SignatureHash::from_rsa_hostkey_algo(algo) - .unwrap_or(SignatureHash::SHA1), - }) - } - #[cfg(not(feature = "openssl"))] + b"ssh-rsa" | b"rsa-sha2-256" | b"rsa-sha2-512" => { + use log::debug; + let mut p = pubkey.reader(0); + let key_algo = p.read_string()?; + debug!("{:?}", std::str::from_utf8(key_algo)); + if key_algo != b"ssh-rsa" + && key_algo != b"rsa-sha2-256" + && key_algo != b"rsa-sha2-512" { - unreachable!() + return Err(Error::CouldNotReadKey); } + Ok(PublicKey::new_rsa_with_hash( + &p.read_ssh()?, + SignatureHash::from_rsa_hostkey_algo(algo).unwrap_or(SignatureHash::SHA1), + )?) } crate::KEYTYPE_ECDSA_SHA2_NISTP256 | crate::KEYTYPE_ECDSA_SHA2_NISTP384 @@ -238,11 +190,20 @@ impl PublicKey { } } + pub fn new_rsa_with_hash( + pk: &protocol::RsaPublicKey<'_>, + hash: SignatureHash, + ) -> Result { + Ok(PublicKey::RSA { + key: RsaPublic::try_from(pk)?, + hash, + }) + } + /// Algorithm name for that key. pub fn name(&self) -> &'static str { match *self { PublicKey::Ed25519(_) => ED25519.0, - #[cfg(feature = "openssl")] PublicKey::RSA { ref hash, .. } => hash.name().0, PublicKey::EC { ref key } => key.algorithm(), } @@ -258,17 +219,7 @@ impl PublicKey { let sig = ed25519_dalek::Signature::from_bytes(&sig); public.verify(buffer, &sig).is_ok() } - - #[cfg(feature = "openssl")] - PublicKey::RSA { ref key, ref hash } => { - use openssl::sign::*; - let verify = || { - let mut verifier = Verifier::new(hash.message_digest(), &key.0)?; - verifier.update(buffer)?; - verifier.verify(sig) - }; - verify().unwrap_or(false) - } + PublicKey::RSA { ref key, ref hash } => key.verify_detached(hash, buffer, sig), PublicKey::EC { ref key, .. } => ec_verify(key, buffer, sig).is_ok(), } } @@ -283,7 +234,6 @@ impl PublicKey { 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" { @@ -295,9 +245,6 @@ impl PublicKey { } } } - - #[cfg(not(feature = "openssl"))] - pub fn set_algorithm(&mut self, _: &[u8]) {} } impl Verify for PublicKey { @@ -313,9 +260,8 @@ impl Verify for PublicKey { #[allow(clippy::large_enum_variant)] pub enum KeyPair { Ed25519(ed25519_dalek::SigningKey), - #[cfg(feature = "openssl")] RSA { - key: openssl::rsa::Rsa, + key: backend::RsaPrivate, hash: SignatureHash, }, EC { @@ -330,7 +276,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, @@ -348,7 +293,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) }}"), KeyPair::EC { .. } => write!(f, "EC {{ (hidden) }}"), } @@ -362,20 +306,25 @@ impl<'b> crate::encoding::Bytes for &'b KeyPair { } impl KeyPair { + pub fn new_rsa_with_hash( + sk: &protocol::RsaPrivateKey<'_>, + extra: Option<&RsaCrtExtra<'_>>, + hash: SignatureHash, + ) -> Result { + Ok(KeyPair::RSA { + key: RsaPrivate::new(sk, extra)?, + hash, + }) + } + /// Copy the public key of this algorithm. pub fn clone_public_key(&self) -> Result { Ok(match self { KeyPair::Ed25519(ref key) => PublicKey::Ed25519(key.verifying_key()), - #[cfg(feature = "openssl")] - KeyPair::RSA { ref key, ref hash } => { - use openssl::pkey::PKey; - use openssl::rsa::Rsa; - let key = Rsa::from_public_components(key.n().to_owned()?, key.e().to_owned()?)?; - PublicKey::RSA { - key: OpenSSLPKey(PKey::from_rsa(key)?), - hash: *hash, - } - } + KeyPair::RSA { ref key, ref hash } => PublicKey::RSA { + key: key.try_into()?, + hash: *hash, + }, KeyPair::EC { ref key } => PublicKey::EC { key: key.to_public_key(), }, @@ -386,13 +335,12 @@ impl KeyPair { pub fn name(&self) -> &'static str { match *self { KeyPair::Ed25519(_) => ED25519.0, - #[cfg(feature = "openssl")] KeyPair::RSA { ref hash, .. } => hash.name().0, KeyPair::EC { ref key } => key.algorithm(), } } - /// Generate a key pair. + /// Generate a ED25519 key pair. pub fn generate_ed25519() -> Option { let keypair = ed25519_dalek::SigningKey::generate(&mut OsRng {}); assert_eq!( @@ -402,9 +350,9 @@ impl KeyPair { Some(KeyPair::Ed25519(keypair)) } - #[cfg(feature = "openssl")] + /// Generate a RSA key pair. pub fn generate_rsa(bits: usize, hash: SignatureHash) -> Option { - let key = openssl::rsa::Rsa::generate(bits as u32).ok()?; + let key = RsaPrivate::generate(bits).ok()?; Some(KeyPair::RSA { key, hash }) } @@ -415,9 +363,8 @@ 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)?, + bytes: key.sign(hash, to_sign)?, hash: *hash, }), KeyPair::EC { ref key } => Ok(Signature::ECDSA { @@ -444,10 +391,9 @@ 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())?; + let signature = key.sign(hash, to_sign.as_ref())?; let name = hash.name(); buffer.push_u32_be((name.0.len() + signature.len() + 8) as u32); buffer.extend_ssh_string(name.0.as_bytes()); @@ -476,10 +422,9 @@ 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)?; + let signature = key.sign(hash, buffer)?; let name = hash.name(); buffer.push_u32_be((name.0.len() + signature.len() + 8) as u32); buffer.extend_ssh_string(name.0.as_bytes()); @@ -497,11 +442,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, @@ -511,28 +454,12 @@ impl KeyPair { } } -#[cfg(feature = "openssl")] -fn rsa_signature( - hash: &SignatureHash, - key: &openssl::rsa::Rsa, - b: &[u8], -) -> Result, Error> { - use openssl::pkey::*; - use openssl::rsa::*; - use openssl::sign::Signer; - let pkey = PKey::from_rsa(Rsa::from_private_components( - key.n().to_owned()?, - key.e().to_owned()?, - key.d().to_owned()?, - key.p().ok_or(Error::KeyIsCorrupt)?.to_owned()?, - key.q().ok_or(Error::KeyIsCorrupt)?.to_owned()?, - key.dmp1().ok_or(Error::KeyIsCorrupt)?.to_owned()?, - key.dmq1().ok_or(Error::KeyIsCorrupt)?.to_owned()?, - key.iqmp().ok_or(Error::KeyIsCorrupt)?.to_owned()?, - )?)?; - let mut signer = Signer::new(hash.message_digest(), &pkey)?; - signer.update(b)?; - Ok(signer.sign_to_vec()?) +/// Extra CRT parameters for RSA private key. +pub struct RsaCrtExtra<'a> { + /// `d mod (p-1)`. + pub dp: Cow<'a, [u8]>, + /// `d mod (q-1)`. + pub dq: Cow<'a, [u8]>, } fn ec_signature(key: &ec::PrivateKey, b: &[u8]) -> Result, Error> { @@ -549,10 +476,7 @@ fn ec_verify(key: &ec::PublicKey, b: &[u8], sig: &[u8]) -> Result<(), Error> { } /// 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], prefer_hash: Option) -> Result { let mut pos = p.reader(0); let t = pos.read_string()?; if t == b"ssh-ed25519" { @@ -565,21 +489,10 @@ pub fn parse_public_key( } } if t == b"ssh-rsa" { - #[cfg(feature = "openssl")] - { - let e = pos.read_string()?; - let n = pos.read_string()?; - use openssl::bn::*; - use openssl::pkey::*; - use openssl::rsa::*; - return Ok(PublicKey::RSA { - key: OpenSSLPKey(PKey::from_rsa(Rsa::from_public_components( - BigNum::from_slice(n)?, - BigNum::from_slice(e)?, - )?)?), - hash: prefer_hash.unwrap_or(SignatureHash::SHA2_256), - }); - } + return PublicKey::new_rsa_with_hash( + &pos.read_ssh()?, + prefer_hash.unwrap_or(SignatureHash::SHA2_256), + ); } if t == crate::KEYTYPE_ECDSA_SHA2_NISTP256 || t == crate::KEYTYPE_ECDSA_SHA2_NISTP384 diff --git a/russh-keys/src/lib.rs b/russh-keys/src/lib.rs index 14bfab6c..0810a676 100644 --- a/russh-keys/src/lib.rs +++ b/russh-keys/src/lib.rs @@ -10,11 +10,11 @@ //! opening key files, deciphering encrypted keys, and dealing with //! agents. //! -//! The following example (which uses the `openssl` feature) shows how -//! to do all these in a single example: start and SSH agent server, -//! connect to it with a client, decipher an encrypted private key -//! (the password is `b"blabla"`), send it to the agent, and ask the -//! agent to sign a piece of data (`b"Please sign this"`, below). +//! The following example shows how to do all these in a single example: +//! start and SSH agent server, connect to it with a client, decipher +//! an encrypted private key (the password is `b"blabla"`), send it to +//! the agent, and ask the agent to sign a piece of data +//! (`b"Please sign this"`, below). //! //!``` //! use russh_keys::*; @@ -30,7 +30,7 @@ //! //! const PKCS8_ENCRYPTED: &'static str = "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQITo1O0b8YrS0CAggA\nMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBtLH4T1KOfo1GGr7salhR8BIIE\n0KN9ednYwcTGSX3hg7fROhTw7JAJ1D4IdT1fsoGeNu2BFuIgF3cthGHe6S5zceI2\nMpkfwvHbsOlDFWMUIAb/VY8/iYxhNmd5J6NStMYRC9NC0fVzOmrJqE1wITqxtORx\nIkzqkgFUbaaiFFQPepsh5CvQfAgGEWV329SsTOKIgyTj97RxfZIKA+TR5J5g2dJY\nj346SvHhSxJ4Jc0asccgMb0HGh9UUDzDSql0OIdbnZW5KzYJPOx+aDqnpbz7UzY/\nP8N0w/pEiGmkdkNyvGsdttcjFpOWlLnLDhtLx8dDwi/sbEYHtpMzsYC9jPn3hnds\nTcotqjoSZ31O6rJD4z18FOQb4iZs3MohwEdDd9XKblTfYKM62aQJWH6cVQcg+1C7\njX9l2wmyK26Tkkl5Qg/qSfzrCveke5muZgZkFwL0GCcgPJ8RixSB4GOdSMa/hAMU\nkvFAtoV2GluIgmSe1pG5cNMhurxM1dPPf4WnD+9hkFFSsMkTAuxDZIdDk3FA8zof\nYhv0ZTfvT6V+vgH3Hv7Tqcxomy5Qr3tj5vvAqqDU6k7fC4FvkxDh2mG5ovWvc4Nb\nXv8sed0LGpYitIOMldu6650LoZAqJVv5N4cAA2Edqldf7S2Iz1QnA/usXkQd4tLa\nZ80+sDNv9eCVkfaJ6kOVLk/ghLdXWJYRLenfQZtVUXrPkaPpNXgD0dlaTN8KuvML\nUw/UGa+4ybnPsdVflI0YkJKbxouhp4iB4S5ACAwqHVmsH5GRnujf10qLoS7RjDAl\no/wSHxdT9BECp7TT8ID65u2mlJvH13iJbktPczGXt07nBiBse6OxsClfBtHkRLzE\nQF6UMEXsJnIIMRfrZQnduC8FUOkfPOSXc8r9SeZ3GhfbV/DmWZvFPCpjzKYPsM5+\nN8Bw/iZ7NIH4xzNOgwdp5BzjH9hRtCt4sUKVVlWfEDtTnkHNOusQGKu7HkBF87YZ\nRN/Nd3gvHob668JOcGchcOzcsqsgzhGMD8+G9T9oZkFCYtwUXQU2XjMN0R4VtQgZ\nrAxWyQau9xXMGyDC67gQ5xSn+oqMK0HmoW8jh2LG/cUowHFAkUxdzGadnjGhMOI2\nzwNJPIjF93eDF/+zW5E1l0iGdiYyHkJbWSvcCuvTwma9FIDB45vOh5mSR+YjjSM5\nnq3THSWNi7Cxqz12Q1+i9pz92T2myYKBBtu1WDh+2KOn5DUkfEadY5SsIu/Rb7ub\n5FBihk2RN3y/iZk+36I69HgGg1OElYjps3D+A9AjVby10zxxLAz8U28YqJZm4wA/\nT0HLxBiVw+rsHmLP79KvsT2+b4Diqih+VTXouPWC/W+lELYKSlqnJCat77IxgM9e\nYIhzD47OgWl33GJ/R10+RDoDvY4koYE+V5NLglEhbwjloo9Ryv5ywBJNS7mfXMsK\n/uf+l2AscZTZ1mhtL38efTQCIRjyFHc3V31DI0UdETADi+/Omz+bXu0D5VvX+7c6\nb1iVZKpJw8KUjzeUV8yOZhvGu3LrQbhkTPVYL555iP1KN0Eya88ra+FUKMwLgjYr\nJkUx4iad4dTsGPodwEP/Y9oX/Qk3ZQr+REZ8lg6IBoKKqqrQeBJ9gkm1jfKE6Xkc\nCog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux\n-----END ENCRYPTED PRIVATE KEY-----\n"; //! -//! #[cfg(all(unix, feature = "openssl"))] +//! #[cfg(unix)] //! fn main() { //! env_logger::try_init().unwrap_or(()); //! let dir = tempdir::TempDir::new("russh").unwrap(); @@ -58,7 +58,7 @@ //! }).unwrap() //! } //! -//! #[cfg(any(not(unix), not(feature = "openssl")))] +//! #[cfg(not(unix))] //! fn main() {} //! //! ``` @@ -80,11 +80,19 @@ use thiserror::Error; pub mod ec; pub mod encoding; pub mod key; +pub mod protocol; pub mod signature; mod format; pub use format::*; +#[cfg(feature = "openssl")] +#[path = "backend_openssl.rs"] +mod backend; +#[cfg(not(feature = "openssl"))] +#[path = "backend_rust.rs"] +mod backend; + /// A module to write SSH agent. pub mod agent; @@ -140,6 +148,13 @@ pub enum Error { #[error(transparent)] Openssl(#[from] openssl::error::ErrorStack), + #[cfg(not(feature = "openssl"))] + #[error("Rsa: {0}")] + Rsa(#[from] rsa::Error), + #[cfg(not(feature = "openssl"))] + #[error("Pkcs1: {0}")] + Pkcs1(#[from] pkcs1::Error), + #[error(transparent)] Pad(#[from] PadError), @@ -202,11 +217,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 { @@ -234,17 +245,13 @@ 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()); + s.extend_ssh(&protocol::RsaPublicKey::from(key)); } key::PublicKey::EC { ref key } => { write_ec_public_key(&mut s, key); @@ -268,11 +275,9 @@ 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()); + s.extend_ssh(&protocol::RsaPublicKey::from(key)); } key::KeyPair::EC { ref key } => { write_ec_public_key(&mut s, &key.to_public_key()); @@ -485,7 +490,7 @@ mod test { use std::fs::File; use std::io::Write; - #[cfg(all(unix, feature = "openssl"))] + #[cfg(unix)] use futures::Future; use super::*; @@ -509,7 +514,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 @@ -551,7 +555,6 @@ 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(); @@ -625,7 +628,6 @@ Ve0k2ddxoEsSE15H4lgNHM2iuYKzIqZJOReHRCTff6QGgMYPDqDfFfL1Hc1Ntql0pwAAAA } #[test] - #[cfg(feature = "openssl")] fn test_fingerprint() { let key = parse_public_key_base64( "AAAAC3NzaC1lZDI1NTE5AAAAILagOJFgwaMNhBWQINinKOXmqS4Gh5NgxgriXwdOoINJ", @@ -727,7 +729,6 @@ Ve0k2ddxoEsSE15H4lgNHM2iuYKzIqZJOReHRCTff6QGgMYPDqDfFfL1Hc1Ntql0pwAAAA } #[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=="; @@ -736,7 +737,6 @@ Ve0k2ddxoEsSE15H4lgNHM2iuYKzIqZJOReHRCTff6QGgMYPDqDfFfL1Hc1Ntql0pwAAAA } #[test] - #[cfg(feature = "openssl")] fn test_nikao() { env_logger::try_init().unwrap_or(()); let key = "-----BEGIN RSA PRIVATE KEY----- @@ -771,7 +771,6 @@ QaChXiDsryJZwsRnruvMRX9nedtqHrgnIsJLTXjppIhGhq5Kg4RQfOU= } #[test] - #[cfg(feature = "openssl")] fn test_decode_pkcs8_rsa_secret_key() { // Generated using: ssh-keygen -t rsa -b 1024 -m pkcs8 -f $file let key = "-----BEGIN PRIVATE KEY----- @@ -971,7 +970,6 @@ ZgmnWhuwhyxErC5UkMHiEvTOZllxBvefs7XeJqL11pqQIHY4Gb5OQGiCNHiRRjg0egAAAA ecdsa_sign_verify(key, public); } - #[cfg(feature = "openssl")] pub const PKCS8_RSA: &str = "-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAwBGetHjW+3bDQpVktdemnk7JXgu1NBWUM+ysifYLDBvJ9ttX GNZSyQKA4v/dNr0FhAJ8I9BuOTjYCy1YfKylhl5D/DiSSXFPsQzERMmGgAlYvU2U @@ -1002,7 +1000,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----- @@ -1045,7 +1042,6 @@ KJaj7gc0n6gmKY6r0/Ddufy1JZ6eihBCSJ64RARBXeg2rZpyT+xxhMEZLK5meOeR } #[test] - #[cfg(feature = "openssl")] fn test_o01eg() { env_logger::try_init().unwrap_or(()); @@ -1083,14 +1079,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 @@ -1123,7 +1117,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]; @@ -1156,7 +1149,6 @@ Cog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux } #[test] - #[cfg(feature = "openssl")] fn test_pkcs8_encrypted() { env_logger::try_init().unwrap_or(()); println!("test"); @@ -1199,7 +1191,6 @@ Cog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux assert!(public.verify_detached(a, sig)); } key::KeyPair::EC { .. } => {} - #[cfg(feature = "openssl")] _ => {} } @@ -1217,14 +1208,14 @@ Cog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux } #[tokio::test] - #[cfg(all(unix, feature = "openssl"))] + #[cfg(unix)] async fn test_client_agent_rsa() { let key = decode_secret_key(PKCS8_ENCRYPTED, Some("blabla")).unwrap(); test_client_agent(key).await.expect("ssh-agent test failed") } #[tokio::test] - #[cfg(all(unix, feature = "openssl"))] + #[cfg(unix)] async fn test_client_agent_openssh_rsa() { let key = decode_secret_key(RSA_KEY, None).unwrap(); test_client_agent(key).await.expect("ssh-agent test failed") @@ -1232,7 +1223,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-keys/src/protocol.rs b/russh-keys/src/protocol.rs new file mode 100644 index 00000000..52ea5275 --- /dev/null +++ b/russh-keys/src/protocol.rs @@ -0,0 +1,74 @@ +use crate::encoding::{Encoding, Position, SshRead, SshWrite}; +use std::borrow::Cow; + +type Result = std::result::Result; + +/// SSH RSA public key. +pub struct RsaPublicKey<'a> { + /// `e`: RSA public exponent. + pub public_exponent: Cow<'a, [u8]>, + /// `n`: RSA modulus. + pub modulus: Cow<'a, [u8]>, +} + +impl<'a> SshRead<'a> for RsaPublicKey<'a> { + fn read_ssh(pos: &mut Position<'a>) -> Result { + Ok(Self { + public_exponent: Cow::Borrowed(pos.read_mpint()?), + modulus: Cow::Borrowed(pos.read_mpint()?), + }) + } +} + +impl SshWrite for RsaPublicKey<'_> { + fn write_ssh(&self, encoder: &mut E) { + encoder.extend_ssh_mpint(&self.public_exponent); + encoder.extend_ssh_mpint(&self.modulus); + } +} + +/// SSH RSA private key. +pub struct RsaPrivateKey<'a> { + /// RSA public key. + pub public_key: RsaPublicKey<'a>, + /// `d`: RSA private exponent. + pub private_exponent: Cow<'a, [u8]>, + /// CRT coefficient: `(inverse of q) mod p`. + pub coefficient: Cow<'a, [u8]>, + /// `p`: first prime factor of `n`. + pub prime1: Cow<'a, [u8]>, + /// `q`: Second prime factor of `n`. + pub prime2: Cow<'a, [u8]>, + /// Comment. + pub comment: Cow<'a, [u8]>, +} + +impl<'a> SshRead<'a> for RsaPrivateKey<'a> { + fn read_ssh(pos: &mut Position<'a>) -> Result { + Ok(Self { + // Note the field order. + public_key: RsaPublicKey { + modulus: Cow::Borrowed(pos.read_mpint()?), + public_exponent: Cow::Borrowed(pos.read_mpint()?), + }, + private_exponent: Cow::Borrowed(pos.read_mpint()?), + coefficient: Cow::Borrowed(pos.read_mpint()?), + prime1: Cow::Borrowed(pos.read_mpint()?), + prime2: Cow::Borrowed(pos.read_mpint()?), + comment: Cow::Borrowed(pos.read_string()?), + }) + } +} + +impl SshWrite for RsaPrivateKey<'_> { + fn write_ssh(&self, encoder: &mut E) { + // Note the field order. + encoder.extend_ssh_mpint(&self.public_key.modulus); + encoder.extend_ssh_mpint(&self.public_key.public_exponent); + encoder.extend_ssh_mpint(&self.private_exponent); + encoder.extend_ssh_mpint(&self.coefficient); + encoder.extend_ssh_mpint(&self.prime1); + encoder.extend_ssh_mpint(&self.prime2); + encoder.extend_ssh_string(&self.comment); + } +} diff --git a/russh/src/client/encrypted.rs b/russh/src/client/encrypted.rs index 187ef1a1..379b3865 100644 --- a/russh/src/client/encrypted.rs +++ b/russh/src/client/encrypted.rs @@ -672,9 +672,6 @@ impl Session { match r.read_string() { Ok(key) => { let key2 = <&[u8]>::clone(&key); - #[cfg(not(feature = "openssl"))] - let key = parse_public_key(key).map_err(crate::Error::from); - #[cfg(feature = "openssl")] let key = parse_public_key(key, None).map_err(crate::Error::from); match key { diff --git a/russh/src/client/mod.rs b/russh/src/client/mod.rs index b2111d1a..fe64da94 100644 --- a/russh/src/client/mod.rs +++ b/russh/src/client/mod.rs @@ -47,7 +47,6 @@ 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 tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, ReadHalf, WriteHalf}; @@ -1212,7 +1211,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 3b8b51d8..d7753ac9 100644 --- a/russh/src/key.rs +++ b/russh/src/key.rs @@ -16,6 +16,7 @@ use russh_cryptovec::CryptoVec; use russh_keys::ec; use russh_keys::encoding::*; use russh_keys::key::*; +use russh_keys::protocol; #[doc(hidden)] pub trait PubKey { @@ -30,16 +31,11 @@ impl PubKey for PublicKey { buffer.extend_ssh_string(ED25519.0.as_bytes()); buffer.extend_ssh_string(public.as_bytes()); } - #[cfg(feature = "openssl")] PublicKey::RSA { ref key, .. } => { - #[allow(clippy::unwrap_used)] // type known - let rsa = key.0.rsa().unwrap(); - let e = rsa.e().to_vec(); - let n = rsa.n().to_vec(); - 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); - buffer.extend_ssh_mpint(&n); + buffer.extend_wrapped(|buffer| { + buffer.extend_ssh_string(SSH_RSA.0.as_bytes()); + buffer.extend_ssh(&protocol::RsaPublicKey::from(key)); + }); } PublicKey::EC { ref key } => { write_ec_public_key(buffer, key); @@ -57,14 +53,11 @@ 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(); - 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); - buffer.extend_ssh_mpint(&n); + buffer.extend_wrapped(|buffer| { + buffer.extend_ssh_string(SSH_RSA.0.as_bytes()); + buffer.extend_ssh(&protocol::RsaPublicKey::from(key)); + }); } KeyPair::EC { ref key } => { write_ec_public_key(buffer, &key.to_public_key()); diff --git a/russh/src/negotiation.rs b/russh/src/negotiation.rs index e2af10e7..0a19ff7e 100644 --- a/russh/src/negotiation.rs +++ b/russh/src/negotiation.rs @@ -102,9 +102,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, @@ -140,14 +138,12 @@ impl Named for () { } use russh_keys::key::ED25519; -#[cfg(feature = "openssl")] use russh_keys::key::SSH_RSA; impl Named for PublicKey { fn name(&self) -> &'static str { match self { PublicKey::Ed25519(_) => ED25519.0, - #[cfg(feature = "openssl")] PublicKey::RSA { .. } => SSH_RSA.0, PublicKey::EC { ref key } => key.algorithm(), } @@ -158,7 +154,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, KeyPair::EC { ref key } => key.algorithm(), }