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::{WireIdentityReader, pki_env::PkiEnvironment};
8#[cfg(test)]
9use x509_cert::der::Encode;
10use zeroize::Zeroize;
11
12use super::{Error, Result};
13#[cfg(test)]
14use crate::test_utils::x509::{PkiKeypair, X509Certificate};
15use crate::{
16 CipherSuite, ClientId, Credential, CredentialType, OpenMlsError, RecursiveError,
17 mls::credential::ext::CredentialExt as _,
18};
19
20#[derive(core_crypto_macros::Debug, Clone, Zeroize, derive::Constructor)]
21#[zeroize(drop)]
22pub struct CertificatePrivateKey {
23 #[sensitive]
24 value: Vec<u8>,
25}
26
27impl CertificatePrivateKey {
28 pub(crate) fn into_inner(mut self) -> Vec<u8> {
29 std::mem::take(&mut self.value)
30 }
31}
32
33#[derive(Clone)]
37pub struct CertificateBundle {
38 pub certificate_chain: Vec<Vec<u8>>,
41 pub private_key: CertificatePrivateKey,
43 pub signature_scheme: SignatureScheme,
45}
46
47impl fmt::Debug for CertificateBundle {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 use base64::prelude::*;
50
51 #[derive(derive_more::Debug)]
52 #[debug("{}", BASE64_STANDARD.encode(_0))]
53 #[expect(dead_code)]
55 struct CertificateDebugHelper<'a>(&'a Vec<u8>);
56
57 let certificates = self
58 .certificate_chain
59 .iter()
60 .map(CertificateDebugHelper)
61 .collect::<Vec<_>>();
62 f.debug_struct("CertificateBundle")
63 .field("certificate_chain", &certificates)
64 .field("private_key", &self.private_key)
65 .finish()
66 }
67}
68
69impl CertificateBundle {
70 pub fn from_raw(certificate_chain: Vec<Vec<u8>>, private_key: Vec<u8>, signature_scheme: SignatureScheme) -> Self {
72 Self {
73 certificate_chain,
74 private_key: CertificatePrivateKey::new(private_key),
75 signature_scheme,
76 }
77 }
78
79 pub async fn get_client_id(&self, pki_env: &PkiEnvironment) -> Result<ClientId> {
81 let leaf = self.certificate_chain.first().ok_or(Error::InvalidIdentity)?;
82
83 let identity = leaf
84 .extract_identity(pki_env)
85 .await
86 .map_err(|_| Error::InvalidIdentity)?;
87
88 ClientId::try_from_str_with_base64_user_id(&identity.client_id)
89 .map_err(RecursiveError::context(
90 "converting client id types from leaf certificate",
91 ))
92 .map_err(Into::into)
93 }
94
95 pub fn get_created_at(&self) -> Result<u64> {
97 let leaf = self.certificate_chain.first().ok_or(Error::InvalidIdentity)?;
98 leaf.extract_created_at().map_err(|_| Error::InvalidIdentity)
99 }
100}
101
102impl Credential {
103 pub fn x509(cipher_suite: CipherSuite, cert: CertificateBundle) -> Result<Self> {
105 let earliest_validity = cert.get_created_at().map_err(RecursiveError::context(
106 "getting credential 'not before' claim from leaf cert in Credential::x509",
107 ))?;
108 let sk = cert.private_key.into_inner();
109 let chain = cert.certificate_chain;
110
111 let kp =
112 CertificateKeyPair::new(sk, chain.clone()).map_err(OpenMlsError::wrap("creating certificate key pair"))?;
113
114 let credential = MlsCredential::new_x509(chain).map_err(OpenMlsError::wrap("creating x509 credential"))?;
115
116 let cb = Credential {
117 cipher_suite,
118 credential_type: CredentialType::X509,
119 mls_credential: credential,
120 signature_key_pair: kp.0,
121 earliest_validity,
122 };
123 Ok(cb)
124 }
125
126 pub(crate) async fn check(&self, pki_env: &PkiEnvironment) -> Result<()> {
132 if self.credential_type == CredentialType::X509 {
133 let cert = self
134 .mls_credential()
135 .parse_leaf_cert()
136 .map_err(RecursiveError::context("parsing leaf certificate"))?
137 .expect("parse_leaf_cert to return a Certificate");
139
140 pki_env
141 .validate_cert(&cert)
142 .await
143 .map_err(RecursiveError::e2e_identity("validating credential certificate"))?;
144 }
145 Ok(())
146 }
147}
148
149#[cfg(test)]
150fn new_rand_client(domain: Option<String>) -> (ClientId, String) {
151 let rand_str = |n: usize| {
152 use rand::distr::{Alphanumeric, SampleString as _};
153 Alphanumeric.sample_string(&mut rand::rng(), n)
154 };
155 let user_id = uuid::Uuid::new_v4();
156 let domain = domain.unwrap_or_else(|| format!("{}.com", rand_str(6)));
157 let device_id = rand::random::<u64>();
158 let client_id = ClientId::new(user_id, device_id, &domain);
159 (client_id, domain)
160}
161
162#[cfg(test)]
163impl CertificateBundle {
164 #![expect(missing_docs)]
166
167 pub fn rand(name: &ClientId, signer: &crate::test_utils::x509::X509Certificate) -> Self {
171 let handle = format!("{name}_wire");
175 let display_name = format!("{name} Smith");
176 Self::new(&handle, &display_name, None, None, signer)
177 }
178
179 pub fn new_with_exact_client_id(client_id: &ClientId, signer: &crate::test_utils::x509::X509Certificate) -> Self {
180 let rand_str = |n: usize| {
184 use rand::distr::{Alphanumeric, SampleString as _};
185 Alphanumeric.sample_string(&mut rand::rng(), n)
186 };
187 let name = rand_str(10);
188 let handle = format!("{name}_wire");
189 let display_name = format!("{name} Smith");
190 Self::new(&handle, &display_name, Some(client_id), None, signer)
191 }
192
193 pub fn new(
195 handle: &str,
196 display_name: &str,
197 client_id: Option<&ClientId>,
198 cert_keypair: Option<PkiKeypair>,
199 signer: &crate::test_utils::x509::X509Certificate,
200 ) -> Self {
201 Self::new_with_expiration(handle, display_name, client_id, cert_keypair, signer, None)
202 }
203
204 pub fn new_with_expiration(
205 handle: &str,
206 display_name: &str,
207 client_id: Option<&ClientId>,
208 cert_keypair: Option<PkiKeypair>,
209 signer: &crate::test_utils::x509::X509Certificate,
210 expiration: Option<std::time::Duration>,
211 ) -> Self {
212 let domain = "wire.com";
216 let (client_id, domain) = client_id
217 .cloned()
218 .map(|cid| (cid, domain.to_string()))
219 .unwrap_or_else(|| new_rand_client(Some(domain.to_string())));
220
221 let mut cert_params = crate::test_utils::x509::CertificateParams {
222 domain: domain.into(),
223 common_name: Some(display_name.to_string()),
224 handle: Some(handle.to_string()),
225 client_id: Some(client_id),
226 cert_keypair,
227 ..Default::default()
228 };
229
230 if let Some(expiration) = expiration {
231 cert_params.expiration = expiration;
232 }
233
234 let cert = signer.create_and_sign_end_identity(cert_params);
235 Self::from_certificate_and_issuer(&cert, signer)
236 }
237
238 pub fn new_with_default_values(
239 signer: &crate::test_utils::x509::X509Certificate,
240 expiration: Option<std::time::Duration>,
241 ) -> Self {
242 Self::new_with_expiration("alice_wire@wire.com", "Alice Smith", None, None, signer, expiration)
243 }
244
245 pub fn from_self_signed_certificate(cert: &X509Certificate) -> Self {
246 Self::from_certificate_and_issuer(cert, cert)
247 }
248
249 pub fn from_certificate_and_issuer(cert: &X509Certificate, issuer: &X509Certificate) -> Self {
250 Self {
251 certificate_chain: vec![cert.certificate.to_der().unwrap(), issuer.certificate.to_der().unwrap()],
252 private_key: CertificatePrivateKey::new(cert.pki_keypair.signing_key_bytes()),
253 signature_scheme: cert.signature_scheme,
254 }
255 }
256}