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 rand::Rng;
use rand::distributions::Standard;
use super::KeyPair;
pub struct DSAKeyPair<P: DSAParameters>
{
@@ -20,6 +21,17 @@ pub struct DSAKeyPair<P: DSAParameters>
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
{
type Params;

View File

@@ -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;

View File

@@ -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<ASN1DecodeErr> for SSHKeyParseError {
fn from(e: ASN1DecodeErr) -> SSHKeyParseError {
println!("asn1 error: {:?}", e);
SSHKeyParseError::ASN1Error(e)
}
}
impl From<DecodeError> for SSHKeyParseError {
fn from(e: DecodeError) -> SSHKeyParseError {
SSHKeyParseError::DecodeError(e)

View File

@@ -9,30 +9,17 @@ 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<P: AsRef<Path>>(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<I: Read>(inp: &mut I) -> Result<Self::Public,SSHKeyParseError>;
fn parse_ssh_private_info<I: Read>(inp: &mut I) -> Result<(Self::Private,String),SSHKeyParseError>;
fn render_ssh_public_info<O: Write>(&self, out: &mut O) -> Result<(),SSHKeyRenderError>;
fn render_ssh_private_info<O: Write>(&self, out: &mut O, comment: &str) -> Result<(),SSHKeyRenderError>;
}
fn encode_ssh_private_key(&self, comment: &str) -> Result<String,SSHKeyRenderError>;
fn write_ssh_private_key_file<P: AsRef<Path>>(&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(())
}
}
impl SSHKey for DSAKeyPair<L1024N160> {
fn decode_ssh_private_key(x: &str) -> Result<(Self,String),SSHKeyParseError>
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;
@@ -59,86 +46,35 @@ impl SSHKey for DSAKeyPair<L1024N160> {
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 pubp = parse_openssh_number(&mut pubkey_cursor)?;
let pubq = parse_openssh_number(&mut pubkey_cursor)?;
let pubg = parse_openssh_number(&mut pubkey_cursor)?;
let pubparams = L1024N160::new(pubp, pubg, pubq);
let puby: U1024 = parse_openssh_number(&mut pubkey_cursor)?;
let pubkey = DSAPubKey::<L1024N160>::new(pubparams.clone(), puby.clone());
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)?;
// 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);
Ok((KP::new(public, private), comment))
}
let privkey_type = parse_openssh_string(&mut privkey_cursor)?;
if privkey_type != pubkey_type {
return Err(SSHKeyParseError::InconsistentKeyTypes(pubkey_type, 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 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 privkey = DSAPrivKey::<L1024N160>::new(pubparams, privx);
let comment = parse_openssh_string(&mut privkey_cursor)?;
for (idx,byte) in privkey_cursor.bytes().enumerate() {
if ((idx+1) as u8) != byte? {
return Err(SSHKeyParseError::InvalidPadding);
}
}
let result = DSAKeyPair{ public: pubkey, private: privkey };
Ok((result,comment))
}
fn encode_ssh_private_key(&self, comment: &str) -> Result<String,SSHKeyRenderError> {
// 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)?;
// 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)?;
// add some padding (not quite sure why)
let mut i = 1;
while (privkeybin.len() % 16) != 0 {
privkeybin.push(i);
i += 1;
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
@@ -146,9 +82,97 @@ impl SSHKey for DSAKeyPair<L1024N160> {
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> {
fn parse_ssh_public_info<I: Read>(inp: &mut I) -> Result<Self::Public,SSHKeyParseError>
{
let pubkey_type = parse_openssh_string(inp)?;
if pubkey_type != "ssh-dss" {
return Err(SSHKeyParseError::UnknownKeyType(pubkey_type));
}
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(inp)?;
for _ in inp.bytes() { return Err(SSHKeyParseError::UnknownTrailingData); }
Ok(DSAPubKey::<L1024N160>::new(pubparams.clone(), puby.clone()))
}
fn parse_ssh_private_info<I: Read>(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(inp)?;
if privkey_type != "ssh-dss" {
return Err(SSHKeyParseError::InconsistentKeyTypes("ssh-dss".to_string(), privkey_type));
}
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 _ = parse_openssh_buffer(inp)?; // a copy of y we don't need
let privx = parse_openssh_number(inp)?;
let privkey = DSAPrivKey::<L1024N160>::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);
}
}
Ok((privkey,comment))
}
fn render_ssh_public_info<O: Write>(&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)
}
fn render_ssh_private_info<O: Write>(&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 = comment.len();
while (i % 16) != 0 {
out.write(&[(i - comment.len() + 1) as u8])?;
i += 1;
}
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::<L1024N160>::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)) => {
let buffer = [0,1,2,3,4,6,2];
let _ : DSAKeyPair<L1024N160> = keypair;
let sig = keypair.private.sign::<Sha256>(&buffer);
assert!(keypair.public.verify::<Sha256>(&buffer, &sig));
let buffer2 = [0,1,2,3,4,6,5];
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)),
Ok(encodedstr) => {
match DSAKeyPair::<L1024N160>::decode_ssh_private_key(&encodedstr) {
match decode_ssh(&encodedstr) {
Err(e3) => assert!(false, format!("reparse error: {:?}", e3)),
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.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)");