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}