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 as _};
51use base64ct::{Base64, Encoding as _};
52use ccm::{
53 Ccm,
54 Nonce,
55 aead::{Aead, Generate, KeyInit},
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.try_into()?);
153 let nonce = Nonce::<U13>::generate();
154 let mut wrapped = cipher.encrypt(&nonce, value.data)?;
155 wrapped.splice(0..0, nonce);
156
157 Ok(Self { wrapped })
158 }
159}
160
161type Aes128Ccm = Ccm<Aes128, U16, U13>;
162
163#[derive(Debug)]
165pub struct YubiHsm2Wrap {
166 wrapped: Vec<u8>,
167}
168
169impl YubiHsm2Wrap {
170 pub fn new(wrapped: Vec<u8>) -> Self {
172 Self { wrapped }
173 }
174
175 pub fn from_yhw(wrapped: &str) -> Result<Self, Error> {
185 let wrapped = wrapped.trim_ascii();
186 let wrapped = Base64::decode_vec(wrapped)?;
187 Ok(Self { wrapped })
188 }
189
190 pub fn to_yhw(&self) -> String {
193 Base64::encode_string(&self.wrapped)
194 }
195
196 pub fn decrypt(&self, wrapping_key: &[u8]) -> Result<Vec<u8>, Error> {
202 let cipher = Aes128Ccm::new(wrapping_key.try_into()?);
203 let (nonce, ciphertext) = self.wrapped.split_at(U13::to_usize());
204 let plaintext = cipher.decrypt(nonce.try_into()?, ciphertext)?;
205
206 Ok(plaintext)
207 }
208}
209
210impl AsRef<[u8]> for YubiHsm2Wrap {
211 fn as_ref(&self) -> &[u8] {
212 &self.wrapped
213 }
214}
215
216#[derive(Clone, Copy, Debug, Eq, FromPrimitive, IntoPrimitive, Ord, PartialEq, PartialOrd)]
220#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
221#[repr(u8)]
222pub enum WrapAlgorithm {
223 Aes128Ccm = 29,
225
226 Aes192Ccm = 41,
228
229 Aes256Ccm = 42,
231
232 #[num_enum(catch_all)]
234 Unknown(u8),
235}
236
237#[derive(Clone, Copy, Debug, Eq, FromPrimitive, IntoPrimitive, PartialEq)]
242#[repr(u8)]
243pub enum ObjectType {
244 Ed25519 = 46,
248
249 Aes128Auth = 38,
253
254 Opaque = 30,
258
259 #[num_enum(catch_all)]
261 Unknown(u8),
262}
263
264#[derive(Clone, Debug, Eq, PartialEq)]
266pub struct ExpandedEd25519KeyData<'a> {
267 pub private_scalar: &'a [u8; 32],
269
270 pub private_hash_prefix: &'a [u8; 32],
272
273 pub public: &'a [u8; 32],
275}
276
277impl ExpandedEd25519KeyData<'_> {
278 pub const LEN: usize = 32 * 3;
280}
281
282impl<'a> From<ExpandedEd25519KeyData<'a>> for ExpandedSecretKey {
283 fn from(value: ExpandedEd25519KeyData<'a>) -> Self {
284 let mut private_scalar = *value.private_scalar;
285 private_scalar.reverse();
286 ExpandedSecretKey {
287 scalar: Scalar::from_bytes_mod_order(private_scalar),
288 hash_prefix: *value.private_hash_prefix,
289 }
290 }
291}
292
293impl<'a> TryFrom<&'a [u8]> for ExpandedEd25519KeyData<'a> {
294 type Error = TryFromSliceError;
295 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
296 Ok(Self {
297 private_scalar: value[0..32].try_into()?,
298 private_hash_prefix: value[32..64].try_into()?,
299 public: value[64..].try_into()?,
300 })
301 }
302}
303
304#[derive(Clone, Debug, Eq, PartialEq)]
310pub struct SeedEd25519KeyData<'a> {
311 pub private_scalar: &'a [u8; 32],
313
314 pub private_hash_prefix: &'a [u8; 32],
316
317 pub public: &'a [u8; 32],
319
320 pub private_seed: &'a [u8; 32],
322}
323
324impl SeedEd25519KeyData<'_> {
325 pub const LEN: usize = 32 * 4;
327}
328
329impl<'a> TryFrom<&'a [u8]> for SeedEd25519KeyData<'a> {
330 type Error = TryFromSliceError;
331 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
332 Ok(Self {
333 private_seed: value[0..32].try_into()?,
334 private_scalar: value[32..64].try_into()?,
335 private_hash_prefix: value[64..96].try_into()?,
336 public: value[96..].try_into()?,
337 })
338 }
339}
340
341impl<'a> From<SeedEd25519KeyData<'a>> for ExpandedSecretKey {
342 fn from(value: SeedEd25519KeyData<'a>) -> Self {
343 let mut private_scalar = *value.private_scalar;
344 private_scalar.reverse();
345
346 ExpandedSecretKey {
348 scalar: Scalar::from_bytes_mod_order(private_scalar),
349 hash_prefix: *value.private_hash_prefix,
350 }
351 }
352}
353
354impl<'a> From<&'a SeedEd25519KeyData<'a>> for SigningKey {
355 fn from(value: &'a SeedEd25519KeyData<'a>) -> Self {
356 SigningKey::from(value.private_seed)
357 }
358}
359
360#[derive(Debug)]
369pub struct SerializedEd25519([u8; 32 * 4]);
370
371impl AsRef<[u8]> for SerializedEd25519 {
372 fn as_ref(&self) -> &[u8] {
373 &self.0
374 }
375}
376
377impl From<&SigningKey> for SerializedEd25519 {
378 fn from(value: &SigningKey) -> Self {
379 let mut result = [0; _];
380 let expanded = ExpandedSecretKey::from(&value.to_bytes());
381 result[0..32].copy_from_slice(value.as_bytes());
382 result[32..64].copy_from_slice(expanded.scalar.as_bytes());
383 result[32..64].reverse();
384 result[64..96].copy_from_slice(&expanded.hash_prefix);
385 result[96..].copy_from_slice(value.verifying_key().as_bytes());
386 Self(result)
387 }
388}
389
390#[derive(Clone, Debug, Eq, PartialEq)]
392pub struct AuthAes128<'a> {
393 pub delegated_capabilities: &'a [u8; 8],
395
396 pub symmetric_keys: &'a [u8; 32],
398}
399
400impl AuthAes128<'_> {
401 const LEN: usize = 8 + 32;
403}
404
405#[derive(Clone, Debug, Eq, PartialEq)]
411pub enum WrappedPayload<'a> {
412 ExpandedEd25519(ExpandedEd25519KeyData<'a>),
414
415 SeedEd25519(SeedEd25519KeyData<'a>),
417
418 AuthAes128(AuthAes128<'a>),
420
421 Opaque(&'a [u8]),
423}
424
425impl<'a> WrappedPayload<'a> {
426 fn parse(object_type: ObjectType, bytes: &'a [u8]) -> Result<WrappedPayload<'a>, Error> {
440 Ok(match object_type {
441 ObjectType::Ed25519 => match bytes.len() {
442 ExpandedEd25519KeyData::LEN => Self::ExpandedEd25519(bytes.try_into()?),
443 SeedEd25519KeyData::LEN => Self::SeedEd25519(bytes.try_into()?),
444 len => return Err(Error::UnexpectedEd25519SerializedLength { actual: len }),
445 },
446 ObjectType::Aes128Auth => {
447 let (delegated_capabilities, symmetric_keys) = bytes.split_at(8);
448 Self::AuthAes128(AuthAes128 {
449 delegated_capabilities: delegated_capabilities.try_into()?,
450 symmetric_keys: symmetric_keys.try_into()?,
451 })
452 }
453 ObjectType::Opaque => Self::Opaque(bytes),
454 object_type => return Err(Error::UnknownObjectType(object_type)),
455 })
456 }
457
458 fn serialize_into(&self, buffer: &mut Vec<u8>) {
460 match self {
461 WrappedPayload::ExpandedEd25519(key_data) => {
462 buffer.extend_from_slice(key_data.private_scalar);
463 buffer.extend_from_slice(key_data.private_hash_prefix);
464 buffer.extend_from_slice(key_data.public);
465 }
466 WrappedPayload::SeedEd25519(key_data) => {
467 buffer.extend_from_slice(key_data.private_seed);
468 buffer.extend_from_slice(key_data.private_scalar);
469 buffer.extend_from_slice(key_data.private_hash_prefix);
470 buffer.extend_from_slice(key_data.public);
471 }
472 WrappedPayload::AuthAes128(key_data) => {
473 buffer.extend_from_slice(key_data.delegated_capabilities);
474 buffer.extend_from_slice(key_data.symmetric_keys);
475 }
476 WrappedPayload::Opaque(key_data) => buffer.extend_from_slice(key_data),
477 }
478 }
479
480 fn len(&self) -> usize {
482 match self {
483 WrappedPayload::ExpandedEd25519(_) => ExpandedEd25519KeyData::LEN,
484 WrappedPayload::SeedEd25519(_) => SeedEd25519KeyData::LEN,
485 WrappedPayload::AuthAes128(_) => AuthAes128::LEN,
486 WrappedPayload::Opaque(key_data) => key_data.len(),
487 }
488 }
489}
490
491struct BeReader<'a> {
493 pos: usize,
494 buf: &'a [u8],
495}
496
497impl<'a> BeReader<'a> {
498 fn new(buf: &'a [u8]) -> Self {
500 Self { buf, pos: 0 }
501 }
502
503 fn position(&self) -> usize {
505 self.pos
506 }
507
508 fn read_u8(&mut self) -> Result<u8, Error> {
514 if self.pos + 1 >= self.buf.len() {
515 return Err(Error::InsufficientDataInBuffer);
516 }
517 let byte = self.buf[self.pos];
518 self.pos += 1;
519 Ok(byte)
520 }
521
522 fn read_u16(&mut self) -> Result<u16, Error> {
529 Ok(u16::from_be_bytes([self.read_u8()?, self.read_u8()?]))
530 }
531
532 fn read<const N: usize>(&mut self) -> Result<&'a [u8; N], Error> {
539 if self.pos + N >= self.buf.len() {
540 return Err(Error::InsufficientDataInBuffer);
541 }
542 let bytes = &self.buf[self.pos..self.pos + N];
543 self.pos += N;
544 bytes
545 .try_into()
546 .map_err(|_| Error::InsufficientDataInBuffer)
547 }
548
549 fn read_to_end(&mut self) -> Result<&'a [u8], Error> {
556 if self.pos > self.buf.len() {
557 return Err(Error::InsufficientDataInBuffer);
558 }
559 let bytes = &self.buf[self.pos..];
560 self.pos = self.buf.len() + 1;
561 Ok(bytes)
562 }
563}
564
565#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
581#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
582#[cfg_attr(feature = "serde", serde(try_from = "String", into = "String"))]
583pub struct Label([u8; 40]);
584
585impl Label {
586 pub fn from_truncated_str(s: &str) -> Self {
593 let buffer = {
594 let label = {
595 let mut label = s.as_bytes().to_vec();
596 label.resize(40, 0);
598 label
599 };
600
601 let mut buffer = [0u8; 40];
602 buffer.copy_from_slice(&label);
604 buffer
605 };
606
607 Label::from(&buffer)
608 }
609}
610
611impl FromStr for Label {
612 type Err = Error;
613
614 fn from_str(s: &str) -> Result<Self, Self::Err> {
643 if s.len() > 40 {
644 return Err(Error::LabelLength { label: s.into() });
645 }
646 if s.contains('\0') {
647 return Err(Error::InvalidLabelCharacter {
648 label: s.to_string(),
649 char: '\0',
650 });
651 }
652 let mut buf = [0; 40];
653 buf[..s.len()].copy_from_slice(s.as_bytes());
654 Ok(Self(buf))
655 }
656}
657
658impl From<&[u8; 40]> for Label {
659 fn from(value: &[u8; 40]) -> Self {
673 let mut buf = [0; 40];
674 buf.copy_from_slice(value);
675 Self(buf)
676 }
677}
678
679impl From<YubiHsmObjectLabel> for Label {
680 fn from(value: YubiHsmObjectLabel) -> Self {
681 Label::from(&value.0)
682 }
683}
684
685impl TryFrom<String> for Label {
687 type Error = Error;
688
689 fn try_from(value: String) -> Result<Self, Self::Error> {
690 Self::from_str(&value)
691 }
692}
693
694impl From<Label> for String {
696 fn from(value: Label) -> Self {
698 format!("{value}")
699 }
700}
701
702impl AsRef<[u8; 40]> for Label {
703 fn as_ref(&self) -> &[u8; 40] {
717 &self.0
718 }
719}
720
721impl Display for Label {
722 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
744 let len = self.0.iter().position(|&b| b == 0).unwrap_or(self.0.len());
745 let label = String::from_utf8_lossy(&self.0[..len]);
746 write!(f, "{label}")
747 }
748}
749
750impl From<&Label> for YubiHsmObjectLabel {
751 fn from(value: &Label) -> Self {
752 Self(*value.as_ref())
753 }
754}
755
756#[derive(Debug)]
758pub struct InnerFormat<'a> {
759 pub wrap_algorithm: WrapAlgorithm,
761
762 pub capabilities: Capabilities,
764
765 pub object_id: ObjectId,
767
768 pub domains: Domains,
770
771 pub object_type: ObjectType,
773
774 pub sequence: u8,
776
777 pub origin: u8,
779
780 pub label: Label,
782
783 pub key_data: WrappedPayload<'a>,
785}
786
787impl<'a> InnerFormat<'a> {
788 pub fn parse(raw: &'a [u8]) -> Result<Self, crate::Error> {
797 let mut reader = BeReader::new(raw);
798
799 let wrap_algorithm = WrapAlgorithm::from(reader.read_u8()?);
800 let capabilities = Capabilities::from(*reader.read::<8>()?);
801 let id = reader.read_u16()?;
802 let datalen = reader.read_u16()?;
803 let domains = reader.read_u16()?.into();
804 let object_id = ObjectId::from(Handle::new(
805 id,
806 Type::from_u8(reader.read_u8()?).map_err(Error::YubiHsmObject)?,
807 ));
808 let object_type = ObjectType::from(reader.read_u8()?);
809 let sequence = reader.read_u8()?;
810 let origin = reader.read_u8()?;
811
812 let label = reader.read::<40>()?.into();
813
814 if reader.position() + datalen as usize != raw.len() {
816 Err(Error::InsufficientDataInBuffer)?;
817 }
818
819 Ok(Self {
820 wrap_algorithm,
821 capabilities,
822 object_id,
823 domains,
824 object_type,
825 sequence,
826 origin,
827 label,
828 key_data: WrappedPayload::parse(object_type, reader.read_to_end()?)?,
829 })
830 }
831
832 pub fn serialize_into(&self, buffer: &mut Vec<u8>) {
834 buffer.push(self.wrap_algorithm.into());
835 buffer.extend_from_slice(&<[u8; 8]>::from(&self.capabilities));
836 buffer.extend_from_slice(&self.object_id.id().to_be_bytes());
837 buffer.extend_from_slice(&(self.key_data.len() as u16).to_be_bytes());
838 buffer.extend_from_slice(&self.domains.to_be_bytes());
839 buffer.push(self.object_id.object_type().to_u8());
840 buffer.push(self.object_type.into());
841 buffer.push(self.sequence);
842 buffer.push(self.origin);
843 buffer.extend_from_slice(self.label.as_ref());
844 self.key_data.serialize_into(buffer);
845 }
846}
847
848pub fn wrap_ed25519(
858 private_key_file: impl AsRef<Path>,
859 wrapping_key: impl AsRef<Path>,
860 object_id: Id,
861 domains: Domains,
862 capabilities: Capabilities,
863 label: Label,
864) -> Result<String, crate::Error> {
865 let wrapping_key = read(&wrapping_key).map_err(|source| crate::Error::IoPath {
866 path: wrapping_key.as_ref().into(),
867 context: "reading wrapping key file",
868 source,
869 })?;
870 let key = SerializedEd25519::from(&SigningKey::from_bytes(
871 &read(&private_key_file)
872 .map_err(|source| crate::Error::IoPath {
873 path: private_key_file.as_ref().into(),
874 context: "reading an ed25519 private key file",
875 source,
876 })?
877 .try_into()
878 .map_err(|_| crate::Error::IncorrectDataLength {
879 context: "reading an ed25519 key file",
880 })?,
881 ));
882 let inner = InnerFormat {
883 wrap_algorithm: WrapAlgorithm::Aes128Ccm,
884 capabilities,
885 object_id: ObjectId::AsymmetricKey(object_id),
886 domains,
887 object_type: ObjectType::Ed25519,
888 sequence: 0,
889 origin: 1,
890 label,
891 key_data: WrappedPayload::SeedEd25519(key.as_ref().try_into().map_err(|_| {
892 crate::Error::IncorrectDataLength {
893 context: "converting key formats",
894 }
895 })?),
896 };
897 let buffer = {
898 let mut buffer = vec![];
899 inner.serialize_into(&mut buffer);
900 buffer
901 };
902 let data_with_key = PlainWrappedDataWithKey {
903 data: &buffer,
904 key: &wrapping_key,
905 };
906 Ok(YubiHsm2Wrap::try_from(data_with_key)?.to_yhw())
907}
908
909#[cfg(test)]
910mod tests {
911
912 use std::{assert_matches, fs::write};
913
914 use ed25519_dalek::VerifyingKey;
915 use tempfile::TempDir;
916 use testresult::TestResult;
917
918 use super::*;
919 use crate::object::{Capability, Domain};
920
921 const WRAP_KEY: &[u8] = &[
922 0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
923 ];
924
925 #[test]
926 fn decrypt_ed25519() -> TestResult {
927 let wrap = YubiHsm2Wrap::from_yhw(include_str!("../tests/backup/private-ed25519.yhw"))?;
928 let decrypted = wrap.decrypt(WRAP_KEY)?;
929 assert!(!decrypted.is_empty());
930 let inner = InnerFormat::parse(&decrypted)?;
931 let mut buffer = vec![];
932 inner.serialize_into(&mut buffer);
933 assert_eq!(buffer, decrypted);
934 assert_eq!(inner.object_type, ObjectType::Ed25519);
935 assert_eq!(inner.wrap_algorithm, WrapAlgorithm::Aes128Ccm);
936 assert_eq!(inner.object_id.id(), 0x1f_u16);
937 assert_eq!(inner.domains, Domain::One.into());
938 assert_eq!(inner.sequence, 0);
939 assert_eq!(inner.origin, 2);
940 assert_eq!(inner.label.to_string(), "Ed25519_Key");
941 let WrappedPayload::ExpandedEd25519(key_data) = inner.key_data else {
942 panic!("Expected Ed25519 key data");
943 };
944 let ExpandedEd25519KeyData {
945 private_scalar,
946 private_hash_prefix,
947 public,
948 } = key_data;
949
950 assert_eq!(
951 private_scalar,
952 &[
953 117, 188, 78, 175, 249, 221, 207, 75, 177, 26, 92, 146, 43, 19, 156, 7, 87, 43,
954 173, 199, 232, 63, 249, 230, 100, 131, 86, 147, 80, 229, 193, 192
955 ]
956 );
957 assert_eq!(
958 private_hash_prefix,
959 &[
960 182, 113, 137, 6, 206, 62, 108, 30, 26, 138, 65, 215, 178, 10, 9, 215, 181, 55,
961 132, 37, 162, 172, 202, 169, 56, 150, 245, 195, 212, 232, 235, 183
962 ]
963 );
964 assert_eq!(
965 public,
966 &[
967 185, 235, 254, 46, 190, 171, 17, 45, 56, 27, 211, 240, 69, 46, 39, 226, 53, 109,
968 50, 78, 181, 96, 30, 177, 162, 240, 122, 187, 82, 30, 156, 242
969 ]
970 );
971 let signing_key: ExpandedSecretKey = key_data.into();
972 let verifying_key = VerifyingKey::from(&signing_key);
973 assert_eq!(public, &verifying_key.to_bytes());
974 Ok(())
975 }
976
977 #[test]
978 fn decrypt_ed25519_with_seed() -> TestResult {
979 let wrap =
980 YubiHsm2Wrap::from_yhw(include_str!("../tests/backup/private-ed25519-seed.yhw"))?;
981 let decrypted = wrap.decrypt(WRAP_KEY)?;
982 assert!(!decrypted.is_empty());
983 let inner = InnerFormat::parse(&decrypted)?;
984 let mut buffer = vec![];
985 inner.serialize_into(&mut buffer);
986 assert_eq!(buffer, decrypted);
987 assert_eq!(inner.object_type, ObjectType::Ed25519);
988 assert_eq!(inner.wrap_algorithm, WrapAlgorithm::Aes128Ccm);
989 assert_eq!(inner.object_id.id(), 13);
990 assert_eq!(inner.domains, Domains::all());
991 assert_eq!(inner.sequence, 0);
992 assert_eq!(inner.origin, 1);
993 assert_eq!(inner.label.to_string(), "Signature_Key_Ed_2");
994 let WrappedPayload::SeedEd25519(key_data) = inner.key_data.clone() else {
995 panic!("Expected Ed25519 key data");
996 };
997
998 let SeedEd25519KeyData {
999 private_scalar,
1000 private_hash_prefix,
1001 public,
1002 private_seed,
1003 } = key_data;
1004
1005 assert_eq!(
1006 private_seed,
1007 &[
1008 73, 122, 141, 156, 79, 125, 147, 201, 97, 207, 112, 15, 133, 155, 17, 216, 4, 254,
1009 88, 71, 207, 139, 63, 170, 229, 246, 54, 32, 206, 12, 84, 86
1010 ]
1011 );
1012 assert_eq!(
1013 private_scalar,
1014 &[
1015 7, 81, 112, 122, 75, 85, 173, 6, 20, 181, 199, 29, 147, 191, 157, 102, 147, 157,
1016 133, 249, 149, 223, 14, 41, 17, 51, 179, 38, 146, 102, 210, 15
1017 ]
1018 );
1019 assert_eq!(
1020 private_hash_prefix,
1021 &[
1022 161, 55, 166, 21, 136, 215, 184, 182, 181, 62, 143, 223, 62, 159, 19, 228, 179, 87,
1023 101, 158, 129, 137, 207, 186, 191, 206, 220, 148, 44, 83, 203, 115
1024 ]
1025 );
1026 assert_eq!(
1027 public,
1028 &[
1029 252, 157, 136, 36, 18, 36, 60, 188, 181, 153, 78, 169, 136, 74, 14, 210, 150, 203,
1030 47, 42, 79, 2, 238, 0, 103, 237, 202, 100, 87, 40, 252, 44
1031 ]
1032 );
1033 let signing_key = SigningKey::from(&key_data);
1034 let serialized = SerializedEd25519::from(&signing_key);
1035 assert_eq!(
1036 inner.key_data,
1037 WrappedPayload::parse(ObjectType::Ed25519, serialized.as_ref())?
1038 );
1039
1040 assert_eq!(public, &signing_key.verifying_key().to_bytes());
1041 let exp = ExpandedSecretKey::from(private_seed);
1042
1043 let mut private_scalar = *private_scalar;
1044 private_scalar.reverse();
1045
1046 assert_eq!(exp.scalar.as_bytes(), &private_scalar);
1047 assert_eq!(&exp.hash_prefix, private_hash_prefix);
1048
1049 let signing_key: ExpandedSecretKey = key_data.into();
1050 assert_eq!(exp.scalar, signing_key.scalar);
1051 assert_eq!(exp.hash_prefix, signing_key.hash_prefix);
1052
1053 let verifying_key = VerifyingKey::from(&signing_key);
1054 assert_eq!(public, &verifying_key.to_bytes());
1055 Ok(())
1056 }
1057
1058 #[test]
1059 fn auth_key() -> TestResult {
1060 let wrap = YubiHsm2Wrap::from_yhw(include_str!("../tests/backup/auth.yhw"))?;
1061 let decrypted = wrap.decrypt(WRAP_KEY)?;
1062 assert!(!decrypted.is_empty());
1063 let inner = InnerFormat::parse(&decrypted)?;
1064 let mut buffer = vec![];
1065 inner.serialize_into(&mut buffer);
1066 assert_eq!(decrypted, buffer);
1067 assert_eq!(inner.object_type, ObjectType::Aes128Auth);
1068 assert_eq!(
1069 inner.capabilities,
1070 Capabilities::from(&[Capability::ExportableUnderWrap][..])
1071 );
1072 assert_eq!(inner.domains, Domain::One.into());
1073 assert_eq!(inner.object_id.id(), 14);
1074 assert_eq!(
1075 inner.key_data,
1076 WrappedPayload::AuthAes128(AuthAes128 {
1077 delegated_capabilities: &[0; 8],
1078 symmetric_keys: &[
1079 152, 123, 73, 154, 181, 120, 84, 139, 48, 32, 41, 176, 213, 53, 39, 232, 122,
1080 150, 131, 153, 10, 233, 98, 202, 67, 12, 27, 245, 184, 198, 41, 93
1081 ]
1082 })
1083 );
1084 assert_eq!(inner.object_id.object_type(), Type::AuthenticationKey);
1085 assert_eq!(inner.label.to_string(), "");
1086 assert_eq!(inner.origin, 2);
1087 assert_eq!(inner.sequence, 0);
1088 Ok(())
1089 }
1090
1091 #[test]
1092 fn opaque_data() -> TestResult {
1093 let wrap = YubiHsm2Wrap::from_yhw(include_str!("../tests/backup/opaque.yhw"))?;
1094 let decrypted = wrap.decrypt(WRAP_KEY)?;
1095 assert!(!decrypted.is_empty());
1096 let inner = InnerFormat::parse(&decrypted)?;
1097 let mut buffer = vec![];
1098 inner.serialize_into(&mut buffer);
1099 assert_eq!(decrypted, buffer);
1100 assert_eq!(inner.object_type, ObjectType::Opaque);
1101 assert_eq!(
1102 inner.capabilities,
1103 Capabilities::from(&[Capability::ExportableUnderWrap][..])
1104 );
1105 assert_eq!(inner.domains, Domain::One.into());
1106 assert_eq!(inner.object_id.id(), 13);
1107 assert_eq!(inner.key_data, WrappedPayload::Opaque(&[1, 2, 3]));
1108 assert_eq!(inner.object_id.object_type(), Type::Opaque);
1109 assert_eq!(inner.label.to_string(), "random");
1110 assert_eq!(inner.origin, 2);
1111 assert_eq!(inner.sequence, 0);
1112 Ok(())
1113 }
1114
1115 #[test]
1116 fn roundtrip_yhw() -> TestResult {
1117 let input = include_str!("../tests/backup/private-ed25519-seed.yhw");
1118 let wrap = YubiHsm2Wrap::from_yhw(input)?;
1119 assert_eq!(input, wrap.to_yhw());
1120 Ok(())
1121 }
1122
1123 #[test]
1124 fn encrypt_decrypt() -> TestResult {
1125 let input = include_str!("../tests/backup/opaque.yhw");
1126 let wrap = YubiHsm2Wrap::from_yhw(input)?;
1127 let decrypted_original = wrap.decrypt(WRAP_KEY)?;
1128 let plain = PlainWrappedDataWithKey {
1129 data: &decrypted_original,
1130 key: WRAP_KEY,
1131 };
1132 let wrap: YubiHsm2Wrap = plain.try_into()?;
1133 let decrypted_from_plain = wrap.decrypt(WRAP_KEY)?;
1134 assert_eq!(decrypted_original, decrypted_from_plain);
1135 Ok(())
1136 }
1137
1138 #[test]
1139 fn roundtrip_wrap() -> TestResult {
1140 let temp_dir = TempDir::new()?;
1141 let private_key_file = temp_dir.path().join("private.key");
1142 let wrapping_key_file = temp_dir.path().join("wrap.key");
1143 write(&private_key_file, [0; 32])?;
1144 write(&wrapping_key_file, WRAP_KEY)?;
1145
1146 let object_id = 1;
1147 let wrapped = wrap_ed25519(
1148 private_key_file,
1149 wrapping_key_file,
1150 object_id,
1151 Domains::all(),
1152 Capabilities::from(&[Capability::SignEddsa][..]),
1153 "test".parse()?,
1154 )?;
1155
1156 let yhw = YubiHsm2Wrap::from_yhw(&wrapped)?;
1157 let raw = yhw.decrypt(WRAP_KEY)?;
1158 let inner = InnerFormat::parse(&raw)?;
1159 assert_eq!(inner.object_id, ObjectId::AsymmetricKey(object_id));
1160 Ok(())
1161 }
1162
1163 #[test]
1166 fn label_from_str_fails_on_invalid_char() -> TestResult {
1167 let text = "some label\0text";
1168 assert_matches!(Label::from_str(text), Err(Error::InvalidLabelCharacter { char, .. }) if char == '\0' );
1169
1170 Ok(())
1171 }
1172
1173 #[test]
1175 fn string_from_label() -> TestResult {
1176 let text = "this is a label";
1177 let label = Label::from_str(text)?;
1178 let string_label: String = label.into();
1179
1180 assert_eq!(string_label, text);
1181 Ok(())
1182 }
1183}