Skip to main content

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