diff --git a/src/ecdsa/curve.rs b/src/ecdsa/curve.rs index 5e37115..977af21 100644 --- a/src/ecdsa/curve.rs +++ b/src/ecdsa/curve.rs @@ -1,11 +1,12 @@ use cryptonum::signed::{I192,I256,I384,I576}; use cryptonum::unsigned::{Decoder}; use cryptonum::unsigned::{U192,U256,U384,U576}; +use std::fmt::Debug; #[allow(non_snake_case)] pub trait EllipticCurve { type Unsigned : Clone; - type Signed : Clone; + type Signed : Clone + Debug + PartialEq; fn size() -> usize; fn p() -> Self::Unsigned; @@ -18,6 +19,7 @@ pub trait EllipticCurve { fn Gy() -> Self::Signed; } +#[derive(Debug,PartialEq)] pub enum P192 {} impl EllipticCurve for P192 { @@ -61,6 +63,7 @@ impl EllipticCurve for P192 { } } +#[derive(Debug,PartialEq)] pub enum P224 {} impl EllipticCurve for P224 { @@ -143,6 +146,7 @@ impl EllipticCurve for P224 { } } +#[derive(Debug,PartialEq)] pub enum P256 {} impl EllipticCurve for P256 { @@ -226,6 +230,7 @@ impl EllipticCurve for P256 { } } +#[derive(Debug,PartialEq)] pub enum P384 {} impl EllipticCurve for P384 { @@ -322,6 +327,7 @@ impl EllipticCurve for P384 { } } +#[derive(Debug,PartialEq)] pub enum P521 {} impl EllipticCurve for P521 { diff --git a/src/ecdsa/point.rs b/src/ecdsa/point.rs index 525e1b9..2d84005 100644 --- a/src/ecdsa/point.rs +++ b/src/ecdsa/point.rs @@ -20,6 +20,7 @@ pub trait ECCPoint : Sized { } } +#[derive(Debug,PartialEq)] pub struct Point { pub x: T::Signed, diff --git a/src/ecdsa/private.rs b/src/ecdsa/private.rs index 0660402..dd44d10 100644 --- a/src/ecdsa/private.rs +++ b/src/ecdsa/private.rs @@ -5,9 +5,18 @@ use dsa::rfc6979::{DSASignature,KIterator,bits2int}; use ecdsa::curve::{EllipticCurve,P192,P224,P256,P384,P521}; use ecdsa::point::{ECCPoint,Point}; use hmac::{Hmac,Mac}; +use std::fmt; +#[derive(PartialEq)] pub struct ECCPrivateKey { - d: Curve::Unsigned + pub(crate) d: Curve::Unsigned +} + +impl fmt::Debug for ECCPrivateKey { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(),fmt::Error> + { + f.write_str("") + } } pub enum ECDSAPrivate { diff --git a/src/ecdsa/public.rs b/src/ecdsa/public.rs index 247b335..b3fb82c 100644 --- a/src/ecdsa/public.rs +++ b/src/ecdsa/public.rs @@ -8,8 +8,9 @@ use hmac::{Hmac,Mac}; use simple_asn1::{ASN1Block,ASN1Class,ASN1DecodeErr,ASN1EncodeErr,FromASN1,ToASN1}; use std::cmp::min; +#[derive(Debug,PartialEq)] pub struct ECCPublicKey { - q: Point + pub(crate) q: Point } pub enum ECDSAPublic { diff --git a/src/ssh/ecdsa.rs b/src/ssh/ecdsa.rs index a113eaa..c6017c9 100644 --- a/src/ssh/ecdsa.rs +++ b/src/ssh/ecdsa.rs @@ -1,5 +1,8 @@ +use cryptonum::signed::*; use cryptonum::unsigned::*; use ecdsa::{ECDSAPair,ECDSAPublic,ECCPublicKey,ECDSAPrivate,ECCPrivateKey}; +use ecdsa::curve::{P256,P384,P521}; +use ecdsa::point::Point; use std::io::{Read,Write}; use ssh::errors::{SSHKeyParseError,SSHKeyRenderError}; use ssh::frame::*; @@ -21,15 +24,45 @@ impl SSHKey for ECDSAPair { // that we didn't know the number format was the same as the buffer // one), but we need to infer what kind of key this is, and this appears // to be the easiest / fastest way. - let mut ebuf = parse_openssh_buffer(inp)?; - let mut ibuf = parse_openssh_buffer(inp)?; - - while ebuf[0] == 0 { ebuf.remove(0); } - while ibuf[0] == 0 { ibuf.remove(0); } - - println!("ebuf: {:?}", ebuf); - println!("ibuf: {:?}", ibuf); - panic!("parse public info") + let curve = parse_openssh_string(inp)?; + match curve.as_ref() { + "nistp256" => { + let val = parse_openssh_buffer(inp)?; + if val[0] != 4 || val.len() != 65 { + return Err(SSHKeyParseError::InvalidECPointCompression); + } + let x = U256::from_bytes(&val[1..33]); + let y = U256::from_bytes(&val[33..]); + let p = Point::{ x: I256::from(x), y: I256::from(y) }; + let pbl = ECCPublicKey::::new(p); + Ok(ECDSAPublic::P256(pbl)) + } + "nistp384" => { + let val = parse_openssh_buffer(inp)?; + if val[0] != 4 || val.len() != 97 { + return Err(SSHKeyParseError::InvalidECPointCompression); + } + let x = U384::from_bytes(&val[1..49]); + let y = U384::from_bytes(&val[49..]); + let p = Point::{ x: I384::from(x), y: I384::from(y) }; + let pbl = ECCPublicKey::::new(p); + Ok(ECDSAPublic::P384(pbl)) + } + "nistp521" => { + let val = parse_openssh_buffer(inp)?; + if val[0] != 4 || val.len() != 133 { + return Err(SSHKeyParseError::InvalidECPointCompression); + } + let x = U576::from_bytes(&val[1..67]); + let y = U576::from_bytes(&val[67..]); + let p = Point::{ x: I576::from(x), y: I576::from(y) }; + let pbl = ECCPublicKey::::new(p); + Ok(ECDSAPublic::P521(pbl)) + } + _ => { + return Err(SSHKeyParseError::UnknownECDSACurve(curve)) + } + } } fn parse_ssh_private_info(inp: &mut I) -> Result<(Self::Private,String),SSHKeyParseError> @@ -39,40 +72,102 @@ impl SSHKey for ECDSAPair { if check1 != check2 { return Err(SSHKeyParseError::PrivateKeyCorruption); } - let privkey_type = parse_openssh_string(inp)?; - if !Self::valid_keytype(&privkey_type) { - return Err(SSHKeyParseError::InconsistentKeyTypes("ssh-rsa".to_string(), privkey_type)); - } - - let comment = parse_openssh_string(inp)?; + let res = match ECDSAPair::parse_ssh_public_info(inp)? { + ECDSAPublic::P192(_) => return Err(SSHKeyParseError::PrivateKeyCorruption), + ECDSAPublic::P224(_) => return Err(SSHKeyParseError::PrivateKeyCorruption), + ECDSAPublic::P256(_) => { + let mut dbytes = parse_openssh_buffer(inp)?; + while dbytes[0] == 0 { dbytes.remove(0); } + assert!(dbytes.len() <= 32); + let d = U256::from_bytes(&dbytes); + ECDSAPrivate::P256(ECCPrivateKey::::new(d)) + } + ECDSAPublic::P384(_) => { + let mut dbytes = parse_openssh_buffer(inp)?; + while dbytes[0] == 0 { dbytes.remove(0); } + assert!(dbytes.len() <= 48); + let d = U384::from_bytes(&dbytes); + ECDSAPrivate::P384(ECCPrivateKey::::new(d)) + } + ECDSAPublic::P521(_) => { + let mut dbytes = parse_openssh_buffer(inp)?; + while dbytes[0] == 0 { dbytes.remove(0); } + assert!(dbytes.len() <= 66); + let d = U576::from_bytes(&dbytes); + ECDSAPrivate::P521(ECCPrivateKey::::new(d)) + } + }; + let comment = parse_openssh_string(inp)?; for (idx,byte) in inp.bytes().enumerate() { if ((idx+1) as u8) != byte? { return Err(SSHKeyParseError::InvalidPadding); } } - panic!("parse private_info") + Ok((res, comment)) } fn render_ssh_public_info(&self, out: &mut O) -> Result<(),SSHKeyRenderError> { render_openssh_string(out, "ssh-ecdsa")?; - panic!("render public info") + match self { + ECDSAPair::P192(_,_) => + return Err(SSHKeyRenderError::IllegalECDSAKeyType("P192".to_string())), + ECDSAPair::P224(_,_) => + return Err(SSHKeyRenderError::IllegalECDSAKeyType("P224".to_string())), + ECDSAPair::P256(pu,_) => { + render_openssh_string(out, "nistp256")?; + let mut vec = Vec::with_capacity(66); + vec.write(&[4u8])?; + render_number(256, &mut vec, &U256::from(pu.q.x.clone()))?; + render_number(256, &mut vec, &U256::from(pu.q.y.clone()))?; + render_openssh_buffer(out, &vec)?; + } + ECDSAPair::P384(pu,_) => { + render_openssh_string(out, "nistp384")?; + let mut vec = Vec::with_capacity(66); + vec.write(&[4u8])?; + render_number(384, &mut vec, &U384::from(pu.q.x.clone()))?; + render_number(384, &mut vec, &U384::from(pu.q.y.clone()))?; + render_openssh_buffer(out, &vec)?; + } + ECDSAPair::P521(pu,_) => { + render_openssh_string(out, "nistp521")?; + let mut vec = Vec::with_capacity(66); + vec.write(&[4u8])?; + render_number(521, &mut vec, &U576::from(pu.q.x.clone()))?; + render_number(521, &mut vec, &U576::from(pu.q.y.clone()))?; + render_openssh_buffer(out, &vec)?; + } + } + Ok(()) } - fn render_ssh_private_info(&self, out: &mut O, comment: &str) -> Result<(),SSHKeyRenderError> + fn render_ssh_private_info(&self, out: &mut O) -> 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-ecdsa")?; - panic!("render private info"); - 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; + self.render_ssh_public_info(out)?; + match self { + ECDSAPair::P192(_,_) => + return Err(SSHKeyRenderError::IllegalECDSAKeyType("P192".to_string())), + ECDSAPair::P224(_,_) => + return Err(SSHKeyRenderError::IllegalECDSAKeyType("P224".to_string())), + ECDSAPair::P256(_,pr) => { render_openssh_u32(out, 256/8)?; render_number(256, out, &pr.d)?; } + ECDSAPair::P384(_,pr) => { render_openssh_u32(out, 384/8)?; render_number(384, out, &pr.d)?; } + ECDSAPair::P521(_,pr) => { render_openssh_u32(out, 528/8)?; render_number(521, out, &pr.d)?; } } Ok(()) } } + +fn render_number(bitlen: usize, out: &mut O, val: &N) -> Result<(),SSHKeyRenderError> + where + O: Write, + N: Encoder +{ + let mut outvec = Vec::new(); + outvec.write(&val.to_bytes())?; + while outvec.len() < ((bitlen + 7) / 8) { outvec.insert(0,0); } + while outvec.len() > ((bitlen + 7) / 8) { outvec.remove(0); } + out.write(&outvec)?; + Ok(()) +} diff --git a/src/ssh/errors.rs b/src/ssh/errors.rs index 856a173..7803b80 100644 --- a/src/ssh/errors.rs +++ b/src/ssh/errors.rs @@ -20,7 +20,9 @@ pub enum SSHKeyParseError InvalidPrivateKeyValue, InvalidPadding, InvalidPublicKeyType, - BrokenPublicKeyLine + BrokenPublicKeyLine, + UnknownECDSACurve(String), + InvalidECPointCompression } impl From for SSHKeyParseError { @@ -39,7 +41,8 @@ impl From for SSHKeyParseError { pub enum SSHKeyRenderError { IOError(io::Error), StringTooLong, - BufferTooLarge + BufferTooLarge, + IllegalECDSAKeyType(String) } impl From for SSHKeyRenderError { diff --git a/src/ssh/mod.rs b/src/ssh/mod.rs index 4d15bc7..bd03ea0 100644 --- a/src/ssh/mod.rs +++ b/src/ssh/mod.rs @@ -1,4 +1,5 @@ mod dsa; +mod ecdsa; mod errors; mod frame; mod rsa; @@ -154,6 +155,8 @@ pub fn write_ssh_keyfile(path: P, x: &KP, comment: &str) -> Result<(),SSHK #[cfg(test)] use dsa::{DSAKeyPair,DSAPublicKey,L1024N160}; #[cfg(test)] +use ecdsa::ECDSAPair; +#[cfg(test)] use rsa::{RSAPair,RSAPublic,SIGNING_HASH_SHA256}; #[cfg(test)] use sha2::Sha256; @@ -258,4 +261,71 @@ fn rsa_examples() { } } } -} \ No newline at end of file +} + +#[cfg(test)] +#[test] +fn ecdsa_examples() { + let test_files = ["ecdsa256-1", "ecdsa256-2", "ecdsa256-3", + "ecdsa384-1", "ecdsa384-2", "ecdsa384-3", + "ecdsa521-1", "ecdsa521-2", "ecdsa521-3"]; + + for file in test_files.iter() { + let path = format!("testdata/ssh/{}",file); + match load_ssh_keyfile::(path) { + Err(e) => + assert!(false, "SSH ECDSA parse error: {:?}", e), + Ok((keypair,comment)) => { + // first see if this roundtrips + let buffer = vec![0,1,2,4,5,6,9]; + match keypair { + ECDSAPair::P192(_,_) => + assert!(false, "Somehow got a P192 in read test"), + ECDSAPair::P224(_,_) => + assert!(false, "Somehow got a P224 in read test"), + ECDSAPair::P256(ref pu, ref pr) => { + let sig = pr.sign::(&buffer); + assert!(pu.verify::(&buffer, &sig)); + } + ECDSAPair::P384(ref pu, ref pr) => { + let sig = pr.sign::(&buffer); + assert!(pu.verify::(&buffer, &sig)); + } + ECDSAPair::P521(ref pu, ref pr) => { + let sig = pr.sign::(&buffer); + assert!(pu.verify::(&buffer, &sig)); + } + } + // encode this, parse it again + match encode_ssh(&keypair, &comment) { + Err(e) => + assert!(false, "SSH ECDSA encoding error: {:?}", e), + Ok(coded) => { + match (decode_ssh(&coded), keypair) { + (Err(e), _) => + assert!(false, "SSSH ECDSA redecoding error: {:?}", e), + (Ok((ECDSAPair::P256(pu2, pr2), comment2)), ECDSAPair::P256(pu,pr)) => { + assert_eq!(pu, pu2, "public key mismatch"); + assert_eq!(pr, pr2, "public key mismatch"); + assert_eq!(comment, comment2, "comment mismatch"); + } + (Ok((ECDSAPair::P384(pu2, pr2), comment2)), ECDSAPair::P384(pu,pr)) => { + assert_eq!(pu, pu2, "public key mismatch"); + assert_eq!(pr, pr2, "public key mismatch"); + assert_eq!(comment, comment2, "comment mismatch"); + } + (Ok((ECDSAPair::P521(pu2, pr2), comment2)), ECDSAPair::P521(pu,pr)) => { + assert_eq!(pu, pu2, "public key mismatch"); + assert_eq!(pr, pr2, "public key mismatch"); + assert_eq!(comment, comment2, "comment mismatch"); + } + _ => + assert!(false, "Failed to accurately re-parse key") + } + } + } + } + } + } +} + \ No newline at end of file