From 69cef498f2bcec12310384b0abc1ae6af0b0099b Mon Sep 17 00:00:00 2001 From: Adam Wick Date: Thu, 28 Mar 2019 18:23:38 -0700 Subject: [PATCH] [CHECKPOINT] Starting to parse these things. --- Cargo.toml | 1 + src/lib.rs | 1 + src/ssh/errors.rs | 42 +++++++++++++ src/ssh/frame.rs | 134 +++++++++++++++++++++++++++++++++++++++++ src/ssh/mod.rs | 63 +++++++++++++++++++ testdata/ssh/rsa1024-1 | 1 - 6 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 src/ssh/errors.rs create mode 100644 src/ssh/frame.rs diff --git a/Cargo.toml b/Cargo.toml index acc289e..2cc1523 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ license-file = "LICENSE" repository = "https://github.com/acw/simple_crypto" [dependencies] +base64 = "^0.10.1" byteorder = "^1.2.7" chrono = "^0.4.6" cryptonum = { path = "cryptonum" } diff --git a/src/lib.rs b/src/lib.rs index 2b70144..0138cec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ //! that a new user should use, along with documentation regarding how and //! when they should use it, and examples. For now, it mostly just fowards //! off to more detailed modules. Help requested! +extern crate base64; extern crate byteorder; extern crate chrono; extern crate cryptonum; diff --git a/src/ssh/errors.rs b/src/ssh/errors.rs new file mode 100644 index 0000000..20d680d --- /dev/null +++ b/src/ssh/errors.rs @@ -0,0 +1,42 @@ +use base64::DecodeError; +use simple_asn1::ASN1DecodeErr; +use std::io; + +#[derive(Debug)] +pub enum SSHKeyParseError +{ + ASN1Error(ASN1DecodeErr), + DecodeError(DecodeError), + IOError(io::Error), + NoBeginBannerFound, NoEndBannerFound, + NoOpenSSHMagicHeader +} + +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) + } +} + +impl From for SSHKeyParseError { + fn from(e: io::Error) -> SSHKeyParseError { + SSHKeyParseError::IOError(e) + } +} + +pub enum SSHKeyRenderError { + IOError(io::Error), +} + +impl From for SSHKeyRenderError { + fn from(e: io::Error) -> SSHKeyRenderError { + SSHKeyRenderError::IOError(e) + } +} \ No newline at end of file diff --git a/src/ssh/frame.rs b/src/ssh/frame.rs new file mode 100644 index 0000000..0b6998a --- /dev/null +++ b/src/ssh/frame.rs @@ -0,0 +1,134 @@ +use base64::{decode,encode}; +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; + +const OPENER: &'static str = "-----BEGIN OPENSSH PRIVATE KEY-----\n"; +const CLOSER: &'static str = "-----END OPENSSH PRIVATE KEY-----"; + +pub fn parse_ssh_private_key_data(s: &str) -> Result,SSHKeyParseError> +{ + if s.starts_with(OPENER) { + if let Some(endidx) = s.find(CLOSER) { + let b64str: String = s[OPENER.len()..endidx].chars().filter(|x| *x != '\n').collect(); + let bytes = decode(&b64str)?; + Ok(bytes) + } else { + Err(SSHKeyParseError::NoEndBannerFound) + } + } else { + Err(SSHKeyParseError::NoBeginBannerFound) + } +} + +pub fn render_ssh_private_key_data(bytes: &[u8]) -> String +{ + let mut bytestr = encode(bytes); + let mut output = String::new(); + + output.push_str(OPENER); + while bytestr.len() > 70 { + let rest = bytestr.split_off(70); + output.push_str(&bytestr); + output.push_str("\n"); + bytestr = rest; + } + output.push_str(&bytestr); + output.push_str("\n"); + output.push_str(CLOSER); + + output +} + +//------------------------------------------------------------------------------ + +const OPENSSH_MAGIC_HEADER: &'static str = "openssh-key-v1\0"; + +pub fn parse_openssh_header(input: &mut Cursor>) -> 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) + } +} + +pub fn render_openssh_header(output: &mut O) -> Result<(),SSHKeyRenderError> +{ + Ok(output.write_all(OPENSSH_MAGIC_HEADER.as_bytes())?) +} + +//------------------------------------------------------------------------------ + +pub fn parse_openssh_u32(input: &mut Cursor>) -> Result +{ + +} + +//------------------------------------------------------------------------------ + +pub fn parse_openssh_string(input: &mut Cursor>) -> Result<(),SSHKeyParseError> +{ + panic!("string") +} + +//------------------------------------------------------------------------------ + +#[cfg(test)] +quickcheck! { + fn bytes_roundtrip(x: Vec) -> bool { + let rendered = render_ssh_private_key_data(&x); + let returned = parse_ssh_private_key_data(&rendered).unwrap(); + returned == x + } + + fn blocks_formatted(x: Vec) -> bool { + let rendered = render_ssh_private_key_data(&x); + let mut is_ok = true; + + for line in rendered.lines() { + is_ok &= line.len() <= 70; + } + + is_ok + } +} + +#[cfg(test)] +#[test] +fn pregenerated_reencode() { + let test_files = ["dsa1024-1", "dsa1024-2", "dsa1024-3", + "ecdsa256-1", "ecdsa256-2", "ecdsa256-3", + "ecdsa384-1", "ecdsa384-2", "ecdsa384-3", + "ecdsa521-1", "ecdsa521-2", "ecdsa521-3", + "ed25519-1", "ed25519-2", "ed25519-3", + "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 mut fd = File::open(path).unwrap(); + let mut contents = String::new(); + fd.read_to_string(&mut contents).unwrap(); + let parsed = parse_ssh_private_key_data(&contents).unwrap(); + let rendered = render_ssh_private_key_data(&parsed); + // starts_with() avoids newline unpleasantness + assert!(contents.starts_with(&rendered)); + } +} + +#[cfg(test)] +#[test] +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()); +} \ No newline at end of file diff --git a/src/ssh/mod.rs b/src/ssh/mod.rs index e69de29..34fab5c 100644 --- a/src/ssh/mod.rs +++ b/src/ssh/mod.rs @@ -0,0 +1,63 @@ +mod errors; +mod frame; + +pub use self::errors::SSHKeyParseError; + +use cryptonum::unsigned::U192; +use dsa::{DSAPrivKey,L1024N160}; +use self::frame::*; +use simple_asn1::from_der; +use std::fs::File; +use std::io; +use std::io::{Cursor,Read,Write}; +use std::path::Path; + +pub trait SSHKey: Sized { + fn decode_ssh_private_key(x: &str) -> Result; + fn read_ssh_private_key_file>(path: P) -> Result { + let mut file = File::open(path)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + Self::decode_ssh_private_key(&contents) + } + + fn encode_ssh_private_key(&self) -> String; + fn write_ssh_private_key_file>(&self, path: P) -> Result<(),io::Error> { + let mut file = File::create(path)?; + let contents = self.encode_ssh_private_key(); + let bytes = contents.into_bytes(); + file.write_all(&bytes)?; + file.sync_all() + } +} + + +impl SSHKey for DSAPrivKey { + fn decode_ssh_private_key(x: &str) -> Result + { + let bytes = parse_ssh_private_key_data(x)?; + 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") + } + + fn encode_ssh_private_key(&self) -> String { + panic!("encode") + } +} + +#[cfg(test)] +#[test] +fn read_dsa_examples() { + let test_files = ["dsa1024-1", "dsa1024-2", "dsa1024-3"]; + + 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()); + } +} \ No newline at end of file diff --git a/testdata/ssh/rsa1024-1 b/testdata/ssh/rsa1024-1 index 66b4a64..76c2160 100644 --- a/testdata/ssh/rsa1024-1 +++ b/testdata/ssh/rsa1024-1 @@ -13,5 +13,4 @@ WX+e5c20I946wF6L0xw2kCITubGuWdmnUeVXDzqEwfjbRAAAAEEA6uXkfRYJJuJfi2Zl3J ofSXfdjQqDFvp4RvAE9r3x3mK7zgClHgwGZOX22TlkH3aBFGwruIjubPk76LVP7u3CvQAA AEEA0GWdJgSVxeSVGQfrD/pV3uLR+gYfpHadkPucaVsU2GO1SUA6/qQweBUzddjTfG5ciu 6rgTXJonyar5w6+8870QAAABphd2lja0BkdW53b3J0aHkudWhzdXJlLmNvbQECAwQFBgc= - -----END OPENSSH PRIVATE KEY-----