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, backup::Label, 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(
358 Clone, Copy, Debug, Default, strum::Display, Eq, Hash, IntoStaticStr, Ord, PartialEq, PartialOrd,
359)]
360#[strum(serialize_all = "kebab-case")]
361pub enum WrapKeyKind {
362 Aes128,
364
365 Aes192,
367
368 #[default]
376 Aes256,
377}
378
379impl WrapKeyKind {
380 pub fn key_len(&self) -> usize {
382 match self {
383 Self::Aes128 => 16,
384 Self::Aes192 => 24,
385 Self::Aes256 => 32,
386 }
387 }
388}
389
390impl From<&WrapKeyKind> for YubiHsmWrapAlgorithm {
391 fn from(value: &WrapKeyKind) -> Self {
392 match value {
393 WrapKeyKind::Aes128 => Self::Aes128Ccm,
394 WrapKeyKind::Aes192 => Self::Aes192Ccm,
395 WrapKeyKind::Aes256 => Self::Aes256Ccm,
396 }
397 }
398}
399
400impl From<YubiHsmWrapAlgorithm> for WrapKeyKind {
401 fn from(value: YubiHsmWrapAlgorithm) -> Self {
402 match value {
403 YubiHsmWrapAlgorithm::Aes128Ccm => Self::Aes128,
404 YubiHsmWrapAlgorithm::Aes192Ccm => Self::Aes192,
405 YubiHsmWrapAlgorithm::Aes256Ccm => Self::Aes256,
406 }
407 }
408}
409
410pub struct WrapKey {
414 kind: WrapKeyKind,
415 data: Zeroizing<Vec<u8>>,
416}
417
418impl WrapKey {
419 pub const PASSPHRASE_POLICY: PassphrasePolicy = PassphrasePolicy {
421 minimum_length: 100,
422 };
423
424 pub fn generate_random(kind: WrapKeyKind) -> Result<Self, crate::Error> {
430 let data = {
431 let mut bytes = Zeroizing::new(vec![0u8; kind.key_len()]);
432 fill(&mut bytes).map_err(|source| crate::object::Error::GetRandom {
433 context: "generating a random wrapping key",
434 source,
435 })?;
436 bytes
437 };
438
439 Ok(Self { kind, data })
440 }
441}
442
443impl Debug for WrapKey {
444 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
445 f.debug_struct("WrapKey")
446 .field("kind", &self.kind)
447 .field("data", &"[REDACTED]")
448 .finish()
449 }
450}
451
452impl From<&WrapKey> for Vec<u8> {
453 fn from(value: &WrapKey) -> Self {
454 value.data.to_vec()
455 }
456}
457
458#[derive(Debug)]
465pub struct WrapKeyFromPassphrase<'passphrase> {
466 passphrase: &'passphrase Passphrase,
467 kind: WrapKeyKind,
468}
469
470impl<'passphrase> WrapKeyFromPassphrase<'passphrase> {
471 pub(crate) const ARGON2_SALT: &'static [u8] = b"Salt for a Signstar backup key";
473
474 pub fn new(
487 passphrase: &'passphrase Passphrase,
488 kind: WrapKeyKind,
489 ) -> Result<Self, crate::Error> {
490 passphrase.check_against_policy(&WrapKey::PASSPHRASE_POLICY)?;
491
492 Ok(Self { passphrase, kind })
493 }
494}
495
496impl<'passphrase> TryFrom<WrapKeyFromPassphrase<'passphrase>> for WrapKey {
497 type Error = crate::Error;
498
499 fn try_from(value: WrapKeyFromPassphrase<'passphrase>) -> Result<Self, Self::Error> {
511 let mut data = Zeroizing::new(vec![0u8; value.kind.key_len()]);
512 Argon2::default()
513 .hash_password_into(
514 value.passphrase.expose_borrowed().as_bytes(),
515 WrapKeyFromPassphrase::ARGON2_SALT,
516 &mut data,
517 )
518 .map_err(|source| crate::object::Error::Argon2 {
519 context: "creating a wrap key from a passphrase",
520 source,
521 })?;
522
523 Ok(WrapKey {
524 kind: value.kind,
525 data,
526 })
527 }
528}
529
530#[derive(Debug)]
534pub struct YubiHsmWrapKeyFromWrapKey<'wrap_key> {
535 pub(crate) id: Id,
536 pub(crate) wrap_key: &'wrap_key WrapKey,
537}
538
539impl<'wrap_key> TryFrom<&YubiHsmWrapKeyFromWrapKey<'wrap_key>> for YubiHsmWrapKey {
540 type Error = crate::Error;
541
542 fn try_from(value: &YubiHsmWrapKeyFromWrapKey) -> Result<Self, Self::Error> {
548 Self::from_bytes(value.id, &value.wrap_key.data).map_err(|source| crate::Error::Device {
549 context: "creating a YubiHSM2 wrap key from bytes",
550 source,
551 })
552 }
553}
554
555#[derive(Clone, Debug)]
560#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
561pub struct KeyInfo {
562 pub key_id: Id,
564
565 pub domains: Domains,
570
571 pub caps: Capabilities,
573
574 pub label: Label,
576}
577
578#[derive(Clone, Copy, Debug, strum::Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
588#[strum(serialize_all = "kebab-case")]
589pub enum AsymmetricAlgorithm {
590 Rsa2048,
592
593 Rsa3072,
595
596 Rsa4096,
598
599 Ed25519,
601
602 EcP224,
604
605 EcP256,
607
608 EcP384,
610
611 EcP521,
613
614 EcK256,
616
617 EcBp256,
619
620 EcBp384,
622
623 EcBp512,
625}
626
627impl From<YubiHsmAsymmetricAlgorithm> for AsymmetricAlgorithm {
628 fn from(value: YubiHsmAsymmetricAlgorithm) -> Self {
629 match value {
630 YubiHsmAsymmetricAlgorithm::Rsa2048 => Self::Rsa2048,
631 YubiHsmAsymmetricAlgorithm::Rsa3072 => Self::Rsa3072,
632 YubiHsmAsymmetricAlgorithm::Rsa4096 => Self::Rsa4096,
633 YubiHsmAsymmetricAlgorithm::Ed25519 => Self::Ed25519,
634 YubiHsmAsymmetricAlgorithm::EcP224 => Self::EcP224,
635 YubiHsmAsymmetricAlgorithm::EcP256 => Self::EcP256,
636 YubiHsmAsymmetricAlgorithm::EcP384 => Self::EcP384,
637 YubiHsmAsymmetricAlgorithm::EcP521 => Self::EcP521,
638 YubiHsmAsymmetricAlgorithm::EcK256 => Self::EcK256,
639 YubiHsmAsymmetricAlgorithm::EcBp256 => Self::EcBp256,
640 YubiHsmAsymmetricAlgorithm::EcBp384 => Self::EcBp384,
641 YubiHsmAsymmetricAlgorithm::EcBp512 => Self::EcBp512,
642 }
643 }
644}
645
646impl PartialEq<KeyType> for AsymmetricAlgorithm {
647 fn eq(&self, other: &KeyType) -> bool {
648 matches!(
649 (other, self),
650 (KeyType::Rsa, Self::Rsa2048)
651 | (KeyType::Rsa, Self::Rsa3072)
652 | (KeyType::Rsa, Self::Rsa4096)
653 | (KeyType::Curve25519, Self::Ed25519)
654 | (KeyType::EcP224, Self::EcP224)
655 | (KeyType::EcP256, Self::EcP256)
656 | (KeyType::EcP384, Self::EcP384)
657 | (KeyType::EcP521, Self::EcP521)
658 | (KeyType::EcK256, Self::EcK256)
659 | (KeyType::EcBp256, Self::EcBp256)
660 | (KeyType::EcBp384, Self::EcBp384)
661 )
662 }
663}
664
665#[derive(Clone, Copy, Debug, strum::Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
675#[strum(serialize_all = "kebab-case")]
676pub enum ObjectAlgorithm {
677 #[strum(to_string = "asymmetric ({0})")]
679 Asymmetric(AsymmetricAlgorithm),
680
681 Authentication,
683
684 Ecdh,
686
687 Ecdsa,
689
690 Hmac,
692
693 Mgf,
695
696 #[strum(to_string = "opaque ({0})")]
698 Opaque(OpaqueDataAlgorithm),
699
700 Symmetric,
702
703 Rsa,
705
706 Template,
708
709 #[strum(to_string = "wrap ({0})")]
711 Wrap(WrapKeyKind),
712
713 YubicoOtp,
715
716 Unknown(u8),
718}
719
720impl From<YubiHsmAlgorithm> for ObjectAlgorithm {
721 fn from(value: YubiHsmAlgorithm) -> Self {
722 match value {
723 YubiHsmAlgorithm::Asymmetric(algorithm) => Self::Asymmetric(algorithm.into()),
724 YubiHsmAlgorithm::Authentication(_) => Self::Authentication,
725 YubiHsmAlgorithm::Ecdh(_) => Self::Ecdh,
726 YubiHsmAlgorithm::Ecdsa(_) => Self::Ecdsa,
727 YubiHsmAlgorithm::Hmac(_) => Self::Hmac,
728 YubiHsmAlgorithm::Mgf(_) => Self::Mgf,
729 YubiHsmAlgorithm::Opaque(algorithm) => Self::Opaque(algorithm.into()),
730 YubiHsmAlgorithm::Rsa(_) => Self::Rsa,
731 YubiHsmAlgorithm::Symmetric(_) => Self::Symmetric,
732 YubiHsmAlgorithm::Template(_) => Self::Template,
733 YubiHsmAlgorithm::Wrap(algorithm) => Self::Wrap(algorithm.into()),
734 YubiHsmAlgorithm::YubicoOtp(_) => Self::YubicoOtp,
735 YubiHsmAlgorithm::Unknown(id) => Self::Unknown(id),
736 }
737 }
738}
739
740#[cfg(test)]
741mod tests {
742 use std::io::Write;
743
744 use rand::{
745 distr::{Alphanumeric, SampleString},
746 rng,
747 };
748 use rstest::{fixture, rstest};
749 use tempfile::{NamedTempFile, TempDir};
750 use testresult::TestResult;
751
752 use super::*;
753
754 #[test]
756 fn domains_to_string() {
757 let domain_list = vec![Domain::One];
758 let domains = Domains::from(domain_list.as_slice());
759 assert_eq!("1", domains.to_string());
760
761 let domain_list = vec![Domain::One, Domain::Two];
762 let domains = Domains::from(domain_list.as_slice());
763 assert_eq!("1, 2", domains.to_string());
764 }
765
766 #[test]
767 fn authentication_key_try_from_path_succeeds() -> TestResult {
768 let file = {
769 let mut file = NamedTempFile::new()?;
770 let passphrase = Alphanumeric.sample_string(&mut rng(), 30);
771 file.write_all(passphrase.as_bytes())?;
772 file
773 };
774
775 match AuthenticationKey::try_from(file.path()) {
776 Ok(_) => {}
777 Err(error) => panic!(
778 "Expected to create an authentication key from the contents of a file, but got an error instead: {error}"
779 ),
780 }
781
782 Ok(())
783 }
784
785 #[test]
786 fn authentication_key_try_from_path_fails_on_short_passphrase() -> TestResult {
787 let file = {
788 let mut file = NamedTempFile::new()?;
789 let passphrase = Alphanumeric.sample_string(&mut rng(), 10);
790 file.write_all(passphrase.as_bytes())?;
791 file
792 };
793
794 match AuthenticationKey::try_from(file.path()) {
795 Ok(_) => panic!(
796 "Expected to fail with Error::Length, but succeeded in creating an authentication key from a passphrase file instead."
797 ),
798 Err(crate::Error::SignstarCrypto(signstar_crypto::Error::Passphrase(_))) => {}
799 Err(error) => panic!(
800 "Expected to fail with Error::Length, but failed with a different error instead: {error}"
801 ),
802 }
803
804 Ok(())
805 }
806
807 #[test]
808 fn authentication_key_try_from_path_fails_on_file_is_dir() -> TestResult {
809 let file = TempDir::new()?;
810
811 match AuthenticationKey::try_from(file.path()) {
812 Ok(_) => panic!(
813 "Expected to fail with Error::IoPath, but succeeded in creating an authentication key from a passphrase file instead."
814 ),
815 Err(crate::Error::IoPath { .. }) => {}
816 Err(error) => panic!(
817 "Expected to fail with Error::IoPath, but failed with a different error instead: {error}"
818 ),
819 }
820
821 Ok(())
822 }
823
824 #[test]
825 fn authentication_key_try_from_passphrase_succeeds() -> TestResult {
826 let passphrase = Passphrase::generate(Some(30));
827
828 match AuthenticationKey::try_from(&passphrase) {
829 Ok(_) => {}
830 Err(error) => panic!(
831 "Expected to create an authentication key from a passphrase, but got an error instead: {error}"
832 ),
833 }
834
835 Ok(())
836 }
837
838 #[test]
839 fn authentication_key_try_from_passphrase_fails_on_passphrase_too_short() -> TestResult {
840 let passphrase = Passphrase::new("passphrase".to_string());
841
842 match AuthenticationKey::try_from(&passphrase) {
843 Ok(_) => panic!("Expected to fail with Error::Length, but succeeded instead."),
844 Err(crate::Error::SignstarCrypto(signstar_crypto::Error::Passphrase(_))) => {}
845 Err(error) => panic!(
846 "Expected to fail with Error::Length, but failed with a different error instead: {error}"
847 ),
848 }
849
850 Ok(())
851 }
852
853 #[rstest]
855 #[case(WrapKeyKind::Aes128, 16)]
856 #[case(WrapKeyKind::Aes192, 24)]
857 #[case(WrapKeyKind::Aes256, 32)]
858 fn wrap_key_kind_key_len(#[case] wrap_key_kind: WrapKeyKind, #[case] len: usize) {
859 assert_eq!(wrap_key_kind.key_len(), len);
860 }
861
862 #[rstest]
865 #[case(WrapKeyKind::Aes128, YubiHsmWrapAlgorithm::Aes128Ccm)]
866 #[case(WrapKeyKind::Aes192, YubiHsmWrapAlgorithm::Aes192Ccm)]
867 #[case(WrapKeyKind::Aes256, YubiHsmWrapAlgorithm::Aes256Ccm)]
868 fn yubihsm_wrap_algorithm_from_wrap_key_kind(
869 #[case] wrap_key_kind: WrapKeyKind,
870 #[case] algorithm: YubiHsmWrapAlgorithm,
871 ) {
872 assert_eq!(YubiHsmWrapAlgorithm::from(&wrap_key_kind), algorithm);
873 }
874
875 #[rstest]
877 #[case(WrapKeyKind::Aes128)]
878 #[case(WrapKeyKind::Aes192)]
879 #[case(WrapKeyKind::Aes256)]
880 fn wrap_key_generate_random_succeeds(#[case] wrap_key_kind: WrapKeyKind) -> TestResult {
881 let wrap_key = WrapKey::generate_random(wrap_key_kind)?;
882 let data: Vec<u8> = From::from(&wrap_key);
883
884 assert_eq!(data.len(), wrap_key_kind.key_len());
885
886 Ok(())
887 }
888
889 #[rstest]
891 #[case(WrapKeyKind::Aes128)]
892 #[case(WrapKeyKind::Aes192)]
893 #[case(WrapKeyKind::Aes256)]
894 fn wrap_key_debug(#[case] wrap_key_kind: WrapKeyKind) -> TestResult {
895 let wrap_key = WrapKey::generate_random(wrap_key_kind)?;
896 let data_debug = format!("{:?}", wrap_key.data.to_vec());
897 let wrap_key_debug = format!("{wrap_key:?}");
898 let wrap_key_kind_debug = format!("{wrap_key_kind:?}");
899
900 assert!(wrap_key_debug.contains(&wrap_key_kind_debug));
901 assert!(wrap_key_debug.contains("[REDACTED]"));
902 assert!(!wrap_key_debug.contains(&data_debug));
903
904 Ok(())
905 }
906
907 #[fixture]
909 fn valid_wrap_key_passphrase() -> Passphrase {
910 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())
911 }
912
913 #[fixture]
915 fn invalid_wrap_key_passphrase() -> Passphrase {
916 Passphrase::new("this passphrase is shorter than 100 chars".to_string())
917 }
918
919 #[rstest]
921 #[case(WrapKeyKind::Aes128)]
922 #[case(WrapKeyKind::Aes192)]
923 #[case(WrapKeyKind::Aes256)]
924 fn wrap_key_from_passphrase_new_succeeds(
925 #[case] wrap_key_kind: WrapKeyKind,
926 valid_wrap_key_passphrase: Passphrase,
927 ) -> TestResult {
928 WrapKeyFromPassphrase::new(&valid_wrap_key_passphrase, wrap_key_kind)?;
929
930 Ok(())
931 }
932
933 #[rstest]
935 #[case(WrapKeyKind::Aes128)]
936 #[case(WrapKeyKind::Aes192)]
937 #[case(WrapKeyKind::Aes256)]
938 fn wrap_key_from_passphrase_new_fails_on_short_passphrase(
939 #[case] wrap_key_kind: WrapKeyKind,
940 invalid_wrap_key_passphrase: Passphrase,
941 ) -> TestResult {
942 assert!(WrapKeyFromPassphrase::new(&invalid_wrap_key_passphrase, wrap_key_kind).is_err());
943
944 Ok(())
945 }
946
947 #[rstest]
949 #[case(WrapKeyKind::Aes128)]
950 #[case(WrapKeyKind::Aes192)]
951 #[case(WrapKeyKind::Aes256)]
952 fn wrap_key_try_from_wrap_key_from_passphrase_succeeds(
953 #[case] wrap_key_kind: WrapKeyKind,
954 valid_wrap_key_passphrase: Passphrase,
955 ) -> TestResult {
956 let wrap_key_from_passphrase =
957 WrapKeyFromPassphrase::new(&valid_wrap_key_passphrase, wrap_key_kind)?;
958 let wrap_key = WrapKey::try_from(wrap_key_from_passphrase)?;
959 let data: Vec<u8> = From::from(&wrap_key);
960
961 assert_eq!(data.len(), wrap_key_kind.key_len());
962
963 Ok(())
964 }
965
966 #[rstest]
968 #[case(WrapKeyKind::Aes128)]
969 #[case(WrapKeyKind::Aes192)]
970 #[case(WrapKeyKind::Aes256)]
971 fn yubihsm_wrap_key_try_from_yubihsm_wrap_key_from_wrap_key_succeeds(
972 #[case] wrap_key_kind: WrapKeyKind,
973 valid_wrap_key_passphrase: Passphrase,
974 ) -> TestResult {
975 let wrap_key = {
976 let wrap_key_from_passphrase =
977 WrapKeyFromPassphrase::new(&valid_wrap_key_passphrase, wrap_key_kind)?;
978 WrapKey::try_from(wrap_key_from_passphrase)?
979 };
980 let yubihsm_wrap_key_from_wrap_key = YubiHsmWrapKeyFromWrapKey {
981 id: "1".parse()?,
982 wrap_key: &wrap_key,
983 };
984
985 YubiHsmWrapKey::try_from(&yubihsm_wrap_key_from_wrap_key)?;
986
987 Ok(())
988 }
989}