nethsm/base/
impl_openpgp.rs

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