diff --git a/src/ssh/errors.rs b/src/ssh/errors.rs index 20d680d..bd6b986 100644 --- a/src/ssh/errors.rs +++ b/src/ssh/errors.rs @@ -9,7 +9,17 @@ pub enum SSHKeyParseError DecodeError(DecodeError), IOError(io::Error), NoBeginBannerFound, NoEndBannerFound, - NoOpenSSHMagicHeader + NoOpenSSHMagicHeader, + UnknownKeyCipher(String), + UnknownKDF(String), UnexpectedKDFOptions, + InvalidNumberOfKeys(u32), + UnknownTrailingData, + UnknownKeyType(String), + InvalidPublicKeyMaterial, + PrivateKeyCorruption, + InconsistentKeyTypes(String,String), + InconsistentPublicKeyValue, + InvalidPrivateKeyValue } impl From for SSHKeyParseError { @@ -31,8 +41,11 @@ impl From for SSHKeyParseError { } } +#[derive(Debug)] pub enum SSHKeyRenderError { IOError(io::Error), + StringTooLong, + BufferTooLarge } impl From for SSHKeyRenderError { diff --git a/src/ssh/frame.rs b/src/ssh/frame.rs index 0b6998a..25be772 100644 --- a/src/ssh/frame.rs +++ b/src/ssh/frame.rs @@ -1,11 +1,12 @@ use base64::{decode,encode}; +use byteorder::{BigEndian,ReadBytesExt,WriteBytesExt}; use ssh::errors::{SSHKeyParseError,SSHKeyRenderError}; use std::io::Cursor; #[cfg(test)] use std::fs::File; -#[cfg(test)] use std::io::Read; use std::io::Write; +use std::iter::Iterator; const OPENER: &'static str = "-----BEGIN OPENSSH PRIVATE KEY-----\n"; const CLOSER: &'static str = "-----END OPENSSH PRIVATE KEY-----"; @@ -47,15 +48,24 @@ pub fn render_ssh_private_key_data(bytes: &[u8]) -> String //------------------------------------------------------------------------------ const OPENSSH_MAGIC_HEADER: &'static str = "openssh-key-v1\0"; +const OPENSSH_MAGIC_HEADER_LEN: usize = 15; -pub fn parse_openssh_header(input: &mut Cursor>) -> Result<(),SSHKeyParseError> +pub fn parse_openssh_header(input: &mut R) -> Result<(),SSHKeyParseError> { - let input_header = input.take(OPENSSH_MAGIC_HEADER.len()).bytes(); - if input_header.eq(OPENSSH_MAGIC_HEADER.as_bytes().iter()) { - Ok(()) - } else { - Err(SSHKeyParseError::NoOpenSSHMagicHeader) + let mut limited_input_header = input.take(OPENSSH_MAGIC_HEADER_LEN as u64); + let mut header: [u8; OPENSSH_MAGIC_HEADER_LEN] = [0; OPENSSH_MAGIC_HEADER_LEN]; + + assert_eq!(OPENSSH_MAGIC_HEADER.len(), OPENSSH_MAGIC_HEADER_LEN); + limited_input_header.read_exact(&mut header); + + for (left, right) in OPENSSH_MAGIC_HEADER.bytes().zip(header.iter()) { + if left != *right { + return Err(SSHKeyParseError::NoOpenSSHMagicHeader) + } + } + + Ok(()) } pub fn render_openssh_header(output: &mut O) -> Result<(),SSHKeyRenderError> @@ -65,16 +75,67 @@ pub fn render_openssh_header(output: &mut O) -> Result<(),SSHKeyRender //------------------------------------------------------------------------------ -pub fn parse_openssh_u32(input: &mut Cursor>) -> Result +pub fn parse_openssh_u32(input: &mut I) -> Result { + let mut limited_input_header = input.take(4); + let res = limited_input_header.read_u32::()?; + Ok(res) +} +pub fn render_openssh_u32(output: &mut O, val: u32) -> Result<(),SSHKeyRenderError> +{ + Ok(output.write_u32::(val)?) } //------------------------------------------------------------------------------ -pub fn parse_openssh_string(input: &mut Cursor>) -> Result<(),SSHKeyParseError> +pub fn parse_openssh_string(input: &mut I) -> Result { - panic!("string") + let length = parse_openssh_u32(input)?; + println!("len: {:X}", length); + let mut limited_input = input.take(length as u64); + let mut result = String::new(); + limited_input.read_to_string(&mut result)?; + Ok(result) +} + +pub fn render_openssh_string(output: &mut O, v: &str) -> Result<(),SSHKeyRenderError> +{ + let vbytes: Vec = v.bytes().collect(); + let len = vbytes.len(); + + if len > 0xFFFFFFFF { + return Err(SSHKeyRenderError::StringTooLong); + } + + render_openssh_u32(output, vbytes.len() as u32)?; + output.write_all(&vbytes)?; + Ok(()) +} + +//------------------------------------------------------------------------------ + +pub fn parse_openssh_buffer(input: &mut I) -> Result,SSHKeyParseError> +{ + let length = parse_openssh_u32(input)?; + let mut limited_input = input.take(length as u64); + let mut res = Vec::with_capacity(length as usize); + limited_input.read_to_end(&mut res); + Ok(res) +} + +pub fn render_openssh_buffer(output: &mut O, b: &[u8]) -> Result<(),SSHKeyRenderError> +{ + if b.len() > 0xFFFFFFFF { + return Err(SSHKeyRenderError::BufferTooLarge); + } + + render_openssh_u32(output, b.len() as u32)?; + if b.len() > 0 { + output.write_all(b)?; + } + + Ok(()) } //------------------------------------------------------------------------------ @@ -97,6 +158,42 @@ quickcheck! { is_ok } + + fn u32s_roundtrip_rp(x: u32) -> bool { + let mut buffer = vec![]; + render_openssh_u32(&mut buffer, x).unwrap(); + let mut cursor = Cursor::new(buffer); + let check = parse_openssh_u32(&mut cursor).unwrap(); + x == check + } + + fn u32s_roundtrip_pr(a: u8, b: u8, c: u8, d: u8) -> bool { + let block = [a,b,c,d]; + let mut cursor = Cursor::new(block); + let base = parse_openssh_u32(&mut cursor).unwrap(); + let mut rendered = vec![]; + render_openssh_u32(&mut rendered, base).unwrap(); + (block[0] == rendered[0]) && + (block[1] == rendered[1]) && + (block[2] == rendered[2]) && + (block[3] == rendered[3]) + } + + fn string_roundtrip(s: String) -> bool { + let mut buffer = vec![]; + render_openssh_string(&mut buffer, &s).unwrap(); + let mut cursor = Cursor::new(buffer); + let check = parse_openssh_string(&mut cursor).unwrap(); + s == check + } + + fn buffer(os: Vec) -> bool { + let mut buffer = vec![]; + render_openssh_buffer(&mut buffer, &os).unwrap(); + let mut cursor = Cursor::new(buffer); + let check = parse_openssh_buffer(&mut cursor).unwrap(); + os == check + } } #[cfg(test)] @@ -130,5 +227,6 @@ fn pregenerated_reencode() { fn header_roundtrips() { let mut vec = Vec::new(); assert!(render_openssh_header(&mut vec).is_ok()); - assert!(parse_openssh_header(&mut vec.iter()).is_ok()); + let mut cursor = Cursor::new(vec); + assert!(parse_openssh_header(&mut cursor).is_ok()); } \ No newline at end of file diff --git a/src/ssh/mod.rs b/src/ssh/mod.rs index 34fab5c..0dc684d 100644 --- a/src/ssh/mod.rs +++ b/src/ssh/mod.rs @@ -3,8 +3,8 @@ mod frame; pub use self::errors::SSHKeyParseError; -use cryptonum::unsigned::U192; -use dsa::{DSAPrivKey,L1024N160}; +use cryptonum::unsigned::*; +use dsa::{DSAKeyPair,DSAParameters,DSAPubKey,DSAPublicKey,DSAPrivKey,DSAPrivateKey,L1024N160}; use self::frame::*; use simple_asn1::from_der; use std::fs::File; @@ -32,17 +32,143 @@ pub trait SSHKey: Sized { } -impl SSHKey for DSAPrivKey { +impl SSHKey for DSAKeyPair { fn decode_ssh_private_key(x: &str) -> 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)?; - // - println!("bytes: {:?}", bytes); - panic!("decode") + 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)?; + if pubkey_type != "ssh-dss" { + return Err(SSHKeyParseError::UnknownKeyType(pubkey_type)); + } + + let mut pbytes = parse_openssh_buffer(&mut pubkey_cursor)?; + while pbytes[0] == 0 { pbytes.remove(0); } + if pbytes.len() > (1024 / 8) { + println!("pbytes.len() = {}", pbytes.len()); + println!("pbytes = {:?}", pbytes); + return Err(SSHKeyParseError::InvalidPublicKeyMaterial); + } + + let mut qbytes = parse_openssh_buffer(&mut pubkey_cursor)?; + while qbytes[0] == 0 { qbytes.remove(0); } + if qbytes.len() > (160 / 8) { + println!("qbytes.len() = {}", qbytes.len()); + return Err(SSHKeyParseError::InvalidPublicKeyMaterial); + } + + let mut gbytes = parse_openssh_buffer(&mut pubkey_cursor)?; + while gbytes[0] == 0 { gbytes.remove(0); } + if gbytes.len() > (1024 / 8) { + println!("gbytes.len() = {}", gbytes.len()); + return Err(SSHKeyParseError::InvalidPublicKeyMaterial); + } + + let mut ybytes = parse_openssh_buffer(&mut pubkey_cursor)?; + while ybytes[0] == 0 { ybytes.remove(0); } + if ybytes.len() > (1024 / 8) { + println!("ybytes.len() = {}", ybytes.len()); + return Err(SSHKeyParseError::InvalidPublicKeyMaterial); + } + + // Finally, we can turn this into a key + let p = U1024::from_bytes(&pbytes); + let g = U1024::from_bytes(&gbytes); + let q = U192::from_bytes(&qbytes); + let params = L1024N160::new(p, g, q); + let y = U1024::from_bytes(&ybytes); + let pubkey = DSAPubKey::::new(params, y); + + // 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)?; + 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 mut pdata = parse_openssh_buffer(&mut privkey_cursor)?; + while pdata[0] == 0 { pdata.remove(0); } + if pdata != pbytes { + return Err(SSHKeyParseError::InconsistentPublicKeyValue); + } + println!("p consistent"); + let p = U1024::from_bytes(&pdata); + + let mut qdata = parse_openssh_buffer(&mut privkey_cursor)?; + while qdata[0] == 0 { qdata.remove(0); } + if qdata != qbytes { + return Err(SSHKeyParseError::InconsistentPublicKeyValue); + } + let q = U192::from_bytes(&qdata); + println!("q consistent"); + + let mut gdata = parse_openssh_buffer(&mut privkey_cursor)?; + while gdata[0] == 0 { gdata.remove(0); } + if gdata != gbytes { + return Err(SSHKeyParseError::InconsistentPublicKeyValue); + } + let g = U1024::from_bytes(&gdata); + println!("g consistent"); + + let params = L1024N160::new(p, g, q); + + // We don't need this, but it's good cross-validation + let mut ydata = parse_openssh_buffer(&mut privkey_cursor)?; + while ydata[0] == 0 { ydata.remove(0); } + if ydata != ybytes { + return Err(SSHKeyParseError::InconsistentPublicKeyValue); + } + println!("y consistent"); + + // Finally, what we actually + let mut xdata = parse_openssh_buffer(&mut privkey_cursor)?; + while xdata[0] == 0 { xdata.remove(0); } + if xdata.len() > (192 / 8) { + // FIXME: Should this test for too small a value? + return Err(SSHKeyParseError::InvalidPrivateKeyValue); + } + let x = U192::from_bytes(&xdata); + println!("x: {:X}", x); + let privkey = DSAPrivKey::::new(params, x); + let comment = parse_openssh_string(&mut privkey_cursor)?; + println!("comment[{}] = {:?}", comment.len(), comment); + + let result = DSAKeyPair{ public: pubkey, private: privkey }; + Ok(result) } fn encode_ssh_private_key(&self) -> String { @@ -50,6 +176,9 @@ impl SSHKey for DSAPrivKey { } } +#[cfg(test)] +use sha2::Sha256; + #[cfg(test)] #[test] fn read_dsa_examples() { @@ -57,7 +186,16 @@ fn read_dsa_examples() { for file in test_files.iter() { let path = format!("testdata/ssh/{}",file); - let privkey = DSAPrivKey::::read_ssh_private_key_file(path); - assert!(privkey.is_ok()); + let mkeypair = DSAKeyPair::::read_ssh_private_key_file(path); + match mkeypair { + Err(e) => assert!(false, format!("reading error: {:?}", e)), + Ok(keypair) => { + let buffer = [0,1,2,3,4,6,2]; + 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)); + } + } } } \ No newline at end of file