nethsm/key.rs
1use std::{fmt::Display, str::FromStr};
2
3use serde::{Deserialize, Serialize};
4use signstar_crypto::key::MIN_RSA_BIT_LENGTH;
5
6use crate::TlsKeyType;
7
8/// An error that can occur when dealing with keys.
9#[derive(Debug, thiserror::Error)]
10pub enum Error {
11    /// Elliptic curve TLS keys do not support providing a length
12    #[error("Elliptic curve key ({tls_key_type}) does not support setting length")]
13    TlsKeyLengthUnsupported { tls_key_type: TlsKeyType },
14
15    /// RSA TLS key type requires setting a length
16    #[error("Generating a key of type {tls_key_type} requires setting a length")]
17    TlsKeyLengthRequired { tls_key_type: TlsKeyType },
18
19    /// RSA TLS key is generated with unsafe key length (smaller than 2048)
20    #[error(
21        "RSA keys shorter than {MIN_RSA_BIT_LENGTH} are not supported. A key length of {key_length} is unsafe!"
22    )]
23    InvalidTlsKeyLengthRsa { key_length: u32 },
24
25    /// One or more [`KeyId`]s are not valid.
26    #[error("Invalid Key ID{}: {}", if key_ids.len() == 1 {"s"} else { " "}, key_ids.join(", "))]
27    InvalidKeyIds {
28        /// A list of strings representing invalid [`KeyId`]s.
29        key_ids: Vec<String>,
30    },
31
32    /// A signstar_crypto key  error.
33    #[error("A signstar_crypto::key error:\n{0}")]
34    SignstarCryptoKey(#[from] signstar_crypto::key::Error),
35}
36
37/// A unique key identifier for a private key on a NetHSM.
38///
39/// A [`KeyId`]s must be in the character set `[a-z0-9]` and must not be empty.
40/// It is used in [key management] on a NetHSM and is unique in its scope.
41/// The same [`KeyId`] may exist system-wide and in one or several [namespaces], but no duplicate
42/// [`KeyId`] can exist system-wide or in the same namespace.
43///
44/// [key management]: https://docs.nitrokey.com/nethsm/operation#key-management
45/// [namespaces]: https://docs.nitrokey.com/nethsm/administration#namespaces
46#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
47#[serde(into = "String", try_from = "String")]
48pub struct KeyId(String);
49
50impl KeyId {
51    /// Constructs a new Key ID from a `String`.
52    ///
53    /// Validates the input string and returns [`crate::Error::Key`]
54    /// if it is invalid.
55    ///
56    /// # Errors
57    ///
58    /// Returns an [`Error`][`crate::Error`] if
59    /// * string contains characters outside of the allowed range (`[a-z0-9]`)
60    /// * string is empty
61    ///
62    /// # Examples
63    ///
64    /// ```
65    /// use nethsm::KeyId;
66    ///
67    /// assert!(KeyId::new("key1".into()).is_ok());
68    /// assert!(KeyId::new("key".into()).is_ok());
69    ///
70    /// // the input can not contain invalid chars
71    /// assert!(KeyId::new("key1#".into()).is_err());
72    /// assert!(KeyId::new("key~1".into()).is_err());
73    ///
74    /// // the key must be non-empty
75    /// assert!(KeyId::new("".into()).is_err());
76    /// ```
77    pub fn new(key_id: String) -> Result<Self, Error> {
78        if key_id.is_empty()
79            || !key_id.chars().all(|char| {
80                char.is_numeric() || (char.is_ascii_lowercase() && char.is_ascii_alphabetic())
81            })
82        {
83            return Err(Error::InvalidKeyIds {
84                key_ids: vec![key_id],
85            });
86        }
87
88        Ok(Self(key_id))
89    }
90}
91
92impl AsRef<str> for KeyId {
93    fn as_ref(&self) -> &str {
94        &self.0
95    }
96}
97
98impl From<KeyId> for String {
99    fn from(value: KeyId) -> Self {
100        value.0
101    }
102}
103
104impl FromStr for KeyId {
105    type Err = Error;
106    fn from_str(s: &str) -> Result<Self, Self::Err> {
107        Self::new(s.into())
108    }
109}
110
111impl Display for KeyId {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        self.0.fmt(f)
114    }
115}
116
117impl TryFrom<&str> for KeyId {
118    type Error = Error;
119
120    fn try_from(value: &str) -> Result<Self, Self::Error> {
121        Self::from_str(value)
122    }
123}
124
125impl TryFrom<&String> for KeyId {
126    type Error = Error;
127
128    fn try_from(value: &String) -> Result<Self, Self::Error> {
129        Self::from_str(value)
130    }
131}
132
133impl TryFrom<String> for KeyId {
134    type Error = Error;
135
136    fn try_from(value: String) -> Result<Self, Self::Error> {
137        Self::new(value)
138    }
139}
140
141/// Ensures that a [`TlsKeyType`] is compatible with an optional key length
142///
143/// # Errors
144///
145/// Returns an [`Error::Key`][`crate::Error::Key`] if
146/// * `tls_key_type` is one of [`TlsKeyType::Curve25519`], [`TlsKeyType::EcP224`],
147///   [`TlsKeyType::EcP256`], [`TlsKeyType::EcP384`] or [`TlsKeyType::EcP521`] and `length` is
148///   [`Some`].
149/// * `tls_key_type` is [`TlsKeyType::Rsa`] and `length` is [`None`].
150/// * `tls_key_type` is [`TlsKeyType::Rsa`] and `length` is not [`Some`] value equal to or greater
151///   than [`MIN_RSA_BIT_LENGTH`].
152///
153/// # Examples
154///
155/// ```
156/// use nethsm::{TlsKeyType, tls_key_type_matches_length};
157///
158/// # fn main() -> testresult::TestResult {
159/// tls_key_type_matches_length(TlsKeyType::Curve25519, None)?;
160/// tls_key_type_matches_length(TlsKeyType::EcP224, None)?;
161/// tls_key_type_matches_length(TlsKeyType::Rsa, Some(2048))?;
162///
163/// // this fails because elliptic curve keys have their length set intrinsically
164/// assert!(tls_key_type_matches_length(TlsKeyType::Curve25519, Some(2048)).is_err());
165/// // this fails because a bit length of 1024 is unsafe to use for RSA keys
166/// assert!(tls_key_type_matches_length(TlsKeyType::Rsa, Some(1024)).is_err());
167/// # Ok(())
168/// # }
169/// ```
170pub fn tls_key_type_matches_length(
171    tls_key_type: TlsKeyType,
172    length: Option<u32>,
173) -> Result<(), Error> {
174    match tls_key_type {
175        TlsKeyType::Curve25519
176        | TlsKeyType::EcP224
177        | TlsKeyType::EcP256
178        | TlsKeyType::EcP384
179        | TlsKeyType::EcP521 => {
180            if length.is_some() {
181                Err(Error::TlsKeyLengthUnsupported { tls_key_type })
182            } else {
183                Ok(())
184            }
185        }
186        TlsKeyType::Rsa => match length {
187            None => Err(Error::TlsKeyLengthRequired { tls_key_type }),
188            Some(length) => {
189                if length < MIN_RSA_BIT_LENGTH {
190                    Err(Error::InvalidTlsKeyLengthRsa { key_length: length })
191                } else {
192                    Ok(())
193                }
194            }
195        },
196    }
197}