core_crypto/mls/credential/
ext.rs

1use crate::prelude::{CryptoError, CryptoResult, DeviceStatus, MlsCiphersuite, MlsCredentialType, WireIdentity};
2use openmls::prelude::{Credential, CredentialType, CredentialWithKey};
3use openmls_traits::types::{HashType, SignatureScheme};
4use wire_e2e_identity::prelude::{compute_raw_key_thumbprint, HashAlgorithm, JwsAlgorithm};
5use x509_cert::{der::Decode, Certificate};
6
7#[allow(dead_code)]
8pub(crate) trait CredentialExt {
9    fn parse_leaf_cert(&self) -> CryptoResult<Option<Certificate>>;
10    fn get_type(&self) -> CryptoResult<MlsCredentialType>;
11    fn extract_identity(
12        &self,
13        cs: MlsCiphersuite,
14        env: Option<&wire_e2e_identity::prelude::x509::revocation::PkiEnvironment>,
15    ) -> CryptoResult<WireIdentity>;
16    fn extract_public_key(&self) -> CryptoResult<Option<Vec<u8>>>;
17    fn is_basic(&self) -> bool;
18}
19
20impl CredentialExt for CredentialWithKey {
21    fn parse_leaf_cert(&self) -> CryptoResult<Option<Certificate>> {
22        self.credential.parse_leaf_cert()
23    }
24
25    fn get_type(&self) -> CryptoResult<MlsCredentialType> {
26        self.credential.get_type()
27    }
28
29    fn extract_identity(
30        &self,
31        cs: MlsCiphersuite,
32        env: Option<&wire_e2e_identity::prelude::x509::revocation::PkiEnvironment>,
33    ) -> CryptoResult<WireIdentity> {
34        match self.credential.mls_credential() {
35            openmls::prelude::MlsCredentialType::X509(cert) => cert.extract_identity(cs, env),
36            openmls::prelude::MlsCredentialType::Basic(_) => {
37                // in case the ClientId is not a Wire identifier, just returning an empty String is
38                // fine since this is simply informative and for Wire only
39                let client_id = std::str::from_utf8(self.credential.identity())
40                    .unwrap_or_default()
41                    .to_string();
42
43                let thumbprint = compute_thumbprint(cs, self.signature_key.as_slice())?;
44
45                Ok(WireIdentity {
46                    client_id,
47                    credential_type: MlsCredentialType::Basic,
48                    thumbprint,
49                    status: DeviceStatus::Valid,
50                    x509_identity: None,
51                })
52            }
53        }
54    }
55
56    fn extract_public_key(&self) -> CryptoResult<Option<Vec<u8>>> {
57        self.credential.extract_public_key()
58    }
59
60    fn is_basic(&self) -> bool {
61        self.credential.is_basic()
62    }
63}
64
65impl CredentialExt for Credential {
66    fn parse_leaf_cert(&self) -> CryptoResult<Option<Certificate>> {
67        match self.mls_credential() {
68            openmls::prelude::MlsCredentialType::X509(cert) => cert.parse_leaf_cert(),
69            _ => Ok(None),
70        }
71    }
72
73    fn get_type(&self) -> CryptoResult<MlsCredentialType> {
74        match self.credential_type() {
75            CredentialType::Basic => Ok(MlsCredentialType::Basic),
76            CredentialType::X509 => Ok(MlsCredentialType::X509),
77            CredentialType::Unknown(_) => Err(CryptoError::ImplementationError),
78        }
79    }
80
81    fn extract_identity(
82        &self,
83        _cs: MlsCiphersuite,
84        _env: Option<&wire_e2e_identity::prelude::x509::revocation::PkiEnvironment>,
85    ) -> CryptoResult<WireIdentity> {
86        // This should not be called directly because one does not have the signature public key and hence
87        // cannot compute the MLS thumbprint for a Basic credential.
88        // [CredentialWithKey::extract_identity] should be preferred
89        Err(CryptoError::ImplementationError)
90    }
91
92    fn extract_public_key(&self) -> CryptoResult<Option<Vec<u8>>> {
93        match self.mls_credential() {
94            openmls::prelude::MlsCredentialType::X509(cert) => cert.extract_public_key(),
95            _ => Ok(None),
96        }
97    }
98
99    fn is_basic(&self) -> bool {
100        self.credential_type() == openmls::prelude::CredentialType::Basic
101    }
102}
103
104impl CredentialExt for openmls::prelude::Certificate {
105    fn parse_leaf_cert(&self) -> CryptoResult<Option<Certificate>> {
106        let leaf = self.certificates.first().ok_or(CryptoError::InvalidIdentity)?;
107        let leaf = Certificate::from_der(leaf.as_slice())?;
108        Ok(Some(leaf))
109    }
110
111    fn get_type(&self) -> CryptoResult<MlsCredentialType> {
112        Ok(MlsCredentialType::X509)
113    }
114
115    fn extract_identity(
116        &self,
117        cs: MlsCiphersuite,
118        env: Option<&wire_e2e_identity::prelude::x509::revocation::PkiEnvironment>,
119    ) -> CryptoResult<WireIdentity> {
120        let leaf = self.certificates.first().ok_or(CryptoError::InvalidIdentity)?;
121        let leaf = leaf.as_slice();
122        use wire_e2e_identity::prelude::WireIdentityReader as _;
123        let identity = leaf
124            .extract_identity(env, cs.e2ei_hash_alg())
125            .map_err(|_| CryptoError::InvalidIdentity)?;
126        let identity = WireIdentity::try_from((identity, leaf))?;
127        Ok(identity)
128    }
129
130    fn extract_public_key(&self) -> CryptoResult<Option<Vec<u8>>> {
131        let leaf = self.certificates.first().ok_or(CryptoError::InvalidIdentity)?;
132        use wire_e2e_identity::prelude::WireIdentityReader as _;
133        let pk = leaf
134            .as_slice()
135            .extract_public_key()
136            .map_err(|_| CryptoError::InvalidIdentity)?;
137        Ok(Some(pk))
138    }
139
140    fn is_basic(&self) -> bool {
141        false
142    }
143}
144
145fn compute_thumbprint(cs: MlsCiphersuite, raw_key: &[u8]) -> CryptoResult<String> {
146    let sign_alg = match cs.signature_algorithm() {
147        SignatureScheme::ED25519 => JwsAlgorithm::Ed25519,
148        SignatureScheme::ECDSA_SECP256R1_SHA256 => JwsAlgorithm::P256,
149        SignatureScheme::ECDSA_SECP384R1_SHA384 => JwsAlgorithm::P384,
150        SignatureScheme::ECDSA_SECP521R1_SHA512 => JwsAlgorithm::P521,
151        SignatureScheme::ED448 => return Err(CryptoError::Unsupported),
152    };
153    let hash_alg = match cs.hash_algorithm() {
154        HashType::Sha2_256 => HashAlgorithm::SHA256,
155        HashType::Sha2_384 => HashAlgorithm::SHA384,
156        HashType::Sha2_512 => HashAlgorithm::SHA512,
157    };
158
159    // return an empty string when it fails. Not worth failing for this, it's just informative
160    Ok(compute_raw_key_thumbprint(sign_alg, hash_alg, raw_key).unwrap_or_default())
161}