acme_lib_load_order/
dir.rs

1//
2use std::sync::Arc;
3
4use crate::acc::AcmeKey;
5use crate::api::{ApiAccount, ApiDirectory};
6use crate::persist::{Persist, PersistKey, PersistKind};
7use crate::req::{req_expect_header, req_get, req_handle_error};
8use crate::trans::{NoncePool, Transport};
9use crate::util::read_json;
10use crate::{Account, Result};
11
12const LETSENCRYPT: &str = "https://acme-v02.api.letsencrypt.org/directory";
13const LETSENCRYPT_STAGING: &str = "https://acme-staging-v02.api.letsencrypt.org/directory";
14
15/// Enumeration of known ACME API directories.
16#[derive(Debug, Clone)]
17pub enum DirectoryUrl<'a> {
18    /// The main Let's Encrypt directory. Not appropriate for testing and dev.
19    LetsEncrypt,
20    /// The staging Let's Encrypt directory. Use for testing and dev. Doesn't issue
21    /// "valid" certificates. The root signing certificate is not supposed
22    /// to be in any trust chains.
23    LetsEncryptStaging,
24    /// Provide an arbitrary director URL to connect to.
25    Other(&'a str),
26}
27
28impl<'a> DirectoryUrl<'a> {
29    fn to_url(&self) -> &str {
30        match self {
31            DirectoryUrl::LetsEncrypt => LETSENCRYPT,
32            DirectoryUrl::LetsEncryptStaging => LETSENCRYPT_STAGING,
33            DirectoryUrl::Other(s) => s,
34        }
35    }
36}
37
38/// Entry point for accessing an ACME API.
39#[derive(Clone)]
40pub struct Directory<P: Persist> {
41    persist: P,
42    nonce_pool: Arc<NoncePool>,
43    api_directory: ApiDirectory,
44}
45
46impl<P: Persist> Directory<P> {
47    /// Create a directory over a persistence implementation and directory url.
48    pub fn from_url(persist: P, url: DirectoryUrl) -> Result<Directory<P>> {
49        let dir_url = url.to_url();
50        let res = req_handle_error(req_get(dir_url))?;
51        let api_directory: ApiDirectory = read_json(res)?;
52        let nonce_pool = Arc::new(NoncePool::new(&api_directory.newNonce));
53        Ok(Directory {
54            persist,
55            nonce_pool,
56            api_directory,
57        })
58    }
59
60    /// Access an account identified by a contact email.
61    ///
62    /// If a persisted private key exists for the contact email, it will be read
63    /// and used for further access. This way we reuse the same ACME API account.
64    ///
65    /// If one doesn't exist, it is created and the corresponding public key is
66    /// uploaded to the ACME API thus creating the account.
67    ///
68    /// Either way the `newAccount` API endpoint is called and thereby ensures the
69    /// account is active and working.
70    ///
71    /// This is the same as calling
72    /// `account_with_realm(contact_email, ["mailto: <contact_email>"]`)
73    pub fn account(&self, contact_email: &str) -> Result<Account<P>> {
74        // Contact email is the persistence realm when using this method.
75        let contact = vec![format!("mailto:{}", contact_email)];
76        self.account_with_realm(contact_email, Some(contact))
77    }
78
79    /// Access an account using a lower level method. The contact is optional
80    /// against the ACME API provider and there might be situations where you
81    /// either don't need it at all, or need it to be something else than
82    /// an email address.
83    ///
84    /// The `realm` parameter is a persistence realm, i.e. a namespace in the
85    /// persistence where all values belonging to this Account will be stored.
86    ///
87    /// If a persisted private key exists for the `realm`, it will be read
88    /// and used for further access. This way we reuse the same ACME API account.
89    ///
90    /// If one doesn't exist, it is created and the corresponding public key is
91    /// uploaded to the ACME API thus creating the account.
92    ///
93    /// Either way the `newAccount` API endpoint is called and thereby ensures the
94    /// account is active and working.
95    pub fn account_with_realm(
96        &self,
97        realm: &str,
98        contact: Option<Vec<String>>,
99    ) -> Result<Account<P>> {
100        // key in persistence for acme account private key
101        let pem_key = PersistKey::new(realm, PersistKind::AccountPrivateKey, "acme_account");
102
103        // Get the key from a saved PEM, or from creating a new
104        let mut is_new = false;
105        let pem = self.persist().get(&pem_key)?;
106        let acme_key = if let Some(pem) = pem {
107            // we got a persisted private key. read it.
108            debug!("Read persisted acme account key");
109            AcmeKey::from_pem(&pem)?
110        } else {
111            // create a new key (and new account)
112            debug!("Create new acme account key");
113            is_new = true;
114            AcmeKey::new()
115        };
116
117        // Prepare making a call to newAccount. This is fine to do both for
118        // new keys and existing. For existing the spec says to return a 200
119        // with the Location header set to the key id (kid).
120        let acc = ApiAccount {
121            contact,
122            termsOfServiceAgreed: Some(true),
123            ..Default::default()
124        };
125
126        let mut transport = Transport::new(&self.nonce_pool, acme_key);
127        let res = transport.call_jwk(&self.api_directory.newAccount, &acc)?;
128        let kid = req_expect_header(&res, "location")?;
129        debug!("Key id is: {}", kid);
130        let api_account: ApiAccount = read_json(res)?;
131
132        // fill in the server returned key id
133        transport.set_key_id(kid);
134
135        // If we did create a new key, save it back to the persistence.
136        if is_new {
137            debug!("Persist acme account key");
138            let pem = transport.acme_key().to_pem();
139            self.persist().put(&pem_key, &pem)?;
140        }
141
142        // The finished account
143        Ok(Account::new(
144            self.persist.clone(),
145            transport,
146            realm,
147            api_account,
148            self.api_directory.clone(),
149        ))
150    }
151
152    /// Access the underlying JSON object for debugging.
153    pub fn api_directory(&self) -> &ApiDirectory {
154        &self.api_directory
155    }
156
157    pub(crate) fn persist(&self) -> &P {
158        &self.persist
159    }
160}
161
162#[cfg(test)]
163mod test {
164    use super::*;
165    use crate::persist::*;
166    #[test]
167    fn test_create_directory() -> Result<()> {
168        let server = crate::test::with_directory_server();
169        let url = DirectoryUrl::Other(&server.dir_url);
170        let persist = MemoryPersist::new();
171        let _ = Directory::from_url(persist, url)?;
172        Ok(())
173    }
174
175    #[test]
176    fn test_create_acount() -> Result<()> {
177        let server = crate::test::with_directory_server();
178        let url = DirectoryUrl::Other(&server.dir_url);
179        let persist = MemoryPersist::new();
180        let dir = Directory::from_url(persist, url)?;
181        let _ = dir.account("foo@bar.com")?;
182        Ok(())
183    }
184
185    #[test]
186    fn test_persisted_acount() -> Result<()> {
187        let server = crate::test::with_directory_server();
188        let url = DirectoryUrl::Other(&server.dir_url);
189        let persist = MemoryPersist::new();
190        let dir = Directory::from_url(persist, url)?;
191        let acc1 = dir.account("foo@bar.com")?;
192        let acc2 = dir.account("foo@bar.com")?;
193        let acc3 = dir.account("karlfoo@bar.com")?;
194        assert_eq!(acc1.acme_private_key_pem(), acc2.acme_private_key_pem());
195        assert!(acc1.acme_private_key_pem() != acc3.acme_private_key_pem());
196        Ok(())
197    }
198
199    // #[test]
200    // fn test_the_whole_hog() -> Result<()> {
201    //     std::env::set_var("RUST_LOG", "acme_lib=trace");
202    //     let _ = env_logger::try_init();
203
204    //     use crate::cert::create_p384_key;
205
206    //     let url = DirectoryUrl::LetsEncryptStaging;
207    //     let persist = FilePersist::new(".");
208    //     let dir = Directory::from_url(persist, url)?;
209    //     let acc = dir.account("foo@bar.com")?;
210
211    //     let mut ord = acc.new_order("myspecialsite.com", &[])?;
212
213    //     let ord = loop {
214    //         if let Some(ord) = ord.confirm_validations() {
215    //             break ord;
216    //         }
217
218    //         let auths = ord.authorizations()?;
219    //         let chall = auths[0].dns_challenge();
220
221    //         info!("Proof: {}", chall.dns_proof());
222
223    //         use std::thread;
224    //         use std::time::Duration;
225    //         thread::sleep(Duration::from_millis(60_000));
226
227    //         chall.validate(5000)?;
228
229    //         ord.refresh()?;
230    //     };
231
232    //     let (pkey_pri, pkey_pub) = create_p384_key();
233
234    //     let ord = ord.finalize_pkey(pkey_pri, pkey_pub, 5000)?;
235
236    //     let cert = ord.download_and_save_cert()?;
237    //     println!(
238    //         "{}{}{}",
239    //         cert.private_key(),
240    //         cert.certificate(),
241    //         cert.valid_days_left()
242    //     );
243    //     Ok(())
244    // }
245}