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}