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}