wire_e2e_identity/acquisition/
dpop_challenge.rs1use rusty_jwt_tools::prelude::{Dpop, Handle, Htm, RustyJwtTools};
2
3use super::{Result, X509CredentialAcquisition, get_header, states};
4use crate::{
5 acme::{AcmeAccount, AcmeChallenge, AcmeChallengeType, AcmeOrder, RustyAcme, RustyAcmeError},
6 pki_env_hooks::HttpMethod,
7};
8
9impl X509CredentialAcquisition<states::Initialized> {
10 async fn get_challenge(
11 &self,
12 url: &url::Url,
13 acme_account: &AcmeAccount,
14 nonce: String,
15 ) -> Result<(String, AcmeChallenge)> {
16 let authz_request =
17 RustyAcme::new_authz_request(url, acme_account, self.config.sign_alg, &self.acme_kp, nonce.clone())?;
18 let (nonce, response) = self.acme_request(url, &authz_request).await?;
19 let authorization = RustyAcme::new_authz_response(response)?;
20 let [challenge] = authorization.challenges;
21 Ok((nonce, challenge))
22 }
23
24 async fn get_challenges(
25 &self,
26 acme_account: &AcmeAccount,
27 order: &AcmeOrder,
28 nonce: String,
29 ) -> Result<(String, AcmeChallenge, AcmeChallenge)> {
30 let (nonce, challenge1) = self
37 .get_challenge(&order.authorizations[0], acme_account, nonce)
38 .await?;
39 let (nonce, challenge2) = self
40 .get_challenge(&order.authorizations[1], acme_account, nonce)
41 .await?;
42
43 use AcmeChallengeType::*;
47 match (challenge1.typ, challenge2.typ) {
48 (WireDpop01, WireOidc01) => Ok((nonce, challenge1, challenge2)),
49 (WireOidc01, WireDpop01) => Ok((nonce, challenge2, challenge1)),
50 _ => Err(RustyAcmeError::from(crate::acme::AcmeAuthzError::InvalidChallengeType).into()),
51 }
52 }
53
54 pub async fn complete_dpop_challenge(self) -> Result<X509CredentialAcquisition<states::DpopChallengeCompleted>> {
56 let hooks = self.pki_env.hooks();
57
58 let url = self.acme_url("directory");
62
63 let resp = hooks
64 .http_request(HttpMethod::Get, url.to_string(), vec![], vec![])
65 .await?;
66 let body = resp.json()?;
67 let directory = RustyAcme::acme_directory_response(body).unwrap();
68
69 let url = directory.new_nonce.to_string();
70 let resp = hooks.http_request(HttpMethod::Get, url, vec![], vec![]).await?;
71 let nonce = get_header(&resp, "replay-nonce")?;
72
73 let account_request = RustyAcme::new_account_request(&directory, self.config.sign_alg, &self.acme_kp, nonce)?;
77 let (nonce, response) = self
78 .acme_request(&self.acme_url("new-account"), &account_request)
79 .await?;
80 let acme_account = RustyAcme::new_account_response(response)?;
81
82 let order_request = RustyAcme::new_order_request(
86 &self.config.display_name,
87 self.config.client_id.clone(),
88 &self.config.handle.clone().into(),
89 self.config.validity_period,
90 &directory,
91 &acme_account,
92 self.config.sign_alg,
93 &self.acme_kp,
94 nonce,
95 )?;
96 let (nonce, response) = self.acme_request(&self.acme_url("new-order"), &order_request).await?;
97 let order = RustyAcme::new_order_response(response)?;
98
99 let (nonce, dpop_challenge, oidc_challenge) = self.get_challenges(&acme_account, &order, nonce).await?;
100
101 let backend_nonce = hooks.get_backend_nonce().await?;
105
106 let audience = dpop_challenge.url.clone();
107 let client_id = &self.config.client_id;
108 let handle = Handle::from(self.config.handle.clone()).try_to_qualified(&client_id.domain)?;
109 let dpop = Dpop {
110 htm: Htm::Post,
111 htu: dpop_challenge.target.clone().into(),
112 challenge: dpop_challenge.token.clone().into(),
113 handle,
114 team: self.config.team.clone().into(),
115 display_name: self.config.display_name.clone(),
116 extra_claims: None,
117 };
118 let token = RustyJwtTools::generate_dpop_token(
119 dpop,
120 client_id,
121 backend_nonce.into(),
122 audience,
123 std::time::Duration::from_mins(5),
124 self.config.sign_alg,
125 &self.acme_kp,
126 )?;
127
128 let access_token = hooks.fetch_backend_access_token(token).await?;
130
131 let dpop_challenge_request = RustyAcme::dpop_chall_request(
135 access_token,
136 dpop_challenge.clone(),
137 &acme_account,
138 self.config.sign_alg,
139 &self.acme_kp,
140 nonce,
141 )?;
142 let (nonce, response) = self.acme_request(&dpop_challenge.url, &dpop_challenge_request).await?;
143 let _ = RustyAcme::new_chall_response(response)?;
144
145 Ok(X509CredentialAcquisition::<states::DpopChallengeCompleted> {
146 pki_env: self.pki_env,
147 config: self.config,
148 sign_kp: self.sign_kp,
149 acme_kp: self.acme_kp,
150 acme_jwk: self.acme_jwk,
151 data: states::DpopChallengeCompleted {
152 nonce,
153 acme_account,
154 order,
155 oidc_challenge,
156 },
157 })
158 }
159}