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}