1 //! A blocking higher-level ACME client implementation using 'curl'.
6 use serde
::{Deserialize, Serialize}
;
10 use crate::order
::OrderData
;
11 use crate::request
::ErrorResponse
;
12 use crate::{Account, Authorization, Challenge, Directory, Error, Order, Request}
;
14 macro_rules
! format_err
{
15 ($
($fmt
:tt
)*) => { Error::Client(format!($($fmt)*)) }
;
19 ($
($fmt
:tt
)*) => {{ return Err(format_err!($($fmt)*)); }
}
22 /// Low level HTTP response structure.
23 pub struct HttpResponse
{
24 /// The raw HTTP response body as a byte vector.
27 /// The http status code.
30 /// The headers relevant to the ACME protocol.
35 /// Check the HTTP status code for a success code (200..299).
36 pub fn is_success(&self) -> bool
{
37 self.status
>= 200 && self.status
< 300
40 /// Convenience shortcut to perform json deserialization of the returned body.
41 pub fn json
<T
: for<'a
> Deserialize
<'a
>>(&self) -> Result
<T
, Error
> {
42 Ok(serde_json
::from_slice(&self.body
)?
)
45 /// Access the raw body as bytes.
46 pub fn bytes(&self) -> &[u8] {
50 /// Get the returned location header. Borrowing shortcut to `self.headers.location`.
51 pub fn location(&self) -> Option
<&str> {
52 self.headers
.location
.as_deref()
55 /// Convenience helper to assert that a location header was part of the response.
56 pub fn location_required(&mut self) -> Result
<String
, Error
> {
60 .ok_or_else(|| format_err
!("missing Location header"))
64 /// Contains headers from the HTTP response which are relevant parts of the Acme API.
66 /// Note that access to the `nonce` header is internal to this crate only, since a nonce will
67 /// always be moved out of the response into the `Client` whenever a new nonce is received.
70 /// The 'Location' header usually encodes the URL where an account or order can be queried from
71 /// after they were created.
72 pub location
: Option
<String
>,
73 nonce
: Option
<String
>,
77 agent
: Option
<ureq
::Agent
>,
78 nonce
: Option
<String
>,
79 proxy
: Option
<String
>,
83 fn agent(&mut self) -> Result
<&mut ureq
::Agent
, Error
> {
84 if self.agent
.is_none() {
85 let connector
= Arc
::new(
86 native_tls
::TlsConnector
::new()
87 .map_err(|err
| format_err
!("failed to create tls connector: {}", err
))?
,
90 let mut builder
= ureq
::AgentBuilder
::new().tls_connector(connector
);
92 if let Some(proxy
) = self.proxy
.as_deref() {
93 builder
= builder
.proxy(
94 ureq
::Proxy
::new(proxy
)
95 .map_err(|err
| format_err
!("failed to set proxy: {}", err
))?
,
99 self.agent
= Some(builder
.build());
102 Ok(self.agent
.as_mut().unwrap())
117 request_body
: Option
<(&str, &[u8])>, // content-type and body
118 ) -> Result
<HttpResponse
, Error
> {
119 let agent
= self.agent()?
;
120 let req
= match method
{
121 b
"POST" => agent
.post(url
),
122 b
"GET" => agent
.get(url
),
123 b
"HEAD" => agent
.head(url
),
124 other
=> bail
!("invalid http method: {:?}", other
),
127 let response
= if let Some((content_type
, body
)) = request_body
{
128 req
.set("Content-Type", content_type
)
129 .set("Content-Length", &body
.len().to_string())
134 .map_err(|err
| format_err
!("http request failed: {}", err
))?
;
136 let mut headers
= Headers
::default();
137 if let Some(value
) = response
.header(crate::LOCATION
) {
138 headers
.location
= Some(value
.to_owned());
141 if let Some(value
) = response
.header(crate::REPLAY_NONCE
) {
142 headers
.nonce
= Some(value
.to_owned());
145 let status
= response
.status();
147 let mut body
= Vec
::new();
150 .take(16 * 1024 * 1024) // arbitrary limit
151 .read_to_end(&mut body
)
152 .map_err(|err
| format_err
!("failed to read response body: {}", err
))?
;
161 pub fn set_proxy(&mut self, proxy
: String
) {
162 self.proxy
= Some(proxy
);
166 /// Low-level API to run an API request. This automatically updates the current nonce!
167 fn run_request(&mut self, request
: Request
) -> Result
<HttpResponse
, Error
> {
168 let body
= if request
.body
.is_empty() {
171 Some((request
.content_type
, request
.body
.as_bytes()))
174 let mut response
= self
175 .execute(request
.method
.as_bytes(), &request
.url
, body
)
178 let method
= &request
.method
;
179 let url
= &request
.url
;
180 move |err
| format_err
!("failed to execute {} request to {}: {}", method
, url
, err
)
183 let got_nonce
= self.update_nonce(&mut response
)?
;
185 if response
.is_success() {
186 if response
.status
!= request
.expected
{
187 return Err(Error
::InvalidApi(format
!(
188 "API server responded with unexpected status code: {:?}",
195 let error
: ErrorResponse
= response
.json().map_err(|err
| {
196 format_err
!("error status with improper error ACME response: {}", err
)
199 if error
.ty
== error
::BAD_NONCE
{
201 return Err(Error
::InvalidApi(
202 "badNonce without a new Replay-Nonce header".to_string(),
205 return Err(Error
::BadNonce
);
208 Err(Error
::Api(error
))
211 /// If the response contained a nonce, update our nonce and return `true`, otherwise return
213 fn update_nonce(&mut self, response
: &mut HttpResponse
) -> Result
<bool
, Error
> {
214 match response
.headers
.nonce
.take() {
216 self.nonce
= Some(nonce
);
223 /// Update the nonce, if there isn't one it is an error.
224 fn must_update_nonce(&mut self, response
: &mut HttpResponse
) -> Result
<(), Error
> {
225 if !self.update_nonce(response
)?
{
226 bail
!("newNonce URL did not return a nonce");
231 /// Update the Nonce.
232 fn new_nonce(&mut self, new_nonce_url
: &str) -> Result
<(), Error
> {
233 let mut response
= self.execute(b
"HEAD", new_nonce_url
, None
).map_err(|err
| {
234 Error
::InvalidApi(format
!("failed to get HEAD of newNonce URL: {}", err
))
237 if !response
.is_success() {
238 bail
!("HEAD on newNonce URL returned error");
241 self.must_update_nonce(&mut response
)?
;
246 /// Make sure a nonce is available without forcing renewal.
247 fn nonce(&mut self, new_nonce_url
: &str) -> Result
<&str, Error
> {
248 if self.nonce
.is_none() {
249 self.new_nonce(new_nonce_url
)?
;
253 .ok_or_else(|| format_err
!("failed to get nonce"))
257 /// A blocking Acme client using curl's `Easy` interface.
260 directory
: Option
<Directory
>,
261 account
: Option
<Account
>,
262 directory_url
: String
,
266 /// Create a new Client. This has no account associated with it yet, so the next step is to
267 /// either attach an existing `Account` or create a new one.
268 pub fn new(directory_url
: String
) -> Self {
277 /// Get the directory URL without querying the `Directory` structure.
279 /// The difference to [`directory`](Client::directory()) is that this does not
280 /// attempt to fetch the directory data from the ACME server.
281 pub fn directory_url(&self) -> &str {
285 /// Set the account this client should use.
286 pub fn set_account(&mut self, account
: Account
) {
287 self.account
= Some(account
);
290 /// Get the Directory information.
291 pub fn directory(&mut self) -> Result
<&Directory
, Error
> {
292 Self::get_directory(&mut self.inner
, &mut self.directory
, &self.directory_url
)
295 /// Get the Directory information.
296 fn get_directory
<'a
>(
297 inner
: &'_
mut Inner
,
298 directory
: &'a
mut Option
<Directory
>,
300 ) -> Result
<&'a Directory
, Error
> {
301 if let Some(d
) = directory
{
306 .execute(b
"GET", directory_url
, None
)
307 .map_err(|err
| Error
::InvalidApi(format
!("failed to get directory info: {}", err
)))?
;
309 if !response
.is_success() {
311 "GET on the directory URL returned error status ({})",
316 *directory
= Some(Directory
::from_parts(
317 directory_url
.to_string(),
320 Ok(directory
.as_ref().unwrap())
323 /// Get the current account, if there is one.
324 pub fn account(&self) -> Option
<&Account
> {
325 self.account
.as_ref()
328 /// Convenience method to get the ToS URL from the contained `Directory`.
330 /// This requires mutable self as the directory information may be lazily loaded, which can
332 pub fn terms_of_service_url(&mut self) -> Result
<Option
<&str>, Error
> {
333 Ok(self.directory()?
.terms_of_service_url())
336 /// Get a fresh nonce (this should normally not be required as nonces are updated
337 /// automatically, even when a `badNonce` error occurs, which according to the ACME API
338 /// specification should include a new valid nonce in its headers anyway).
339 pub fn new_nonce(&mut self) -> Result
<(), Error
> {
340 let was_none
= self.inner
.nonce
.is_none();
342 Self::get_directory(&mut self.inner
, &mut self.directory
, &self.directory_url
)?
;
343 if was_none
&& self.inner
.nonce
.is_some() {
344 // this was the first call and we already got a nonce from querying the directory
348 // otherwise actually call up to get a new nonce
349 self.inner
.new_nonce(directory
.new_nonce_url())
353 fn nonce
<'a
>(inner
: &'a
mut Inner
, directory
: &'_ Directory
) -> Result
<&'a
str, Error
> {
354 inner
.nonce(directory
.new_nonce_url())
357 /// Convenience method to create a new account with a list of ACME compatible contact strings
358 /// (eg. `mailto:someone@example.com`).
360 /// Please remember to persist the returned `Account` structure somewhere to not lose access to
363 /// If an RSA key size is provided, an RSA key will be generated. Otherwise an EC key using the
364 /// P-256 curve will be generated.
367 contact
: Vec
<String
>,
369 rsa_bits
: Option
<u32>,
370 ) -> Result
<&Account
, Error
> {
371 let account
= Account
::creator()
372 .set_contacts(contact
)
373 .agree_to_tos(tos_agreed
);
374 let account
= if let Some(bits
) = rsa_bits
{
375 account
.generate_rsa_key(bits
)?
377 account
.generate_ec_key()?
380 self.register_account(account
)
383 /// Register an ACME account.
385 /// This uses an [`AccountCreator`](crate::account::AccountCreator) since it may need to build
386 /// the request multiple times in case the we get a `BadNonce` error.
387 pub fn register_account(
389 account
: crate::account
::AccountCreator
,
390 ) -> Result
<&Account
, Error
> {
391 let mut retry
= retry();
392 let mut response
= loop {
396 Self::get_directory(&mut self.inner
, &mut self.directory
, &self.directory_url
)?
;
397 let nonce
= Self::nonce(&mut self.inner
, directory
)?
;
398 let request
= account
.request(directory
, nonce
)?
;
399 match self.run_request(request
) {
400 Ok(response
) => break response
,
401 Err(err
) if err
.is_bad_nonce() => continue,
402 Err(err
) => return Err(err
),
406 let account
= account
.response(response
.location_required()?
, response
.bytes().as_ref())?
;
408 self.account
= Some(account
);
409 Ok(self.account
.as_ref().unwrap())
412 fn need_account(account
: &Option
<Account
>) -> Result
<&Account
, Error
> {
415 .ok_or_else(|| format_err
!("cannot use client without an account"))
418 /// Update account data.
420 /// Low-level version: we allow arbitrary data to be passed to the remote here, it's up to the
421 /// user to know what to do for now.
422 pub fn update_account
<T
: Serialize
>(&mut self, data
: &T
) -> Result
<&Account
, Error
> {
423 let account
= Self::need_account(&self.account
)?
;
425 let mut retry
= retry();
426 let response
= loop {
429 Self::get_directory(&mut self.inner
, &mut self.directory
, &self.directory_url
)?
;
430 let nonce
= Self::nonce(&mut self.inner
, directory
)?
;
431 let request
= account
.post_request(&account
.location
, nonce
, data
)?
;
432 let response
= match self.inner
.run_request(request
) {
433 Ok(response
) => response
,
434 Err(err
) if err
.is_bad_nonce() => continue,
435 Err(err
) => return Err(err
),
441 // unwrap: we asserted we have an account at the top of the method!
442 let account
= self.account
.as_mut().unwrap();
443 account
.data
= response
.json()?
;
447 /// Method to create a new order for a set of domains.
449 /// Please remember to persist the order somewhere (ideally along with the account data) in
450 /// order to finish & query it later on.
451 pub fn new_order(&mut self, domains
: Vec
<String
>) -> Result
<Order
, Error
> {
452 let account
= Self::need_account(&self.account
)?
;
456 .fold(OrderData
::new(), |order
, domain
| order
.domain(domain
));
458 let mut retry
= retry();
463 Self::get_directory(&mut self.inner
, &mut self.directory
, &self.directory_url
)?
;
464 let nonce
= Self::nonce(&mut self.inner
, directory
)?
;
465 let mut new_order
= account
.new_order(&order
, directory
, nonce
)?
;
466 let mut response
= match self.inner
.run_request(new_order
.request
.take().unwrap()) {
467 Ok(response
) => response
,
468 Err(err
) if err
.is_bad_nonce() => continue,
469 Err(err
) => return Err(err
),
472 return new_order
.response(response
.location_required()?
, response
.bytes().as_ref());
476 /// Assuming the provided URL is an 'Authorization' URL, get and deserialize it.
477 pub fn get_authorization(&mut self, url
: &str) -> Result
<Authorization
, Error
> {
478 self.post_as_get(url
)?
.json()
481 /// Assuming the provided URL is an 'Order' URL, get and deserialize it.
482 pub fn get_order(&mut self, url
: &str) -> Result
<OrderData
, Error
> {
483 self.post_as_get(url
)?
.json()
486 /// Low level "POST-as-GET" request.
487 pub fn post_as_get(&mut self, url
: &str) -> Result
<HttpResponse
, Error
> {
488 let account
= Self::need_account(&self.account
)?
;
490 let mut retry
= retry();
495 Self::get_directory(&mut self.inner
, &mut self.directory
, &self.directory_url
)?
;
496 let nonce
= Self::nonce(&mut self.inner
, directory
)?
;
497 let request
= account
.get_request(url
, nonce
)?
;
498 match self.inner
.run_request(request
) {
499 Ok(response
) => return Ok(response
),
500 Err(err
) if err
.is_bad_nonce() => continue,
501 Err(err
) => return Err(err
),
506 /// Low level POST request.
507 pub fn post
<T
: Serialize
>(&mut self, url
: &str, data
: &T
) -> Result
<HttpResponse
, Error
> {
508 let account
= Self::need_account(&self.account
)?
;
510 let mut retry
= retry();
515 Self::get_directory(&mut self.inner
, &mut self.directory
, &self.directory_url
)?
;
516 let nonce
= Self::nonce(&mut self.inner
, directory
)?
;
517 let request
= account
.post_request(url
, nonce
, data
)?
;
518 match self.inner
.run_request(request
) {
519 Ok(response
) => return Ok(response
),
520 Err(err
) if err
.is_bad_nonce() => continue,
521 Err(err
) => return Err(err
),
526 /// Request challenge validation. Afterwards, the challenge should be polled.
527 pub fn request_challenge_validation(&mut self, url
: &str) -> Result
<Challenge
, Error
> {
528 self.post(url
, &serde_json
::json
!({}
))?
.json()
531 /// Shortcut to `account().ok_or_else(...).key_authorization()`.
532 pub fn key_authorization(&self, token
: &str) -> Result
<String
, Error
> {
533 Self::need_account(&self.account
)?
.key_authorization(token
)
536 /// Shortcut to `account().ok_or_else(...).dns_01_txt_value()`.
537 /// the key authorization value.
538 pub fn dns_01_txt_value(&self, token
: &str) -> Result
<String
, Error
> {
539 Self::need_account(&self.account
)?
.dns_01_txt_value(token
)
542 /// Low-level API to run an n API request. This automatically updates the current nonce!
543 pub fn run_request(&mut self, request
: Request
) -> Result
<HttpResponse
, Error
> {
544 self.inner
.run_request(request
)
547 /// Finalize an Order via its `finalize` URL property and the DER encoded CSR.
548 pub fn finalize(&mut self, url
: &str, csr
: &[u8]) -> Result
<(), Error
> {
549 let csr
= b64u
::encode(csr
);
550 let data
= serde_json
::json
!({ "csr": csr }
);
551 self.post(url
, &data
)?
;
555 /// Download a certificate via its 'certificate' URL property.
557 /// The certificate will be a PEM certificate chain.
558 pub fn get_certificate(&mut self, url
: &str) -> Result
<Vec
<u8>, Error
> {
559 Ok(self.post_as_get(url
)?
.body
)
562 /// Revoke an existing certificate (PEM or DER formatted).
563 pub fn revoke_certificate(
567 ) -> Result
<(), Error
> {
568 // TODO: This can also work without an account.
569 let account
= Self::need_account(&self.account
)?
;
571 let revocation
= account
.revoke_certificate(certificate
, reason
)?
;
573 let mut retry
= retry();
578 Self::get_directory(&mut self.inner
, &mut self.directory
, &self.directory_url
)?
;
579 let nonce
= Self::nonce(&mut self.inner
, directory
)?
;
580 let request
= revocation
.request(directory
, nonce
)?
;
581 match self.inner
.run_request(request
) {
582 Ok(_response
) => return Ok(()),
583 Err(err
) if err
.is_bad_nonce() => continue,
584 Err(err
) => return Err(err
),
590 pub fn set_proxy(&mut self, proxy
: String
) {
591 self.inner
.set_proxy(proxy
)
595 /// bad nonce retry count helper
598 const fn retry() -> Retry
{
603 fn tick(&mut self) -> Result
<(), Error
> {
605 bail
!("kept getting a badNonce error!");