acme_micro/acc/
mod.rs

1//
2use std::sync::Arc;
3
4use crate::api::{ApiAccount, ApiDirectory, ApiIdentifier, ApiOrder, ApiRevocation};
5use crate::cert::Certificate;
6use crate::error::*;
7use crate::order::{NewOrder, Order};
8use crate::req::req_expect_header;
9use crate::trans::Transport;
10use crate::util::{base64url, read_json};
11
12mod akey;
13
14pub(crate) use self::akey::AcmeKey;
15
16#[derive(Clone, Debug)]
17pub(crate) struct AccountInner {
18    pub transport: Transport,
19    pub api_account: ApiAccount,
20    pub api_directory: ApiDirectory,
21}
22
23/// Account with an ACME provider.
24///
25/// Accounts are created using [`Directory::account`] and consist of a contact
26/// email address and a private key for signing requests to the ACME API.
27///
28/// acme-lib uses elliptic curve P-256 for accessing the account. This
29/// does not affect which key algorithms that can be used for the
30/// issued certificates.
31///
32/// The advantage of using elliptic curve cryptography is that the signed
33/// requests against the ACME lib are kept small and that the public key
34/// can be derived from the private.
35///
36/// [`Directory::account`]: struct.Directory.html#method.account
37#[derive(Clone)]
38pub struct Account {
39    inner: Arc<AccountInner>,
40}
41
42impl Account {
43    pub(crate) fn new(
44        transport: Transport,
45        api_account: ApiAccount,
46        api_directory: ApiDirectory,
47    ) -> Self {
48        Account {
49            inner: Arc::new(AccountInner {
50                transport,
51                api_account,
52                api_directory,
53            }),
54        }
55    }
56
57    /// Private key for this account.
58    ///
59    /// The key is an elliptic curve private key.
60    pub fn acme_private_key_pem(&self) -> Result<String> {
61        let pem = String::from_utf8(self.inner.transport.acme_key().to_pem()?)?;
62        Ok(pem)
63    }
64
65    /// Create a new order to issue a certificate for this account.
66    ///
67    /// Each order has a required `primary_name` (which will be set as the certificates `CN`)
68    /// and a variable number of `alt_names`.
69    ///
70    /// This library doesn't constrain the number of `alt_names`, but it is limited by the ACME
71    /// API provider. Let's Encrypt sets a max of [100 names] per certificate.
72    ///
73    /// Every call creates a new order with the ACME API provider, even when the domain
74    /// names supplied are exactly the same.
75    ///
76    /// [100 names]: https://letsencrypt.org/docs/rate-limits/
77    pub fn new_order(&self, primary_name: &str, alt_names: &[&str]) -> Result<NewOrder> {
78        // construct the identifiers
79        let prim_arr = [primary_name];
80        let domains = prim_arr.iter().chain(alt_names);
81        let order = ApiOrder {
82            identifiers: domains
83                .map(|s| ApiIdentifier {
84                    _type: "dns".into(),
85                    value: s.to_string(),
86                })
87                .collect(),
88            ..Default::default()
89        };
90
91        let new_order_url = &self.inner.api_directory.newOrder;
92
93        let res = self.inner.transport.call(new_order_url, &order)?;
94        let order_url = req_expect_header(&res, "location")?;
95        let api_order: ApiOrder = read_json(res)?;
96
97        let order = Order::new(&self.inner, api_order, order_url);
98        Ok(NewOrder { order })
99    }
100
101    /// Revoke a certificate for the reason given.
102    pub fn revoke_certificate(&self, cert: &Certificate, reason: RevocationReason) -> Result<()> {
103        // convert to base64url of the DER (which is not PEM).
104        let certificate = base64url(&cert.certificate_der()?);
105
106        let revoc = ApiRevocation {
107            certificate,
108            reason: reason as usize,
109        };
110
111        let url = &self.inner.api_directory.revokeCert;
112        self.inner.transport.call(url, &revoc)?;
113
114        Ok(())
115    }
116
117    /// Access the underlying JSON object for debugging.
118    pub fn api_account(&self) -> &ApiAccount {
119        &self.inner.api_account
120    }
121}
122
123/// Enumeration of reasons for revocation.
124///
125/// The reason codes are taken from [rfc5280](https://tools.ietf.org/html/rfc5280#section-5.3.1).
126pub enum RevocationReason {
127    Unspecified = 0,
128    KeyCompromise = 1,
129    CACompromise = 2,
130    AffiliationChanged = 3,
131    Superseded = 4,
132    CessationOfOperation = 5,
133    CertificateHold = 6,
134    // value 7 is not used
135    RemoveFromCRL = 8,
136    PrivilegeWithdrawn = 9,
137    AACompromise = 10,
138}
139
140#[cfg(test)]
141mod test {
142    use crate::*;
143
144    #[test]
145    fn test_create_order() -> Result<()> {
146        let server = crate::test::with_directory_server();
147        let url = DirectoryUrl::Other(&server.dir_url);
148        let dir = Directory::from_url(url)?;
149        let acc = dir.register_account(vec!["mailto:foo@bar.com".to_string()])?;
150        let _ = acc.new_order("acmetest.example.com", &[])?;
151        Ok(())
152    }
153}