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