ruma_common/identifiers/
key_id.rs

1use std::{
2    cmp::Ordering,
3    hash::{Hash, Hasher},
4    marker::PhantomData,
5};
6
7use ruma_macros::IdDst;
8
9use super::{
10    Base64PublicKey, Base64PublicKeyOrDeviceId, DeviceId, DeviceKeyAlgorithm, KeyName,
11    OneTimeKeyAlgorithm, OneTimeKeyName, ServerSigningKeyVersion,
12    crypto_algorithms::SigningKeyAlgorithm,
13};
14
15/// A key algorithm and key name delimited by a colon.
16///
17/// Examples of the use of this struct are [`DeviceKeyId`], which identifies a Ed25519 or Curve25519
18/// [device key](https://spec.matrix.org/latest/client-server-api/#device-keys), and
19/// [`CrossSigningKeyId`], which identifies a user's
20/// [cross signing key](https://spec.matrix.org/latest/client-server-api/#cross-signing).
21///
22/// This format of identifier is often used in the `signatures` field of
23/// [signed JSON](https://spec.matrix.org/latest/appendices/#signing-details)
24/// where it is referred to as a "signing key identifier".
25///
26/// This struct is rarely used directly - instead you should expect to use one of the type aliases
27/// that rely on it like [`CrossSigningKeyId`] or [`DeviceSigningKeyId`].
28///
29/// # Examples
30///
31/// To parse a colon-separated identifier:
32///
33/// ```
34/// use ruma_common::DeviceKeyId;
35///
36/// let k = DeviceKeyId::parse("ed25519:1").unwrap();
37/// assert_eq!(k.algorithm().as_str(), "ed25519");
38/// assert_eq!(k.key_name(), "1");
39/// ```
40///
41/// To construct a colon-separated identifier from its parts:
42///
43/// ```
44/// use ruma_common::{DeviceKeyAlgorithm, DeviceKeyId};
45///
46/// let k = DeviceKeyId::from_parts(DeviceKeyAlgorithm::Curve25519, "MYDEVICE".into());
47/// assert_eq!(k.as_str(), "curve25519:MYDEVICE");
48/// ```
49#[repr(transparent)]
50#[derive(IdDst)]
51#[ruma_id(
52    validate = ruma_identifiers_validation::key_id::validate::<K>,
53)]
54pub struct KeyId<A: KeyAlgorithm, K: KeyName + ?Sized>(PhantomData<(A, K)>, str);
55
56impl<A: KeyAlgorithm, K: KeyName + ?Sized> KeyId<A, K> {
57    /// Creates a new `KeyId` from an algorithm and key name.
58    pub fn from_parts(algorithm: A, key_name: &K) -> OwnedKeyId<A, K> {
59        let algorithm = algorithm.as_ref();
60        let key_name = key_name.as_ref();
61
62        let mut res = String::with_capacity(algorithm.len() + 1 + key_name.len());
63        res.push_str(algorithm);
64        res.push(':');
65        res.push_str(key_name);
66
67        Self::from_borrowed(&res).to_owned()
68    }
69
70    /// Returns key algorithm of the key ID - the part that comes before the colon.
71    ///
72    /// # Example
73    ///
74    /// ```
75    /// use ruma_common::{DeviceKeyAlgorithm, DeviceKeyId};
76    ///
77    /// let k = DeviceKeyId::parse("ed25519:1").unwrap();
78    /// assert_eq!(k.algorithm(), DeviceKeyAlgorithm::Ed25519);
79    /// ```
80    pub fn algorithm(&self) -> A {
81        A::from(&self.as_str()[..self.colon_idx()])
82    }
83
84    /// Returns the key name of the key ID - the part that comes after the colon.
85    ///
86    /// # Example
87    ///
88    /// ```
89    /// use ruma_common::{DeviceKeyId, device_id};
90    ///
91    /// let k = DeviceKeyId::parse("ed25519:DEV1").unwrap();
92    /// assert_eq!(k.key_name(), device_id!("DEV1"));
93    /// ```
94    pub fn key_name<'a>(&'a self) -> &'a K
95    where
96        &'a K: TryFrom<&'a str>,
97    {
98        <&'a K>::try_from(&self.as_str()[(self.colon_idx() + 1)..])
99            .unwrap_or_else(|_| unreachable!())
100    }
101
102    fn colon_idx(&self) -> usize {
103        self.as_str().find(':').unwrap()
104    }
105}
106
107/// Algorithm + key name for signing keys.
108pub type SigningKeyId<K> = KeyId<SigningKeyAlgorithm, K>;
109
110/// Algorithm + key name for signing keys.
111pub type OwnedSigningKeyId<K> = OwnedKeyId<SigningKeyAlgorithm, K>;
112
113/// Algorithm + key name for homeserver signing keys.
114pub type ServerSigningKeyId = SigningKeyId<ServerSigningKeyVersion>;
115
116/// Algorithm + key name for homeserver signing keys.
117pub type OwnedServerSigningKeyId = OwnedSigningKeyId<ServerSigningKeyVersion>;
118
119/// Algorithm + key name for [device signing keys].
120///
121/// [device signing keys]: https://spec.matrix.org/latest/client-server-api/#device-keys
122pub type DeviceSigningKeyId = SigningKeyId<DeviceId>;
123
124/// Algorithm + key name for [device signing] keys.
125///
126/// [device signing keys]: https://spec.matrix.org/latest/client-server-api/#device-keys
127pub type OwnedDeviceSigningKeyId = OwnedSigningKeyId<DeviceId>;
128
129/// Algorithm + key name for [cross-signing] keys.
130///
131/// [cross-signing]: https://spec.matrix.org/latest/client-server-api/#cross-signing
132pub type CrossSigningKeyId = SigningKeyId<Base64PublicKey>;
133
134/// Algorithm + key name for [cross-signing] keys.
135///
136/// [cross-signing]: https://spec.matrix.org/latest/client-server-api/#cross-signing
137pub type OwnedCrossSigningKeyId = OwnedSigningKeyId<Base64PublicKey>;
138
139/// Algorithm + key name for [cross-signing] or [device signing] keys.
140///
141/// [cross-signing]: https://spec.matrix.org/latest/client-server-api/#cross-signing
142/// [device signing]: https://spec.matrix.org/latest/client-server-api/#device-keys
143pub type CrossSigningOrDeviceSigningKeyId = SigningKeyId<Base64PublicKeyOrDeviceId>;
144
145/// Algorithm + key name for [cross-signing] or [device signing] keys.
146///
147/// [cross-signing]: https://spec.matrix.org/latest/client-server-api/#cross-signing
148/// [device signing]: https://spec.matrix.org/latest/client-server-api/#device-keys
149pub type OwnedCrossSigningOrDeviceSigningKeyId = OwnedSigningKeyId<Base64PublicKeyOrDeviceId>;
150
151/// Algorithm + key name for [device keys].
152///
153/// [device keys]: https://spec.matrix.org/latest/client-server-api/#device-keys
154pub type DeviceKeyId = KeyId<DeviceKeyAlgorithm, DeviceId>;
155
156/// Algorithm + key name for [device keys].
157///
158/// [device keys]: https://spec.matrix.org/latest/client-server-api/#device-keys
159pub type OwnedDeviceKeyId = OwnedKeyId<DeviceKeyAlgorithm, DeviceId>;
160
161/// Algorithm + key name for [one-time and fallback keys].
162///
163/// [one-time and fallback keys]: https://spec.matrix.org/latest/client-server-api/#one-time-and-fallback-keys
164pub type OneTimeKeyId = KeyId<OneTimeKeyAlgorithm, OneTimeKeyName>;
165
166/// Algorithm + key name for [one-time and fallback keys].
167///
168/// [one-time and fallback keys]: https://spec.matrix.org/latest/client-server-api/#one-time-and-fallback-keys
169pub type OwnedOneTimeKeyId = OwnedKeyId<OneTimeKeyAlgorithm, OneTimeKeyName>;
170
171// The following impls are usually derived using the std macros.
172// They are implemented manually here to avoid unnecessary bounds.
173impl<A: KeyAlgorithm, K: KeyName + ?Sized> PartialEq for KeyId<A, K> {
174    fn eq(&self, other: &Self) -> bool {
175        self.as_str() == other.as_str()
176    }
177}
178
179impl<A: KeyAlgorithm, K: KeyName + ?Sized> Eq for KeyId<A, K> {}
180
181impl<A: KeyAlgorithm, K: KeyName + ?Sized> PartialOrd for KeyId<A, K> {
182    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
183        Some(self.cmp(other))
184    }
185}
186
187impl<A: KeyAlgorithm, K: KeyName + ?Sized> Ord for KeyId<A, K> {
188    fn cmp(&self, other: &Self) -> Ordering {
189        Ord::cmp(self.as_str(), other.as_str())
190    }
191}
192
193impl<A: KeyAlgorithm, K: KeyName + ?Sized> Hash for KeyId<A, K> {
194    fn hash<H: Hasher>(&self, state: &mut H) {
195        self.as_str().hash(state);
196    }
197}
198
199/// The algorithm of a key.
200pub trait KeyAlgorithm: for<'a> From<&'a str> + AsRef<str> {}
201
202impl KeyAlgorithm for SigningKeyAlgorithm {}
203
204impl KeyAlgorithm for DeviceKeyAlgorithm {}
205
206impl KeyAlgorithm for OneTimeKeyAlgorithm {}
207
208/// An opaque identifier type to use with [`KeyId`].
209///
210/// This type has no semantic value and no validation is done. It is meant to be able to use the
211/// [`KeyId`] API without validating the key name.
212#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdDst)]
213pub struct AnyKeyName(str);
214
215impl KeyName for AnyKeyName {
216    fn validate(_s: &str) -> Result<(), ruma_common::IdParseError> {
217        Ok(())
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use assert_matches2::assert_matches;
224    use ruma_identifiers_validation::Error;
225
226    use super::DeviceKeyId;
227
228    #[test]
229    fn algorithm_and_key_name_are_correctly_extracted() {
230        let key_id = DeviceKeyId::parse("ed25519:MYDEVICE").expect("Should parse correctly");
231        assert_eq!(key_id.algorithm().as_str(), "ed25519");
232        assert_eq!(key_id.key_name(), "MYDEVICE");
233    }
234
235    #[test]
236    fn empty_key_name_is_correctly_extracted() {
237        let key_id = DeviceKeyId::parse("ed25519:").expect("Should parse correctly");
238        assert_eq!(key_id.algorithm().as_str(), "ed25519");
239        assert_eq!(key_id.key_name(), "");
240    }
241
242    #[test]
243    fn missing_colon_fails_to_parse() {
244        let error = DeviceKeyId::parse("ed25519_MYDEVICE").expect_err("Should fail to parse");
245        assert_matches!(error, Error::MissingColon);
246    }
247
248    #[test]
249    fn empty_algorithm_fails_to_parse() {
250        let error = DeviceKeyId::parse(":MYDEVICE").expect_err("Should fail to parse");
251        // Weirdly, this also reports MissingColon
252        assert_matches!(error, Error::MissingColon);
253    }
254}