Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add a non OpenSSL RSA impl #252

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion russh-keys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,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"
openssl = { version = "0.10", optional = true }
rsa = { version = "0.9.6", features = ["sha1", "sha2"]}
p256 = "0.13"
p384 = "0.13"
p521 = "0.13"
Expand All @@ -62,7 +64,14 @@ serde = { version = "1.0", features = ["derive"] }
sha1 = "0.10"
sha2 = "0.10"
thiserror = "1.0"
tokio = { version = "1.17.0", features = ["io-util", "rt-multi-thread", "time", "net"] }
tokio = { version = "1.36.0", features = [
"io-util",
"rt-multi-thread",
"time",
"net",
"macros",
"process"
] }
tokio-stream = { version = "0.1", features = ["net"] }
typenum = "1.17"
yasna = { version = "0.5.0", features = ["bit-vec", "num-bigint"] }
Expand Down
60 changes: 55 additions & 5 deletions russh-keys/src/agent/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use std::convert::TryFrom;

use byteorder::{BigEndian, ByteOrder};
use log::{debug, info};
#[cfg(not(feature = "openssl"))]
use rsa::traits::{PrivateKeyParts, PublicKeyParts};
use russh_cryptovec::CryptoVec;
use tokio;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
Expand Down Expand Up @@ -140,6 +142,32 @@ impl<S: AsyncRead + AsyncWrite + Unpin> AgentClient<S> {
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, .. } => {
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
.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.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"");
}

key::KeyPair::EC { ref key } => {
self.buf.extend_ssh_string(key.algorithm().as_bytes());
self.buf.extend_ssh_string(key.ident().as_bytes());
Expand Down Expand Up @@ -282,6 +310,20 @@ impl<S: AsyncRead + AsyncWrite + Unpin> AgentClient<S> {
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()?)?,
)),
Expand Down Expand Up @@ -351,7 +393,6 @@ impl<S: AsyncRead + AsyncWrite + Unpin> AgentClient<S> {
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,
Expand Down Expand Up @@ -534,14 +575,23 @@ impl<S: AsyncRead + AsyncWrite + Unpin> AgentClient<S> {

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);
Expand Down
35 changes: 34 additions & 1 deletion russh-keys/src/agent/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -321,6 +322,38 @@ impl<S: AsyncRead + AsyncWrite + Send + Unpin + 'static, A: Agent + Send + Sync
},
)
}
#[cfg(not(feature = "openssl"))]
b"ssh-rsa" => {
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))?;
Expand Down
72 changes: 27 additions & 45 deletions russh-keys/src/format/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<Format> {
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<key::KeyPair, Error> {
let mut format = None;
let secret = {
let secret: String = {
let mut started = false;
let mut sec = String::new();
for l in secret.lines() {
Expand All @@ -57,41 +62,18 @@ pub fn decode_secret_key(secret: &str, password: Option<&str>) -> Result<key::Ke
if l.chars().all(is_base64_char) {
sec.push_str(l)
} else if l.starts_with(AES_128_CBC) {
#[cfg(feature = "openssl")]
{
let iv_: Vec<u8> = 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<u8> = 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
Expand All @@ -100,9 +82,7 @@ pub fn decode_secret_key(secret: &str, password: Option<&str>) -> Result<key::Ke
let secret = BASE64_MIME.decode(secret.as_bytes())?;
match format {
Some(Format::Openssh) => 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()))
Expand Down Expand Up @@ -132,10 +112,12 @@ pub fn encode_pkcs8_pem_encrypted<W: Write>(
Ok(())
}

#[cfg(feature = "openssl")]
fn decode_rsa(secret: &[u8]) -> Result<key::KeyPair, Error> {
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,
})
}
25 changes: 23 additions & 2 deletions russh-keys/src/format/openssh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{ec, key, Error, KEYTYPE_ED25519, KEYTYPE_RSA};

Expand Down Expand Up @@ -48,7 +52,7 @@ pub fn decode_openssh(secret: &[u8], password: Option<&str>) -> Result<key::KeyP
seckey.get(..32).ok_or(Error::KeyIsCorrupt)?,
)?;
return Ok(key::KeyPair::Ed25519(secret));
} else if key_type == KEYTYPE_RSA && cfg!(feature = "openssl") {
} else if key_type == KEYTYPE_RSA {
#[cfg(feature = "openssl")]
{
let n = BigNum::from_slice(position.read_string()?)?;
Expand Down Expand Up @@ -79,6 +83,23 @@ pub fn decode_openssh(secret: &[u8], password: Option<&str>) -> Result<key::KeyP
hash: key::SignatureHash::SHA2_512,
});
}

#[cfg(not(feature = "openssl"))]
{
let n = BigUint::from_bytes_be(position.read_string()?);
let e = BigUint::from_bytes_be(position.read_string()?);
let d = BigUint::from_bytes_be(position.read_string()?);
let _ = position.read_string()?;
let p = BigUint::from_bytes_be(position.read_string()?);
let q = BigUint::from_bytes_be(position.read_string()?);

let key = RsaPrivateKey::from_components(n, e, d, vec![p, q])?;
return Ok(key::KeyPair::RSA {
key,
hash: key::SignatureHash::SHA2_512,
});
}

} else if key_type == crate::KEYTYPE_ECDSA_SHA2_NISTP256
|| key_type == crate::KEYTYPE_ECDSA_SHA2_NISTP384
|| key_type == crate::KEYTYPE_ECDSA_SHA2_NISTP521
Expand Down
1 change: 0 additions & 1 deletion russh-keys/src/format/pkcs5.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use crate::{key, Error};

/// Decode a secret key in the PKCS#5 format, possibly deciphering it
/// using the supplied password.
#[cfg(feature = "openssl")]
pub fn decode_pkcs5(
secret: &[u8],
password: Option<&str>,
Expand Down
Loading