Skip to main content

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