Skip to main content

nethsm/base/
impl_openpgp.rs

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