ECDSA SSH key support.
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
use cryptonum::signed::{I192,I256,I384,I576};
|
use cryptonum::signed::{I192,I256,I384,I576};
|
||||||
use cryptonum::unsigned::{Decoder};
|
use cryptonum::unsigned::{Decoder};
|
||||||
use cryptonum::unsigned::{U192,U256,U384,U576};
|
use cryptonum::unsigned::{U192,U256,U384,U576};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub trait EllipticCurve {
|
pub trait EllipticCurve {
|
||||||
type Unsigned : Clone;
|
type Unsigned : Clone;
|
||||||
type Signed : Clone;
|
type Signed : Clone + Debug + PartialEq;
|
||||||
|
|
||||||
fn size() -> usize;
|
fn size() -> usize;
|
||||||
fn p() -> Self::Unsigned;
|
fn p() -> Self::Unsigned;
|
||||||
@@ -18,6 +19,7 @@ pub trait EllipticCurve {
|
|||||||
fn Gy() -> Self::Signed;
|
fn Gy() -> Self::Signed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,PartialEq)]
|
||||||
pub enum P192 {}
|
pub enum P192 {}
|
||||||
|
|
||||||
impl EllipticCurve for P192 {
|
impl EllipticCurve for P192 {
|
||||||
@@ -61,6 +63,7 @@ impl EllipticCurve for P192 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,PartialEq)]
|
||||||
pub enum P224 {}
|
pub enum P224 {}
|
||||||
|
|
||||||
impl EllipticCurve for P224 {
|
impl EllipticCurve for P224 {
|
||||||
@@ -143,6 +146,7 @@ impl EllipticCurve for P224 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,PartialEq)]
|
||||||
pub enum P256 {}
|
pub enum P256 {}
|
||||||
|
|
||||||
impl EllipticCurve for P256 {
|
impl EllipticCurve for P256 {
|
||||||
@@ -226,6 +230,7 @@ impl EllipticCurve for P256 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,PartialEq)]
|
||||||
pub enum P384 {}
|
pub enum P384 {}
|
||||||
|
|
||||||
impl EllipticCurve for P384 {
|
impl EllipticCurve for P384 {
|
||||||
@@ -322,6 +327,7 @@ impl EllipticCurve for P384 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,PartialEq)]
|
||||||
pub enum P521 {}
|
pub enum P521 {}
|
||||||
|
|
||||||
impl EllipticCurve for P521 {
|
impl EllipticCurve for P521 {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ pub trait ECCPoint : Sized {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,PartialEq)]
|
||||||
pub struct Point<T: EllipticCurve>
|
pub struct Point<T: EllipticCurve>
|
||||||
{
|
{
|
||||||
pub x: T::Signed,
|
pub x: T::Signed,
|
||||||
|
|||||||
@@ -5,9 +5,18 @@ use dsa::rfc6979::{DSASignature,KIterator,bits2int};
|
|||||||
use ecdsa::curve::{EllipticCurve,P192,P224,P256,P384,P521};
|
use ecdsa::curve::{EllipticCurve,P192,P224,P256,P384,P521};
|
||||||
use ecdsa::point::{ECCPoint,Point};
|
use ecdsa::point::{ECCPoint,Point};
|
||||||
use hmac::{Hmac,Mac};
|
use hmac::{Hmac,Mac};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub struct ECCPrivateKey<Curve: EllipticCurve> {
|
pub struct ECCPrivateKey<Curve: EllipticCurve> {
|
||||||
d: Curve::Unsigned
|
pub(crate) d: Curve::Unsigned
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Curve: EllipticCurve> fmt::Debug for ECCPrivateKey<Curve> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(),fmt::Error>
|
||||||
|
{
|
||||||
|
f.write_str("<ECCPrivateKey>")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ECDSAPrivate {
|
pub enum ECDSAPrivate {
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ use hmac::{Hmac,Mac};
|
|||||||
use simple_asn1::{ASN1Block,ASN1Class,ASN1DecodeErr,ASN1EncodeErr,FromASN1,ToASN1};
|
use simple_asn1::{ASN1Block,ASN1Class,ASN1DecodeErr,ASN1EncodeErr,FromASN1,ToASN1};
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
|
|
||||||
|
#[derive(Debug,PartialEq)]
|
||||||
pub struct ECCPublicKey<Curve: EllipticCurve> {
|
pub struct ECCPublicKey<Curve: EllipticCurve> {
|
||||||
q: Point<Curve>
|
pub(crate) q: Point<Curve>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ECDSAPublic {
|
pub enum ECDSAPublic {
|
||||||
|
|||||||
147
src/ssh/ecdsa.rs
147
src/ssh/ecdsa.rs
@@ -1,5 +1,8 @@
|
|||||||
|
use cryptonum::signed::*;
|
||||||
use cryptonum::unsigned::*;
|
use cryptonum::unsigned::*;
|
||||||
use ecdsa::{ECDSAPair,ECDSAPublic,ECCPublicKey,ECDSAPrivate,ECCPrivateKey};
|
use ecdsa::{ECDSAPair,ECDSAPublic,ECCPublicKey,ECDSAPrivate,ECCPrivateKey};
|
||||||
|
use ecdsa::curve::{P256,P384,P521};
|
||||||
|
use ecdsa::point::Point;
|
||||||
use std::io::{Read,Write};
|
use std::io::{Read,Write};
|
||||||
use ssh::errors::{SSHKeyParseError,SSHKeyRenderError};
|
use ssh::errors::{SSHKeyParseError,SSHKeyRenderError};
|
||||||
use ssh::frame::*;
|
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
|
// 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
|
// one), but we need to infer what kind of key this is, and this appears
|
||||||
// to be the easiest / fastest way.
|
// to be the easiest / fastest way.
|
||||||
let mut ebuf = parse_openssh_buffer(inp)?;
|
let curve = parse_openssh_string(inp)?;
|
||||||
let mut ibuf = parse_openssh_buffer(inp)?;
|
match curve.as_ref() {
|
||||||
|
"nistp256" => {
|
||||||
while ebuf[0] == 0 { ebuf.remove(0); }
|
let val = parse_openssh_buffer(inp)?;
|
||||||
while ibuf[0] == 0 { ibuf.remove(0); }
|
if val[0] != 4 || val.len() != 65 {
|
||||||
|
return Err(SSHKeyParseError::InvalidECPointCompression);
|
||||||
println!("ebuf: {:?}", ebuf);
|
}
|
||||||
println!("ibuf: {:?}", ibuf);
|
let x = U256::from_bytes(&val[1..33]);
|
||||||
panic!("parse public info")
|
let y = U256::from_bytes(&val[33..]);
|
||||||
|
let p = Point::<P256>{ x: I256::from(x), y: I256::from(y) };
|
||||||
|
let pbl = ECCPublicKey::<P256>::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::<P384>{ x: I384::from(x), y: I384::from(y) };
|
||||||
|
let pbl = ECCPublicKey::<P384>::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::<P521>{ x: I576::from(x), y: I576::from(y) };
|
||||||
|
let pbl = ECCPublicKey::<P521>::new(p);
|
||||||
|
Ok(ECDSAPublic::P521(pbl))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(SSHKeyParseError::UnknownECDSACurve(curve))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_ssh_private_info<I: Read>(inp: &mut I) -> Result<(Self::Private,String),SSHKeyParseError>
|
fn parse_ssh_private_info<I: Read>(inp: &mut I) -> Result<(Self::Private,String),SSHKeyParseError>
|
||||||
@@ -39,11 +72,31 @@ impl SSHKey for ECDSAPair {
|
|||||||
if check1 != check2 {
|
if check1 != check2 {
|
||||||
return Err(SSHKeyParseError::PrivateKeyCorruption);
|
return Err(SSHKeyParseError::PrivateKeyCorruption);
|
||||||
}
|
}
|
||||||
let privkey_type = parse_openssh_string(inp)?;
|
let res = match ECDSAPair::parse_ssh_public_info(inp)? {
|
||||||
if !Self::valid_keytype(&privkey_type) {
|
ECDSAPublic::P192(_) => return Err(SSHKeyParseError::PrivateKeyCorruption),
|
||||||
return Err(SSHKeyParseError::InconsistentKeyTypes("ssh-rsa".to_string(), privkey_type));
|
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::<P256>::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::<P384>::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::<P521>::new(d))
|
||||||
|
}
|
||||||
|
};
|
||||||
let comment = parse_openssh_string(inp)?;
|
let comment = parse_openssh_string(inp)?;
|
||||||
for (idx,byte) in inp.bytes().enumerate() {
|
for (idx,byte) in inp.bytes().enumerate() {
|
||||||
if ((idx+1) as u8) != byte? {
|
if ((idx+1) as u8) != byte? {
|
||||||
@@ -51,28 +104,70 @@ impl SSHKey for ECDSAPair {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
panic!("parse private_info")
|
Ok((res, comment))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_ssh_public_info<O: Write>(&self, out: &mut O) -> Result<(),SSHKeyRenderError>
|
fn render_ssh_public_info<O: Write>(&self, out: &mut O) -> Result<(),SSHKeyRenderError>
|
||||||
{
|
{
|
||||||
render_openssh_string(out, "ssh-ecdsa")?;
|
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<O: Write>(&self, out: &mut O, comment: &str) -> Result<(),SSHKeyRenderError>
|
fn render_ssh_private_info<O: Write>(&self, out: &mut O) -> Result<(),SSHKeyRenderError>
|
||||||
{
|
{
|
||||||
render_openssh_u32(out, 0xDEADBEEF)?; // FIXME: Any reason for this to be random?
|
self.render_ssh_public_info(out)?;
|
||||||
render_openssh_u32(out, 0xDEADBEEF)?; // ditto
|
match self {
|
||||||
render_openssh_string(out, "ssh-ecdsa")?;
|
ECDSAPair::P192(_,_) =>
|
||||||
panic!("render private info");
|
return Err(SSHKeyRenderError::IllegalECDSAKeyType("P192".to_string())),
|
||||||
render_openssh_string(out, comment)?;
|
ECDSAPair::P224(_,_) =>
|
||||||
// add some padding (not quite sure why)
|
return Err(SSHKeyRenderError::IllegalECDSAKeyType("P224".to_string())),
|
||||||
let mut i = comment.len();
|
ECDSAPair::P256(_,pr) => { render_openssh_u32(out, 256/8)?; render_number(256, out, &pr.d)?; }
|
||||||
while (i % 16) != 0 {
|
ECDSAPair::P384(_,pr) => { render_openssh_u32(out, 384/8)?; render_number(384, out, &pr.d)?; }
|
||||||
out.write(&[(i - comment.len() + 1) as u8])?;
|
ECDSAPair::P521(_,pr) => { render_openssh_u32(out, 528/8)?; render_number(521, out, &pr.d)?; }
|
||||||
i += 1;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_number<O,N>(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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ pub enum SSHKeyParseError
|
|||||||
InvalidPrivateKeyValue,
|
InvalidPrivateKeyValue,
|
||||||
InvalidPadding,
|
InvalidPadding,
|
||||||
InvalidPublicKeyType,
|
InvalidPublicKeyType,
|
||||||
BrokenPublicKeyLine
|
BrokenPublicKeyLine,
|
||||||
|
UnknownECDSACurve(String),
|
||||||
|
InvalidECPointCompression
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DecodeError> for SSHKeyParseError {
|
impl From<DecodeError> for SSHKeyParseError {
|
||||||
@@ -39,7 +41,8 @@ impl From<io::Error> for SSHKeyParseError {
|
|||||||
pub enum SSHKeyRenderError {
|
pub enum SSHKeyRenderError {
|
||||||
IOError(io::Error),
|
IOError(io::Error),
|
||||||
StringTooLong,
|
StringTooLong,
|
||||||
BufferTooLarge
|
BufferTooLarge,
|
||||||
|
IllegalECDSAKeyType(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<io::Error> for SSHKeyRenderError {
|
impl From<io::Error> for SSHKeyRenderError {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
mod dsa;
|
mod dsa;
|
||||||
|
mod ecdsa;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod frame;
|
mod frame;
|
||||||
mod rsa;
|
mod rsa;
|
||||||
@@ -154,6 +155,8 @@ pub fn write_ssh_keyfile<KP,P>(path: P, x: &KP, comment: &str) -> Result<(),SSHK
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use dsa::{DSAKeyPair,DSAPublicKey,L1024N160};
|
use dsa::{DSAKeyPair,DSAPublicKey,L1024N160};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
use ecdsa::ECDSAPair;
|
||||||
|
#[cfg(test)]
|
||||||
use rsa::{RSAPair,RSAPublic,SIGNING_HASH_SHA256};
|
use rsa::{RSAPair,RSAPublic,SIGNING_HASH_SHA256};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
@@ -259,3 +262,70 @@ fn rsa_examples() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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::<ECDSAPair,String>(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::<Sha256>(&buffer);
|
||||||
|
assert!(pu.verify::<Sha256>(&buffer, &sig));
|
||||||
|
}
|
||||||
|
ECDSAPair::P384(ref pu, ref pr) => {
|
||||||
|
let sig = pr.sign::<Sha256>(&buffer);
|
||||||
|
assert!(pu.verify::<Sha256>(&buffer, &sig));
|
||||||
|
}
|
||||||
|
ECDSAPair::P521(ref pu, ref pr) => {
|
||||||
|
let sig = pr.sign::<Sha256>(&buffer);
|
||||||
|
assert!(pu.verify::<Sha256>(&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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user