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};
61#[cfg(feature = "serde")]
62use serde::{Deserialize, Serialize};
63use yubihsm::object::{Handle, Id, Label as YubiHsmObjectLabel, Type};
64
65use crate::object::{Capabilities, Domains, ObjectId};
66
67#[derive(Debug, thiserror::Error)]
69pub enum Error {
70 #[error("Decoding Base64 failed: {0}")]
72 Base64Decode(#[from] base64ct::Error),
73
74 #[error("Decryption error: {0}")]
76 Decrypt(#[from] ccm::Error),
77
78 #[error("Incorrect slice length: {0}")]
80 SliceLength(#[from] TryFromSliceError),
81
82 #[error("Unexpected Ed25519 serialized form length: {actual}")]
86 UnexpectedEd25519SerializedLength {
87 actual: usize,
89 },
90
91 #[error("Cannot parse data of unknown type: {0:?}")]
93 UnknownObjectType(ObjectType),
94
95 #[error("YubiHSM2 object error: {0:?}")]
97 YubiHsmObject(#[from] yubihsm::object::Error),
98
99 #[error("Parsing buffer: not enough data.")]
101 InsufficientDataInBuffer,
102
103 #[error(
105 "The string '{label}' could not be converted to a label as it exceeds the 40 bytes limit."
106 )]
107 LabelLength {
108 label: String,
110 },
111
112 #[error(
114 "The label '{label}' is invalid, because it contains the invalid character '{char:?}'."
115 )]
116 InvalidLabelCharacter {
117 label: String,
119
120 char: char,
122 },
123}
124
125pub struct PlainWrappedDataWithKey<'a, 'b> {
127 pub data: &'a [u8],
129
130 pub key: &'b [u8],
132}
133
134impl Debug for PlainWrappedDataWithKey<'_, '_> {
135 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136 f.debug_struct("PlainWrappedDataWithKey")
137 .field("data", &self.data)
138 .field("key", &"[REDACTED]")
139 .finish()
140 }
141}
142
143impl TryFrom<PlainWrappedDataWithKey<'_, '_>> for YubiHsm2Wrap {
144 type Error = Error;
145
146 fn try_from(value: PlainWrappedDataWithKey<'_, '_>) -> Result<Self, Self::Error> {
152 let cipher = Aes128Ccm::new(value.key.into());
153 let mut nonce = [0; 13];
154 let mut rng = aes::cipher::crypto_common::rand_core::OsRng;
155 rng.fill_bytes(&mut nonce);
156 let mut wrapped = cipher.encrypt(Nonce::from_slice(&nonce), value.data)?;
157 wrapped.splice(0..0, nonce);
158
159 Ok(Self { wrapped })
160 }
161}
162
163type Aes128Ccm = Ccm<Aes128, U16, U13>;
164
165#[derive(Debug)]
167pub struct YubiHsm2Wrap {
168 wrapped: Vec<u8>,
169}
170
171impl YubiHsm2Wrap {
172 pub fn new(wrapped: Vec<u8>) -> Self {
174 Self { wrapped }
175 }
176
177 pub fn from_yhw(wrapped: &str) -> Result<Self, Error> {
187 let wrapped = wrapped.trim_ascii();
188 let wrapped = Base64::decode_vec(wrapped)?;
189 Ok(Self { wrapped })
190 }
191
192 pub fn to_yhw(&self) -> String {
195 Base64::encode_string(&self.wrapped)
196 }
197
198 pub fn decrypt(&self, wrapping_key: &[u8]) -> Result<Vec<u8>, Error> {
204 let cipher = Aes128Ccm::new(wrapping_key.into());
205 let (nonce, ciphertext) = self.wrapped.split_at(U13::to_usize());
206 let plaintext = cipher.decrypt(nonce.into(), ciphertext)?;
207
208 Ok(plaintext)
209 }
210}
211
212impl AsRef<[u8]> for YubiHsm2Wrap {
213 fn as_ref(&self) -> &[u8] {
214 &self.wrapped
215 }
216}
217
218#[derive(Clone, Copy, Debug, Eq, FromPrimitive, IntoPrimitive, Ord, PartialEq, PartialOrd)]
222#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
223#[repr(u8)]
224pub enum WrapAlgorithm {
225 Aes128Ccm = 29,
227
228 Aes192Ccm = 41,
230
231 Aes256Ccm = 42,
233
234 #[num_enum(catch_all)]
236 Unknown(u8),
237}
238
239#[derive(Clone, Copy, Debug, Eq, FromPrimitive, IntoPrimitive, PartialEq)]
244#[repr(u8)]
245pub enum ObjectType {
246 Ed25519 = 46,
250
251 Aes128Auth = 38,
255
256 Opaque = 30,
260
261 #[num_enum(catch_all)]
263 Unknown(u8),
264}
265
266#[derive(Clone, Debug, Eq, PartialEq)]
268pub struct ExpandedEd25519KeyData<'a> {
269 pub private_scalar: &'a [u8; 32],
271
272 pub private_hash_prefix: &'a [u8; 32],
274
275 pub public: &'a [u8; 32],
277}
278
279impl ExpandedEd25519KeyData<'_> {
280 pub const LEN: usize = 32 * 3;
282}
283
284impl<'a> From<ExpandedEd25519KeyData<'a>> for ExpandedSecretKey {
285 fn from(value: ExpandedEd25519KeyData<'a>) -> Self {
286 let mut private_scalar = *value.private_scalar;
287 private_scalar.reverse();
288 ExpandedSecretKey {
289 scalar: Scalar::from_bytes_mod_order(private_scalar),
290 hash_prefix: *value.private_hash_prefix,
291 }
292 }
293}
294
295impl<'a> TryFrom<&'a [u8]> for ExpandedEd25519KeyData<'a> {
296 type Error = TryFromSliceError;
297 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
298 Ok(Self {
299 private_scalar: value[0..32].try_into()?,
300 private_hash_prefix: value[32..64].try_into()?,
301 public: value[64..].try_into()?,
302 })
303 }
304}
305
306#[derive(Clone, Debug, Eq, PartialEq)]
312pub struct SeedEd25519KeyData<'a> {
313 pub private_scalar: &'a [u8; 32],
315
316 pub private_hash_prefix: &'a [u8; 32],
318
319 pub public: &'a [u8; 32],
321
322 pub private_seed: &'a [u8; 32],
324}
325
326impl SeedEd25519KeyData<'_> {
327 pub const LEN: usize = 32 * 4;
329}
330
331impl<'a> TryFrom<&'a [u8]> for SeedEd25519KeyData<'a> {
332 type Error = TryFromSliceError;
333 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
334 Ok(Self {
335 private_seed: value[0..32].try_into()?,
336 private_scalar: value[32..64].try_into()?,
337 private_hash_prefix: value[64..96].try_into()?,
338 public: value[96..].try_into()?,
339 })
340 }
341}
342
343impl<'a> From<SeedEd25519KeyData<'a>> for ExpandedSecretKey {
344 fn from(value: SeedEd25519KeyData<'a>) -> Self {
345 let mut private_scalar = *value.private_scalar;
346 private_scalar.reverse();
347
348 ExpandedSecretKey {
350 scalar: Scalar::from_bytes_mod_order(private_scalar),
351 hash_prefix: *value.private_hash_prefix,
352 }
353 }
354}
355
356impl<'a> From<&'a SeedEd25519KeyData<'a>> for SigningKey {
357 fn from(value: &'a SeedEd25519KeyData<'a>) -> Self {
358 SigningKey::from(value.private_seed)
359 }
360}
361
362#[derive(Debug)]
371pub struct SerializedEd25519([u8; 32 * 4]);
372
373impl AsRef<[u8]> for SerializedEd25519 {
374 fn as_ref(&self) -> &[u8] {
375 &self.0
376 }
377}
378
379impl From<&SigningKey> for SerializedEd25519 {
380 fn from(value: &SigningKey) -> Self {
381 let mut result = [0; _];
382 let expanded = ExpandedSecretKey::from(&value.to_bytes());
383 result[0..32].copy_from_slice(value.as_bytes());
384 result[32..64].copy_from_slice(expanded.scalar.as_bytes());
385 result[32..64].reverse();
386 result[64..96].copy_from_slice(&expanded.hash_prefix);
387 result[96..].copy_from_slice(value.verifying_key().as_bytes());
388 Self(result)
389 }
390}
391
392#[derive(Clone, Debug, Eq, PartialEq)]
394pub struct AuthAes128<'a> {
395 pub delegated_capabilities: &'a [u8; 8],
397
398 pub symmetric_keys: &'a [u8; 32],
400}
401
402impl AuthAes128<'_> {
403 const LEN: usize = 8 + 32;
405}
406
407#[derive(Clone, Debug, Eq, PartialEq)]
413pub enum WrappedPayload<'a> {
414 ExpandedEd25519(ExpandedEd25519KeyData<'a>),
416
417 SeedEd25519(SeedEd25519KeyData<'a>),
419
420 AuthAes128(AuthAes128<'a>),
422
423 Opaque(&'a [u8]),
425}
426
427impl<'a> WrappedPayload<'a> {
428 fn parse(object_type: ObjectType, bytes: &'a [u8]) -> Result<WrappedPayload<'a>, Error> {
442 Ok(match object_type {
443 ObjectType::Ed25519 => match bytes.len() {
444 ExpandedEd25519KeyData::LEN => Self::ExpandedEd25519(bytes.try_into()?),
445 SeedEd25519KeyData::LEN => Self::SeedEd25519(bytes.try_into()?),
446 len => return Err(Error::UnexpectedEd25519SerializedLength { actual: len }),
447 },
448 ObjectType::Aes128Auth => {
449 let (delegated_capabilities, symmetric_keys) = bytes.split_at(8);
450 Self::AuthAes128(AuthAes128 {
451 delegated_capabilities: delegated_capabilities.try_into()?,
452 symmetric_keys: symmetric_keys.try_into()?,
453 })
454 }
455 ObjectType::Opaque => Self::Opaque(bytes),
456 object_type => return Err(Error::UnknownObjectType(object_type)),
457 })
458 }
459
460 fn serialize_into(&self, buffer: &mut Vec<u8>) {
462 match self {
463 WrappedPayload::ExpandedEd25519(key_data) => {
464 buffer.extend_from_slice(key_data.private_scalar);
465 buffer.extend_from_slice(key_data.private_hash_prefix);
466 buffer.extend_from_slice(key_data.public);
467 }
468 WrappedPayload::SeedEd25519(key_data) => {
469 buffer.extend_from_slice(key_data.private_seed);
470 buffer.extend_from_slice(key_data.private_scalar);
471 buffer.extend_from_slice(key_data.private_hash_prefix);
472 buffer.extend_from_slice(key_data.public);
473 }
474 WrappedPayload::AuthAes128(key_data) => {
475 buffer.extend_from_slice(key_data.delegated_capabilities);
476 buffer.extend_from_slice(key_data.symmetric_keys);
477 }
478 WrappedPayload::Opaque(key_data) => buffer.extend_from_slice(key_data),
479 }
480 }
481
482 fn len(&self) -> usize {
484 match self {
485 WrappedPayload::ExpandedEd25519(_) => ExpandedEd25519KeyData::LEN,
486 WrappedPayload::SeedEd25519(_) => SeedEd25519KeyData::LEN,
487 WrappedPayload::AuthAes128(_) => AuthAes128::LEN,
488 WrappedPayload::Opaque(key_data) => key_data.len(),
489 }
490 }
491}
492
493struct BeReader<'a> {
495 pos: usize,
496 buf: &'a [u8],
497}
498
499impl<'a> BeReader<'a> {
500 fn new(buf: &'a [u8]) -> Self {
502 Self { buf, pos: 0 }
503 }
504
505 fn position(&self) -> usize {
507 self.pos
508 }
509
510 fn read_u8(&mut self) -> Result<u8, Error> {
516 if self.pos + 1 >= self.buf.len() {
517 return Err(Error::InsufficientDataInBuffer);
518 }
519 let byte = self.buf[self.pos];
520 self.pos += 1;
521 Ok(byte)
522 }
523
524 fn read_u16(&mut self) -> Result<u16, Error> {
531 Ok(u16::from_be_bytes([self.read_u8()?, self.read_u8()?]))
532 }
533
534 fn read<const N: usize>(&mut self) -> Result<&'a [u8; N], Error> {
541 if self.pos + N >= self.buf.len() {
542 return Err(Error::InsufficientDataInBuffer);
543 }
544 let bytes = &self.buf[self.pos..self.pos + N];
545 self.pos += N;
546 bytes
547 .try_into()
548 .map_err(|_| Error::InsufficientDataInBuffer)
549 }
550
551 fn read_to_end(&mut self) -> Result<&'a [u8], Error> {
558 if self.pos > self.buf.len() {
559 return Err(Error::InsufficientDataInBuffer);
560 }
561 let bytes = &self.buf[self.pos..];
562 self.pos = self.buf.len() + 1;
563 Ok(bytes)
564 }
565}
566
567#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
583#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
584#[cfg_attr(feature = "serde", serde(try_from = "String", into = "String"))]
585pub struct Label([u8; 40]);
586
587impl FromStr for Label {
588 type Err = Error;
589
590 fn from_str(s: &str) -> Result<Self, Self::Err> {
619 if s.len() > 40 {
620 return Err(Error::LabelLength { label: s.into() });
621 }
622 if s.contains('\0') {
623 return Err(Error::InvalidLabelCharacter {
624 label: s.to_string(),
625 char: '\0',
626 });
627 }
628 let mut buf = [0; 40];
629 buf[..s.len()].copy_from_slice(s.as_bytes());
630 Ok(Self(buf))
631 }
632}
633
634impl From<&[u8; 40]> for Label {
635 fn from(value: &[u8; 40]) -> Self {
649 let mut buf = [0; 40];
650 buf.copy_from_slice(value);
651 Self(buf)
652 }
653}
654
655impl From<YubiHsmObjectLabel> for Label {
656 fn from(value: YubiHsmObjectLabel) -> Self {
657 Label::from(&value.0)
658 }
659}
660
661impl TryFrom<String> for Label {
663 type Error = Error;
664
665 fn try_from(value: String) -> Result<Self, Self::Error> {
666 Self::from_str(&value)
667 }
668}
669
670impl From<Label> for String {
672 fn from(value: Label) -> Self {
674 format!("{value}")
675 }
676}
677
678impl AsRef<[u8; 40]> for Label {
679 fn as_ref(&self) -> &[u8; 40] {
693 &self.0
694 }
695}
696
697impl Display for Label {
698 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
720 let len = self.0.iter().position(|&b| b == 0).unwrap_or(self.0.len());
721 let label = String::from_utf8_lossy(&self.0[..len]);
722 write!(f, "{label}")
723 }
724}
725
726impl From<&Label> for YubiHsmObjectLabel {
727 fn from(value: &Label) -> Self {
728 Self(*value.as_ref())
729 }
730}
731
732#[derive(Debug)]
734pub struct InnerFormat<'a> {
735 pub wrap_algorithm: WrapAlgorithm,
737
738 pub capabilities: Capabilities,
740
741 pub object_id: ObjectId,
743
744 pub domains: Domains,
746
747 pub object_type: ObjectType,
749
750 pub sequence: u8,
752
753 pub origin: u8,
755
756 pub label: Label,
758
759 pub key_data: WrappedPayload<'a>,
761}
762
763impl<'a> InnerFormat<'a> {
764 pub fn parse(raw: &'a [u8]) -> Result<Self, crate::Error> {
773 let mut reader = BeReader::new(raw);
774
775 let wrap_algorithm = WrapAlgorithm::from(reader.read_u8()?);
776 let capabilities = Capabilities::from(*reader.read::<8>()?);
777 let id = reader.read_u16()?;
778 let datalen = reader.read_u16()?;
779 let domains = reader.read_u16()?.into();
780 let object_id = ObjectId::from(Handle::new(
781 id,
782 Type::from_u8(reader.read_u8()?).map_err(Error::YubiHsmObject)?,
783 ));
784 let object_type = ObjectType::from(reader.read_u8()?);
785 let sequence = reader.read_u8()?;
786 let origin = reader.read_u8()?;
787
788 let label = reader.read::<40>()?.into();
789
790 if reader.position() + datalen as usize != raw.len() {
792 Err(Error::InsufficientDataInBuffer)?;
793 }
794
795 Ok(Self {
796 wrap_algorithm,
797 capabilities,
798 object_id,
799 domains,
800 object_type,
801 sequence,
802 origin,
803 label,
804 key_data: WrappedPayload::parse(object_type, reader.read_to_end()?)?,
805 })
806 }
807
808 pub fn serialize_into(&self, buffer: &mut Vec<u8>) {
810 buffer.push(self.wrap_algorithm.into());
811 buffer.extend_from_slice(&<[u8; 8]>::from(&self.capabilities));
812 buffer.extend_from_slice(&self.object_id.id().to_be_bytes());
813 buffer.extend_from_slice(&(self.key_data.len() as u16).to_be_bytes());
814 buffer.extend_from_slice(&self.domains.to_be_bytes());
815 buffer.push(self.object_id.object_type().to_u8());
816 buffer.push(self.object_type.into());
817 buffer.push(self.sequence);
818 buffer.push(self.origin);
819 buffer.extend_from_slice(self.label.as_ref());
820 self.key_data.serialize_into(buffer);
821 }
822}
823
824pub fn wrap_ed25519(
834 private_key_file: impl AsRef<Path>,
835 wrapping_key: impl AsRef<Path>,
836 object_id: Id,
837 domains: Domains,
838 capabilities: Capabilities,
839 label: Label,
840) -> Result<String, crate::Error> {
841 let wrapping_key = read(&wrapping_key).map_err(|source| crate::Error::IoPath {
842 path: wrapping_key.as_ref().into(),
843 context: "reading wrapping key file",
844 source,
845 })?;
846 let key = SerializedEd25519::from(&SigningKey::from_bytes(
847 &read(&private_key_file)
848 .map_err(|source| crate::Error::IoPath {
849 path: private_key_file.as_ref().into(),
850 context: "reading an ed25519 private key file",
851 source,
852 })?
853 .try_into()
854 .map_err(|_| crate::Error::IncorrectDataLength {
855 context: "reading an ed25519 key file",
856 })?,
857 ));
858 let inner = InnerFormat {
859 wrap_algorithm: WrapAlgorithm::Aes128Ccm,
860 capabilities,
861 object_id: ObjectId::AsymmetricKey(object_id),
862 domains,
863 object_type: ObjectType::Ed25519,
864 sequence: 0,
865 origin: 1,
866 label,
867 key_data: WrappedPayload::SeedEd25519(key.as_ref().try_into().map_err(|_| {
868 crate::Error::IncorrectDataLength {
869 context: "converting key formats",
870 }
871 })?),
872 };
873 let buffer = {
874 let mut buffer = vec![];
875 inner.serialize_into(&mut buffer);
876 buffer
877 };
878 let data_with_key = PlainWrappedDataWithKey {
879 data: &buffer,
880 key: &wrapping_key,
881 };
882 Ok(YubiHsm2Wrap::try_from(data_with_key)?.to_yhw())
883}
884
885#[cfg(test)]
886mod tests {
887
888 use std::{assert_matches, fs::write};
889
890 use ed25519_dalek::VerifyingKey;
891 use tempfile::TempDir;
892 use testresult::TestResult;
893
894 use super::*;
895 use crate::object::{Capability, Domain};
896
897 const WRAP_KEY: &[u8] = &[
898 0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
899 ];
900
901 #[test]
902 fn decrypt_ed25519() -> TestResult {
903 let wrap = YubiHsm2Wrap::from_yhw(include_str!("../tests/backup/private-ed25519.yhw"))?;
904 let decrypted = wrap.decrypt(WRAP_KEY)?;
905 assert!(!decrypted.is_empty());
906 let inner = InnerFormat::parse(&decrypted)?;
907 let mut buffer = vec![];
908 inner.serialize_into(&mut buffer);
909 assert_eq!(buffer, decrypted);
910 assert_eq!(inner.object_type, ObjectType::Ed25519);
911 assert_eq!(inner.wrap_algorithm, WrapAlgorithm::Aes128Ccm);
912 assert_eq!(inner.object_id.id(), 0x1f_u16);
913 assert_eq!(inner.domains, Domain::One.into());
914 assert_eq!(inner.sequence, 0);
915 assert_eq!(inner.origin, 2);
916 assert_eq!(inner.label.to_string(), "Ed25519_Key");
917 let WrappedPayload::ExpandedEd25519(key_data) = inner.key_data else {
918 panic!("Expected Ed25519 key data");
919 };
920 let ExpandedEd25519KeyData {
921 private_scalar,
922 private_hash_prefix,
923 public,
924 } = key_data;
925
926 assert_eq!(
927 private_scalar,
928 &[
929 117, 188, 78, 175, 249, 221, 207, 75, 177, 26, 92, 146, 43, 19, 156, 7, 87, 43,
930 173, 199, 232, 63, 249, 230, 100, 131, 86, 147, 80, 229, 193, 192
931 ]
932 );
933 assert_eq!(
934 private_hash_prefix,
935 &[
936 182, 113, 137, 6, 206, 62, 108, 30, 26, 138, 65, 215, 178, 10, 9, 215, 181, 55,
937 132, 37, 162, 172, 202, 169, 56, 150, 245, 195, 212, 232, 235, 183
938 ]
939 );
940 assert_eq!(
941 public,
942 &[
943 185, 235, 254, 46, 190, 171, 17, 45, 56, 27, 211, 240, 69, 46, 39, 226, 53, 109,
944 50, 78, 181, 96, 30, 177, 162, 240, 122, 187, 82, 30, 156, 242
945 ]
946 );
947 let signing_key: ExpandedSecretKey = key_data.into();
948 let verifying_key = VerifyingKey::from(&signing_key);
949 assert_eq!(public, &verifying_key.to_bytes());
950 Ok(())
951 }
952
953 #[test]
954 fn decrypt_ed25519_with_seed() -> TestResult {
955 let wrap =
956 YubiHsm2Wrap::from_yhw(include_str!("../tests/backup/private-ed25519-seed.yhw"))?;
957 let decrypted = wrap.decrypt(WRAP_KEY)?;
958 assert!(!decrypted.is_empty());
959 let inner = InnerFormat::parse(&decrypted)?;
960 let mut buffer = vec![];
961 inner.serialize_into(&mut buffer);
962 assert_eq!(buffer, decrypted);
963 assert_eq!(inner.object_type, ObjectType::Ed25519);
964 assert_eq!(inner.wrap_algorithm, WrapAlgorithm::Aes128Ccm);
965 assert_eq!(inner.object_id.id(), 13);
966 assert_eq!(inner.domains, Domains::all());
967 assert_eq!(inner.sequence, 0);
968 assert_eq!(inner.origin, 1);
969 assert_eq!(inner.label.to_string(), "Signature_Key_Ed_2");
970 let WrappedPayload::SeedEd25519(key_data) = inner.key_data.clone() else {
971 panic!("Expected Ed25519 key data");
972 };
973
974 let SeedEd25519KeyData {
975 private_scalar,
976 private_hash_prefix,
977 public,
978 private_seed,
979 } = key_data;
980
981 assert_eq!(
982 private_seed,
983 &[
984 73, 122, 141, 156, 79, 125, 147, 201, 97, 207, 112, 15, 133, 155, 17, 216, 4, 254,
985 88, 71, 207, 139, 63, 170, 229, 246, 54, 32, 206, 12, 84, 86
986 ]
987 );
988 assert_eq!(
989 private_scalar,
990 &[
991 7, 81, 112, 122, 75, 85, 173, 6, 20, 181, 199, 29, 147, 191, 157, 102, 147, 157,
992 133, 249, 149, 223, 14, 41, 17, 51, 179, 38, 146, 102, 210, 15
993 ]
994 );
995 assert_eq!(
996 private_hash_prefix,
997 &[
998 161, 55, 166, 21, 136, 215, 184, 182, 181, 62, 143, 223, 62, 159, 19, 228, 179, 87,
999 101, 158, 129, 137, 207, 186, 191, 206, 220, 148, 44, 83, 203, 115
1000 ]
1001 );
1002 assert_eq!(
1003 public,
1004 &[
1005 252, 157, 136, 36, 18, 36, 60, 188, 181, 153, 78, 169, 136, 74, 14, 210, 150, 203,
1006 47, 42, 79, 2, 238, 0, 103, 237, 202, 100, 87, 40, 252, 44
1007 ]
1008 );
1009 let signing_key = SigningKey::from(&key_data);
1010 let serialized = SerializedEd25519::from(&signing_key);
1011 assert_eq!(
1012 inner.key_data,
1013 WrappedPayload::parse(ObjectType::Ed25519, serialized.as_ref())?
1014 );
1015
1016 assert_eq!(public, &signing_key.verifying_key().to_bytes());
1017 let exp = ExpandedSecretKey::from(private_seed);
1018
1019 let mut private_scalar = *private_scalar;
1020 private_scalar.reverse();
1021
1022 assert_eq!(exp.scalar.as_bytes(), &private_scalar);
1023 assert_eq!(&exp.hash_prefix, private_hash_prefix);
1024
1025 let signing_key: ExpandedSecretKey = key_data.into();
1026 assert_eq!(exp.scalar, signing_key.scalar);
1027 assert_eq!(exp.hash_prefix, signing_key.hash_prefix);
1028
1029 let verifying_key = VerifyingKey::from(&signing_key);
1030 assert_eq!(public, &verifying_key.to_bytes());
1031 Ok(())
1032 }
1033
1034 #[test]
1035 fn auth_key() -> TestResult {
1036 let wrap = YubiHsm2Wrap::from_yhw(include_str!("../tests/backup/auth.yhw"))?;
1037 let decrypted = wrap.decrypt(WRAP_KEY)?;
1038 assert!(!decrypted.is_empty());
1039 let inner = InnerFormat::parse(&decrypted)?;
1040 let mut buffer = vec![];
1041 inner.serialize_into(&mut buffer);
1042 assert_eq!(decrypted, buffer);
1043 assert_eq!(inner.object_type, ObjectType::Aes128Auth);
1044 assert_eq!(
1045 inner.capabilities,
1046 Capabilities::from(&[Capability::ExportableUnderWrap][..])
1047 );
1048 assert_eq!(inner.domains, Domain::One.into());
1049 assert_eq!(inner.object_id.id(), 14);
1050 assert_eq!(
1051 inner.key_data,
1052 WrappedPayload::AuthAes128(AuthAes128 {
1053 delegated_capabilities: &[0; 8],
1054 symmetric_keys: &[
1055 152, 123, 73, 154, 181, 120, 84, 139, 48, 32, 41, 176, 213, 53, 39, 232, 122,
1056 150, 131, 153, 10, 233, 98, 202, 67, 12, 27, 245, 184, 198, 41, 93
1057 ]
1058 })
1059 );
1060 assert_eq!(inner.object_id.object_type(), Type::AuthenticationKey);
1061 assert_eq!(inner.label.to_string(), "");
1062 assert_eq!(inner.origin, 2);
1063 assert_eq!(inner.sequence, 0);
1064 Ok(())
1065 }
1066
1067 #[test]
1068 fn opaque_data() -> TestResult {
1069 let wrap = YubiHsm2Wrap::from_yhw(include_str!("../tests/backup/opaque.yhw"))?;
1070 let decrypted = wrap.decrypt(WRAP_KEY)?;
1071 assert!(!decrypted.is_empty());
1072 let inner = InnerFormat::parse(&decrypted)?;
1073 let mut buffer = vec![];
1074 inner.serialize_into(&mut buffer);
1075 assert_eq!(decrypted, buffer);
1076 assert_eq!(inner.object_type, ObjectType::Opaque);
1077 assert_eq!(
1078 inner.capabilities,
1079 Capabilities::from(&[Capability::ExportableUnderWrap][..])
1080 );
1081 assert_eq!(inner.domains, Domain::One.into());
1082 assert_eq!(inner.object_id.id(), 13);
1083 assert_eq!(inner.key_data, WrappedPayload::Opaque(&[1, 2, 3]));
1084 assert_eq!(inner.object_id.object_type(), Type::Opaque);
1085 assert_eq!(inner.label.to_string(), "random");
1086 assert_eq!(inner.origin, 2);
1087 assert_eq!(inner.sequence, 0);
1088 Ok(())
1089 }
1090
1091 #[test]
1092 fn roundtrip_yhw() -> TestResult {
1093 let input = include_str!("../tests/backup/private-ed25519-seed.yhw");
1094 let wrap = YubiHsm2Wrap::from_yhw(input)?;
1095 assert_eq!(input, wrap.to_yhw());
1096 Ok(())
1097 }
1098
1099 #[test]
1100 fn encrypt_decrypt() -> TestResult {
1101 let input = include_str!("../tests/backup/opaque.yhw");
1102 let wrap = YubiHsm2Wrap::from_yhw(input)?;
1103 let decrypted_original = wrap.decrypt(WRAP_KEY)?;
1104 let plain = PlainWrappedDataWithKey {
1105 data: &decrypted_original,
1106 key: WRAP_KEY,
1107 };
1108 let wrap: YubiHsm2Wrap = plain.try_into()?;
1109 let decrypted_from_plain = wrap.decrypt(WRAP_KEY)?;
1110 assert_eq!(decrypted_original, decrypted_from_plain);
1111 Ok(())
1112 }
1113
1114 #[test]
1115 fn roundtrip_wrap() -> TestResult {
1116 let temp_dir = TempDir::new()?;
1117 let private_key_file = temp_dir.path().join("private.key");
1118 let wrapping_key_file = temp_dir.path().join("wrap.key");
1119 write(&private_key_file, [0; 32])?;
1120 write(&wrapping_key_file, WRAP_KEY)?;
1121
1122 let object_id = 1;
1123 let wrapped = wrap_ed25519(
1124 private_key_file,
1125 wrapping_key_file,
1126 object_id,
1127 Domains::all(),
1128 Capabilities::from(&[Capability::SignEddsa][..]),
1129 "test".parse()?,
1130 )?;
1131
1132 let yhw = YubiHsm2Wrap::from_yhw(&wrapped)?;
1133 let raw = yhw.decrypt(WRAP_KEY)?;
1134 let inner = InnerFormat::parse(&raw)?;
1135 assert_eq!(inner.object_id, ObjectId::AsymmetricKey(object_id));
1136 Ok(())
1137 }
1138
1139 #[test]
1142 fn label_from_str_fails_on_invalid_char() -> TestResult {
1143 let text = "some label\0text";
1144 assert_matches!(Label::from_str(text), Err(Error::InvalidLabelCharacter { char, .. }) if char == '\0' );
1145
1146 Ok(())
1147 }
1148
1149 #[test]
1151 fn string_from_label() -> TestResult {
1152 let text = "this is a label";
1153 let label = Label::from_str(text)?;
1154 let string_label: String = label.into();
1155
1156 assert_eq!(string_label, text);
1157 Ok(())
1158 }
1159}