core_crypto/mls/credential/
x509.rs1use std::fmt;
2
3use derive_more::derive;
4use openmls::prelude::Credential as MlsCredential;
5use openmls_traits::types::SignatureScheme;
6use openmls_x509_credential::CertificateKeyPair;
7use wire_e2e_identity::{
8 HashAlgorithm, WireIdentityReader, legacy::id::WireQualifiedClientId, pki_env::PkiEnvironment,
9};
10#[cfg(test)]
11use x509_cert::der::Encode;
12use zeroize::Zeroize;
13
14use super::{Error, Result};
15#[cfg(test)]
16use crate::mls_provider::PkiKeypair;
17#[cfg(test)]
18use crate::test_utils::x509::X509Certificate;
19use crate::{
20 CipherSuite, ClientId, Credential, CredentialType, OpenMlsError, RecursiveError,
21 mls::credential::ext::CredentialExt as _,
22};
23
24#[derive(core_crypto_macros::Debug, Clone, Zeroize, derive::Constructor)]
25#[zeroize(drop)]
26pub struct CertificatePrivateKey {
27 #[sensitive]
28 value: Vec<u8>,
29}
30
31impl CertificatePrivateKey {
32 pub(crate) fn into_inner(mut self) -> Vec<u8> {
33 std::mem::take(&mut self.value)
34 }
35}
36
37#[derive(Clone)]
41pub struct CertificateBundle {
42 pub certificate_chain: Vec<Vec<u8>>,
45 pub private_key: CertificatePrivateKey,
47 pub signature_scheme: SignatureScheme,
49}
50
51impl fmt::Debug for CertificateBundle {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 use base64::prelude::*;
54
55 #[derive(derive_more::Debug)]
56 #[debug("{}", BASE64_STANDARD.encode(_0))]
57 #[expect(dead_code)]
59 struct CertificateDebugHelper<'a>(&'a Vec<u8>);
60
61 let certificates = self
62 .certificate_chain
63 .iter()
64 .map(CertificateDebugHelper)
65 .collect::<Vec<_>>();
66 f.debug_struct("CertificateBundle")
67 .field("certificate_chain", &certificates)
68 .field("private_key", &self.private_key)
69 .finish()
70 }
71}
72
73impl CertificateBundle {
74 pub fn from_raw(certificate_chain: Vec<Vec<u8>>, private_key: Vec<u8>, signature_scheme: SignatureScheme) -> Self {
76 Self {
77 certificate_chain,
78 private_key: CertificatePrivateKey::new(private_key),
79 signature_scheme,
80 }
81 }
82
83 pub async fn get_client_id(&self, pki_env: &PkiEnvironment) -> Result<ClientId> {
85 let leaf = self.certificate_chain.first().ok_or(Error::InvalidIdentity)?;
86
87 let hash_alg = match self.signature_scheme {
88 SignatureScheme::ECDSA_SECP256R1_SHA256 | SignatureScheme::ED25519 => HashAlgorithm::SHA256,
89 SignatureScheme::ECDSA_SECP384R1_SHA384 => HashAlgorithm::SHA384,
90 SignatureScheme::ED448 | SignatureScheme::ECDSA_SECP521R1_SHA512 => HashAlgorithm::SHA512,
91 };
92
93 let identity = leaf
94 .extract_identity(pki_env, hash_alg)
95 .await
96 .map_err(|_| Error::InvalidIdentity)?;
97
98 use wire_e2e_identity::legacy::id as legacy_id;
99
100 let client_id: legacy_id::ClientId = identity
101 .client_id
102 .parse::<WireQualifiedClientId>()
103 .map_err(RecursiveError::e2e_identity("parsing wire qualified client id"))?
104 .into();
105 let client_id: Vec<u8> = client_id.into();
106 let client_id = ClientId::new_from_bytes(client_id).map_err(RecursiveError::context("client id from bytes"))?;
107 Ok(client_id)
108 }
109
110 pub fn get_created_at(&self) -> Result<u64> {
112 let leaf = self.certificate_chain.first().ok_or(Error::InvalidIdentity)?;
113 leaf.extract_created_at().map_err(|_| Error::InvalidIdentity)
114 }
115}
116
117impl Credential {
118 pub fn x509(cipher_suite: CipherSuite, cert: CertificateBundle) -> Result<Self> {
120 let earliest_validity = cert.get_created_at().map_err(RecursiveError::context(
121 "getting credential 'not before' claim from leaf cert in Credential::x509",
122 ))?;
123 let sk = cert.private_key.into_inner();
124 let chain = cert.certificate_chain;
125
126 let kp =
127 CertificateKeyPair::new(sk, chain.clone()).map_err(OpenMlsError::wrap("creating certificate key pair"))?;
128
129 let credential = MlsCredential::new_x509(chain).map_err(OpenMlsError::wrap("creating x509 credential"))?;
130
131 let cb = Credential {
132 cipher_suite,
133 credential_type: CredentialType::X509,
134 mls_credential: credential,
135 signature_key_pair: kp.0,
136 earliest_validity,
137 };
138 Ok(cb)
139 }
140
141 pub(crate) async fn check(&self, pki_env: &PkiEnvironment) -> Result<()> {
147 if self.credential_type == CredentialType::X509 {
148 let cert = self
149 .mls_credential()
150 .parse_leaf_cert()
151 .map_err(RecursiveError::context("parsing leaf certificate"))?
152 .expect("parse_leaf_cert to return a Certificate");
154
155 pki_env
156 .validate_cert(&cert)
157 .await
158 .map_err(RecursiveError::e2e_identity("validating credential certificate"))?;
159 }
160 Ok(())
161 }
162}
163
164#[cfg(test)]
165fn new_rand_client(domain: Option<String>) -> (ClientId, String) {
166 let rand_str = |n: usize| {
167 use rand::distributions::{Alphanumeric, DistString as _};
168 Alphanumeric.sample_string(&mut rand::thread_rng(), n)
169 };
170 let user_id = uuid::Uuid::new_v4();
171 let domain = domain.unwrap_or_else(|| format!("{}.com", rand_str(6)));
172 let device_id = rand::random::<u64>();
173 let client_id = ClientId::new(user_id, device_id, &domain);
174 (client_id, domain)
175}
176
177#[cfg(test)]
178impl CertificateBundle {
179 #![allow(missing_docs)]
181
182 pub fn rand(name: &ClientId, signer: &crate::test_utils::x509::X509Certificate) -> Self {
186 let handle = format!("{name}_wire");
190 let display_name = format!("{name} Smith");
191 Self::new(&handle, &display_name, None, None, signer)
192 }
193
194 pub fn new_with_exact_client_id(client_id: &ClientId, signer: &crate::test_utils::x509::X509Certificate) -> Self {
195 let rand_str = |n: usize| {
199 use rand::distributions::{Alphanumeric, DistString as _};
200 Alphanumeric.sample_string(&mut rand::thread_rng(), n)
201 };
202 let name = rand_str(10);
203 let handle = format!("{name}_wire");
204 let display_name = format!("{name} Smith");
205 Self::new(&handle, &display_name, Some(client_id), None, signer)
206 }
207
208 pub fn new(
210 handle: &str,
211 display_name: &str,
212 client_id: Option<&ClientId>,
213 cert_keypair: Option<PkiKeypair>,
214 signer: &crate::test_utils::x509::X509Certificate,
215 ) -> Self {
216 Self::new_with_expiration(handle, display_name, client_id, cert_keypair, signer, None)
217 }
218
219 pub fn new_with_expiration(
220 handle: &str,
221 display_name: &str,
222 client_id: Option<&ClientId>,
223 cert_keypair: Option<PkiKeypair>,
224 signer: &crate::test_utils::x509::X509Certificate,
225 expiration: Option<std::time::Duration>,
226 ) -> Self {
227 let domain = "world.com";
231 let (client_id, domain) = client_id
232 .cloned()
233 .map(|cid| (cid, domain.to_string()))
234 .unwrap_or_else(|| new_rand_client(Some(domain.to_string())));
235
236 let mut cert_params = crate::test_utils::x509::CertificateParams {
237 domain: domain.into(),
238 common_name: Some(display_name.to_string()),
239 handle: Some(handle.to_string()),
240 client_id: Some(client_id),
241 cert_keypair,
242 ..Default::default()
243 };
244
245 if let Some(expiration) = expiration {
246 cert_params.expiration = expiration;
247 }
248
249 let cert = signer.create_and_sign_end_identity(cert_params);
250 Self::from_certificate_and_issuer(&cert, signer)
251 }
252
253 pub fn new_with_default_values(
254 signer: &crate::test_utils::x509::X509Certificate,
255 expiration: Option<std::time::Duration>,
256 ) -> Self {
257 Self::new_with_expiration("alice_wire@world.com", "Alice Smith", None, None, signer, expiration)
258 }
259
260 pub fn from_self_signed_certificate(cert: &X509Certificate) -> Self {
261 Self::from_certificate_and_issuer(cert, cert)
262 }
263
264 pub fn from_certificate_and_issuer(cert: &X509Certificate, issuer: &X509Certificate) -> Self {
265 Self {
266 certificate_chain: vec![cert.certificate.to_der().unwrap(), issuer.certificate.to_der().unwrap()],
267 private_key: CertificatePrivateKey::new(cert.pki_keypair.signing_key_bytes()),
268 signature_scheme: cert.signature_scheme,
269 }
270 }
271}