use ed25519_dalek::{
Signature, Signer as DalekSigner, SigningKey as DalekSigningKey, Verifier as DalekVerifier,
VerifyingKey as DalekVerifyingKey,
};
use rand_core::OsRng;
use serde::{Deserialize, Serialize};
use zeroize::Zeroizing;
use crate::entry::AuditEntry;
use crate::hasher::{hex_decode, hex_encode, hex_encode_slice};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum SignatureAlgorithm {
#[serde(rename = "ed25519")]
Ed25519,
#[serde(rename = "ml-dsa-65")]
MlDsa65,
#[serde(rename = "ed25519+ml-dsa-65")]
Ed25519MlDsa65,
}
impl SignatureAlgorithm {
#[inline]
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Ed25519 => "ed25519",
Self::MlDsa65 => "ml-dsa-65",
Self::Ed25519MlDsa65 => "ed25519+ml-dsa-65",
}
}
}
impl std::fmt::Display for SignatureAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl std::str::FromStr for SignatureAlgorithm {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ed25519" => Ok(Self::Ed25519),
"ml-dsa-65" => Ok(Self::MlDsa65),
"ed25519+ml-dsa-65" => Ok(Self::Ed25519MlDsa65),
other => Err(format!("unknown signature algorithm: {other}")),
}
}
}
pub trait EntrySigner: Send + Sync {
fn algorithm(&self) -> SignatureAlgorithm;
fn verifying_key_bytes(&self) -> Vec<u8>;
fn sign_bytes(&self, message: &[u8]) -> Vec<u8>;
fn sign_entry(&self, entry: &AuditEntry) -> EntrySignature {
let hash = entry.hash();
let sig_bytes = self.sign_bytes(hash.as_bytes());
EntrySignature {
entry_hash: hash.to_owned(),
signature: hex_encode_slice(&sig_bytes),
verifying_key: hex_encode_slice(&self.verifying_key_bytes()),
key_id: None,
algorithm: Some(self.algorithm().as_str().to_owned()),
}
}
fn sign_entry_with_key_id(&self, entry: &AuditEntry, key_id: String) -> EntrySignature {
let mut sig = self.sign_entry(entry);
sig.key_id = Some(key_id);
sig
}
}
pub trait EntryVerifier: Send + Sync {
fn algorithm(&self) -> SignatureAlgorithm;
fn verifying_key_bytes(&self) -> Vec<u8>;
fn verify_bytes(&self, message: &[u8], signature: &[u8]) -> bool;
fn verify_entry_signature(&self, entry: &AuditEntry, sig: &EntrySignature) -> bool {
if !crate::entry::constant_time_eq(entry.hash(), &sig.entry_hash) {
return false;
}
let sig_bytes = match hex_decode(&sig.signature) {
Some(b) => b,
None => return false,
};
self.verify_bytes(sig.entry_hash.as_bytes(), &sig_bytes)
}
}
#[derive(Debug)]
pub struct SigningKey {
inner: DalekSigningKey,
}
impl Drop for SigningKey {
fn drop(&mut self) {
self.inner = DalekSigningKey::from_bytes(&[0u8; 32]);
}
}
#[derive(Debug, Clone)]
pub struct VerifyingKey {
inner: DalekVerifyingKey,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct EntrySignature {
pub entry_hash: String,
pub signature: String,
pub verifying_key: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub key_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub algorithm: Option<String>,
}
impl SigningKey {
pub fn generate() -> Self {
let mut rng = OsRng;
Self {
inner: DalekSigningKey::generate(&mut rng),
}
}
pub fn from_bytes(bytes: &[u8; 32]) -> Self {
Self {
inner: DalekSigningKey::from_bytes(bytes),
}
}
#[must_use]
pub fn to_bytes(&self) -> Zeroizing<[u8; 32]> {
Zeroizing::new(self.inner.to_bytes())
}
#[must_use]
pub fn verifying_key(&self) -> VerifyingKey {
VerifyingKey {
inner: self.inner.verifying_key(),
}
}
pub fn sign(&self, entry: &AuditEntry) -> EntrySignature {
let hash = entry.hash();
let sig: Signature = self.inner.sign(hash.as_bytes());
EntrySignature {
entry_hash: hash.to_owned(),
signature: hex_encode(sig.to_bytes()),
verifying_key: hex_encode(self.inner.verifying_key().to_bytes()),
key_id: None,
algorithm: Some(SignatureAlgorithm::Ed25519.as_str().to_owned()),
}
}
pub fn sign_with_key_id(
&self,
entry: &AuditEntry,
key_id: impl Into<String>,
) -> EntrySignature {
let mut sig = self.sign(entry);
sig.key_id = Some(key_id.into());
sig
}
}
impl VerifyingKey {
pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, crate::LibroError> {
let key = DalekVerifyingKey::from_bytes(bytes)
.map_err(|e| crate::LibroError::Store(format!("invalid verifying key: {e}")))?;
Ok(Self { inner: key })
}
#[must_use]
pub fn to_bytes(&self) -> [u8; 32] {
self.inner.to_bytes()
}
#[must_use]
pub fn to_hex(&self) -> String {
hex_encode(self.inner.to_bytes())
}
}
impl Serialize for VerifyingKey {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_hex())
}
}
impl<'de> Deserialize<'de> for VerifyingKey {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let hex = String::deserialize(deserializer)?;
let bytes = hex_decode(&hex)
.ok_or_else(|| serde::de::Error::custom("invalid hex for verifying key"))?;
let array: [u8; 32] = bytes
.try_into()
.map_err(|_| serde::de::Error::custom("verifying key must be 32 bytes"))?;
VerifyingKey::from_bytes(&array).map_err(serde::de::Error::custom)
}
}
impl EntrySignature {
#[must_use]
pub fn verify(&self, entry: &AuditEntry, key: &VerifyingKey) -> bool {
if !crate::entry::constant_time_eq(entry.hash(), &self.entry_hash) {
return false;
}
let sig_bytes = match hex_decode(&self.signature) {
Some(b) => b,
None => return false,
};
let sig_array: [u8; 64] = match sig_bytes.try_into() {
Ok(a) => a,
Err(_) => return false,
};
let sig = Signature::from_bytes(&sig_array);
key.inner.verify(self.entry_hash.as_bytes(), &sig).is_ok()
}
#[must_use]
pub fn verify_with(&self, entry: &AuditEntry, verifier: &dyn EntryVerifier) -> bool {
verifier.verify_entry_signature(entry, self)
}
#[must_use]
pub fn algorithm_parsed(&self) -> Option<SignatureAlgorithm> {
self.algorithm.as_deref()?.parse().ok()
}
}
impl EntrySigner for SigningKey {
fn algorithm(&self) -> SignatureAlgorithm {
SignatureAlgorithm::Ed25519
}
fn verifying_key_bytes(&self) -> Vec<u8> {
self.inner.verifying_key().to_bytes().to_vec()
}
fn sign_bytes(&self, message: &[u8]) -> Vec<u8> {
let sig: Signature = self.inner.sign(message);
sig.to_bytes().to_vec()
}
}
impl EntryVerifier for VerifyingKey {
fn algorithm(&self) -> SignatureAlgorithm {
SignatureAlgorithm::Ed25519
}
fn verifying_key_bytes(&self) -> Vec<u8> {
self.inner.to_bytes().to_vec()
}
fn verify_bytes(&self, message: &[u8], signature: &[u8]) -> bool {
let sig_array: [u8; 64] = match signature.try_into() {
Ok(a) => a,
Err(_) => return false,
};
let sig = Signature::from_bytes(&sig_array);
self.inner.verify(message, &sig).is_ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::entry::EventSeverity;
#[test]
fn sign_and_verify() {
let key = SigningKey::generate();
let entry = AuditEntry::new(
EventSeverity::Info,
"daimon",
"start",
serde_json::json!({}),
"",
);
let sig = key.sign(&entry);
assert!(sig.verify(&entry, &key.verifying_key()));
}
#[test]
fn verify_fails_for_wrong_key() {
let key_a = SigningKey::generate();
let key_b = SigningKey::generate();
let entry = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
let sig = key_a.sign(&entry);
assert!(!sig.verify(&entry, &key_b.verifying_key()));
}
#[test]
fn verify_fails_for_tampered_entry() {
let key = SigningKey::generate();
let entry = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
let sig = key.sign(&entry);
let other = AuditEntry::new(EventSeverity::Info, "s", "b", serde_json::json!({}), "");
assert!(!sig.verify(&other, &key.verifying_key()));
}
#[test]
fn verify_fails_for_tampered_signature() {
let key = SigningKey::generate();
let entry = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
let mut sig = key.sign(&entry);
sig.signature = "00".repeat(64);
assert!(!sig.verify(&entry, &key.verifying_key()));
}
#[test]
fn key_roundtrip() {
let key = SigningKey::generate();
let bytes = key.to_bytes();
let restored = SigningKey::from_bytes(&bytes);
assert_eq!(
key.verifying_key().to_hex(),
restored.verifying_key().to_hex()
);
}
#[test]
fn verifying_key_roundtrip() {
let key = SigningKey::generate();
let vk = key.verifying_key();
let bytes = vk.to_bytes();
let restored = VerifyingKey::from_bytes(&bytes).unwrap();
assert_eq!(vk.to_hex(), restored.to_hex());
}
#[test]
fn sign_chain_entries() {
let key = SigningKey::generate();
let vk = key.verifying_key();
let e1 = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
let e2 = AuditEntry::new(
EventSeverity::Info,
"s",
"b",
serde_json::json!({}),
e1.hash(),
);
let sig1 = key.sign(&e1);
let sig2 = key.sign(&e2);
assert!(sig1.verify(&e1, &vk));
assert!(sig2.verify(&e2, &vk));
assert!(!sig1.verify(&e2, &vk));
assert!(!sig2.verify(&e1, &vk));
}
#[test]
fn signature_contains_entry_hash() {
let key = SigningKey::generate();
let entry = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
let sig = key.sign(&entry);
assert_eq!(sig.entry_hash, entry.hash());
}
#[test]
fn invalid_signature_hex() {
let key = SigningKey::generate();
let entry = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
let mut sig = key.sign(&entry);
sig.signature = "not-hex".to_owned();
assert!(!sig.verify(&entry, &key.verifying_key()));
sig.signature = "ab".to_owned();
assert!(!sig.verify(&entry, &key.verifying_key()));
}
#[test]
fn invalid_verifying_key_bytes() {
let result = VerifyingKey::from_bytes(&[0u8; 32]);
let _ = result;
}
#[test]
fn sign_with_key_id() {
let key = SigningKey::generate();
let entry = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
let sig = key.sign(&entry);
assert!(sig.key_id.is_none());
let sig = key.sign_with_key_id(&entry, "key-v2");
assert_eq!(sig.key_id.as_deref(), Some("key-v2"));
assert!(sig.verify(&entry, &key.verifying_key()));
let json = serde_json::to_string(&sig).unwrap();
assert!(json.contains("key_id"));
let back: EntrySignature = serde_json::from_str(&json).unwrap();
assert_eq!(back.key_id.as_deref(), Some("key-v2"));
}
#[test]
fn key_id_skipped_when_none() {
let key = SigningKey::generate();
let entry = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
let sig = key.sign(&entry);
let json = serde_json::to_string(&sig).unwrap();
assert!(!json.contains("key_id")); }
#[test]
fn to_bytes_returns_zeroizing() {
let key = SigningKey::generate();
let bytes = key.to_bytes();
let _: &[u8; 32] = &bytes;
let restored = SigningKey::from_bytes(&bytes);
assert_eq!(
key.verifying_key().to_hex(),
restored.verifying_key().to_hex()
);
}
#[test]
fn trait_sign_and_verify() {
let key = SigningKey::generate();
let entry = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
let sig = EntrySigner::sign_entry(&key, &entry);
let vk = key.verifying_key();
assert!(EntryVerifier::verify_entry_signature(&vk, &entry, &sig));
}
#[test]
fn trait_wrong_key_fails() {
let key_a = SigningKey::generate();
let key_b = SigningKey::generate();
let entry = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
let sig = EntrySigner::sign_entry(&key_a, &entry);
assert!(!EntryVerifier::verify_entry_signature(
&key_b.verifying_key(),
&entry,
&sig
));
}
#[test]
fn algorithm_field_present() {
let key = SigningKey::generate();
let entry = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
let sig = key.sign(&entry);
assert_eq!(sig.algorithm.as_deref(), Some("ed25519"));
}
#[test]
fn algorithm_field_serde_roundtrip() {
let key = SigningKey::generate();
let entry = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
let sig = key.sign(&entry);
let json = serde_json::to_string(&sig).unwrap();
assert!(json.contains("\"algorithm\":\"ed25519\""));
let back: EntrySignature = serde_json::from_str(&json).unwrap();
assert_eq!(back.algorithm, sig.algorithm);
}
#[test]
fn algorithm_field_missing_backward_compat() {
let json = r#"{"entry_hash":"abc","signature":"def","verifying_key":"012"}"#;
let sig: EntrySignature = serde_json::from_str(json).unwrap();
assert!(sig.algorithm.is_none());
assert!(sig.key_id.is_none());
}
#[test]
fn algorithm_parsed() {
let key = SigningKey::generate();
let entry = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
let sig = key.sign(&entry);
assert_eq!(sig.algorithm_parsed(), Some(SignatureAlgorithm::Ed25519));
let mut sig2 = sig.clone();
sig2.algorithm = None;
assert_eq!(sig2.algorithm_parsed(), None);
sig2.algorithm = Some("unknown-algo".into());
assert_eq!(sig2.algorithm_parsed(), None);
}
#[test]
fn signature_algorithm_display_roundtrip() {
for alg in [
SignatureAlgorithm::Ed25519,
SignatureAlgorithm::MlDsa65,
SignatureAlgorithm::Ed25519MlDsa65,
] {
let s = alg.to_string();
let parsed: SignatureAlgorithm = s.parse().unwrap();
assert_eq!(alg, parsed);
}
}
#[test]
fn verify_with_matches_verify() {
let key = SigningKey::generate();
let vk = key.verifying_key();
let entry = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
let sig = key.sign(&entry);
let concrete_result = sig.verify(&entry, &vk);
let trait_result = sig.verify_with(&entry, &vk);
assert_eq!(concrete_result, trait_result);
assert!(concrete_result);
}
#[test]
fn dyn_verifier_works() {
let key = SigningKey::generate();
let vk = key.verifying_key();
let entry = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
let sig = key.sign(&entry);
let boxed: Box<dyn EntryVerifier> = Box::new(vk);
assert!(sig.verify_with(&entry, boxed.as_ref()));
}
#[test]
fn trait_sign_entry_with_key_id() {
let key = SigningKey::generate();
let entry = AuditEntry::new(EventSeverity::Info, "s", "a", serde_json::json!({}), "");
let sig = EntrySigner::sign_entry_with_key_id(&key, &entry, "key-v2".to_owned());
assert_eq!(sig.key_id.as_deref(), Some("key-v2"));
assert_eq!(sig.algorithm.as_deref(), Some("ed25519"));
assert!(sig.verify_with(&entry, &key.verifying_key()));
}
}