Skip to main content

wire_e2e_identity/acquisition/
serialization.rs

1use std::sync::Arc;
2
3use rusty_jwt_tools::{
4    jsonwebtoken::jwk::Jwk,
5    prelude::{ClientId, Pem},
6};
7use uuid::Uuid;
8
9use super::{
10    CiphersuiteExt as _, Result, X509CredentialAcquisition, X509CredentialConfiguration, error::Error, states,
11};
12use crate::pki_env::PkiEnvironment;
13
14#[derive(serde::Serialize, serde::Deserialize)]
15#[serde(remote = "ClientId")]
16pub struct ClientIdDef {
17    /// base64url encoded UUIDv4 unique user identifier
18    pub user_id: Uuid,
19    /// the device id assigned by the backend in hex
20    pub device_id: u64,
21    /// the backend domain of the client
22    pub domain: String,
23}
24
25#[derive(serde::Deserialize, serde::Serialize)]
26struct X509CredentialAcquisitionSerialisationHelper<T: std::fmt::Debug> {
27    config: X509CredentialConfiguration,
28    sign_kp: Pem,
29    acme_kp: Pem,
30    acme_jwk: Jwk,
31    data: T,
32}
33
34impl X509CredentialAcquisition<states::DpopChallengeCompleted> {
35    pub fn deserialize(pki_env: Arc<PkiEnvironment>, bytes: &[u8]) -> Result<Self> {
36        let helper: X509CredentialAcquisitionSerialisationHelper<states::DpopChallengeCompleted> =
37            serde_json::from_slice(bytes)?;
38        let sign_alg = helper
39            .config
40            .cipher_suite
41            .jws_algorithm()
42            .ok_or(Error::UnsupportedCipherSuite(helper.config.cipher_suite))?;
43
44        Ok(Self {
45            pki_env,
46            config: helper.config,
47            sign_alg,
48            sign_kp: helper.sign_kp,
49            acme_kp: helper.acme_kp,
50            acme_jwk: helper.acme_jwk,
51            data: helper.data,
52        })
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use core_crypto_keystore::Database;
59    use openmls_traits::types::Ciphersuite;
60
61    use super::*;
62    use crate::{
63        acme::{AcmeAccount, AcmeChallenge, AcmeOrder},
64        pki_env::hooks::{HttpHeader, HttpMethod, HttpResponse, PkiEnvironmentHooks, PkiEnvironmentHooksError},
65    };
66
67    #[derive(Debug)]
68    struct UnusedPkiEnvironmentHooks;
69
70    #[async_trait::async_trait]
71    impl PkiEnvironmentHooks for UnusedPkiEnvironmentHooks {
72        async fn http_request(
73            &self,
74            _method: HttpMethod,
75            _url: String,
76            _headers: Vec<HttpHeader>,
77            _body: Vec<u8>,
78        ) -> std::result::Result<HttpResponse, PkiEnvironmentHooksError> {
79            unreachable!("serialization round-trip should not perform HTTP requests")
80        }
81
82        async fn authenticate(
83            &self,
84            _idp: String,
85            _key_auth: String,
86            _acme_aud: String,
87            _acquisition_snapshot: Vec<u8>,
88        ) -> std::result::Result<String, PkiEnvironmentHooksError> {
89            unreachable!("serialization round-trip should not authenticate")
90        }
91
92        async fn get_backend_nonce(&self) -> std::result::Result<String, PkiEnvironmentHooksError> {
93            unreachable!("serialization round-trip should not request backend nonces")
94        }
95
96        async fn fetch_backend_access_token(
97            &self,
98            _dpop: String,
99        ) -> std::result::Result<String, PkiEnvironmentHooksError> {
100            unreachable!("serialization round-trip should not fetch backend access tokens")
101        }
102    }
103
104    #[tokio::test]
105    async fn can_serialize_and_deserialize_dpop_challenge_completed_acquisition() {
106        let pki_env = Arc::new(
107            PkiEnvironment::new(Arc::new(UnusedPkiEnvironmentHooks), Database::open_in_memory().unwrap())
108                .await
109                .unwrap(),
110        );
111        let client_id = ClientId::try_new(Uuid::new_v4().to_string(), 1, "wire.example").unwrap();
112        let config = X509CredentialConfiguration {
113            acme_directory_url: "acme.example".into(),
114            cipher_suite: Ciphersuite::MLS_128_MLKEM768P256_AES128GCM_SHA256_P256,
115            display_name: "Alice".into(),
116            client_id,
117            handle: "alice".into(),
118            domain: "wire.example".into(),
119            team: Some("team".into()),
120            validity_period: std::time::Duration::from_secs(3600),
121        };
122        let initialized = X509CredentialAcquisition::try_new(pki_env.clone(), config).unwrap();
123        let acquisition = X509CredentialAcquisition::<states::DpopChallengeCompleted> {
124            pki_env: initialized.pki_env,
125            config: initialized.config,
126            sign_alg: initialized.sign_alg,
127            sign_kp: initialized.sign_kp,
128            acme_kp: initialized.acme_kp,
129            acme_jwk: initialized.acme_jwk,
130            data: states::DpopChallengeCompleted {
131                nonce: "acme-nonce".into(),
132                acme_account: AcmeAccount::default(),
133                order: AcmeOrder::default(),
134                oidc_challenge: AcmeChallenge::new_user(),
135            },
136        };
137
138        let serialized = serde_json::to_vec(&acquisition).unwrap();
139        let deserialized =
140            X509CredentialAcquisition::<states::DpopChallengeCompleted>::deserialize(pki_env, &serialized).unwrap();
141
142        assert_eq!(
143            serde_json::to_value(&acquisition).unwrap(),
144            serde_json::to_value(&deserialized).unwrap()
145        );
146    }
147}