From 16cf6172ce2df97a35e0a13e37c4df4bb28a8a23 Mon Sep 17 00:00:00 2001 From: Adam Wick Date: Wed, 22 May 2019 19:57:20 -0700 Subject: [PATCH] Support reading and writing ED25519 SSH keys. --- src/ssh/ed25519.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++ src/ssh/errors.rs | 10 ++++++++ src/ssh/mod.rs | 39 +++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 src/ssh/ed25519.rs diff --git a/src/ssh/ed25519.rs b/src/ssh/ed25519.rs new file mode 100644 index 0000000..c19dc51 --- /dev/null +++ b/src/ssh/ed25519.rs @@ -0,0 +1,58 @@ +use ed25519::{ED25519KeyPair,ED25519Private,ED25519Public}; +use std::io::{Read,Write}; +use ssh::errors::{SSHKeyParseError,SSHKeyRenderError}; +use ssh::frame::*; +use ssh::SSHKey; + +impl SSHKey for ED25519KeyPair { + fn valid_keytype(s: &str) -> bool { + (s == "ssh-ed25519") + } + + fn parse_ssh_public_info(inp: &mut I) -> Result + { + let pubkey_type = parse_openssh_string(inp)?; + if !Self::valid_keytype(&pubkey_type) { + return Err(SSHKeyParseError::UnknownKeyType(pubkey_type)); + } + let pubkey_bytes = parse_openssh_buffer(inp)?; + Ok(ED25519Public::new(&pubkey_bytes)?) + } + + 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 public = ED25519KeyPair::parse_ssh_public_info(inp)?; + let private_bytes = parse_openssh_buffer(inp)?; + let private = ED25519Private::from_seed(&private_bytes[0..32]); + let comment = parse_openssh_string(inp)?; + for (idx,byte) in inp.bytes().enumerate() { + if ((idx+1) as u8) != byte? { + return Err(SSHKeyParseError::InvalidPadding); + } + } + assert_eq!(public, ED25519Public::from(&private)); + + Ok((private, comment)) + } + + fn render_ssh_public_info(&self, out: &mut O) -> Result<(),SSHKeyRenderError> + { + render_openssh_string(out, "ssh-ed25519")?; + render_openssh_buffer(out, &self.public.to_bytes())?; + Ok(()) + } + + fn render_ssh_private_info(&self, out: &mut O) -> Result<(),SSHKeyRenderError> + { + self.render_ssh_public_info(out)?; + let mut private_bytes = self.private.to_bytes(); + private_bytes.append(&mut self.public.to_bytes()); + render_openssh_buffer(out, &private_bytes)?; + Ok(()) + } +} \ No newline at end of file diff --git a/src/ssh/errors.rs b/src/ssh/errors.rs index 7803b80..bda88f1 100644 --- a/src/ssh/errors.rs +++ b/src/ssh/errors.rs @@ -1,4 +1,5 @@ use base64::DecodeError; +use ed25519::ED25519PublicImportError; use std::io; #[derive(Debug)] @@ -37,6 +38,15 @@ impl From for SSHKeyParseError { } } +impl From for SSHKeyParseError { + fn from(e: ED25519PublicImportError) -> SSHKeyParseError { + match e { + ED25519PublicImportError::WrongNumberOfBytes(_) => + SSHKeyParseError::InvalidPublicKeyMaterial + } + } +} + #[derive(Debug)] pub enum SSHKeyRenderError { IOError(io::Error), diff --git a/src/ssh/mod.rs b/src/ssh/mod.rs index bd03ea0..82d213f 100644 --- a/src/ssh/mod.rs +++ b/src/ssh/mod.rs @@ -1,5 +1,6 @@ mod dsa; mod ecdsa; +mod ed25519; mod errors; mod frame; mod rsa; @@ -157,6 +158,8 @@ use dsa::{DSAKeyPair,DSAPublicKey,L1024N160}; #[cfg(test)] use ecdsa::ECDSAPair; #[cfg(test)] +use ed25519::ED25519KeyPair; +#[cfg(test)] use rsa::{RSAPair,RSAPublic,SIGNING_HASH_SHA256}; #[cfg(test)] use sha2::Sha256; @@ -328,4 +331,40 @@ fn ecdsa_examples() { } } } + +#[cfg(test)] +#[test] +fn ed25519_examples() { + let test_files = ["ed25519-1", "ed25519-2", "ed25519-3"]; + + for file in test_files.iter() { + let path = format!("testdata/ssh/{}",file); + match load_ssh_keyfile::(path) { + Err(e) => + assert!(false, "SSH ED25519 parse error: {:?}", e), + Ok((keypair,comment)) => { + // first see if this roundtrips + let buffer = vec![0,1,2,4,5,6,9]; + let sig = keypair.private.sign(&buffer); + assert!(keypair.public.verify(&buffer, &sig)); + match encode_ssh(&keypair, &comment) { + Err(e) => + assert!(false, "SSH ED25519 encoding error: {:?}", e), + Ok(coded) => { + match decode_ssh(&coded) { + Err(e) => + assert!(false, "SSSH ECDSA redecoding error: {:?}", e), + Ok((keypair2, comment2)) => { + let _ : ED25519KeyPair = keypair2; + assert_eq!(keypair.public, keypair2.public, "public key mismatch"); + assert_eq!(keypair.private, keypair2.private, "public key mismatch"); + assert_eq!(comment, comment2, "comment mismatch"); + } + } + } + } + } + } + } +} \ No newline at end of file