Skip to main content

wire_e2e_identity/acquisition/
serialization.rs

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