Skip to main content

openmls/credentials/
mod.rs

1//! # Credentials
2//!
3//! A [`Credential`] contains identifying information about the client that
4//! created it. [`Credential`]s represent clients in MLS groups and are used to
5//! authenticate their messages. Each
6//! [`KeyPackage`](crate::key_packages::KeyPackage), as well as each client
7//! (leaf node) in the group (tree), contains a [`Credential`] and is
8//! authenticated.
9//!
10//! The [`Credential`] must be checked by an authentication server and the
11//! application. This process is out of scope for MLS.
12//!
13//! Clients can create a [`Credential`].
14//!
15//! The MLS protocol allows the [`Credential`] representing a client in a group
16//! to change over time. Concretely, members can issue an Update proposal or a
17//! Full Commit to update their [`LeafNode`],
18//! including the [`Credential`] in it. The Update must be authenticated using
19//! the signature public key corresponding to the old [`Credential`].
20//!
21//! When receiving a credential update from another member, applications must
22//! query the Authentication Service to ensure the new credential is valid.
23//!
24//! There are multiple [`CredentialType`]s, although OpenMLS currently only
25//! supports the [`BasicCredential`].
26
27use std::io::{Read, Write};
28
29use openmls_traits::signatures::Signer;
30use serde::{Deserialize, Serialize};
31use tls_codec::{
32    Deserialize as TlsDeserializeTrait, DeserializeBytes, Error, Serialize as TlsSerializeTrait,
33    Size, TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize, VLBytes,
34};
35
36#[cfg(test)]
37mod tests;
38
39use crate::{ciphersuite::SignaturePublicKey, group::Member, treesync::LeafNode};
40use errors::*;
41
42#[cfg(doc)]
43use crate::group::MlsGroup;
44
45// Public
46pub mod errors;
47
48/// CredentialType.
49///
50/// This enum contains variants for the different Credential Types.
51///
52/// ```c
53/// // See IANA registry for registered values
54/// uint16 CredentialType;
55/// ```
56///
57/// **IANA Considerations**
58///
59/// | Value            | Name                     | R | Ref      |
60/// |:-----------------|:-------------------------|:--|:---------|
61/// | 0x0000           | RESERVED                 | - | RFC XXXX |
62/// | 0x0001           | basic                    | Y | RFC XXXX |
63/// | 0x0002           | x509                     | Y | RFC XXXX |
64/// | 0x0A0A           | GREASE                   | Y | RFC XXXX |
65/// | 0x1A1A           | GREASE                   | Y | RFC XXXX |
66/// | 0x2A2A           | GREASE                   | Y | RFC XXXX |
67/// | 0x3A3A           | GREASE                   | Y | RFC XXXX |
68/// | 0x4A4A           | GREASE                   | Y | RFC XXXX |
69/// | 0x5A5A           | GREASE                   | Y | RFC XXXX |
70/// | 0x6A6A           | GREASE                   | Y | RFC XXXX |
71/// | 0x7A7A           | GREASE                   | Y | RFC XXXX |
72/// | 0x8A8A           | GREASE                   | Y | RFC XXXX |
73/// | 0x9A9A           | GREASE                   | Y | RFC XXXX |
74/// | 0xAAAA           | GREASE                   | Y | RFC XXXX |
75/// | 0xBABA           | GREASE                   | Y | RFC XXXX |
76/// | 0xCACA           | GREASE                   | Y | RFC XXXX |
77/// | 0xDADA           | GREASE                   | Y | RFC XXXX |
78/// | 0xEAEA           | GREASE                   | Y | RFC XXXX |
79/// | 0xF000  - 0xFFFF | Reserved for Private Use | - | RFC XXXX |
80#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
81#[cfg_attr(
82    feature = "0-8-1-storage-format",
83    derive(serde::Serialize, serde::Deserialize)
84)]
85#[cfg_attr(
86    not(feature = "0-8-1-storage-format"),
87    derive(
88        openmls_serialization_helpers::Serialize,
89        openmls_serialization_helpers::Deserialize,
90    )
91)]
92#[repr(u16)]
93pub enum CredentialType {
94    #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 0)]
95    /// A [`BasicCredential`]
96    Basic = 1,
97    #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 1)]
98    /// An X.509 [`Certificate`]
99    X509 = 2,
100    #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 3)]
101    /// A GREASE credential type for ensuring extensibility.
102    Grease(u16),
103    #[cfg_attr(not(feature = "0-8-1-storage-format"), storage_tag = 2)]
104    /// Another type of credential that is not in the MLS protocol spec.
105    Other(u16),
106}
107
108impl CredentialType {
109    /// Returns true if this is a GREASE credential type.
110    ///
111    /// GREASE values are used to ensure implementations properly handle unknown
112    /// credential types. See [RFC 9420 Section 13.5](https://www.rfc-editor.org/rfc/rfc9420.html#section-13.5).
113    pub fn is_grease(&self) -> bool {
114        matches!(self, CredentialType::Grease(v) if crate::grease::is_grease_value(*v))
115    }
116}
117
118impl Size for CredentialType {
119    fn tls_serialized_len(&self) -> usize {
120        2
121    }
122}
123
124impl TlsDeserializeTrait for CredentialType {
125    fn tls_deserialize<R: Read>(bytes: &mut R) -> Result<Self, Error>
126    where
127        Self: Sized,
128    {
129        let mut extension_type = [0u8; 2];
130        bytes.read_exact(&mut extension_type)?;
131
132        Ok(CredentialType::from(u16::from_be_bytes(extension_type)))
133    }
134}
135
136impl TlsSerializeTrait for CredentialType {
137    fn tls_serialize<W: Write>(&self, writer: &mut W) -> Result<usize, Error> {
138        let value = u16::from(*self);
139        // `Other` and `Grease` accept any value. Refuse to encode one that
140        // would decode as a different credential type.
141        if CredentialType::from(value) != *self {
142            return Err(Error::EncodingError(format!(
143                "{self:?} is encoded as {value:#06x}, which decodes as a different credential type"
144            )));
145        }
146        writer.write_all(&value.to_be_bytes())?;
147
148        Ok(2)
149    }
150}
151
152impl DeserializeBytes for CredentialType {
153    fn tls_deserialize_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error>
154    where
155        Self: Sized,
156    {
157        let mut bytes_ref = bytes;
158        let credential_type = CredentialType::tls_deserialize(&mut bytes_ref)?;
159        Ok((credential_type, bytes_ref))
160    }
161}
162
163impl From<u16> for CredentialType {
164    fn from(value: u16) -> Self {
165        match value {
166            1 => CredentialType::Basic,
167            2 => CredentialType::X509,
168            other if crate::grease::is_grease_value(other) => CredentialType::Grease(other),
169            other => CredentialType::Other(other),
170        }
171    }
172}
173
174impl From<CredentialType> for u16 {
175    fn from(value: CredentialType) -> Self {
176        match value {
177            CredentialType::Basic => 1,
178            CredentialType::X509 => 2,
179            CredentialType::Grease(value) => value,
180            CredentialType::Other(other) => other,
181        }
182    }
183}
184
185/// X.509 Certificate.
186///
187/// This struct contains an X.509 certificate chain.  Note that X.509
188/// certificates are not yet supported by OpenMLS.
189///
190/// ```c
191/// struct {
192///     opaque cert_data<V>;
193/// } Certificate;
194/// ```
195#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
196pub struct Certificate {
197    cert_data: Vec<u8>,
198}
199
200/// Credential.
201///
202/// OpenMLS does not look into credentials and only passes them along.
203/// As such they are opaque to the code in OpenMLS and only the basic necessary
204/// checks and operations are done.
205///
206/// OpenMLS provides an implementation of the [`BasicCredential`].
207///
208/// This struct contains MLS credential data, where the data depends on the
209/// type.
210///
211/// **Note:** While the credential is opaque to OpenMLS, the library must know how
212///           to deserialize it. The implementation only works with credentials
213///           that are encoded as variable-sized vectors.
214///           Other credentials will cause OpenMLS either to crash or exhibit
215///           unexpected behaviour.
216///
217/// ```c
218/// struct {
219///     CredentialType credential_type;
220///     select (Credential.credential_type) {
221///         case basic:
222///             opaque identity<V>;
223///
224///         case x509:
225///             Certificate chain<V>;
226///     };
227/// } Credential;
228/// ```
229#[derive(
230    Debug,
231    PartialEq,
232    Eq,
233    Clone,
234    Serialize,
235    Deserialize,
236    TlsSize,
237    TlsSerialize,
238    TlsDeserialize,
239    TlsDeserializeBytes,
240)]
241pub struct Credential {
242    credential_type: CredentialType,
243    serialized_credential_content: VLBytes,
244}
245
246impl Credential {
247    /// Returns the credential type.
248    pub fn credential_type(&self) -> CredentialType {
249        self.credential_type
250    }
251
252    /// Creates and returns a new [`Credential`] of the given
253    /// [`CredentialType`].
254    pub fn new(credential_type: CredentialType, serialized_credential: Vec<u8>) -> Self {
255        Self {
256            credential_type,
257            serialized_credential_content: serialized_credential.into(),
258        }
259    }
260
261    /// Get this serialized credential content.
262    ///
263    /// This is the content of the `select` statement. It is a TLS serialized
264    /// vector.
265    pub fn serialized_content(&self) -> &[u8] {
266        self.serialized_credential_content.as_slice()
267    }
268
269    /// Get the credential, deserialized.
270    pub fn deserialized<T: tls_codec::Size + tls_codec::Deserialize>(
271        &self,
272    ) -> Result<T, tls_codec::Error> {
273        T::tls_deserialize_exact(&self.serialized_credential_content)
274    }
275}
276
277/// Basic Credential.
278///
279/// A `BasicCredential` as defined in the MLS protocol spec. It exposes only an
280/// `identity` to represent the client.
281///
282/// Note that this credential does not contain any key material or any other
283/// information.
284///
285/// OpenMLS provides an implementation of signature keys for convenience in the
286/// `openmls_basic_credential` crate.
287#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
288pub struct BasicCredential {
289    identity: VLBytes,
290}
291
292impl BasicCredential {
293    /// Create a new basic credential.
294    ///
295    /// Errors
296    ///
297    /// Returns a [`BasicCredentialError`] if the length of the identity is too
298    /// large to be encoded as a variable-length vector.
299    pub fn new(identity: Vec<u8>) -> Self {
300        Self {
301            identity: identity.into(),
302        }
303    }
304
305    /// Get the identity of this basic credential as byte slice.
306    pub fn identity(&self) -> &[u8] {
307        self.identity.as_slice()
308    }
309}
310
311impl From<BasicCredential> for Credential {
312    fn from(credential: BasicCredential) -> Self {
313        Credential {
314            credential_type: CredentialType::Basic,
315            serialized_credential_content: credential.identity,
316        }
317    }
318}
319
320impl TryFrom<Credential> for BasicCredential {
321    type Error = BasicCredentialError;
322
323    fn try_from(credential: Credential) -> Result<Self, Self::Error> {
324        match credential.credential_type {
325            CredentialType::Basic => Ok(BasicCredential::new(
326                credential.serialized_credential_content.into(),
327            )),
328            _ => Err(errors::BasicCredentialError::WrongCredentialType),
329        }
330    }
331}
332
333/// Bundle consisting of a [`Signer`] and a [`CredentialWithKey`] to be used to
334/// update the signature key in an [`MlsGroup`]. The public key and credential
335/// in `credential_with_key` MUST match the signature key exposed by `signer`.
336#[derive(Debug, Clone)]
337pub struct NewSignerBundle<'a, S: Signer> {
338    /// The signer to be used with the group after the update.
339    pub signer: &'a S,
340    /// The credential and public key corresponding to the `signer`.
341    pub credential_with_key: CredentialWithKey,
342}
343
344#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
345/// A wrapper around a credential with a corresponding public key.
346pub struct CredentialWithKey {
347    /// The [`Credential`].
348    pub credential: Credential,
349    /// The corresponding public key as [`SignaturePublicKey`].
350    pub signature_key: SignaturePublicKey,
351}
352
353impl From<&LeafNode> for CredentialWithKey {
354    fn from(leaf_node: &LeafNode) -> Self {
355        Self {
356            credential: leaf_node.credential().clone(),
357            signature_key: leaf_node.signature_key().clone(),
358        }
359    }
360}
361
362impl From<&Member> for CredentialWithKey {
363    fn from(member: &Member) -> Self {
364        Self {
365            credential: member.credential.clone(),
366            signature_key: member.signature_key.clone().into(),
367        }
368    }
369}
370
371#[cfg(test)]
372impl CredentialWithKey {
373    pub fn from_parts(credential: Credential, key: &[u8]) -> Self {
374        Self {
375            credential,
376            signature_key: key.into(),
377        }
378    }
379}
380
381#[cfg(any(test, feature = "test-utils"))]
382pub mod test_utils {
383    use openmls_basic_credential::SignatureKeyPair;
384    use openmls_traits::{types::SignatureScheme, OpenMlsProvider};
385
386    use super::{BasicCredential, CredentialWithKey};
387
388    /// Convenience function that generates a new credential and a key pair for
389    /// it (using the basic credential crate).
390    /// The signature keys are stored in the key store.
391    ///
392    /// Returns the [`Credential`] and the [`SignatureKeyPair`].
393    ///
394    /// [`Credential`]: super::Credential
395    #[openmls_traits::maybe_async]
396    pub async fn new_credential(
397        provider: &impl OpenMlsProvider,
398        identity: &[u8],
399        signature_scheme: SignatureScheme,
400    ) -> (CredentialWithKey, SignatureKeyPair) {
401        let credential = BasicCredential::new(identity.into());
402        let signature_keys = SignatureKeyPair::new(signature_scheme).unwrap();
403        signature_keys.store(provider.storage()).await.unwrap();
404
405        (
406            CredentialWithKey {
407                credential: credential.into(),
408                signature_key: signature_keys.public().into(),
409            },
410            signature_keys,
411        )
412    }
413}
414
415#[cfg(test)]
416mod unit_tests {
417    use tls_codec::{
418        DeserializeBytes, Serialize, TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize,
419    };
420
421    use super::{BasicCredential, Credential, CredentialType};
422
423    #[test]
424    fn basic_credential_identity_and_codec() {
425        const IDENTITY: &str = "identity";
426        // Test the identity getter.
427        let basic_credential = BasicCredential::new(IDENTITY.into());
428        assert_eq!(basic_credential.identity(), IDENTITY.as_bytes());
429
430        // Test the encoding and decoding.
431        let credential = Credential::from(basic_credential.clone());
432        let serialized = credential.tls_serialize_detached().unwrap();
433
434        let deserialized = Credential::tls_deserialize_exact_bytes(&serialized).unwrap();
435        assert_eq!(credential.credential_type(), deserialized.credential_type());
436        assert_eq!(
437            credential.serialized_content(),
438            deserialized.serialized_content()
439        );
440
441        let deserialized_basic_credential = BasicCredential::try_from(deserialized).unwrap();
442        assert_eq!(
443            deserialized_basic_credential.identity(),
444            IDENTITY.as_bytes()
445        );
446        assert_eq!(basic_credential, deserialized_basic_credential);
447    }
448
449    /// Test the [`Credential`] with a custom credential.
450    #[test]
451    fn custom_credential() {
452        #[derive(
453            Debug, Clone, PartialEq, Eq, TlsSize, TlsSerialize, TlsDeserialize, TlsDeserializeBytes,
454        )]
455        struct CustomCredential {
456            custom_field1: u32,
457            custom_field2: Vec<u8>,
458            custom_field3: Option<u8>,
459        }
460
461        let custom_credential = CustomCredential {
462            custom_field1: 42,
463            custom_field2: vec![1, 2, 3],
464            custom_field3: Some(2),
465        };
466
467        let credential = Credential::new(
468            CredentialType::Other(1234),
469            custom_credential.tls_serialize_detached().unwrap(),
470        );
471
472        let serialized = credential.tls_serialize_detached().unwrap();
473        let deserialized = Credential::tls_deserialize_exact_bytes(&serialized).unwrap();
474        assert_eq!(credential, deserialized);
475
476        let deserialized_custom_credential =
477            CustomCredential::tls_deserialize_exact_bytes(deserialized.serialized_content())
478                .unwrap();
479
480        assert_eq!(custom_credential, deserialized_custom_credential);
481    }
482}