Skip to main content

cdx_core/security/
es384.rs

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