diff --git a/src/dsa/mod.rs b/src/dsa/mod.rs index 24d1c48..54ae7e5 100644 --- a/src/dsa/mod.rs +++ b/src/dsa/mod.rs @@ -13,6 +13,7 @@ pub use self::public::*; use cryptonum::unsigned::*; use rand::Rng; use rand::distributions::Standard; +use super::KeyPair; pub struct DSAKeyPair { @@ -20,6 +21,17 @@ pub struct DSAKeyPair pub public: DSAPubKey

} +impl KeyPair for DSAKeyPair

+{ + type Private = DSAPrivKey

; + type Public = DSAPubKey

; + + fn new(public: DSAPubKey

, private: DSAPrivKey

) -> DSAKeyPair

+ { + DSAKeyPair{ private, public } + } +} + pub trait DSAKeyGeneration { type Params; diff --git a/src/lib.rs b/src/lib.rs index 0138cec..378f34e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,13 @@ pub mod ssh; /// used by TLS and others. pub mod x509; +pub trait KeyPair { + type Public; + type Private; + + fn new(pbl: Self::Public, prv: Self::Private) -> Self; +} + #[cfg(test)] mod testing; mod utils; diff --git a/src/ssh/errors.rs b/src/ssh/errors.rs index 52d6b67..3e649d7 100644 --- a/src/ssh/errors.rs +++ b/src/ssh/errors.rs @@ -1,11 +1,9 @@ use base64::DecodeError; -use simple_asn1::ASN1DecodeErr; use std::io; #[derive(Debug)] pub enum SSHKeyParseError { - ASN1Error(ASN1DecodeErr), DecodeError(DecodeError), IOError(io::Error), NoBeginBannerFound, NoEndBannerFound, @@ -23,13 +21,6 @@ pub enum SSHKeyParseError InvalidPadding } -impl From for SSHKeyParseError { - fn from(e: ASN1DecodeErr) -> SSHKeyParseError { - println!("asn1 error: {:?}", e); - SSHKeyParseError::ASN1Error(e) - } -} - impl From for SSHKeyParseError { fn from(e: DecodeError) -> SSHKeyParseError { SSHKeyParseError::DecodeError(e) diff --git a/src/ssh/mod.rs b/src/ssh/mod.rs index af445e2..ab5fca5 100644 --- a/src/ssh/mod.rs +++ b/src/ssh/mod.rs @@ -9,146 +9,170 @@ use self::frame::*; use std::fs::File; use std::io::{Cursor,Read,Write}; use std::path::Path; +use super::KeyPair; -pub trait SSHKey: Sized { - fn decode_ssh_private_key(x: &str) -> Result<(Self,String),SSHKeyParseError>; - fn read_ssh_private_key_file>(path: P) -> Result<(Self,String),SSHKeyParseError> { - let mut file = File::open(path)?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - Self::decode_ssh_private_key(&contents) +pub trait SSHKey: Sized + KeyPair { + fn parse_ssh_public_info(inp: &mut I) -> Result; + fn parse_ssh_private_info(inp: &mut I) -> Result<(Self::Private,String),SSHKeyParseError>; + + fn render_ssh_public_info(&self, out: &mut O) -> Result<(),SSHKeyRenderError>; + fn render_ssh_private_info(&self, out: &mut O, comment: &str) -> Result<(),SSHKeyRenderError>; +} + +pub fn decode_ssh(x: &str) -> Result<(KP, String),SSHKeyParseError> +{ + let bytes = parse_ssh_private_key_data(x)?; + let data_size = bytes.len() as u64; + let mut byte_cursor = Cursor::new(bytes); + + parse_openssh_header(&mut byte_cursor)?; + let ciphername = parse_openssh_string(&mut byte_cursor)?; + if ciphername != "none" { + return Err(SSHKeyParseError::UnknownKeyCipher(ciphername)); + } + let kdfname = parse_openssh_string(&mut byte_cursor)?; + if kdfname != "none" { + return Err(SSHKeyParseError::UnknownKeyCipher(kdfname)); + } + let kdfoptions = parse_openssh_buffer(&mut byte_cursor)?; + if kdfoptions.len() > 0 { + return Err(SSHKeyParseError::UnexpectedKDFOptions); + } + let numkeys = parse_openssh_u32(&mut byte_cursor)?; + if numkeys != 1 { + return Err(SSHKeyParseError::InvalidNumberOfKeys(numkeys)); + } + let pubkey0 = parse_openssh_buffer(&mut byte_cursor)?; + let privkeys = parse_openssh_buffer(&mut byte_cursor)?; + if byte_cursor.position() < data_size { + return Err(SSHKeyParseError::UnknownTrailingData); } - fn encode_ssh_private_key(&self, comment: &str) -> Result; - fn write_ssh_private_key_file>(&self, path: P, comment: &str) -> Result<(),SSHKeyRenderError> { - let mut file = File::create(path)?; - let contents = self.encode_ssh_private_key(comment)?; - let bytes = contents.into_bytes(); - file.write_all(&bytes)?; - file.sync_all()?; - Ok(()) - } + let mut pubcursor = Cursor::new(pubkey0); + let public = KP::parse_ssh_public_info(&mut pubcursor)?; + let mut privcursor = Cursor::new(privkeys); + let (private, comment) = KP::parse_ssh_private_info(&mut privcursor)?; + + Ok((KP::new(public, private), comment)) +} + +pub fn load_ssh_keyfile(path: P) -> Result<(KP, String),SSHKeyParseError> + where + KP: SSHKey, + P: AsRef +{ + let mut file = File::open(path)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + decode_ssh(&contents) +} + +pub fn encode_ssh(x: &KP, comment: &str) -> Result +{ + let mut pubkeybin = Vec::with_capacity(8192); + let mut privkeybin = Vec::with_capacity(8192); + let mut binary = Vec::with_capacity(16384); + + x.render_ssh_public_info(&mut pubkeybin)?; + x.render_ssh_private_info(&mut privkeybin, comment)?; + render_openssh_header(&mut binary)?; + render_openssh_string(&mut binary, "none")?; // ciphername + render_openssh_string(&mut binary, "none")?; // kdfname + render_openssh_buffer(&mut binary, &[])?; // kdfoptions + render_openssh_u32(&mut binary, 1)?; // numkeys + render_openssh_buffer(&mut binary, &pubkeybin)?; + render_openssh_buffer(&mut binary, &privkeybin)?; + Ok(render_ssh_private_key_data(&binary)) +} + +pub fn write_ssh_keyfile(path: P, x: &KP, comment: &str) -> Result<(),SSHKeyRenderError> + where + KP: SSHKey, + P: AsRef +{ + let mut file = File::create(path)?; + let contents = encode_ssh(x, comment)?; + let bytes = contents.into_bytes(); + file.write_all(&bytes)?; + file.sync_all()?; + Ok(()) + } impl SSHKey for DSAKeyPair { - fn decode_ssh_private_key(x: &str) -> Result<(Self,String),SSHKeyParseError> + fn parse_ssh_public_info(inp: &mut I) -> Result { - let bytes = parse_ssh_private_key_data(x)?; - let data_size = bytes.len() as u64; - let mut byte_cursor = Cursor::new(bytes); - - parse_openssh_header(&mut byte_cursor)?; - let ciphername = parse_openssh_string(&mut byte_cursor)?; - if ciphername != "none" { - return Err(SSHKeyParseError::UnknownKeyCipher(ciphername)); - } - let kdfname = parse_openssh_string(&mut byte_cursor)?; - if kdfname != "none" { - return Err(SSHKeyParseError::UnknownKeyCipher(kdfname)); - } - let kdfoptions = parse_openssh_buffer(&mut byte_cursor)?; - if kdfoptions.len() > 0 { - return Err(SSHKeyParseError::UnexpectedKDFOptions); - } - let numkeys = parse_openssh_u32(&mut byte_cursor)?; - if numkeys != 1 { - return Err(SSHKeyParseError::InvalidNumberOfKeys(numkeys)); - } - let pubkey0 = parse_openssh_buffer(&mut byte_cursor)?; - let privkeys = parse_openssh_buffer(&mut byte_cursor)?; - if byte_cursor.position() < data_size { - return Err(SSHKeyParseError::UnknownTrailingData); - - } - // Now that we've sorted out the details at this level, - // see if we can decode the public key - let mut pubkey_cursor = Cursor::new(pubkey0); - let pubkey_type = parse_openssh_string(&mut pubkey_cursor)?; + let pubkey_type = parse_openssh_string(inp)?; if pubkey_type != "ssh-dss" { return Err(SSHKeyParseError::UnknownKeyType(pubkey_type)); } - - let pubp = parse_openssh_number(&mut pubkey_cursor)?; - let pubq = parse_openssh_number(&mut pubkey_cursor)?; - let pubg = parse_openssh_number(&mut pubkey_cursor)?; + let pubp = parse_openssh_number(inp)?; + let pubq = parse_openssh_number(inp)?; + let pubg = parse_openssh_number(inp)?; let pubparams = L1024N160::new(pubp, pubg, pubq); - let puby: U1024 = parse_openssh_number(&mut pubkey_cursor)?; - let pubkey = DSAPubKey::::new(pubparams.clone(), puby.clone()); + let puby: U1024 = parse_openssh_number(inp)?; + for _ in inp.bytes() { return Err(SSHKeyParseError::UnknownTrailingData); } + Ok(DSAPubKey::::new(pubparams.clone(), puby.clone())) + } - // And now we can look at the private key! - let mut privkey_cursor = Cursor::new(privkeys); - let check1 = parse_openssh_u32(&mut privkey_cursor)?; - let check2 = parse_openssh_u32(&mut privkey_cursor)?; + fn parse_ssh_private_info(inp: &mut I) -> Result<(Self::Private,String),SSHKeyParseError> + { + let check1 = parse_openssh_u32(inp)?; + let check2 = parse_openssh_u32(inp)?; if check1 != check2 { return Err(SSHKeyParseError::PrivateKeyCorruption); } - - let privkey_type = parse_openssh_string(&mut privkey_cursor)?; - if privkey_type != pubkey_type { - return Err(SSHKeyParseError::InconsistentKeyTypes(pubkey_type, privkey_type)); + let privkey_type = parse_openssh_string(inp)?; + if privkey_type != "ssh-dss" { + return Err(SSHKeyParseError::InconsistentKeyTypes("ssh-dss".to_string(), privkey_type)); } - - let privp = parse_openssh_number(&mut privkey_cursor)?; - let privq = parse_openssh_number(&mut privkey_cursor)?; - let privg = parse_openssh_number(&mut privkey_cursor)?; + let privp = parse_openssh_number(inp)?; + let privq = parse_openssh_number(inp)?; + let privg = parse_openssh_number(inp)?; let privparams = L1024N160::new(privp, privg, privq); - let privy = parse_openssh_number(&mut privkey_cursor)?; - let privx = parse_openssh_number(&mut privkey_cursor)?; - if (pubparams != privparams) || (puby != privy) { - return Err(SSHKeyParseError::InconsistentPublicKeyValue); - } + let _ = parse_openssh_buffer(inp)?; // a copy of y we don't need + let privx = parse_openssh_number(inp)?; - let privkey = DSAPrivKey::::new(pubparams, privx); - let comment = parse_openssh_string(&mut privkey_cursor)?; - for (idx,byte) in privkey_cursor.bytes().enumerate() { + let privkey = DSAPrivKey::::new(privparams, privx); + let comment = parse_openssh_string(inp)?; + for (idx,byte) in inp.bytes().enumerate() { if ((idx+1) as u8) != byte? { return Err(SSHKeyParseError::InvalidPadding); } } - let result = DSAKeyPair{ public: pubkey, private: privkey }; - Ok((result,comment)) + Ok((privkey,comment)) } - fn encode_ssh_private_key(&self, comment: &str) -> Result { - // render the public key - let mut pubkeybin = Vec::with_capacity(4096); - render_openssh_string(&mut pubkeybin, "ssh-dss")?; - render_openssh_number(&mut pubkeybin, &self.public.params.p)?; - render_openssh_number(&mut pubkeybin, &self.public.params.q)?; - render_openssh_number(&mut pubkeybin, &self.public.params.g)?; - render_openssh_number(&mut pubkeybin, &self.public.y)?; + fn render_ssh_public_info(&self, out: &mut O) -> Result<(),SSHKeyRenderError> + { + render_openssh_string(out, "ssh-dss")?; + render_openssh_number(out, &self.public.params.p)?; + render_openssh_number(out, &self.public.params.q)?; + render_openssh_number(out, &self.public.params.g)?; + render_openssh_number(out, &self.public.y) + } - // render the private key - let mut privkeybin = Vec::with_capacity(4096); - render_openssh_u32(&mut privkeybin, 0xDEADBEEF)?; // FIXME: Any reason for this to be random? - render_openssh_u32(&mut privkeybin, 0xDEADBEEF)?; // ditto - render_openssh_string(&mut privkeybin, "ssh-dss")?; - render_openssh_number(&mut privkeybin, &self.private.params.p)?; - render_openssh_number(&mut privkeybin, &self.private.params.q)?; - render_openssh_number(&mut privkeybin, &self.private.params.g)?; - render_openssh_number(&mut privkeybin, &self.public.y)?; - render_openssh_number(&mut privkeybin, &self.private.x)?; - render_openssh_string(&mut privkeybin, comment)?; + fn render_ssh_private_info(&self, out: &mut O, comment: &str) -> Result<(),SSHKeyRenderError> + { + render_openssh_u32(out, 0xDEADBEEF)?; // FIXME: Any reason for this to be random? + render_openssh_u32(out, 0xDEADBEEF)?; // ditto + render_openssh_string(out, "ssh-dss")?; + render_openssh_number(out, &self.private.params.p)?; + render_openssh_number(out, &self.private.params.q)?; + render_openssh_number(out, &self.private.params.g)?; + render_openssh_number(out, &self.public.y)?; + render_openssh_number(out, &self.private.x)?; + render_openssh_string(out, comment)?; // add some padding (not quite sure why) - let mut i = 1; - while (privkeybin.len() % 16) != 0 { - privkeybin.push(i); + let mut i = comment.len(); + while (i % 16) != 0 { + out.write(&[(i - comment.len() + 1) as u8])?; i += 1; } - - let mut binary = Vec::with_capacity(16384); - render_openssh_header(&mut binary)?; - render_openssh_string(&mut binary, "none")?; // ciphername - render_openssh_string(&mut binary, "none")?; // kdfname - render_openssh_buffer(&mut binary, &[])?; // kdfoptions - render_openssh_u32(&mut binary, 1)?; // numkeys - render_openssh_buffer(&mut binary, &pubkeybin)?; - render_openssh_buffer(&mut binary, &privkeybin)?; - - Ok(render_ssh_private_key_data(&binary)) - } + Ok(()) + } } #[cfg(test)] @@ -161,21 +185,23 @@ fn read_dsa_examples() { for file in test_files.iter() { let path = format!("testdata/ssh/{}",file); - let mkeypair = DSAKeyPair::::read_ssh_private_key_file(path); + let mkeypair = load_ssh_keyfile(path); match mkeypair { Err(e) => assert!(false, format!("reading error: {:?}", e)), - Ok((keypair,comment)) => { + Ok((keypair, comment)) => { let buffer = [0,1,2,3,4,6,2]; + let _ : DSAKeyPair = keypair; let sig = keypair.private.sign::(&buffer); assert!(keypair.public.verify::(&buffer, &sig)); let buffer2 = [0,1,2,3,4,6,5]; assert!(!keypair.public.verify::(&buffer2, &sig)); - match keypair.encode_ssh_private_key(&comment) { + match encode_ssh(&keypair, &comment) { Err(e2) => assert!(false, format!("render error: {:?}", e2)), Ok(encodedstr) => { - match DSAKeyPair::::decode_ssh_private_key(&encodedstr) { + match decode_ssh(&encodedstr) { Err(e3) => assert!(false, format!("reparse error: {:?}", e3)), Ok((keypair2,comment2)) => { + let _ : DSAKeyPair = keypair2; assert_eq!(keypair.public.params.p,keypair2.public.params.p,"failed to reparse key pair (p)"); assert_eq!(keypair.public.params.q,keypair2.public.params.q,"failed to reparse key pair (q)"); assert_eq!(keypair.public.params.g,keypair2.public.params.g,"failed to reparse key pair (g)");