1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
//
use std::sync::Arc;

use crate::api::{ApiAccount, ApiDirectory, ApiIdentifier, ApiOrder, ApiRevocation};
use crate::cert::Certificate;
use crate::order::{NewOrder, Order};
use crate::persist::{Persist, PersistKey, PersistKind};
use crate::req::req_expect_header;
use crate::trans::Transport;
use crate::util::{base64url, read_json};
use crate::Result;

mod akey;

pub(crate) use self::akey::AcmeKey;

#[derive(Clone)]
pub(crate) struct AccountInner<P: Persist> {
    pub persist: P,
    pub transport: Transport,
    pub contact_email: String,
    pub api_account: ApiAccount,
    pub api_directory: ApiDirectory,
}

/// Account with an ACME provider.
///
/// Accounts are created using [`Directory::account`] and consist of a contact
/// email address and a private key for signing requests to the ACME API.
///
/// acme-lib uses elliptic curve P-256 for accessing the account. This
/// does not affect which key algorithms that can be used for the
/// issued certificates.
///
/// The advantage of using elliptic curve cryptography is that the signed
/// requests against the ACME lib are kept small and that the public key
/// can be derived from the private.
///
/// [`Directory::account`]: struct.Directory.html#method.account
#[derive(Clone)]
pub struct Account<P: Persist> {
    inner: Arc<AccountInner<P>>,
}

impl<P: Persist> Account<P> {
    pub(crate) fn new(
        persist: P,
        transport: Transport,
        contact_email: &str,
        api_account: ApiAccount,
        api_directory: ApiDirectory,
    ) -> Self {
        Account {
            inner: Arc::new(AccountInner {
                persist,
                transport,
                contact_email: contact_email.into(),
                api_account,
                api_directory,
            }),
        }
    }

    /// Private key for this account.
    ///
    /// The key is an elliptic curve private key.
    pub fn acme_private_key_pem(&self) -> String {
        String::from_utf8(self.inner.transport.acme_key().to_pem()).expect("from_utf8")
    }

    /// Contact email for this account.
    pub fn contact_email(&self) -> &str {
        &self.inner.contact_email
    }

    /// Get an already issued and [downloaded] certificate.
    ///
    /// Every time a certificate is downloaded, the certificate and corresponding
    /// private key are persisted. This method returns an already existing certificate
    /// from the local storage (no API calls involved).
    ///
    /// This can form the basis for implemeting automatic renewal of
    /// certificates where the [valid days left] are running low.
    ///
    /// [downloaded]: order/struct.CertOrder.html#method.download_and_save_cert
    /// [valid days left]: struct.Certificate.html#method.valid_days_left
    pub fn certificate(&self, primary_name: &str) -> Result<Option<Certificate>> {
        // details needed for persistence
        let realm = &self.inner.contact_email;
        let persist = &self.inner.persist;

        // read primary key
        let pk_key = PersistKey::new(realm, PersistKind::PrivateKey, primary_name);
        debug!("Read private key: {}", pk_key);
        let private_key = persist
            .get(&pk_key)?
            .and_then(|s| String::from_utf8(s).ok());

        // read certificate
        let pk_crt = PersistKey::new(realm, PersistKind::Certificate, primary_name);
        debug!("Read certificate: {}", pk_crt);
        let certificate = persist
            .get(&pk_crt)?
            .and_then(|s| String::from_utf8(s).ok());

        Ok(match (private_key, certificate) {
            (Some(k), Some(c)) => Some(Certificate::new(k, c)),
            _ => None,
        })
    }

    /// Create a new order to issue a certificate for this account.
    ///
    /// Each order has a required `primary_name` (which will be set as the certificates `CN`)
    /// and a variable number of `alt_names`.
    ///
    /// This library doesn't constrain the number of `alt_names`, but it is limited by the ACME
    /// API provider. Let's Encrypt sets a max of [100 names] per certificate.
    ///
    /// Every call creates a new order with the ACME API provider, even when the domain
    /// names supplied are exactly the same.
    ///
    /// [100 names]: https://letsencrypt.org/docs/rate-limits/
    pub fn new_order(&self, primary_name: &str, alt_names: &[&str]) -> Result<NewOrder<P>> {
        // construct the identifiers
        let prim_arr = [primary_name];
        let domains = prim_arr.iter().chain(alt_names);
        let order = ApiOrder {
            identifiers: domains
                .map(|s| ApiIdentifier {
                    _type: "dns".into(),
                    value: s.to_string(),
                })
                .collect(),
            ..Default::default()
        };

        let new_order_url = &self.inner.api_directory.newOrder;

        let res = self.inner.transport.call(new_order_url, &order)?;
        let order_url = req_expect_header(&res, "location")?;
        let api_order: ApiOrder = read_json(res)?;

        let order = Order::new(&self.inner, api_order, order_url);
        Ok(NewOrder { order })
    }

    /// Revoke a certificate for the reason given.
    ///
    /// This calls the ACME API revoke endpoint, but does not affect the locally persisted
    /// certs, the revoked certificate will still be available using [`certificate`].
    ///
    /// [`certificate`]: struct.Account.html#method.certificate
    pub fn revoke_certificate(&self, cert: &Certificate, reason: RevocationReason) -> Result<()> {
        // convert to base64url of the DER (which is not PEM).
        let certificate = base64url(&cert.certificate_der());

        let revoc = ApiRevocation {
            certificate,
            reason: reason as usize,
        };

        let url = &self.inner.api_directory.revokeCert;
        self.inner.transport.call(url, &revoc)?;

        Ok(())
    }

    /// Access the underlying JSON object for debugging.
    pub fn api_account(&self) -> &ApiAccount {
        &self.inner.api_account
    }
}

/// Enumeration of reasons for revocation.
///
/// The reason codes are taken from [rfc5280](https://tools.ietf.org/html/rfc5280#section-5.3.1).
pub enum RevocationReason {
    Unspecified = 0,
    KeyCompromise = 1,
    CACompromise = 2,
    AffiliationChanged = 3,
    Superseded = 4,
    CessationOfOperation = 5,
    CertificateHold = 6,
    // value 7 is not used
    RemoveFromCRL = 8,
    PrivilegeWithdrawn = 9,
    AACompromise = 10,
}

#[cfg(test)]
mod test {
    use crate::persist::*;
    use crate::*;

    #[test]
    fn test_create_order() -> Result<()> {
        let server = crate::test::with_directory_server();
        let url = DirectoryUrl::Other(&server.dir_url);
        let persist = MemoryPersist::new();
        let dir = Directory::from_url(persist, url)?;
        let acc = dir.account("foo@bar.com")?;
        let _ = acc.new_order("acmetest.example.com", &[])?;
        Ok(())
    }
}