wire_e2e_identity/acquisition/
dpop_challenge.rs1use obfuscate::Obfuscated;
2use rusty_jwt_tools::prelude::{Dpop, Handle, Htm, RustyJwtTools};
3use url::Url;
4
5use super::{Result, X509CredentialAcquisition, get_header, states};
6use crate::{
7 acme,
8 acme::{AcmeAccount, AcmeChallenge, AcmeChallengeType, AcmeOrder},
9 pki_env::hooks::HttpMethod,
10};
11
12impl X509CredentialAcquisition<states::Initialized> {
13 async fn get_challenge(
14 &self,
15 url: &url::Url,
16 acme_account: &AcmeAccount,
17 nonce: String,
18 ) -> Result<(String, AcmeChallenge)> {
19 let authz_request =
20 acme::new_authz_request(url, acme_account, self.config.sign_alg, &self.acme_kp, nonce.clone())?;
21 let (nonce, response) = self.acme_request(url, &authz_request).await?;
22 let authorization = acme::new_authz_response(response)?;
23 let [challenge] = authorization.challenges;
24 log::debug!(
25 "acquisition({:?}): got ACME challenge {:?}",
26 Obfuscated::from(&self.sign_kp),
27 challenge.typ
28 );
29 Ok((nonce, challenge))
30 }
31
32 async fn get_challenges(
33 &self,
34 acme_account: &AcmeAccount,
35 order: &AcmeOrder,
36 nonce: String,
37 ) -> Result<(String, AcmeChallenge, AcmeChallenge)> {
38 let (nonce, challenge1) = self
45 .get_challenge(&order.authorizations[0], acme_account, nonce)
46 .await?;
47 let (nonce, challenge2) = self
48 .get_challenge(&order.authorizations[1], acme_account, nonce)
49 .await?;
50
51 use AcmeChallengeType::*;
55 match (challenge1.typ, challenge2.typ) {
56 (WireDpop01, WireOidc01) => Ok((nonce, challenge1, challenge2)),
57 (WireOidc01, WireDpop01) => Ok((nonce, challenge2, challenge1)),
58 _ => Err(acme::Error::from(crate::acme::AcmeAuthzError::InvalidChallengeType).into()),
59 }
60 }
61
62 pub async fn complete_dpop_challenge(self) -> Result<X509CredentialAcquisition<states::DpopChallengeCompleted>> {
64 let hooks = self.pki_env.hooks();
65
66 let url: Url = self
70 .config
71 .acme_directory_url
72 .parse()
73 .expect("valid ACME directory URL");
74
75 let resp = hooks
76 .http_request(HttpMethod::Get, url.to_string(), vec![], vec![])
77 .await?;
78 log::debug!(
79 "acquisition({:?}): got ACME server directory: {:?}",
80 Obfuscated::from(&self.sign_kp),
81 str::from_utf8(&resp.body),
82 );
83 let body = resp.json()?;
84 let directory = acme::acme_directory_response(body)?;
85
86 let url = directory.new_nonce.to_string();
87 let resp = hooks.http_request(HttpMethod::Head, url, vec![], vec![]).await?;
88 let nonce = get_header(&resp, "replay-nonce")?;
89 log::debug!(
90 "acquisition({:?}): got the initial nonce",
91 Obfuscated::from(&self.sign_kp),
92 );
93
94 let account_request = acme::new_account_request(&directory, self.config.sign_alg, &self.acme_kp, nonce)?;
98 let (nonce, response) = self.acme_request(&directory.new_account, &account_request).await?;
99 let acme_account = acme::new_account_response(response)?;
100 log::debug!(
101 "acquisition({:?}): created a new ACME account",
102 Obfuscated::from(&self.sign_kp),
103 );
104
105 let order_request = acme::new_order_request(
109 &self.config.display_name,
110 self.config.client_id.clone(),
111 &self.config.handle.clone().into(),
112 self.config.validity_period,
113 &directory,
114 &acme_account,
115 self.config.sign_alg,
116 &self.acme_kp,
117 nonce,
118 )?;
119 let (nonce, response) = self.acme_request(&directory.new_order, &order_request).await?;
120 let order = acme::new_order_response(response)?;
121 log::debug!(
122 "acquisition({:?}): created a new ACME order",
123 Obfuscated::from(&self.sign_kp),
124 );
125
126 let (nonce, dpop_challenge, oidc_challenge) = self.get_challenges(&acme_account, &order, nonce).await?;
127
128 let backend_nonce = hooks.get_backend_nonce().await?;
132 log::debug!(
133 "acquisition({:?}): got the Wire server nonce",
134 Obfuscated::from(&self.sign_kp),
135 );
136
137 let audience = dpop_challenge.url.clone();
138 let client_id = &self.config.client_id;
139 let handle = Handle::from(self.config.handle.clone()).try_to_qualified(&client_id.domain)?;
140 let dpop = Dpop {
141 htm: Htm::Post,
142 htu: dpop_challenge.target.clone().into(),
143 challenge: dpop_challenge.token.clone().into(),
144 handle,
145 team: self.config.team.clone().into(),
146 display_name: self.config.display_name.clone(),
147 extra_claims: None,
148 };
149 let token = RustyJwtTools::generate_dpop_token(
150 dpop,
151 client_id,
152 backend_nonce.into(),
153 audience,
154 std::time::Duration::from_mins(5),
155 self.config.sign_alg,
156 &self.acme_kp,
157 )?;
158
159 let access_token = hooks.fetch_backend_access_token(token).await?;
161 log::debug!(
162 "acquisition({:?}): got the Wire server access token",
163 Obfuscated::from(&self.sign_kp),
164 );
165
166 let dpop_challenge_request = acme::dpop_chall_request(
170 access_token,
171 dpop_challenge.clone(),
172 &acme_account,
173 self.config.sign_alg,
174 &self.acme_kp,
175 nonce,
176 )?;
177 let (nonce, response) = self.acme_request(&dpop_challenge.url, &dpop_challenge_request).await?;
178 let _ = acme::new_chall_response(response)?;
179 log::info!(
180 "acquisition({:?}): DPoP challenge completed",
181 Obfuscated::from(&self.sign_kp),
182 );
183
184 Ok(X509CredentialAcquisition::<states::DpopChallengeCompleted> {
185 pki_env: self.pki_env,
186 config: self.config,
187 sign_kp: self.sign_kp,
188 acme_kp: self.acme_kp,
189 acme_jwk: self.acme_jwk,
190 data: states::DpopChallengeCompleted {
191 nonce,
192 acme_account,
193 order,
194 oidc_challenge,
195 },
196 })
197 }
198}