Rejigger the SSHKey trait a little more cleanly.

This commit is contained in:
2019-04-04 19:51:03 -07:00
parent 68ddc7096b
commit e4f67f0918
4 changed files with 159 additions and 123 deletions

View File

@@ -13,6 +13,7 @@ pub use self::public::*;
use cryptonum::unsigned::*; use cryptonum::unsigned::*;
use rand::Rng; use rand::Rng;
use rand::distributions::Standard; use rand::distributions::Standard;
use super::KeyPair;
pub struct DSAKeyPair<P: DSAParameters> pub struct DSAKeyPair<P: DSAParameters>
{ {
@@ -20,6 +21,17 @@ pub struct DSAKeyPair<P: DSAParameters>
pub public: DSAPubKey<P> pub public: DSAPubKey<P>
} }
impl<P: DSAParameters> KeyPair for DSAKeyPair<P>
{
type Private = DSAPrivKey<P>;
type Public = DSAPubKey<P>;
fn new(public: DSAPubKey<P>, private: DSAPrivKey<P>) -> DSAKeyPair<P>
{
DSAKeyPair{ private, public }
}
}
pub trait DSAKeyGeneration pub trait DSAKeyGeneration
{ {
type Params; type Params;

View File

@@ -43,6 +43,13 @@ pub mod ssh;
/// used by TLS and others. /// used by TLS and others.
pub mod x509; pub mod x509;
pub trait KeyPair {
type Public;
type Private;
fn new(pbl: Self::Public, prv: Self::Private) -> Self;
}
#[cfg(test)] #[cfg(test)]
mod testing; mod testing;
mod utils; mod utils;

View File

@@ -1,11 +1,9 @@
use base64::DecodeError; use base64::DecodeError;
use simple_asn1::ASN1DecodeErr;
use std::io; use std::io;
#[derive(Debug)] #[derive(Debug)]
pub enum SSHKeyParseError pub enum SSHKeyParseError
{ {
ASN1Error(ASN1DecodeErr),
DecodeError(DecodeError), DecodeError(DecodeError),
IOError(io::Error), IOError(io::Error),
NoBeginBannerFound, NoEndBannerFound, NoBeginBannerFound, NoEndBannerFound,
@@ -23,13 +21,6 @@ pub enum SSHKeyParseError
InvalidPadding InvalidPadding
} }
impl From<ASN1DecodeErr> for SSHKeyParseError {
fn from(e: ASN1DecodeErr) -> SSHKeyParseError {
println!("asn1 error: {:?}", e);
SSHKeyParseError::ASN1Error(e)
}
}
impl From<DecodeError> for SSHKeyParseError { impl From<DecodeError> for SSHKeyParseError {
fn from(e: DecodeError) -> SSHKeyParseError { fn from(e: DecodeError) -> SSHKeyParseError {
SSHKeyParseError::DecodeError(e) SSHKeyParseError::DecodeError(e)

View File

@@ -9,146 +9,170 @@ use self::frame::*;
use std::fs::File; use std::fs::File;
use std::io::{Cursor,Read,Write}; use std::io::{Cursor,Read,Write};
use std::path::Path; use std::path::Path;
use super::KeyPair;
pub trait SSHKey: Sized { pub trait SSHKey: Sized + KeyPair {
fn decode_ssh_private_key(x: &str) -> Result<(Self,String),SSHKeyParseError>; fn parse_ssh_public_info<I: Read>(inp: &mut I) -> Result<Self::Public,SSHKeyParseError>;
fn read_ssh_private_key_file<P: AsRef<Path>>(path: P) -> Result<(Self,String),SSHKeyParseError> { fn parse_ssh_private_info<I: Read>(inp: &mut I) -> Result<(Self::Private,String),SSHKeyParseError>;
let mut file = File::open(path)?;
let mut contents = String::new(); fn render_ssh_public_info<O: Write>(&self, out: &mut O) -> Result<(),SSHKeyRenderError>;
file.read_to_string(&mut contents)?; fn render_ssh_private_info<O: Write>(&self, out: &mut O, comment: &str) -> Result<(),SSHKeyRenderError>;
Self::decode_ssh_private_key(&contents) }
pub fn decode_ssh<KP: SSHKey>(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<String,SSHKeyRenderError>; let mut pubcursor = Cursor::new(pubkey0);
fn write_ssh_private_key_file<P: AsRef<Path>>(&self, path: P, comment: &str) -> Result<(),SSHKeyRenderError> { let public = KP::parse_ssh_public_info(&mut pubcursor)?;
let mut file = File::create(path)?; let mut privcursor = Cursor::new(privkeys);
let contents = self.encode_ssh_private_key(comment)?; let (private, comment) = KP::parse_ssh_private_info(&mut privcursor)?;
let bytes = contents.into_bytes();
file.write_all(&bytes)?; Ok((KP::new(public, private), comment))
file.sync_all()?; }
Ok(())
} pub fn load_ssh_keyfile<KP,P>(path: P) -> Result<(KP, String),SSHKeyParseError>
where
KP: SSHKey,
P: AsRef<Path>
{
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
decode_ssh(&contents)
}
pub fn encode_ssh<KP: SSHKey>(x: &KP, comment: &str) -> Result<String,SSHKeyRenderError>
{
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<KP,P>(path: P, x: &KP, comment: &str) -> Result<(),SSHKeyRenderError>
where
KP: SSHKey,
P: AsRef<Path>
{
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<L1024N160> { impl SSHKey for DSAKeyPair<L1024N160> {
fn decode_ssh_private_key(x: &str) -> Result<(Self,String),SSHKeyParseError> fn parse_ssh_public_info<I: Read>(inp: &mut I) -> Result<Self::Public,SSHKeyParseError>
{ {
let bytes = parse_ssh_private_key_data(x)?; let pubkey_type = parse_openssh_string(inp)?;
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)?;
if pubkey_type != "ssh-dss" { if pubkey_type != "ssh-dss" {
return Err(SSHKeyParseError::UnknownKeyType(pubkey_type)); return Err(SSHKeyParseError::UnknownKeyType(pubkey_type));
} }
let pubp = parse_openssh_number(inp)?;
let pubp = parse_openssh_number(&mut pubkey_cursor)?; let pubq = parse_openssh_number(inp)?;
let pubq = parse_openssh_number(&mut pubkey_cursor)?; let pubg = parse_openssh_number(inp)?;
let pubg = parse_openssh_number(&mut pubkey_cursor)?;
let pubparams = L1024N160::new(pubp, pubg, pubq); let pubparams = L1024N160::new(pubp, pubg, pubq);
let puby: U1024 = parse_openssh_number(&mut pubkey_cursor)?; let puby: U1024 = parse_openssh_number(inp)?;
let pubkey = DSAPubKey::<L1024N160>::new(pubparams.clone(), puby.clone()); for _ in inp.bytes() { return Err(SSHKeyParseError::UnknownTrailingData); }
Ok(DSAPubKey::<L1024N160>::new(pubparams.clone(), puby.clone()))
}
// And now we can look at the private key! fn parse_ssh_private_info<I: Read>(inp: &mut I) -> Result<(Self::Private,String),SSHKeyParseError>
let mut privkey_cursor = Cursor::new(privkeys); {
let check1 = parse_openssh_u32(&mut privkey_cursor)?; let check1 = parse_openssh_u32(inp)?;
let check2 = parse_openssh_u32(&mut privkey_cursor)?; let check2 = parse_openssh_u32(inp)?;
if check1 != check2 { if check1 != check2 {
return Err(SSHKeyParseError::PrivateKeyCorruption); return Err(SSHKeyParseError::PrivateKeyCorruption);
} }
let privkey_type = parse_openssh_string(inp)?;
let privkey_type = parse_openssh_string(&mut privkey_cursor)?; if privkey_type != "ssh-dss" {
if privkey_type != pubkey_type { return Err(SSHKeyParseError::InconsistentKeyTypes("ssh-dss".to_string(), privkey_type));
return Err(SSHKeyParseError::InconsistentKeyTypes(pubkey_type, privkey_type));
} }
let privp = parse_openssh_number(inp)?;
let privp = parse_openssh_number(&mut privkey_cursor)?; let privq = parse_openssh_number(inp)?;
let privq = parse_openssh_number(&mut privkey_cursor)?; let privg = parse_openssh_number(inp)?;
let privg = parse_openssh_number(&mut privkey_cursor)?;
let privparams = L1024N160::new(privp, privg, privq); let privparams = L1024N160::new(privp, privg, privq);
let privy = parse_openssh_number(&mut privkey_cursor)?; let _ = parse_openssh_buffer(inp)?; // a copy of y we don't need
let privx = parse_openssh_number(&mut privkey_cursor)?; let privx = parse_openssh_number(inp)?;
if (pubparams != privparams) || (puby != privy) {
return Err(SSHKeyParseError::InconsistentPublicKeyValue);
}
let privkey = DSAPrivKey::<L1024N160>::new(pubparams, privx); let privkey = DSAPrivKey::<L1024N160>::new(privparams, privx);
let comment = parse_openssh_string(&mut privkey_cursor)?; let comment = parse_openssh_string(inp)?;
for (idx,byte) in privkey_cursor.bytes().enumerate() { for (idx,byte) in inp.bytes().enumerate() {
if ((idx+1) as u8) != byte? { if ((idx+1) as u8) != byte? {
return Err(SSHKeyParseError::InvalidPadding); return Err(SSHKeyParseError::InvalidPadding);
} }
} }
let result = DSAKeyPair{ public: pubkey, private: privkey }; Ok((privkey,comment))
Ok((result,comment))
} }
fn encode_ssh_private_key(&self, comment: &str) -> Result<String,SSHKeyRenderError> { fn render_ssh_public_info<O: Write>(&self, out: &mut O) -> Result<(),SSHKeyRenderError>
// render the public key {
let mut pubkeybin = Vec::with_capacity(4096); render_openssh_string(out, "ssh-dss")?;
render_openssh_string(&mut pubkeybin, "ssh-dss")?; render_openssh_number(out, &self.public.params.p)?;
render_openssh_number(&mut pubkeybin, &self.public.params.p)?; render_openssh_number(out, &self.public.params.q)?;
render_openssh_number(&mut pubkeybin, &self.public.params.q)?; render_openssh_number(out, &self.public.params.g)?;
render_openssh_number(&mut pubkeybin, &self.public.params.g)?; render_openssh_number(out, &self.public.y)
render_openssh_number(&mut pubkeybin, &self.public.y)?; }
// render the private key fn render_ssh_private_info<O: Write>(&self, out: &mut O, comment: &str) -> Result<(),SSHKeyRenderError>
let mut privkeybin = Vec::with_capacity(4096); {
render_openssh_u32(&mut privkeybin, 0xDEADBEEF)?; // FIXME: Any reason for this to be random? render_openssh_u32(out, 0xDEADBEEF)?; // FIXME: Any reason for this to be random?
render_openssh_u32(&mut privkeybin, 0xDEADBEEF)?; // ditto render_openssh_u32(out, 0xDEADBEEF)?; // ditto
render_openssh_string(&mut privkeybin, "ssh-dss")?; render_openssh_string(out, "ssh-dss")?;
render_openssh_number(&mut privkeybin, &self.private.params.p)?; render_openssh_number(out, &self.private.params.p)?;
render_openssh_number(&mut privkeybin, &self.private.params.q)?; render_openssh_number(out, &self.private.params.q)?;
render_openssh_number(&mut privkeybin, &self.private.params.g)?; render_openssh_number(out, &self.private.params.g)?;
render_openssh_number(&mut privkeybin, &self.public.y)?; render_openssh_number(out, &self.public.y)?;
render_openssh_number(&mut privkeybin, &self.private.x)?; render_openssh_number(out, &self.private.x)?;
render_openssh_string(&mut privkeybin, comment)?; render_openssh_string(out, comment)?;
// add some padding (not quite sure why) // add some padding (not quite sure why)
let mut i = 1; let mut i = comment.len();
while (privkeybin.len() % 16) != 0 { while (i % 16) != 0 {
privkeybin.push(i); out.write(&[(i - comment.len() + 1) as u8])?;
i += 1; i += 1;
} }
Ok(())
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))
}
} }
#[cfg(test)] #[cfg(test)]
@@ -161,21 +185,23 @@ fn read_dsa_examples() {
for file in test_files.iter() { for file in test_files.iter() {
let path = format!("testdata/ssh/{}",file); let path = format!("testdata/ssh/{}",file);
let mkeypair = DSAKeyPair::<L1024N160>::read_ssh_private_key_file(path); let mkeypair = load_ssh_keyfile(path);
match mkeypair { match mkeypair {
Err(e) => assert!(false, format!("reading error: {:?}", e)), Err(e) => assert!(false, format!("reading error: {:?}", e)),
Ok((keypair,comment)) => { Ok((keypair, comment)) => {
let buffer = [0,1,2,3,4,6,2]; let buffer = [0,1,2,3,4,6,2];
let _ : DSAKeyPair<L1024N160> = keypair;
let sig = keypair.private.sign::<Sha256>(&buffer); let sig = keypair.private.sign::<Sha256>(&buffer);
assert!(keypair.public.verify::<Sha256>(&buffer, &sig)); assert!(keypair.public.verify::<Sha256>(&buffer, &sig));
let buffer2 = [0,1,2,3,4,6,5]; let buffer2 = [0,1,2,3,4,6,5];
assert!(!keypair.public.verify::<Sha256>(&buffer2, &sig)); assert!(!keypair.public.verify::<Sha256>(&buffer2, &sig));
match keypair.encode_ssh_private_key(&comment) { match encode_ssh(&keypair, &comment) {
Err(e2) => assert!(false, format!("render error: {:?}", e2)), Err(e2) => assert!(false, format!("render error: {:?}", e2)),
Ok(encodedstr) => { Ok(encodedstr) => {
match DSAKeyPair::<L1024N160>::decode_ssh_private_key(&encodedstr) { match decode_ssh(&encodedstr) {
Err(e3) => assert!(false, format!("reparse error: {:?}", e3)), Err(e3) => assert!(false, format!("reparse error: {:?}", e3)),
Ok((keypair2,comment2)) => { Ok((keypair2,comment2)) => {
let _ : DSAKeyPair<L1024N160> = keypair2;
assert_eq!(keypair.public.params.p,keypair2.public.params.p,"failed to reparse key pair (p)"); 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.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)"); assert_eq!(keypair.public.params.g,keypair2.public.params.g,"failed to reparse key pair (g)");