nethsm/key.rs
1use std::{fmt::Display, str::FromStr};
2
3use serde::{Deserialize, Serialize};
4use signstar_crypto::key::{
5 CryptographicKeyContext,
6 KeyMechanism,
7 KeyType,
8 MIN_RSA_BIT_LENGTH,
9 SignatureType,
10 key_type_and_mechanisms_match_signature_type,
11 key_type_matches_length,
12 key_type_matches_mechanisms,
13};
14
15use crate::TlsKeyType;
16
17/// An error that can occur when dealing with keys.
18#[derive(Debug, thiserror::Error)]
19pub enum Error {
20 /// Elliptic curve TLS keys do not support providing a length
21 #[error("Elliptic curve key ({tls_key_type}) does not support setting length")]
22 TlsKeyLengthUnsupported { tls_key_type: TlsKeyType },
23
24 /// RSA TLS key type requires setting a length
25 #[error("Generating a key of type {tls_key_type} requires setting a length")]
26 TlsKeyLengthRequired { tls_key_type: TlsKeyType },
27
28 /// RSA TLS key is generated with unsafe key length (smaller than 2048)
29 #[error(
30 "RSA keys shorter than {MIN_RSA_BIT_LENGTH} are not supported. A key length of {key_length} is unsafe!"
31 )]
32 InvalidTlsKeyLengthRsa { key_length: u32 },
33
34 /// One or more [`KeyId`]s are not valid.
35 #[error("Invalid Key ID{}: {}", if key_ids.len() == 1 {"s"} else { " "}, key_ids.join(", "))]
36 InvalidKeyIds {
37 /// A list of strings representing invalid [`KeyId`]s.
38 key_ids: Vec<String>,
39 },
40
41 /// A signstar_crypto key error.
42 #[error("A signstar_crypto::key error:\n{0}")]
43 SignstarCryptoKey(#[from] signstar_crypto::key::Error),
44}
45
46/// A unique key identifier for a private key on a NetHSM.
47///
48/// A [`KeyId`]s must be in the character set `[a-z0-9]` and must not be empty.
49/// It is used in [key management] on a NetHSM and is unique in its scope.
50/// The same [`KeyId`] may exist system-wide and in one or several [namespaces], but no duplicate
51/// [`KeyId`] can exist system-wide or in the same namespace.
52///
53/// [key management]: https://docs.nitrokey.com/nethsm/operation#key-management
54/// [namespaces]: https://docs.nitrokey.com/nethsm/administration#namespaces
55#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
56#[serde(into = "String", try_from = "String")]
57pub struct KeyId(String);
58
59impl KeyId {
60 /// Constructs a new Key ID from a `String`.
61 ///
62 /// Validates the input string and returns [`crate::Error::Key`]
63 /// if it is invalid.
64 ///
65 /// # Errors
66 ///
67 /// Returns an [`Error`][`crate::Error`] if
68 /// * string contains characters outside of the allowed range (`[a-z0-9]`)
69 /// * string is empty
70 ///
71 /// # Examples
72 ///
73 /// ```
74 /// use nethsm::KeyId;
75 ///
76 /// assert!(KeyId::new("key1".into()).is_ok());
77 /// assert!(KeyId::new("key".into()).is_ok());
78 ///
79 /// // the input can not contain invalid chars
80 /// assert!(KeyId::new("key1#".into()).is_err());
81 /// assert!(KeyId::new("key~1".into()).is_err());
82 ///
83 /// // the key must be non-empty
84 /// assert!(KeyId::new("".into()).is_err());
85 /// ```
86 pub fn new(key_id: String) -> Result<Self, Error> {
87 if key_id.is_empty()
88 || !key_id.chars().all(|char| {
89 char.is_numeric() || (char.is_ascii_lowercase() && char.is_ascii_alphabetic())
90 })
91 {
92 return Err(Error::InvalidKeyIds {
93 key_ids: vec![key_id],
94 });
95 }
96
97 Ok(Self(key_id))
98 }
99}
100
101impl AsRef<str> for KeyId {
102 fn as_ref(&self) -> &str {
103 &self.0
104 }
105}
106
107impl From<KeyId> for String {
108 fn from(value: KeyId) -> Self {
109 value.0
110 }
111}
112
113impl FromStr for KeyId {
114 type Err = Error;
115 fn from_str(s: &str) -> Result<Self, Self::Err> {
116 Self::new(s.into())
117 }
118}
119
120impl Display for KeyId {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 self.0.fmt(f)
123 }
124}
125
126impl TryFrom<&str> for KeyId {
127 type Error = Error;
128
129 fn try_from(value: &str) -> Result<Self, Self::Error> {
130 Self::from_str(value)
131 }
132}
133
134impl TryFrom<&String> for KeyId {
135 type Error = Error;
136
137 fn try_from(value: &String) -> Result<Self, Self::Error> {
138 Self::from_str(value)
139 }
140}
141
142impl TryFrom<String> for KeyId {
143 type Error = Error;
144
145 fn try_from(value: String) -> Result<Self, Self::Error> {
146 Self::new(value)
147 }
148}
149
150/// The validated setup for a cryptographic signing key
151#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
152pub struct SigningKeySetup {
153 key_id: KeyId,
154 key_type: KeyType,
155 key_mechanisms: Vec<KeyMechanism>,
156 key_length: Option<u32>,
157 signature_type: SignatureType,
158 key_context: CryptographicKeyContext,
159}
160
161impl SigningKeySetup {
162 /// Creates a new [`SigningKeySetup`]
163 ///
164 /// # Errors
165 ///
166 /// Returns an [`Error::Key`][`crate::Error::Key`] if the key setup is not valid in itself or
167 /// can not be used for signing operations in the respective cryptographic context.
168 ///
169 /// # Examples
170 ///
171 /// ```
172 /// use nethsm::{
173 /// CryptographicKeyContext,
174 /// KeyMechanism,
175 /// KeyType,
176 /// OpenPgpUserIdList,
177 /// SignatureType,
178 /// SigningKeySetup,
179 /// };
180 ///
181 /// # fn main() -> testresult::TestResult {
182 /// SigningKeySetup::new(
183 /// "key1".parse()?,
184 /// KeyType::Curve25519,
185 /// vec![KeyMechanism::EdDsaSignature],
186 /// None,
187 /// SignatureType::EdDsa,
188 /// CryptographicKeyContext::Raw,
189 /// )?;
190 ///
191 /// SigningKeySetup::new(
192 /// "key1".parse()?,
193 /// KeyType::Curve25519,
194 /// vec![KeyMechanism::EdDsaSignature],
195 /// None,
196 /// SignatureType::EdDsa,
197 /// CryptographicKeyContext::OpenPgp {
198 /// user_ids: OpenPgpUserIdList::new(vec![
199 /// "Foobar McFooface <foobar@mcfooface.org>".parse()?,
200 /// ])?,
201 /// version: "v4".parse()?,
202 /// },
203 /// )?;
204 ///
205 /// // this fails because Curve25519 does not support the ECDSA key mechanism
206 /// assert!(
207 /// SigningKeySetup::new(
208 /// "key1".parse()?,
209 /// KeyType::Curve25519,
210 /// vec![KeyMechanism::EcdsaSignature],
211 /// None,
212 /// SignatureType::EdDsa,
213 /// CryptographicKeyContext::OpenPgp {
214 /// user_ids: OpenPgpUserIdList::new(vec![
215 /// "Foobar McFooface <foobar@mcfooface.org>".parse()?
216 /// ])?,
217 /// version: "v4".parse()?,
218 /// },
219 /// )
220 /// .is_err()
221 /// );
222 /// # Ok(())
223 /// # }
224 /// ```
225 pub fn new(
226 key_id: KeyId,
227 key_type: KeyType,
228 key_mechanisms: Vec<KeyMechanism>,
229 key_length: Option<u32>,
230 signature_type: SignatureType,
231 cryptographic_key_context: CryptographicKeyContext,
232 ) -> Result<Self, Error> {
233 key_type_matches_mechanisms(key_type, &key_mechanisms)?;
234 key_type_matches_length(key_type, key_length)?;
235 key_type_and_mechanisms_match_signature_type(key_type, &key_mechanisms, signature_type)?;
236 cryptographic_key_context.validate_signing_key_setup(
237 key_type,
238 &key_mechanisms,
239 signature_type,
240 )?;
241
242 Ok(Self {
243 key_id,
244 key_type,
245 key_mechanisms,
246 key_length,
247 signature_type,
248 key_context: cryptographic_key_context,
249 })
250 }
251
252 /// Returns the [`KeyId`]
253 pub fn get_key_id(&self) -> KeyId {
254 self.key_id.clone()
255 }
256
257 /// Returns the [`KeyType`]
258 pub fn get_key_type(&self) -> KeyType {
259 self.key_type
260 }
261
262 /// Returns the list of [`KeyMechanism`]s
263 pub fn get_key_mechanisms(&self) -> Vec<KeyMechanism> {
264 self.key_mechanisms.clone()
265 }
266
267 /// Returns the optional key length
268 pub fn get_key_length(&self) -> Option<u32> {
269 self.key_length
270 }
271
272 /// Returns the [`SignatureType`]
273 pub fn get_signature_type(&self) -> SignatureType {
274 self.signature_type
275 }
276
277 /// Returns the [`CryptographicKeyContext`]
278 pub fn get_key_context(&self) -> CryptographicKeyContext {
279 self.key_context.clone()
280 }
281}
282
283/// Ensures that a [`TlsKeyType`] is compatible with an optional key length
284///
285/// # Errors
286///
287/// Returns an [`Error::Key`][`crate::Error::Key`] if
288/// * `tls_key_type` is one of [`TlsKeyType::Curve25519`], [`TlsKeyType::EcP224`],
289/// [`TlsKeyType::EcP256`], [`TlsKeyType::EcP384`] or [`TlsKeyType::EcP521`] and `length` is
290/// [`Some`].
291/// * `tls_key_type` is [`TlsKeyType::Rsa`] and `length` is [`None`].
292/// * `tls_key_type` is [`TlsKeyType::Rsa`] and `length` is not [`Some`] value equal to or greater
293/// than [`MIN_RSA_BIT_LENGTH`].
294///
295/// # Examples
296///
297/// ```
298/// use nethsm::{TlsKeyType, tls_key_type_matches_length};
299///
300/// # fn main() -> testresult::TestResult {
301/// tls_key_type_matches_length(TlsKeyType::Curve25519, None)?;
302/// tls_key_type_matches_length(TlsKeyType::EcP224, None)?;
303/// tls_key_type_matches_length(TlsKeyType::Rsa, Some(2048))?;
304///
305/// // this fails because elliptic curve keys have their length set intrinsically
306/// assert!(tls_key_type_matches_length(TlsKeyType::Curve25519, Some(2048)).is_err());
307/// // this fails because a bit length of 1024 is unsafe to use for RSA keys
308/// assert!(tls_key_type_matches_length(TlsKeyType::Rsa, Some(1024)).is_err());
309/// # Ok(())
310/// # }
311/// ```
312pub fn tls_key_type_matches_length(
313 tls_key_type: TlsKeyType,
314 length: Option<u32>,
315) -> Result<(), Error> {
316 match tls_key_type {
317 TlsKeyType::Curve25519
318 | TlsKeyType::EcP224
319 | TlsKeyType::EcP256
320 | TlsKeyType::EcP384
321 | TlsKeyType::EcP521 => {
322 if length.is_some() {
323 Err(Error::TlsKeyLengthUnsupported { tls_key_type })
324 } else {
325 Ok(())
326 }
327 }
328 TlsKeyType::Rsa => match length {
329 None => Err(Error::TlsKeyLengthRequired { tls_key_type }),
330 Some(length) => {
331 if length < MIN_RSA_BIT_LENGTH {
332 Err(Error::InvalidTlsKeyLengthRsa { key_length: length })
333 } else {
334 Ok(())
335 }
336 }
337 },
338 }
339}