core_crypto/mls/credential/
ext.rs

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