Skip to main content

signstar_yubihsm2/
backup.rs

1//! Utilities for parsing and creating YubiHSM2 wrap files.
2//!
3//! Wrap files are used for [backup and restore] actions with a YubiHSM2 device.
4//! This module provides support for the proprietary YHW data format, used by Yubico tooling.
5//!
6//! The module supports backup of the following types of objects:
7//! - ed25519 private keys (both seeded and expanded form),
8//! - AES-128 authentication keys,
9//! - opaque byte vectors.
10//!
11//! # YHW format
12//!
13//! YubiHSM wrap files (`*.yhw`) consist of an inner and an outer format.
14//!
15//! ## Outer
16//!
17//! The outer format is represented by a base64-encoded file.
18//! Its contents consist of 13 bytes of [nonce] at the start and AES-CCM encrypted data until the
19//! end of the file.
20//!
21//! ## Inner
22//!
23//! Decrypting the AES-CCM encrypted outer data reveals the inner format which has the following
24//! structure:
25//!
26//! - 1 byte for [`WrapAlgorithm`]
27//! - 8 bytes for [`Capabilities`]
28//! - 2 bytes for encoding the object's identifier
29//! - 2 bytes for encoding the wrapped object length without framing
30//! - 2 bytes for [`Domains`]
31//! - 1 byte for the object type (e.g. asymmetric key, opaque)
32//! - 1 byte for the subtype of the object (e.g. ed25519 key)
33//! - 1 byte for a sequence number, which is used internally and always `0`
34//! - 1 byte for encoding the origin (this is only relevant when exporting)
35//! - 40 bytes for a UTF-8 encoded label
36//! - the rest of the inner format is specific to each object type (e.g. opaque byte vectors are
37//!   embedded in their entirety here)
38//!
39//! [backup and restore]: https://docs.yubico.com/hardware/yubihsm-2/hsm-2-user-guide/hsm2-backup-restore.html
40//! [nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce
41
42use 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/// Backup error.
62#[derive(Debug, thiserror::Error)]
63pub enum Error {
64    /// Base64 decoding error.
65    #[error("Decoding Base64 failed: {0}")]
66    Base64Decode(#[from] base64ct::Error),
67
68    /// Decryption error.
69    #[error("Decryption error: {0}")]
70    Decrypt(#[from] ccm::Error),
71
72    /// Slice length error.
73    #[error("Incorrect slice length: {0}")]
74    SliceLength(#[from] TryFromSliceError),
75
76    /// Unexpected Ed25519 serialized form length.
77    ///
78    /// The only supported values are [ExpandedEd25519KeyData::LEN] and [SeedEd25519KeyData::LEN].
79    #[error("Unexpected Ed25519 serialized form length: {actual}")]
80    UnexpectedEd25519SerializedLength {
81        /// Length of the serialized form encountered.
82        actual: usize,
83    },
84
85    /// Unsupported object type.
86    #[error("Cannot parse data of unknown type: {0:?}")]
87    UnknownObjectType(ObjectType),
88
89    /// Object error.
90    #[error("YubiHSM2 object error: {0:?}")]
91    YubiHsmObject(#[from] yubihsm::object::Error),
92
93    /// Parsing failed because the buffer does not contain enough data.
94    #[error("Parsing buffer: not enough data.")]
95    InsufficientDataInBuffer,
96}
97
98/// The representation of data about to be wrapped (encrypted) with key.
99pub struct PlainWrappedDataWithKey<'a, 'b> {
100    /// Data that is being wrapped.
101    pub data: &'a [u8],
102
103    /// Wrapping key.
104    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    /// Encrypts `value.data` using a `value.key` and returns it as a new [`YubiHsm2Wrap`].
120    ///
121    /// # Errors
122    ///
123    /// Returns an error if encryption of `wrapped_data` with `wrapping_key` fails.
124    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/// The representation of wrapped (encrypted) data of a YubiHSM2.
139#[derive(Debug)]
140pub struct YubiHsm2Wrap {
141    wrapped: Vec<u8>,
142}
143
144impl YubiHsm2Wrap {
145    /// Creates a new [`YubiHsm2Wrap`] from raw binary bytes.
146    pub fn new(wrapped: Vec<u8>) -> Self {
147        Self { wrapped }
148    }
149
150    /// Creates a new [`YubiHsm2Wrap`] from bytes containing the proprietary Yubico YHW format.
151    ///
152    /// # Note
153    ///
154    /// Leading and trailing whitespace are stripped.
155    ///
156    /// # Errors
157    ///
158    /// Returns an error if `wrapped` cannot be decoded from base64.
159    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    /// Creates a [`String`] containing the representation of [`Self`] in the proprietary Yubico YHW
166    /// format.
167    pub fn to_yhw(&self) -> String {
168        Base64::encode_string(&self.wrapped)
169    }
170
171    /// Decrypts the [`YubiHsm2Wrap`] using the provided `wrapping_key`.
172    ///
173    /// # Errors
174    ///
175    /// Returns an error if decrypting the data using `wrapping_key` fails.
176    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/// The supported algorithms available for wrapping (encryption) of data.
192///
193/// See <https://github.com/Yubico/yubihsm-shell/blob/5a0447b9786d0e6149b67529789bd67530b38d6b/lib/yubihsm.h#L488-L515>.
194#[derive(Clone, Copy, Debug, Eq, FromPrimitive, IntoPrimitive, PartialEq)]
195#[repr(u8)]
196pub enum WrapAlgorithm {
197    /// CCM using AES-128 keys.
198    Aes128Ccm = 29,
199
200    /// CCM using AES-192 keys.
201    Aes192Ccm = 41,
202
203    /// CCM using AES-256 keys.
204    Aes256Ccm = 42,
205
206    /// Unknown wrap algorithm.
207    #[num_enum(catch_all)]
208    Unknown(u8),
209}
210
211/// The object type contained in the backup.
212///
213/// All variants that are known (that is, all with the exception of [`ObjectType::Unknown`]) are
214/// supported.
215#[derive(Clone, Copy, Debug, Eq, FromPrimitive, IntoPrimitive, PartialEq)]
216#[repr(u8)]
217pub enum ObjectType {
218    /// Ed25519.
219    ///
220    /// See <https://github.com/Yubico/yubihsm-shell/blob/5a0447b9786d0e6149b67529789bd67530b38d6b/lib/yubihsm.h#L520>.
221    Ed25519 = 46,
222
223    /// AES-128 used for authentication keys.
224    ///
225    /// See <https://github.com/Yubico/yubihsm-shell/blob/5a0447b9786d0e6149b67529789bd67530b38d6b/lib/yubihsm.h#L507C3-L507C45>.
226    Aes128Auth = 38,
227
228    /// Raw byte data.
229    ///
230    /// See <https://github.com/Yubico/yubihsm-shell/blob/5a0447b9786d0e6149b67529789bd67530b38d6b/lib/yubihsm.h#L491>.
231    Opaque = 30,
232
233    /// Unknown object type.
234    #[num_enum(catch_all)]
235    Unknown(u8),
236}
237
238/// Expanded form of an ed25519 private key without seed.
239#[derive(Clone, Debug, Eq, PartialEq)]
240pub struct ExpandedEd25519KeyData<'a> {
241    /// Private scalar.
242    pub private_scalar: &'a [u8; 32],
243
244    /// Private hash prefix.
245    pub private_hash_prefix: &'a [u8; 32],
246
247    /// Public key.
248    pub public: &'a [u8; 32],
249}
250
251impl ExpandedEd25519KeyData<'_> {
252    /// The number of bytes tracked in an [`ExpandedEd25519KeyData`].
253    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/// The private parts of an ed25519 key.
279///
280/// # Note
281///
282/// The data includes the private key seed.
283#[derive(Clone, Debug, Eq, PartialEq)]
284pub struct SeedEd25519KeyData<'a> {
285    /// Private scalar.
286    pub private_scalar: &'a [u8; 32],
287
288    /// Private hash prefix.
289    pub private_hash_prefix: &'a [u8; 32],
290
291    /// Public key.
292    pub public: &'a [u8; 32],
293
294    /// Private key seed.
295    pub private_seed: &'a [u8; 32],
296}
297
298impl SeedEd25519KeyData<'_> {
299    /// The number of bytes tracked in a [`SeedEd25519KeyData`].
300    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        // NOTE: `ExpandedSecretKey::from_slice` unnecessarily clamps the scalar
321        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/// An Ed25519 key serialized in YubiHSM2 specific format.
335///
336/// The serialized form, as accepted by the YubiHSM2, consists of four 32-byte values:
337/// - secret key seed, from with the scalar and hash-prefix are derived,
338/// - scalar value, used directly for signing,
339/// - hash prefix, which is a domain separator used when hashing the message to generate the
340///   pseudorandom `r` value,
341/// - public key, used for verifying signed data.
342#[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/// An AES-128 based authentication key.
365#[derive(Clone, Debug, Eq, PartialEq)]
366pub struct AuthAes128<'a> {
367    /// Delegated capabilities of the key.
368    pub delegated_capabilities: &'a [u8; 8],
369
370    /// Pair of symmetric keys used for encryption and MAC.
371    pub symmetric_keys: &'a [u8; 32],
372}
373
374impl AuthAes128<'_> {
375    /// The number of bytes tracked in an [`AuthAes128`].
376    const LEN: usize = 8 + 32;
377}
378
379/// The deserialized body of a wrapped object.
380///
381/// This usually is the private key material for a signing or authentication object.
382/// However, it can also represent [raw binary data][WrappedPayload::Opaque], which may have no
383/// specific purpose in the context of the cryptographic functionalities of the YubiHSM2 hardware.
384#[derive(Clone, Debug, Eq, PartialEq)]
385pub enum WrappedPayload<'a> {
386    /// Ed25519 private key parts without the private key seed.
387    ExpandedEd25519(ExpandedEd25519KeyData<'a>),
388
389    /// Ed25519 private key parts with the private key seed.
390    SeedEd25519(SeedEd25519KeyData<'a>),
391
392    /// AES-128-based authentication key.
393    AuthAes128(AuthAes128<'a>),
394
395    /// Raw binary data.
396    Opaque(&'a [u8]),
397}
398
399impl<'a> WrappedPayload<'a> {
400    /// Parses raw bytes of specified object type into a [`WrappedPayload`] structure.
401    ///
402    /// Depending on the [`ObjectType`] the expected shape of `bytes` differs:
403    /// - for ed25519 keys two forms are accepted: expanded (exactly 96 bytes) and seeded (128
404    ///   bytes)
405    /// - for AES-128 authentication keys, `bytes` need to be exactly 40 bytes long (8 bytes for
406    ///   delecated capabilities and 32 for a pair of AES-128 keys)
407    /// - opaque does not make any restrictions and will accept any `bytes`
408    ///
409    /// # Errors
410    ///
411    /// Returns an [`Error`] if:
412    /// - private key material length is incorrect
413    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    /// Serializes itself into the provided buffer.
433    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    /// Returns the expected length of the serialized form.
455    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
465/// Reader of big-endian encoded bytes.
466struct BeReader<'a> {
467    pos: usize,
468    buf: &'a [u8],
469}
470
471impl<'a> BeReader<'a> {
472    /// Constructs a new reader backed by the specified buffer.
473    fn new(buf: &'a [u8]) -> Self {
474        Self { buf, pos: 0 }
475    }
476
477    /// Returns the current position of this reader.
478    fn position(&self) -> usize {
479        self.pos
480    }
481
482    /// Reads one byte and forwards the reader's position.
483    ///
484    /// # Errors
485    ///
486    /// Returns an [error][Error::InsufficientDataInBuffer] if there are no more bytes to read.
487    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    /// Reads a [`u16`] and forwards the reader's position.
497    ///
498    /// # Errors
499    ///
500    /// Returns an [error][Error::InsufficientDataInBuffer] if there are insufficient bytes in the
501    /// buffer.
502    fn read_u16(&mut self) -> Result<u16, Error> {
503        Ok(u16::from_be_bytes([self.read_u8()?, self.read_u8()?]))
504    }
505
506    /// Reads a constant-size array and forwards the reader's position.
507    ///
508    /// # Errors
509    ///
510    /// Returns an [error][Error::InsufficientDataInBuffer] if there are insufficient bytes in the
511    /// buffer.
512    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    /// Reads a constant-size array and forwards the reader's position.
524    ///
525    /// # Errors
526    ///
527    /// Returns an [error][Error::InsufficientDataInBuffer] if the reader has already been fully
528    /// read.
529    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/// Parsed representation of the backup's inner format.
540#[derive(Debug)]
541pub struct InnerFormat<'a> {
542    /// Algorithm used for creating this wrap.
543    pub wrap_algorithm: WrapAlgorithm,
544
545    /// Capabilities of the wrapped object.
546    pub capabilities: &'a [u8; 8],
547
548    /// Identifier of the wrapped object.
549    pub object_id: ObjectId,
550
551    /// Domains of the wrapped object.
552    pub domains: Domains,
553
554    /// Type of the object.
555    pub object_type: ObjectType,
556
557    /// Sequence number, which is an internal number and is always `0`.
558    pub sequence: u8,
559
560    /// Key origin.
561    pub origin: u8,
562
563    /// Key label.
564    pub label: String,
565
566    /// Payload of the key.
567    pub key_data: WrappedPayload<'a>,
568}
569
570impl<'a> InnerFormat<'a> {
571    /// Parses the inner format from `raw`.
572    ///
573    /// # Errors
574    ///
575    /// Returns an error if
576    /// - the buffer does not contain enough bytes for parsing
577    /// - the data in the buffer is inconsistent
578    /// - parsing private key material fails
579    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        // check if the datalen is consistent with the buffer's length
600        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    /// Serializes this format into a list of bytes.
618    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}