acme_lib_load_order/acc/
mod.rs

1//
2use std::sync::Arc;
3
4use crate::api::{ApiAccount, ApiDirectory, ApiIdentifier, ApiOrder, ApiRevocation};
5use crate::cert::Certificate;
6use crate::order::{NewOrder, Order};
7use crate::persist::{Persist, PersistKey, PersistKind};
8use crate::req::req_expect_header;
9use crate::trans::Transport;
10use crate::util::{base64url, read_json};
11use crate::Result;
12
13mod akey;
14
15pub(crate) use self::akey::AcmeKey;
16
17#[derive(Clone, Debug)]
18pub(crate) struct AccountInner<P: Persist> {
19    pub persist: P,
20    pub transport: Transport,
21    pub realm: String,
22    pub api_account: ApiAccount,
23    pub api_directory: ApiDirectory,
24}
25
26/// Account with an ACME provider.
27///
28/// Accounts are created using [`Directory::account`] and consist of a contact
29/// email address and a private key for signing requests to the ACME API.
30///
31/// acme-lib uses elliptic curve P-256 for accessing the account. This
32/// does not affect which key algorithms that can be used for the
33/// issued certificates.
34///
35/// The advantage of using elliptic curve cryptography is that the signed
36/// requests against the ACME lib are kept small and that the public key
37/// can be derived from the private.
38///
39/// [`Directory::account`]: struct.Directory.html#method.account
40#[derive(Clone)]
41pub struct Account<P: Persist> {
42    inner: Arc<AccountInner<P>>,
43}
44
45impl<P: Persist> Account<P> {
46    pub(crate) fn new(
47        persist: P,
48        transport: Transport,
49        realm: &str,
50        api_account: ApiAccount,
51        api_directory: ApiDirectory,
52    ) -> Self {
53        Account {
54            inner: Arc::new(AccountInner {
55                persist,
56                transport,
57                realm: realm.to_string(),
58                api_account,
59                api_directory,
60            }),
61        }
62    }
63
64    /// Private key for this account.
65    ///
66    /// The key is an elliptic curve private key.
67    pub fn acme_private_key_pem(&self) -> String {
68        String::from_utf8(self.inner.transport.acme_key().to_pem()).expect("from_utf8")
69    }
70
71    /// Get an already issued and [downloaded] certificate.
72    ///
73    /// Every time a certificate is downloaded, the certificate and corresponding
74    /// private key are persisted. This method returns an already existing certificate
75    /// from the local storage (no API calls involved).
76    ///
77    /// This can form the basis for implemeting automatic renewal of
78    /// certificates where the [valid days left] are running low.
79    ///
80    /// [downloaded]: order/struct.CertOrder.html#method.download_and_save_cert
81    /// [valid days left]: struct.Certificate.html#method.valid_days_left
82    pub fn certificate(&self, primary_name: &str) -> Result<Option<Certificate>> {
83        // details needed for persistence
84        let realm = &self.inner.realm;
85        let persist = &self.inner.persist;
86
87        // read primary key
88        let pk_key = PersistKey::new(realm, PersistKind::PrivateKey, primary_name);
89        debug!("Read private key: {}", pk_key);
90        let private_key = persist
91            .get(&pk_key)?
92            .and_then(|s| String::from_utf8(s).ok());
93
94        // read certificate
95        let pk_crt = PersistKey::new(realm, PersistKind::Certificate, primary_name);
96        debug!("Read certificate: {}", pk_crt);
97        let certificate = persist
98            .get(&pk_crt)?
99            .and_then(|s| String::from_utf8(s).ok());
100
101        Ok(match (private_key, certificate) {
102            (Some(k), Some(c)) => Some(Certificate::new(k, c)),
103            _ => None,
104        })
105    }
106
107    /// Create a new order to issue a certificate for this account.
108    ///
109    /// Each order has a required `primary_name` (which will be set as the certificates `CN`)
110    /// and a variable number of `alt_names`.
111    ///
112    /// This library doesn't constrain the number of `alt_names`, but it is limited by the ACME
113    /// API provider. Let's Encrypt sets a max of [100 names] per certificate.
114    ///
115    /// Every call creates a new order with the ACME API provider, even when the domain
116    /// names supplied are exactly the same.
117    ///
118    /// [100 names]: https://letsencrypt.org/docs/rate-limits/
119    pub fn new_order(&self, primary_name: &str, alt_names: &[&str]) -> Result<NewOrder<P>> {
120        // construct the identifiers
121        let prim_arr = [primary_name];
122        let domains = prim_arr.iter().chain(alt_names);
123        let order = ApiOrder {
124            identifiers: domains
125                .map(|s| ApiIdentifier {
126                    _type: "dns".into(),
127                    value: s.to_string(),
128                })
129                .collect(),
130            ..Default::default()
131        };
132
133        let new_order_url = &self.inner.api_directory.newOrder;
134
135        let res = self.inner.transport.call(new_order_url, &order)?;
136        let order_url = req_expect_header(&res, "location")?;
137        let api_order: ApiOrder = read_json(res)?;
138
139        let order = Order::new(&self.inner, api_order, order_url);
140        Ok(NewOrder { order })
141    }
142
143
144    /// Load an existing order from its URL.
145    ///
146    /// This can be used to resume processing of an order that was
147    /// created earlier.
148    pub fn load_order(&self, order_url: &str) -> Result<NewOrder<P>> {
149        let res = self.inner.transport.call(order_url, "")?;
150        let api_order: ApiOrder = read_json(res)?;
151        let order = Order::new(&self.inner, api_order, order_url.to_string());
152        Ok(NewOrder { order })
153    }
154    
155    /// Revoke a certificate for the reason given.
156    ///
157    /// This calls the ACME API revoke endpoint, but does not affect the locally persisted
158    /// certs, the revoked certificate will still be available using [`certificate`].
159    ///
160    /// [`certificate`]: struct.Account.html#method.certificate
161    pub fn revoke_certificate(&self, cert: &Certificate, reason: RevocationReason) -> Result<()> {
162        // convert to base64url of the DER (which is not PEM).
163        let certificate = base64url(&cert.certificate_der());
164
165        let revoc = ApiRevocation {
166            certificate,
167            reason: reason as usize,
168        };
169
170        let url = &self.inner.api_directory.revokeCert;
171        self.inner.transport.call(url, &revoc)?;
172
173        Ok(())
174    }
175
176    /// Access the underlying JSON object for debugging.
177    pub fn api_account(&self) -> &ApiAccount {
178        &self.inner.api_account
179    }
180}
181
182/// Enumeration of reasons for revocation.
183///
184/// The reason codes are taken from [rfc5280](https://tools.ietf.org/html/rfc5280#section-5.3.1).
185pub enum RevocationReason {
186    Unspecified = 0,
187    KeyCompromise = 1,
188    CACompromise = 2,
189    AffiliationChanged = 3,
190    Superseded = 4,
191    CessationOfOperation = 5,
192    CertificateHold = 6,
193    // value 7 is not used
194    RemoveFromCRL = 8,
195    PrivilegeWithdrawn = 9,
196    AACompromise = 10,
197}
198
199#[cfg(test)]
200mod test {
201    use crate::persist::*;
202    use crate::*;
203
204    #[test]
205    fn test_create_order() -> Result<()> {
206        let server = crate::test::with_directory_server();
207        let url = DirectoryUrl::Other(&server.dir_url);
208        let persist = MemoryPersist::new();
209        let dir = Directory::from_url(persist, url)?;
210        let acc = dir.account("foo@bar.com")?;
211        let _ = acc.new_order("acmetest.example.com", &[])?;
212        Ok(())
213    }
214}