acme_lite/order/mod.rs
1//! Order life cycle.
2//!
3//! An order goes through a life cycle of different states that require various actions by
4//! the user. To ensure the user only use appropriate actions, this library have simple façade
5//! structs that wraps the actual [`ApiOrder`].
6//!
7//! 1. First prove ownership:
8//! * [`NewOrder`] -> [`Auth`]* -> [`Challenge`]
9//! 2. Then submit CSR and download the cert.
10//! * [`NewOrder`] -> [`CsrOrder`] -> [`CertOrder`]
11//!
12//! \* Possibly multiple auths.
13//!
14//! [`ApiOrder`]: ../api/struct.ApiOrder.html
15//! [`NewOrder`]: struct.NewOrder.html
16//! [`Auth`]: struct.Auth.html
17//! [`Challenge`]: struct.Challenge.html
18//! [`CsrOrder`]: struct.CsrOrder.html
19//! [`CertOrder`]: struct.CertOrder.html
20
21use std::{sync::Arc, time::Duration};
22
23use base64::prelude::*;
24use der::Encode as _;
25use ecdsa::SigningKey;
26use eyre::Context as _;
27use pkcs8::{DecodePrivateKey as _, EncodePrivateKey as _};
28
29use crate::{
30 acc::AccountInner,
31 api::{ApiAuth, ApiEmptyString, ApiFinalize, ApiOrder},
32 cert::{create_csr, Certificate},
33};
34
35mod auth;
36pub use self::auth::{Auth, Challenge};
37
38/// The order wrapped with an outer façade.
39pub(crate) struct Order {
40 inner: Arc<AccountInner>,
41 api_order: ApiOrder,
42 url: String,
43}
44
45impl Order {
46 pub(crate) fn new(inner: &Arc<AccountInner>, api_order: ApiOrder, url: String) -> Self {
47 Order {
48 inner: inner.clone(),
49 api_order,
50 url,
51 }
52 }
53}
54
55/// Helper to refresh an order status (POST-as-GET).
56pub(crate) async fn refresh_order(
57 inner: &Arc<AccountInner>,
58 url: String,
59 want_status: &'static str,
60) -> eyre::Result<Order> {
61 let res = inner.transport.call(&url, &ApiEmptyString).await?;
62
63 // our test rig requires the order to be in `want_status`.
64 // api_order_of is different for test compilation
65 let api_order = api_order_of(res, want_status).await?;
66
67 Ok(Order {
68 inner: inner.clone(),
69 api_order,
70 url,
71 })
72}
73
74#[cfg(not(test))]
75async fn api_order_of(res: reqwest::Response, _want_status: &str) -> eyre::Result<ApiOrder> {
76 Ok(res.json().await?)
77}
78
79#[cfg(test)]
80// our test rig requires the order to be in `want_status`
81async fn api_order_of(res: reqwest::Response, want_status: &str) -> eyre::Result<ApiOrder> {
82 let s = res.text().await?;
83 #[allow(clippy::trivial_regex)]
84 let re = regex::Regex::new("<STATUS>").unwrap();
85 let b = re.replace_all(&s, want_status).into_owned();
86 let api_order: ApiOrder = serde_json::from_str(&b)?;
87 Ok(api_order)
88}
89
90/// A new order created by [`Account::new_order`].
91///
92/// An order is created using one or many domains (a primary `CN` and possible multiple
93/// alt names). All domains in the order must have authorizations ([confirmed ownership])
94/// before the order can progress to submitting a [CSR].
95///
96/// This order façade provides calls to provide such authorizations and to progress the order
97/// when ready.
98///
99/// The ACME API provider might "remember" for a time that you already own a domain, which
100/// means you might not need to prove the ownership every time. Use appropriate methods to
101/// first check whether you really need to handle authorizations.
102///
103/// [`Account::new_order`]: ../struct.Account.html#method.new_order
104/// [confirmed ownership]: ../index.html#domain-ownership
105/// [CSR]: https://en.wikipedia.org/wiki/Certificate_signing_request
106pub struct NewOrder {
107 pub(crate) order: Order,
108}
109
110impl NewOrder {
111 /// Tell if the domains in this order have been authorized.
112 ///
113 /// This doesn't do any calls against the API. You must manually call [`refresh`].
114 ///
115 /// In ACME API terms, the order can either be `ready` or `valid`, which both would
116 /// mean we have passed the authorization stage.
117 ///
118 /// [`refresh`]: struct.NewOrder.html#method.refresh
119 pub fn is_validated(&self) -> bool {
120 self.order.api_order.is_status_ready() || self.order.api_order.is_status_valid()
121 }
122
123 /// If the order [`is_validated`] progress it to a [`CsrOrder`].
124 ///
125 /// This doesn't do any calls against the API. You must manually call [`refresh`].
126 ///
127 /// [`is_validated`]: struct.NewOrder.html#method.is_validated
128 /// [`CsrOrder`]: struct.CsrOrder.html
129 pub fn confirm_validations(&self) -> Option<CsrOrder> {
130 if self.is_validated() {
131 Some(CsrOrder {
132 order: Order::new(
133 &self.order.inner,
134 self.order.api_order.clone(),
135 self.order.url.clone(),
136 ),
137 })
138 } else {
139 None
140 }
141 }
142
143 /// Refresh the order state against the ACME API.
144 ///
145 /// The specification calls this a "POST-as-GET" against the order URL.
146 pub async fn refresh(&mut self) -> eyre::Result<()> {
147 let order = refresh_order(&self.order.inner, self.order.url.clone(), "ready").await?;
148 self.order = order;
149 Ok(())
150 }
151
152 /// Provide the authorizations. The number of authorizations will be the same as
153 /// the number of domains requests, i.e. at least one (the primary CN), but possibly
154 /// more (for alt names).
155 ///
156 /// If the order includes new domain names that have not been authorized before, this
157 /// list might contain a mix of already valid and not yet valid auths.
158 pub async fn authorizations(&self) -> eyre::Result<Vec<Auth>> {
159 let mut result = vec![];
160 if let Some(authorizations) = &self.order.api_order.authorizations {
161 for auth_url in authorizations {
162 let res = self
163 .order
164 .inner
165 .transport
166 .call(auth_url, &ApiEmptyString)
167 .await?;
168 let api_auth = res.json::<ApiAuth>().await?;
169 result.push(Auth::new(&self.order.inner, api_auth, auth_url));
170 }
171 }
172 Ok(result)
173 }
174
175 /// Access the underlying JSON object for debugging.
176 pub fn api_order(&self) -> &ApiOrder {
177 &self.order.api_order
178 }
179}
180
181/// An order that is ready for a [CSR] submission.
182///
183/// To submit the CSR is called "finalizing" the order.
184///
185/// To finalize, the user supplies a private key (from which a public key is derived). This
186/// library provides [functions to create private keys], but the user can opt for creating them
187/// in some other way.
188///
189/// This library makes no attempt at validating which key algorithms are used. Unsupported
190/// algorithms will show as an error when finalizing the order. It is up to the ACME API
191/// provider to decide which key algorithms to support.
192///
193/// Right now Let's Encrypt [supports]:
194///
195/// * RSA keys from 2048 to 4096 bits in length
196/// * P-256 and P-384 ECDSA keys
197///
198/// [CSR]: https://en.wikipedia.org/wiki/Certificate_signing_request
199/// [functions to create key pairs]: ../index.html#functions
200/// [supports]: https://letsencrypt.org/docs/integration-guide/#supported-key-algorithms
201pub struct CsrOrder {
202 pub(crate) order: Order,
203}
204
205impl CsrOrder {
206 /// Finalize the order by providing a private key as PEM.
207 ///
208 /// Once the CSR has been submitted, the order goes into a `processing` status,
209 /// where we must poll until the status changes. The `delay` is the
210 /// amount of time to wait between each poll attempt.
211 ///
212 /// This is a convenience wrapper that in turn calls the lower level [`finalize_signing_key`].
213 ///
214 /// [`finalize_signing_key`]: struct.CsrOrder.html#method.finalize_signing_key
215 pub async fn finalize(self, private_key_pem: &str, delay: Duration) -> eyre::Result<CertOrder> {
216 let signing_key =
217 SigningKey::from_pkcs8_pem(private_key_pem).context("Error reading private key PEM")?;
218 self.finalize_signing_key(signing_key, delay).await
219 }
220
221 /// Lower level finalize call that works directly with the openssl crate structures.
222 ///
223 /// Creates the CSR for the domains in the order and submit it to the ACME API.
224 ///
225 /// Once the CSR has been submitted, the order goes into a `processing` status,
226 /// where we must poll until the status changes. The `delay` is the
227 /// amount of time to wait between each poll attempt.
228 pub async fn finalize_signing_key(
229 self,
230 signing_key: p256::ecdsa::SigningKey,
231 delay: Duration,
232 ) -> eyre::Result<CertOrder> {
233 // the domains that we have authorized
234 let domains = self.order.api_order.domains();
235
236 // csr from private key and authorized domains.
237 let csr = create_csr(&signing_key, &domains)?;
238
239 // this is not the same as PEM.
240 let csr_der = csr.to_der()?;
241 let csr_enc = BASE64_URL_SAFE_NO_PAD.encode(&csr_der);
242 let finalize = ApiFinalize { csr: csr_enc };
243
244 let inner = self.order.inner;
245 let order_url = self.order.url;
246 let finalize_url = &self.order.api_order.finalize;
247
248 // if the CSR is invalid, we will get a 4xx code back that
249 // bombs out from this retry_call.
250 inner.transport.call(finalize_url, &finalize).await?;
251
252 // wait for the status to not be processing.
253 // valid -> cert is issued
254 // invalid -> the whole thing is off
255 let order = wait_for_order_status(&inner, &order_url, delay).await?;
256
257 if !order.api_order.is_status_valid() {
258 return Err(eyre::eyre!(
259 "Order is in status: {:?}",
260 order.api_order.status
261 ));
262 }
263
264 Ok(CertOrder { signing_key, order })
265 }
266
267 /// Access the underlying JSON object for debugging.
268 pub fn api_order(&self) -> &ApiOrder {
269 &self.order.api_order
270 }
271}
272
273async fn wait_for_order_status(
274 inner: &Arc<AccountInner>,
275 url: &str,
276 delay: Duration,
277) -> eyre::Result<Order> {
278 loop {
279 let order = refresh_order(inner, url.to_owned(), "valid").await?;
280
281 if !order.api_order.is_status_processing() {
282 return Ok(order);
283 }
284
285 tokio::time::sleep(delay).await;
286 }
287}
288
289/// Order for an issued certificate that is ready to download.
290pub struct CertOrder {
291 signing_key: p256::ecdsa::SigningKey,
292 order: Order,
293}
294
295impl CertOrder {
296 /// Request download of the issued certificate.
297 pub async fn download_cert(self) -> eyre::Result<Certificate> {
298 let url = self
299 .order
300 .api_order
301 .certificate
302 .ok_or_else(|| eyre::eyre!("certificate url"))?;
303
304 let inner = self.order.inner;
305
306 let res = inner.transport.call(&url, &ApiEmptyString).await?;
307
308 let signing_key_pem = self.signing_key.to_pkcs8_pem(der::pem::LineEnding::LF)?;
309
310 let certificate = res.text().await?;
311
312 Ok(Certificate::new(signing_key_pem, certificate))
313 }
314
315 /// Access the underlying JSON object for debugging.
316 pub fn api_order(&self) -> &ApiOrder {
317 &self.order.api_order
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324 use crate::{cert, Directory, DirectoryUrl};
325
326 #[tokio::test]
327 async fn test_get_authorizations() {
328 let server = crate::test::with_directory_server();
329 let url = DirectoryUrl::Other(&server.dir_url);
330 let dir = Directory::from_url(url).await.unwrap();
331 let acc = dir
332 .register_account(Some(vec!["mailto:foo@bar.com".to_owned()]))
333 .await
334 .unwrap();
335 let ord = acc.new_order("acmetest.example.com", &[]).await.unwrap();
336 let _authorizations = ord.authorizations().await.unwrap();
337 }
338
339 #[tokio::test]
340 async fn test_finalize() {
341 let server = crate::test::with_directory_server();
342 let url = DirectoryUrl::Other(&server.dir_url);
343 let dir = Directory::from_url(url).await.unwrap();
344 let acc = dir
345 .register_account(Some(vec!["mailto:foo@bar.com".to_owned()]))
346 .await
347 .unwrap();
348 let ord = acc.new_order("acmetest.example.com", &[]).await.unwrap();
349 // shortcut auth
350 let ord = CsrOrder { order: ord.order };
351 let signing_key = cert::create_p256_key();
352 let _ord = ord
353 .finalize_signing_key(signing_key, Duration::from_millis(1))
354 .await
355 .unwrap();
356 }
357
358 #[tokio::test]
359 async fn test_download_and_save_cert() {
360 let server = crate::test::with_directory_server();
361 let url = DirectoryUrl::Other(&server.dir_url);
362 let dir = Directory::from_url(url).await.unwrap();
363 let acc = dir
364 .register_account(Some(vec!["mailto:foo@bar.com".to_owned()]))
365 .await
366 .unwrap();
367 let ord = acc.new_order("acmetest.example.com", &[]).await.unwrap();
368
369 // shortcut auth
370 let ord = CsrOrder { order: ord.order };
371 let signing_key = cert::create_p256_key();
372 let ord = ord
373 .finalize_signing_key(signing_key, Duration::from_millis(1))
374 .await
375 .unwrap();
376
377 let cert = ord.download_cert().await.unwrap();
378 assert_eq!("CERT HERE", cert.certificate());
379 assert!(!cert.private_key().is_empty());
380 assert_eq!(cert.valid_days_left().unwrap(), 89);
381 }
382}