From 031b4be14e63b063e8bd70412ead8af9d44c426b Mon Sep 17 00:00:00 2001 From: Adam Wick Date: Sun, 14 Apr 2019 22:12:36 -0700 Subject: [PATCH] Full SSH support for RSA. --- src/rsa/mod.rs | 110 ++++++++++++++++++++++++++++ src/rsa/private.rs | 2 + src/rsa/public.rs | 18 ++++- src/ssh/mod.rs | 39 ++++------ src/ssh/rsa.rs | 174 +++++++++++++++++++++++++++++++++++++++------ 5 files changed, 297 insertions(+), 46 deletions(-) diff --git a/src/rsa/mod.rs b/src/rsa/mod.rs index c2bf967..0c4bf90 100644 --- a/src/rsa/mod.rs +++ b/src/rsa/mod.rs @@ -40,6 +40,8 @@ use cryptonum::signed::{EGCD,ModInv}; use cryptonum::unsigned::{CryptoNum,PrimeGen}; use cryptonum::unsigned::{U256,U512,U1024,U1536,U2048,U3072,U4096,U7680,U8192,U15360}; use rand::RngCore; +#[cfg(test)] +use std::fmt; use std::ops::Sub; use super::KeyPair; @@ -60,6 +62,114 @@ pub struct RSAKeyPair { pub private: RSAPrivateKey } +#[derive(PartialEq)] +pub enum RSAPair { + R512(RSAPublicKey, RSAPrivateKey), + R1024(RSAPublicKey, RSAPrivateKey), + R2048(RSAPublicKey, RSAPrivateKey), + R3072(RSAPublicKey, RSAPrivateKey), + R4096(RSAPublicKey, RSAPrivateKey), + R8192(RSAPublicKey, RSAPrivateKey), + R15360(RSAPublicKey, RSAPrivateKey), +} + +#[cfg(test)] +impl fmt::Debug for RSAPair { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> + { + match self { + RSAPair::R512(_,_) => f.write_str("512-bit RSA key pair"), + RSAPair::R1024(_,_) => f.write_str("1024-bit RSA key pair"), + RSAPair::R2048(_,_) => f.write_str("2048-bit RSA key pair"), + RSAPair::R3072(_,_) => f.write_str("3072-bit RSA key pair"), + RSAPair::R4096(_,_) => f.write_str("4096-bit RSA key pair"), + RSAPair::R8192(_,_) => f.write_str("8192-bit RSA key pair"), + RSAPair::R15360(_,_) => f.write_str("15360-bit RSA key pair"), + } + } +} + +impl KeyPair for RSAPair { + type Public = RSAPublic; + type Private = RSAPrivate; + + fn new(pu: RSAPublic, pr: RSAPrivate) -> RSAPair + { + match (pu, pr) { + (RSAPublic::Key512(pbl), RSAPrivate::Key512(prv)) => + RSAPair::R512(pbl, prv), + (RSAPublic::Key1024(pbl), RSAPrivate::Key1024(prv)) => + RSAPair::R1024(pbl, prv), + (RSAPublic::Key2048(pbl), RSAPrivate::Key2048(prv)) => + RSAPair::R2048(pbl, prv), + (RSAPublic::Key3072(pbl), RSAPrivate::Key3072(prv)) => + RSAPair::R3072(pbl, prv), + (RSAPublic::Key4096(pbl), RSAPrivate::Key4096(prv)) => + RSAPair::R4096(pbl, prv), + (RSAPublic::Key8192(pbl), RSAPrivate::Key8192(prv)) => + RSAPair::R8192(pbl, prv), + (RSAPublic::Key15360(pbl), RSAPrivate::Key15360(prv)) => + RSAPair::R15360(pbl, prv), + _ => + panic!("Unmatched public/private arguments to RSAPair::new()") + } + } +} + +impl RSAPair { + pub fn sign(&self, signhash: &SigningHash, msg: &[u8]) -> Vec + { + match self { + RSAPair::R512(_,prv) => prv.sign(signhash, msg), + RSAPair::R1024(_,prv) => prv.sign(signhash, msg), + RSAPair::R2048(_,prv) => prv.sign(signhash, msg), + RSAPair::R3072(_,prv) => prv.sign(signhash, msg), + RSAPair::R4096(_,prv) => prv.sign(signhash, msg), + RSAPair::R8192(_,prv) => prv.sign(signhash, msg), + RSAPair::R15360(_,prv) => prv.sign(signhash, msg), + } + } + + pub fn verify(&self, signhash: &SigningHash, msg: &[u8], sig: &[u8]) -> bool + { + match self { + RSAPair::R512(pbl,_) => pbl.verify(signhash, msg, sig), + RSAPair::R1024(pbl,_) => pbl.verify(signhash, msg, sig), + RSAPair::R2048(pbl,_) => pbl.verify(signhash, msg, sig), + RSAPair::R3072(pbl,_) => pbl.verify(signhash, msg, sig), + RSAPair::R4096(pbl,_) => pbl.verify(signhash, msg, sig), + RSAPair::R8192(pbl,_) => pbl.verify(signhash, msg, sig), + RSAPair::R15360(pbl,_) => pbl.verify(signhash, msg, sig), + } + } + + pub fn public(&self) -> RSAPublic + { + match self { + &RSAPair::R512(ref pbl,_) => RSAPublic::Key512(pbl.clone()), + &RSAPair::R1024(ref pbl,_) => RSAPublic::Key1024(pbl.clone()), + &RSAPair::R2048(ref pbl,_) => RSAPublic::Key2048(pbl.clone()), + &RSAPair::R3072(ref pbl,_) => RSAPublic::Key3072(pbl.clone()), + &RSAPair::R4096(ref pbl,_) => RSAPublic::Key4096(pbl.clone()), + &RSAPair::R8192(ref pbl,_) => RSAPublic::Key8192(pbl.clone()), + &RSAPair::R15360(ref pbl,_) => RSAPublic::Key15360(pbl.clone()), + } + } + + pub fn private(&self) -> RSAPrivate + { + match self { + &RSAPair::R512(_,ref prv) => RSAPrivate::Key512(prv.clone()), + &RSAPair::R1024(_,ref prv) => RSAPrivate::Key1024(prv.clone()), + &RSAPair::R2048(_,ref prv) => RSAPrivate::Key2048(prv.clone()), + &RSAPair::R3072(_,ref prv) => RSAPrivate::Key3072(prv.clone()), + &RSAPair::R4096(_,ref prv) => RSAPrivate::Key4096(prv.clone()), + &RSAPair::R8192(_,ref prv) => RSAPrivate::Key8192(prv.clone()), + &RSAPair::R15360(_,ref prv) => RSAPrivate::Key15360(prv.clone()), + } + } +} + macro_rules! generate_rsa_pair { ($uint: ident, $half: ident, $iterations: expr) => { diff --git a/src/rsa/private.rs b/src/rsa/private.rs index 57dcd75..0a8a872 100644 --- a/src/rsa/private.rs +++ b/src/rsa/private.rs @@ -5,12 +5,14 @@ use rsa::errors::RSAError; use rsa::oaep::OAEPParams; use rsa::signing_hashes::SigningHash; +#[derive(Clone,PartialEq)] pub struct RSAPrivateKey { pub(crate) nu: R::Barrett, pub(crate) d: R } +#[derive(Clone,PartialEq)] pub enum RSAPrivate { Key512(RSAPrivateKey), Key1024(RSAPrivateKey), diff --git a/src/rsa/public.rs b/src/rsa/public.rs index 528fd01..f002353 100644 --- a/src/rsa/public.rs +++ b/src/rsa/public.rs @@ -12,13 +12,14 @@ use simple_asn1::{ASN1Block,ASN1DecodeErr,ASN1EncodeErr, use std::fmt; use utils::TranslateNums; -#[derive(PartialEq)] +#[derive(Clone,PartialEq)] pub struct RSAPublicKey { pub(crate) n: R, pub(crate) nu: R::Barrett, pub(crate) e: R } +#[derive(Clone,PartialEq)] pub enum RSAPublic { Key512( RSAPublicKey), Key1024( RSAPublicKey), @@ -135,6 +136,21 @@ impl ToASN1 for RSAPublic { } } +#[cfg(test)] +impl fmt::Debug for RSAPublic { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + RSAPublic::Key512(x) => write!(fmt, "RSA:{:?}", x), + RSAPublic::Key1024(x) => write!(fmt, "RSA:{:?}", x), + RSAPublic::Key2048(x) => write!(fmt, "RSA:{:?}", x), + RSAPublic::Key3072(x) => write!(fmt, "RSA:{:?}", x), + RSAPublic::Key4096(x) => write!(fmt, "RSA:{:?}", x), + RSAPublic::Key8192(x) => write!(fmt, "RSA:{:?}", x), + RSAPublic::Key15360(x) => write!(fmt, "RSA:{:?}", x) + } + } +} + macro_rules! generate_rsa_public { ($num: ident, $bar: ident, $var: ident, $size: expr) => { diff --git a/src/ssh/mod.rs b/src/ssh/mod.rs index 131b0e7..2220d7c 100644 --- a/src/ssh/mod.rs +++ b/src/ssh/mod.rs @@ -5,7 +5,7 @@ mod rsa; pub use self::errors::{SSHKeyParseError,SSHKeyRenderError}; -use base64::{decode,encode}; +use base64::decode; use self::frame::*; use std::fs::File; use std::io::{Cursor,Read,Write}; @@ -137,17 +137,12 @@ pub fn write_ssh_keyfile(path: P, x: &KP, comment: &str) -> Result<(),SSHK file.write_all(&bytes)?; file.sync_all()?; Ok(()) - } - - -#[cfg(test)] -use cryptonum::unsigned::{U1024}; #[cfg(test)] use dsa::{DSAKeyPair,DSAPublicKey,L1024N160}; #[cfg(test)] -use rsa::{RSAKeyPair,RSAPublicKey,SIGNING_HASH_SHA256}; +use rsa::{RSAPair,RSAPublic,SIGNING_HASH_SHA256}; #[cfg(test)] use sha2::Sha256; @@ -210,41 +205,37 @@ fn read_dsa_examples() { #[cfg(test)] #[test] fn read_rsa_examples() { - let test_files = ["rsa1024-1", "rsa1024-2", "rsa1024-3"]; + let test_files = ["rsa1024-1", "rsa1024-2", "rsa1024-3", + "rsa2048-1", "rsa2048-2", "rsa2048-3", + "rsa3072-1", "rsa3072-2", "rsa3072-3", + "rsa4096-1", "rsa4096-2", "rsa4096-3", + "rsa8192-1", "rsa8192-2", "rsa8192-3"]; for file in test_files.iter() { let path = format!("testdata/ssh/{}",file); - let mkeypair = load_ssh_keyfile(path); + let mkeypair = load_ssh_keyfile::(path); match mkeypair { Err(e) => assert!(false, format!("reading error: {:?}", e)), Ok((keypair, comment)) => { let buffer = [0,1,2,3,4,6,2]; - let _ : RSAKeyPair = keypair; - let sig = keypair.private.sign(&SIGNING_HASH_SHA256, &buffer); - assert!(keypair.public.verify(&SIGNING_HASH_SHA256, &buffer, &sig)); - let buffer2 = [0,1,2,3,4,6,5]; - assert!(!keypair.public.verify(&SIGNING_HASH_SHA256, &buffer2, &sig)); + let sig = keypair.sign(&SIGNING_HASH_SHA256, &buffer); + assert!(keypair.verify(&SIGNING_HASH_SHA256, &buffer, &sig)); match encode_ssh(&keypair, &comment) { Err(e2) => assert!(false, format!("render error: {:?}", e2)), Ok(encodedstr) => { match decode_ssh(&encodedstr) { Err(e3) => assert!(false, format!("reparse error: {:?}", e3)), Ok((keypair2,comment2)) => { - let _ : RSAKeyPair = keypair2; - assert_eq!(keypair.public.n,keypair2.public.n,"failed to reparse key pair (n)"); - assert_eq!(keypair.public.e,keypair2.public.e,"failed to reparse key pair (e)"); - assert_eq!(keypair.private.nu,keypair2.private.nu,"failed to reparse key pair (n)"); - assert_eq!(keypair.private.d,keypair2.private.d,"failed to reparse key pair (d)"); + assert_eq!(keypair,keypair2,"failed to reparse key pair"); assert_eq!(comment,comment2,"failed to reparse comment"); let ppath = format!("testdata/ssh/{}.pub",file); - match load_ssh_pubkeys::,String>(ppath) { + match load_ssh_pubkeys::(ppath) { Err(e4) => assert!(false, format!("pubkey error: {:?}", e4)), Ok(pubkeys) => { - let _ : Vec<(RSAPublicKey,String)> = pubkeys; + let _ : Vec<(RSAPublic,String)> = pubkeys; for (pubkey, comment3) in pubkeys { - assert_eq!(pubkey.n, keypair.public.n, "public key check (n)"); - assert_eq!(pubkey.e, keypair.public.e, "public key check (e)"); - assert_eq!(comment, comment3, "public key check comment") + assert_eq!(pubkey, keypair.public(), "public key check"); + assert_eq!(comment, comment3, "public key check comment"); } } } diff --git a/src/ssh/rsa.rs b/src/ssh/rsa.rs index 4e3f9cd..90d3d1b 100644 --- a/src/ssh/rsa.rs +++ b/src/ssh/rsa.rs @@ -1,11 +1,11 @@ use cryptonum::unsigned::*; -use rsa::{RSAKeyPair,RSAPublicKey,RSAPrivateKey}; +use rsa::{RSAPair,RSAPublic,RSAPublicKey,RSAPrivate,RSAPrivateKey}; use std::io::{Read,Write}; use ssh::errors::{SSHKeyParseError,SSHKeyRenderError}; use ssh::frame::*; use ssh::SSHKey; -impl SSHKey for RSAKeyPair { +impl SSHKey for RSAPair { fn valid_keytype(s: &str) -> bool { (s == "ssh-rsa") || (s == "rsa") } @@ -16,9 +16,45 @@ impl SSHKey for RSAKeyPair { if !Self::valid_keytype(&pubkey_type) { return Err(SSHKeyParseError::UnknownKeyType(pubkey_type)); } - let e = parse_openssh_number(inp)?; - let n = parse_openssh_number(inp)?; - Ok(RSAPublicKey::::new(n, e)) + // this peaks a little under the cover a bit (it'd be nice to pretend + // that we didn't know the number format was the same as the buffer + // one), but we need to infer what kind of key this is, and this appears + // to be the easiest / fastest way. + let mut ebuf = parse_openssh_buffer(inp)?; + let mut nbuf = parse_openssh_buffer(inp)?; + + while ebuf[0] == 0 { ebuf.remove(0); } + while nbuf[0] == 0 { nbuf.remove(0); } + + if nbuf.len() > (8192 / 8) { + let e = U15360::from_bytes(&ebuf); + let n = U15360::from_bytes(&nbuf); + Ok(RSAPublic::Key15360(RSAPublicKey::::new(n, e))) + } else if nbuf.len() > (4096 / 8) { + let e = U8192::from_bytes(&ebuf); + let n = U8192::from_bytes(&nbuf); + Ok(RSAPublic::Key8192(RSAPublicKey::::new(n, e))) + } else if nbuf.len() > (3072 / 8) { + let e = U4096::from_bytes(&ebuf); + let n = U4096::from_bytes(&nbuf); + Ok(RSAPublic::Key4096(RSAPublicKey::::new(n, e))) + } else if nbuf.len() > (2048 / 8) { + let e = U3072::from_bytes(&ebuf); + let n = U3072::from_bytes(&nbuf); + Ok(RSAPublic::Key3072(RSAPublicKey::::new(n, e))) + } else if nbuf.len() > (1024 / 8) { + let e = U2048::from_bytes(&ebuf); + let n = U2048::from_bytes(&nbuf); + Ok(RSAPublic::Key2048(RSAPublicKey::::new(n, e))) + } else if nbuf.len() > (512 / 8) { + let e = U1024::from_bytes(&ebuf); + let n = U1024::from_bytes(&nbuf); + Ok(RSAPublic::Key1024(RSAPublicKey::::new(n, e))) + } else { + let e = U512::from_bytes(&ebuf); + let n = U512::from_bytes(&nbuf); + Ok(RSAPublic::Key512(RSAPublicKey::::new(n, e))) + } } fn parse_ssh_private_info(inp: &mut I) -> Result<(Self::Private,String),SSHKeyParseError> @@ -32,27 +68,88 @@ impl SSHKey for RSAKeyPair { if !Self::valid_keytype(&privkey_type) { return Err(SSHKeyParseError::InconsistentKeyTypes("ssh-rsa".to_string(), privkey_type)); } - let n = parse_openssh_number(inp)?; - let _e: U1024 = parse_openssh_number(inp)?; - let d = parse_openssh_number(inp)?; - let _iqmp: U1024 = parse_openssh_number(inp)?; - let _p: U1024 = parse_openssh_number(inp)?; - let _q: U1024 = parse_openssh_number(inp)?; - let comment = parse_openssh_string(inp)?; + + // See the comment in the public key section. + let mut nbuf = parse_openssh_buffer(inp)?; + let _ebuf = parse_openssh_buffer(inp)?; + let mut dbuf = parse_openssh_buffer(inp)?; + let _iqmp = parse_openssh_buffer(inp)?; + let _pbuf = parse_openssh_buffer(inp)?; + let _qbuf = parse_openssh_buffer(inp)?; + let comment = parse_openssh_string(inp)?; for (idx,byte) in inp.bytes().enumerate() { if ((idx+1) as u8) != byte? { return Err(SSHKeyParseError::InvalidPadding); } } - Ok((RSAPrivateKey::::new(n, d), comment)) + while dbuf[0] == 0 { dbuf.remove(0); } + while nbuf[0] == 0 { nbuf.remove(0); } + + if nbuf.len() > (8192 / 8) { + let d = U15360::from_bytes(&dbuf); + let n = U15360::from_bytes(&nbuf); + Ok((RSAPrivate::Key15360(RSAPrivateKey::::new(n, d)), comment)) + } else if nbuf.len() > (4096 / 8) { + let d = U8192::from_bytes(&dbuf); + let n = U8192::from_bytes(&nbuf); + Ok((RSAPrivate::Key8192(RSAPrivateKey::::new(n, d)), comment)) + } else if nbuf.len() > (3072 / 8) { + let d = U4096::from_bytes(&dbuf); + let n = U4096::from_bytes(&nbuf); + Ok((RSAPrivate::Key4096(RSAPrivateKey::::new(n, d)), comment)) + } else if nbuf.len() > (2048 / 8) { + let d = U3072::from_bytes(&dbuf); + let n = U3072::from_bytes(&nbuf); + Ok((RSAPrivate::Key3072(RSAPrivateKey::::new(n, d)), comment)) + } else if nbuf.len() > (1024 / 8) { + let d = U2048::from_bytes(&dbuf); + let n = U2048::from_bytes(&nbuf); + Ok((RSAPrivate::Key2048(RSAPrivateKey::::new(n, d)), comment)) + } else if nbuf.len() > (512 / 8) { + let d = U1024::from_bytes(&dbuf); + let n = U1024::from_bytes(&nbuf); + Ok((RSAPrivate::Key1024(RSAPrivateKey::::new(n, d)), comment)) + } else { + let d = U512::from_bytes(&dbuf); + let n = U512::from_bytes(&nbuf); + Ok((RSAPrivate::Key512(RSAPrivateKey::::new(n, d)), comment)) + } } fn render_ssh_public_info(&self, out: &mut O) -> Result<(),SSHKeyRenderError> { render_openssh_string(out, "ssh-rsa")?; - render_openssh_number(out, &self.public.e)?; - render_openssh_number(out, &self.public.n)?; + match self { + RSAPair::R512(pbl,_) => { + render_openssh_number(out, &pbl.e)?; + render_openssh_number(out, &pbl.n)?; + } + RSAPair::R1024(pbl,_) => { + render_openssh_number(out, &pbl.e)?; + render_openssh_number(out, &pbl.n)?; + } + RSAPair::R2048(pbl,_) => { + render_openssh_number(out, &pbl.e)?; + render_openssh_number(out, &pbl.n)?; + } + RSAPair::R3072(pbl,_) => { + render_openssh_number(out, &pbl.e)?; + render_openssh_number(out, &pbl.n)?; + } + RSAPair::R4096(pbl,_) => { + render_openssh_number(out, &pbl.e)?; + render_openssh_number(out, &pbl.n)?; + } + RSAPair::R8192(pbl,_) => { + render_openssh_number(out, &pbl.e)?; + render_openssh_number(out, &pbl.n)?; + } + RSAPair::R15360(pbl,_) => { + render_openssh_number(out, &pbl.e)?; + render_openssh_number(out, &pbl.n)?; + } + } Ok(()) } @@ -61,12 +158,47 @@ impl SSHKey for RSAKeyPair { render_openssh_u32(out, 0xDEADBEEF)?; // FIXME: Any reason for this to be random? render_openssh_u32(out, 0xDEADBEEF)?; // ditto render_openssh_string(out, "ssh-rsa")?; - render_openssh_number(out, &self.public.n)?; - render_openssh_number(out, &self.public.e)?; - render_openssh_number(out, &self.private.d)?; - render_openssh_number(out, &self.private.d)?; - render_openssh_number(out, &self.private.d)?; - render_openssh_number(out, &self.private.d)?; + match self { + RSAPair::R512(pbl,prv) => { + render_openssh_number(out, &pbl.n)?; + render_openssh_number(out, &pbl.e)?; + render_openssh_number(out, &prv.d)?; + } + RSAPair::R1024(pbl,prv) => { + render_openssh_number(out, &pbl.n)?; + render_openssh_number(out, &pbl.e)?; + render_openssh_number(out, &prv.d)?; + } + RSAPair::R2048(pbl,prv) => { + render_openssh_number(out, &pbl.n)?; + render_openssh_number(out, &pbl.e)?; + render_openssh_number(out, &prv.d)?; + } + RSAPair::R3072(pbl,prv) => { + render_openssh_number(out, &pbl.n)?; + render_openssh_number(out, &pbl.e)?; + render_openssh_number(out, &prv.d)?; + } + RSAPair::R4096(pbl,prv) => { + render_openssh_number(out, &pbl.n)?; + render_openssh_number(out, &pbl.e)?; + render_openssh_number(out, &prv.d)?; + } + RSAPair::R8192(pbl,prv) => { + render_openssh_number(out, &pbl.n)?; + render_openssh_number(out, &pbl.e)?; + render_openssh_number(out, &prv.d)?; + } + RSAPair::R15360(pbl,prv) => { + render_openssh_number(out, &pbl.n)?; + render_openssh_number(out, &pbl.e)?; + render_openssh_number(out, &prv.d)?; + } + } + /* iqmp */ render_openssh_buffer(out, &vec![])?; + /* p */ render_openssh_buffer(out, &vec![])?; + /* q */ render_openssh_buffer(out, &vec![])?; + render_openssh_string(out, comment)?; // add some padding (not quite sure why) let mut i = comment.len();