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