1use std::{array::TryFromSliceError, fmt::Debug};
43
44use aes::{Aes128, cipher::typenum::Unsigned};
45use base64ct::{Base64, Encoding as _};
46use ccm::{
47 Ccm,
48 Nonce,
49 aead::{Aead, KeyInit, rand_core::RngCore},
50 consts::{U13, U16},
51};
52use curve25519_dalek::Scalar;
53use ed25519_dalek::{SigningKey, hazmat::ExpandedSecretKey};
54use num_enum::{FromPrimitive, IntoPrimitive};
55use yubihsm::object::{Handle, Type};
56
57#[cfg(doc)]
58use crate::object::Capabilities;
59use crate::object::{Domains, ObjectId};
60
61#[derive(Debug, thiserror::Error)]
63pub enum Error {
64 #[error("Decoding Base64 failed: {0}")]
66 Base64Decode(#[from] base64ct::Error),
67
68 #[error("Decryption error: {0}")]
70 Decrypt(#[from] ccm::Error),
71
72 #[error("Incorrect slice length: {0}")]
74 SliceLength(#[from] TryFromSliceError),
75
76 #[error("Unexpected Ed25519 serialized form length: {actual}")]
80 UnexpectedEd25519SerializedLength {
81 actual: usize,
83 },
84
85 #[error("Cannot parse data of unknown type: {0:?}")]
87 UnknownObjectType(ObjectType),
88
89 #[error("YubiHSM2 object error: {0:?}")]
91 YubiHsmObject(#[from] yubihsm::object::Error),
92
93 #[error("Parsing buffer: not enough data.")]
95 InsufficientDataInBuffer,
96}
97
98pub struct PlainWrappedDataWithKey<'a, 'b> {
100 pub data: &'a [u8],
102
103 pub key: &'b [u8],
105}
106
107impl Debug for PlainWrappedDataWithKey<'_, '_> {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 f.debug_struct("PlainWrappedDataWithKey")
110 .field("data", &self.data)
111 .field("key", &"[REDACTED]")
112 .finish()
113 }
114}
115
116impl TryFrom<PlainWrappedDataWithKey<'_, '_>> for YubiHsm2Wrap {
117 type Error = Error;
118
119 fn try_from(value: PlainWrappedDataWithKey<'_, '_>) -> Result<Self, Self::Error> {
125 let cipher = Aes128Ccm::new(value.key.into());
126 let mut nonce = [0; 13];
127 let mut rng = aes::cipher::crypto_common::rand_core::OsRng;
128 rng.fill_bytes(&mut nonce);
129 let mut wrapped = cipher.encrypt(Nonce::from_slice(&nonce), value.data)?;
130 wrapped.splice(0..0, nonce);
131
132 Ok(Self { wrapped })
133 }
134}
135
136type Aes128Ccm = Ccm<Aes128, U16, U13>;
137
138#[derive(Debug)]
140pub struct YubiHsm2Wrap {
141 wrapped: Vec<u8>,
142}
143
144impl YubiHsm2Wrap {
145 pub fn new(wrapped: Vec<u8>) -> Self {
147 Self { wrapped }
148 }
149
150 pub fn from_yhw(wrapped: &str) -> Result<Self, Error> {
160 let wrapped = wrapped.trim_ascii();
161 let wrapped = Base64::decode_vec(wrapped)?;
162 Ok(Self { wrapped })
163 }
164
165 pub fn to_yhw(&self) -> String {
168 Base64::encode_string(&self.wrapped)
169 }
170
171 pub fn decrypt(&self, wrapping_key: &[u8]) -> Result<Vec<u8>, Error> {
177 let cipher = Aes128Ccm::new(wrapping_key.into());
178 let (nonce, ciphertext) = self.wrapped.split_at(U13::to_usize());
179 let plaintext = cipher.decrypt(nonce.into(), ciphertext)?;
180
181 Ok(plaintext)
182 }
183}
184
185impl AsRef<[u8]> for YubiHsm2Wrap {
186 fn as_ref(&self) -> &[u8] {
187 &self.wrapped
188 }
189}
190
191#[derive(Clone, Copy, Debug, Eq, FromPrimitive, IntoPrimitive, PartialEq)]
195#[repr(u8)]
196pub enum WrapAlgorithm {
197 Aes128Ccm = 29,
199
200 Aes192Ccm = 41,
202
203 Aes256Ccm = 42,
205
206 #[num_enum(catch_all)]
208 Unknown(u8),
209}
210
211#[derive(Clone, Copy, Debug, Eq, FromPrimitive, IntoPrimitive, PartialEq)]
216#[repr(u8)]
217pub enum ObjectType {
218 Ed25519 = 46,
222
223 Aes128Auth = 38,
227
228 Opaque = 30,
232
233 #[num_enum(catch_all)]
235 Unknown(u8),
236}
237
238#[derive(Clone, Debug, Eq, PartialEq)]
240pub struct ExpandedEd25519KeyData<'a> {
241 pub private_scalar: &'a [u8; 32],
243
244 pub private_hash_prefix: &'a [u8; 32],
246
247 pub public: &'a [u8; 32],
249}
250
251impl ExpandedEd25519KeyData<'_> {
252 pub const LEN: usize = 32 * 3;
254}
255
256impl<'a> From<ExpandedEd25519KeyData<'a>> for ExpandedSecretKey {
257 fn from(value: ExpandedEd25519KeyData<'a>) -> Self {
258 let mut private_scalar = *value.private_scalar;
259 private_scalar.reverse();
260 ExpandedSecretKey {
261 scalar: Scalar::from_bytes_mod_order(private_scalar),
262 hash_prefix: *value.private_hash_prefix,
263 }
264 }
265}
266
267impl<'a> TryFrom<&'a [u8]> for ExpandedEd25519KeyData<'a> {
268 type Error = TryFromSliceError;
269 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
270 Ok(Self {
271 private_scalar: value[0..32].try_into()?,
272 private_hash_prefix: value[32..64].try_into()?,
273 public: value[64..].try_into()?,
274 })
275 }
276}
277
278#[derive(Clone, Debug, Eq, PartialEq)]
284pub struct SeedEd25519KeyData<'a> {
285 pub private_scalar: &'a [u8; 32],
287
288 pub private_hash_prefix: &'a [u8; 32],
290
291 pub public: &'a [u8; 32],
293
294 pub private_seed: &'a [u8; 32],
296}
297
298impl SeedEd25519KeyData<'_> {
299 pub const LEN: usize = 32 * 4;
301}
302
303impl<'a> TryFrom<&'a [u8]> for SeedEd25519KeyData<'a> {
304 type Error = TryFromSliceError;
305 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
306 Ok(Self {
307 private_seed: value[0..32].try_into()?,
308 private_scalar: value[32..64].try_into()?,
309 private_hash_prefix: value[64..96].try_into()?,
310 public: value[96..].try_into()?,
311 })
312 }
313}
314
315impl<'a> From<SeedEd25519KeyData<'a>> for ExpandedSecretKey {
316 fn from(value: SeedEd25519KeyData<'a>) -> Self {
317 let mut private_scalar = *value.private_scalar;
318 private_scalar.reverse();
319
320 ExpandedSecretKey {
322 scalar: Scalar::from_bytes_mod_order(private_scalar),
323 hash_prefix: *value.private_hash_prefix,
324 }
325 }
326}
327
328impl<'a> From<&'a SeedEd25519KeyData<'a>> for SigningKey {
329 fn from(value: &'a SeedEd25519KeyData<'a>) -> Self {
330 SigningKey::from(value.private_seed)
331 }
332}
333
334#[derive(Debug)]
343pub struct SerializedEd25519([u8; 32 * 4]);
344
345impl AsRef<[u8]> for SerializedEd25519 {
346 fn as_ref(&self) -> &[u8] {
347 &self.0
348 }
349}
350
351impl From<&SigningKey> for SerializedEd25519 {
352 fn from(value: &SigningKey) -> Self {
353 let mut result = [0; _];
354 let expanded = ExpandedSecretKey::from(&value.to_bytes());
355 result[0..32].copy_from_slice(value.as_bytes());
356 result[32..64].copy_from_slice(expanded.scalar.as_bytes());
357 result[32..64].reverse();
358 result[64..96].copy_from_slice(&expanded.hash_prefix);
359 result[96..].copy_from_slice(value.verifying_key().as_bytes());
360 Self(result)
361 }
362}
363
364#[derive(Clone, Debug, Eq, PartialEq)]
366pub struct AuthAes128<'a> {
367 pub delegated_capabilities: &'a [u8; 8],
369
370 pub symmetric_keys: &'a [u8; 32],
372}
373
374impl AuthAes128<'_> {
375 const LEN: usize = 8 + 32;
377}
378
379#[derive(Clone, Debug, Eq, PartialEq)]
385pub enum WrappedPayload<'a> {
386 ExpandedEd25519(ExpandedEd25519KeyData<'a>),
388
389 SeedEd25519(SeedEd25519KeyData<'a>),
391
392 AuthAes128(AuthAes128<'a>),
394
395 Opaque(&'a [u8]),
397}
398
399impl<'a> WrappedPayload<'a> {
400 fn parse(object_type: ObjectType, bytes: &'a [u8]) -> Result<WrappedPayload<'a>, Error> {
414 Ok(match object_type {
415 ObjectType::Ed25519 => match bytes.len() {
416 ExpandedEd25519KeyData::LEN => Self::ExpandedEd25519(bytes.try_into()?),
417 SeedEd25519KeyData::LEN => Self::SeedEd25519(bytes.try_into()?),
418 len => return Err(Error::UnexpectedEd25519SerializedLength { actual: len }),
419 },
420 ObjectType::Aes128Auth => {
421 let (delegated_capabilities, symmetric_keys) = bytes.split_at(8);
422 Self::AuthAes128(AuthAes128 {
423 delegated_capabilities: delegated_capabilities.try_into()?,
424 symmetric_keys: symmetric_keys.try_into()?,
425 })
426 }
427 ObjectType::Opaque => Self::Opaque(bytes),
428 object_type => return Err(Error::UnknownObjectType(object_type)),
429 })
430 }
431
432 fn serialize_into(&self, buffer: &mut Vec<u8>) {
434 match self {
435 WrappedPayload::ExpandedEd25519(key_data) => {
436 buffer.extend_from_slice(key_data.private_scalar);
437 buffer.extend_from_slice(key_data.private_hash_prefix);
438 buffer.extend_from_slice(key_data.public);
439 }
440 WrappedPayload::SeedEd25519(key_data) => {
441 buffer.extend_from_slice(key_data.private_seed);
442 buffer.extend_from_slice(key_data.private_scalar);
443 buffer.extend_from_slice(key_data.private_hash_prefix);
444 buffer.extend_from_slice(key_data.public);
445 }
446 WrappedPayload::AuthAes128(key_data) => {
447 buffer.extend_from_slice(key_data.delegated_capabilities);
448 buffer.extend_from_slice(key_data.symmetric_keys);
449 }
450 WrappedPayload::Opaque(key_data) => buffer.extend_from_slice(key_data),
451 }
452 }
453
454 fn len(&self) -> usize {
456 match self {
457 WrappedPayload::ExpandedEd25519(_) => ExpandedEd25519KeyData::LEN,
458 WrappedPayload::SeedEd25519(_) => SeedEd25519KeyData::LEN,
459 WrappedPayload::AuthAes128(_) => AuthAes128::LEN,
460 WrappedPayload::Opaque(key_data) => key_data.len(),
461 }
462 }
463}
464
465struct BeReader<'a> {
467 pos: usize,
468 buf: &'a [u8],
469}
470
471impl<'a> BeReader<'a> {
472 fn new(buf: &'a [u8]) -> Self {
474 Self { buf, pos: 0 }
475 }
476
477 fn position(&self) -> usize {
479 self.pos
480 }
481
482 fn read_u8(&mut self) -> Result<u8, Error> {
488 if self.pos + 1 >= self.buf.len() {
489 return Err(Error::InsufficientDataInBuffer);
490 }
491 let byte = self.buf[self.pos];
492 self.pos += 1;
493 Ok(byte)
494 }
495
496 fn read_u16(&mut self) -> Result<u16, Error> {
503 Ok(u16::from_be_bytes([self.read_u8()?, self.read_u8()?]))
504 }
505
506 fn read<const N: usize>(&mut self) -> Result<&'a [u8; N], Error> {
513 if self.pos + N >= self.buf.len() {
514 return Err(Error::InsufficientDataInBuffer);
515 }
516 let bytes = &self.buf[self.pos..self.pos + N];
517 self.pos += N;
518 bytes
519 .try_into()
520 .map_err(|_| Error::InsufficientDataInBuffer)
521 }
522
523 fn read_to_end(&mut self) -> Result<&'a [u8], Error> {
530 if self.pos > self.buf.len() {
531 return Err(Error::InsufficientDataInBuffer);
532 }
533 let bytes = &self.buf[self.pos..];
534 self.pos = self.buf.len() + 1;
535 Ok(bytes)
536 }
537}
538
539#[derive(Debug)]
541pub struct InnerFormat<'a> {
542 pub wrap_algorithm: WrapAlgorithm,
544
545 pub capabilities: &'a [u8; 8],
547
548 pub object_id: ObjectId,
550
551 pub domains: Domains,
553
554 pub object_type: ObjectType,
556
557 pub sequence: u8,
559
560 pub origin: u8,
562
563 pub label: String,
565
566 pub key_data: WrappedPayload<'a>,
568}
569
570impl<'a> InnerFormat<'a> {
571 pub fn parse(raw: &'a [u8]) -> Result<Self, crate::Error> {
580 let mut reader = BeReader::new(raw);
581
582 let wrap_algorithm = WrapAlgorithm::from(reader.read_u8()?);
583 let capabilities = reader.read()?;
584 let id = reader.read_u16()?;
585 let datalen = reader.read_u16()?;
586 let domains = reader.read_u16()?.into();
587 let object_id = ObjectId::try_from(Handle::new(
588 id,
589 Type::from_u8(reader.read_u8()?).map_err(Error::YubiHsmObject)?,
590 ))?;
591 let object_type = ObjectType::from(reader.read_u8()?);
592 let sequence = reader.read_u8()?;
593 let origin = reader.read_u8()?;
594
595 let label = reader.read::<40>()?;
596 let len = label.iter().position(|&b| b == 0).unwrap_or(label.len());
597 let label = String::from_utf8_lossy(&label[..len]).into();
598
599 if reader.position() + datalen as usize != raw.len() {
601 return Err(Error::InsufficientDataInBuffer)?;
602 }
603
604 Ok(Self {
605 wrap_algorithm,
606 capabilities,
607 object_id,
608 domains,
609 object_type,
610 sequence,
611 origin,
612 label,
613 key_data: WrappedPayload::parse(object_type, reader.read_to_end()?)?,
614 })
615 }
616
617 pub fn serialize_into(&self, buffer: &mut Vec<u8>) {
619 buffer.push(self.wrap_algorithm.into());
620 buffer.extend_from_slice(self.capabilities);
621 buffer.extend_from_slice(&u16::from(self.object_id.id()).to_be_bytes());
622 buffer.extend_from_slice(&(self.key_data.len() as u16).to_be_bytes());
623 buffer.extend_from_slice(&self.domains.to_be_bytes());
624 buffer.push(self.object_id.object_type().to_u8());
625 buffer.push(self.object_type.into());
626 buffer.push(self.sequence);
627 buffer.push(self.origin);
628 let mut label: [u8; 40] = [0; 40];
629 let slice_len = self.label.len().min(label.len());
630 label[..slice_len].copy_from_slice(self.label.as_bytes());
631 buffer.extend_from_slice(&label);
632 self.key_data.serialize_into(buffer);
633 }
634}
635
636#[cfg(test)]
637mod tests {
638
639 use ed25519_dalek::VerifyingKey;
640 use testresult::TestResult;
641
642 use super::*;
643 use crate::object::Domain;
644
645 const WRAP_KEY: &[u8] = &[
646 0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
647 ];
648
649 #[test]
650 fn decrypt_ed25519() -> TestResult {
651 let wrap = YubiHsm2Wrap::from_yhw(include_str!("../tests/backup/private-ed25519.yhw"))?;
652 let decrypted = wrap.decrypt(WRAP_KEY)?;
653 assert!(!decrypted.is_empty());
654 let inner = InnerFormat::parse(&decrypted)?;
655 let mut buffer = vec![];
656 inner.serialize_into(&mut buffer);
657 assert_eq!(buffer, decrypted);
658 assert_eq!(inner.object_type, ObjectType::Ed25519);
659 assert_eq!(inner.wrap_algorithm, WrapAlgorithm::Aes128Ccm);
660 assert_eq!(u16::from(inner.object_id.id()), 0x1f_u16);
661 assert_eq!(inner.domains, Domain::One.into());
662 assert_eq!(inner.sequence, 0);
663 assert_eq!(inner.origin, 2);
664 assert_eq!(inner.label, "Ed25519_Key");
665 let WrappedPayload::ExpandedEd25519(key_data) = inner.key_data else {
666 panic!("Expected Ed25519 key data");
667 };
668 let ExpandedEd25519KeyData {
669 private_scalar,
670 private_hash_prefix,
671 public,
672 } = key_data;
673
674 assert_eq!(
675 private_scalar,
676 &[
677 117, 188, 78, 175, 249, 221, 207, 75, 177, 26, 92, 146, 43, 19, 156, 7, 87, 43,
678 173, 199, 232, 63, 249, 230, 100, 131, 86, 147, 80, 229, 193, 192
679 ]
680 );
681 assert_eq!(
682 private_hash_prefix,
683 &[
684 182, 113, 137, 6, 206, 62, 108, 30, 26, 138, 65, 215, 178, 10, 9, 215, 181, 55,
685 132, 37, 162, 172, 202, 169, 56, 150, 245, 195, 212, 232, 235, 183
686 ]
687 );
688 assert_eq!(
689 public,
690 &[
691 185, 235, 254, 46, 190, 171, 17, 45, 56, 27, 211, 240, 69, 46, 39, 226, 53, 109,
692 50, 78, 181, 96, 30, 177, 162, 240, 122, 187, 82, 30, 156, 242
693 ]
694 );
695 let signing_key: ExpandedSecretKey = key_data.into();
696 let verifying_key = VerifyingKey::from(&signing_key);
697 assert_eq!(public, &verifying_key.to_bytes());
698 Ok(())
699 }
700
701 #[test]
702 fn decrypt_ed25519_with_seed() -> TestResult {
703 let wrap =
704 YubiHsm2Wrap::from_yhw(include_str!("../tests/backup/private-ed25519-seed.yhw"))?;
705 let decrypted = wrap.decrypt(WRAP_KEY)?;
706 assert!(!decrypted.is_empty());
707 let inner = InnerFormat::parse(&decrypted)?;
708 let mut buffer = vec![];
709 inner.serialize_into(&mut buffer);
710 assert_eq!(buffer, decrypted);
711 assert_eq!(inner.object_type, ObjectType::Ed25519);
712 assert_eq!(inner.wrap_algorithm, WrapAlgorithm::Aes128Ccm);
713 assert_eq!(u16::from(inner.object_id.id()), 13);
714 assert_eq!(inner.domains, Domains::all());
715 assert_eq!(inner.sequence, 0);
716 assert_eq!(inner.origin, 1);
717 assert_eq!(inner.label, "Signature_Key_Ed_2");
718 let WrappedPayload::SeedEd25519(key_data) = inner.key_data.clone() else {
719 panic!("Expected Ed25519 key data");
720 };
721
722 let SeedEd25519KeyData {
723 private_scalar,
724 private_hash_prefix,
725 public,
726 private_seed,
727 } = key_data;
728
729 assert_eq!(
730 private_seed,
731 &[
732 73, 122, 141, 156, 79, 125, 147, 201, 97, 207, 112, 15, 133, 155, 17, 216, 4, 254,
733 88, 71, 207, 139, 63, 170, 229, 246, 54, 32, 206, 12, 84, 86
734 ]
735 );
736 assert_eq!(
737 private_scalar,
738 &[
739 7, 81, 112, 122, 75, 85, 173, 6, 20, 181, 199, 29, 147, 191, 157, 102, 147, 157,
740 133, 249, 149, 223, 14, 41, 17, 51, 179, 38, 146, 102, 210, 15
741 ]
742 );
743 assert_eq!(
744 private_hash_prefix,
745 &[
746 161, 55, 166, 21, 136, 215, 184, 182, 181, 62, 143, 223, 62, 159, 19, 228, 179, 87,
747 101, 158, 129, 137, 207, 186, 191, 206, 220, 148, 44, 83, 203, 115
748 ]
749 );
750 assert_eq!(
751 public,
752 &[
753 252, 157, 136, 36, 18, 36, 60, 188, 181, 153, 78, 169, 136, 74, 14, 210, 150, 203,
754 47, 42, 79, 2, 238, 0, 103, 237, 202, 100, 87, 40, 252, 44
755 ]
756 );
757 let signing_key = SigningKey::from(&key_data);
758 let serialized = SerializedEd25519::from(&signing_key);
759 assert_eq!(
760 inner.key_data,
761 WrappedPayload::parse(ObjectType::Ed25519, serialized.as_ref())?
762 );
763
764 assert_eq!(public, &signing_key.verifying_key().to_bytes());
765 let exp = ExpandedSecretKey::from(private_seed);
766
767 let mut private_scalar = *private_scalar;
768 private_scalar.reverse();
769
770 assert_eq!(exp.scalar.as_bytes(), &private_scalar);
771 assert_eq!(&exp.hash_prefix, private_hash_prefix);
772
773 let signing_key: ExpandedSecretKey = key_data.into();
774 assert_eq!(exp.scalar, signing_key.scalar);
775 assert_eq!(exp.hash_prefix, signing_key.hash_prefix);
776
777 let verifying_key = VerifyingKey::from(&signing_key);
778 assert_eq!(public, &verifying_key.to_bytes());
779 Ok(())
780 }
781
782 #[test]
783 fn auth_key() -> TestResult {
784 let wrap = YubiHsm2Wrap::from_yhw(include_str!("../tests/backup/auth.yhw"))?;
785 let decrypted = wrap.decrypt(WRAP_KEY)?;
786 assert!(!decrypted.is_empty());
787 let inner = InnerFormat::parse(&decrypted)?;
788 let mut buffer = vec![];
789 inner.serialize_into(&mut buffer);
790 assert_eq!(decrypted, buffer);
791 assert_eq!(inner.object_type, ObjectType::Aes128Auth);
792 assert_eq!(inner.capabilities, &[0, 0, 0, 0, 0, 1, 0, 0]);
793 assert_eq!(inner.domains, Domain::One.into());
794 assert_eq!(u16::from(inner.object_id.id()), 14);
795 assert_eq!(
796 inner.key_data,
797 WrappedPayload::AuthAes128(AuthAes128 {
798 delegated_capabilities: &[0; 8],
799 symmetric_keys: &[
800 152, 123, 73, 154, 181, 120, 84, 139, 48, 32, 41, 176, 213, 53, 39, 232, 122,
801 150, 131, 153, 10, 233, 98, 202, 67, 12, 27, 245, 184, 198, 41, 93
802 ]
803 })
804 );
805 assert_eq!(inner.object_id.object_type(), Type::AuthenticationKey);
806 assert_eq!(inner.label, "");
807 assert_eq!(inner.origin, 2);
808 assert_eq!(inner.sequence, 0);
809 Ok(())
810 }
811
812 #[test]
813 fn opaque_data() -> TestResult {
814 let wrap = YubiHsm2Wrap::from_yhw(include_str!("../tests/backup/opaque.yhw"))?;
815 let decrypted = wrap.decrypt(WRAP_KEY)?;
816 assert!(!decrypted.is_empty());
817 let inner = InnerFormat::parse(&decrypted)?;
818 let mut buffer = vec![];
819 inner.serialize_into(&mut buffer);
820 assert_eq!(decrypted, buffer);
821 assert_eq!(inner.object_type, ObjectType::Opaque);
822 assert_eq!(inner.capabilities, &[0, 0, 0, 0, 0, 1, 0, 0]);
823 assert_eq!(inner.domains, Domain::One.into());
824 assert_eq!(u16::from(inner.object_id.id()), 13);
825 assert_eq!(inner.key_data, WrappedPayload::Opaque(&[1, 2, 3]));
826 assert_eq!(inner.object_id.object_type(), Type::Opaque);
827 assert_eq!(inner.label, "random");
828 assert_eq!(inner.origin, 2);
829 assert_eq!(inner.sequence, 0);
830 Ok(())
831 }
832
833 #[test]
834 fn roundtrip_yhw() -> TestResult {
835 let input = include_str!("../tests/backup/private-ed25519-seed.yhw");
836 let wrap = YubiHsm2Wrap::from_yhw(input)?;
837 assert_eq!(input, wrap.to_yhw());
838 Ok(())
839 }
840
841 #[test]
842 fn encrypt_decrypt() -> TestResult {
843 let input = include_str!("../tests/backup/opaque.yhw");
844 let wrap = YubiHsm2Wrap::from_yhw(input)?;
845 let decrypted_original = wrap.decrypt(WRAP_KEY)?;
846 let plain = PlainWrappedDataWithKey {
847 data: &decrypted_original,
848 key: WRAP_KEY,
849 };
850 let wrap: YubiHsm2Wrap = plain.try_into()?;
851 let decrypted_from_plain = wrap.decrypt(WRAP_KEY)?;
852 assert_eq!(decrypted_original, decrypted_from_plain);
853 Ok(())
854 }
855}