nethsm/base/
impl_openpgp.rs

1//! [`NetHsm`] implementation for OpenPGP functionality.
2
3use log::debug;
4use signstar_crypto::{
5    key::{KeyMechanism, PrivateKeyImport},
6    signer::openpgp::Timestamp,
7};
8
9#[cfg(doc)]
10use crate::{Credentials, SystemState, UserRole};
11use crate::{
12    Error,
13    KeyId,
14    NetHsm,
15    OpenPgpKeyUsageFlags,
16    OpenPgpUserId,
17    OpenPgpVersion,
18    SignedSecretKey,
19    base::utils::user_or_no_user_string,
20    signer::NetHsmKey,
21};
22
23impl NetHsm {
24    /// Creates an [OpenPGP certificate] for an existing key.
25    ///
26    /// The NetHSM key identified by `key_id` is used to issue required [binding signatures] (e.g.
27    /// those for the [User ID] defined by `user_id`).
28    /// Using `flags` it is possible to define the key's [capabilities] and with `created_at` to
29    /// provide the certificate's creation time.
30    /// Using `version` the OpenPGP version is provided (currently only [`OpenPgpVersion::V4`] is
31    /// supported).
32    /// The resulting [OpenPGP certificate] is returned as vector of bytes.
33    ///
34    /// To make use of the [OpenPGP certificate] (e.g. with
35    /// [`openpgp_sign`][`NetHsm::openpgp_sign`]), it should be added as certificate for the key
36    /// using [`import_key_certificate`][`NetHsm::import_key_certificate`].
37    ///
38    /// This call requires using a user in the [`Operator`][`UserRole::Operator`] [role], which
39    /// carries a tag (see [`add_user_tag`][`NetHsm::add_user_tag`]) matching one of the tags of
40    /// the targeted key (see [`add_key_tag`][`NetHsm::add_key_tag`]).
41    ///
42    /// ## Namespaces
43    ///
44    /// * [`Operator`][`UserRole::Operator`] users in a [namespace] only have access to keys in
45    ///   their own [namespace].
46    /// * System-wide [`Operator`][`UserRole::Operator`] users only have access to system-wide keys.
47    ///
48    /// # Errors
49    ///
50    /// Returns an [`Error::Api`] if creating an [OpenPGP certificate] for a key fails:
51    /// * the NetHSM is not in [`Operational`][`SystemState::Operational`] [state]
52    /// * no key identified by `key_id` exists on the NetHSM
53    /// * the [`Operator`][`UserRole::Operator`] user does not have access to the key (e.g.
54    ///   different [namespace])
55    /// * the [`Operator`][`UserRole::Operator`] user does not carry a tag matching one of the key
56    ///   tags
57    /// * the used [`Credentials`] are not correct
58    /// * the used [`Credentials`] are not those of a user in the [`Operator`][`UserRole::Operator`]
59    ///   [role]
60    ///
61    /// # Panics
62    ///
63    /// Panics if the currently unimplemented [`OpenPgpVersion::V6`] is provided as `version`.
64    ///
65    /// # Examples
66    ///
67    /// ```no_run
68    /// use nethsm::{
69    ///     Connection,
70    ///     ConnectionSecurity,
71    ///     Credentials,
72    ///     KeyMechanism,
73    ///     KeyType,
74    ///     NetHsm,
75    ///     OpenPgpKeyUsageFlags,
76    ///     OpenPgpVersion,
77    ///     Passphrase,
78    ///     Timestamp,
79    ///     UserRole,
80    /// };
81    ///
82    /// # fn main() -> testresult::TestResult {
83    /// // create a connection with a system-wide user in the Administrator role (R-Administrator)
84    /// let nethsm = NetHsm::new(
85    ///     Connection::new(
86    ///         "https://example.org/api/v1".try_into()?,
87    ///         ConnectionSecurity::Unsafe,
88    ///     ),
89    ///     Some(Credentials::new(
90    ///         "admin".parse()?,
91    ///         Some(Passphrase::new("passphrase".to_string())),
92    ///     )),
93    ///     None,
94    ///     None,
95    /// )?;
96    /// // add a system-wide user in the Operator role
97    /// nethsm.add_user(
98    ///     "Operator1".to_string(),
99    ///     UserRole::Operator,
100    ///     Passphrase::new("operator-passphrase".to_string()),
101    ///     Some("operator1".parse()?),
102    /// )?;
103    /// // generate system-wide key with tag
104    /// nethsm.generate_key(
105    ///     KeyType::Curve25519,
106    ///     vec![KeyMechanism::EdDsaSignature],
107    ///     None,
108    ///     Some("signing1".parse()?),
109    ///     Some(vec!["tag1".to_string()]),
110    /// )?;
111    /// // tag system-wide user in Operator role for access to signing key
112    /// nethsm.add_user_tag(&"operator1".parse()?, "tag1")?;
113    ///
114    /// // create an OpenPGP certificate for the key with ID "signing1"
115    /// nethsm.use_credentials(&"operator1".parse()?)?;
116    /// assert!(
117    ///     !nethsm
118    ///         .create_openpgp_cert(
119    ///             &"signing1".parse()?,
120    ///             OpenPgpKeyUsageFlags::default(),
121    ///             "Test <test@example.org>".parse()?,
122    ///             Timestamp::now(),
123    ///             OpenPgpVersion::V4,
124    ///         )?
125    ///         .is_empty()
126    /// );
127    /// # Ok(())
128    /// # }
129    /// ```
130    /// [OpenPGP certificate]: https://openpgp.dev/book/certificates.html
131    /// [binding signatures]: https://openpgp.dev/book/signing_components.html#binding-signatures
132    /// [User ID]: https://openpgp.dev/book/glossary.html#term-User-ID
133    /// [key certificate]: https://docs.nitrokey.com/nethsm/operation#key-certificates
134    /// [capabilities]: https://openpgp.dev/book/glossary.html#term-Capability
135    /// [namespace]: https://docs.nitrokey.com/nethsm/administration#namespaces
136    /// [role]: https://docs.nitrokey.com/nethsm/administration#roles
137    /// [state]: https://docs.nitrokey.com/nethsm/administration#state
138    pub fn create_openpgp_cert(
139        &self,
140        key_id: &KeyId,
141        flags: OpenPgpKeyUsageFlags,
142        user_id: OpenPgpUserId,
143        created_at: Timestamp,
144        version: OpenPgpVersion,
145    ) -> Result<Vec<u8>, Error> {
146        debug!(
147            "Create an OpenPGP certificate (User ID: {user_id}; flags: {:?}; creation date: {created_at:?}; version: {version}) for key \"{key_id}\" on the NetHSM at {} using {}",
148            flags.as_ref(),
149            self.url.borrow(),
150            user_or_no_user_string(self.current_credentials.borrow().as_ref()),
151        );
152
153        let raw_signer = NetHsmKey::new(self, key_id)?;
154
155        Ok(signstar_crypto::signer::openpgp::add_certificate(
156            &raw_signer,
157            flags,
158            user_id,
159            created_at,
160            version,
161        )?)
162    }
163
164    /// Creates an [OpenPGP signature] for a message.
165    ///
166    /// Signs the `message` using the key identified by `key_id` and returns a binary [OpenPGP data
167    /// signature].
168    ///
169    /// This call requires using a user in the [`Operator`][`UserRole::Operator`] [role], which
170    /// carries a tag (see [`add_user_tag`][`NetHsm::add_user_tag`]) matching one of the tags of
171    /// the targeted key (see [`add_key_tag`][`NetHsm::add_key_tag`]).
172    ///
173    /// ## Namespaces
174    ///
175    /// * [`Operator`][`UserRole::Operator`] users in a [namespace] only have access to keys in
176    ///   their own [namespace].
177    /// * System-wide [`Operator`][`UserRole::Operator`] users only have access to system-wide keys.
178    ///
179    /// # Errors
180    ///
181    /// Returns an [`Error::Api`] if creating an [OpenPGP signature] for the `message` fails:
182    /// * the NetHSM is not in [`Operational`][`SystemState::Operational`] [state]
183    /// * no key identified by `key_id` exists on the NetHSM
184    /// * the [`Operator`][`UserRole::Operator`] user does not have access to the key (e.g.
185    ///   different [namespace])
186    /// * the [`Operator`][`UserRole::Operator`] user does not carry a tag matching one of the key
187    ///   tags
188    /// * the used [`Credentials`] are not correct
189    /// * the used [`Credentials`] are not those of a user in the [`Operator`][`UserRole::Operator`]
190    ///   [role]
191    ///
192    /// # Examples
193    ///
194    /// ```no_run
195    /// use nethsm::{
196    ///     Connection,
197    ///     ConnectionSecurity,
198    ///     Credentials,
199    ///     KeyMechanism,
200    ///     KeyType,
201    ///     NetHsm,
202    ///     OpenPgpKeyUsageFlags,
203    ///     OpenPgpVersion,
204    ///     Passphrase,
205    ///     Timestamp,
206    ///     UserRole,
207    /// };
208    ///
209    /// # fn main() -> testresult::TestResult {
210    /// // create a connection with a system-wide user in the Administrator role (R-Administrator)
211    /// let nethsm = NetHsm::new(
212    ///     Connection::new(
213    ///         "https://example.org/api/v1".try_into()?,
214    ///         ConnectionSecurity::Unsafe,
215    ///     ),
216    ///     Some(Credentials::new(
217    ///         "admin".parse()?,
218    ///         Some(Passphrase::new("passphrase".to_string())),
219    ///     )),
220    ///     None,
221    ///     None,
222    /// )?;
223    /// // add a system-wide user in the Operator role
224    /// nethsm.add_user(
225    ///     "Operator1".to_string(),
226    ///     UserRole::Operator,
227    ///     Passphrase::new("operator-passphrase".to_string()),
228    ///     Some("operator1".parse()?),
229    /// )?;
230    /// // generate system-wide key with tag
231    /// nethsm.generate_key(
232    ///     KeyType::Curve25519,
233    ///     vec![KeyMechanism::EdDsaSignature],
234    ///     None,
235    ///     Some("signing1".parse()?),
236    ///     Some(vec!["tag1".to_string()]),
237    /// )?;
238    /// // tag system-wide user in Operator role for access to signing key
239    /// nethsm.add_user_tag(&"operator1".parse()?, "tag1")?;
240    /// // create an OpenPGP certificate for the key with ID "signing1"
241    /// nethsm.use_credentials(&"operator1".parse()?)?;
242    /// let openpgp_cert = nethsm.create_openpgp_cert(
243    ///     &"signing1".parse()?,
244    ///     OpenPgpKeyUsageFlags::default(),
245    ///     "Test <test@example.org>".parse()?,
246    ///     Timestamp::now(),
247    ///     OpenPgpVersion::V4,
248    /// )?;
249    /// // import the OpenPGP certificate as key certificate
250    /// nethsm.use_credentials(&"admin".parse()?)?;
251    /// nethsm.import_key_certificate(&"signing1".parse()?, openpgp_cert)?;
252    ///
253    /// // create OpenPGP signature
254    /// nethsm.use_credentials(&"operator1".parse()?)?;
255    /// assert!(
256    ///     !nethsm
257    ///         .openpgp_sign(&"signing1".parse()?, b"sample message")?
258    ///         .is_empty()
259    /// );
260    /// # Ok(()) }
261    /// ```
262    /// [OpenPGP signature]: https://openpgp.dev/book/signing_data.html
263    /// [OpenPGP data signature]: https://openpgp.dev/book/signing_data.html
264    /// [namespace]: https://docs.nitrokey.com/nethsm/administration#namespaces
265    /// [role]: https://docs.nitrokey.com/nethsm/administration#roles
266    /// [state]: https://docs.nitrokey.com/nethsm/administration#state
267    pub fn openpgp_sign(&self, key_id: &KeyId, message: &[u8]) -> Result<Vec<u8>, Error> {
268        debug!(
269            "Create an OpenPGP signature for a message with key \"{key_id}\" on the NetHSM at {} using {}",
270            self.url.borrow(),
271            user_or_no_user_string(self.current_credentials.borrow().as_ref()),
272        );
273        let raw_signer = NetHsmKey::new(self, key_id)?;
274
275        Ok(signstar_crypto::signer::openpgp::sign(
276            &raw_signer,
277            message,
278        )?)
279    }
280
281    /// Generates an armored OpenPGP signature based on provided hasher state.
282    ///
283    /// Signs the hasher `state` using the key identified by `key_id`
284    /// and returns a binary [OpenPGP data signature].
285    ///
286    /// This call requires using a user in the [`Operator`][`UserRole::Operator`] [role], which
287    /// carries a tag (see [`add_user_tag`][`NetHsm::add_user_tag`]) matching one of the tags of
288    /// the targeted key (see [`add_key_tag`][`NetHsm::add_key_tag`]).
289    ///
290    /// ## Namespaces
291    ///
292    /// * [`Operator`][`UserRole::Operator`] users in a [namespace] only have access to keys in
293    ///   their own [namespace].
294    /// * System-wide [`Operator`][`UserRole::Operator`] users only have access to system-wide keys.
295    ///
296    /// # Errors
297    ///
298    /// Returns an [`Error::Api`] if creating an [OpenPGP signature] for the hasher state fails:
299    /// * the NetHSM is not in [`Operational`][`SystemState::Operational`] [state]
300    /// * no key identified by `key_id` exists on the NetHSM
301    /// * the [`Operator`][`UserRole::Operator`] user does not have access to the key (e.g.
302    ///   different [namespace])
303    /// * the [`Operator`][`UserRole::Operator`] user does not carry a tag matching one of the key
304    ///   tags
305    /// * the used [`Credentials`] are not correct
306    /// * the used [`Credentials`] are not those of a user in the [`Operator`][`UserRole::Operator`]
307    ///   [role]
308    ///
309    /// # Examples
310    ///
311    /// ```no_run
312    /// use nethsm::{
313    ///     Connection,
314    ///     ConnectionSecurity,
315    ///     Credentials,
316    ///     KeyMechanism,
317    ///     KeyType,
318    ///     NetHsm,
319    ///     OpenPgpKeyUsageFlags,
320    ///     OpenPgpVersion,
321    ///     Passphrase,
322    ///     Timestamp,
323    ///     UserRole,
324    /// };
325    /// use sha2::{Digest, Sha512};
326    ///
327    /// # fn main() -> testresult::TestResult {
328    /// // create a connection with a system-wide user in the Administrator role (R-Administrator)
329    /// let nethsm = NetHsm::new(
330    ///     Connection::new(
331    ///         "https://example.org/api/v1".try_into()?,
332    ///         ConnectionSecurity::Unsafe,
333    ///     ),
334    ///     Some(Credentials::new(
335    ///         "admin".parse()?,
336    ///         Some(Passphrase::new("passphrase".to_string())),
337    ///     )),
338    ///     None,
339    ///     None,
340    /// )?;
341    /// // add a system-wide user in the Operator role
342    /// nethsm.add_user(
343    ///     "Operator1".to_string(),
344    ///     UserRole::Operator,
345    ///     Passphrase::new("operator-passphrase".to_string()),
346    ///     Some("operator1".parse()?),
347    /// )?;
348    /// // generate system-wide key with tag
349    /// nethsm.generate_key(
350    ///     KeyType::Curve25519,
351    ///     vec![KeyMechanism::EdDsaSignature],
352    ///     None,
353    ///     Some("signing1".parse()?),
354    ///     Some(vec!["tag1".to_string()]),
355    /// )?;
356    /// // tag system-wide user in Operator role for access to signing key
357    /// nethsm.add_user_tag(&"operator1".parse()?, "tag1")?;
358    /// // create an OpenPGP certificate for the key with ID "signing1"
359    /// nethsm.use_credentials(&"operator1".parse()?)?;
360    /// let openpgp_cert = nethsm.create_openpgp_cert(
361    ///     &"signing1".parse()?,
362    ///     OpenPgpKeyUsageFlags::default(),
363    ///     "Test <test@example.org>".parse()?,
364    ///     Timestamp::now(),
365    ///     OpenPgpVersion::V4,
366    /// )?;
367    /// // import the OpenPGP certificate as key certificate
368    /// nethsm.use_credentials(&"admin".parse()?)?;
369    /// nethsm.import_key_certificate(&"signing1".parse()?, openpgp_cert)?;
370    ///
371    /// let mut state = Sha512::new();
372    /// state.update(b"Hello world!");
373    ///
374    /// // create OpenPGP signature
375    /// nethsm.use_credentials(&"operator1".parse()?)?;
376    /// assert!(
377    ///     !nethsm
378    ///         .openpgp_sign_state(&"signing1".parse()?, state)?
379    ///         .is_empty()
380    /// );
381    /// # Ok(()) }
382    /// ```
383    /// [OpenPGP signature]: https://openpgp.dev/book/signing_data.html
384    /// [OpenPGP data signature]: https://openpgp.dev/book/signing_data.html
385    /// [namespace]: https://docs.nitrokey.com/nethsm/administration#namespaces
386    /// [role]: https://docs.nitrokey.com/nethsm/administration#roles
387    /// [state]: https://docs.nitrokey.com/nethsm/administration#state
388    pub fn openpgp_sign_state(
389        &self,
390        key_id: &KeyId,
391        state: sha2::Sha512,
392    ) -> Result<String, crate::Error> {
393        debug!(
394            "Create an OpenPGP signature for a hasher state with key \"{key_id}\" on the NetHSM at {} using {}",
395            self.url.borrow(),
396            user_or_no_user_string(self.current_credentials.borrow().as_ref()),
397        );
398        let raw_signer = NetHsmKey::new(self, key_id)?;
399
400        Ok(signstar_crypto::signer::openpgp::sign_hasher_state(
401            &raw_signer,
402            state,
403        )?)
404    }
405}
406
407/// Extracts certificate (public key) from an OpenPGP TSK.
408///
409/// # Errors
410///
411/// Returns an error if
412///
413/// - a secret key cannot be decoded from `key_data`,
414/// - or writing a serialized certificate into a vector fails.
415pub fn extract_openpgp_certificate(key: SignedSecretKey) -> Result<Vec<u8>, Error> {
416    signstar_crypto::signer::openpgp::extract_certificate(key).map_err(Error::SignstarCryptoSigner)
417}
418
419/// Converts an OpenPGP Transferable Secret Key into [`PrivateKeyImport`] object.
420///
421/// # Errors
422///
423/// Returns an [`Error::SignstarCryptoSigner`] if creating a [`PrivateKeyImport`] from `key_data` is
424/// not possible.
425///
426/// Returns an [`crate::Error::Key`] if `key_data` is an RSA public key and is shorter than
427/// [`signstar_crypto::key::MIN_RSA_BIT_LENGTH`].
428pub fn tsk_to_private_key_import(
429    key: &SignedSecretKey,
430) -> Result<(PrivateKeyImport, KeyMechanism), Error> {
431    signstar_crypto::signer::openpgp::tsk_to_private_key_import(key)
432        .map_err(Error::SignstarCryptoSigner)
433}