wire_e2e_identity/acme/
chall.rs1use rusty_jwt_tools::prelude::*;
2
3use crate::acme::prelude::*;
4
5impl RustyAcme {
6 pub fn dpop_chall_request(
9 access_token: String,
10 dpop_chall: AcmeChallenge,
11 account: &AcmeAccount,
12 alg: JwsAlgorithm,
13 kp: &Pem,
14 previous_nonce: String,
15 ) -> RustyAcmeResult<AcmeJws> {
16 let acct_url = account.acct_url()?;
18
19 let payload = Some(serde_json::json!({
20 "access_token": access_token,
21 }));
22
23 let req = AcmeJws::new(alg, previous_nonce, &dpop_chall.url, Some(&acct_url), payload, kp)?;
24 Ok(req)
25 }
26
27 #[allow(clippy::too_many_arguments)]
30 pub fn oidc_chall_request(
31 id_token: String,
32 oidc_chall: AcmeChallenge,
33 account: &AcmeAccount,
34 alg: JwsAlgorithm,
35 kp: &Pem,
36 previous_nonce: String,
37 ) -> RustyAcmeResult<AcmeJws> {
38 let acct_url = account.acct_url()?;
40 let payload = Some(serde_json::json!({
41 "id_token": id_token,
42 }));
43 let req = AcmeJws::new(alg, previous_nonce, &oidc_chall.url, Some(&acct_url), payload, kp)?;
44 Ok(req)
45 }
46
47 pub fn new_chall_response(response: serde_json::Value) -> RustyAcmeResult<AcmeChallenge> {
49 let chall = serde_json::from_value::<AcmeChallenge>(response)?;
50 match chall.status {
51 Some(AcmeChallengeStatus::Valid) => {}
52 Some(AcmeChallengeStatus::Processing) => return Err(AcmeChallError::Processing)?,
53 Some(AcmeChallengeStatus::Invalid) => return Err(AcmeChallError::Invalid)?,
54 Some(AcmeChallengeStatus::Pending) => {
55 return Err(RustyAcmeError::ClientImplementationError(
56 "a challenge is not supposed to be pending at this point. \
57 It must either be 'valid' or 'processing'.",
58 ));
59 }
60 None => {
61 return Err(RustyAcmeError::ClientImplementationError(
62 "at this point a challenge is supposed to have a status",
63 ));
64 }
65 }
66 Ok(chall)
67 }
68}
69
70#[derive(Debug, thiserror::Error)]
71pub enum AcmeChallError {
72 #[error("This challenge is invalid")]
74 Invalid,
75 #[error("This challenge is being processed, retry later")]
77 Processing,
78}
79
80#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
83#[serde(rename_all = "camelCase")]
84pub struct AcmeChallenge {
85 #[serde(rename = "type")]
86 pub typ: AcmeChallengeType,
88 pub url: url::Url,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub status: Option<AcmeChallengeStatus>,
93 pub token: String,
95 pub target: url::Url,
99}
100
101#[cfg(test)]
102impl AcmeChallenge {
103 pub fn new_device() -> Self {
104 Self {
105 status: None,
106 typ: AcmeChallengeType::WireDpop01,
107 url: "ttps://stepca/acme/wire/challenge/EitdRA8gzxuRCrHlppZJfQsB8Hjsklpj/DaugXj4rBw04OfjyWfucICoaOAGGzXFQ"
108 .parse()
109 .unwrap(),
110 token: "DGyRejmCefe7v4NfDGDKfA".to_string(),
111 target: "http://wire.com:21893/clients/aeddd6d37af25726/access-token"
112 .parse()
113 .unwrap(),
114 }
115 }
116
117 pub fn new_user() -> Self {
118 Self {
119 status: None,
120 typ: AcmeChallengeType::WireOidc01,
121 url: "https://stepca/acme/wire/challenge/EitdRA8gzxuRCrHlppZJfQsB8Hjsklpj/47eOxmrLEJR3aJl7X0hpnH4y0rU8uRo2"
122 .parse()
123 .unwrap(),
124 token: "4xQIED9iPLQo1fkPLBq1znAniwvcVsxQ".to_string(),
125 target: "http://keycloak:15170/realms/master".parse().unwrap(),
126 }
127 }
128}
129
130#[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
132#[serde(rename_all = "lowercase")]
133pub enum AcmeChallengeStatus {
134 Pending,
135 Processing,
136 Valid,
137 Invalid,
138}
139
140#[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
141pub enum AcmeChallengeType {
142 #[serde(rename = "http-01")]
143 Http01,
144 #[serde(rename = "dns-01")]
145 Dns01,
146 #[serde(rename = "tls-alpn-01")]
147 TlsAlpn01,
148 #[serde(rename = "wire-dpop-01")]
150 WireDpop01,
151 #[serde(rename = "wire-oidc-01")]
153 WireOidc01,
154}
155
156#[cfg(test)]
157pub mod tests {
158 use serde_json::json;
159 use wasm_bindgen_test::*;
160
161 use super::*;
162
163 wasm_bindgen_test_configure!(run_in_browser);
164
165 #[test]
166 #[wasm_bindgen_test]
167 fn can_deserialize_rfc_sample_response() {
168 let rfc_sample = json!({
171 "type": "http-01",
172 "url": "https://example.com/acme/chall/prV_B7yEyA4",
173 "status": "pending",
174 "token": "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0",
175 "target": "https://example.com/target"
176 });
177 assert!(serde_json::from_value::<AcmeChallenge>(rfc_sample).is_ok());
178
179 let rfc_sample = json!({
182 "type": "dns-01",
183 "url": "https://example.com/acme/chall/Rg5dV14Gh1Q",
184 "status": "pending",
185 "token": "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA",
186 "target": "https://example.com/target"
187 });
188 assert!(serde_json::from_value::<AcmeChallenge>(rfc_sample).is_ok());
189 }
190
191 #[test]
192 #[wasm_bindgen_test]
193 fn chall_type_should_deserialize_as_expected() {
194 use serde_json::from_value as deser;
195 assert_eq!(
196 deser::<AcmeChallengeType>(json!("http-01")).unwrap(),
197 AcmeChallengeType::Http01
198 );
199 assert_eq!(
200 deser::<AcmeChallengeType>(json!("dns-01")).unwrap(),
201 AcmeChallengeType::Dns01
202 );
203 assert_eq!(
204 deser::<AcmeChallengeType>(json!("tls-alpn-01")).unwrap(),
205 AcmeChallengeType::TlsAlpn01
206 );
207 assert_eq!(
208 deser::<AcmeChallengeType>(json!("wire-dpop-01")).unwrap(),
209 AcmeChallengeType::WireDpop01
210 );
211 assert_eq!(
212 deser::<AcmeChallengeType>(json!("wire-oidc-01")).unwrap(),
213 AcmeChallengeType::WireOidc01
214 );
215 assert!(deser::<AcmeChallengeType>(json!("Http-01")).is_err());
216 assert!(deser::<AcmeChallengeType>(json!("http01")).is_err());
217 }
218}