nethsm/base/
impl_openpgp.rs

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