use ring::signature;
use snafu::ResultExt;
pub enum PrivateKey {
PEM(String),
DER(Vec<u8>),
Raw(Vec<u8>),
}
impl PrivateKey {
pub fn read(&self) -> crate::Result<PrivateKeyBytes> {
Ok(match self {
Self::PEM(s) => {
let p = pem::parse(s).context(crate::InvalidPemSnafu)?;
PrivateKeyBytes::PKCS8v1v2(p.contents)
}
Self::DER(b) => PrivateKeyBytes::PKCS8v1v2(b.clone()),
Self::Raw(b) => PrivateKeyBytes::PKCS8v2(b.clone()),
})
}
}
pub enum PrivateKeyBytes {
PKCS8v1v2(Vec<u8>),
PKCS8v2(Vec<u8>),
}
pub enum PublicKey {
PEM(String),
DER(Vec<u8>),
Raw(Vec<u8>),
}
impl PublicKey {
pub fn read(&self) -> crate::Result<PublicKeyBytes> {
Ok(PublicKeyBytes::Raw(match self {
Self::PEM(s) => {
let p = pem::parse(s).context(crate::InvalidPemSnafu)?;
p.contents.iter().skip(12).copied().collect::<Vec<_>>()
}
Self::DER(bs) => bs.iter().skip(12).copied().collect::<Vec<_>>(),
Self::Raw(bs) => bs.clone(),
}))
}
}
pub enum PublicKeyBytes {
Raw(Vec<u8>),
}
pub fn verify(msg: &[u8], sig: &[u8], pubkey: &PublicKeyBytes) -> crate::Result<()> {
let PublicKeyBytes::Raw(key_bytes) = pubkey;
let peer_public_key = signature::UnparsedPublicKey::new(&signature::ED25519, &key_bytes);
peer_public_key
.verify(msg, sig)
.context(crate::SignatureFailedSnafu)?;
Ok(())
}
pub fn sign(msg: &[u8], prkey: &PrivateKeyBytes) -> crate::Result<Vec<u8>> {
let key_pair = match prkey {
PrivateKeyBytes::PKCS8v1v2(b) => {
signature::Ed25519KeyPair::from_pkcs8_maybe_unchecked(b).context(crate::InvalidKeySnafu)
}
PrivateKeyBytes::PKCS8v2(b) => {
signature::Ed25519KeyPair::from_pkcs8(b).context(crate::InvalidKeySnafu)
}
}?;
let sig = key_pair.sign(msg);
Ok(sig.as_ref().to_vec())
}
#[cfg(feature = "base64")]
pub fn sign_base64(msg: &[u8], privkey: &PrivateKeyBytes) -> crate::Result<String> {
use base64::{prelude::BASE64_STANDARD, Engine};
let sig = sign(msg, privkey)?;
Ok(BASE64_STANDARD.encode(sig))
}
#[cfg(feature = "base64")]
pub fn verify_base64(msg: &[u8], sig: &str, pubkey: &PublicKeyBytes) -> crate::Result<()> {
use base64::{prelude::BASE64_STANDARD, Engine};
let sig = BASE64_STANDARD
.decode(sig)
.context(crate::DecodeFailedSnafu)?;
verify(msg, &sig, pubkey)
}
#[cfg(test)]
mod tests {
use ring::{rand, signature::KeyPair};
use super::*;
use std::{fs, path::Path, process::Command};
#[test]
fn test_negatives() {
let privkey = PrivateKey::PEM(
String::from_utf8_lossy(&fs::read("fixtures/ed.private.pem").unwrap()).to_string(),
)
.read()
.unwrap();
let sig = sign(b"hello world", &privkey).expect("should sign");
let pubkey = PublicKey::PEM(
String::from_utf8_lossy(&fs::read("fixtures/ed.public.pem").unwrap()).to_string(),
);
verify(b"hello world_", &sig, &pubkey.read().unwrap()).expect_err("should fail");
let pubkey = PublicKey::PEM(
String::from_utf8_lossy(&fs::read("fixtures/rsa.public.pem").unwrap()).to_string(),
);
verify(b"hello world", &sig, &pubkey.read().unwrap()).expect_err("should fail");
let pubkey = PublicKey::DER(fs::read("fixtures/ed.public.pem").unwrap());
verify(b"hello world", &sig, &pubkey.read().unwrap()).expect_err("should fail");
}
#[test]
fn test_pem() {
let privkey = PrivateKey::PEM(
String::from_utf8_lossy(&fs::read("fixtures/ed.private.pem").unwrap()).to_string(),
)
.read()
.unwrap();
let sig = sign(b"hello world", &privkey).expect("should sign");
let pubkey = PublicKey::PEM(
String::from_utf8_lossy(&fs::read("fixtures/ed.public.pem").unwrap()).to_string(),
);
verify(b"hello world", &sig, &pubkey.read().unwrap()).expect("should verify");
}
#[cfg(feature = "base64")]
#[test]
fn test_base64() {
let privkey = PrivateKey::PEM(
String::from_utf8_lossy(&fs::read("fixtures/ed.private.pem").unwrap()).to_string(),
)
.read()
.unwrap();
let sig = sign_base64(b"hello world", &privkey).expect("should sign");
let pubkey = PublicKey::PEM(
String::from_utf8_lossy(&fs::read("fixtures/ed.public.pem").unwrap()).to_string(),
);
verify_base64(b"hello world", &sig, &pubkey.read().unwrap()).expect("should verify");
}
#[test]
fn test_pkcs8v2() {
let rng = rand::SystemRandom::new();
let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(&rng)
.unwrap()
.as_ref()
.to_vec();
let key_pair = signature::Ed25519KeyPair::from_pkcs8(pkcs8_bytes.as_ref()).unwrap();
let sig = sign(
b"hello world",
&PrivateKey::Raw(pkcs8_bytes).read().unwrap(),
)
.expect("should sign");
let pubkey = PublicKey::Raw(key_pair.public_key().as_ref().to_vec());
verify(b"hello world", &sig, &pubkey.read().unwrap()).expect("should verify");
}
#[test]
fn test_nodejs_signed() {
let sig = fs::read("fixtures/ed.sign-me.txt.nodejs-sig").expect("file should exist");
let pubkey = PublicKey::PEM(
String::from_utf8_lossy(&fs::read("fixtures/ed.public.pem").unwrap()).to_string(),
);
let msg = fs::read("fixtures/sign-me.txt").expect("file should exist");
verify(&msg, &sig, &pubkey.read().unwrap()).expect("should verify");
}
#[test]
fn test_nodejs_verified() {
let privkey = PrivateKey::PEM(
String::from_utf8_lossy(&fs::read("fixtures/ed.private.pem").unwrap()).to_string(),
)
.read()
.unwrap();
let msg = fs::read("fixtures/sign-me.txt").expect("file should exist");
let sig = sign(&msg, &privkey).expect("should sign");
let sigfile = "fixtures/ed.sign-me.txt.ring-sig";
if Path::new(sigfile).exists() {
fs::remove_file(sigfile).expect("should remove");
}
fs::write(sigfile, sig).expect("should write sig file");
let out = Command::new("node")
.args(["fixtures/verify.js"])
.output()
.expect("failed to execute process");
let ok = String::from_utf8_lossy(&out.stdout);
println!("ok: {ok}");
assert!(ok.contains("OK"));
}
}