use super::HostKeyAlgorithm;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "alloc")]
use purecrypto::ec::{Ed25519PrivateKey, Ed25519PublicKey, Ed25519Signature};
#[cfg(feature = "alloc")]
use super::{HostKey, HostKeyVerify};
#[cfg(feature = "alloc")]
use crate::error::{Error, Result};
#[cfg(feature = "alloc")]
use crate::format::{Reader, Writer};
pub struct SshEd25519;
impl HostKeyAlgorithm for SshEd25519 {
const NAME: &'static str = "ssh-ed25519";
}
#[cfg(feature = "alloc")]
pub struct Ed25519HostKey {
private: Option<Ed25519PrivateKey>,
public: Ed25519PublicKey,
}
#[cfg(feature = "alloc")]
impl Ed25519HostKey {
pub fn from_seed(seed: [u8; 32]) -> Self {
let private = Ed25519PrivateKey::from_bytes(seed);
let public = private.public_key();
Ed25519HostKey {
private: Some(private),
public,
}
}
pub fn from_public(pk: [u8; 32]) -> Self {
Ed25519HostKey {
private: None,
public: Ed25519PublicKey::from_bytes(pk),
}
}
pub fn public_bytes(&self) -> [u8; 32] {
self.public.to_bytes()
}
}
#[cfg(feature = "alloc")]
impl HostKey for Ed25519HostKey {
fn algorithm(&self) -> &'static str {
SshEd25519::NAME
}
fn public_blob(&self) -> Vec<u8> {
let mut w = Writer::with_capacity(4 + 11 + 4 + 32);
w.write_string(SshEd25519::NAME.as_bytes());
w.write_string(&self.public.to_bytes());
w.into_vec()
}
fn sign(&self, msg: &[u8]) -> Result<Vec<u8>> {
let sk = self
.private
.as_ref()
.ok_or(Error::Crypto("ed25519: no private key"))?;
let sig = sk.sign(msg);
let bytes = sig.to_bytes();
let mut w = Writer::with_capacity(4 + 11 + 4 + 64);
w.write_string(SshEd25519::NAME.as_bytes());
w.write_string(&bytes);
Ok(w.into_vec())
}
}
#[cfg(feature = "alloc")]
impl HostKeyVerify for Ed25519HostKey {
fn algorithm(&self) -> &'static str {
SshEd25519::NAME
}
fn verify(&self, msg: &[u8], sig_blob: &[u8]) -> Result<()> {
let mut r = Reader::new(sig_blob);
let name = r.read_string()?;
if name != SshEd25519::NAME.as_bytes() {
return Err(Error::Format("ed25519: signature algorithm mismatch"));
}
let raw = r.read_string()?;
if raw.len() != 64 {
return Err(Error::Format("ed25519: signature length"));
}
if !r.is_empty() {
return Err(Error::Format("ed25519: signature trailing data"));
}
let mut buf = [0u8; 64];
buf.copy_from_slice(raw);
let sig = Ed25519Signature::from_bytes(buf);
self.public
.verify(msg, &sig)
.map_err(|_| Error::BadSignature)
}
fn from_public_blob(blob: &[u8]) -> Result<Self> {
let mut r = Reader::new(blob);
let name = r.read_string()?;
if name != SshEd25519::NAME.as_bytes() {
return Err(Error::Format("ed25519: public key type mismatch"));
}
let raw = r.read_string()?;
if raw.len() != 32 {
return Err(Error::Format("ed25519: public key length"));
}
if !r.is_empty() {
return Err(Error::Format("ed25519: public key trailing data"));
}
let mut buf = [0u8; 32];
buf.copy_from_slice(raw);
Ok(Ed25519HostKey {
private: None,
public: Ed25519PublicKey::from_bytes(buf),
})
}
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::*;
const RFC8032_SEED: [u8; 32] = [
0x9d, 0x61, 0xb1, 0x9d, 0xef, 0xfd, 0x5a, 0x60, 0xba, 0x84, 0x4a, 0xf4, 0x92, 0xec, 0x2c,
0xc4, 0x44, 0x49, 0xc5, 0x69, 0x7b, 0x32, 0x69, 0x19, 0x70, 0x3b, 0xac, 0x03, 0x1c, 0xae,
0x7f, 0x60,
];
const RFC8032_PUB: [u8; 32] = [
0xd7, 0x5a, 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7, 0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64, 0x07,
0x3a, 0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25, 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07,
0x51, 0x1a,
];
#[test]
fn public_blob_roundtrip() {
let hk = Ed25519HostKey::from_seed(RFC8032_SEED);
let blob = hk.public_blob();
let parsed = Ed25519HostKey::from_public_blob(&blob).unwrap();
assert_eq!(parsed.public_bytes(), RFC8032_PUB);
}
#[test]
fn sign_verify_roundtrip() {
let hk = Ed25519HostKey::from_seed(RFC8032_SEED);
let sig = hk.sign(b"hello ssh").unwrap();
hk.verify(b"hello ssh", &sig).unwrap();
assert!(hk.verify(b"goodbye ssh", &sig).is_err());
}
#[test]
fn verify_rejects_wrong_algorithm() {
let hk = Ed25519HostKey::from_seed(RFC8032_SEED);
let mut bad = Writer::new();
bad.write_string(b"ssh-rsa");
bad.write_string(&[0u8; 64]);
assert!(hk.verify(b"x", &bad.into_vec()).is_err());
}
#[test]
fn rejects_bad_lengths() {
let hk = Ed25519HostKey::from_seed(RFC8032_SEED);
let mut bad = Writer::new();
bad.write_string(b"ssh-ed25519");
bad.write_string(&[0u8; 63]);
assert!(hk.verify(b"x", &bad.into_vec()).is_err());
}
}