Support reading and writing ED25519 SSH keys.
This commit is contained in:
58
src/ssh/ed25519.rs
Normal file
58
src/ssh/ed25519.rs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
use ed25519::{ED25519KeyPair,ED25519Private,ED25519Public};
|
||||||
|
use std::io::{Read,Write};
|
||||||
|
use ssh::errors::{SSHKeyParseError,SSHKeyRenderError};
|
||||||
|
use ssh::frame::*;
|
||||||
|
use ssh::SSHKey;
|
||||||
|
|
||||||
|
impl SSHKey for ED25519KeyPair {
|
||||||
|
fn valid_keytype(s: &str) -> bool {
|
||||||
|
(s == "ssh-ed25519")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_ssh_public_info<I: Read>(inp: &mut I) -> Result<Self::Public,SSHKeyParseError>
|
||||||
|
{
|
||||||
|
let pubkey_type = parse_openssh_string(inp)?;
|
||||||
|
if !Self::valid_keytype(&pubkey_type) {
|
||||||
|
return Err(SSHKeyParseError::UnknownKeyType(pubkey_type));
|
||||||
|
}
|
||||||
|
let pubkey_bytes = parse_openssh_buffer(inp)?;
|
||||||
|
Ok(ED25519Public::new(&pubkey_bytes)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 public = ED25519KeyPair::parse_ssh_public_info(inp)?;
|
||||||
|
let private_bytes = parse_openssh_buffer(inp)?;
|
||||||
|
let private = ED25519Private::from_seed(&private_bytes[0..32]);
|
||||||
|
let comment = parse_openssh_string(inp)?;
|
||||||
|
for (idx,byte) in inp.bytes().enumerate() {
|
||||||
|
if ((idx+1) as u8) != byte? {
|
||||||
|
return Err(SSHKeyParseError::InvalidPadding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(public, ED25519Public::from(&private));
|
||||||
|
|
||||||
|
Ok((private, comment))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_ssh_public_info<O: Write>(&self, out: &mut O) -> Result<(),SSHKeyRenderError>
|
||||||
|
{
|
||||||
|
render_openssh_string(out, "ssh-ed25519")?;
|
||||||
|
render_openssh_buffer(out, &self.public.to_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_ssh_private_info<O: Write>(&self, out: &mut O) -> Result<(),SSHKeyRenderError>
|
||||||
|
{
|
||||||
|
self.render_ssh_public_info(out)?;
|
||||||
|
let mut private_bytes = self.private.to_bytes();
|
||||||
|
private_bytes.append(&mut self.public.to_bytes());
|
||||||
|
render_openssh_buffer(out, &private_bytes)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
use base64::DecodeError;
|
use base64::DecodeError;
|
||||||
|
use ed25519::ED25519PublicImportError;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -37,6 +38,15 @@ impl From<io::Error> for SSHKeyParseError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ED25519PublicImportError> for SSHKeyParseError {
|
||||||
|
fn from(e: ED25519PublicImportError) -> SSHKeyParseError {
|
||||||
|
match e {
|
||||||
|
ED25519PublicImportError::WrongNumberOfBytes(_) =>
|
||||||
|
SSHKeyParseError::InvalidPublicKeyMaterial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SSHKeyRenderError {
|
pub enum SSHKeyRenderError {
|
||||||
IOError(io::Error),
|
IOError(io::Error),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
mod dsa;
|
mod dsa;
|
||||||
mod ecdsa;
|
mod ecdsa;
|
||||||
|
mod ed25519;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod frame;
|
mod frame;
|
||||||
mod rsa;
|
mod rsa;
|
||||||
@@ -157,6 +158,8 @@ use dsa::{DSAKeyPair,DSAPublicKey,L1024N160};
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use ecdsa::ECDSAPair;
|
use ecdsa::ECDSAPair;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
use ed25519::ED25519KeyPair;
|
||||||
|
#[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;
|
||||||
@@ -329,3 +332,39 @@ fn ecdsa_examples() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[test]
|
||||||
|
fn ed25519_examples() {
|
||||||
|
let test_files = ["ed25519-1", "ed25519-2", "ed25519-3"];
|
||||||
|
|
||||||
|
for file in test_files.iter() {
|
||||||
|
let path = format!("testdata/ssh/{}",file);
|
||||||
|
match load_ssh_keyfile::<ED25519KeyPair,String>(path) {
|
||||||
|
Err(e) =>
|
||||||
|
assert!(false, "SSH ED25519 parse error: {:?}", e),
|
||||||
|
Ok((keypair,comment)) => {
|
||||||
|
// first see if this roundtrips
|
||||||
|
let buffer = vec![0,1,2,4,5,6,9];
|
||||||
|
let sig = keypair.private.sign(&buffer);
|
||||||
|
assert!(keypair.public.verify(&buffer, &sig));
|
||||||
|
match encode_ssh(&keypair, &comment) {
|
||||||
|
Err(e) =>
|
||||||
|
assert!(false, "SSH ED25519 encoding error: {:?}", e),
|
||||||
|
Ok(coded) => {
|
||||||
|
match decode_ssh(&coded) {
|
||||||
|
Err(e) =>
|
||||||
|
assert!(false, "SSSH ECDSA redecoding error: {:?}", e),
|
||||||
|
Ok((keypair2, comment2)) => {
|
||||||
|
let _ : ED25519KeyPair = keypair2;
|
||||||
|
assert_eq!(keypair.public, keypair2.public, "public key mismatch");
|
||||||
|
assert_eq!(keypair.private, keypair2.private, "public key mismatch");
|
||||||
|
assert_eq!(comment, comment2, "comment mismatch");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user