1use crate::error::{Error, Result};
2use atlas_c2pa_lib::cose::HashAlgorithm;
3use openssl::hash::MessageDigest;
4use openssl::pkey::{PKey, Private, Public};
5use openssl::sign::Signer;
6use std::fs::read;
7use std::path::Path;
8use zeroize::{ZeroizeOnDrop, Zeroizing};
9
10pub mod signable;
11
12#[derive(ZeroizeOnDrop)]
14pub struct SecurePrivateKey {
15 #[zeroize(skip)]
16 pkey: PKey<Private>,
17 _key_data: Zeroizing<Vec<u8>>,
19}
20
21impl SecurePrivateKey {
22 pub fn from_pem(pem_data: Vec<u8>) -> Result<Self> {
24 let zeroizing_pem = Zeroizing::new(pem_data);
26
27 let pkey = PKey::private_key_from_pem(&zeroizing_pem)
29 .map_err(|e| Error::Signing(format!("Failed to load private key: {e}")))?;
30
31 Ok(Self {
32 pkey,
33 _key_data: zeroizing_pem,
34 })
35 }
36
37 pub fn as_pkey(&self) -> &PKey<Private> {
39 &self.pkey
40 }
41}
42
43pub fn load_private_key(key_path: &Path) -> Result<SecurePrivateKey> {
45 let key_data = read(key_path)?;
47 SecurePrivateKey::from_pem(key_data)
48}
49
50pub fn sign_data_with_algorithm(
52 data: &[u8],
53 private_key: &SecurePrivateKey,
54 algorithm: &HashAlgorithm,
55) -> Result<Vec<u8>> {
56 let message_digest = match algorithm {
57 HashAlgorithm::Sha256 => MessageDigest::sha256(),
58 HashAlgorithm::Sha384 => MessageDigest::sha384(),
59 HashAlgorithm::Sha512 => MessageDigest::sha512(),
60 };
61
62 let mut signer = Signer::new(message_digest, private_key.as_pkey())
63 .map_err(|e| Error::Signing(format!("Failed to create signer: {e}")))?;
64
65 signer
66 .update(data)
67 .map_err(|e| Error::Signing(format!("Failed to update signer: {e}")))?;
68
69 let sig_len = signer
71 .len()
72 .map_err(|e| Error::Signing(format!("Failed to get signature length: {e}")))?;
73 let mut signature = Zeroizing::new(vec![0u8; sig_len]);
74 let len = signer
75 .sign(&mut signature)
76 .map_err(|e| Error::Signing(format!("Failed to sign data: {e}")))?;
77
78 Ok(signature[..len].to_vec())
80}
81
82pub fn sign_data(data: &[u8], private_key: &SecurePrivateKey) -> Result<Vec<u8>> {
84 sign_data_with_algorithm(data, private_key, &HashAlgorithm::Sha384)
85}
86
87pub fn verify_signature(data: &[u8], signature: &[u8], public_key: &PKey<Public>) -> Result<bool> {
89 verify_signature_with_algorithm(data, signature, public_key, &HashAlgorithm::Sha384)
90}
91
92pub fn verify_signature_with_algorithm(
94 data: &[u8],
95 signature: &[u8],
96 public_key: &PKey<Public>,
97 algorithm: &HashAlgorithm,
98) -> Result<bool> {
99 let message_digest = match algorithm {
100 HashAlgorithm::Sha256 => MessageDigest::sha256(),
101 HashAlgorithm::Sha384 => MessageDigest::sha384(),
102 HashAlgorithm::Sha512 => MessageDigest::sha512(),
103 };
104
105 let mut verifier = openssl::sign::Verifier::new(message_digest, public_key)
106 .map_err(|e| Error::Signing(e.to_string()))?;
107
108 verifier
109 .update(data)
110 .map_err(|e| Error::Signing(e.to_string()))?;
111
112 verifier
113 .verify(signature)
114 .map_err(|e| Error::Signing(e.to_string()))
115}
116
117pub fn pkey_to_secure(pkey: PKey<Private>) -> Result<SecurePrivateKey> {
118 let pem_data = pkey
120 .private_key_to_pem_pkcs8()
121 .map_err(|e| Error::Signing(format!("Failed to export key to PEM: {e}")))?;
122
123 SecurePrivateKey::from_pem(pem_data)
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use crate::error::Result;
130 use crate::signing::test_utils::generate_temp_key;
131
132 #[test]
133 fn test_load_private_key() -> Result<()> {
134 let (secure_key, _dir) = generate_temp_key()?;
136
137 let test_data = b"test data for signing";
139 let signature = sign_data(test_data, &secure_key)?;
140
141 assert!(!signature.is_empty());
143
144 Ok(())
145 }
146
147 #[test]
148 fn test_sign_data() -> Result<()> {
149 let (secure_key, _) = generate_temp_key()?;
151
152 let data1 = b"test data for signing";
154 let data2 = b"different test data";
155
156 let signature1 = sign_data(data1, &secure_key)?;
158 let signature2 = sign_data(data1, &secure_key)?; let signature3 = sign_data(data2, &secure_key)?; assert!(!signature1.is_empty(), "Signature should not be empty");
163
164 assert_eq!(
166 signature1, signature2,
167 "Signatures for the same data should match"
168 );
169
170 assert_ne!(
172 signature1, signature3,
173 "Signatures for different data should not match"
174 );
175
176 Ok(())
177 }
178
179 #[test]
180 fn test_signature_different_keys() -> Result<()> {
181 let (secure_key1, _) = generate_temp_key()?;
183 let (secure_key2, _) = generate_temp_key()?;
184
185 let data = b"test data for signature comparison";
187
188 let signature1 = sign_data(data, &secure_key1)?;
190 let signature2 = sign_data(data, &secure_key2)?;
191
192 assert_ne!(
194 signature1, signature2,
195 "Signatures from different keys should not match"
196 );
197
198 Ok(())
199 }
200
201 #[test]
202 fn test_load_private_key_error() {
203 let result = load_private_key(std::path::Path::new("/nonexistent/path/to/key.pem"));
205
206 assert!(result.is_err(), "Loading non-existent key should fail");
208
209 if let Err(e) = result {
211 match e {
212 crate::error::Error::Io(_) => {} _ => panic!("Unexpected error type: {e:?}"),
214 }
215 }
216 }
217
218 #[test]
219 fn test_sign_data_with_empty_data() -> Result<()> {
220 let (secure_key, _) = generate_temp_key()?;
222
223 let signature = sign_data(&[], &secure_key)?;
225
226 assert!(
228 !signature.is_empty(),
229 "Signature of empty data should not be empty"
230 );
231
232 Ok(())
233 }
234
235 #[test]
236 fn test_sign_large_data() -> Result<()> {
237 let (secure_key, _) = generate_temp_key()?;
239
240 let large_data = vec![0x55; 100 * 1024]; let signature = sign_data(&large_data, &secure_key)?;
245
246 assert!(
248 !signature.is_empty(),
249 "Signature of large data should not be empty"
250 );
251
252 Ok(())
253 }
254
255 #[test]
256 fn test_secure_key_zeroization() -> Result<()> {
257 let pem_data = b"-----BEGIN PRIVATE KEY-----
259MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7W8pGqWu2VZtD
260TEST_KEY_DATA_THAT_SHOULD_BE_ZEROIZED
261-----END PRIVATE KEY-----"
262 .to_vec();
263
264 {
266 let _secure_key = SecurePrivateKey::from_pem(pem_data.clone());
267 }
269 Ok(())
276 }
277
278 #[test]
279 fn test_sign_with_different_algorithms() -> Result<()> {
280 let (secure_key, _) = generate_temp_key()?;
282
283 let data = b"test data for different algorithms";
284
285 let sig_sha256 = sign_data_with_algorithm(data, &secure_key, &HashAlgorithm::Sha256)?;
287 let sig_sha384 = sign_data_with_algorithm(data, &secure_key, &HashAlgorithm::Sha384)?;
288 let sig_sha512 = sign_data_with_algorithm(data, &secure_key, &HashAlgorithm::Sha512)?;
289
290 assert!(!sig_sha256.is_empty());
292 assert!(!sig_sha384.is_empty());
293 assert!(!sig_sha512.is_empty());
294
295 assert_ne!(sig_sha256, sig_sha384);
297 assert_ne!(sig_sha384, sig_sha512);
298 assert_ne!(sig_sha256, sig_sha512);
299
300 Ok(())
301 }
302 #[test]
303 fn test_zeroization_with_multiple_references() -> Result<()> {
304 use std::sync::Arc;
306
307 let (secure_key, _dir) = generate_temp_key()?;
308
309 let key_arc = Arc::new(secure_key);
311 let key_ref1 = Arc::clone(&key_arc);
312 let key_ref2 = Arc::clone(&key_arc);
313
314 let data = b"test data";
316
317 let sig1 = sign_data(data, &key_ref1)?;
318 let sig2 = sign_data(data, &key_ref2)?;
319
320 assert_eq!(sig1, sig2);
322
323 drop(key_ref1);
325 assert_eq!(Arc::strong_count(&key_arc), 2);
326
327 drop(key_ref2);
328 assert_eq!(Arc::strong_count(&key_arc), 1);
329
330 let sig3 = sign_data(data, &key_arc)?;
332 assert_eq!(sig1, sig3);
333
334 drop(key_arc);
336
337 Ok(())
338 }
339 #[test]
340 fn test_sign_and_verify_with_algorithms() -> Result<()> {
341 let (secure_key, _dir) = generate_temp_key()?;
343
344 let public_key = secure_key
346 .as_pkey()
347 .public_key_to_pem()
348 .map_err(|e| Error::Signing(e.to_string()))?;
349 let public_key =
350 PKey::public_key_from_pem(&public_key).map_err(|e| Error::Signing(e.to_string()))?;
351
352 let data = b"test data for sign and verify";
353
354 for algo in &[
356 HashAlgorithm::Sha256,
357 HashAlgorithm::Sha384,
358 HashAlgorithm::Sha512,
359 ] {
360 let signature = sign_data_with_algorithm(data, &secure_key, algo)?;
362
363 let valid = verify_signature_with_algorithm(data, &signature, &public_key, algo)?;
365 assert!(
366 valid,
367 "Verification should succeed with matching algorithm {:?}",
368 algo
369 );
370
371 for wrong_algo in &[
373 HashAlgorithm::Sha256,
374 HashAlgorithm::Sha384,
375 HashAlgorithm::Sha512,
376 ] {
377 if wrong_algo != algo {
378 let invalid =
379 verify_signature_with_algorithm(data, &signature, &public_key, wrong_algo)?;
380 assert!(
381 !invalid,
382 "Verification should fail with mismatched algorithms {:?} != {:?}",
383 algo, wrong_algo
384 );
385 }
386 }
387 }
388
389 let signature = sign_data(data, &secure_key)?;
391 let valid = verify_signature(data, &signature, &public_key)?;
392 assert!(valid, "Default sign/verify should work together");
393
394 let valid_384 =
396 verify_signature_with_algorithm(data, &signature, &public_key, &HashAlgorithm::Sha384)?;
397 assert!(valid_384, "Default should be SHA-384");
398
399 let invalid_256 =
400 verify_signature_with_algorithm(data, &signature, &public_key, &HashAlgorithm::Sha256)?;
401 assert!(!invalid_256, "Default should not be SHA-256");
402
403 Ok(())
404 }
405}
406
407#[cfg(test)]
408pub(crate) mod test_utils {
409 use crate::error::Result;
410 use crate::signing::SecurePrivateKey;
411 use crate::signing::load_private_key;
412 use openssl::pkey::PKey;
413 use openssl::rsa::Rsa;
414 use std::fs::File;
415 use std::io::Write;
416 use tempfile::tempdir;
417
418 pub fn generate_temp_key() -> Result<(SecurePrivateKey, tempfile::TempDir)> {
420 let dir = tempdir()?;
422 let key_path = dir.path().join("test_key.pem");
423
424 let rsa = Rsa::generate(2048).map_err(|e| crate::error::Error::Signing(e.to_string()))?;
426
427 let private_key =
429 PKey::from_rsa(rsa).map_err(|e| crate::error::Error::Signing(e.to_string()))?;
430
431 let pem = private_key
433 .private_key_to_pem_pkcs8()
434 .map_err(|e| crate::error::Error::Signing(e.to_string()))?;
435
436 let mut key_file = File::create(&key_path)?;
437 key_file.write_all(&pem)?;
438
439 let secure_key = load_private_key(&key_path)?;
441
442 Ok((secure_key, dir))
443 }
444}