core_crypto/mls/credential/
x509.rs1#[cfg(test)]
2use std::collections::HashMap;
3use std::fmt;
4
5use derive_more::derive;
6use openmls::prelude::Credential as MlsCredential;
7use openmls_traits::types::SignatureScheme;
8use openmls_x509_credential::CertificateKeyPair;
9use wire_e2e_identity::{HashAlgorithm, WireIdentityReader, legacy::id::WireQualifiedClientId};
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::{CipherSuite, ClientId, Credential, CredentialType, MlsError, RecursiveError};
20
21#[derive(core_crypto_macros::Debug, Clone, Zeroize, derive::Constructor)]
22#[zeroize(drop)]
23pub struct CertificatePrivateKey {
24 #[sensitive]
25 value: Vec<u8>,
26}
27
28impl CertificatePrivateKey {
29 pub(crate) fn into_inner(mut self) -> Vec<u8> {
30 std::mem::take(&mut self.value)
31 }
32}
33
34#[derive(Clone)]
38pub struct CertificateBundle {
39 pub certificate_chain: Vec<Vec<u8>>,
42 pub private_key: CertificatePrivateKey,
44 pub signature_scheme: SignatureScheme,
46}
47
48impl fmt::Debug for CertificateBundle {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 use base64::prelude::*;
51
52 #[derive(derive_more::Debug)]
53 #[debug("{}", BASE64_STANDARD.encode(_0))]
54 #[expect(dead_code)]
56 struct CertificateDebugHelper<'a>(&'a Vec<u8>);
57
58 let certificates = self
59 .certificate_chain
60 .iter()
61 .map(CertificateDebugHelper)
62 .collect::<Vec<_>>();
63 f.debug_struct("CertificateBundle")
64 .field("certificate_chain", &certificates)
65 .field("private_key", &self.private_key)
66 .finish()
67 }
68}
69
70impl CertificateBundle {
71 pub fn from_raw(certificate_chain: Vec<Vec<u8>>, private_key: Vec<u8>, signature_scheme: SignatureScheme) -> Self {
73 Self {
74 certificate_chain,
75 private_key: CertificatePrivateKey::new(private_key),
76 signature_scheme,
77 }
78 }
79
80 pub fn get_client_id(
82 &self,
83 env: Option<&wire_e2e_identity::x509_check::revocation::PkiEnvironment>,
84 ) -> Result<ClientId> {
85 let env = env.ok_or(Error::MissingPKIEnvironment)?;
86 let leaf = self.certificate_chain.first().ok_or(Error::InvalidIdentity)?;
87
88 let hash_alg = match self.signature_scheme {
89 SignatureScheme::ECDSA_SECP256R1_SHA256 | SignatureScheme::ED25519 => HashAlgorithm::SHA256,
90 SignatureScheme::ECDSA_SECP384R1_SHA384 => HashAlgorithm::SHA384,
91 SignatureScheme::ED448 | SignatureScheme::ECDSA_SECP521R1_SHA512 => HashAlgorithm::SHA512,
92 };
93
94 let identity = leaf
95 .extract_identity(env, hash_alg)
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 Ok(client_id.into())
107 }
108
109 pub fn get_created_at(&self) -> Result<u64> {
111 let leaf = self.certificate_chain.first().ok_or(Error::InvalidIdentity)?;
112 leaf.extract_created_at().map_err(|_| Error::InvalidIdentity)
113 }
114}
115
116impl Credential {
117 pub fn x509(ciphersuite: CipherSuite, cert: CertificateBundle) -> Result<Self> {
119 let earliest_validity = cert.get_created_at().map_err(RecursiveError::mls_credential(
120 "getting credential 'not before' claim from leaf cert in Credential::x509",
121 ))?;
122 let sk = cert.private_key.into_inner();
123 let chain = cert.certificate_chain;
124
125 let kp = CertificateKeyPair::new(sk, chain.clone()).map_err(MlsError::wrap("creating certificate key pair"))?;
126
127 let credential = MlsCredential::new_x509(chain).map_err(MlsError::wrap("creating x509 credential"))?;
128
129 let cb = Credential {
130 ciphersuite,
131 credential_type: CredentialType::X509,
132 mls_credential: credential,
133 signature_key_pair: kp.0,
134 earliest_validity,
135 };
136 Ok(cb)
137 }
138}
139
140#[cfg(test)]
141fn new_rand_client(domain: Option<String>) -> (String, String) {
142 let rand_str = |n: usize| {
143 use rand::distributions::{Alphanumeric, DistString as _};
144 Alphanumeric.sample_string(&mut rand::thread_rng(), n)
145 };
146 let user_id = uuid::Uuid::new_v4().to_string();
147 let domain = domain.unwrap_or_else(|| format!("{}.com", rand_str(6)));
148 let client_id = wire_e2e_identity::E2eiClientId::try_new(user_id, rand::random::<u64>(), &domain)
149 .unwrap()
150 .to_qualified();
151 (client_id, domain)
152}
153
154#[cfg(test)]
155impl CertificateBundle {
156 #![allow(missing_docs)]
158
159 pub fn rand(name: &ClientId, signer: &crate::test_utils::x509::X509Certificate) -> Self {
163 let handle = format!("{name}_wire");
167 let display_name = format!("{name} Smith");
168 Self::new(&handle, &display_name, None, None, signer)
169 }
170
171 pub fn new_with_exact_client_id(client_id: &ClientId, signer: &crate::test_utils::x509::X509Certificate) -> Self {
172 let rand_str = |n: usize| {
176 use rand::distributions::{Alphanumeric, DistString as _};
177 Alphanumeric.sample_string(&mut rand::thread_rng(), n)
178 };
179 let name = rand_str(10);
180 let handle = format!("{name}_wire");
181 let display_name = format!("{name} Smith");
182 let client_id = wire_e2e_identity::legacy::id::ClientId::from(client_id.clone());
183 let client_id = wire_e2e_identity::legacy::id::QualifiedE2eiClientId::from(client_id);
184 Self::new(&handle, &display_name, Some(&client_id), None, signer)
185 }
186
187 pub fn new(
189 handle: &str,
190 display_name: &str,
191 client_id: Option<&wire_e2e_identity::legacy::id::QualifiedE2eiClientId>,
192 cert_keypair: Option<PkiKeypair>,
193 signer: &crate::test_utils::x509::X509Certificate,
194 ) -> Self {
195 Self::new_with_expiration(handle, display_name, client_id, cert_keypair, signer, None)
196 }
197
198 pub fn new_with_expiration(
199 handle: &str,
200 display_name: &str,
201 client_id: Option<&wire_e2e_identity::legacy::id::QualifiedE2eiClientId>,
202 cert_keypair: Option<PkiKeypair>,
203 signer: &crate::test_utils::x509::X509Certificate,
204 expiration: Option<std::time::Duration>,
205 ) -> Self {
206 let domain = "world.com";
210 let (client_id, domain) = client_id
211 .map(|cid| {
212 let cid = String::from_utf8(cid.to_vec()).unwrap();
213 (cid, domain.to_string())
214 })
215 .unwrap_or_else(|| new_rand_client(Some(domain.to_string())));
216
217 let mut cert_params = crate::test_utils::x509::CertificateParams {
218 domain: domain.into(),
219 common_name: Some(display_name.to_string()),
220 handle: Some(handle.to_string()),
221 client_id: Some(client_id.to_string()),
222 cert_keypair,
223 ..Default::default()
224 };
225
226 if let Some(expiration) = expiration {
227 cert_params.expiration = expiration;
228 }
229
230 let cert = signer.create_and_sign_end_identity(cert_params);
231 Self::from_certificate_and_issuer(&cert, signer)
232 }
233
234 pub fn new_with_default_values(
235 signer: &crate::test_utils::x509::X509Certificate,
236 expiration: Option<std::time::Duration>,
237 ) -> Self {
238 Self::new_with_expiration("alice_wire@world.com", "Alice Smith", None, None, signer, expiration)
239 }
240
241 pub fn from_self_signed_certificate(cert: &X509Certificate) -> Self {
242 Self::from_certificate_and_issuer(cert, cert)
243 }
244
245 pub fn from_certificate_and_issuer(cert: &X509Certificate, issuer: &X509Certificate) -> Self {
246 Self {
247 certificate_chain: vec![cert.certificate.to_der().unwrap(), issuer.certificate.to_der().unwrap()],
248 private_key: CertificatePrivateKey::new(cert.pki_keypair.signing_key_bytes()),
249 signature_scheme: cert.signature_scheme,
250 }
251 }
252
253 pub fn rand_identifier_certs(
254 client_id: &ClientId,
255 signers: &[&crate::test_utils::x509::X509Certificate],
256 ) -> HashMap<SignatureScheme, CertificateBundle> {
257 signers
258 .iter()
259 .map(|signer| (signer.signature_scheme, Self::rand(client_id, signer)))
260 .collect()
261 }
262
263 pub fn rand_identifier(
264 client_id: &ClientId,
265 signers: &[&crate::test_utils::x509::X509Certificate],
266 ) -> crate::ClientIdentifier {
267 crate::ClientIdentifier::X509(Self::rand_identifier_certs(client_id, signers))
268 }
269}