#![allow(clippy::doc_markdown)]
use crate::error::signature_error;
use crate::{DocumentId, Result};
use super::signature::{Signature, SignatureAlgorithm, SignatureVerification, SignerInfo};
use super::signer::{Signer, Verifier};
#[cfg(feature = "eddsa")]
pub struct EddsaSigner {
signing_key: ed25519_dalek::SigningKey,
signer_info: SignerInfo,
}
#[cfg(feature = "eddsa")]
impl EddsaSigner {
pub fn from_pem(pem: &str, signer_info: SignerInfo) -> Result<Self> {
use ed25519_dalek::pkcs8::DecodePrivateKey;
let signing_key = ed25519_dalek::SigningKey::from_pkcs8_pem(pem)
.map_err(|e| signature_error(format!("Failed to parse EdDSA private key PEM: {e}")))?;
Ok(Self {
signing_key,
signer_info,
})
}
pub fn generate(signer_info: SignerInfo) -> Result<(Self, String)> {
use ed25519_dalek::pkcs8::spki::{der::pem::LineEnding, EncodePublicKey};
let mut key_bytes = [0u8; 32];
getrandom::fill(&mut key_bytes)
.map_err(|e| signature_error(format!("System RNG failed: {e}")))?;
let signing_key = ed25519_dalek::SigningKey::from_bytes(&key_bytes);
let verifying_key = signing_key.verifying_key();
let public_key_pem = verifying_key
.to_public_key_pem(LineEnding::LF)
.map_err(|e| signature_error(format!("Failed to encode EdDSA public key: {e}")))?;
Ok((
Self {
signing_key,
signer_info,
},
public_key_pem,
))
}
pub fn public_key_pem(&self) -> Result<String> {
use ed25519_dalek::pkcs8::spki::{der::pem::LineEnding, EncodePublicKey};
self.signing_key
.verifying_key()
.to_public_key_pem(LineEnding::LF)
.map_err(|e| signature_error(format!("Failed to encode EdDSA public key: {e}")))
}
}
#[cfg(feature = "eddsa")]
impl Signer for EddsaSigner {
fn algorithm(&self) -> SignatureAlgorithm {
SignatureAlgorithm::EdDSA
}
fn signer_info(&self) -> SignerInfo {
self.signer_info.clone()
}
fn sign(&self, document_id: &DocumentId) -> Result<Signature> {
use base64::Engine;
use ed25519_dalek::Signer as EddsaSignerTrait;
if document_id.is_pending() {
return Err(crate::Error::InvalidManifest {
reason: "Cannot sign a pending document ID".to_string(),
});
}
let signature = self.signing_key.sign(document_id.digest());
let value = base64::engine::general_purpose::STANDARD.encode(signature.to_bytes());
let sig_id = format!(
"sig-{}",
&crate::Hasher::hash(crate::HashAlgorithm::Sha256, value.as_bytes()).hex_digest()[..8]
);
Ok(Signature::new(
sig_id,
SignatureAlgorithm::EdDSA,
self.signer_info.clone(),
value,
))
}
}
#[cfg(feature = "eddsa")]
pub struct EddsaVerifier {
verifying_key: ed25519_dalek::VerifyingKey,
}
#[cfg(feature = "eddsa")]
impl EddsaVerifier {
pub fn from_pem(pem: &str) -> Result<Self> {
use ed25519_dalek::pkcs8::DecodePublicKey;
let verifying_key = ed25519_dalek::VerifyingKey::from_public_key_pem(pem)
.map_err(|e| signature_error(format!("Failed to parse EdDSA public key PEM: {e}")))?;
Ok(Self { verifying_key })
}
}
#[cfg(feature = "eddsa")]
impl Verifier for EddsaVerifier {
fn verify(
&self,
document_id: &DocumentId,
signature: &Signature,
) -> Result<SignatureVerification> {
use base64::Engine;
use ed25519_dalek::Verifier as EddsaVerifierTrait;
if signature.algorithm != SignatureAlgorithm::EdDSA {
return Ok(SignatureVerification::invalid(
&signature.id,
format!(
"Algorithm mismatch: expected EdDSA, got {}",
signature.algorithm
),
));
}
let sig_bytes = base64::engine::general_purpose::STANDARD
.decode(&signature.value)
.map_err(|e| signature_error(format!("Failed to decode signature: {e}")))?;
let sig_array: [u8; 64] =
sig_bytes
.try_into()
.map_err(|_| crate::Error::InvalidManifest {
reason: "Invalid EdDSA signature length (expected 64 bytes)".to_string(),
})?;
let eddsa_sig = ed25519_dalek::Signature::from_bytes(&sig_array);
match self.verifying_key.verify(document_id.digest(), &eddsa_sig) {
Ok(()) => Ok(SignatureVerification::valid(&signature.id)),
Err(e) => Ok(SignatureVerification::invalid(
&signature.id,
format!("EdDSA signature verification failed: {e}"),
)),
}
}
}
#[cfg(all(test, feature = "eddsa"))]
mod tests {
use super::*;
use crate::security::test_helpers;
fn generate_keypair() -> (EddsaSigner, EddsaVerifier) {
let signer_info = SignerInfo::new("Test EdDSA Signer");
let (signer, public_key_pem) = EddsaSigner::generate(signer_info).unwrap();
let verifier = EddsaVerifier::from_pem(&public_key_pem).unwrap();
(signer, verifier)
}
#[test]
fn test_generate_and_sign() {
let signer_info = SignerInfo::new("Test EdDSA Signer");
let (signer, public_key_pem) = EddsaSigner::generate(signer_info).unwrap();
assert!(!public_key_pem.is_empty());
assert!(public_key_pem.contains("BEGIN PUBLIC KEY"));
test_helpers::assert_sign_produces_valid_signature(&signer, SignatureAlgorithm::EdDSA);
}
#[test]
fn test_sign_and_verify() {
let (signer, verifier) = generate_keypair();
test_helpers::assert_sign_verify_roundtrip(&signer, &verifier);
}
#[test]
fn test_verify_wrong_document() {
let (signer, verifier) = generate_keypair();
test_helpers::assert_verify_wrong_document_fails(&signer, &verifier);
}
#[test]
fn test_cannot_sign_pending_id() {
let (signer, _) = generate_keypair();
test_helpers::assert_cannot_sign_pending_id(&signer);
}
#[test]
fn test_algorithm_mismatch() {
let (signer, verifier) = generate_keypair();
test_helpers::assert_algorithm_mismatch_rejected(
&signer,
&verifier,
SignatureAlgorithm::ES256,
);
}
}