1use std::{
43 array::TryFromSliceError,
44 fmt::{Debug, Display},
45 fs::read,
46 path::Path,
47 str::FromStr,
48};
49
50use aes::{Aes128, cipher::typenum::Unsigned};
51use base64ct::{Base64, Encoding as _};
52use ccm::{
53 Ccm,
54 Nonce,
55 aead::{Aead, KeyInit, rand_core::RngCore},
56 consts::{U13, U16},
57};
58use curve25519_dalek::Scalar;
59use ed25519_dalek::{SigningKey, hazmat::ExpandedSecretKey};
60use num_enum::{FromPrimitive, IntoPrimitive};
61use yubihsm::object::{Handle, Type};
62
63use crate::object::{Capabilities, Domains, Id, ObjectId};
64
65#[derive(Debug, thiserror::Error)]
67pub enum Error {
68 #[error("Decoding Base64 failed: {0}")]
70 Base64Decode(#[from] base64ct::Error),
71
72 #[error("Decryption error: {0}")]
74 Decrypt(#[from] ccm::Error),
75
76 #[error("Incorrect slice length: {0}")]
78 SliceLength(#[from] TryFromSliceError),
79
80 #[error("Unexpected Ed25519 serialized form length: {actual}")]
84 UnexpectedEd25519SerializedLength {
85 actual: usize,
87 },
88
89 #[error("Cannot parse data of unknown type: {0:?}")]
91 UnknownObjectType(ObjectType),
92
93 #[error("YubiHSM2 object error: {0:?}")]
95 YubiHsmObject(#[from] yubihsm::object::Error),
96
97 #[error("Parsing buffer: not enough data.")]
99 InsufficientDataInBuffer,
100
101 #[error(
103 "The string '{label}' could not be converted to a label as it exceeds the 40 bytes limit."
104 )]
105 LabelLength {
106 label: String,
108 },
109}
110
111pub struct PlainWrappedDataWithKey<'a, 'b> {
113 pub data: &'a [u8],
115
116 pub key: &'b [u8],
118}
119
120impl Debug for PlainWrappedDataWithKey<'_, '_> {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 f.debug_struct("PlainWrappedDataWithKey")
123 .field("data", &self.data)
124 .field("key", &"[REDACTED]")
125 .finish()
126 }
127}
128
129impl TryFrom<PlainWrappedDataWithKey<'_, '_>> for YubiHsm2Wrap {
130 type Error = Error;
131
132 fn try_from(value: PlainWrappedDataWithKey<'_, '_>) -> Result<Self, Self::Error> {
138 let cipher = Aes128Ccm::new(value.key.into());
139 let mut nonce = [0; 13];
140 let mut rng = aes::cipher::crypto_common::rand_core::OsRng;
141 rng.fill_bytes(&mut nonce);
142 let mut wrapped = cipher.encrypt(Nonce::from_slice(&nonce), value.data)?;
143 wrapped.splice(0..0, nonce);
144
145 Ok(Self { wrapped })
146 }
147}
148
149type Aes128Ccm = Ccm<Aes128, U16, U13>;
150
151#[derive(Debug)]
153pub struct YubiHsm2Wrap {
154 wrapped: Vec<u8>,
155}
156
157impl YubiHsm2Wrap {
158 pub fn new(wrapped: Vec<u8>) -> Self {
160 Self { wrapped }
161 }
162
163 pub fn from_yhw(wrapped: &str) -> Result<Self, Error> {
173 let wrapped = wrapped.trim_ascii();
174 let wrapped = Base64::decode_vec(wrapped)?;
175 Ok(Self { wrapped })
176 }
177
178 pub fn to_yhw(&self) -> String {
181 Base64::encode_string(&self.wrapped)
182 }
183
184 pub fn decrypt(&self, wrapping_key: &[u8]) -> Result<Vec<u8>, Error> {
190 let cipher = Aes128Ccm::new(wrapping_key.into());
191 let (nonce, ciphertext) = self.wrapped.split_at(U13::to_usize());
192 let plaintext = cipher.decrypt(nonce.into(), ciphertext)?;
193
194 Ok(plaintext)
195 }
196}
197
198impl AsRef<[u8]> for YubiHsm2Wrap {
199 fn as_ref(&self) -> &[u8] {
200 &self.wrapped
201 }
202}
203
204#[derive(Clone, Copy, Debug, Eq, FromPrimitive, IntoPrimitive, PartialEq)]
208#[repr(u8)]
209pub enum WrapAlgorithm {
210 Aes128Ccm = 29,
212
213 Aes192Ccm = 41,
215
216 Aes256Ccm = 42,
218
219 #[num_enum(catch_all)]
221 Unknown(u8),
222}
223
224#[derive(Clone, Copy, Debug, Eq, FromPrimitive, IntoPrimitive, PartialEq)]
229#[repr(u8)]
230pub enum ObjectType {
231 Ed25519 = 46,
235
236 Aes128Auth = 38,
240
241 Opaque = 30,
245
246 #[num_enum(catch_all)]
248 Unknown(u8),
249}
250
251#[derive(Clone, Debug, Eq, PartialEq)]
253pub struct ExpandedEd25519KeyData<'a> {
254 pub private_scalar: &'a [u8; 32],
256
257 pub private_hash_prefix: &'a [u8; 32],
259
260 pub public: &'a [u8; 32],
262}
263
264impl ExpandedEd25519KeyData<'_> {
265 pub const LEN: usize = 32 * 3;
267}
268
269impl<'a> From<ExpandedEd25519KeyData<'a>> for ExpandedSecretKey {
270 fn from(value: ExpandedEd25519KeyData<'a>) -> Self {
271 let mut private_scalar = *value.private_scalar;
272 private_scalar.reverse();
273 ExpandedSecretKey {
274 scalar: Scalar::from_bytes_mod_order(private_scalar),
275 hash_prefix: *value.private_hash_prefix,
276 }
277 }
278}
279
280impl<'a> TryFrom<&'a [u8]> for ExpandedEd25519KeyData<'a> {
281 type Error = TryFromSliceError;
282 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
283 Ok(Self {
284 private_scalar: value[0..32].try_into()?,
285 private_hash_prefix: value[32..64].try_into()?,
286 public: value[64..].try_into()?,
287 })
288 }
289}
290
291#[derive(Clone, Debug, Eq, PartialEq)]
297pub struct SeedEd25519KeyData<'a> {
298 pub private_scalar: &'a [u8; 32],
300
301 pub private_hash_prefix: &'a [u8; 32],
303
304 pub public: &'a [u8; 32],
306
307 pub private_seed: &'a [u8; 32],
309}
310
311impl SeedEd25519KeyData<'_> {
312 pub const LEN: usize = 32 * 4;
314}
315
316impl<'a> TryFrom<&'a [u8]> for SeedEd25519KeyData<'a> {
317 type Error = TryFromSliceError;
318 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
319 Ok(Self {
320 private_seed: value[0..32].try_into()?,
321 private_scalar: value[32..64].try_into()?,
322 private_hash_prefix: value[64..96].try_into()?,
323 public: value[96..].try_into()?,
324 })
325 }
326}
327
328impl<'a> From<SeedEd25519KeyData<'a>> for ExpandedSecretKey {
329 fn from(value: SeedEd25519KeyData<'a>) -> Self {
330 let mut private_scalar = *value.private_scalar;
331 private_scalar.reverse();
332
333 ExpandedSecretKey {
335 scalar: Scalar::from_bytes_mod_order(private_scalar),
336 hash_prefix: *value.private_hash_prefix,
337 }
338 }
339}
340
341impl<'a> From<&'a SeedEd25519KeyData<'a>> for SigningKey {
342 fn from(value: &'a SeedEd25519KeyData<'a>) -> Self {
343 SigningKey::from(value.private_seed)
344 }
345}
346
347#[derive(Debug)]
356pub struct SerializedEd25519([u8; 32 * 4]);
357
358impl AsRef<[u8]> for SerializedEd25519 {
359 fn as_ref(&self) -> &[u8] {
360 &self.0
361 }
362}
363
364impl From<&SigningKey> for SerializedEd25519 {
365 fn from(value: &SigningKey) -> Self {
366 let mut result = [0; _];
367 let expanded = ExpandedSecretKey::from(&value.to_bytes());
368 result[0..32].copy_from_slice(value.as_bytes());
369 result[32..64].copy_from_slice(expanded.scalar.as_bytes());
370 result[32..64].reverse();
371 result[64..96].copy_from_slice(&expanded.hash_prefix);
372 result[96..].copy_from_slice(value.verifying_key().as_bytes());
373 Self(result)
374 }
375}
376
377#[derive(Clone, Debug, Eq, PartialEq)]
379pub struct AuthAes128<'a> {
380 pub delegated_capabilities: &'a [u8; 8],
382
383 pub symmetric_keys: &'a [u8; 32],
385}
386
387impl AuthAes128<'_> {
388 const LEN: usize = 8 + 32;
390}
391
392#[derive(Clone, Debug, Eq, PartialEq)]
398pub enum WrappedPayload<'a> {
399 ExpandedEd25519(ExpandedEd25519KeyData<'a>),
401
402 SeedEd25519(SeedEd25519KeyData<'a>),
404
405 AuthAes128(AuthAes128<'a>),
407
408 Opaque(&'a [u8]),
410}
411
412impl<'a> WrappedPayload<'a> {
413 fn parse(object_type: ObjectType, bytes: &'a [u8]) -> Result<WrappedPayload<'a>, Error> {
427 Ok(match object_type {
428 ObjectType::Ed25519 => match bytes.len() {
429 ExpandedEd25519KeyData::LEN => Self::ExpandedEd25519(bytes.try_into()?),
430 SeedEd25519KeyData::LEN => Self::SeedEd25519(bytes.try_into()?),
431 len => return Err(Error::UnexpectedEd25519SerializedLength { actual: len }),
432 },
433 ObjectType::Aes128Auth => {
434 let (delegated_capabilities, symmetric_keys) = bytes.split_at(8);
435 Self::AuthAes128(AuthAes128 {
436 delegated_capabilities: delegated_capabilities.try_into()?,
437 symmetric_keys: symmetric_keys.try_into()?,
438 })
439 }
440 ObjectType::Opaque => Self::Opaque(bytes),
441 object_type => return Err(Error::UnknownObjectType(object_type)),
442 })
443 }
444
445 fn serialize_into(&self, buffer: &mut Vec<u8>) {
447 match self {
448 WrappedPayload::ExpandedEd25519(key_data) => {
449 buffer.extend_from_slice(key_data.private_scalar);
450 buffer.extend_from_slice(key_data.private_hash_prefix);
451 buffer.extend_from_slice(key_data.public);
452 }
453 WrappedPayload::SeedEd25519(key_data) => {
454 buffer.extend_from_slice(key_data.private_seed);
455 buffer.extend_from_slice(key_data.private_scalar);
456 buffer.extend_from_slice(key_data.private_hash_prefix);
457 buffer.extend_from_slice(key_data.public);
458 }
459 WrappedPayload::AuthAes128(key_data) => {
460 buffer.extend_from_slice(key_data.delegated_capabilities);
461 buffer.extend_from_slice(key_data.symmetric_keys);
462 }
463 WrappedPayload::Opaque(key_data) => buffer.extend_from_slice(key_data),
464 }
465 }
466
467 fn len(&self) -> usize {
469 match self {
470 WrappedPayload::ExpandedEd25519(_) => ExpandedEd25519KeyData::LEN,
471 WrappedPayload::SeedEd25519(_) => SeedEd25519KeyData::LEN,
472 WrappedPayload::AuthAes128(_) => AuthAes128::LEN,
473 WrappedPayload::Opaque(key_data) => key_data.len(),
474 }
475 }
476}
477
478struct BeReader<'a> {
480 pos: usize,
481 buf: &'a [u8],
482}
483
484impl<'a> BeReader<'a> {
485 fn new(buf: &'a [u8]) -> Self {
487 Self { buf, pos: 0 }
488 }
489
490 fn position(&self) -> usize {
492 self.pos
493 }
494
495 fn read_u8(&mut self) -> Result<u8, Error> {
501 if self.pos + 1 >= self.buf.len() {
502 return Err(Error::InsufficientDataInBuffer);
503 }
504 let byte = self.buf[self.pos];
505 self.pos += 1;
506 Ok(byte)
507 }
508
509 fn read_u16(&mut self) -> Result<u16, Error> {
516 Ok(u16::from_be_bytes([self.read_u8()?, self.read_u8()?]))
517 }
518
519 fn read<const N: usize>(&mut self) -> Result<&'a [u8; N], Error> {
526 if self.pos + N >= self.buf.len() {
527 return Err(Error::InsufficientDataInBuffer);
528 }
529 let bytes = &self.buf[self.pos..self.pos + N];
530 self.pos += N;
531 bytes
532 .try_into()
533 .map_err(|_| Error::InsufficientDataInBuffer)
534 }
535
536 fn read_to_end(&mut self) -> Result<&'a [u8], Error> {
543 if self.pos > self.buf.len() {
544 return Err(Error::InsufficientDataInBuffer);
545 }
546 let bytes = &self.buf[self.pos..];
547 self.pos = self.buf.len() + 1;
548 Ok(bytes)
549 }
550}
551
552#[derive(Clone, Debug)]
568pub struct Label([u8; 40]);
569
570impl FromStr for Label {
571 type Err = Error;
572
573 fn from_str(s: &str) -> Result<Self, Self::Err> {
602 if s.len() > 40 {
603 return Err(Error::LabelLength { label: s.into() });
604 }
605 let mut buf = [0; 40];
606 buf[..s.len()].copy_from_slice(s.as_bytes());
607 Ok(Self(buf))
608 }
609}
610
611impl From<&[u8; 40]> for Label {
612 fn from(value: &[u8; 40]) -> Self {
626 let mut buf = [0; 40];
627 buf.copy_from_slice(value);
628 Self(buf)
629 }
630}
631
632impl AsRef<[u8; 40]> for Label {
633 fn as_ref(&self) -> &[u8; 40] {
647 &self.0
648 }
649}
650
651impl Display for Label {
652 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
674 let len = self.0.iter().position(|&b| b == 0).unwrap_or(self.0.len());
675 let label = String::from_utf8_lossy(&self.0[..len]);
676 write!(f, "{label}")
677 }
678}
679
680#[derive(Debug)]
682pub struct InnerFormat<'a> {
683 pub wrap_algorithm: WrapAlgorithm,
685
686 pub capabilities: Capabilities,
688
689 pub object_id: ObjectId,
691
692 pub domains: Domains,
694
695 pub object_type: ObjectType,
697
698 pub sequence: u8,
700
701 pub origin: u8,
703
704 pub label: Label,
706
707 pub key_data: WrappedPayload<'a>,
709}
710
711impl<'a> InnerFormat<'a> {
712 pub fn parse(raw: &'a [u8]) -> Result<Self, crate::Error> {
721 let mut reader = BeReader::new(raw);
722
723 let wrap_algorithm = WrapAlgorithm::from(reader.read_u8()?);
724 let capabilities = Capabilities::from(*reader.read::<8>()?);
725 let id = reader.read_u16()?;
726 let datalen = reader.read_u16()?;
727 let domains = reader.read_u16()?.into();
728 let object_id = ObjectId::try_from(Handle::new(
729 id,
730 Type::from_u8(reader.read_u8()?).map_err(Error::YubiHsmObject)?,
731 ))?;
732 let object_type = ObjectType::from(reader.read_u8()?);
733 let sequence = reader.read_u8()?;
734 let origin = reader.read_u8()?;
735
736 let label = reader.read::<40>()?.into();
737
738 if reader.position() + datalen as usize != raw.len() {
740 Err(Error::InsufficientDataInBuffer)?;
741 }
742
743 Ok(Self {
744 wrap_algorithm,
745 capabilities,
746 object_id,
747 domains,
748 object_type,
749 sequence,
750 origin,
751 label,
752 key_data: WrappedPayload::parse(object_type, reader.read_to_end()?)?,
753 })
754 }
755
756 pub fn serialize_into(&self, buffer: &mut Vec<u8>) {
758 buffer.push(self.wrap_algorithm.into());
759 buffer.extend_from_slice(&<[u8; 8]>::from(&self.capabilities));
760 buffer.extend_from_slice(&u16::from(self.object_id.id()).to_be_bytes());
761 buffer.extend_from_slice(&(self.key_data.len() as u16).to_be_bytes());
762 buffer.extend_from_slice(&self.domains.to_be_bytes());
763 buffer.push(self.object_id.object_type().to_u8());
764 buffer.push(self.object_type.into());
765 buffer.push(self.sequence);
766 buffer.push(self.origin);
767 buffer.extend_from_slice(self.label.as_ref());
768 self.key_data.serialize_into(buffer);
769 }
770}
771
772pub fn wrap_ed25519(
782 private_key_file: impl AsRef<Path>,
783 wrapping_key: impl AsRef<Path>,
784 object_id: Id,
785 domains: Domains,
786 capabilities: Capabilities,
787 label: Label,
788) -> Result<String, crate::Error> {
789 let wrapping_key = read(&wrapping_key).map_err(|source| crate::Error::IoPath {
790 path: wrapping_key.as_ref().into(),
791 context: "reading wrapping key file",
792 source,
793 })?;
794 let key = SerializedEd25519::from(&SigningKey::from_bytes(
795 &read(&private_key_file)
796 .map_err(|source| crate::Error::IoPath {
797 path: private_key_file.as_ref().into(),
798 context: "reading an ed25519 private key file",
799 source,
800 })?
801 .try_into()
802 .map_err(|_| crate::Error::IncorrectDataLength {
803 context: "reading an ed25519 key file",
804 })?,
805 ));
806 let inner = InnerFormat {
807 wrap_algorithm: WrapAlgorithm::Aes128Ccm,
808 capabilities,
809 object_id: ObjectId::AsymmetricKey(object_id),
810 domains,
811 object_type: ObjectType::Ed25519,
812 sequence: 0,
813 origin: 1,
814 label,
815 key_data: WrappedPayload::SeedEd25519(key.as_ref().try_into().map_err(|_| {
816 crate::Error::IncorrectDataLength {
817 context: "converting key formats",
818 }
819 })?),
820 };
821 let buffer = {
822 let mut buffer = vec![];
823 inner.serialize_into(&mut buffer);
824 buffer
825 };
826 let data_with_key = PlainWrappedDataWithKey {
827 data: &buffer,
828 key: &wrapping_key,
829 };
830 Ok(YubiHsm2Wrap::try_from(data_with_key)?.to_yhw())
831}
832
833#[cfg(test)]
834mod tests {
835
836 use std::fs::write;
837
838 use ed25519_dalek::VerifyingKey;
839 use tempfile::TempDir;
840 use testresult::TestResult;
841
842 use super::*;
843 use crate::object::{Capability, Domain};
844
845 const WRAP_KEY: &[u8] = &[
846 0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
847 ];
848
849 #[test]
850 fn decrypt_ed25519() -> TestResult {
851 let wrap = YubiHsm2Wrap::from_yhw(include_str!("../tests/backup/private-ed25519.yhw"))?;
852 let decrypted = wrap.decrypt(WRAP_KEY)?;
853 assert!(!decrypted.is_empty());
854 let inner = InnerFormat::parse(&decrypted)?;
855 let mut buffer = vec![];
856 inner.serialize_into(&mut buffer);
857 assert_eq!(buffer, decrypted);
858 assert_eq!(inner.object_type, ObjectType::Ed25519);
859 assert_eq!(inner.wrap_algorithm, WrapAlgorithm::Aes128Ccm);
860 assert_eq!(u16::from(inner.object_id.id()), 0x1f_u16);
861 assert_eq!(inner.domains, Domain::One.into());
862 assert_eq!(inner.sequence, 0);
863 assert_eq!(inner.origin, 2);
864 assert_eq!(inner.label.to_string(), "Ed25519_Key");
865 let WrappedPayload::ExpandedEd25519(key_data) = inner.key_data else {
866 panic!("Expected Ed25519 key data");
867 };
868 let ExpandedEd25519KeyData {
869 private_scalar,
870 private_hash_prefix,
871 public,
872 } = key_data;
873
874 assert_eq!(
875 private_scalar,
876 &[
877 117, 188, 78, 175, 249, 221, 207, 75, 177, 26, 92, 146, 43, 19, 156, 7, 87, 43,
878 173, 199, 232, 63, 249, 230, 100, 131, 86, 147, 80, 229, 193, 192
879 ]
880 );
881 assert_eq!(
882 private_hash_prefix,
883 &[
884 182, 113, 137, 6, 206, 62, 108, 30, 26, 138, 65, 215, 178, 10, 9, 215, 181, 55,
885 132, 37, 162, 172, 202, 169, 56, 150, 245, 195, 212, 232, 235, 183
886 ]
887 );
888 assert_eq!(
889 public,
890 &[
891 185, 235, 254, 46, 190, 171, 17, 45, 56, 27, 211, 240, 69, 46, 39, 226, 53, 109,
892 50, 78, 181, 96, 30, 177, 162, 240, 122, 187, 82, 30, 156, 242
893 ]
894 );
895 let signing_key: ExpandedSecretKey = key_data.into();
896 let verifying_key = VerifyingKey::from(&signing_key);
897 assert_eq!(public, &verifying_key.to_bytes());
898 Ok(())
899 }
900
901 #[test]
902 fn decrypt_ed25519_with_seed() -> TestResult {
903 let wrap =
904 YubiHsm2Wrap::from_yhw(include_str!("../tests/backup/private-ed25519-seed.yhw"))?;
905 let decrypted = wrap.decrypt(WRAP_KEY)?;
906 assert!(!decrypted.is_empty());
907 let inner = InnerFormat::parse(&decrypted)?;
908 let mut buffer = vec![];
909 inner.serialize_into(&mut buffer);
910 assert_eq!(buffer, decrypted);
911 assert_eq!(inner.object_type, ObjectType::Ed25519);
912 assert_eq!(inner.wrap_algorithm, WrapAlgorithm::Aes128Ccm);
913 assert_eq!(u16::from(inner.object_id.id()), 13);
914 assert_eq!(inner.domains, Domains::all());
915 assert_eq!(inner.sequence, 0);
916 assert_eq!(inner.origin, 1);
917 assert_eq!(inner.label.to_string(), "Signature_Key_Ed_2");
918 let WrappedPayload::SeedEd25519(key_data) = inner.key_data.clone() else {
919 panic!("Expected Ed25519 key data");
920 };
921
922 let SeedEd25519KeyData {
923 private_scalar,
924 private_hash_prefix,
925 public,
926 private_seed,
927 } = key_data;
928
929 assert_eq!(
930 private_seed,
931 &[
932 73, 122, 141, 156, 79, 125, 147, 201, 97, 207, 112, 15, 133, 155, 17, 216, 4, 254,
933 88, 71, 207, 139, 63, 170, 229, 246, 54, 32, 206, 12, 84, 86
934 ]
935 );
936 assert_eq!(
937 private_scalar,
938 &[
939 7, 81, 112, 122, 75, 85, 173, 6, 20, 181, 199, 29, 147, 191, 157, 102, 147, 157,
940 133, 249, 149, 223, 14, 41, 17, 51, 179, 38, 146, 102, 210, 15
941 ]
942 );
943 assert_eq!(
944 private_hash_prefix,
945 &[
946 161, 55, 166, 21, 136, 215, 184, 182, 181, 62, 143, 223, 62, 159, 19, 228, 179, 87,
947 101, 158, 129, 137, 207, 186, 191, 206, 220, 148, 44, 83, 203, 115
948 ]
949 );
950 assert_eq!(
951 public,
952 &[
953 252, 157, 136, 36, 18, 36, 60, 188, 181, 153, 78, 169, 136, 74, 14, 210, 150, 203,
954 47, 42, 79, 2, 238, 0, 103, 237, 202, 100, 87, 40, 252, 44
955 ]
956 );
957 let signing_key = SigningKey::from(&key_data);
958 let serialized = SerializedEd25519::from(&signing_key);
959 assert_eq!(
960 inner.key_data,
961 WrappedPayload::parse(ObjectType::Ed25519, serialized.as_ref())?
962 );
963
964 assert_eq!(public, &signing_key.verifying_key().to_bytes());
965 let exp = ExpandedSecretKey::from(private_seed);
966
967 let mut private_scalar = *private_scalar;
968 private_scalar.reverse();
969
970 assert_eq!(exp.scalar.as_bytes(), &private_scalar);
971 assert_eq!(&exp.hash_prefix, private_hash_prefix);
972
973 let signing_key: ExpandedSecretKey = key_data.into();
974 assert_eq!(exp.scalar, signing_key.scalar);
975 assert_eq!(exp.hash_prefix, signing_key.hash_prefix);
976
977 let verifying_key = VerifyingKey::from(&signing_key);
978 assert_eq!(public, &verifying_key.to_bytes());
979 Ok(())
980 }
981
982 #[test]
983 fn auth_key() -> TestResult {
984 let wrap = YubiHsm2Wrap::from_yhw(include_str!("../tests/backup/auth.yhw"))?;
985 let decrypted = wrap.decrypt(WRAP_KEY)?;
986 assert!(!decrypted.is_empty());
987 let inner = InnerFormat::parse(&decrypted)?;
988 let mut buffer = vec![];
989 inner.serialize_into(&mut buffer);
990 assert_eq!(decrypted, buffer);
991 assert_eq!(inner.object_type, ObjectType::Aes128Auth);
992 assert_eq!(
993 inner.capabilities,
994 Capabilities::from(&[Capability::ExportableUnderWrap][..])
995 );
996 assert_eq!(inner.domains, Domain::One.into());
997 assert_eq!(u16::from(inner.object_id.id()), 14);
998 assert_eq!(
999 inner.key_data,
1000 WrappedPayload::AuthAes128(AuthAes128 {
1001 delegated_capabilities: &[0; 8],
1002 symmetric_keys: &[
1003 152, 123, 73, 154, 181, 120, 84, 139, 48, 32, 41, 176, 213, 53, 39, 232, 122,
1004 150, 131, 153, 10, 233, 98, 202, 67, 12, 27, 245, 184, 198, 41, 93
1005 ]
1006 })
1007 );
1008 assert_eq!(inner.object_id.object_type(), Type::AuthenticationKey);
1009 assert_eq!(inner.label.to_string(), "");
1010 assert_eq!(inner.origin, 2);
1011 assert_eq!(inner.sequence, 0);
1012 Ok(())
1013 }
1014
1015 #[test]
1016 fn opaque_data() -> TestResult {
1017 let wrap = YubiHsm2Wrap::from_yhw(include_str!("../tests/backup/opaque.yhw"))?;
1018 let decrypted = wrap.decrypt(WRAP_KEY)?;
1019 assert!(!decrypted.is_empty());
1020 let inner = InnerFormat::parse(&decrypted)?;
1021 let mut buffer = vec![];
1022 inner.serialize_into(&mut buffer);
1023 assert_eq!(decrypted, buffer);
1024 assert_eq!(inner.object_type, ObjectType::Opaque);
1025 assert_eq!(
1026 inner.capabilities,
1027 Capabilities::from(&[Capability::ExportableUnderWrap][..])
1028 );
1029 assert_eq!(inner.domains, Domain::One.into());
1030 assert_eq!(u16::from(inner.object_id.id()), 13);
1031 assert_eq!(inner.key_data, WrappedPayload::Opaque(&[1, 2, 3]));
1032 assert_eq!(inner.object_id.object_type(), Type::Opaque);
1033 assert_eq!(inner.label.to_string(), "random");
1034 assert_eq!(inner.origin, 2);
1035 assert_eq!(inner.sequence, 0);
1036 Ok(())
1037 }
1038
1039 #[test]
1040 fn roundtrip_yhw() -> TestResult {
1041 let input = include_str!("../tests/backup/private-ed25519-seed.yhw");
1042 let wrap = YubiHsm2Wrap::from_yhw(input)?;
1043 assert_eq!(input, wrap.to_yhw());
1044 Ok(())
1045 }
1046
1047 #[test]
1048 fn encrypt_decrypt() -> TestResult {
1049 let input = include_str!("../tests/backup/opaque.yhw");
1050 let wrap = YubiHsm2Wrap::from_yhw(input)?;
1051 let decrypted_original = wrap.decrypt(WRAP_KEY)?;
1052 let plain = PlainWrappedDataWithKey {
1053 data: &decrypted_original,
1054 key: WRAP_KEY,
1055 };
1056 let wrap: YubiHsm2Wrap = plain.try_into()?;
1057 let decrypted_from_plain = wrap.decrypt(WRAP_KEY)?;
1058 assert_eq!(decrypted_original, decrypted_from_plain);
1059 Ok(())
1060 }
1061
1062 #[test]
1063 fn roundtrip_wrap() -> TestResult {
1064 let temp_dir = TempDir::new()?;
1065 let private_key_file = temp_dir.path().join("private.key");
1066 let wrapping_key_file = temp_dir.path().join("wrap.key");
1067 write(&private_key_file, [0; 32])?;
1068 write(&wrapping_key_file, WRAP_KEY)?;
1069
1070 let object_id = Id::new(1.try_into()?)?;
1071 let wrapped = wrap_ed25519(
1072 private_key_file,
1073 wrapping_key_file,
1074 object_id,
1075 Domains::all(),
1076 Capabilities::from(&[Capability::SignEddsa][..]),
1077 "test".parse()?,
1078 )?;
1079
1080 let yhw = YubiHsm2Wrap::from_yhw(&wrapped)?;
1081 let raw = yhw.decrypt(WRAP_KEY)?;
1082 let inner = InnerFormat::parse(&raw)?;
1083 assert_eq!(inner.object_id, ObjectId::AsymmetricKey(object_id));
1084 Ok(())
1085 }
1086}