1#![allow(clippy::disallowed_methods)]
12
13use zeroize::{Zeroize, ZeroizeOnDrop};
14
15use crate::provider::{CryptoError, CurveType, SecureSeed};
16
17#[derive(Clone, Zeroize, ZeroizeOnDrop)]
22pub enum TypedSeed {
23 Ed25519(#[zeroize] [u8; 32]),
25 P256(#[zeroize] [u8; 32]),
27}
28
29impl TypedSeed {
30 pub fn from_curve(curve: CurveType, bytes: [u8; 32]) -> Self {
34 match curve {
35 CurveType::Ed25519 => Self::Ed25519(bytes),
36 CurveType::P256 => Self::P256(bytes),
37 }
38 }
39
40 pub fn curve(&self) -> CurveType {
42 match self {
43 Self::Ed25519(_) => CurveType::Ed25519,
44 Self::P256(_) => CurveType::P256,
45 }
46 }
47
48 pub fn as_bytes(&self) -> &[u8; 32] {
50 match self {
51 Self::Ed25519(b) | Self::P256(b) => b,
52 }
53 }
54
55 pub fn to_secure_seed(&self) -> SecureSeed {
57 SecureSeed::new(*self.as_bytes())
58 }
59}
60
61impl std::fmt::Debug for TypedSeed {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 match self {
64 Self::Ed25519(_) => f.write_str("TypedSeed::Ed25519([REDACTED])"),
65 Self::P256(_) => f.write_str("TypedSeed::P256([REDACTED])"),
66 }
67 }
68}
69
70#[derive(Debug)]
72pub struct ParsedKey {
73 pub seed: TypedSeed,
75 pub public_key: Vec<u8>,
77}
78
79pub fn parse_key_material(bytes: &[u8]) -> Result<ParsedKey, CryptoError> {
90 if let Ok((seed, maybe_pk)) = crate::key_material::parse_ed25519_key_material(bytes) {
92 let public_key = match maybe_pk {
93 Some(pk) => pk.to_vec(),
94 None => {
95 #[cfg(all(feature = "native", not(target_arch = "wasm32")))]
97 {
98 use ring::signature::{Ed25519KeyPair, KeyPair};
99 let kp = Ed25519KeyPair::from_seed_unchecked(seed.as_bytes()).map_err(|e| {
100 CryptoError::OperationFailed(format!("Ed25519 pubkey: {e}"))
101 })?;
102 kp.public_key().as_ref().to_vec()
103 }
104 #[cfg(not(all(feature = "native", not(target_arch = "wasm32"))))]
105 {
106 return Err(CryptoError::UnsupportedTarget);
107 }
108 }
109 };
110 return Ok(ParsedKey {
111 seed: TypedSeed::Ed25519(*seed.as_bytes()),
112 public_key,
113 });
114 }
115
116 #[cfg(feature = "native")]
118 {
119 use p256::pkcs8::DecodePrivateKey;
120 if let Ok(sk) = p256::ecdsa::SigningKey::from_pkcs8_der(bytes) {
121 let vk = p256::ecdsa::VerifyingKey::from(&sk);
122 let compressed = vk.to_encoded_point(true);
123 let mut scalar = [0u8; 32];
124 scalar.copy_from_slice(&sk.to_bytes());
125 return Ok(ParsedKey {
126 seed: TypedSeed::P256(scalar),
127 public_key: compressed.as_bytes().to_vec(),
128 });
129 }
130 }
131
132 Err(CryptoError::InvalidPrivateKey(format!(
133 "Unrecognized key format ({} bytes)",
134 bytes.len()
135 )))
136}
137
138#[cfg(all(feature = "fips", not(target_arch = "wasm32")))]
144use crate::aws_lc_provider::AwsLcProvider as SyncProvider;
145#[cfg(all(feature = "cnsa", not(feature = "fips"), not(target_arch = "wasm32")))]
146use crate::cnsa_provider::CnsaProvider as SyncProvider;
147#[cfg(all(
148 feature = "native",
149 not(feature = "fips"),
150 not(feature = "cnsa"),
151 not(target_arch = "wasm32")
152))]
153use crate::ring_provider::RingCryptoProvider as SyncProvider;
154
155#[cfg(all(feature = "native", not(target_arch = "wasm32")))]
168pub fn sign(seed: &TypedSeed, message: &[u8]) -> Result<Vec<u8>, CryptoError> {
169 match seed {
170 TypedSeed::Ed25519(s) => SyncProvider::ed25519_sign(s, message),
171 TypedSeed::P256(s) => SyncProvider::p256_sign(s, message),
172 }
173}
174
175#[cfg(all(feature = "native", not(target_arch = "wasm32")))]
181pub fn public_key(seed: &TypedSeed) -> Result<Vec<u8>, CryptoError> {
182 match seed {
183 TypedSeed::Ed25519(s) => Ok(SyncProvider::ed25519_public_key(s)?.to_vec()),
184 TypedSeed::P256(s) => SyncProvider::p256_public_key_from_seed(s),
185 }
186}
187
188#[cfg(all(feature = "native", not(target_arch = "wasm32")))]
203pub fn verify(
204 curve: CurveType,
205 public_key: &[u8],
206 message: &[u8],
207 signature: &[u8],
208) -> Result<(), CryptoError> {
209 match curve {
210 CurveType::Ed25519 => SyncProvider::ed25519_verify(public_key, message, signature),
211 CurveType::P256 => SyncProvider::p256_verify(public_key, message, signature),
212 }
213}
214
215#[cfg(all(feature = "native", not(target_arch = "wasm32")))]
228pub fn generate(curve: CurveType) -> Result<(TypedSeed, Vec<u8>), CryptoError> {
229 match curve {
230 CurveType::Ed25519 => {
231 let (seed, public_key) = SyncProvider::ed25519_generate()?;
232 Ok((TypedSeed::Ed25519(*seed.as_bytes()), public_key.to_vec()))
233 }
234 CurveType::P256 => {
235 let (seed, public_key) = SyncProvider::p256_generate()?;
236 Ok((TypedSeed::P256(*seed.as_bytes()), public_key))
237 }
238 }
239}
240
241#[derive(Debug)]
263pub struct TypedSignerKey {
264 seed: TypedSeed,
268 public_key: Vec<u8>,
271}
272
273impl zeroize::ZeroizeOnDrop for TypedSignerKey {}
278
279impl TypedSignerKey {
280 pub fn from_pkcs8(bytes: &[u8]) -> Result<Self, CryptoError> {
282 let parsed = parse_key_material(bytes)?;
283 Ok(Self {
284 seed: parsed.seed,
285 public_key: parsed.public_key,
286 })
287 }
288
289 pub fn from_parts(seed: TypedSeed, public_key: Vec<u8>) -> Result<Self, CryptoError> {
293 let expected = seed.curve().public_key_len();
294 if public_key.len() != expected {
295 return Err(CryptoError::InvalidPrivateKey(format!(
296 "public key length {} does not match {} expected {} bytes",
297 public_key.len(),
298 seed.curve(),
299 expected
300 )));
301 }
302 Ok(Self { seed, public_key })
303 }
304
305 #[cfg(all(feature = "native", not(target_arch = "wasm32")))]
307 pub fn from_seed(seed: TypedSeed) -> Result<Self, CryptoError> {
308 let pk = public_key(&seed)?;
309 Ok(Self {
310 seed,
311 public_key: pk,
312 })
313 }
314
315 pub fn cesr_encoded_pubkey(&self) -> String {
326 use cesride::Matter;
327 let code = match self.seed.curve() {
328 CurveType::Ed25519 => cesride::matter::Codex::Ed25519,
329 CurveType::P256 => cesride::matter::Codex::ECDSA_256r1,
330 };
331 #[allow(clippy::expect_used)]
332 cesride::Verfer::new(Some(code), Some(&self.public_key), None, None, None)
334 .and_then(|v| v.qb64())
335 .expect("cesride verkey encode is infallible for a validated key")
336 }
337
338 pub fn cesr_encoded(&self) -> String {
340 self.cesr_encoded_pubkey()
341 }
342
343 #[cfg(all(feature = "native", not(target_arch = "wasm32")))]
347 pub fn to_pkcs8(&self) -> Result<crate::pkcs8::Pkcs8Der, CryptoError> {
348 match &self.seed {
349 TypedSeed::Ed25519(seed_bytes) => {
350 if self.public_key.len() != crate::provider::ED25519_PUBLIC_KEY_LEN {
351 return Err(CryptoError::InvalidPrivateKey(
352 "Ed25519 public key must be 32 bytes".to_string(),
353 ));
354 }
355 let mut pk = [0u8; 32];
356 pk.copy_from_slice(&self.public_key);
357 let bytes = crate::key_material::build_ed25519_pkcs8_v2(seed_bytes, &pk);
358 Ok(crate::pkcs8::Pkcs8Der::new(bytes.to_vec()))
359 }
360 TypedSeed::P256(scalar) => {
361 use p256::ecdsa::SigningKey;
362 use p256::pkcs8::EncodePrivateKey;
363 let sk = SigningKey::from_slice(scalar)
364 .map_err(|e| CryptoError::InvalidPrivateKey(format!("P-256 scalar: {e}")))?;
365 let doc = sk
366 .to_pkcs8_der()
367 .map_err(|e| CryptoError::OperationFailed(format!("P-256 PKCS8: {e}")))?;
368 Ok(crate::pkcs8::Pkcs8Der::new(doc.as_bytes().to_vec()))
369 }
370 }
371 }
372
373 #[cfg(all(feature = "native", not(target_arch = "wasm32")))]
375 pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, CryptoError> {
376 sign(&self.seed, message)
377 }
378
379 pub fn curve(&self) -> CurveType {
381 self.seed.curve()
382 }
383
384 pub fn public_key(&self) -> &[u8] {
386 &self.public_key
387 }
388
389 pub fn seed(&self) -> &TypedSeed {
393 &self.seed
394 }
395}
396
397pub fn normalize_verkey(bytes: &[u8], curve: CurveType) -> Result<Vec<u8>, CryptoError> {
414 match curve {
415 CurveType::Ed25519 => {
416 if bytes.len() != 32 {
417 return Err(CryptoError::OperationFailed(format!(
418 "Ed25519 verkey must be 32 bytes, got {}",
419 bytes.len()
420 )));
421 }
422 Ok(bytes.to_vec())
423 }
424 CurveType::P256 => {
425 #[cfg(feature = "native")]
426 {
427 use p256::elliptic_curve::sec1::ToEncodedPoint;
428 let pk = p256::PublicKey::from_sec1_bytes(bytes).map_err(|e| {
429 CryptoError::OperationFailed(format!("invalid P-256 public key: {e}"))
430 })?;
431 Ok(pk.to_encoded_point(true).as_bytes().to_vec())
432 }
433 #[cfg(not(feature = "native"))]
434 {
435 let _ = bytes;
436 Err(CryptoError::UnsupportedTarget)
437 }
438 }
439 #[allow(unreachable_patterns)]
440 other => Err(CryptoError::OperationFailed(format!(
441 "normalize_verkey: unsupported curve {other:?}"
442 ))),
443 }
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449
450 #[test]
451 fn typed_seed_curve_identification() {
452 let ed = TypedSeed::Ed25519([1u8; 32]);
453 assert_eq!(ed.curve(), CurveType::Ed25519);
454
455 let p = TypedSeed::P256([2u8; 32]);
456 assert_eq!(p.curve(), CurveType::P256);
457 }
458
459 #[test]
460 fn typed_seed_as_bytes() {
461 let seed = TypedSeed::Ed25519([42u8; 32]);
462 assert_eq!(seed.as_bytes(), &[42u8; 32]);
463 }
464
465 #[test]
466 fn typed_seed_debug_redacts() {
467 let seed = TypedSeed::P256([0u8; 32]);
468 let debug = format!("{:?}", seed);
469 assert!(debug.contains("REDACTED"));
470 assert!(!debug.contains("0, 0, 0"));
471 }
472
473 #[cfg(all(feature = "native", not(target_arch = "wasm32")))]
474 mod native {
475 use super::*;
476
477 #[test]
478 fn parse_ed25519_pkcs8_v2() {
479 use ring::rand::SystemRandom;
481 use ring::signature::Ed25519KeyPair;
482 let rng = SystemRandom::new();
483 let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
484 let parsed = parse_key_material(pkcs8.as_ref()).unwrap();
485 assert_eq!(parsed.seed.curve(), CurveType::Ed25519);
486 assert_eq!(parsed.public_key.len(), 32);
487 }
488
489 #[test]
490 fn parse_p256_pkcs8() {
491 use p256::ecdsa::SigningKey;
492 use p256::elliptic_curve::rand_core::OsRng;
493 use p256::pkcs8::EncodePrivateKey;
494 let sk = SigningKey::random(&mut OsRng);
495 let pkcs8 = sk.to_pkcs8_der().unwrap();
496 let parsed = parse_key_material(pkcs8.as_bytes()).unwrap();
497 assert_eq!(parsed.seed.curve(), CurveType::P256);
498 assert_eq!(parsed.public_key.len(), 33);
499 }
500
501 #[test]
502 fn parse_raw_32_bytes_is_ed25519() {
503 let raw = [7u8; 32];
504 let parsed = parse_key_material(&raw).unwrap();
505 assert_eq!(parsed.seed.curve(), CurveType::Ed25519);
506 }
507
508 #[test]
509 fn parse_garbage_fails() {
510 let garbage = [0xFFu8; 50];
511 assert!(parse_key_material(&garbage).is_err());
512 }
513
514 #[test]
515 fn parse_empty_fails() {
516 assert!(parse_key_material(&[]).is_err());
517 }
518
519 #[test]
520 fn sign_ed25519_roundtrip() {
521 use ring::signature::{ED25519, UnparsedPublicKey};
522 let seed = TypedSeed::Ed25519([1u8; 32]);
523 let msg = b"hello world";
524 let sig = sign(&seed, msg).unwrap();
525 assert_eq!(sig.len(), 64);
526
527 let pk = public_key(&seed).unwrap();
528 let verifier = UnparsedPublicKey::new(&ED25519, &pk);
529 assert!(verifier.verify(msg, &sig).is_ok());
530 }
531
532 #[test]
533 fn sign_p256_roundtrip() {
534 use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier};
535 let seed = TypedSeed::P256([3u8; 32]);
536 let msg = b"hello p256";
537 let sig_bytes = sign(&seed, msg).unwrap();
538 assert_eq!(sig_bytes.len(), 64);
539
540 let pk_bytes = public_key(&seed).unwrap();
541 assert_eq!(pk_bytes.len(), 33);
542
543 let vk = VerifyingKey::from_sec1_bytes(&pk_bytes).unwrap();
544 let sig = Signature::from_slice(&sig_bytes).unwrap();
545 assert!(vk.verify(msg, &sig).is_ok());
546 }
547
548 #[test]
549 fn cross_curve_isolation() {
550 let bytes = [5u8; 32];
552 let ed_seed = TypedSeed::Ed25519(bytes);
553 let p256_seed = TypedSeed::P256(bytes);
554
555 let ed_pk = public_key(&ed_seed).unwrap();
556 let p256_pk = public_key(&p256_seed).unwrap();
557
558 assert_ne!(ed_pk.len(), p256_pk.len());
560
561 let msg = b"test";
562 let ed_sig = sign(&ed_seed, msg).unwrap();
563 let p256_sig = sign(&p256_seed, msg).unwrap();
564
565 assert_eq!(ed_sig.len(), 64);
567 assert_eq!(p256_sig.len(), 64);
568 assert_ne!(ed_sig, p256_sig);
569 }
570
571 #[test]
572 fn parse_then_sign_ed25519() {
573 use ring::rand::SystemRandom;
574 use ring::signature::{ED25519, Ed25519KeyPair, UnparsedPublicKey};
575 let rng = SystemRandom::new();
576 let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
577 let parsed = parse_key_material(pkcs8.as_ref()).unwrap();
578
579 let msg = b"end to end";
580 let sig = sign(&parsed.seed, msg).unwrap();
581 let verifier = UnparsedPublicKey::new(&ED25519, &parsed.public_key);
582 assert!(verifier.verify(msg, &sig).is_ok());
583 }
584
585 #[test]
586 fn parse_then_sign_p256() {
587 use p256::ecdsa::{Signature, SigningKey, VerifyingKey, signature::Verifier};
588 use p256::elliptic_curve::rand_core::OsRng;
589 use p256::pkcs8::EncodePrivateKey;
590
591 let sk = SigningKey::random(&mut OsRng);
592 let pkcs8 = sk.to_pkcs8_der().unwrap();
593 let parsed = parse_key_material(pkcs8.as_bytes()).unwrap();
594
595 let msg = b"end to end p256";
596 let sig_bytes = sign(&parsed.seed, msg).unwrap();
597
598 let vk = VerifyingKey::from_sec1_bytes(&parsed.public_key).unwrap();
599 let sig = Signature::from_slice(&sig_bytes).unwrap();
600 assert!(vk.verify(msg, &sig).is_ok());
601 }
602
603 #[test]
604 fn typed_signer_key_ed25519_roundtrip() {
605 use ring::rand::SystemRandom;
606 use ring::signature::Ed25519KeyPair;
607 let pkcs8 = Ed25519KeyPair::generate_pkcs8(&SystemRandom::new()).unwrap();
608 let s = TypedSignerKey::from_pkcs8(pkcs8.as_ref()).unwrap();
609 assert_eq!(s.curve(), CurveType::Ed25519);
610 assert!(s.cesr_encoded_pubkey().starts_with('D'));
611 assert_eq!(s.public_key().len(), 32);
612 let sig = s.sign(b"msg").unwrap();
613 assert_eq!(sig.len(), 64);
614 }
615
616 #[test]
617 fn typed_signer_key_p256_roundtrip() {
618 use p256::ecdsa::SigningKey;
619 use p256::elliptic_curve::rand_core::OsRng;
620 use p256::pkcs8::EncodePrivateKey;
621 let sk = SigningKey::random(&mut OsRng);
622 let pkcs8 = sk.to_pkcs8_der().unwrap();
623 let s = TypedSignerKey::from_pkcs8(pkcs8.as_bytes()).unwrap();
624 assert_eq!(s.curve(), CurveType::P256);
625 assert!(s.cesr_encoded_pubkey().starts_with("1AAJ"));
626 assert_eq!(s.public_key().len(), 33);
627 let sig = s.sign(b"msg").unwrap();
628 assert_eq!(sig.len(), 64);
629 }
630
631 #[test]
632 fn typed_signer_key_to_pkcs8_ed25519_roundtrip() {
633 use ring::rand::SystemRandom;
634 use ring::signature::Ed25519KeyPair;
635 let pkcs8 = Ed25519KeyPair::generate_pkcs8(&SystemRandom::new()).unwrap();
636 let s = TypedSignerKey::from_pkcs8(pkcs8.as_ref()).unwrap();
637 let encoded = s.to_pkcs8().unwrap();
638 let reparsed = TypedSignerKey::from_pkcs8(encoded.as_ref()).unwrap();
639 assert_eq!(reparsed.curve(), CurveType::Ed25519);
640 assert_eq!(reparsed.public_key(), s.public_key());
641 assert_eq!(reparsed.seed.as_bytes(), s.seed.as_bytes());
642 }
643
644 #[test]
645 fn typed_signer_key_to_pkcs8_p256_roundtrip() {
646 let seed = TypedSeed::P256({
647 let mut scalar = [9u8; 32];
648 scalar[0] |= 1;
649 scalar
650 });
651 let s = TypedSignerKey::from_seed(seed).unwrap();
652 let encoded = s.to_pkcs8().unwrap();
653 let reparsed = TypedSignerKey::from_pkcs8(encoded.as_ref()).unwrap();
654 assert_eq!(reparsed.curve(), CurveType::P256);
655 assert_eq!(reparsed.public_key(), s.public_key());
656 assert_eq!(reparsed.seed.as_bytes(), s.seed.as_bytes());
657 }
658
659 #[test]
660 fn rotation_signer_alias_still_works() {
661 use ring::rand::SystemRandom;
662 use ring::signature::Ed25519KeyPair;
663 let pkcs8 = Ed25519KeyPair::generate_pkcs8(&SystemRandom::new()).unwrap();
664 let s = TypedSignerKey::from_pkcs8(pkcs8.as_ref()).unwrap();
666 assert_eq!(s.curve(), CurveType::Ed25519);
667 }
668
669 #[test]
670 fn typed_signer_key_from_parts_rejects_mismatched_pubkey_length() {
671 let seed = TypedSeed::Ed25519([1u8; 32]);
672 let wrong_len_pk = vec![0u8; 33]; let err = TypedSignerKey::from_parts(seed, wrong_len_pk).unwrap_err();
674 assert!(matches!(err, CryptoError::InvalidPrivateKey(_)));
675 }
676
677 #[test]
686 #[cfg(not(feature = "fips"))]
687 fn sign_p256_is_rfc6979_deterministic() {
688 let seed = TypedSeed::P256([7u8; 32]);
689 let msg = b"fn-128.T2 determinism";
690 let a = sign(&seed, msg).unwrap();
691 let b = sign(&seed, msg).unwrap();
692 let c = sign(&seed, msg).unwrap();
693 assert_eq!(a, b);
694 assert_eq!(b, c);
695 assert_eq!(a.len(), 64);
696 }
697
698 #[test]
702 fn sign_ed25519_is_deterministic() {
703 let seed = TypedSeed::Ed25519([11u8; 32]);
704 let msg = b"fn-128.T2 determinism";
705 let a = sign(&seed, msg).unwrap();
706 let b = sign(&seed, msg).unwrap();
707 assert_eq!(a, b);
708 assert_eq!(a.len(), 64);
709 }
710
711 #[tokio::test]
721 #[cfg(not(feature = "fips"))]
722 async fn sync_sign_matches_async_sign_typed_p256() {
723 use crate::provider::CryptoProvider;
724 use crate::ring_provider::RingCryptoProvider;
725
726 let seed = TypedSeed::P256([42u8; 32]);
727 let msg = b"parity check";
728
729 let sync_sig = sign(&seed, msg).unwrap();
730 let async_sig = RingCryptoProvider.sign_typed(&seed, msg).await.unwrap();
731
732 assert_eq!(sync_sig, async_sig);
733 }
734
735 #[tokio::test]
736 async fn sync_sign_matches_async_sign_typed_ed25519() {
737 use crate::provider::CryptoProvider;
738 use crate::ring_provider::RingCryptoProvider;
739
740 let seed = TypedSeed::Ed25519([19u8; 32]);
741 let msg = b"parity check";
742
743 let sync_sig = sign(&seed, msg).unwrap();
744 let async_sig = RingCryptoProvider.sign_typed(&seed, msg).await.unwrap();
745
746 assert_eq!(sync_sig, async_sig);
747 }
748
749 #[tokio::test]
753 async fn sync_public_key_matches_async_typed_public_key() {
754 use crate::provider::CryptoProvider;
755 use crate::ring_provider::RingCryptoProvider;
756
757 for (name, seed) in [
758 ("ed25519", TypedSeed::Ed25519([23u8; 32])),
759 ("p256", TypedSeed::P256([29u8; 32])),
760 ] {
761 let sync_pk = public_key(&seed).unwrap();
762 let async_pk = RingCryptoProvider
763 .typed_public_key_from_seed(&seed)
764 .await
765 .unwrap();
766 assert_eq!(sync_pk, async_pk, "pub key drift on {name}");
767 }
768 }
769
770 #[tokio::test]
774 async fn sign_typed_and_verify_typed_round_trip() {
775 use crate::provider::CryptoProvider;
776 use crate::ring_provider::RingCryptoProvider;
777
778 for seed in [TypedSeed::Ed25519([31u8; 32]), TypedSeed::P256([37u8; 32])] {
779 let msg = b"curve-agnostic round trip";
780 let pk = RingCryptoProvider
781 .typed_public_key_from_seed(&seed)
782 .await
783 .unwrap();
784 let sig = RingCryptoProvider.sign_typed(&seed, msg).await.unwrap();
785 RingCryptoProvider
786 .verify_typed(seed.curve(), &pk, msg, &sig)
787 .await
788 .expect("verify_typed should accept matching signature");
789 }
790 }
791 }
792}