nethsm/
openpgp.rs

1//! OpenPGP-related functions.
2
3use std::{
4    backtrace::Backtrace,
5    borrow::{Borrow, Cow},
6    collections::HashSet,
7    fmt::{Debug, Display},
8    str::FromStr,
9    string::FromUtf8Error,
10};
11
12use base64ct::{Base64, Encoding as _};
13use chrono::{DateTime, Utc};
14use digest::DynDigest;
15use ed25519_dalek::VerifyingKey;
16use email_address::{EmailAddress, Options};
17use log::{error, warn};
18use pgp::{
19    composed::{
20        ArmorOptions,
21        Deserializable as _,
22        SignedPublicKey,
23        SignedSecretKey,
24        StandaloneSignature,
25    },
26    crypto::{hash::HashAlgorithm, public_key::PublicKeyAlgorithm},
27    packet::{
28        KeyFlags,
29        Notation,
30        PacketTrait,
31        PubKeyInner,
32        PublicKey,
33        Signature,
34        SignatureConfig,
35        SignatureType,
36        Subpacket,
37        SubpacketData,
38        UserId,
39    },
40    ser::Serialize,
41    types::{
42        CompressionAlgorithm,
43        EcdsaPublicParams,
44        KeyDetails as _,
45        KeyId,
46        KeyVersion,
47        Mpi,
48        Password,
49        PlainSecretParams,
50        PublicKeyTrait as _,
51        PublicParams,
52        RsaPublicParams,
53        SecretKeyTrait,
54        SecretParams,
55        SignatureBytes,
56        SignedUser,
57    },
58};
59use picky_asn1_x509::{
60    AlgorithmIdentifier,
61    DigestInfo,
62    ShaVariant,
63    signature::EcdsaSignatureValue,
64};
65use rsa::BigUint;
66use rsa::traits::PublicKeyParts as _;
67use sha2::digest::Digest as _;
68
69use crate::{KeyMechanism, KeyType, NetHsm, PrivateKeyImport, key_type_matches_length};
70
71/// An error that may occur when working with OpenPGP data.
72#[derive(Debug, thiserror::Error)]
73pub enum Error {
74    /// A Base64 encoded string can not be decode
75    #[error("Decoding Base64 string failed: {0}")]
76    Base64Decode(#[from] base64ct::Error),
77
78    /// Certificate for the key has not been initialized
79    #[error("Certificate for the key \"{0}\" has not been initialized")]
80    CertificateMissing(crate::KeyId),
81
82    /// Elliptic curve error
83    #[error("Elliptic curve error: {0}")]
84    EllipticCurve(#[from] p256::elliptic_curve::Error),
85
86    /// Duplicate OpenPGP User ID
87    #[error("The OpenPGP User ID {user_id} is used more than once!")]
88    DuplicateUserId {
89        /// The duplicate OpenPGP User ID.
90        user_id: OpenPgpUserId,
91    },
92
93    /// Provided OpenPGP version is invalid
94    #[error("Invalid OpenPGP version: {0}")]
95    InvalidOpenPgpVersion(String),
96
97    /// Provided key data is invalid
98    #[error("Key data invalid: {0}")]
99    KeyData(String),
100
101    /// NetHsm error
102    #[error("NetHSM error: {0}")]
103    NetHsm(String),
104
105    /// OpenPGP error
106    #[error("rPGP error: {0}")]
107    Pgp(#[from] pgp::errors::Error),
108
109    /// The Transferable Secret Key is passphrase protected
110    #[error("Transferable Secret Key is passphrase protected")]
111    PrivateKeyPassphraseProtected,
112
113    /// Multiple component keys are unsupported
114    #[error("Unsupported multiple component keys")]
115    UnsupportedMultipleComponentKeys,
116
117    /// The key format used is unsupported
118    #[error("Unsupported key format: {public_params:?}")]
119    UnsupportedKeyFormat {
120        /// The unsupported public key parameters.
121        public_params: Box<PublicParams>,
122    },
123
124    /// The User ID is too large
125    #[error("The OpenPGP User ID is too large: {user_id}")]
126    UserIdTooLarge {
127        /// The string that is too long to be used as an OpenPGP User ID.
128        user_id: String,
129    },
130
131    /// A UTF-8 error when trying to create a string from bytes.
132    #[error("Creating a valid UTF-8 string from bytes failed while {context}:\n{source}")]
133    FromUtf8 {
134        /// The context in which a UTF-8 error occurred.
135        ///
136        /// This is meant to complete the sentence "Creating a valid UTF-8 string from bytes failed
137        /// while ".
138        context: &'static str,
139        /// The source error.
140        source: FromUtf8Error,
141    },
142}
143
144/// The OpenPGP version
145#[derive(
146    Clone,
147    Copy,
148    Debug,
149    Default,
150    serde::Deserialize,
151    strum::Display,
152    strum::EnumIter,
153    Hash,
154    strum::IntoStaticStr,
155    Eq,
156    PartialEq,
157    serde::Serialize,
158)]
159#[serde(into = "String", try_from = "String")]
160pub enum OpenPgpVersion {
161    /// OpenPGP version 4 as defined in [RFC 4880]
162    ///
163    /// [RFC 4880]: https://www.rfc-editor.org/rfc/rfc4880.html
164    #[default]
165    #[strum(to_string = "4")]
166    V4,
167
168    /// OpenPGP version 6 as defined in [RFC 9580]
169    ///
170    /// [RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html
171    #[strum(to_string = "6")]
172    V6,
173}
174
175impl AsRef<str> for OpenPgpVersion {
176    fn as_ref(&self) -> &str {
177        match self {
178            Self::V4 => "4",
179            Self::V6 => "6",
180        }
181    }
182}
183
184impl FromStr for OpenPgpVersion {
185    type Err = Error;
186
187    /// Creates an [`OpenPgpVersion`] from a string slice
188    ///
189    /// Only valid OpenPGP versions are considered:
190    /// * [RFC 4880] aka "v4"
191    /// * [RFC 9580] aka "v6"
192    ///
193    /// # Errors
194    ///
195    /// Returns an error if the provided string slice does not represent a valid OpenPGP version.
196    ///
197    /// # Examples
198    ///
199    /// ```
200    /// use std::str::FromStr;
201    ///
202    /// use nethsm::OpenPgpVersion;
203    ///
204    /// # fn main() -> testresult::TestResult {
205    /// assert_eq!(OpenPgpVersion::from_str("4")?, OpenPgpVersion::V4);
206    /// assert_eq!(OpenPgpVersion::from_str("6")?, OpenPgpVersion::V6);
207    ///
208    /// assert!(OpenPgpVersion::from_str("5").is_err());
209    /// # Ok(())
210    /// # }
211    /// ```
212    /// [RFC 4880]: https://www.rfc-editor.org/rfc/rfc4880.html
213    /// [RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html
214    fn from_str(s: &str) -> Result<Self, Self::Err> {
215        match s {
216            "4" | "v4" | "V4" | "OpenPGPv4" => Ok(Self::V4),
217            "5" | "v5" | "V5" | "OpenPGPv5" => Err(Error::InvalidOpenPgpVersion(format!(
218                "{s} (\"we don't do these things around here\")"
219            ))),
220            "6" | "v6" | "V6" | "OpenPGPv6" => Ok(Self::V6),
221            _ => Err(Error::InvalidOpenPgpVersion(s.to_string())),
222        }
223    }
224}
225
226impl From<OpenPgpVersion> for String {
227    fn from(value: OpenPgpVersion) -> Self {
228        value.to_string()
229    }
230}
231
232impl TryFrom<KeyVersion> for OpenPgpVersion {
233    type Error = Error;
234
235    /// Creates an [`OpenPgpVersion`] from a [`KeyVersion`].
236    ///
237    /// # Errors
238    ///
239    /// Returns an error if an invalid OpenPGP version is encountered.
240    fn try_from(value: KeyVersion) -> Result<Self, Self::Error> {
241        Ok(match value {
242            KeyVersion::V4 => Self::V4,
243            KeyVersion::V6 => Self::V6,
244            _ => {
245                return Err(Error::InvalidOpenPgpVersion(
246                    Into::<u8>::into(value).to_string(),
247                ));
248            }
249        })
250    }
251}
252
253impl TryFrom<String> for OpenPgpVersion {
254    type Error = Error;
255
256    fn try_from(value: String) -> Result<Self, Self::Error> {
257        Self::from_str(&value)
258    }
259}
260
261/// A distinction between types of OpenPGP User IDs
262#[derive(Clone, Debug, Eq, Hash, PartialEq)]
263enum OpenPgpUserIdType {
264    /// An OpenPGP User ID that contains a valid e-mail address (e.g. "John Doe
265    /// <john@example.org>")
266    ///
267    /// The e-mail address must use a top-level domain (TLD) and no domain literal (e.g. an IP
268    /// address) is allowed.
269    Email(EmailAddress),
270
271    /// A plain OpenPGP User ID
272    ///
273    /// The User ID may contain any UTF-8 character, but does not represent a valid e-mail address.
274    Plain(String),
275}
276
277/// A basic representation of a User ID for OpenPGP
278///
279/// While [OpenPGP User IDs] are loosely defined to be UTF-8 strings, they do not enforce
280/// particular rules around the use of e-mail addresses or their general length.
281/// This type allows to distinguish between plain UTF-8 strings and valid e-mail addresses.
282/// Valid e-mail addresses must provide a display part, use a top-level domain (TLD) and not rely on
283/// domain literals (e.g. IP address).
284/// The length of a User ID is implicitly limited by the maximum length of an OpenPGP packet (8192
285/// bytes).
286/// As such, this type only allows a maximum length of 4096 bytes as middle ground.
287///
288/// [OpenPGP User IDs]: https://www.rfc-editor.org/rfc/rfc9580.html#name-user-id-packet-type-id-13
289#[derive(Clone, Debug, serde::Deserialize, Eq, Hash, PartialEq, serde::Serialize)]
290#[serde(into = "String", try_from = "String")]
291pub struct OpenPgpUserId(OpenPgpUserIdType);
292
293impl OpenPgpUserId {
294    /// Creates a new [`OpenPgpUserId`] from a String
295    ///
296    /// # Errors
297    ///
298    /// Returns an [`Error::UserIdTooLarge`] if the chars of the provided String exceed
299    /// 4096 bytes. This ensures to stay below the valid upper limit defined by the maximum OpenPGP
300    /// packet size of 8192 bytes.
301    ///
302    /// # Examples
303    ///
304    /// ```
305    /// use std::str::FromStr;
306    ///
307    /// use nethsm::OpenPgpUserId;
308    ///
309    /// # fn main() -> testresult::TestResult {
310    /// assert!(!OpenPgpUserId::new("🤡".to_string())?.is_email());
311    ///
312    /// assert!(OpenPgpUserId::new("🤡 <foo@xn--rl8h.org>".to_string())?.is_email());
313    ///
314    /// // an e-mail without a display name is not considered a valid e-mail
315    /// assert!(!OpenPgpUserId::new("<foo@xn--rl8h.org>".to_string())?.is_email());
316    ///
317    /// // this fails because the provided String is too long
318    /// assert!(OpenPgpUserId::new("U".repeat(4097)).is_err());
319    /// # Ok(())
320    /// # }
321    /// ```
322    pub fn new(user_id: String) -> Result<Self, Error> {
323        if user_id.len() > 4096 {
324            return Err(Error::UserIdTooLarge { user_id });
325        }
326        if let Ok(email) = EmailAddress::parse_with_options(
327            &user_id,
328            Options::default()
329                .with_required_tld()
330                .without_domain_literal(),
331        ) {
332            Ok(Self(OpenPgpUserIdType::Email(email)))
333        } else {
334            Ok(Self(OpenPgpUserIdType::Plain(user_id)))
335        }
336    }
337
338    /// Returns whether the [`OpenPgpUserId`] is a valid e-mail address
339    ///
340    /// # Examples
341    ///
342    /// ```
343    /// use nethsm::OpenPgpUserId;
344    ///
345    /// # fn main() -> testresult::TestResult {
346    /// assert!(!OpenPgpUserId::new("🤡".to_string())?.is_email());
347    ///
348    /// assert!(OpenPgpUserId::new("🤡 <foo@xn--rl8h.org>".to_string())?.is_email());
349    /// # Ok(())
350    /// # }
351    /// ```
352    pub fn is_email(&self) -> bool {
353        matches!(self.0, OpenPgpUserIdType::Email(..))
354    }
355}
356
357impl AsRef<str> for OpenPgpUserId {
358    fn as_ref(&self) -> &str {
359        match self.0.borrow() {
360            OpenPgpUserIdType::Email(user_id) => user_id.as_str(),
361            OpenPgpUserIdType::Plain(user_id) => user_id.as_str(),
362        }
363    }
364}
365
366impl Display for OpenPgpUserId {
367    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368        write!(f, "{}", self.as_ref())
369    }
370}
371
372impl FromStr for OpenPgpUserId {
373    type Err = Error;
374
375    fn from_str(s: &str) -> Result<Self, Self::Err> {
376        Self::new(s.to_string())
377    }
378}
379
380impl From<OpenPgpUserId> for String {
381    fn from(value: OpenPgpUserId) -> Self {
382        value.to_string()
383    }
384}
385
386impl TryFrom<&SignedUser> for OpenPgpUserId {
387    type Error = Error;
388
389    /// Creates an [`OpenPgpUserId`] from [`SignedUser`].
390    ///
391    /// # Errors
392    ///
393    /// Returns an error if the [`SignedUser`]'s User ID can not be converted to a valid UTF-8
394    /// string.
395    fn try_from(value: &SignedUser) -> Result<Self, Self::Error> {
396        Self::new(
397            String::from_utf8(value.id.id().to_vec()).map_err(|source| Error::FromUtf8 {
398                context: "converting an OpenPGP UserID",
399                source,
400            })?,
401        )
402    }
403}
404
405impl TryFrom<String> for OpenPgpUserId {
406    type Error = Error;
407
408    fn try_from(value: String) -> Result<Self, Self::Error> {
409        Self::new(value)
410    }
411}
412
413/// A list of [`OpenPgpUserId`]
414///
415/// The items of the list are guaranteed to be unique.
416#[derive(Clone, Debug, serde::Deserialize, Eq, Hash, PartialEq, serde::Serialize)]
417#[serde(into = "Vec<String>", try_from = "Vec<String>")]
418pub struct OpenPgpUserIdList(Vec<OpenPgpUserId>);
419
420impl OpenPgpUserIdList {
421    /// Creates a new [`OpenPgpUserIdList`]
422    ///
423    /// # Errors
424    ///
425    /// Returns an error, if one of the provided [`OpenPgpUserId`]s is a duplicate.
426    ///
427    /// # Examples
428    ///
429    /// ```
430    /// use nethsm::OpenPgpUserIdList;
431    ///
432    /// # fn main() -> testresult::TestResult {
433    /// OpenPgpUserIdList::new(vec![
434    ///     "🤡 <foo@xn--rl8h.org>".parse()?,
435    ///     "🤡 <bar@xn--rl8h.org>".parse()?,
436    /// ])?;
437    ///
438    /// // this fails because the two OpenPgpUserIds are the same
439    /// assert!(
440    ///     OpenPgpUserIdList::new(vec![
441    ///         "🤡 <foo@xn--rl8h.org>".parse()?,
442    ///         "🤡 <foo@xn--rl8h.org>".parse()?,
443    ///     ])
444    ///     .is_err()
445    /// );
446    /// # Ok(())
447    /// # }
448    /// ```
449    pub fn new(user_ids: Vec<OpenPgpUserId>) -> Result<Self, Error> {
450        let mut set = HashSet::new();
451        for user_id in user_ids.iter() {
452            if !set.insert(user_id) {
453                return Err(Error::DuplicateUserId {
454                    user_id: user_id.to_owned(),
455                });
456            }
457        }
458        Ok(Self(user_ids))
459    }
460
461    /// Iterator for OpenPGP User IDs contained in this list.
462    pub fn iter(&self) -> impl Iterator<Item = &OpenPgpUserId> {
463        self.0.iter()
464    }
465
466    /// Returns a reference to the first [`OpenPgpUserId`] if there is one.
467    pub fn first(&self) -> Option<&OpenPgpUserId> {
468        self.0.first()
469    }
470}
471
472impl AsRef<[OpenPgpUserId]> for OpenPgpUserIdList {
473    fn as_ref(&self) -> &[OpenPgpUserId] {
474        &self.0
475    }
476}
477
478impl From<OpenPgpUserIdList> for Vec<String> {
479    fn from(value: OpenPgpUserIdList) -> Self {
480        value
481            .iter()
482            .map(|user_id| user_id.to_string())
483            .collect::<Vec<String>>()
484    }
485}
486
487impl TryFrom<Vec<String>> for OpenPgpUserIdList {
488    type Error = Error;
489
490    fn try_from(value: Vec<String>) -> Result<Self, Self::Error> {
491        let user_ids = {
492            let mut user_ids: Vec<OpenPgpUserId> = vec![];
493            for user_id in value {
494                user_ids.push(OpenPgpUserId::new(user_id)?)
495            }
496            user_ids
497        };
498        OpenPgpUserIdList::new(user_ids)
499    }
500}
501
502/// PGP-adapter for a NetHSM key.
503///
504/// All PGP-related operations executed on objects of this type will be forwarded to
505/// the NetHSM instance.
506struct HsmKey<'a, 'b> {
507    public_key: PublicKey,
508    nethsm: &'a NetHsm,
509    key_id: &'b crate::KeyId,
510}
511
512impl Debug for HsmKey<'_, '_> {
513    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
514        f.debug_struct("HsmKey")
515            .field("public_key", &self.public_key)
516            .field("key_id", &self.key_id)
517            .finish()
518    }
519}
520
521/// Wraps an [`Error`] in a [`std::io::Error`] and returns it as a [`pgp::errors::Error`].
522///
523/// Since it is currently not possible to wrap the arbitrary [`Error`] of an external function
524/// cleanly in a [`pgp::errors::Error`], this function first wraps it in a [`std::io::Error`].
525/// This behavior has been suggested upstream in <https://github.com/rpgp/rpgp/issues/517#issuecomment-2778245199>
526#[inline]
527fn to_rpgp_error(e: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> pgp::errors::Error {
528    pgp::errors::Error::IO {
529        source: std::io::Error::other(e),
530        backtrace: Some(Backtrace::capture()),
531    }
532}
533
534/// Parse signature bytes into algorithm-specific vector of MPIs.
535fn parse_signature(sig_type: crate::SignatureType, sig: &[u8]) -> pgp::errors::Result<Vec<Mpi>> {
536    use crate::SignatureType;
537    Ok(match sig_type {
538        SignatureType::EcdsaP256 | SignatureType::EcdsaP384 | SignatureType::EcdsaP521 => {
539            let sig: EcdsaSignatureValue = picky_asn1_der::from_bytes(sig).map_err(|e| {
540                error!("DER decoding error when parsing ECDSA signature: {e:?}");
541                to_rpgp_error(e)
542            })?;
543            vec![
544                Mpi::from_slice(sig.r.as_unsigned_bytes_be()),
545                Mpi::from_slice(sig.s.as_unsigned_bytes_be()),
546            ]
547        }
548        SignatureType::EdDsa => {
549            if sig.len() != 64 {
550                return Err(pgp::errors::Error::InvalidKeyLength);
551            }
552
553            vec![Mpi::from_slice(&sig[..32]), Mpi::from_slice(&sig[32..])]
554        }
555        SignatureType::Pkcs1 => {
556            // RSA
557            vec![Mpi::from_slice(sig)]
558        }
559        _ => {
560            warn!("Unsupported signature type: {sig_type}");
561            return Err(pgp::errors::Error::InvalidInput {
562                backtrace: Some(Backtrace::capture()),
563            });
564        }
565    })
566}
567
568impl<'a, 'b> HsmKey<'a, 'b> {
569    /// Creates a new remote signing key which will use `key_id` key for signing.
570    fn new(nethsm: &'a NetHsm, public_key: PublicKey, key_id: &'b crate::KeyId) -> Self {
571        Self {
572            nethsm,
573            public_key,
574            key_id,
575        }
576    }
577
578    /// Returns correct mode to use for signatures which depend on the public key.
579    fn sign_mode(&self) -> pgp::errors::Result<crate::SignatureType> {
580        Ok(match self.public_key.public_params() {
581            PublicParams::ECDSA(ecdsa) => match ecdsa {
582                EcdsaPublicParams::P256 { .. } => crate::SignatureType::EcdsaP256,
583                EcdsaPublicParams::P384 { .. } => crate::SignatureType::EcdsaP384,
584                EcdsaPublicParams::P521 { .. } => crate::SignatureType::EcdsaP521,
585                _ => {
586                    warn!("Unsupported ECDSA parameter type: {ecdsa:?}");
587                    return Err(pgp::errors::Error::InvalidInput {
588                        backtrace: Some(Backtrace::capture()),
589                    });
590                }
591            },
592            PublicParams::EdDSALegacy { .. } => crate::SignatureType::EdDsa,
593            PublicParams::RSA { .. } => crate::SignatureType::Pkcs1,
594            params => {
595                warn!("Unsupported signing parameters: {params:?}");
596                return Err(pgp::errors::Error::InvalidInput {
597                    backtrace: Some(Backtrace::capture()),
598                });
599            }
600        })
601    }
602}
603
604impl pgp::types::KeyDetails for HsmKey<'_, '_> {
605    fn version(&self) -> KeyVersion {
606        self.public_key.version()
607    }
608
609    fn fingerprint(&self) -> pgp::types::Fingerprint {
610        self.public_key.fingerprint()
611    }
612
613    fn key_id(&self) -> KeyId {
614        self.public_key.key_id()
615    }
616
617    fn algorithm(&self) -> PublicKeyAlgorithm {
618        self.public_key.algorithm()
619    }
620}
621
622/// Transforms the raw digest data for cryptographic signing.
623///
624/// Raw cryptographic signing primitives have special provisions that
625/// need to be taken care of when using certain combinations of
626/// signing schemes and hashing algorithms.
627///
628/// This function transforms the digest into bytes that are ready to
629/// be passed to raw cryptographic functions. The exact specifics of
630/// the transformations are documented inside the function.
631fn prepare_digest_data(
632    signature_type: crate::SignatureType,
633    hash: HashAlgorithm,
634    digest: &[u8],
635) -> pgp::errors::Result<Cow<'_, [u8]>> {
636    Ok(match signature_type {
637        // RSA-PKCS#1 signing scheme needs to wrap the digest value
638        // in an DER-encoded ASN.1 DigestInfo structure which captures
639        // the hash used.
640        // See: https://www.rfc-editor.org/rfc/rfc8017#appendix-A.2.4
641        crate::SignatureType::Pkcs1 => picky_asn1_der::to_vec(&DigestInfo {
642            oid: hash_to_oid(hash)?,
643            digest: digest.to_vec().into(),
644        })
645        .map_err(|e| {
646            error!("Encoding signature to PKCS#1 format failed: {e:?}");
647            to_rpgp_error(e)
648        })?
649        .into(),
650
651        // ECDSA may need to truncate the digest if it's too long
652        // See: https://www.rfc-editor.org/rfc/rfc9580#section-5.2.3.2
653        crate::SignatureType::EcdsaP224 => digest[..usize::min(28, digest.len())].into(),
654        crate::SignatureType::EcdsaP256 => digest[..usize::min(32, digest.len())].into(),
655        crate::SignatureType::EcdsaP384 => digest[..usize::min(48, digest.len())].into(),
656
657        // All other schemes that we use will not need any kind of
658        // digest transformations.
659        _ => digest.into(),
660    })
661}
662
663impl SecretKeyTrait for HsmKey<'_, '_> {
664    /// Creates a data signature.
665    ///
666    /// # Note
667    ///
668    /// The [`NetHsm`] in use is expected to be unlocked and configured to use a user in the
669    /// [`Operator`][`crate::UserRole::Operator`] role with access to the signing key.
670    /// Using a [`Password`] is not necessary as the operation deals with unencrypted cryptographic
671    /// key material.
672    ///
673    /// # Errors
674    ///
675    /// Returns an error if
676    /// - the key uses unsupported parameters (e.g. brainpool curves),
677    /// - digest serialization fails (e.g. ASN1 encoding of digest for RSA signatures),
678    /// - NetHSM `sign_digest` call fails,
679    /// - parsing of signature returned from the NetHSM fails.
680    fn create_signature(
681        &self,
682        _key_pw: &Password,
683        hash: HashAlgorithm,
684        data: &[u8],
685    ) -> pgp::errors::Result<SignatureBytes> {
686        let signature_type = self.sign_mode()?;
687        let request_data = prepare_digest_data(signature_type, hash, data)?;
688
689        let sig = self
690            .nethsm
691            .sign_digest(self.key_id, signature_type, &request_data)
692            .map_err(|e| {
693                error!("NetHsm::sign_digest failed: {e:?}");
694                to_rpgp_error(e)
695            })?;
696
697        Ok(SignatureBytes::Mpis(parse_signature(signature_type, &sig)?))
698    }
699
700    /// Returns the preferred hash algorithm for data digests.
701    ///
702    /// # Note
703    /// We always return SHA-512 since this it is faster than SHA-256 on modern hardware and of
704    /// sufficient size to accommodate all elliptic-curve algorithms.
705    fn hash_alg(&self) -> HashAlgorithm {
706        HashAlgorithm::Sha512
707    }
708}
709
710/// Generates an OpenPGP certificate for the given NetHSM key.
711pub fn add_certificate(
712    nethsm: &NetHsm,
713    flags: KeyUsageFlags,
714    key_id: &crate::KeyId,
715    user_id: OpenPgpUserId,
716    created_at: DateTime<Utc>,
717    version: OpenPgpVersion,
718) -> Result<Vec<u8>, Error> {
719    if version != OpenPgpVersion::V4 {
720        unimplemented!(
721            "Support for creating OpenPGP {version} certificates is not yet implemented!"
722        );
723    }
724
725    let public_key = nethsm.get_key(key_id).map_err(to_rpgp_error)?;
726    let signer = HsmKey::new(nethsm, hsm_pk_to_pgp_pk(public_key, created_at)?, key_id);
727
728    let composed_pk = pgp::composed::PublicKey::new(
729        signer.public_key.clone(),
730        pgp::composed::KeyDetails::new(
731            Some(UserId::from_str(Default::default(), user_id.as_ref())?),
732            vec![],
733            vec![],
734            flags.into(),
735            Default::default(),
736            Default::default(),
737            Default::default(),
738            vec![CompressionAlgorithm::Uncompressed].into(),
739            vec![].into(),
740        ),
741        vec![],
742    );
743
744    let signed_pk = composed_pk.sign(
745        rand::thread_rng(),
746        &signer,
747        &signer.public_key,
748        &Password::empty(),
749    )?;
750
751    let mut buffer = vec![];
752    signed_pk.to_writer(&mut buffer)?;
753    Ok(buffer)
754}
755
756/// Converts OpenPGP hash algorithm into an OID form for PKCS#1 signing.
757fn hash_to_oid(hash: HashAlgorithm) -> pgp::errors::Result<AlgorithmIdentifier> {
758    Ok(AlgorithmIdentifier::new_sha(match hash {
759        HashAlgorithm::Sha1 => ShaVariant::SHA1,
760        HashAlgorithm::Sha256 => ShaVariant::SHA2_256,
761        HashAlgorithm::Sha384 => ShaVariant::SHA2_384,
762        HashAlgorithm::Sha512 => ShaVariant::SHA2_512,
763        HashAlgorithm::Sha224 => ShaVariant::SHA2_224,
764        HashAlgorithm::Sha3_256 => ShaVariant::SHA3_256,
765        HashAlgorithm::Sha3_512 => ShaVariant::SHA3_512,
766        _ => {
767            warn!("Unsupported hash algorithm: {hash}");
768            return Err(pgp::errors::Error::InvalidInput {
769                backtrace: Some(Backtrace::capture()),
770            });
771        }
772    }))
773}
774
775/// Converts an OpenPGP Transferable Secret Key into [`PrivateKeyImport`] object.
776///
777/// # Errors
778///
779/// Returns an [`Error::Pgp`] if creating a [`PrivateKeyImport`] from `key_data` is not
780/// possible.
781///
782/// Returns an [`crate::Error::Key`] if `key_data` is an RSA public key and is shorter than
783/// [`crate::MIN_RSA_BIT_LENGTH`].
784pub fn tsk_to_private_key_import(
785    key_data: &[u8],
786) -> Result<(PrivateKeyImport, KeyMechanism), Error> {
787    let key = SignedSecretKey::from_bytes(key_data)?;
788    if !key.secret_subkeys.is_empty() {
789        return Err(Error::UnsupportedMultipleComponentKeys);
790    }
791    let SecretParams::Plain(secret) = key.primary_key.secret_params() else {
792        return Err(Error::PrivateKeyPassphraseProtected);
793    };
794    Ok(match (secret, key.public_key().public_params()) {
795        (PlainSecretParams::RSA(secret), PublicParams::RSA(public)) => {
796            // ensure, that we have sufficient bit length
797            key_type_matches_length(
798                KeyType::Rsa,
799                Some(public.key.n().to_bytes_be().len() as u32 * 8),
800            )
801            .map_err(to_rpgp_error)?;
802
803            let (_d, p, q, _u) = secret.to_bytes();
804
805            (
806                PrivateKeyImport::from_rsa(p, q, public.key.e().to_bytes_be().to_vec()),
807                KeyMechanism::RsaSignaturePkcs1,
808            )
809        }
810        (PlainSecretParams::ECDSA(secret_key), _) => {
811            let ec = if let PublicParams::ECDSA(pp) = key.primary_key.public_key().public_params() {
812                match pp {
813                    EcdsaPublicParams::P256 { .. } => crate::KeyType::EcP256,
814                    EcdsaPublicParams::P384 { .. } => crate::KeyType::EcP384,
815                    EcdsaPublicParams::P521 { .. } => crate::KeyType::EcP521,
816                    pp => {
817                        warn!("Unsupported ECDSA parameters: {pp:?}");
818                        return Err(Error::UnsupportedKeyFormat {
819                            public_params: Box::new(key.public_key().public_params().clone()),
820                        })?;
821                    }
822                }
823            } else {
824                return Err(Error::UnsupportedKeyFormat {
825                    public_params: Box::new(key.public_key().public_params().clone()),
826                });
827            };
828
829            let bytes = match secret_key {
830                pgp::crypto::ecdsa::SecretKey::P256(secret_key) => secret_key.to_bytes().to_vec(),
831                pgp::crypto::ecdsa::SecretKey::P384(secret_key) => secret_key.to_bytes().to_vec(),
832                pgp::crypto::ecdsa::SecretKey::P521(secret_key) => secret_key.to_bytes().to_vec(),
833
834                pgp::crypto::ecdsa::SecretKey::Secp256k1(secret_key) => {
835                    secret_key.to_bytes().to_vec()
836                }
837                secret_key => {
838                    warn!("Unsupported secret key parameters: {secret_key:?}");
839                    return Err(Error::UnsupportedKeyFormat {
840                        public_params: Box::new(key.public_key().public_params().clone()),
841                    })?;
842                }
843            };
844
845            (
846                PrivateKeyImport::from_raw_bytes(ec, bytes).map_err(to_rpgp_error)?,
847                KeyMechanism::EcdsaSignature,
848            )
849        }
850        (PlainSecretParams::Ed25519Legacy(bytes), _) => (
851            PrivateKeyImport::from_raw_bytes(crate::KeyType::Curve25519, bytes.as_bytes())
852                .map_err(to_rpgp_error)?,
853            KeyMechanism::EdDsaSignature,
854        ),
855        (_, public_params) => {
856            return Err(Error::UnsupportedKeyFormat {
857                public_params: Box::new(public_params.clone()),
858            });
859        }
860    })
861}
862
863/// Generates an OpenPGP signature using a given NetHSM key for the message.
864///
865/// Signs the message `message` using the key identified by `key_id`
866/// and returns a binary [OpenPGP data signature].
867///
868/// This call requires using a user in the [`Operator`][`crate::UserRole::Operator`] [role], which
869/// carries a tag (see [`add_user_tag`][`NetHsm::add_user_tag`]) matching one of the tags of
870/// the targeted key (see [`add_key_tag`][`NetHsm::add_key_tag`]).
871///
872/// ## Namespaces
873///
874/// * [`Operator`][`crate::UserRole::Operator`] users in a [namespace] only have access to keys in
875///   their own [namespace].
876/// * System-wide [`Operator`][`crate::UserRole::Operator`] users only have access to system-wide
877///   keys.
878///
879/// # Errors
880///
881/// Returns an [`crate::Error::Api`] if creating an [OpenPGP signature] for the hasher state fails:
882/// * the NetHSM is not in [`Operational`][`crate::SystemState::Operational`] [state]
883/// * no key identified by `key_id` exists on the NetHSM
884/// * the [`Operator`][`crate::UserRole::Operator`] user does not have access to the key (e.g.
885///   different [namespace])
886/// * the [`Operator`][`crate::UserRole::Operator`] user does not carry a tag matching one of the
887///   key tags
888/// * the used [`Credentials`][`crate::Credentials`] are not correct
889/// * the used [`Credentials`][`crate::Credentials`] are not those of a user in the
890///   [`Operator`][`crate::UserRole::Operator`] [role]
891/// * the certificate for a given key has not been generated or is invalid
892/// * subpacket lengths exceed maximum values
893/// * hashing signed data fails
894/// * signature creation using the NetHSM fails
895/// * constructing OpenPGP signature from parts fails
896/// * writing the signature to vector fails
897///
898/// [OpenPGP signature]: https://openpgp.dev/book/signing_data.html
899/// [OpenPGP data signature]: https://openpgp.dev/book/signing_data.html
900/// [namespace]: https://docs.nitrokey.com/nethsm/administration#namespaces
901/// [role]: https://docs.nitrokey.com/nethsm/administration#roles
902/// [state]: https://docs.nitrokey.com/nethsm/administration#state
903pub fn sign(nethsm: &NetHsm, key_id: &crate::KeyId, message: &[u8]) -> Result<Vec<u8>, Error> {
904    let Some(public_key) = nethsm.get_key_certificate(key_id).map_err(to_rpgp_error)? else {
905        return Err(Error::CertificateMissing(key_id.clone()));
906    };
907
908    let signer = HsmKey::new(
909        nethsm,
910        SignedPublicKey::from_bytes(&*public_key)?.primary_key,
911        key_id,
912    );
913
914    let mut sig_config =
915        SignatureConfig::v4(SignatureType::Binary, signer.algorithm(), signer.hash_alg());
916    sig_config.hashed_subpackets = vec![
917        Subpacket::regular(SubpacketData::SignatureCreationTime(
918            std::time::SystemTime::now().into(),
919        ))?,
920        Subpacket::regular(SubpacketData::Issuer(signer.key_id()))?,
921        Subpacket::regular(SubpacketData::IssuerFingerprint(signer.fingerprint()))?,
922    ];
923
924    let mut hasher = sig_config.hash_alg.new_hasher().map_err(to_rpgp_error)?;
925    sig_config.hash_data_to_sign(&mut hasher, message)?;
926
927    let len = sig_config.hash_signature_data(&mut hasher)?;
928
929    hasher.update(&sig_config.trailer(len)?);
930
931    let hash = &hasher.finalize()[..];
932
933    let signed_hash_value = [hash[0], hash[1]];
934    let raw_sig = signer.create_signature(&Password::empty(), sig_config.hash_alg, hash)?;
935
936    let signature = Signature::from_config(sig_config, signed_hash_value, raw_sig)?;
937
938    let mut out = vec![];
939    signature.to_writer_with_header(&mut out)?;
940
941    Ok(out)
942}
943
944/// Provides an adapter bridging two versions of the `digest` crate.
945///
946/// # Note
947///
948/// rPGP uses a different version of the `digest` crate than the latest (as used by e.g.
949/// `signstar-request-signature`). This adapter exposes the old `digest` 0.10 interface for
950/// the [sha2::Sha512] object which uses digest 0.11.
951///
952/// When rPGP updates to digest 0.11 this entire struct can be removed.
953#[derive(Clone, Default)]
954struct Hasher(sha2::Sha512);
955
956impl DynDigest for Hasher {
957    /// Updates the digest with input data.
958    ///
959    /// This method can be called repeatedly for use with streaming messages.
960    fn update(&mut self, data: &[u8]) {
961        self.0.update(data);
962    }
963
964    /// Writes digest into provided buffer `buf` and consumes `self`.
965    ///
966    /// # Errors
967    ///
968    /// Returns an error if the length of `buf` is too small for `self`.
969    fn finalize_into(self, buf: &mut [u8]) -> Result<(), digest::InvalidBufferSize> {
970        sha2::digest::DynDigest::finalize_into(self.0, buf)
971            .map_err(|_| digest::InvalidBufferSize)?;
972        Ok(())
973    }
974
975    /// Writes digest into provided buffer `buf` and resets `self` to an empty hasher.
976    ///
977    /// # Errors
978    ///
979    /// Returns an error if the length of `buf` is too small for `self`.
980    fn finalize_into_reset(&mut self, out: &mut [u8]) -> Result<(), digest::InvalidBufferSize> {
981        sha2::digest::DynDigest::finalize_into_reset(&mut self.0, out)
982            .map_err(|_| digest::InvalidBufferSize)?;
983        Ok(())
984    }
985
986    /// Reset hasher instance to its initial state.
987    fn reset(&mut self) {
988        sha2::digest::DynDigest::reset(&mut self.0)
989    }
990
991    /// Get output size of the hasher
992    fn output_size(&self) -> usize {
993        sha2::digest::DynDigest::output_size(&self.0)
994    }
995
996    /// Clone hasher state into a boxed trait object
997    fn box_clone(&self) -> Box<dyn DynDigest> {
998        Box::new(self.clone())
999    }
1000}
1001
1002/// Generates an armored OpenPGP signature based on provided hasher state.
1003///
1004/// Signs the hasher `state` using the key identified by `key_id`
1005/// and returns a binary [OpenPGP data signature].
1006///
1007/// This call requires using a user in the [`Operator`][`crate::UserRole::Operator`] [role], which
1008/// carries a tag (see [`add_user_tag`][`NetHsm::add_user_tag`]) matching one of the tags of
1009/// the targeted key (see [`add_key_tag`][`NetHsm::add_key_tag`]).
1010///
1011/// ## Namespaces
1012///
1013/// * [`Operator`][`crate::UserRole::Operator`] users in a [namespace] only have access to keys in
1014///   their own [namespace].
1015/// * System-wide [`Operator`][`crate::UserRole::Operator`] users only have access to system-wide
1016///   keys.
1017///
1018/// # Errors
1019///
1020/// Returns an [`crate::Error::Api`] if creating an [OpenPGP signature] for the hasher state fails:
1021/// * the NetHSM is not in [`Operational`][`crate::SystemState::Operational`] [state]
1022/// * no key identified by `key_id` exists on the NetHSM
1023/// * the [`Operator`][`crate::UserRole::Operator`] user does not have access to the key (e.g.
1024///   different [namespace])
1025/// * the [`Operator`][`crate::UserRole::Operator`] user does not carry a tag matching one of the
1026///   key tags
1027/// * the used [`Credentials`][`crate::Credentials`] are not correct
1028/// * the used [`Credentials`][`crate::Credentials`] are not those of a user in the
1029///   [`Operator`][`crate::UserRole::Operator`] [role]
1030///
1031/// [OpenPGP signature]: https://openpgp.dev/book/signing_data.html
1032/// [OpenPGP data signature]: https://openpgp.dev/book/signing_data.html
1033/// [namespace]: https://docs.nitrokey.com/nethsm/administration#namespaces
1034/// [role]: https://docs.nitrokey.com/nethsm/administration#roles
1035/// [state]: https://docs.nitrokey.com/nethsm/administration#state
1036pub fn sign_hasher_state(
1037    nethsm: &NetHsm,
1038    key_id: &crate::KeyId,
1039    state: sha2::Sha512,
1040) -> Result<String, Error> {
1041    let Some(public_key) = nethsm.get_key_certificate(key_id).map_err(to_rpgp_error)? else {
1042        return Err(Error::CertificateMissing(key_id.clone()));
1043    };
1044
1045    let signer = HsmKey::new(
1046        nethsm,
1047        SignedPublicKey::from_bytes(public_key.as_slice())?.primary_key,
1048        key_id,
1049    );
1050
1051    let hasher = state.clone();
1052
1053    let file_hash = Box::new(hasher).finalize().to_vec();
1054
1055    let sig_config = {
1056        let mut sig_config =
1057            SignatureConfig::v4(SignatureType::Binary, signer.algorithm(), signer.hash_alg());
1058        sig_config.hashed_subpackets = vec![
1059            Subpacket::regular(SubpacketData::SignatureCreationTime(
1060                std::time::SystemTime::now().into(),
1061            ))?,
1062            Subpacket::regular(SubpacketData::Issuer(signer.key_id()))?,
1063            Subpacket::regular(SubpacketData::IssuerFingerprint(signer.fingerprint()))?,
1064            Subpacket::regular(SubpacketData::Notation(Notation {
1065                readable: false,
1066                name: "data-digest@archlinux.org".into(),
1067                value: file_hash.into(),
1068            }))?,
1069        ];
1070        sig_config
1071    };
1072
1073    let mut hasher = Box::new(Hasher(state.clone())) as Box<dyn DynDigest + Send>;
1074
1075    let len = sig_config.hash_signature_data(&mut hasher)?;
1076
1077    hasher.update(&sig_config.trailer(len)?);
1078
1079    let hash = &hasher.finalize()[..];
1080
1081    let signed_hash_value = [hash[0], hash[1]];
1082
1083    let raw_sig = signer.create_signature(&Password::empty(), sig_config.hash_alg, hash)?;
1084
1085    let signature = pgp::packet::Signature::from_config(sig_config, signed_hash_value, raw_sig)?;
1086
1087    let signature = StandaloneSignature { signature };
1088    Ok(signature.to_armored_string(ArmorOptions::default())?)
1089}
1090
1091/// Creates a [`PublicKey`] object from ECDSA parameters.
1092///
1093/// Takes a `created_at` date and ECDSA `key` parameters.
1094///
1095/// # Errors
1096///
1097/// Returns an error if
1098///
1099/// - the ECDSA algorithm is unsupported by rPGP,
1100/// - or the calculated packet length is invalid.
1101fn ecdsa_to_public_key(
1102    created_at: DateTime<Utc>,
1103    key: EcdsaPublicParams,
1104) -> Result<PublicKey, Error> {
1105    Ok(PublicKey::from_inner(PubKeyInner::new(
1106        KeyVersion::V4,
1107        PublicKeyAlgorithm::ECDSA,
1108        created_at,
1109        None,
1110        PublicParams::ECDSA(key),
1111    )?)?)
1112}
1113
1114/// Converts base64-encoded data into a vector of bytes.
1115///
1116/// # Errors
1117///
1118/// Returns an error if
1119///
1120/// - `data` is [`None`],
1121/// - or `data` provides invalid base64 encoding.
1122fn data_to_bytes(data: Option<&str>) -> Result<Vec<u8>, Error> {
1123    Ok(Base64::decode_vec(data.ok_or(Error::KeyData(
1124        "missing EC public key data".into(),
1125    ))?)?)
1126}
1127
1128/// Converts NetHSM public key to OpenPGP public key.
1129///
1130/// Since OpenPGP public keys have a date of creation (which is used
1131/// for fingerprint calculation) this is an additional, explicit
1132/// parameter.
1133fn hsm_pk_to_pgp_pk(
1134    pk: nethsm_sdk_rs::models::PublicKey,
1135    created_at: DateTime<Utc>,
1136) -> Result<PublicKey, Error> {
1137    let public = &pk
1138        .public
1139        .ok_or(Error::KeyData("missing public key data".into()))?;
1140
1141    let key_type: KeyType = pk.r#type.into();
1142    Ok(match key_type {
1143        KeyType::Rsa => PublicKey::from_inner(PubKeyInner::new(
1144            KeyVersion::V4,
1145            PublicKeyAlgorithm::RSA,
1146            created_at,
1147            None,
1148            PublicParams::RSA(RsaPublicParams {
1149                key: rsa::RsaPublicKey::new(
1150                    BigUint::from_bytes_be(&Base64::decode_vec(
1151                        public
1152                            .modulus
1153                            .as_ref()
1154                            .ok_or(Error::KeyData("missing RSA modulus".into()))?,
1155                    )?),
1156                    BigUint::from_bytes_be(&Base64::decode_vec(
1157                        public
1158                            .public_exponent
1159                            .as_ref()
1160                            .ok_or(Error::KeyData("missing RSA exponent".into()))?,
1161                    )?),
1162                )
1163                .map_err(to_rpgp_error)?,
1164            }),
1165        )?)?,
1166
1167        KeyType::Curve25519 => {
1168            let pubkey: &[u8] = &data_to_bytes(public.data.as_deref())?;
1169
1170            PublicKey::from_inner(PubKeyInner::new(
1171                KeyVersion::V4,
1172                PublicKeyAlgorithm::EdDSALegacy,
1173                created_at,
1174                None,
1175                PublicParams::EdDSALegacy(pgp::types::EddsaLegacyPublicParams::Ed25519 {
1176                    key: VerifyingKey::from_bytes(pubkey.try_into().map_err(to_rpgp_error)?)
1177                        .map_err(to_rpgp_error)?,
1178                }),
1179            )?)?
1180        }
1181
1182        KeyType::EcP256 => ecdsa_to_public_key(
1183            created_at,
1184            EcdsaPublicParams::P256 {
1185                key: p256::PublicKey::from_sec1_bytes(&data_to_bytes(public.data.as_deref())?)?,
1186            },
1187        )?,
1188        KeyType::EcP384 => ecdsa_to_public_key(
1189            created_at,
1190            EcdsaPublicParams::P384 {
1191                key: p384::PublicKey::from_sec1_bytes(&data_to_bytes(public.data.as_deref())?)?,
1192            },
1193        )?,
1194        KeyType::EcP521 => ecdsa_to_public_key(
1195            created_at,
1196            EcdsaPublicParams::P521 {
1197                key: p521::PublicKey::from_sec1_bytes(&data_to_bytes(public.data.as_deref())?)?,
1198            },
1199        )?,
1200
1201        _ => {
1202            warn!("Unsupported key type: {key_type}");
1203            return Err(pgp::errors::Error::InvalidInput {
1204                backtrace: Some(Backtrace::capture()),
1205            })?;
1206        }
1207    })
1208}
1209
1210/// Extracts certificate (public key) from an OpenPGP TSK.
1211///
1212/// # Errors
1213///
1214/// Returns an error if
1215///
1216/// - a secret key cannot be decoded from `key_data`,
1217/// - or writing a serialized certificate into a vector fails.
1218pub fn extract_certificate(key_data: &[u8]) -> Result<Vec<u8>, Error> {
1219    let key = SignedSecretKey::from_bytes(key_data)?;
1220    let public: SignedPublicKey = key.into();
1221    let mut buffer = vec![];
1222    public.to_writer(&mut buffer)?;
1223    Ok(buffer)
1224}
1225
1226/// Key usage flags that can be set on the generated certificate.
1227#[derive(Debug, Default)]
1228pub struct KeyUsageFlags(KeyFlags);
1229
1230impl KeyUsageFlags {
1231    /// Makes it possible for this key to issue data signatures.
1232    pub fn set_sign(&mut self) {
1233        self.0.set_sign(true);
1234    }
1235
1236    /// Makes it impossible for this key to issue data signatures.
1237    pub fn clear_sign(&mut self) {
1238        self.0.set_sign(false);
1239    }
1240}
1241
1242impl AsRef<KeyFlags> for KeyUsageFlags {
1243    fn as_ref(&self) -> &KeyFlags {
1244        &self.0
1245    }
1246}
1247
1248impl From<KeyUsageFlags> for KeyFlags {
1249    fn from(value: KeyUsageFlags) -> Self {
1250        value.0
1251    }
1252}
1253
1254#[cfg(test)]
1255mod tests {
1256    use nethsm_sdk_rs::models::{KeyMechanism, KeyPublicData, KeyRestrictions, KeyType};
1257    use pgp::types::{EcdsaPublicParams, PublicParams};
1258    use rstest::rstest;
1259    use testresult::TestResult;
1260
1261    use super::*;
1262
1263    #[test]
1264    fn convert_ed25519_to_pgp() -> TestResult {
1265        let hsm_key = nethsm_sdk_rs::models::PublicKey {
1266            mechanisms: vec![KeyMechanism::EdDsaSignature],
1267            r#type: KeyType::Curve25519,
1268            restrictions: Box::new(KeyRestrictions {
1269                tags: Some(vec!["signing1".into()]),
1270            }),
1271            public: Some(Box::new(KeyPublicData {
1272                modulus: None,
1273                public_exponent: None,
1274                data: Some("/ODoaDzX9xDjpx2LfR0DCIgdxqOndY9tukEFLVCObQo=".into()),
1275            })),
1276            operations: 1,
1277        };
1278
1279        let pgp_key = hsm_pk_to_pgp_pk(hsm_key, DateTime::UNIX_EPOCH)?;
1280        let PublicParams::EdDSALegacy(pgp::types::EddsaLegacyPublicParams::Ed25519 { key }) =
1281            pgp_key.public_params()
1282        else {
1283            panic!("Wrong type of public params");
1284        };
1285        assert_eq!(
1286            key.to_bytes(),
1287            [
1288                252, 224, 232, 104, 60, 215, 247, 16, 227, 167, 29, 139, 125, 29, 3, 8, 136, 29,
1289                198, 163, 167, 117, 143, 109, 186, 65, 5, 45, 80, 142, 109, 10
1290            ]
1291        );
1292
1293        Ok(())
1294    }
1295
1296    #[test]
1297    fn convert_p256_to_pgp() -> TestResult {
1298        let hsm_key = nethsm_sdk_rs::models::PublicKey {
1299            mechanisms: vec![KeyMechanism::EcdsaSignature],
1300            r#type: KeyType::EcP256,
1301            restrictions: Box::new(KeyRestrictions {
1302                tags: Some(vec!["signing2".into()]),
1303            }),
1304            public: Some(Box::new(KeyPublicData {
1305                modulus: None,
1306                public_exponent: None,
1307                data: Some(
1308                    "BN5q7GCR8w1RtXdMBR1IcIaCqbbn92vM5LItTcRbdXo5RfDwhnKK6D8tjWakqXbWY9eKelkCtALtD/hoU44WuYU="
1309                        .into(),
1310                ),
1311            })),
1312            operations: 1,
1313        };
1314        let pgp_key = hsm_pk_to_pgp_pk(hsm_key, DateTime::UNIX_EPOCH)?;
1315        let PublicParams::ECDSA(EcdsaPublicParams::P256 { key, .. }) = pgp_key.public_params()
1316        else {
1317            panic!("Wrong type of public params");
1318        };
1319        assert_eq!(
1320            key.to_sec1_bytes().to_vec(),
1321            [
1322                4, 222, 106, 236, 96, 145, 243, 13, 81, 181, 119, 76, 5, 29, 72, 112, 134, 130,
1323                169, 182, 231, 247, 107, 204, 228, 178, 45, 77, 196, 91, 117, 122, 57, 69, 240,
1324                240, 134, 114, 138, 232, 63, 45, 141, 102, 164, 169, 118, 214, 99, 215, 138, 122,
1325                89, 2, 180, 2, 237, 15, 248, 104, 83, 142, 22, 185, 133
1326            ]
1327        );
1328
1329        Ok(())
1330    }
1331
1332    #[test]
1333    fn convert_p384_to_pgp() -> TestResult {
1334        let hsm_key = nethsm_sdk_rs::models::PublicKey {
1335            mechanisms: vec![KeyMechanism::EcdsaSignature],
1336            r#type: KeyType::EcP384,
1337            restrictions: Box::new(KeyRestrictions {
1338                tags: Some(vec!["signing2".into()]),
1339            }),
1340            public: Some(Box::new(KeyPublicData {
1341                modulus: None,
1342                public_exponent: None,
1343                data: Some(
1344                    "BH+Ik2+7v4NUpnZDTGs0jq9I+kDFTJqiMNOHP5k81agoKW8ICEJ13aL06dLNzkZAdB5iulgRCEuX/Htitii3BhxuHTUPWuN0uVKGhgYRddpTteaaauv0cOPni9la3O+/lA=="
1345                        .into(),
1346                ),
1347            })),
1348            operations: 3,
1349        };
1350        let pgp_key = hsm_pk_to_pgp_pk(hsm_key, DateTime::UNIX_EPOCH)?;
1351        let PublicParams::ECDSA(EcdsaPublicParams::P384 { key, .. }) = pgp_key.public_params()
1352        else {
1353            panic!("Wrong type of public params");
1354        };
1355        assert_eq!(
1356            key.to_sec1_bytes().to_vec(),
1357            [
1358                4, 127, 136, 147, 111, 187, 191, 131, 84, 166, 118, 67, 76, 107, 52, 142, 175, 72,
1359                250, 64, 197, 76, 154, 162, 48, 211, 135, 63, 153, 60, 213, 168, 40, 41, 111, 8, 8,
1360                66, 117, 221, 162, 244, 233, 210, 205, 206, 70, 64, 116, 30, 98, 186, 88, 17, 8,
1361                75, 151, 252, 123, 98, 182, 40, 183, 6, 28, 110, 29, 53, 15, 90, 227, 116, 185, 82,
1362                134, 134, 6, 17, 117, 218, 83, 181, 230, 154, 106, 235, 244, 112, 227, 231, 139,
1363                217, 90, 220, 239, 191, 148
1364            ]
1365        );
1366
1367        Ok(())
1368    }
1369
1370    #[test]
1371    fn convert_p521_to_pgp() -> TestResult {
1372        let hsm_key = nethsm_sdk_rs::models::PublicKey {
1373            mechanisms: vec![KeyMechanism::EcdsaSignature],
1374            r#type: KeyType::EcP521,
1375            restrictions: Box::new(KeyRestrictions {
1376                tags: Some(vec!["signing2".into()]),
1377            }),
1378            public: Some(Box::new(KeyPublicData {
1379                modulus: None,
1380                public_exponent: None,
1381                data: Some(
1382                    "BAEhJ8HuyTN/DBjAoXD3H7jTdl+TwOwJ3taKwq2q+HsBislgZjeg1JZlOus1Mh4viKv0iuwaviid0D9cqsO2UHLN/QHTWGbzQw6fLiNZvCaGuNDf1c5+aiFMxvAgbDB8qp4eBAsl6f6ro5kKQXbpT7NauRVHYxUv32TgxG5mcRpnf+ovUQ=="
1383                        .into(),
1384                ),
1385            })),
1386            operations: 2,
1387        };
1388        let pgp_key = hsm_pk_to_pgp_pk(hsm_key, DateTime::UNIX_EPOCH)?;
1389        let PublicParams::ECDSA(EcdsaPublicParams::P521 { key, .. }) = pgp_key.public_params()
1390        else {
1391            panic!("Wrong type of public params");
1392        };
1393        assert_eq!(
1394            key.to_sec1_bytes().to_vec(),
1395            [
1396                4, 1, 33, 39, 193, 238, 201, 51, 127, 12, 24, 192, 161, 112, 247, 31, 184, 211,
1397                118, 95, 147, 192, 236, 9, 222, 214, 138, 194, 173, 170, 248, 123, 1, 138, 201, 96,
1398                102, 55, 160, 212, 150, 101, 58, 235, 53, 50, 30, 47, 136, 171, 244, 138, 236, 26,
1399                190, 40, 157, 208, 63, 92, 170, 195, 182, 80, 114, 205, 253, 1, 211, 88, 102, 243,
1400                67, 14, 159, 46, 35, 89, 188, 38, 134, 184, 208, 223, 213, 206, 126, 106, 33, 76,
1401                198, 240, 32, 108, 48, 124, 170, 158, 30, 4, 11, 37, 233, 254, 171, 163, 153, 10,
1402                65, 118, 233, 79, 179, 90, 185, 21, 71, 99, 21, 47, 223, 100, 224, 196, 110, 102,
1403                113, 26, 103, 127, 234, 47, 81
1404            ]
1405        );
1406
1407        Ok(())
1408    }
1409
1410    #[test]
1411    fn convert_rsa_to_pgp() -> TestResult {
1412        let hsm_key = nethsm_sdk_rs::models::PublicKey {
1413            mechanisms: vec![KeyMechanism::RsaSignaturePkcs1],
1414            r#type: KeyType::Rsa,
1415            restrictions: Box::new(KeyRestrictions {
1416                tags: Some(vec!["signing8".into()]) }),
1417                public: Some(Box::new(KeyPublicData {
1418                    modulus: Some("4386l1aC1e4N93rxM+Npj+dy0CGY0W3PNbOTBGRj7tTEflkEl2qx2xW7kyme8sLQQ/yxhyJ4mqo/ggR9ODfvYytzxsS/n/MNZwdATGC4QDBjPv74s/51nC/gZHq9VzvYq3bmF0e0WNiXRT3p53Zofmv1CBDPBEDrrJq3Mq+O3+TH8/ur3OOMgvNx2CDgwwQ1WGSW3XITN9ekZpoj/h8cwxFkz5ljmygCLRtXdNWrzVJGW3G5L/Jz9sdSfE2tyb8+312IVFJ57zcvRygqAkkS11uYIPxuoabT6IJ8SpScfqltGsU3jiALKyFRV58I91KUlXegjUVR31ExFc0eADuhuw==".into()),
1419                    public_exponent: Some("AQAB".into()),
1420                    data: None })),
1421            operations: 2 };
1422        let pgp_key = hsm_pk_to_pgp_pk(hsm_key, DateTime::UNIX_EPOCH)?;
1423        let PublicParams::RSA(public) = pgp_key.public_params() else {
1424            panic!("Wrong type of public params");
1425        };
1426        assert_eq!(public.key.e().to_bytes_be(), [1, 0, 1]);
1427        assert_eq!(
1428            public.key.n().to_bytes_be(),
1429            [
1430                227, 127, 58, 151, 86, 130, 213, 238, 13, 247, 122, 241, 51, 227, 105, 143, 231,
1431                114, 208, 33, 152, 209, 109, 207, 53, 179, 147, 4, 100, 99, 238, 212, 196, 126, 89,
1432                4, 151, 106, 177, 219, 21, 187, 147, 41, 158, 242, 194, 208, 67, 252, 177, 135, 34,
1433                120, 154, 170, 63, 130, 4, 125, 56, 55, 239, 99, 43, 115, 198, 196, 191, 159, 243,
1434                13, 103, 7, 64, 76, 96, 184, 64, 48, 99, 62, 254, 248, 179, 254, 117, 156, 47, 224,
1435                100, 122, 189, 87, 59, 216, 171, 118, 230, 23, 71, 180, 88, 216, 151, 69, 61, 233,
1436                231, 118, 104, 126, 107, 245, 8, 16, 207, 4, 64, 235, 172, 154, 183, 50, 175, 142,
1437                223, 228, 199, 243, 251, 171, 220, 227, 140, 130, 243, 113, 216, 32, 224, 195, 4,
1438                53, 88, 100, 150, 221, 114, 19, 55, 215, 164, 102, 154, 35, 254, 31, 28, 195, 17,
1439                100, 207, 153, 99, 155, 40, 2, 45, 27, 87, 116, 213, 171, 205, 82, 70, 91, 113,
1440                185, 47, 242, 115, 246, 199, 82, 124, 77, 173, 201, 191, 62, 223, 93, 136, 84, 82,
1441                121, 239, 55, 47, 71, 40, 42, 2, 73, 18, 215, 91, 152, 32, 252, 110, 161, 166, 211,
1442                232, 130, 124, 74, 148, 156, 126, 169, 109, 26, 197, 55, 142, 32, 11, 43, 33, 81,
1443                87, 159, 8, 247, 82, 148, 149, 119, 160, 141, 69, 81, 223, 81, 49, 21, 205, 30, 0,
1444                59, 161, 187
1445            ]
1446        );
1447
1448        Ok(())
1449    }
1450
1451    #[test]
1452    fn parse_rsa_signature_produces_valid_data() -> TestResult {
1453        let sig = parse_signature(crate::SignatureType::Pkcs1, &[0, 1, 2])?;
1454        assert_eq!(sig.len(), 1);
1455        assert_eq!(&sig[0].as_ref(), &[1, 2]);
1456
1457        Ok(())
1458    }
1459
1460    #[test]
1461    fn parse_ed25519_signature_produces_valid_data() -> TestResult {
1462        let sig = parse_signature(
1463            crate::SignatureType::EdDsa,
1464            &[
1465                2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
1466                2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1467                1, 1, 1, 1, 1, 1, 1, 1,
1468            ],
1469        )?;
1470        assert_eq!(sig.len(), 2);
1471        assert_eq!(sig[0].as_ref(), vec![2; 32]);
1472        assert_eq!(sig[1].as_ref(), vec![1; 32]);
1473
1474        Ok(())
1475    }
1476
1477    #[test]
1478    fn parse_p256_signature_produces_valid_data() -> TestResult {
1479        let sig = parse_signature(
1480            crate::SignatureType::EcdsaP256,
1481            &[
1482                48, 70, 2, 33, 0, 193, 176, 219, 0, 133, 254, 212, 239, 236, 122, 85, 239, 73, 161,
1483                179, 53, 100, 172, 103, 45, 123, 21, 169, 28, 59, 150, 72, 92, 242, 9, 53, 143, 2,
1484                33, 0, 165, 1, 144, 97, 102, 109, 66, 50, 185, 234, 211, 150, 253, 228, 210, 126,
1485                26, 0, 189, 184, 230, 163, 36, 203, 232, 161, 12, 75, 121, 171, 45, 107,
1486            ],
1487        )?;
1488        assert_eq!(sig.len(), 2);
1489        assert_eq!(
1490            sig[0].as_ref(),
1491            [
1492                193, 176, 219, 0, 133, 254, 212, 239, 236, 122, 85, 239, 73, 161, 179, 53, 100,
1493                172, 103, 45, 123, 21, 169, 28, 59, 150, 72, 92, 242, 9, 53, 143
1494            ]
1495        );
1496        assert_eq!(
1497            sig[1].as_ref(),
1498            [
1499                165, 1, 144, 97, 102, 109, 66, 50, 185, 234, 211, 150, 253, 228, 210, 126, 26, 0,
1500                189, 184, 230, 163, 36, 203, 232, 161, 12, 75, 121, 171, 45, 107
1501            ]
1502        );
1503
1504        Ok(())
1505    }
1506
1507    #[test]
1508    fn parse_p384_signature_produces_valid_data() -> TestResult {
1509        let sig = parse_signature(
1510            crate::SignatureType::EcdsaP384,
1511            &[
1512                48, 101, 2, 49, 0, 134, 13, 108, 74, 135, 234, 174, 105, 208, 46, 109, 18, 77, 21,
1513                177, 59, 73, 150, 228, 26, 244, 134, 187, 217, 172, 34, 2, 1, 229, 123, 105, 202,
1514                132, 233, 72, 41, 243, 138, 127, 107, 135, 95, 139, 19, 121, 179, 170, 27, 2, 48,
1515                44, 80, 117, 90, 18, 137, 36, 190, 8, 60, 201, 235, 242, 168, 164, 245, 119, 136,
1516                207, 178, 237, 64, 117, 69, 218, 189, 209, 110, 2, 9, 191, 194, 70, 50, 227, 47, 6,
1517                34, 8, 135, 43, 188, 236, 192, 184, 227, 59, 40,
1518            ],
1519        )?;
1520        assert_eq!(sig.len(), 2);
1521        assert_eq!(
1522            sig[0].as_ref(),
1523            [
1524                134, 13, 108, 74, 135, 234, 174, 105, 208, 46, 109, 18, 77, 21, 177, 59, 73, 150,
1525                228, 26, 244, 134, 187, 217, 172, 34, 2, 1, 229, 123, 105, 202, 132, 233, 72, 41,
1526                243, 138, 127, 107, 135, 95, 139, 19, 121, 179, 170, 27
1527            ]
1528        );
1529        assert_eq!(
1530            sig[1].as_ref(),
1531            [
1532                44, 80, 117, 90, 18, 137, 36, 190, 8, 60, 201, 235, 242, 168, 164, 245, 119, 136,
1533                207, 178, 237, 64, 117, 69, 218, 189, 209, 110, 2, 9, 191, 194, 70, 50, 227, 47, 6,
1534                34, 8, 135, 43, 188, 236, 192, 184, 227, 59, 40
1535            ]
1536        );
1537
1538        Ok(())
1539    }
1540
1541    #[test]
1542    fn parse_p521_signature_produces_valid_data() -> TestResult {
1543        let sig = parse_signature(
1544            crate::SignatureType::EcdsaP521,
1545            &[
1546                48, 129, 136, 2, 66, 0, 203, 246, 21, 57, 217, 6, 101, 73, 103, 113, 98, 39, 223,
1547                246, 199, 136, 238, 213, 134, 163, 153, 151, 116, 237, 207, 181, 107, 183, 204,
1548                110, 97, 160, 95, 160, 193, 3, 219, 46, 105, 191, 0, 139, 124, 234, 90, 125, 114,
1549                115, 205, 109, 15, 193, 166, 100, 224, 108, 87, 143, 240, 65, 41, 93, 164, 166, 2,
1550                2, 66, 1, 203, 115, 121, 219, 49, 18, 3, 101, 130, 153, 95, 80, 27, 148, 249, 221,
1551                198, 251, 149, 118, 119, 32, 44, 160, 24, 125, 72, 161, 168, 71, 48, 138, 223, 200,
1552                37, 124, 234, 17, 237, 246, 13, 123, 102, 151, 83, 95, 186, 161, 112, 41, 158, 138,
1553                144, 55, 23, 110, 100, 185, 237, 13, 174, 83, 4, 153, 34,
1554            ],
1555        )?;
1556        assert_eq!(sig.len(), 2);
1557        assert_eq!(
1558            sig[0].as_ref(),
1559            [
1560                203, 246, 21, 57, 217, 6, 101, 73, 103, 113, 98, 39, 223, 246, 199, 136, 238, 213,
1561                134, 163, 153, 151, 116, 237, 207, 181, 107, 183, 204, 110, 97, 160, 95, 160, 193,
1562                3, 219, 46, 105, 191, 0, 139, 124, 234, 90, 125, 114, 115, 205, 109, 15, 193, 166,
1563                100, 224, 108, 87, 143, 240, 65, 41, 93, 164, 166, 2
1564            ]
1565        );
1566        assert_eq!(
1567            sig[1].as_ref(),
1568            [
1569                1, 203, 115, 121, 219, 49, 18, 3, 101, 130, 153, 95, 80, 27, 148, 249, 221, 198,
1570                251, 149, 118, 119, 32, 44, 160, 24, 125, 72, 161, 168, 71, 48, 138, 223, 200, 37,
1571                124, 234, 17, 237, 246, 13, 123, 102, 151, 83, 95, 186, 161, 112, 41, 158, 138,
1572                144, 55, 23, 110, 100, 185, 237, 13, 174, 83, 4, 153, 34
1573            ]
1574        );
1575
1576        Ok(())
1577    }
1578
1579    #[test]
1580    fn private_key_import_ed25199_is_correctly_zero_padded() -> TestResult {
1581        let mut key_data = vec![];
1582        SignedSecretKey::from_armor_single(std::fs::File::open(
1583            "tests/fixtures/ed25519-key-with-31-byte-private-key-scalar.asc",
1584        )?)?
1585        .0
1586        .to_writer(&mut key_data)?;
1587
1588        let import: nethsm_sdk_rs::models::KeyPrivateData =
1589            tsk_to_private_key_import(&key_data)?.0.into();
1590
1591        let data = Base64::decode_vec(&import.data.unwrap())?;
1592
1593        // data needs to be zero-padded for NetHSM import even if the
1594        // input is *not* zero-padded
1595        assert_eq!(data.len(), 32);
1596        assert_eq!(data[0], 0x00);
1597
1598        Ok(())
1599    }
1600
1601    #[test]
1602    fn private_key_import_rsa_key_with_nonstandard_moduli_is_read_correctly() -> TestResult {
1603        let mut key_data = vec![];
1604        SignedSecretKey::from_armor_single(std::fs::File::open(
1605            "tests/fixtures/rsa-key-with-modulus-e-257.asc",
1606        )?)?
1607        .0
1608        .to_writer(&mut key_data)?;
1609
1610        let import: nethsm_sdk_rs::models::KeyPrivateData =
1611            tsk_to_private_key_import(&key_data)?.0.into();
1612
1613        let data = Base64::decode_vec(&import.public_exponent.unwrap())?;
1614
1615        // this key used a non-standard modulus (e) of 257
1616        assert_eq!(data, vec![0x01, 0x01]); // 257 in hex
1617
1618        Ok(())
1619    }
1620
1621    #[test]
1622    fn rsa_digest_info_is_wrapped() -> TestResult {
1623        let data = prepare_digest_data(crate::SignatureType::Pkcs1, HashAlgorithm::Sha1, &[0; 20])?;
1624
1625        assert_eq!(
1626            data,
1627            vec![
1628                48, 33, 48, 9, 6, 5, 43, 14, 3, 2, 26, 5, 0, 4, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1629                0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1630            ]
1631        );
1632
1633        Ok(())
1634    }
1635
1636    #[rstest]
1637    #[case(crate::SignatureType::EcdsaP224, 28)]
1638    #[case(crate::SignatureType::EcdsaP256, 32)]
1639    #[case(crate::SignatureType::EcdsaP384, 48)]
1640    #[case(crate::SignatureType::EcdsaP521, 64)]
1641    fn ecdsa_wrapped_up_to_max_len(
1642        #[case] sig_type: crate::SignatureType,
1643        #[case] max_len: usize,
1644        #[values(HashAlgorithm::Sha1, HashAlgorithm::Sha256, HashAlgorithm::Sha512)]
1645        hash_algo: HashAlgorithm,
1646    ) -> TestResult {
1647        // the digest value is irrelevant - just the size of the digest
1648        let digest = hash_algo.new_hasher()?.finalize();
1649        let data = prepare_digest_data(sig_type, hash_algo, &digest)?;
1650
1651        // The data to be signed size needs to be truncated to the value specific the the curve
1652        // being used. If the digest is short enough to be smaller than the curve specific field
1653        // size the digest is used as a whole.
1654        assert_eq!(
1655            data.len(),
1656            usize::min(max_len, digest.len()),
1657            "the data to be signed's length ({}) cannot exceed maximum length imposed by the curve ({})",
1658            data.len(),
1659            max_len
1660        );
1661
1662        Ok(())
1663    }
1664
1665    #[rstest]
1666    fn eddsa_is_not_wrapped(
1667        #[values(HashAlgorithm::Sha1, HashAlgorithm::Sha256, HashAlgorithm::Sha512)]
1668        hash_algo: HashAlgorithm,
1669    ) -> TestResult {
1670        // the digest value is irrelevant - just the size of the digest
1671        let digest = &hash_algo.new_hasher()?.finalize()[..];
1672
1673        let data = prepare_digest_data(crate::SignatureType::EdDsa, hash_algo, digest)?;
1674
1675        assert_eq!(data, digest);
1676
1677        Ok(())
1678    }
1679}