1use std::{
4 collections::BTreeSet,
5 fmt::{Debug, Display},
6 fs::read_to_string,
7 hash::Hash,
8 path::Path,
9};
10
11use argon2::Argon2;
12use getrandom::fill;
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15#[cfg(feature = "serde")]
16use serde_repr::{Deserialize_repr, Serialize_repr};
17use signstar_crypto::{
18 key::KeyType,
19 passphrase::{Passphrase, PassphrasePolicy},
20};
21use strum::{AsRefStr, IntoStaticStr};
22use yubihsm::{
23 Algorithm as YubiHsmAlgorithm,
24 asymmetric::Algorithm as YubiHsmAsymmetricAlgorithm,
25 authentication::Key as YubiHsmAuthenticationKey,
26 object::Id,
27 wrap::{Algorithm as YubiHsmWrapAlgorithm, Key as YubiHsmWrapKey},
28};
29use zeroize::Zeroizing;
30
31use crate::{automation::OpaqueDataAlgorithm, object::Capabilities};
32
33#[derive(
38 AsRefStr,
39 Clone,
40 Copy,
41 Debug,
42 strum::Display,
43 Eq,
44 Hash,
45 IntoStaticStr,
46 Ord,
47 PartialEq,
48 PartialOrd,
49)]
50#[cfg_attr(feature = "serde", derive(Deserialize_repr, Serialize_repr))]
51#[repr(u8)]
52pub enum Domain {
53 #[strum(serialize = "1")]
55 One = 1,
56 #[strum(serialize = "2")]
58 Two = 2,
59 #[strum(serialize = "3")]
61 Three = 3,
62 #[strum(serialize = "4")]
64 Four = 4,
65 #[strum(serialize = "5")]
67 Five = 5,
68 #[strum(serialize = "6")]
70 Six = 6,
71 #[strum(serialize = "7")]
73 Seven = 7,
74 #[strum(serialize = "8")]
76 Eight = 8,
77 #[strum(serialize = "9")]
79 Nine = 9,
80 #[strum(serialize = "10")]
82 Ten = 10,
83 #[strum(serialize = "11")]
85 Eleven = 11,
86 #[strum(serialize = "12")]
88 Twelve = 12,
89 #[strum(serialize = "13")]
91 Thirteen = 13,
92 #[strum(serialize = "14")]
94 Fourteen = 14,
95 #[strum(serialize = "15")]
97 Fifteen = 15,
98 #[strum(serialize = "16")]
100 Sixteen = 16,
101}
102
103#[cfg(feature = "cli")]
104impl clap::ValueEnum for Domain {
105 fn value_variants<'a>() -> &'a [Self] {
106 static VARIANTS: &[Domain] = &[
107 Domain::One,
108 Domain::Two,
109 Domain::Three,
110 Domain::Four,
111 Domain::Five,
112 Domain::Six,
113 Domain::Seven,
114 Domain::Eight,
115 Domain::Nine,
116 Domain::Ten,
117 Domain::Eleven,
118 Domain::Twelve,
119 Domain::Thirteen,
120 Domain::Fourteen,
121 Domain::Fifteen,
122 Domain::Sixteen,
123 ];
124 VARIANTS
125 }
126
127 fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
128 let str: &'static str = self.into();
129 Some(clap::builder::PossibleValue::new(str))
130 }
131}
132
133impl From<Domain> for yubihsm::Domain {
134 fn from(value: Domain) -> Self {
135 match value {
136 Domain::One => Self::DOM1,
137 Domain::Two => Self::DOM2,
138 Domain::Three => Self::DOM3,
139 Domain::Four => Self::DOM4,
140 Domain::Five => Self::DOM5,
141 Domain::Six => Self::DOM6,
142 Domain::Seven => Self::DOM7,
143 Domain::Eight => Self::DOM8,
144 Domain::Nine => Self::DOM9,
145 Domain::Ten => Self::DOM10,
146 Domain::Eleven => Self::DOM11,
147 Domain::Twelve => Self::DOM12,
148 Domain::Thirteen => Self::DOM13,
149 Domain::Fourteen => Self::DOM14,
150 Domain::Fifteen => Self::DOM15,
151 Domain::Sixteen => Self::DOM16,
152 }
153 }
154}
155
156#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
160#[cfg_attr(
161 feature = "serde",
162 derive(Serialize, Deserialize),
163 serde(try_from = "BTreeSet<Domain>")
164)]
165pub struct Domains(BTreeSet<Domain>);
166
167impl Domains {
168 pub fn to_be_bytes(&self) -> [u8; 2] {
170 self.bits().to_be_bytes()
171 }
172
173 pub fn all() -> Self {
175 yubihsm::Domain::all().bits().into()
176 }
177
178 pub fn bits(&self) -> u16 {
180 yubihsm::Domain::from(self).bits()
181 }
182}
183
184impl AsRef<BTreeSet<Domain>> for Domains {
185 fn as_ref(&self) -> &BTreeSet<Domain> {
186 &self.0
187 }
188}
189
190impl Display for Domains {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 write!(
197 f,
198 "{}",
199 self.0
200 .iter()
201 .map(|domain| domain.as_ref())
202 .collect::<Vec<_>>()
203 .join(", ")
204 )
205 }
206}
207
208impl From<Domain> for Domains {
209 fn from(value: Domain) -> Self {
210 Self(BTreeSet::from_iter([value]))
211 }
212}
213
214impl From<yubihsm::Domain> for Domains {
215 fn from(value: yubihsm::Domain) -> Self {
216 let lookup = [
217 (yubihsm::Domain::DOM1, Domain::One),
218 (yubihsm::Domain::DOM2, Domain::Two),
219 (yubihsm::Domain::DOM3, Domain::Three),
220 (yubihsm::Domain::DOM4, Domain::Four),
221 (yubihsm::Domain::DOM5, Domain::Five),
222 (yubihsm::Domain::DOM6, Domain::Six),
223 (yubihsm::Domain::DOM7, Domain::Seven),
224 (yubihsm::Domain::DOM8, Domain::Eight),
225 (yubihsm::Domain::DOM9, Domain::Nine),
226 (yubihsm::Domain::DOM10, Domain::Ten),
227 (yubihsm::Domain::DOM11, Domain::Eleven),
228 (yubihsm::Domain::DOM12, Domain::Twelve),
229 (yubihsm::Domain::DOM13, Domain::Thirteen),
230 (yubihsm::Domain::DOM14, Domain::Fourteen),
231 (yubihsm::Domain::DOM15, Domain::Fifteen),
232 (yubihsm::Domain::DOM16, Domain::Sixteen),
233 ];
234
235 Domains(BTreeSet::from_iter(lookup.iter().filter_map(
236 |(yubi_dom, dom)| {
237 if value.contains(*yubi_dom) {
238 Some(*dom)
239 } else {
240 None
241 }
242 },
243 )))
244 }
245}
246
247impl From<u16> for Domains {
248 fn from(value: u16) -> Self {
249 yubihsm::Domain::from_bits_retain(value).into()
250 }
251}
252
253impl From<&[Domain]> for Domains {
254 fn from(value: &[Domain]) -> Self {
255 Self(value.iter().copied().collect())
256 }
257}
258
259impl TryFrom<BTreeSet<Domain>> for Domains {
260 type Error = crate::object::Error;
261
262 fn try_from(domains: BTreeSet<Domain>) -> Result<Self, Self::Error> {
263 if domains.is_empty() {
264 return Err(Self::Error::EmptySetOfDomains);
265 }
266 Ok(Self(domains))
267 }
268}
269
270impl From<&Domains> for yubihsm::Domain {
271 fn from(value: &Domains) -> Self {
272 value
273 .0
274 .iter()
275 .map(|cap| yubihsm::Domain::from(*cap))
276 .fold(yubihsm::Domain::empty(), |acc, c| acc | c)
277 }
278}
279
280#[derive(Debug)]
282pub struct AuthenticationKey(YubiHsmAuthenticationKey);
283
284impl AuthenticationKey {
285 pub const PASSPHRASE_POLICY: PassphrasePolicy = PassphrasePolicy { minimum_length: 30 };
287}
288
289impl AsRef<YubiHsmAuthenticationKey> for AuthenticationKey {
290 fn as_ref(&self) -> &YubiHsmAuthenticationKey {
291 &self.0
292 }
293}
294
295impl From<AuthenticationKey> for YubiHsmAuthenticationKey {
296 fn from(value: AuthenticationKey) -> Self {
297 value.0
298 }
299}
300
301impl From<&AuthenticationKey> for YubiHsmAuthenticationKey {
302 fn from(value: &AuthenticationKey) -> Self {
303 value.0.clone()
304 }
305}
306
307impl TryFrom<&Path> for AuthenticationKey {
308 type Error = crate::Error;
309
310 fn try_from(file: &Path) -> Result<Self, Self::Error> {
322 let passphrase = Passphrase::new_with_policy(
323 read_to_string(file).map_err(|source| crate::Error::IoPath {
324 path: file.into(),
325 context: "reading the passphrase for an authentication key derivation from file",
326 source,
327 })?,
328 &Self::PASSPHRASE_POLICY,
329 )?;
330
331 Ok(Self(YubiHsmAuthenticationKey::derive_from_password(
332 passphrase.expose_borrowed().as_bytes(),
333 )))
334 }
335}
336
337impl TryFrom<&Passphrase> for AuthenticationKey {
338 type Error = crate::Error;
339
340 fn try_from(passphrase: &Passphrase) -> Result<Self, Self::Error> {
348 passphrase.check_against_policy(&Self::PASSPHRASE_POLICY)?;
349
350 Ok(Self(YubiHsmAuthenticationKey::derive_from_password(
351 passphrase.expose_borrowed().as_bytes(),
352 )))
353 }
354}
355
356#[derive(Clone, Copy, Debug, Default, Eq, Hash, IntoStaticStr, Ord, PartialEq, PartialOrd)]
358pub enum WrapKeyKind {
359 Aes128,
361
362 Aes192,
364
365 #[default]
373 Aes256,
374}
375
376impl WrapKeyKind {
377 pub fn key_len(&self) -> usize {
379 match self {
380 Self::Aes128 => 16,
381 Self::Aes192 => 24,
382 Self::Aes256 => 32,
383 }
384 }
385}
386
387impl From<&WrapKeyKind> for YubiHsmWrapAlgorithm {
388 fn from(value: &WrapKeyKind) -> Self {
389 match value {
390 WrapKeyKind::Aes128 => Self::Aes128Ccm,
391 WrapKeyKind::Aes192 => Self::Aes192Ccm,
392 WrapKeyKind::Aes256 => Self::Aes256Ccm,
393 }
394 }
395}
396
397impl From<YubiHsmWrapAlgorithm> for WrapKeyKind {
398 fn from(value: YubiHsmWrapAlgorithm) -> Self {
399 match value {
400 YubiHsmWrapAlgorithm::Aes128Ccm => Self::Aes128,
401 YubiHsmWrapAlgorithm::Aes192Ccm => Self::Aes192,
402 YubiHsmWrapAlgorithm::Aes256Ccm => Self::Aes256,
403 }
404 }
405}
406
407pub struct WrapKey {
411 kind: WrapKeyKind,
412 data: Zeroizing<Vec<u8>>,
413}
414
415impl WrapKey {
416 pub const PASSPHRASE_POLICY: PassphrasePolicy = PassphrasePolicy {
418 minimum_length: 100,
419 };
420
421 pub fn generate_random(kind: WrapKeyKind) -> Result<Self, crate::Error> {
427 let data = {
428 let mut bytes = Zeroizing::new(vec![0u8; kind.key_len()]);
429 fill(&mut bytes).map_err(|source| crate::object::Error::GetRandom {
430 context: "generating a random wrapping key",
431 source,
432 })?;
433 bytes
434 };
435
436 Ok(Self { kind, data })
437 }
438}
439
440impl Debug for WrapKey {
441 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
442 f.debug_struct("WrapKey")
443 .field("kind", &self.kind)
444 .field("data", &"[REDACTED]")
445 .finish()
446 }
447}
448
449impl From<&WrapKey> for Vec<u8> {
450 fn from(value: &WrapKey) -> Self {
451 value.data.to_vec()
452 }
453}
454
455#[derive(Debug)]
462pub struct WrapKeyFromPassphrase<'passphrase> {
463 passphrase: &'passphrase Passphrase,
464 kind: WrapKeyKind,
465}
466
467impl<'passphrase> WrapKeyFromPassphrase<'passphrase> {
468 pub(crate) const ARGON2_SALT: &'static [u8] = b"Salt for a Signstar backup key";
470
471 pub fn new(
484 passphrase: &'passphrase Passphrase,
485 kind: WrapKeyKind,
486 ) -> Result<Self, crate::Error> {
487 passphrase.check_against_policy(&WrapKey::PASSPHRASE_POLICY)?;
488
489 Ok(Self { passphrase, kind })
490 }
491}
492
493impl<'passphrase> TryFrom<WrapKeyFromPassphrase<'passphrase>> for WrapKey {
494 type Error = crate::Error;
495
496 fn try_from(value: WrapKeyFromPassphrase<'passphrase>) -> Result<Self, Self::Error> {
508 let mut data = Zeroizing::new(vec![0u8; value.kind.key_len()]);
509 Argon2::default()
510 .hash_password_into(
511 value.passphrase.expose_borrowed().as_bytes(),
512 WrapKeyFromPassphrase::ARGON2_SALT,
513 &mut data,
514 )
515 .map_err(|source| crate::object::Error::Argon2 {
516 context: "creating a wrap key from a passphrase",
517 source,
518 })?;
519
520 Ok(WrapKey {
521 kind: value.kind,
522 data,
523 })
524 }
525}
526
527#[derive(Debug)]
531pub struct YubiHsmWrapKeyFromWrapKey<'wrap_key> {
532 pub(crate) id: Id,
533 pub(crate) wrap_key: &'wrap_key WrapKey,
534}
535
536impl<'wrap_key> TryFrom<&YubiHsmWrapKeyFromWrapKey<'wrap_key>> for YubiHsmWrapKey {
537 type Error = crate::Error;
538
539 fn try_from(value: &YubiHsmWrapKeyFromWrapKey) -> Result<Self, Self::Error> {
545 Self::from_bytes(value.id, &value.wrap_key.data).map_err(|source| crate::Error::Device {
546 context: "creating a YubiHSM2 wrap key from bytes",
547 source,
548 })
549 }
550}
551
552#[derive(Clone, Debug)]
557#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
558pub struct KeyInfo {
559 pub key_id: Id,
561
562 pub domains: Domains,
567
568 pub caps: Capabilities,
570}
571
572#[derive(Clone, Copy, Debug, strum::Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
582pub enum AsymmetricAlgorithm {
583 Rsa2048,
585
586 Rsa3072,
588
589 Rsa4096,
591
592 Ed25519,
594
595 EcP224,
597
598 EcP256,
600
601 EcP384,
603
604 EcP521,
606
607 EcK256,
609
610 EcBp256,
612
613 EcBp384,
615
616 EcBp512,
618}
619
620impl From<YubiHsmAsymmetricAlgorithm> for AsymmetricAlgorithm {
621 fn from(value: YubiHsmAsymmetricAlgorithm) -> Self {
622 match value {
623 YubiHsmAsymmetricAlgorithm::Rsa2048 => Self::Rsa2048,
624 YubiHsmAsymmetricAlgorithm::Rsa3072 => Self::Rsa3072,
625 YubiHsmAsymmetricAlgorithm::Rsa4096 => Self::Rsa4096,
626 YubiHsmAsymmetricAlgorithm::Ed25519 => Self::Ed25519,
627 YubiHsmAsymmetricAlgorithm::EcP224 => Self::EcP224,
628 YubiHsmAsymmetricAlgorithm::EcP256 => Self::EcP256,
629 YubiHsmAsymmetricAlgorithm::EcP384 => Self::EcP384,
630 YubiHsmAsymmetricAlgorithm::EcP521 => Self::EcP521,
631 YubiHsmAsymmetricAlgorithm::EcK256 => Self::EcK256,
632 YubiHsmAsymmetricAlgorithm::EcBp256 => Self::EcBp256,
633 YubiHsmAsymmetricAlgorithm::EcBp384 => Self::EcBp384,
634 YubiHsmAsymmetricAlgorithm::EcBp512 => Self::EcBp512,
635 }
636 }
637}
638
639impl PartialEq<KeyType> for AsymmetricAlgorithm {
640 fn eq(&self, other: &KeyType) -> bool {
641 matches!(
642 (other, self),
643 (KeyType::Rsa, Self::Rsa2048)
644 | (KeyType::Rsa, Self::Rsa3072)
645 | (KeyType::Rsa, Self::Rsa4096)
646 | (KeyType::Curve25519, Self::Ed25519)
647 | (KeyType::EcP224, Self::EcP224)
648 | (KeyType::EcP256, Self::EcP256)
649 | (KeyType::EcP384, Self::EcP384)
650 | (KeyType::EcP521, Self::EcP521)
651 | (KeyType::EcK256, Self::EcK256)
652 | (KeyType::EcBp256, Self::EcBp256)
653 | (KeyType::EcBp384, Self::EcBp384)
654 )
655 }
656}
657
658#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
668pub enum ObjectAlgorithm {
669 Asymmetric(AsymmetricAlgorithm),
671
672 Authentication,
674
675 Ecdh,
677
678 Ecdsa,
680
681 Hmac,
683
684 Mgf,
686
687 Opaque(OpaqueDataAlgorithm),
689
690 Rsa,
692
693 Template,
695
696 Wrap(WrapKeyKind),
698
699 YubicoOtp,
701}
702
703impl From<YubiHsmAlgorithm> for ObjectAlgorithm {
704 fn from(value: YubiHsmAlgorithm) -> Self {
705 match value {
706 YubiHsmAlgorithm::Asymmetric(algorithm) => Self::Asymmetric(algorithm.into()),
707 YubiHsmAlgorithm::Authentication(_) => Self::Authentication,
708 YubiHsmAlgorithm::Ecdh(_) => Self::Ecdh,
709 YubiHsmAlgorithm::Ecdsa(_) => Self::Ecdsa,
710 YubiHsmAlgorithm::Hmac(_) => Self::Hmac,
711 YubiHsmAlgorithm::Mgf(_) => Self::Mgf,
712 YubiHsmAlgorithm::Opaque(algorithm) => Self::Opaque(algorithm.into()),
713 YubiHsmAlgorithm::Rsa(_) => Self::Rsa,
714 YubiHsmAlgorithm::Template(_) => Self::Template,
715 YubiHsmAlgorithm::Wrap(algorithm) => Self::Wrap(algorithm.into()),
716 YubiHsmAlgorithm::YubicoOtp(_) => Self::YubicoOtp,
717 }
718 }
719}
720
721#[cfg(test)]
722mod tests {
723 use std::io::Write;
724
725 use rand::{
726 distributions::{Alphanumeric, DistString},
727 thread_rng,
728 };
729 use rstest::{fixture, rstest};
730 use tempfile::{NamedTempFile, TempDir};
731 use testresult::TestResult;
732
733 use super::*;
734
735 #[test]
737 fn domains_to_string() {
738 let domain_list = vec![Domain::One];
739 let domains = Domains::from(domain_list.as_slice());
740 assert_eq!("1", domains.to_string());
741
742 let domain_list = vec![Domain::One, Domain::Two];
743 let domains = Domains::from(domain_list.as_slice());
744 assert_eq!("1, 2", domains.to_string());
745 }
746
747 #[test]
748 fn authentication_key_try_from_path_succeeds() -> TestResult {
749 let file = {
750 let mut file = NamedTempFile::new()?;
751 let passphrase = Alphanumeric.sample_string(&mut thread_rng(), 30);
752 file.write_all(passphrase.as_bytes())?;
753 file
754 };
755
756 match AuthenticationKey::try_from(file.path()) {
757 Ok(_) => {}
758 Err(error) => panic!(
759 "Expected to create an authentication key from the contents of a file, but got an error instead: {error}"
760 ),
761 }
762
763 Ok(())
764 }
765
766 #[test]
767 fn authentication_key_try_from_path_fails_on_short_passphrase() -> TestResult {
768 let file = {
769 let mut file = NamedTempFile::new()?;
770 let passphrase = Alphanumeric.sample_string(&mut thread_rng(), 10);
771 file.write_all(passphrase.as_bytes())?;
772 file
773 };
774
775 match AuthenticationKey::try_from(file.path()) {
776 Ok(_) => panic!(
777 "Expected to fail with Error::Length, but succeeded in creating an authentication key from a passphrase file instead."
778 ),
779 Err(crate::Error::SignstarCrypto(signstar_crypto::Error::Passphrase(_))) => {}
780 Err(error) => panic!(
781 "Expected to fail with Error::Length, but failed with a different error instead: {error}"
782 ),
783 }
784
785 Ok(())
786 }
787
788 #[test]
789 fn authentication_key_try_from_path_fails_on_file_is_dir() -> TestResult {
790 let file = TempDir::new()?;
791
792 match AuthenticationKey::try_from(file.path()) {
793 Ok(_) => panic!(
794 "Expected to fail with Error::IoPath, but succeeded in creating an authentication key from a passphrase file instead."
795 ),
796 Err(crate::Error::IoPath { .. }) => {}
797 Err(error) => panic!(
798 "Expected to fail with Error::IoPath, but failed with a different error instead: {error}"
799 ),
800 }
801
802 Ok(())
803 }
804
805 #[test]
806 fn authentication_key_try_from_passphrase_succeeds() -> TestResult {
807 let passphrase = Passphrase::generate(Some(30));
808
809 match AuthenticationKey::try_from(&passphrase) {
810 Ok(_) => {}
811 Err(error) => panic!(
812 "Expected to create an authentication key from a passphrase, but got an error instead: {error}"
813 ),
814 }
815
816 Ok(())
817 }
818
819 #[test]
820 fn authentication_key_try_from_passphrase_fails_on_passphrase_too_short() -> TestResult {
821 let passphrase = Passphrase::new("passphrase".to_string());
822
823 match AuthenticationKey::try_from(&passphrase) {
824 Ok(_) => panic!("Expected to fail with Error::Length, but succeeded instead."),
825 Err(crate::Error::SignstarCrypto(signstar_crypto::Error::Passphrase(_))) => {}
826 Err(error) => panic!(
827 "Expected to fail with Error::Length, but failed with a different error instead: {error}"
828 ),
829 }
830
831 Ok(())
832 }
833
834 #[rstest]
836 #[case(WrapKeyKind::Aes128, 16)]
837 #[case(WrapKeyKind::Aes192, 24)]
838 #[case(WrapKeyKind::Aes256, 32)]
839 fn wrap_key_kind_key_len(#[case] wrap_key_kind: WrapKeyKind, #[case] len: usize) {
840 assert_eq!(wrap_key_kind.key_len(), len);
841 }
842
843 #[rstest]
846 #[case(WrapKeyKind::Aes128, YubiHsmWrapAlgorithm::Aes128Ccm)]
847 #[case(WrapKeyKind::Aes192, YubiHsmWrapAlgorithm::Aes192Ccm)]
848 #[case(WrapKeyKind::Aes256, YubiHsmWrapAlgorithm::Aes256Ccm)]
849 fn yubihsm_wrap_algorithm_from_wrap_key_kind(
850 #[case] wrap_key_kind: WrapKeyKind,
851 #[case] algorithm: YubiHsmWrapAlgorithm,
852 ) {
853 assert_eq!(YubiHsmWrapAlgorithm::from(&wrap_key_kind), algorithm);
854 }
855
856 #[rstest]
858 #[case(WrapKeyKind::Aes128)]
859 #[case(WrapKeyKind::Aes192)]
860 #[case(WrapKeyKind::Aes256)]
861 fn wrap_key_generate_random_succeeds(#[case] wrap_key_kind: WrapKeyKind) -> TestResult {
862 let wrap_key = WrapKey::generate_random(wrap_key_kind)?;
863 let data: Vec<u8> = From::from(&wrap_key);
864
865 assert_eq!(data.len(), wrap_key_kind.key_len());
866
867 Ok(())
868 }
869
870 #[rstest]
872 #[case(WrapKeyKind::Aes128)]
873 #[case(WrapKeyKind::Aes192)]
874 #[case(WrapKeyKind::Aes256)]
875 fn wrap_key_debug(#[case] wrap_key_kind: WrapKeyKind) -> TestResult {
876 let wrap_key = WrapKey::generate_random(wrap_key_kind)?;
877 let data_debug = format!("{:?}", wrap_key.data.to_vec());
878 let wrap_key_debug = format!("{wrap_key:?}");
879 let wrap_key_kind_debug = format!("{wrap_key_kind:?}");
880
881 assert!(wrap_key_debug.contains(&wrap_key_kind_debug));
882 assert!(wrap_key_debug.contains("[REDACTED]"));
883 assert!(!wrap_key_debug.contains(&data_debug));
884
885 Ok(())
886 }
887
888 #[fixture]
890 fn valid_wrap_key_passphrase() -> Passphrase {
891 Passphrase::new("this is a long passphrase that is at least 100 chars long, very long omg, so long, really now, you gotta believe me".to_string())
892 }
893
894 #[fixture]
896 fn invalid_wrap_key_passphrase() -> Passphrase {
897 Passphrase::new("this passphrase is shorter than 100 chars".to_string())
898 }
899
900 #[rstest]
902 #[case(WrapKeyKind::Aes128)]
903 #[case(WrapKeyKind::Aes192)]
904 #[case(WrapKeyKind::Aes256)]
905 fn wrap_key_from_passphrase_new_succeeds(
906 #[case] wrap_key_kind: WrapKeyKind,
907 valid_wrap_key_passphrase: Passphrase,
908 ) -> TestResult {
909 WrapKeyFromPassphrase::new(&valid_wrap_key_passphrase, wrap_key_kind)?;
910
911 Ok(())
912 }
913
914 #[rstest]
916 #[case(WrapKeyKind::Aes128)]
917 #[case(WrapKeyKind::Aes192)]
918 #[case(WrapKeyKind::Aes256)]
919 fn wrap_key_from_passphrase_new_fails_on_short_passphrase(
920 #[case] wrap_key_kind: WrapKeyKind,
921 invalid_wrap_key_passphrase: Passphrase,
922 ) -> TestResult {
923 assert!(WrapKeyFromPassphrase::new(&invalid_wrap_key_passphrase, wrap_key_kind).is_err());
924
925 Ok(())
926 }
927
928 #[rstest]
930 #[case(WrapKeyKind::Aes128)]
931 #[case(WrapKeyKind::Aes192)]
932 #[case(WrapKeyKind::Aes256)]
933 fn wrap_key_try_from_wrap_key_from_passphrase_succeeds(
934 #[case] wrap_key_kind: WrapKeyKind,
935 valid_wrap_key_passphrase: Passphrase,
936 ) -> TestResult {
937 let wrap_key_from_passphrase =
938 WrapKeyFromPassphrase::new(&valid_wrap_key_passphrase, wrap_key_kind)?;
939 let wrap_key = WrapKey::try_from(wrap_key_from_passphrase)?;
940 let data: Vec<u8> = From::from(&wrap_key);
941
942 assert_eq!(data.len(), wrap_key_kind.key_len());
943
944 Ok(())
945 }
946
947 #[rstest]
949 #[case(WrapKeyKind::Aes128)]
950 #[case(WrapKeyKind::Aes192)]
951 #[case(WrapKeyKind::Aes256)]
952 fn yubihsm_wrap_key_try_from_yubihsm_wrap_key_from_wrap_key_succeeds(
953 #[case] wrap_key_kind: WrapKeyKind,
954 valid_wrap_key_passphrase: Passphrase,
955 ) -> TestResult {
956 let wrap_key = {
957 let wrap_key_from_passphrase =
958 WrapKeyFromPassphrase::new(&valid_wrap_key_passphrase, wrap_key_kind)?;
959 WrapKey::try_from(wrap_key_from_passphrase)?
960 };
961 let yubihsm_wrap_key_from_wrap_key = YubiHsmWrapKeyFromWrapKey {
962 id: "1".parse()?,
963 wrap_key: &wrap_key,
964 };
965
966 YubiHsmWrapKey::try_from(&yubihsm_wrap_key_from_wrap_key)?;
967
968 Ok(())
969 }
970}