1use base64::Engine;
2use jwt_simple::prelude::*;
3use rusty_jwt_tools::prelude::*;
4use x509_cert::der::Encode;
5
6use crate::acme::{
7 identifier::CanonicalIdentifier,
8 order::{AcmeOrderError, AcmeOrderStatus},
9 prelude::*,
10};
11
12impl RustyAcme {
13 pub fn finalize_req(
15 order: &AcmeOrder,
16 account: &AcmeAccount,
17 alg: JwsAlgorithm,
18 acme_kp: &Pem,
19 signing_kp: &Pem,
20 previous_nonce: String,
21 ) -> RustyAcmeResult<AcmeJws> {
22 let acct_url = account.acct_url()?;
24 order.verify()?;
25 let csr = Self::generate_csr(alg, order.try_get_coalesce_identifier()?, signing_kp)?;
26 let payload = AcmeFinalizeRequest { csr };
27 let req = AcmeJws::new(
28 alg,
29 previous_nonce,
30 &order.finalize,
31 Some(&acct_url),
32 Some(payload),
33 acme_kp,
34 )?;
35 Ok(req)
36 }
37
38 fn generate_csr(alg: JwsAlgorithm, identifier: CanonicalIdentifier, kp: &Pem) -> RustyAcmeResult<String> {
39 let algorithm = Self::csr_alg(alg)?;
40 let cert_info = x509_cert::request::CertReqInfo {
41 version: x509_cert::request::Version::V1,
42 subject: Self::csr_subject(&identifier)?,
43 public_key: Self::csr_spki(alg, kp)?,
44 attributes: Self::csr_attributes(identifier)?,
45 };
46 let signature = Self::csr_signature(alg, kp, &cert_info)?;
47
48 let csr = x509_cert::request::CertReq {
49 info: cert_info,
50 algorithm,
51 signature,
52 };
53 let csr = csr.to_der()?;
54 let csr = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(csr);
55 Ok(csr)
56 }
57
58 fn csr_alg(alg: JwsAlgorithm) -> RustyAcmeResult<x509_cert::spki::AlgorithmIdentifierOwned> {
59 let oid = match alg {
60 JwsAlgorithm::Ed25519 => const_oid::db::rfc8410::ID_ED_25519,
61 JwsAlgorithm::P256 => const_oid::db::rfc5912::ECDSA_WITH_SHA_256,
62 JwsAlgorithm::P384 => const_oid::db::rfc5912::ECDSA_WITH_SHA_384,
63 JwsAlgorithm::P521 => const_oid::db::rfc5912::ECDSA_WITH_SHA_512,
64 };
65 Self::into_asn1_alg(oid, None)
66 }
67
68 fn csr_subject(identifier: &CanonicalIdentifier) -> RustyAcmeResult<x509_cert::name::DistinguishedName> {
69 let dn_domain_oid = const_oid::db::rfc4519::ORGANIZATION_NAME;
70 let dn_domain_value =
71 x509_cert::attr::AttributeValue::new(x509_cert::der::Tag::Utf8String, identifier.domain.as_bytes())?;
72 let dn_domain = x509_cert::attr::AttributeTypeAndValue {
73 oid: dn_domain_oid,
74 value: dn_domain_value,
75 };
76
77 let dn_display_name_oid = const_oid::ObjectIdentifier::new("2.16.840.1.113730.3.1.241")?;
80 let dn_display_name_value =
82 x509_cert::attr::AttributeValue::new(x509_cert::der::Tag::Utf8String, identifier.display_name.as_bytes())?;
83 let dn_display_name = x509_cert::attr::AttributeTypeAndValue {
84 oid: dn_display_name_oid,
85 value: dn_display_name_value,
86 };
87
88 let domain = x509_cert::name::RelativeDistinguishedName(vec![dn_domain].try_into()?);
89 let display_name = x509_cert::name::RelativeDistinguishedName(vec![dn_display_name].try_into()?);
90 let subject = x509_cert::name::DistinguishedName::from(vec![domain, display_name]);
91 Ok(subject)
92 }
93
94 fn csr_spki(alg: JwsAlgorithm, kp: &Pem) -> RustyAcmeResult<x509_cert::spki::SubjectPublicKeyInfoOwned> {
95 let (pk, algorithm) = match alg {
96 JwsAlgorithm::Ed25519 => {
97 let pk = Ed25519KeyPair::from_pem(kp.as_str())?.public_key().to_bytes();
98 let alg = Self::into_asn1_alg(const_oid::db::rfc8410::ID_ED_25519, None)?;
100 (pk, alg)
101 }
102 JwsAlgorithm::P256 => {
103 let kp = ES256KeyPair::from_pem(kp.as_str())?;
104 let pk = kp.public_key().public_key().to_bytes_uncompressed();
105
106 let alg = Self::into_asn1_alg(
108 const_oid::db::rfc5912::ID_EC_PUBLIC_KEY,
109 Some(const_oid::db::rfc5912::SECP_256_R_1),
110 )?;
111 (pk, alg)
112 }
113 JwsAlgorithm::P384 => {
114 let kp = ES384KeyPair::from_pem(kp.as_str())?;
115 let pk = kp.public_key().public_key().to_bytes_uncompressed();
116
117 let alg = Self::into_asn1_alg(
119 const_oid::db::rfc5912::ID_EC_PUBLIC_KEY,
120 Some(const_oid::db::rfc5912::SECP_384_R_1),
121 )?;
122 (pk, alg)
123 }
124 JwsAlgorithm::P521 => {
125 let kp = ES512KeyPair::from_pem(kp.as_str())?;
126 let pk = kp.public_key().public_key().to_bytes_uncompressed();
127
128 let alg = Self::into_asn1_alg(
130 const_oid::db::rfc5912::ID_EC_PUBLIC_KEY,
131 Some(const_oid::db::rfc5912::SECP_521_R_1),
132 )?;
133 (pk, alg)
134 }
135 };
136 let subject_public_key = x509_cert::der::asn1::BitString::new(0, pk)?;
137 Ok(x509_cert::spki::SubjectPublicKeyInfoOwned {
138 algorithm,
139 subject_public_key,
140 })
141 }
142
143 fn csr_attributes(identifier: CanonicalIdentifier) -> RustyAcmeResult<x509_cert::attr::Attributes> {
145 fn gn(n: impl AsRef<str>) -> RustyAcmeResult<x509_cert::ext::pkix::name::GeneralName> {
146 let ia5_str = x509_cert::der::asn1::Ia5String::new(n.as_ref())?;
147 Ok(x509_cert::ext::pkix::name::GeneralName::UniformResourceIdentifier(
148 ia5_str,
149 ))
150 }
151 let san =
152 x509_cert::ext::pkix::SubjectAltName(vec![gn(identifier.client_id)?, gn(identifier.handle.as_str())?]);
153 let san = x509_cert::attr::AttributeValue::new(x509_cert::der::Tag::OctetString, san.to_der()?)?;
154
155 let san_oid = const_oid::db::rfc5280::ID_CE_SUBJECT_ALT_NAME.to_der()?;
156 let san = [san_oid, san.to_der()?].concat();
157 let san = x509_cert::attr::AttributeValue::new(x509_cert::der::Tag::Sequence, san)?;
158 let san = x509_cert::attr::AttributeValue::new(x509_cert::der::Tag::Sequence, san.to_der()?)?;
159
160 let attributes = vec![x509_cert::attr::Attribute {
161 oid: const_oid::db::rfc5912::ID_EXTENSION_REQ,
162 values: vec![san].try_into()?,
163 }];
164 Ok(attributes.try_into()?)
165 }
166
167 fn csr_signature(
168 alg: JwsAlgorithm,
169 kp: &Pem,
170 cert_info: &x509_cert::request::CertReqInfo,
171 ) -> RustyAcmeResult<x509_cert::der::asn1::BitString> {
172 use signature::Signer as _;
173 let cert_data = cert_info.to_der()?;
174
175 let signature = match alg {
176 JwsAlgorithm::Ed25519 => {
177 let kp = Ed25519KeyPair::from_pem(kp.as_str())?;
178 let signature = kp.key_pair().as_ref().sign(&cert_data);
179 x509_cert::der::asn1::BitString::new(0, signature.to_vec())?
180 }
181 JwsAlgorithm::P256 => {
182 let kp = ES256KeyPair::from_pem(kp.as_str())?;
183 let sk: &p256::ecdsa::SigningKey = kp.key_pair().as_ref();
184 let signature: p256::ecdsa::DerSignature = sk.try_sign(&cert_data)?;
185 x509_cert::der::asn1::BitString::new(0, signature.to_der()?)?
186 }
187 JwsAlgorithm::P384 => {
188 let kp = ES384KeyPair::from_pem(kp.as_str())?;
189 let sk: &p384::ecdsa::SigningKey = kp.key_pair().as_ref();
190 let signature: p384::ecdsa::DerSignature = sk.try_sign(&cert_data)?;
191 x509_cert::der::asn1::BitString::new(0, signature.to_der()?)?
192 }
193 JwsAlgorithm::P521 => {
194 let kp = ES512KeyPair::from_pem(kp.as_str())?;
195 let sk: ecdsa::SigningKey<p521::NistP521> = kp.key_pair().as_ref().clone();
196 let sk = p521::ecdsa::SigningKey::from(sk);
197
198 let signature: p521::ecdsa::DerSignature = sk.try_sign(&cert_data)?.to_der();
199 x509_cert::der::asn1::BitString::new(0, signature.to_der()?)?
200 }
201 };
202 Ok(signature)
203 }
204
205 fn into_asn1_alg(
206 oid: const_oid::ObjectIdentifier,
207 oid_parameter: Option<const_oid::ObjectIdentifier>,
208 ) -> RustyAcmeResult<x509_cert::spki::AlgorithmIdentifierOwned> {
209 let alg = x509_cert::spki::AlgorithmIdentifierOwned {
210 oid,
211 parameters: oid_parameter.map(Into::into),
212 };
213 Ok(alg)
214 }
215
216 pub fn finalize_response(response: serde_json::Value) -> RustyAcmeResult<AcmeFinalize> {
218 let finalize = serde_json::from_value::<AcmeFinalize>(response)?;
219 Ok(finalize)
220 }
221}
222
223#[derive(Debug, thiserror::Error)]
224#[error(transparent)]
225pub struct AcmeFinalizeError(#[from] AcmeOrderError);
226
227#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
228#[cfg_attr(test, derive(Clone))]
229#[serde(rename_all = "camelCase")]
230struct AcmeFinalizeRequest {
231 csr: String,
233}
234
235#[derive(Debug, serde::Serialize, serde::Deserialize)]
236#[cfg_attr(test, derive(Clone))]
237#[serde(rename_all = "camelCase")]
238pub struct AcmeFinalize {
239 pub certificate: url::Url,
240 #[serde(flatten)]
241 pub order: AcmeOrder,
242}
243
244impl AcmeFinalize {
245 pub fn verify(&self) -> RustyAcmeResult<()> {
246 match self.order.status {
247 AcmeOrderStatus::Valid => {}
248 AcmeOrderStatus::Pending | AcmeOrderStatus::Processing | AcmeOrderStatus::Ready => {
249 return Err(RustyAcmeError::ClientImplementationError(
250 "finalize is not supposed to be 'pending | processing | ready' at this point. \
251 It means you have forgotten previous steps",
252 ));
253 }
254 AcmeOrderStatus::Invalid => return Err(AcmeFinalizeError(AcmeOrderError::Invalid))?,
255 }
256 self.order.verify().map_err(|e| match e {
257 RustyAcmeError::OrderError(e) => RustyAcmeError::FinalizeError(AcmeFinalizeError(e)),
258 _ => e,
259 })?;
260 Ok(())
261 }
262}
263
264#[cfg(test)]
265impl Default for AcmeFinalize {
266 fn default() -> Self {
267 Self {
268 certificate: "https://acme-server/acme/wire-acme/certificate/poWXmZGdL5d5qlvHMHRC19w2O9s96fvz"
269 .parse()
270 .unwrap(),
271 order: AcmeOrder {
272 status: AcmeOrderStatus::Valid,
273 ..Default::default()
274 },
275 }
276 }
277}
278
279#[cfg(test)]
280pub mod tests {
281 use serde_json::json;
282 use wasm_bindgen_test::*;
283
284 use super::*;
285
286 wasm_bindgen_test_configure!(run_in_browser);
287
288 mod json {
289 use super::*;
290
291 #[test]
292 #[wasm_bindgen_test]
293 fn can_deserialize_sample_response() {
294 let rfc_sample = json!({
295 "status": "valid",
296 "expires": "2016-01-20T14:09:07.99Z",
297 "notBefore": "2016-01-01T00:00:00Z",
298 "notAfter": "2016-01-08T00:00:00Z",
299 "identifiers": [
300 { "type": "wireapp-user", "value": "www.example.org" },
301 { "type": "wireapp-device", "value": "example.org" }
302 ],
303 "authorizations": [
304 "https://example.com/acme/authz/PAniVnsZcis",
305 "https://example.com/acme/authz/r4HqLzrSrpI"
306 ],
307 "finalize": "https://example.com/acme/order/TOlocE8rfgo/finalize",
308 "certificate": "https://example.com/acme/cert/mAt3xBGaobw"
309 });
310 assert!(serde_json::from_value::<AcmeFinalize>(rfc_sample).is_ok());
311 }
312 }
313
314 mod verify {
315 use super::*;
316
317 #[test]
318 #[wasm_bindgen_test]
319 fn should_succeed_when_valid() {
320 let finalize = AcmeFinalize {
321 order: AcmeOrder {
322 status: AcmeOrderStatus::Valid,
323 ..Default::default()
324 },
325 ..Default::default()
326 };
327 assert!(finalize.verify().is_ok());
328 }
329
330 #[test]
331 #[wasm_bindgen_test]
332 fn should_fail_when_expired() {
333 let yesterday = time::OffsetDateTime::now_utc() - time::Duration::days(1);
335 let finalize = AcmeFinalize {
336 order: AcmeOrder {
337 status: AcmeOrderStatus::Valid,
338 expires: Some(yesterday),
339 ..Default::default()
340 },
341 ..Default::default()
342 };
343 assert!(matches!(
344 finalize.verify().unwrap_err(),
345 RustyAcmeError::FinalizeError(AcmeFinalizeError(AcmeOrderError::Expired))
346 ));
347 }
348
349 #[test]
350 #[wasm_bindgen_test]
351 fn should_fail_when_status_not_valid() {
352 for status in [
353 AcmeOrderStatus::Pending,
354 AcmeOrderStatus::Processing,
355 AcmeOrderStatus::Ready,
356 ] {
357 let finalize = AcmeFinalize {
358 order: AcmeOrder {
359 status,
360 ..Default::default()
361 },
362 ..Default::default()
363 };
364 assert!(matches!(
365 finalize.verify().unwrap_err(),
366 RustyAcmeError::ClientImplementationError(_),
367 ));
368 }
369 }
370
371 #[test]
372 #[wasm_bindgen_test]
373 fn should_fail_when_status_invalid() {
374 let finalize = AcmeFinalize {
375 order: AcmeOrder {
376 status: AcmeOrderStatus::Invalid,
377 ..Default::default()
378 },
379 ..Default::default()
380 };
381 assert!(matches!(
382 finalize.verify().unwrap_err(),
383 RustyAcmeError::FinalizeError(AcmeFinalizeError(AcmeOrderError::Invalid)),
384 ));
385 }
386 }
387}