Skip to main content

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::PssSha1
295        | SignatureType::PssSha224
296        | SignatureType::PssSha256
297        | SignatureType::PssSha384
298        | SignatureType::PssSha512 => {
299            return Err(Error::UnsupportedSignatureAlgorithm(signature_type));
300        }
301    })
302}
303
304/// Parses raw signature bytes as vector of algorithm-specific multiple precision integers (MPIs).
305///
306/// MPIs (see [arbitrary-precision arithmetic]) are handled in an algorithm specific way.
307/// This function prepares raw signature bytes for technology specific use.
308///
309/// # Errors
310///
311/// Returns an error if
312///
313/// - parsing DER-encoded ECDSA signature fails
314/// - EdDSA signature is of wrong length
315/// - the signature type is not supported
316///
317/// [arbitrary-precision arithmetic]: https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic
318fn raw_signature_to_mpis(sig_type: SignatureType, sig: &[u8]) -> Result<Vec<Vec<u8>>, Error> {
319    use SignatureType;
320    Ok(match sig_type {
321        SignatureType::EcdsaP256 | SignatureType::EcdsaP384 | SignatureType::EcdsaP521 => {
322            let sig: EcdsaSignatureValue = picky_asn1_der::from_bytes(sig).map_err(|e| {
323                error!("DER decoding error when parsing ECDSA signature: {e:?}");
324                Error::Hsm {
325                    context: "DER decoding ECDSA signature",
326                    source: Box::new(e),
327                }
328            })?;
329            vec![
330                sig.r.as_unsigned_bytes_be().into(),
331                sig.s.as_unsigned_bytes_be().into(),
332            ]
333        }
334        SignatureType::EdDsa => {
335            if sig.len() != 64 {
336                error!(
337                    "Signature length should be exactly 64 bytes but is: {}",
338                    sig.len()
339                );
340                return Err(Error::InvalidSignature {
341                    context: "decoding EdDSA signature",
342                    signature_type: sig_type,
343                });
344            }
345
346            vec![sig[..32].into(), sig[32..].into()]
347        }
348        SignatureType::Pkcs1 => {
349            // RSA
350            vec![sig.into()]
351        }
352        SignatureType::PssSha1
353        | SignatureType::PssSha224
354        | SignatureType::PssSha256
355        | SignatureType::PssSha384
356        | SignatureType::PssSha512 => {
357            error!("Unsupported signature type: {sig_type}");
358            return Err(Error::InvalidSignature {
359                context: "parsing signature",
360                signature_type: sig_type,
361            });
362        }
363    })
364}
365
366#[cfg(test)]
367mod tests {
368    use rstest::rstest;
369    use testresult::TestResult;
370
371    use super::*;
372
373    #[test]
374    fn parse_rsa_signature_produces_valid_data() -> TestResult {
375        let sig = raw_signature_to_mpis(SignatureType::Pkcs1, &[0, 1, 2])?;
376        assert_eq!(sig.len(), 1);
377        assert_eq!(&sig[0].as_ref(), &[0, 1, 2]);
378
379        Ok(())
380    }
381
382    #[test]
383    fn parse_ed25519_signature_produces_valid_data() -> TestResult {
384        let sig = raw_signature_to_mpis(
385            SignatureType::EdDsa,
386            &[
387                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,
388                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,
389                1, 1, 1, 1, 1, 1, 1, 1,
390            ],
391        )?;
392        assert_eq!(sig.len(), 2);
393        assert_eq!(sig[0].as_ref(), vec![2; 32]);
394        assert_eq!(sig[1].as_ref(), vec![1; 32]);
395
396        Ok(())
397    }
398
399    #[test]
400    fn parse_p256_signature_produces_valid_data() -> TestResult {
401        let sig = raw_signature_to_mpis(
402            SignatureType::EcdsaP256,
403            &[
404                48, 70, 2, 33, 0, 193, 176, 219, 0, 133, 254, 212, 239, 236, 122, 85, 239, 73, 161,
405                179, 53, 100, 172, 103, 45, 123, 21, 169, 28, 59, 150, 72, 92, 242, 9, 53, 143, 2,
406                33, 0, 165, 1, 144, 97, 102, 109, 66, 50, 185, 234, 211, 150, 253, 228, 210, 126,
407                26, 0, 189, 184, 230, 163, 36, 203, 232, 161, 12, 75, 121, 171, 45, 107,
408            ],
409        )?;
410        assert_eq!(sig.len(), 2);
411        assert_eq!(
412            sig[0].as_ref(),
413            [
414                193, 176, 219, 0, 133, 254, 212, 239, 236, 122, 85, 239, 73, 161, 179, 53, 100,
415                172, 103, 45, 123, 21, 169, 28, 59, 150, 72, 92, 242, 9, 53, 143
416            ]
417        );
418        assert_eq!(
419            sig[1].as_ref(),
420            [
421                165, 1, 144, 97, 102, 109, 66, 50, 185, 234, 211, 150, 253, 228, 210, 126, 26, 0,
422                189, 184, 230, 163, 36, 203, 232, 161, 12, 75, 121, 171, 45, 107
423            ]
424        );
425
426        Ok(())
427    }
428
429    #[test]
430    fn parse_p384_signature_produces_valid_data() -> TestResult {
431        let sig = raw_signature_to_mpis(
432            SignatureType::EcdsaP384,
433            &[
434                48, 101, 2, 49, 0, 134, 13, 108, 74, 135, 234, 174, 105, 208, 46, 109, 18, 77, 21,
435                177, 59, 73, 150, 228, 26, 244, 134, 187, 217, 172, 34, 2, 1, 229, 123, 105, 202,
436                132, 233, 72, 41, 243, 138, 127, 107, 135, 95, 139, 19, 121, 179, 170, 27, 2, 48,
437                44, 80, 117, 90, 18, 137, 36, 190, 8, 60, 201, 235, 242, 168, 164, 245, 119, 136,
438                207, 178, 237, 64, 117, 69, 218, 189, 209, 110, 2, 9, 191, 194, 70, 50, 227, 47, 6,
439                34, 8, 135, 43, 188, 236, 192, 184, 227, 59, 40,
440            ],
441        )?;
442        assert_eq!(sig.len(), 2);
443        assert_eq!(
444            sig[0].as_ref(),
445            [
446                134, 13, 108, 74, 135, 234, 174, 105, 208, 46, 109, 18, 77, 21, 177, 59, 73, 150,
447                228, 26, 244, 134, 187, 217, 172, 34, 2, 1, 229, 123, 105, 202, 132, 233, 72, 41,
448                243, 138, 127, 107, 135, 95, 139, 19, 121, 179, 170, 27
449            ]
450        );
451        assert_eq!(
452            sig[1].as_ref(),
453            [
454                44, 80, 117, 90, 18, 137, 36, 190, 8, 60, 201, 235, 242, 168, 164, 245, 119, 136,
455                207, 178, 237, 64, 117, 69, 218, 189, 209, 110, 2, 9, 191, 194, 70, 50, 227, 47, 6,
456                34, 8, 135, 43, 188, 236, 192, 184, 227, 59, 40
457            ]
458        );
459
460        Ok(())
461    }
462
463    #[test]
464    fn parse_p521_signature_produces_valid_data() -> TestResult {
465        let sig = raw_signature_to_mpis(
466            SignatureType::EcdsaP521,
467            &[
468                48, 129, 136, 2, 66, 0, 203, 246, 21, 57, 217, 6, 101, 73, 103, 113, 98, 39, 223,
469                246, 199, 136, 238, 213, 134, 163, 153, 151, 116, 237, 207, 181, 107, 183, 204,
470                110, 97, 160, 95, 160, 193, 3, 219, 46, 105, 191, 0, 139, 124, 234, 90, 125, 114,
471                115, 205, 109, 15, 193, 166, 100, 224, 108, 87, 143, 240, 65, 41, 93, 164, 166, 2,
472                2, 66, 1, 203, 115, 121, 219, 49, 18, 3, 101, 130, 153, 95, 80, 27, 148, 249, 221,
473                198, 251, 149, 118, 119, 32, 44, 160, 24, 125, 72, 161, 168, 71, 48, 138, 223, 200,
474                37, 124, 234, 17, 237, 246, 13, 123, 102, 151, 83, 95, 186, 161, 112, 41, 158, 138,
475                144, 55, 23, 110, 100, 185, 237, 13, 174, 83, 4, 153, 34,
476            ],
477        )?;
478        assert_eq!(sig.len(), 2);
479        assert_eq!(
480            sig[0].as_ref(),
481            [
482                203, 246, 21, 57, 217, 6, 101, 73, 103, 113, 98, 39, 223, 246, 199, 136, 238, 213,
483                134, 163, 153, 151, 116, 237, 207, 181, 107, 183, 204, 110, 97, 160, 95, 160, 193,
484                3, 219, 46, 105, 191, 0, 139, 124, 234, 90, 125, 114, 115, 205, 109, 15, 193, 166,
485                100, 224, 108, 87, 143, 240, 65, 41, 93, 164, 166, 2
486            ]
487        );
488        assert_eq!(
489            sig[1].as_ref(),
490            [
491                1, 203, 115, 121, 219, 49, 18, 3, 101, 130, 153, 95, 80, 27, 148, 249, 221, 198,
492                251, 149, 118, 119, 32, 44, 160, 24, 125, 72, 161, 168, 71, 48, 138, 223, 200, 37,
493                124, 234, 17, 237, 246, 13, 123, 102, 151, 83, 95, 186, 161, 112, 41, 158, 138,
494                144, 55, 23, 110, 100, 185, 237, 13, 174, 83, 4, 153, 34
495            ]
496        );
497
498        Ok(())
499    }
500
501    #[test]
502    fn rsa_digest_info_is_wrapped_sha1() -> TestResult {
503        let hash = AlgorithmIdentifier::new_sha(ShaVariant::SHA1);
504        let data = prepare_digest_data_for_openpgp(SignatureType::Pkcs1, hash, &[0; 20])?;
505
506        assert_eq!(
507            data,
508            &[
509                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,
510                0, 0, 0, 0, 0, 0, 0, 0, 0, 0
511            ][..]
512        );
513
514        Ok(())
515    }
516
517    #[test]
518    fn rsa_digest_info_is_wrapped_sha512() -> TestResult {
519        let hash = AlgorithmIdentifier::new_sha(ShaVariant::SHA2_512);
520        let data = prepare_digest_data_for_openpgp(SignatureType::Pkcs1, hash, &[0; 64])?;
521
522        assert_eq!(
523            data,
524            &[
525                48, 81, 48, 13, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 3, 5, 0, 4, 64, 0, 0, 0, 0, 0,
526                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,
527                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,
528                0, 0, 0
529            ][..]
530        );
531
532        Ok(())
533    }
534
535    #[rstest]
536    #[case(SignatureType::EcdsaP256, 32)]
537    #[case(SignatureType::EcdsaP384, 48)]
538    #[case(SignatureType::EcdsaP521, 64)]
539    fn ecdsa_wrapped_up_to_max_len(
540        #[case] sig_type: SignatureType,
541        #[case] max_len: usize,
542    ) -> TestResult {
543        // the digest value is irrelevant - just the size of the digest
544        let digest = [0; 512 / 8];
545        let hash = AlgorithmIdentifier::new_sha(ShaVariant::SHA2_512);
546        let data = prepare_digest_data_for_openpgp(sig_type, hash, &digest)?;
547
548        // The data to be signed size needs to be truncated to the value specific the the curve
549        // being used. If the digest is short enough to be smaller than the curve specific field
550        // size the digest is used as a whole.
551        assert_eq!(
552            data.len(),
553            usize::min(max_len, digest.len()),
554            "the data to be signed's length ({}) cannot exceed maximum length imposed by the curve ({})",
555            data.len(),
556            max_len
557        );
558
559        Ok(())
560    }
561
562    #[rstest]
563    fn eddsa_is_not_wrapped() -> TestResult {
564        // the digest value is irrelevant - just the size of the digest
565        let digest = &[0; 512 / 8][..];
566
567        let hash = AlgorithmIdentifier::new_sha(ShaVariant::SHA2_512);
568        let data = prepare_digest_data_for_openpgp(SignatureType::EdDsa, hash, digest)?;
569
570        assert_eq!(data, digest);
571
572        Ok(())
573    }
574}