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}