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}