nethsm/
signer.rs

1//! OpenPGP related signing facilities for NetHSM.
2
3use std::borrow::Cow;
4
5use base64ct::{Base64, Encoding as _};
6use log::{error, warn};
7use nethsm_sdk_rs::models::KeyType;
8use picky_asn1_x509::{
9    AlgorithmIdentifier,
10    DigestInfo,
11    ShaVariant,
12    signature::EcdsaSignatureValue,
13};
14use signstar_crypto::signer::{
15    error::Error,
16    traits::{RawPublicKey, RawSigningKey},
17};
18
19use crate::{KeyId, NetHsm, SignatureType};
20
21/// Access to signature creation with a specific key in a [`NetHsm`].
22///
23/// Tracks a [`SignatureType`], which defines the type of signature that is created when using a key
24/// identified by [`KeyId`] on a [`NetHsm`].
25///
26/// For owned access see [`OwnedNetHsmKey`].
27#[derive(Debug)]
28pub struct NetHsmKey<'a, 'b> {
29    signature_type: SignatureType,
30    nethsm: &'a NetHsm,
31    key_id: &'b KeyId,
32}
33
34/// Returns a [`SignatureType`] for a [`KeyType`].
35///
36/// Reflects the specific capabilities of a NetHSM backend and only returns a [`SignatureType`] for
37/// a supported `key_type`.
38///
39/// # Errors
40///
41/// Returns an error if the key type is unsupported. This includes [`KeyType::EcP224`] and
42/// [`KeyType::Generic`].
43pub(crate) fn nethsm_signature_type(key_type: KeyType) -> Result<SignatureType, crate::Error> {
44    Ok(match key_type {
45        KeyType::Rsa => SignatureType::Pkcs1,
46        KeyType::Curve25519 => SignatureType::EdDsa,
47        KeyType::EcP224 => {
48            return Err(crate::Error::Default(
49                "P-224 keys are unsupported by the NetHSM".into(),
50            ));
51        }
52        KeyType::EcP256 => SignatureType::EcdsaP256,
53        KeyType::EcP384 => SignatureType::EcdsaP384,
54        KeyType::EcP521 => SignatureType::EcdsaP521,
55        KeyType::Generic => {
56            return Err(crate::Error::Default(
57                "Generic keys cannot be used to sign OpenPGP data".into(),
58            ));
59        }
60    })
61}
62
63impl<'a, 'b> NetHsmKey<'a, 'b> {
64    /// Creates a new remote signing key which will use `key_id` key for signing.
65    ///
66    /// # Errors
67    ///
68    /// Returns an error if no key can be retrieved from `nethsm` using `key_id`.
69    pub fn new(nethsm: &'a NetHsm, key_id: &'b KeyId) -> Result<Self, crate::Error> {
70        let pk = nethsm.get_key(key_id)?;
71        let signature_type = nethsm_signature_type(pk.r#type)?;
72
73        Ok(Self {
74            nethsm,
75            signature_type,
76            key_id,
77        })
78    }
79}
80
81/// Converts base64-encoded EC public key data into a vector of bytes.
82///
83/// # Errors
84///
85/// Returns an error if
86///
87/// - `data` is [`None`],
88/// - or `data` provides invalid base64 encoding.
89fn ec_public_key_data_to_bytes(data: Option<&str>) -> Result<Vec<u8>, Error> {
90    Base64::decode_vec(data.ok_or(Error::InvalidPublicKeyData {
91        context: "EC public key data is missing".into(),
92    })?)
93    .map_err(|e| Error::Hsm {
94        context: "deserializing EC data",
95        source: Box::new(e),
96    })
97}
98
99impl RawSigningKey for NetHsmKey<'_, '_> {
100    fn key_id(&self) -> String {
101        self.key_id.to_string()
102    }
103
104    fn sign(&self, digest: &[u8]) -> Result<Vec<Vec<u8>>, Error> {
105        let hash = AlgorithmIdentifier::new_sha(ShaVariant::SHA2_512);
106        let request_data = prepare_digest_data_for_openpgp(self.signature_type, hash, digest)?;
107
108        let sig = self
109            .nethsm
110            .sign_digest(self.key_id, self.signature_type, &request_data)
111            .map_err(|e| {
112                error!("NetHsm::sign_digest failed: {e:?}");
113                Error::Hsm {
114                    context: "executing NetHsm::sign_digest",
115                    source: e.into(),
116                }
117            })?;
118
119        raw_signature_to_mpis(self.signature_type, &sig)
120    }
121
122    fn certificate(&self) -> Result<Option<Vec<u8>>, Error> {
123        self.nethsm
124            .get_key_certificate(self.key_id)
125            .map_err(|e| Error::Hsm {
126                context: "executing NetHsm::get_key_certificate",
127                source: e.into(),
128            })
129    }
130
131    fn public(&self) -> Result<RawPublicKey, Error> {
132        let pk = self.nethsm.get_key(self.key_id).map_err(|e| Error::Hsm {
133            context: "executing NetHsm::get_key",
134            source: e.into(),
135        })?;
136
137        let public = &pk.public.ok_or(Error::InvalidPublicKeyData {
138            context: "public key data is missing".into(),
139        })?;
140
141        let key_type: KeyType = pk.r#type;
142        Ok(match key_type {
143            KeyType::Rsa => RawPublicKey::Rsa {
144                modulus: Base64::decode_vec(public.modulus.as_ref().ok_or(
145                    Error::InvalidPublicKeyData {
146                        context: "RSA modulus is missing".into(),
147                    },
148                )?)
149                .map_err(|e| Error::Hsm {
150                    context: "deserializing modulus",
151                    source: Box::new(e),
152                })?,
153                exponent: Base64::decode_vec(public.public_exponent.as_ref().ok_or(
154                    Error::InvalidPublicKeyData {
155                        context: "RSA exponent is missing".into(),
156                    },
157                )?)
158                .map_err(|e| Error::Hsm {
159                    context: "deserializing exponent",
160                    source: Box::new(e),
161                })?,
162            },
163            KeyType::Curve25519 => {
164                RawPublicKey::Ed25519(ec_public_key_data_to_bytes(public.data.as_deref())?)
165            }
166            KeyType::EcP256 => {
167                RawPublicKey::P256(ec_public_key_data_to_bytes(public.data.as_deref())?)
168            }
169            KeyType::EcP384 => {
170                RawPublicKey::P384(ec_public_key_data_to_bytes(public.data.as_deref())?)
171            }
172            KeyType::EcP521 => {
173                RawPublicKey::P521(ec_public_key_data_to_bytes(public.data.as_deref())?)
174            }
175            KeyType::EcP224 | KeyType::Generic => {
176                warn!("Unsupported key type: {key_type}");
177                return Err(Error::InvalidPublicKeyData {
178                    context: format!("Unsupported key type: {key_type}"),
179                });
180            }
181        })
182    }
183}
184
185/// Owned access to signature creation with a specific key in a [`NetHsm`].
186///
187/// Tracks a [`SignatureType`], which defines the type of signature that is created when using a key
188/// identified by [`KeyId`] on a [`NetHsm`].
189///
190/// For reference access see [`NetHsmKey`].
191#[derive(Debug)]
192pub struct OwnedNetHsmKey {
193    signature_type: SignatureType,
194    nethsm: NetHsm,
195    key_id: KeyId,
196}
197
198impl OwnedNetHsmKey {
199    /// Creates a new [`OwnedNetHsmKey`].
200    ///
201    /// This remote signing key relies on a backend key accessible via `key_id` for signing.
202    ///
203    /// # Errors
204    ///
205    /// Returns an error if
206    ///
207    /// - retrieving raw signing key from NetHSM fails
208    /// - signing mode of the key is unsupported
209    pub fn new(nethsm: NetHsm, key_id: KeyId) -> Result<Self, crate::Error> {
210        let pk = nethsm.get_key(&key_id)?;
211        let signature_type = nethsm_signature_type(pk.r#type)?;
212
213        Ok(Self {
214            nethsm,
215            signature_type,
216            key_id,
217        })
218    }
219
220    /// Returns a reference view of `self` (a [`NetHsmKey`]).
221    pub(crate) fn as_nethsm_key<'a>(&'a self) -> NetHsmKey<'a, 'a> {
222        NetHsmKey {
223            signature_type: self.signature_type,
224            nethsm: &self.nethsm,
225            key_id: &self.key_id,
226        }
227    }
228}
229
230impl RawSigningKey for OwnedNetHsmKey {
231    fn key_id(&self) -> String {
232        self.as_nethsm_key().key_id()
233    }
234
235    fn sign(&self, digest: &[u8]) -> Result<Vec<Vec<u8>>, Error> {
236        self.as_nethsm_key().sign(digest)
237    }
238
239    fn certificate(&self) -> Result<Option<Vec<u8>>, Error> {
240        self.as_nethsm_key().certificate()
241    }
242
243    fn public(&self) -> Result<RawPublicKey, Error> {
244        self.as_nethsm_key().public()
245    }
246}
247
248/// Transforms the raw digest data for cryptographic signing with OpenPGP.
249///
250/// Raw cryptographic signing primitives have special provisions that
251/// need to be taken care of when using certain combinations of
252/// signing schemes and hashing algorithms.
253///
254/// This function transforms the digest into bytes that are ready to
255/// be passed to raw cryptographic functions. The exact specifics of
256/// the transformations are documented inside the function.
257///
258/// # Errors
259///
260/// Returns a PKCS#1 encoding error wrapped in [`Error::Hsm`] in case the RSA-PKCS#1 signing scheme
261/// is used but the encoding of digest to the `DigestInfo` structure fails.
262fn prepare_digest_data_for_openpgp(
263    signature_type: SignatureType,
264    oid: AlgorithmIdentifier,
265    digest: &[u8],
266) -> Result<Cow<'_, [u8]>, Error> {
267    Ok(match signature_type {
268        // RSA-PKCS#1 signing scheme needs to wrap the digest value
269        // in an DER-encoded ASN.1 DigestInfo structure which captures
270        // the hash used.
271        // See: https://www.rfc-editor.org/rfc/rfc8017#appendix-A.2.4
272        SignatureType::Pkcs1 => picky_asn1_der::to_vec(&DigestInfo {
273            oid,
274            digest: digest.to_vec().into(),
275        })
276        .map_err(|e| {
277            error!("Encoding signature to PKCS#1 format failed: {e:?}");
278            Error::Hsm {
279                context: "preparing digest data",
280                source: Box::new(e),
281            }
282        })?
283        .into(),
284
285        // ECDSA may need to truncate the digest if it's too long
286        // See: https://www.rfc-editor.org/rfc/rfc9580#section-5.2.3.2
287        SignatureType::EcdsaP256 => digest[..usize::min(32, digest.len())].into(),
288        SignatureType::EcdsaP384 => digest[..usize::min(48, digest.len())].into(),
289
290        // All other schemes that we use will not need any kind of
291        // digest transformations.
292        SignatureType::EdDsa | SignatureType::EcdsaP521 => digest.into(),
293
294        SignatureType::PssMd5
295        | SignatureType::PssSha1
296        | SignatureType::PssSha224
297        | SignatureType::PssSha256
298        | SignatureType::PssSha384
299        | SignatureType::PssSha512 => {
300            return Err(Error::UnsupportedSignatureAlgorithm(signature_type));
301        }
302    })
303}
304
305/// Parses raw signature bytes as vector of algorithm-specific multiple precision integers (MPIs).
306///
307/// MPIs (see [arbitrary-precision arithmetic]) are handled in an algorithm specific way.
308/// This function prepares raw signature bytes for technology specific use.
309///
310/// # Errors
311///
312/// Returns an error if
313///
314/// - parsing DER-encoded ECDSA signature fails
315/// - EdDSA signature is of wrong length
316/// - the signature type is not supported
317///
318/// [arbitrary-precision arithmetic]: https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic
319fn raw_signature_to_mpis(sig_type: SignatureType, sig: &[u8]) -> Result<Vec<Vec<u8>>, Error> {
320    use SignatureType;
321    Ok(match sig_type {
322        SignatureType::EcdsaP256 | SignatureType::EcdsaP384 | SignatureType::EcdsaP521 => {
323            let sig: EcdsaSignatureValue = picky_asn1_der::from_bytes(sig).map_err(|e| {
324                error!("DER decoding error when parsing ECDSA signature: {e:?}");
325                Error::Hsm {
326                    context: "DER decoding ECDSA signature",
327                    source: Box::new(e),
328                }
329            })?;
330            vec![
331                sig.r.as_unsigned_bytes_be().into(),
332                sig.s.as_unsigned_bytes_be().into(),
333            ]
334        }
335        SignatureType::EdDsa => {
336            if sig.len() != 64 {
337                error!(
338                    "Signature length should be exactly 64 bytes but is: {}",
339                    sig.len()
340                );
341                return Err(Error::InvalidSignature {
342                    context: "decoding EdDSA signature",
343                    signature_type: sig_type,
344                });
345            }
346
347            vec![sig[..32].into(), sig[32..].into()]
348        }
349        SignatureType::Pkcs1 => {
350            // RSA
351            vec![sig.into()]
352        }
353        SignatureType::PssMd5
354        | SignatureType::PssSha1
355        | SignatureType::PssSha224
356        | SignatureType::PssSha256
357        | SignatureType::PssSha384
358        | SignatureType::PssSha512 => {
359            error!("Unsupported signature type: {sig_type}");
360            return Err(Error::InvalidSignature {
361                context: "parsing signature",
362                signature_type: sig_type,
363            });
364        }
365    })
366}
367
368#[cfg(test)]
369mod tests {
370    use rstest::rstest;
371    use testresult::TestResult;
372
373    use super::*;
374
375    #[test]
376    fn parse_rsa_signature_produces_valid_data() -> TestResult {
377        let sig = raw_signature_to_mpis(SignatureType::Pkcs1, &[0, 1, 2])?;
378        assert_eq!(sig.len(), 1);
379        assert_eq!(&sig[0].as_ref(), &[0, 1, 2]);
380
381        Ok(())
382    }
383
384    #[test]
385    fn parse_ed25519_signature_produces_valid_data() -> TestResult {
386        let sig = raw_signature_to_mpis(
387            SignatureType::EdDsa,
388            &[
389                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,
390                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,
391                1, 1, 1, 1, 1, 1, 1, 1,
392            ],
393        )?;
394        assert_eq!(sig.len(), 2);
395        assert_eq!(sig[0].as_ref(), vec![2; 32]);
396        assert_eq!(sig[1].as_ref(), vec![1; 32]);
397
398        Ok(())
399    }
400
401    #[test]
402    fn parse_p256_signature_produces_valid_data() -> TestResult {
403        let sig = raw_signature_to_mpis(
404            SignatureType::EcdsaP256,
405            &[
406                48, 70, 2, 33, 0, 193, 176, 219, 0, 133, 254, 212, 239, 236, 122, 85, 239, 73, 161,
407                179, 53, 100, 172, 103, 45, 123, 21, 169, 28, 59, 150, 72, 92, 242, 9, 53, 143, 2,
408                33, 0, 165, 1, 144, 97, 102, 109, 66, 50, 185, 234, 211, 150, 253, 228, 210, 126,
409                26, 0, 189, 184, 230, 163, 36, 203, 232, 161, 12, 75, 121, 171, 45, 107,
410            ],
411        )?;
412        assert_eq!(sig.len(), 2);
413        assert_eq!(
414            sig[0].as_ref(),
415            [
416                193, 176, 219, 0, 133, 254, 212, 239, 236, 122, 85, 239, 73, 161, 179, 53, 100,
417                172, 103, 45, 123, 21, 169, 28, 59, 150, 72, 92, 242, 9, 53, 143
418            ]
419        );
420        assert_eq!(
421            sig[1].as_ref(),
422            [
423                165, 1, 144, 97, 102, 109, 66, 50, 185, 234, 211, 150, 253, 228, 210, 126, 26, 0,
424                189, 184, 230, 163, 36, 203, 232, 161, 12, 75, 121, 171, 45, 107
425            ]
426        );
427
428        Ok(())
429    }
430
431    #[test]
432    fn parse_p384_signature_produces_valid_data() -> TestResult {
433        let sig = raw_signature_to_mpis(
434            SignatureType::EcdsaP384,
435            &[
436                48, 101, 2, 49, 0, 134, 13, 108, 74, 135, 234, 174, 105, 208, 46, 109, 18, 77, 21,
437                177, 59, 73, 150, 228, 26, 244, 134, 187, 217, 172, 34, 2, 1, 229, 123, 105, 202,
438                132, 233, 72, 41, 243, 138, 127, 107, 135, 95, 139, 19, 121, 179, 170, 27, 2, 48,
439                44, 80, 117, 90, 18, 137, 36, 190, 8, 60, 201, 235, 242, 168, 164, 245, 119, 136,
440                207, 178, 237, 64, 117, 69, 218, 189, 209, 110, 2, 9, 191, 194, 70, 50, 227, 47, 6,
441                34, 8, 135, 43, 188, 236, 192, 184, 227, 59, 40,
442            ],
443        )?;
444        assert_eq!(sig.len(), 2);
445        assert_eq!(
446            sig[0].as_ref(),
447            [
448                134, 13, 108, 74, 135, 234, 174, 105, 208, 46, 109, 18, 77, 21, 177, 59, 73, 150,
449                228, 26, 244, 134, 187, 217, 172, 34, 2, 1, 229, 123, 105, 202, 132, 233, 72, 41,
450                243, 138, 127, 107, 135, 95, 139, 19, 121, 179, 170, 27
451            ]
452        );
453        assert_eq!(
454            sig[1].as_ref(),
455            [
456                44, 80, 117, 90, 18, 137, 36, 190, 8, 60, 201, 235, 242, 168, 164, 245, 119, 136,
457                207, 178, 237, 64, 117, 69, 218, 189, 209, 110, 2, 9, 191, 194, 70, 50, 227, 47, 6,
458                34, 8, 135, 43, 188, 236, 192, 184, 227, 59, 40
459            ]
460        );
461
462        Ok(())
463    }
464
465    #[test]
466    fn parse_p521_signature_produces_valid_data() -> TestResult {
467        let sig = raw_signature_to_mpis(
468            SignatureType::EcdsaP521,
469            &[
470                48, 129, 136, 2, 66, 0, 203, 246, 21, 57, 217, 6, 101, 73, 103, 113, 98, 39, 223,
471                246, 199, 136, 238, 213, 134, 163, 153, 151, 116, 237, 207, 181, 107, 183, 204,
472                110, 97, 160, 95, 160, 193, 3, 219, 46, 105, 191, 0, 139, 124, 234, 90, 125, 114,
473                115, 205, 109, 15, 193, 166, 100, 224, 108, 87, 143, 240, 65, 41, 93, 164, 166, 2,
474                2, 66, 1, 203, 115, 121, 219, 49, 18, 3, 101, 130, 153, 95, 80, 27, 148, 249, 221,
475                198, 251, 149, 118, 119, 32, 44, 160, 24, 125, 72, 161, 168, 71, 48, 138, 223, 200,
476                37, 124, 234, 17, 237, 246, 13, 123, 102, 151, 83, 95, 186, 161, 112, 41, 158, 138,
477                144, 55, 23, 110, 100, 185, 237, 13, 174, 83, 4, 153, 34,
478            ],
479        )?;
480        assert_eq!(sig.len(), 2);
481        assert_eq!(
482            sig[0].as_ref(),
483            [
484                203, 246, 21, 57, 217, 6, 101, 73, 103, 113, 98, 39, 223, 246, 199, 136, 238, 213,
485                134, 163, 153, 151, 116, 237, 207, 181, 107, 183, 204, 110, 97, 160, 95, 160, 193,
486                3, 219, 46, 105, 191, 0, 139, 124, 234, 90, 125, 114, 115, 205, 109, 15, 193, 166,
487                100, 224, 108, 87, 143, 240, 65, 41, 93, 164, 166, 2
488            ]
489        );
490        assert_eq!(
491            sig[1].as_ref(),
492            [
493                1, 203, 115, 121, 219, 49, 18, 3, 101, 130, 153, 95, 80, 27, 148, 249, 221, 198,
494                251, 149, 118, 119, 32, 44, 160, 24, 125, 72, 161, 168, 71, 48, 138, 223, 200, 37,
495                124, 234, 17, 237, 246, 13, 123, 102, 151, 83, 95, 186, 161, 112, 41, 158, 138,
496                144, 55, 23, 110, 100, 185, 237, 13, 174, 83, 4, 153, 34
497            ]
498        );
499
500        Ok(())
501    }
502
503    #[test]
504    fn rsa_digest_info_is_wrapped_sha1() -> TestResult {
505        let hash = AlgorithmIdentifier::new_sha(ShaVariant::SHA1);
506        let data = prepare_digest_data_for_openpgp(SignatureType::Pkcs1, hash, &[0; 20])?;
507
508        assert_eq!(
509            data,
510            &[
511                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,
512                0, 0, 0, 0, 0, 0, 0, 0, 0, 0
513            ][..]
514        );
515
516        Ok(())
517    }
518
519    #[test]
520    fn rsa_digest_info_is_wrapped_sha512() -> TestResult {
521        let hash = AlgorithmIdentifier::new_sha(ShaVariant::SHA2_512);
522        let data = prepare_digest_data_for_openpgp(SignatureType::Pkcs1, hash, &[0; 64])?;
523
524        assert_eq!(
525            data,
526            &[
527                48, 81, 48, 13, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 3, 5, 0, 4, 64, 0, 0, 0, 0, 0,
528                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
529                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
530                0, 0, 0
531            ][..]
532        );
533
534        Ok(())
535    }
536
537    #[rstest]
538    #[case(SignatureType::EcdsaP256, 32)]
539    #[case(SignatureType::EcdsaP384, 48)]
540    #[case(SignatureType::EcdsaP521, 64)]
541    fn ecdsa_wrapped_up_to_max_len(
542        #[case] sig_type: SignatureType,
543        #[case] max_len: usize,
544    ) -> TestResult {
545        // the digest value is irrelevant - just the size of the digest
546        let digest = [0; 512 / 8];
547        let hash = AlgorithmIdentifier::new_sha(ShaVariant::SHA2_512);
548        let data = prepare_digest_data_for_openpgp(sig_type, hash, &digest)?;
549
550        // The data to be signed size needs to be truncated to the value specific the the curve
551        // being used. If the digest is short enough to be smaller than the curve specific field
552        // size the digest is used as a whole.
553        assert_eq!(
554            data.len(),
555            usize::min(max_len, digest.len()),
556            "the data to be signed's length ({}) cannot exceed maximum length imposed by the curve ({})",
557            data.len(),
558            max_len
559        );
560
561        Ok(())
562    }
563
564    #[rstest]
565    fn eddsa_is_not_wrapped() -> TestResult {
566        // the digest value is irrelevant - just the size of the digest
567        let digest = &[0; 512 / 8][..];
568
569        let hash = AlgorithmIdentifier::new_sha(ShaVariant::SHA2_512);
570        let data = prepare_digest_data_for_openpgp(SignatureType::EdDsa, hash, digest)?;
571
572        assert_eq!(data, digest);
573
574        Ok(())
575    }
576}