use ed25519_dalek::SigningKey;
use ed25519_dalek::pkcs8::spki::EncodePublicKey;
use ed25519_dalek::pkcs8::spki::der::pem::LineEnding;
use ed25519_dalek::pkcs8::{DecodePrivateKey, EncodePrivateKey};
use zeroize::Zeroizing;
use crate::error::{Error, Result};
pub struct GeneratedKeyPair {
pub private_pem: Zeroizing<String>,
pub public_pem: String,
}
pub fn generate_key_pair() -> Result<GeneratedKeyPair> {
let mut seed = Zeroizing::new([0u8; 32]);
getrandom::fill(seed.as_mut_slice()).map_err(|e| Error::KeyGeneration {
message: format!("could not read OS randomness: {e}"),
})?;
let signing = SigningKey::from_bytes(&seed);
let private_pem = signing
.to_pkcs8_pem(LineEnding::LF)
.map_err(|e| Error::KeyGeneration {
message: format!("could not encode private key: {e}"),
})?;
let public_pem = signing
.verifying_key()
.to_public_key_pem(LineEnding::LF)
.map_err(|e| Error::KeyGeneration {
message: format!("could not encode public key: {e}"),
})?;
Ok(GeneratedKeyPair {
private_pem,
public_pem,
})
}
pub fn public_pem_from_private_pem(private_pem: &str) -> Result<String> {
let signing = SigningKey::from_pkcs8_pem(private_pem)
.map_err(|e| Error::key(format!("could not parse PKCS#8 PEM Ed25519 key: {e}")))?;
signing
.verifying_key()
.to_public_key_pem(LineEnding::LF)
.map_err(|e| Error::KeyGeneration {
message: format!("could not encode public key: {e}"),
})
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn generates_distinct_pem_pairs() {
let a = generate_key_pair().unwrap();
let b = generate_key_pair().unwrap();
assert!(a.private_pem.starts_with("-----BEGIN PRIVATE KEY-----"));
assert!(a.public_pem.starts_with("-----BEGIN PUBLIC KEY-----"));
assert_ne!(*a.private_pem, *b.private_pem);
assert_ne!(a.public_pem, b.public_pem);
}
#[test]
fn derives_the_matching_public_key_from_a_private_pem() {
let pair = generate_key_pair().unwrap();
let derived = public_pem_from_private_pem(&pair.private_pem).unwrap();
assert_eq!(derived, pair.public_pem);
}
#[test]
fn rejects_an_unparseable_private_pem() {
assert!(public_pem_from_private_pem("-----BEGIN PRIVATE KEY-----\nnope\n").is_err());
}
}