cdx_core/security/
signer.rs1use crate::error::invalid_manifest;
4use crate::{DocumentId, Result};
5
6use super::signature::{Signature, SignatureAlgorithm, SignatureVerification, SignerInfo};
7
8pub trait Signer {
10 fn algorithm(&self) -> SignatureAlgorithm;
12
13 fn signer_info(&self) -> SignerInfo;
15
16 fn sign(&self, document_id: &DocumentId) -> Result<Signature>;
22}
23
24pub trait Verifier {
26 fn verify(
32 &self,
33 document_id: &DocumentId,
34 signature: &Signature,
35 ) -> Result<SignatureVerification>;
36}
37
38#[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 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 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 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 let signature: p256::ecdsa::Signature = self.signing_key.sign(document_id.digest());
127
128 let value = base64::engine::general_purpose::STANDARD.encode(signature.to_bytes());
130
131 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#[cfg(feature = "signatures")]
148pub struct EcdsaVerifier {
149 verifying_key: p256::ecdsa::VerifyingKey,
150}
151
152#[cfg(feature = "signatures")]
153impl EcdsaVerifier {
154 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 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 let ecdsa_sig = p256::ecdsa::Signature::from_slice(&sig_bytes)
196 .map_err(|e| invalid_manifest(format!("Invalid signature format: {e}")))?;
197
198 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}