Skip to main content

signstar_yubihsm2/object/
id.rs

1//! YubiHSM2 objects.
2
3use std::{
4    fmt::Display,
5    num::{NonZeroU16, NonZeroUsize},
6    str::FromStr,
7};
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11use yubihsm::object::{Handle, Type};
12
13/// The fundamental representation of an object identifier.
14///
15/// Wraps [`NonZeroU16`] to reflect on the limitations imposed by a YubiHSM2 [Object ID].
16///
17/// # Note
18///
19/// Limits the allowed values to a maximum of `256`.
20///
21/// [object-id]: https://docs.yubico.com/hardware/yubihsm-2/hsm-2-user-guide/hsm2-core-concepts.html#object-id
22#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
23#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
24#[cfg_attr(feature = "serde", serde(into = "u16", try_from = "NonZeroU16"))]
25pub struct Id(NonZeroU16);
26
27impl Id {
28    /// Creates a new [`Id`] from a [`NonZeroU16`].
29    pub fn new(num: NonZeroU16) -> Result<Self, crate::Error> {
30        if num.get() > 256 {
31            return Err(crate::object::Error::InvalidId {
32                reason: "an ID must be a number between 1-256".to_string(),
33                id: num.to_string(),
34            }
35            .into());
36        }
37
38        Ok(Self(num))
39    }
40
41    /// Returns the inner [`NonZeroU16`].
42    pub fn get(&self) -> NonZeroU16 {
43        self.0
44    }
45}
46
47impl AsRef<NonZeroU16> for Id {
48    fn as_ref(&self) -> &NonZeroU16 {
49        &self.0
50    }
51}
52
53impl Display for Id {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        self.get().fmt(f)
56    }
57}
58
59impl TryFrom<u16> for Id {
60    type Error = crate::Error;
61
62    fn try_from(value: u16) -> Result<Self, Self::Error> {
63        Self::new(
64            NonZeroU16::new(value).ok_or(crate::object::Error::InvalidId {
65                reason: "it must not be 0".to_string(),
66                id: value.to_string(),
67            })?,
68        )
69    }
70}
71
72impl TryFrom<NonZeroU16> for Id {
73    type Error = crate::Error;
74
75    fn try_from(value: NonZeroU16) -> Result<Self, Self::Error> {
76        Self::new(value)
77    }
78}
79
80impl TryFrom<NonZeroUsize> for Id {
81    type Error = crate::Error;
82
83    fn try_from(value: NonZeroUsize) -> Result<Self, Self::Error> {
84        Self::try_from(NonZeroU16::try_from(value).map_err(|source| {
85            crate::object::Error::InvalidId {
86                reason: source.to_string(),
87                id: value.to_string(),
88            }
89        })?)
90    }
91}
92
93impl TryFrom<usize> for Id {
94    type Error = crate::Error;
95
96    fn try_from(value: usize) -> Result<Self, Self::Error> {
97        Self::try_from(NonZeroUsize::try_from(value).map_err(|source| {
98            crate::object::Error::InvalidId {
99                reason: source.to_string(),
100                id: value.to_string(),
101            }
102        })?)
103    }
104}
105
106impl FromStr for Id {
107    type Err = crate::Error;
108
109    fn from_str(s: &str) -> Result<Self, Self::Err> {
110        Self::new(
111            NonZeroU16::from_str(s).map_err(|source| crate::object::Error::InvalidId {
112                reason: source.to_string(),
113                id: s.to_string(),
114            })?,
115        )
116    }
117}
118
119impl From<&Id> for u16 {
120    fn from(value: &Id) -> Self {
121        value.get().get()
122    }
123}
124
125impl From<Id> for u16 {
126    fn from(value: Id) -> Self {
127        value.get().get()
128    }
129}
130
131/// Identifier for an object stored on a YubiHSM2.
132///
133/// The YubiHSM2 provides several different types of objects.
134/// Each object type serves as a namespace, which means that an object of a specific type is
135/// isolated from objects of a different type.
136#[derive(Debug)]
137#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
138#[cfg_attr(
139    feature = "serde",
140    serde(tag = "object_type", content = "object_id", rename_all = "kebab-case")
141)]
142pub enum ObjectId {
143    /// Asymmetric key used for data signing.
144    AsymmetricKey(Id),
145
146    /// Authentication key used for authentication.
147    AuthenticationKey(Id),
148
149    /// Wrapping key used for exporting other objects under wrap.
150    WrappingKey(Id),
151
152    /// Opaque byte arrays which hold implementation-defined data, e.g. an OpenPGP certificate.
153    Opaque(Id),
154
155    /// HMAC-signing key.
156    Hmac(Id),
157
158    /// SSH certificate template.
159    Template(Id),
160
161    /// One-Time-Password AEAD key.
162    Otp(Id),
163}
164
165impl ObjectId {
166    /// Returns the raw identifier of the YubiHSM2 object.
167    pub fn id(&self) -> Id {
168        match self {
169            ObjectId::AsymmetricKey(id) => *id,
170            ObjectId::AuthenticationKey(id) => *id,
171            ObjectId::WrappingKey(id) => *id,
172            ObjectId::Opaque(id) => *id,
173            ObjectId::Hmac(id) => *id,
174            ObjectId::Template(id) => *id,
175            ObjectId::Otp(id) => *id,
176        }
177    }
178
179    /// Returns the type of the YubiHSM2 object.
180    pub fn object_type(&self) -> Type {
181        match self {
182            ObjectId::AsymmetricKey(_) => Type::AsymmetricKey,
183            ObjectId::AuthenticationKey(_) => Type::AuthenticationKey,
184            ObjectId::WrappingKey(_) => Type::WrapKey,
185            ObjectId::Opaque(_) => Type::Opaque,
186            ObjectId::Hmac(_) => Type::HmacKey,
187            ObjectId::Template(_) => Type::Template,
188            ObjectId::Otp(_) => Type::OtpAeadKey,
189        }
190    }
191}
192
193impl TryFrom<Handle> for ObjectId {
194    type Error = crate::Error;
195
196    fn try_from(value: Handle) -> Result<Self, Self::Error> {
197        let id = Id::try_from(value.object_id)?;
198
199        Ok(match value.object_type {
200            Type::Opaque => ObjectId::Opaque(id),
201            Type::AuthenticationKey => ObjectId::AuthenticationKey(id),
202            Type::AsymmetricKey => ObjectId::AsymmetricKey(id),
203            Type::WrapKey => ObjectId::WrappingKey(id),
204            Type::HmacKey => ObjectId::Hmac(id),
205            Type::Template => ObjectId::Template(id),
206            Type::OtpAeadKey => ObjectId::Otp(id),
207        })
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use rstest::rstest;
214
215    use super::*;
216
217    #[test]
218    fn id_new_too_large() {
219        assert!(Id::new(NonZeroU16::new(257u16).unwrap()).is_err());
220    }
221
222    #[test]
223    fn id_from_str_too_large() {
224        assert!(Id::from_str("257").is_err());
225    }
226
227    #[test]
228    fn id_try_from_str_invalid_nonzero_u16() {
229        assert!(Id::from_str("foo").is_err());
230    }
231
232    #[test]
233    fn id_try_from_non_zero_usize_too_large() {
234        assert!(Id::try_from(NonZeroUsize::new(257).unwrap()).is_err());
235    }
236
237    #[test]
238    fn id_try_from_non_zero_usize_invalid_non_zero_u16() {
239        assert!(Id::try_from(NonZeroUsize::new(65536).unwrap()).is_err());
240    }
241
242    #[rstest]
243    #[case::zero(0usize)]
244    #[case::too_large(257usize)]
245    fn id_from_usize_fails(#[case] input: usize) {
246        assert!(Id::try_from(input).is_err());
247    }
248}