core_crypto/mls/credential/
x509.rs

1#[cfg(test)]
2use crate::test_utils::x509::X509Certificate;
3#[cfg(test)]
4use mls_crypto_provider::PkiKeypair;
5#[cfg(test)]
6use x509_cert::der::Encode;
7
8use super::{Error, Result};
9use openmls_traits::types::SignatureScheme;
10use wire_e2e_identity::prelude::{HashAlgorithm, WireIdentityReader};
11use zeroize::Zeroize;
12
13use crate::{RecursiveError, e2e_identity::id::WireQualifiedClientId, prelude::ClientId};
14
15#[derive(core_crypto_macros::Debug, Clone, Zeroize)]
16#[zeroize(drop)]
17pub struct CertificatePrivateKey {
18    #[sensitive]
19    pub(crate) value: Vec<u8>,
20    #[zeroize(skip)]
21    pub(crate) signature_scheme: SignatureScheme,
22}
23
24impl CertificatePrivateKey {
25    pub(crate) fn into_parts(mut self) -> (Vec<u8>, SignatureScheme) {
26        (std::mem::take(&mut self.value), self.signature_scheme)
27    }
28}
29
30/// Represents a x509 certificate chain supplied by the client
31/// It can fetch it after an end-to-end identity process where it can get back a certificate
32/// from the Authentication Service
33#[derive(Debug, Clone)]
34pub struct CertificateBundle {
35    /// x509 certificate chain
36    /// First entry is the leaf certificate and each subsequent is its issuer
37    pub certificate_chain: Vec<Vec<u8>>,
38    /// Leaf certificate private key
39    pub private_key: CertificatePrivateKey,
40}
41
42impl CertificateBundle {
43    /// Reads the client_id from the leaf certificate
44    pub fn get_client_id(&self) -> Result<ClientId> {
45        let leaf = self.certificate_chain.first().ok_or(Error::InvalidIdentity)?;
46
47        let hash_alg = match self.private_key.signature_scheme {
48            SignatureScheme::ECDSA_SECP256R1_SHA256 | SignatureScheme::ED25519 => HashAlgorithm::SHA256,
49            SignatureScheme::ECDSA_SECP384R1_SHA384 => HashAlgorithm::SHA384,
50            SignatureScheme::ED448 | SignatureScheme::ECDSA_SECP521R1_SHA512 => HashAlgorithm::SHA512,
51        };
52
53        let identity = leaf
54            .extract_identity(None, hash_alg)
55            .map_err(|_| Error::InvalidIdentity)?;
56        let client_id = identity
57            .client_id
58            .parse::<WireQualifiedClientId>()
59            .map_err(RecursiveError::e2e_identity("parsing wire qualified client id"))?;
60        Ok(client_id.into())
61    }
62
63    /// Reads the 'Not Before' claim from the leaf certificate
64    pub fn get_created_at(&self) -> Result<u64> {
65        let leaf = self.certificate_chain.first().ok_or(Error::InvalidIdentity)?;
66        leaf.extract_created_at().map_err(|_| Error::InvalidIdentity)
67    }
68}
69
70#[cfg(test)]
71fn new_rand_client(domain: Option<String>) -> (String, String) {
72    let rand_str = |n: usize| {
73        use rand::distributions::{Alphanumeric, DistString as _};
74        Alphanumeric.sample_string(&mut rand::thread_rng(), n)
75    };
76    let user_id = uuid::Uuid::new_v4().to_string();
77    let domain = domain.unwrap_or_else(|| format!("{}.com", rand_str(6)));
78    let client_id = wire_e2e_identity::prelude::E2eiClientId::try_new(user_id, rand::random::<u64>(), &domain)
79        .unwrap()
80        .to_qualified();
81    (client_id, domain)
82}
83
84#[cfg(test)]
85impl CertificateBundle {
86    // test functions are not held to the same standard as real functions
87    #![allow(missing_docs)]
88
89    /// Generates a certificate that is later turned into a [openmls::prelude::CredentialBundle]
90    pub fn rand(name: &ClientId, signer: &crate::test_utils::x509::X509Certificate) -> Self {
91        // here in our tests client_id is generally just "alice" or "bob"
92        // so we will use it to augment handle & display_name
93        // and not a real client_id, instead we'll generate a random one
94        let handle = format!("{name}_wire");
95        let display_name = format!("{name} Smith");
96        Self::new(&handle, &display_name, None, None, signer)
97    }
98
99    /// Generates a certificate that is later turned into a [openmls::prelude::CredentialBundle]
100    pub fn new(
101        handle: &str,
102        display_name: &str,
103        client_id: Option<&crate::e2e_identity::id::QualifiedE2eiClientId>,
104        cert_keypair: Option<PkiKeypair>,
105        signer: &crate::test_utils::x509::X509Certificate,
106    ) -> Self {
107        Self::new_with_expiration(handle, display_name, client_id, cert_keypair, signer, None)
108    }
109
110    pub fn new_with_expiration(
111        handle: &str,
112        display_name: &str,
113        client_id: Option<&crate::e2e_identity::id::QualifiedE2eiClientId>,
114        cert_keypair: Option<PkiKeypair>,
115        signer: &crate::test_utils::x509::X509Certificate,
116        expiration: Option<std::time::Duration>,
117    ) -> Self {
118        // here in our tests client_id is generally just "alice" or "bob"
119        // so we will use it to augment handle & display_name
120        // and not a real client_id, instead we'll generate a random one
121        let domain = "world.com";
122        let (client_id, domain) = client_id
123            .map(|cid| {
124                let cid = String::from_utf8(cid.to_vec()).unwrap();
125                (cid, domain.to_string())
126            })
127            .unwrap_or_else(|| new_rand_client(Some(domain.to_string())));
128
129        let mut cert_params = crate::test_utils::x509::CertificateParams {
130            domain: domain.into(),
131            common_name: Some(display_name.to_string()),
132            handle: Some(handle.to_string()),
133            client_id: Some(client_id.to_string()),
134            cert_keypair,
135            ..Default::default()
136        };
137
138        if let Some(expiration) = expiration {
139            cert_params.expiration = expiration;
140        }
141
142        let cert = signer.create_and_sign_end_identity(cert_params);
143        Self::from_certificate_and_issuer(&cert, signer)
144    }
145
146    pub fn new_with_default_values(
147        signer: &crate::test_utils::x509::X509Certificate,
148        expiration: Option<std::time::Duration>,
149    ) -> Self {
150        Self::new_with_expiration("alice_wire@world.com", "Alice Smith", None, None, signer, expiration)
151    }
152
153    pub fn from_self_signed_certificate(cert: &X509Certificate) -> Self {
154        Self::from_certificate_and_issuer(cert, cert)
155    }
156
157    pub fn from_certificate_and_issuer(cert: &X509Certificate, issuer: &X509Certificate) -> Self {
158        Self {
159            certificate_chain: vec![cert.certificate.to_der().unwrap(), issuer.certificate.to_der().unwrap()],
160            private_key: CertificatePrivateKey {
161                value: cert.pki_keypair.signing_key_bytes(),
162                signature_scheme: cert.signature_scheme,
163            },
164        }
165    }
166
167    pub fn rand_identifier(
168        name: &str,
169        signers: &[&crate::test_utils::x509::X509Certificate],
170    ) -> crate::prelude::ClientIdentifier {
171        crate::prelude::ClientIdentifier::X509(
172            signers
173                .iter()
174                .map(|signer| (signer.signature_scheme, Self::rand(&name.into(), signer)))
175                .collect::<std::collections::HashMap<_, _>>(),
176        )
177    }
178}