Skip to main content

cdx_core/security/
signer.rs

1//! Signer and verifier traits and implementations.
2
3use crate::error::invalid_manifest;
4use crate::{DocumentId, Result};
5
6use super::signature::{Signature, SignatureAlgorithm, SignatureVerification, SignerInfo};
7
8/// Trait for signing documents.
9pub trait Signer {
10    /// Get the signature algorithm used by this signer.
11    fn algorithm(&self) -> SignatureAlgorithm;
12
13    /// Get information about the signer.
14    fn signer_info(&self) -> SignerInfo;
15
16    /// Sign a document ID.
17    ///
18    /// # Errors
19    ///
20    /// Returns an error if signing fails.
21    fn sign(&self, document_id: &DocumentId) -> Result<Signature>;
22}
23
24/// Trait for verifying signatures.
25pub trait Verifier {
26    /// Verify a signature.
27    ///
28    /// # Errors
29    ///
30    /// Returns an error if verification fails due to an error (not just invalid signature).
31    fn verify(
32        &self,
33        document_id: &DocumentId,
34        signature: &Signature,
35    ) -> Result<SignatureVerification>;
36}
37
38/// ECDSA P-256 signer (ES256).
39#[cfg(feature = "signatures")]
40pub struct EcdsaSigner {
41    signing_key: p256::ecdsa::SigningKey,
42    signer_info: SignerInfo,
43}
44
45#[cfg(feature = "signatures")]
46impl EcdsaSigner {
47    /// Create a new signer from a PEM-encoded private key.
48    ///
49    /// # Errors
50    ///
51    /// Returns an error if the PEM cannot be parsed.
52    pub fn from_pem(pem: &str, signer_info: SignerInfo) -> Result<Self> {
53        use p256::pkcs8::DecodePrivateKey;
54
55        let signing_key = p256::ecdsa::SigningKey::from_pkcs8_pem(pem)
56            .map_err(|e| invalid_manifest(format!("Failed to parse private key PEM: {e}")))?;
57
58        Ok(Self {
59            signing_key,
60            signer_info,
61        })
62    }
63
64    /// Generate a new random signing key.
65    ///
66    /// Returns the signer and the public key in PEM format.
67    ///
68    /// # Errors
69    ///
70    /// Returns an error if key generation fails.
71    pub fn generate(signer_info: SignerInfo) -> Result<(Self, String)> {
72        use p256::pkcs8::EncodePublicKey;
73
74        use p256::elliptic_curve::Generate;
75        let signing_key = p256::ecdsa::SigningKey::generate();
76        let verifying_key = signing_key.verifying_key();
77        let public_key_pem = verifying_key
78            .to_public_key_pem(p256::pkcs8::LineEnding::LF)
79            .map_err(|e| invalid_manifest(format!("Failed to encode public key: {e}")))?;
80
81        Ok((
82            Self {
83                signing_key,
84                signer_info,
85            },
86            public_key_pem,
87        ))
88    }
89
90    /// Get the public key in PEM format.
91    ///
92    /// # Errors
93    ///
94    /// Returns an error if encoding fails.
95    pub fn public_key_pem(&self) -> Result<String> {
96        use p256::pkcs8::EncodePublicKey;
97
98        self.signing_key
99            .verifying_key()
100            .to_public_key_pem(p256::pkcs8::LineEnding::LF)
101            .map_err(|e| invalid_manifest(format!("Failed to encode public key: {e}")))
102    }
103}
104
105#[cfg(feature = "signatures")]
106impl Signer for EcdsaSigner {
107    fn algorithm(&self) -> SignatureAlgorithm {
108        SignatureAlgorithm::ES256
109    }
110
111    fn signer_info(&self) -> SignerInfo {
112        self.signer_info.clone()
113    }
114
115    fn sign(&self, document_id: &DocumentId) -> Result<Signature> {
116        use base64::Engine;
117        use ecdsa::signature::Signer as EcdsaSignerTrait;
118
119        if document_id.is_pending() {
120            return Err(crate::Error::InvalidManifest {
121                reason: "Cannot sign a pending document ID".to_string(),
122            });
123        }
124
125        // Sign the document ID bytes
126        let signature: p256::ecdsa::Signature = self.signing_key.sign(document_id.digest());
127
128        // Encode as base64
129        let value = base64::engine::general_purpose::STANDARD.encode(signature.to_bytes());
130
131        // Generate signature ID
132        let sig_id = format!(
133            "sig-{}",
134            &crate::Hasher::hash(crate::HashAlgorithm::Sha256, value.as_bytes()).hex_digest()[..8]
135        );
136
137        Ok(Signature::new(
138            sig_id,
139            SignatureAlgorithm::ES256,
140            self.signer_info.clone(),
141            value,
142        ))
143    }
144}
145
146/// ECDSA P-256 verifier (ES256).
147#[cfg(feature = "signatures")]
148pub struct EcdsaVerifier {
149    verifying_key: p256::ecdsa::VerifyingKey,
150}
151
152#[cfg(feature = "signatures")]
153impl EcdsaVerifier {
154    /// Create a new verifier from a PEM-encoded public key.
155    ///
156    /// # Errors
157    ///
158    /// Returns an error if the PEM cannot be parsed.
159    pub fn from_pem(pem: &str) -> Result<Self> {
160        use p256::pkcs8::DecodePublicKey;
161
162        let verifying_key = p256::ecdsa::VerifyingKey::from_public_key_pem(pem)
163            .map_err(|e| invalid_manifest(format!("Failed to parse public key PEM: {e}")))?;
164
165        Ok(Self { verifying_key })
166    }
167}
168
169#[cfg(feature = "signatures")]
170impl Verifier for EcdsaVerifier {
171    fn verify(
172        &self,
173        document_id: &DocumentId,
174        signature: &Signature,
175    ) -> Result<SignatureVerification> {
176        use base64::Engine;
177        use ecdsa::signature::Verifier as EcdsaVerifierTrait;
178
179        if signature.algorithm != SignatureAlgorithm::ES256 {
180            return Ok(SignatureVerification::invalid(
181                &signature.id,
182                format!(
183                    "Algorithm mismatch: expected ES256, got {}",
184                    signature.algorithm
185                ),
186            ));
187        }
188
189        // Decode signature from base64
190        let sig_bytes = base64::engine::general_purpose::STANDARD
191            .decode(&signature.value)
192            .map_err(|e| invalid_manifest(format!("Failed to decode signature: {e}")))?;
193
194        // Parse signature
195        let ecdsa_sig = p256::ecdsa::Signature::from_slice(&sig_bytes)
196            .map_err(|e| invalid_manifest(format!("Invalid signature format: {e}")))?;
197
198        // Verify
199        match self.verifying_key.verify(document_id.digest(), &ecdsa_sig) {
200            Ok(()) => Ok(SignatureVerification::valid(&signature.id)),
201            Err(e) => Ok(SignatureVerification::invalid(
202                &signature.id,
203                format!("Signature verification failed: {e}"),
204            )),
205        }
206    }
207}
208
209#[cfg(all(test, feature = "signatures"))]
210mod tests {
211    use super::*;
212    use crate::security::test_helpers;
213
214    fn generate_keypair() -> (EcdsaSigner, EcdsaVerifier) {
215        let signer_info = SignerInfo::new("Test Signer");
216        let (signer, public_key_pem) = EcdsaSigner::generate(signer_info).unwrap();
217        let verifier = EcdsaVerifier::from_pem(&public_key_pem).unwrap();
218        (signer, verifier)
219    }
220
221    #[test]
222    fn test_generate_and_sign() {
223        let signer_info = SignerInfo::new("Test Signer");
224        let (signer, public_key_pem) = EcdsaSigner::generate(signer_info).unwrap();
225
226        assert!(!public_key_pem.is_empty());
227        assert!(public_key_pem.contains("BEGIN PUBLIC KEY"));
228
229        test_helpers::assert_sign_produces_valid_signature(&signer, SignatureAlgorithm::ES256);
230    }
231
232    #[test]
233    fn test_sign_and_verify() {
234        let (signer, verifier) = generate_keypair();
235        test_helpers::assert_sign_verify_roundtrip(&signer, &verifier);
236    }
237
238    #[test]
239    fn test_verify_wrong_document() {
240        let (signer, verifier) = generate_keypair();
241        test_helpers::assert_verify_wrong_document_fails(&signer, &verifier);
242    }
243
244    #[test]
245    fn test_cannot_sign_pending_id() {
246        let (signer, _) = generate_keypair();
247        test_helpers::assert_cannot_sign_pending_id(&signer);
248    }
249
250    #[test]
251    fn test_algorithm_mismatch() {
252        let (signer, verifier) = generate_keypair();
253        test_helpers::assert_algorithm_mismatch_rejected(
254            &signer,
255            &verifier,
256            SignatureAlgorithm::EdDSA,
257        );
258    }
259}