Rejigger the SSHKey trait a little more cleanly.
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
220
src/ssh/mod.rs
220
src/ssh/mod.rs
@@ -9,30 +9,17 @@ 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_ssh_private_key(&self, comment: &str) -> Result<String,SSHKeyRenderError>;
|
pub fn decode_ssh<KP: SSHKey>(x: &str) -> Result<(KP, String),SSHKeyParseError>
|
||||||
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>
|
|
||||||
{
|
{
|
||||||
let bytes = parse_ssh_private_key_data(x)?;
|
let bytes = parse_ssh_private_key_data(x)?;
|
||||||
let data_size = bytes.len() as u64;
|
let data_size = bytes.len() as u64;
|
||||||
@@ -59,86 +46,35 @@ impl SSHKey for DSAKeyPair<L1024N160> {
|
|||||||
let privkeys = parse_openssh_buffer(&mut byte_cursor)?;
|
let privkeys = parse_openssh_buffer(&mut byte_cursor)?;
|
||||||
if byte_cursor.position() < data_size {
|
if byte_cursor.position() < data_size {
|
||||||
return Err(SSHKeyParseError::UnknownTrailingData);
|
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 mut pubcursor = Cursor::new(pubkey0);
|
||||||
let pubq = parse_openssh_number(&mut pubkey_cursor)?;
|
let public = KP::parse_ssh_public_info(&mut pubcursor)?;
|
||||||
let pubg = parse_openssh_number(&mut pubkey_cursor)?;
|
let mut privcursor = Cursor::new(privkeys);
|
||||||
let pubparams = L1024N160::new(pubp, pubg, pubq);
|
let (private, comment) = KP::parse_ssh_private_info(&mut privcursor)?;
|
||||||
let puby: U1024 = parse_openssh_number(&mut pubkey_cursor)?;
|
|
||||||
let pubkey = DSAPubKey::<L1024N160>::new(pubparams.clone(), puby.clone());
|
|
||||||
|
|
||||||
// And now we can look at the private key!
|
Ok((KP::new(public, private), comment))
|
||||||
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)?;
|
pub fn load_ssh_keyfile<KP,P>(path: P) -> Result<(KP, String),SSHKeyParseError>
|
||||||
if privkey_type != pubkey_type {
|
where
|
||||||
return Err(SSHKeyParseError::InconsistentKeyTypes(pubkey_type, privkey_type));
|
KP: SSHKey,
|
||||||
}
|
P: AsRef<Path>
|
||||||
|
{
|
||||||
let privp = parse_openssh_number(&mut privkey_cursor)?;
|
let mut file = File::open(path)?;
|
||||||
let privq = parse_openssh_number(&mut privkey_cursor)?;
|
let mut contents = String::new();
|
||||||
let privg = parse_openssh_number(&mut privkey_cursor)?;
|
file.read_to_string(&mut contents)?;
|
||||||
let privparams = L1024N160::new(privp, privg, privq);
|
decode_ssh(&contents)
|
||||||
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 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);
|
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_header(&mut binary)?;
|
||||||
render_openssh_string(&mut binary, "none")?; // ciphername
|
render_openssh_string(&mut binary, "none")?; // ciphername
|
||||||
render_openssh_string(&mut binary, "none")?; // kdfname
|
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_u32(&mut binary, 1)?; // numkeys
|
||||||
render_openssh_buffer(&mut binary, &pubkeybin)?;
|
render_openssh_buffer(&mut binary, &pubkeybin)?;
|
||||||
render_openssh_buffer(&mut binary, &privkeybin)?;
|
render_openssh_buffer(&mut binary, &privkeybin)?;
|
||||||
|
|
||||||
Ok(render_ssh_private_key_data(&binary))
|
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)]
|
#[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)");
|
||||||
|
|||||||
Reference in New Issue
Block a user