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