wire_e2e_identity/pki_env_hooks.rs
1//! PKI Environment API Hooks
2use std::fmt;
3
4/// An http method
5#[derive(Debug)]
6pub enum HttpMethod {
7 /// GET
8 Get,
9 /// POST
10 Post,
11 /// PUT
12 Put,
13 /// DELETE
14 Delete,
15 /// PATCH
16 Patch,
17 /// HEAD
18 Head,
19}
20
21/// An http header
22#[derive(Debug)]
23pub struct HttpHeader {
24 /// header name
25 pub name: String,
26 /// header value
27 pub value: String,
28}
29
30/// An HTTP Response
31pub struct HttpResponse {
32 /// Response status code
33 pub status: u16,
34 /// Response Header
35 pub headers: Vec<HttpHeader>,
36 /// Response Body
37 pub body: Vec<u8>,
38}
39
40impl HttpResponse {
41 /// Deserialize the body of the response into a JSON value.
42 pub fn json(&self) -> Result<serde_json::Value, serde_json::Error> {
43 serde_json::from_slice(&self.body)
44 }
45
46 /// Return the value of the first header with the given name.
47 pub fn first_header(&self, name: &str) -> Option<String> {
48 self.headers
49 .iter()
50 .find_map(|h| (h.name == name).then(|| h.value.clone()))
51 }
52}
53
54impl std::fmt::Debug for HttpResponse {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 if f.alternate() {
57 writeln!(f, "BEGIN HttpResponse")?;
58 writeln!(f, "Status: {}", self.status)?;
59 for header in &self.headers {
60 writeln!(f, "{}: {}", header.name, header.value)?;
61 }
62 writeln!(f)?;
63 match std::str::from_utf8(&self.body) {
64 Ok(body) => match serde_json::from_str::<serde_json::Value>(body) {
65 Ok(body) => writeln!(f, "{body:#}")?,
66 Err(_) => writeln!(f, "{body}")?,
67 },
68 Err(_) => writeln!(f, "{:#?}", self.body)?,
69 }
70 writeln!(f, "END HttpResponse")
71 } else {
72 f.debug_struct("HttpResponse")
73 .field("status", &self.status)
74 .field("headers", &self.headers)
75 .field("body", &self.body)
76 .finish()
77 }
78 }
79}
80
81/// Error type for PKI environment hooks
82#[derive(Debug, thiserror::Error, derive_more::From)]
83#[error("reason: {reason}")]
84pub struct PkiEnvironmentHooksError {
85 /// the error reason
86 pub reason: String,
87}
88
89/// The PKI Environment Hooks used for external calls during e2e enrollment flow.
90/// When communicating with the Identity Provider (IDP) and Wire server,
91/// CoreCrypto delegates to the client app by calling the relevant methods.
92///
93/// Client App CoreCrypto Acme IDP
94/// | | | |
95/// | X509CredentialAcquisition().finalize() | |
96/// |-------------------------->| | |
97/// | | GET acme/root.pem | |
98/// | |------------------------> | |
99/// | | 200 OK | |
100/// | |<------------------------ | |
101/// | authenticate() | | |
102/// |<--------------------------| | |
103/// | | Authentication flow | |
104/// | ----------------------------------------------------------------------------> |
105/// |<----------------------------------------------------------------------------- |
106/// | return Success [PkiEnvironmentHooks.authenticate()] | |
107/// |<--------------------------| | |
108/// | | (excluded several calls for brevity) |
109/// | return Success(Credential) [X509CredentialAcquisition().finalize()] |
110/// |<--------------------------| | |
111#[cfg_attr(target_os = "unknown", async_trait::async_trait(?Send))]
112#[cfg_attr(not(target_os = "unknown"), async_trait::async_trait)]
113pub trait PkiEnvironmentHooks: std::fmt::Debug + Send + Sync {
114 /// Make an HTTP request
115 /// Used for requests to ACME servers, CRL distributors etc.
116 async fn http_request(
117 &self,
118 method: HttpMethod,
119 url: String,
120 headers: Vec<HttpHeader>,
121 body: Vec<u8>,
122 ) -> Result<HttpResponse, PkiEnvironmentHooksError>;
123
124 /// Authenticate with the user's identity provider (IdP)
125 ///
126 /// The implementation should perform an [authentication using the authorization code flow]
127 /// (<https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth>) with the PKCE
128 /// (<https://www.rfc-editor.org/rfc/rfc7636>) extension. As part of the authorization
129 /// request, the implementation should specify `key_auth` and `acme_aud` claims, along with
130 /// their values, in the `claims` parameter. This is to instruct the IdP to add the `key_auth`
131 /// and `acme_aud` claims to the ID token that will be returned as part of the access token.
132 ///
133 /// Once the authentication is completed successfully, the implementation should request
134 /// an access token from the IdP, extract the ID token from it and return it to the caller.
135 async fn authenticate(
136 &self,
137 idp: String,
138 key_auth: String,
139 acme_aud: String,
140 ) -> Result<String, PkiEnvironmentHooksError>;
141
142 /// Get a nonce from the backend
143 async fn get_backend_nonce(&self) -> Result<String, PkiEnvironmentHooksError>;
144
145 /// Fetch an access token to be used for the DPoP challenge (`wire-dpop-01`)
146 ///
147 /// The implementation should take the provided DPoP token (`dpop`) and make a request to the
148 /// backend to obtain an access token, which should be returned to the caller.
149 async fn fetch_backend_access_token(&self, dpop: String) -> Result<String, PkiEnvironmentHooksError>;
150}