Skip to main content

acmer/
store.rs

1use std::{
2    collections::{HashMap, HashSet},
3    sync::Arc,
4    time::SystemTime,
5};
6
7use async_trait::async_trait;
8use dashmap::DashMap;
9pub use papaleguas::OrderStatus;
10use rustls_pki_types::{CertificateDer, PrivateKeyDer};
11use serde::{Deserialize, Serialize};
12use time::OffsetDateTime;
13use tokio::{
14    io,
15    sync::{OwnedRwLockWriteGuard, RwLock},
16    try_join,
17};
18use tracing::trace;
19use x509_cert::{der::Decode, Certificate as X509Certificate};
20
21pub use boxed::*;
22#[cfg(feature = "dynamodb-store")]
23pub use dynamodb::*;
24pub use fs::*;
25
26mod boxed;
27#[cfg(feature = "dynamodb-store")]
28mod dynamodb;
29mod fs;
30
31pub type PrivateKey = PrivateKeyDer<'static>;
32pub type Certificate = CertificateDer<'static>;
33
34#[async_trait]
35pub trait CertStore: Send + Sync {
36    async fn get_cert(&self, domain: &str) -> io::Result<Option<(PrivateKey, Vec<Certificate>)>>;
37    async fn put_cert(
38        &self,
39        domain: &str,
40        key: PrivateKey,
41        cert: Vec<Certificate>,
42    ) -> io::Result<()>;
43}
44
45#[derive(Debug, Clone, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "camelCase")]
47pub struct Order {
48    pub url: String,
49    pub status: OrderStatus,
50    #[serde(default, with = "time::serde::iso8601::option")]
51    pub expires: Option<OffsetDateTime>,
52}
53
54impl std::hash::Hash for Order {
55    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
56        self.url.hash(state)
57    }
58}
59
60impl PartialEq for Order {
61    fn eq(&self, other: &Self) -> bool {
62        self.url.eq(&other.url)
63    }
64}
65
66impl From<&papaleguas::Order> for Order {
67    fn from(value: &papaleguas::Order) -> Self {
68        Self {
69            url: value.url().to_owned(),
70            status: *value.status(),
71            expires: value.expires(),
72        }
73    }
74}
75
76#[async_trait]
77pub trait OrderStore: Send + Sync {
78    async fn list_orders(&self, domain: &str) -> io::Result<HashSet<Order>>;
79    async fn upsert_order(&self, domain: &str, order: Order) -> io::Result<()>;
80    async fn remove_order(&self, domain: &str, order_url: &str) -> io::Result<()>;
81}
82
83#[async_trait]
84pub trait AccountStore: Send + Sync {
85    async fn get_account(&self, directory: &str) -> io::Result<Option<PrivateKey>>;
86    async fn put_account(&self, directory: &str, key: PrivateKey) -> io::Result<()>;
87}
88
89#[derive(Debug, Clone, Default, Serialize, Deserialize)]
90pub struct AuthChallenge {
91    challenges: Vec<ChallengeKind>,
92}
93
94#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
95enum ChallengeKind {
96    TlsAlpn(String),
97    Http01 { token: String, challenge: String },
98}
99
100impl AuthChallenge {
101    pub fn new() -> Self {
102        Default::default()
103    }
104
105    pub fn with_http01(mut self, token: impl Into<String>, challenge: impl Into<String>) -> Self {
106        self.add_http01(token, challenge);
107        self
108    }
109
110    pub fn with_tls_alpn01(mut self, challenge: impl Into<String>) -> Self {
111        self.add_tls_alpn01(challenge);
112        self
113    }
114
115    pub fn add_http01(&mut self, token: impl Into<String>, challenge: impl Into<String>) {
116        self.challenges.push(ChallengeKind::Http01 {
117            token: token.into(),
118            challenge: challenge.into(),
119        });
120    }
121
122    pub fn add_tls_alpn01(&mut self, challenge: impl Into<String>) {
123        self.challenges
124            .push(ChallengeKind::TlsAlpn(challenge.into()));
125    }
126
127    pub fn http01_challenge(&self) -> Option<(&str, &str)> {
128        self.challenges
129            .iter()
130            .find_map(|challenge| match challenge {
131                ChallengeKind::Http01 { token, challenge } => {
132                    Some((token.as_str(), challenge.as_str()))
133                }
134                _ => None,
135            })
136    }
137
138    pub fn tls_alpn01_challenge(&self) -> Option<&str> {
139        self.challenges
140            .iter()
141            .find_map(|challenge| match challenge {
142                ChallengeKind::TlsAlpn(challenge) => Some(challenge.as_str()),
143                _ => None,
144            })
145    }
146
147    pub fn is_empty(&self) -> bool {
148        self.challenges.is_empty()
149    }
150}
151
152#[async_trait]
153pub trait AuthChallengeStore: Send + Sync {
154    type LockGuard: AuthChallengeDomainLock + Send;
155    async fn get_challenge(&self, domain: &str) -> io::Result<Option<AuthChallenge>>;
156    async fn lock(&self, domain: &str) -> io::Result<Self::LockGuard>;
157    async fn unlock(&self, domain: &str) -> io::Result<()>;
158}
159
160#[async_trait]
161pub trait AuthChallengeDomainLock: Send + Sync {
162    async fn put_challenge(&mut self, challenge: AuthChallenge) -> io::Result<()>;
163}
164
165#[async_trait]
166impl CertStore for RwLock<HashMap<String, (PrivateKey, Vec<Certificate>)>> {
167    async fn get_cert(&self, domain: &str) -> io::Result<Option<(PrivateKey, Vec<Certificate>)>> {
168        Ok(self
169            .read()
170            .await
171            .get(domain)
172            .map(|(key, certs)| (key.clone_key(), certs.clone())))
173    }
174
175    async fn put_cert(
176        &self,
177        domain: &str,
178        key: PrivateKey,
179        cert: Vec<Certificate>,
180    ) -> io::Result<()> {
181        self.write().await.insert(domain.to_owned(), (key, cert));
182        Ok(())
183    }
184}
185
186#[async_trait]
187impl CertStore for DashMap<String, (PrivateKey, Vec<Certificate>)> {
188    async fn get_cert(&self, domain: &str) -> io::Result<Option<(PrivateKey, Vec<Certificate>)>> {
189        Ok(self.get(domain).map(|item| {
190            let (key, certs) = item.value();
191            (key.clone_key(), certs.clone())
192        }))
193    }
194
195    async fn put_cert(
196        &self,
197        domain: &str,
198        key: PrivateKey,
199        cert: Vec<Certificate>,
200    ) -> io::Result<()> {
201        self.insert(domain.to_owned(), (key, cert));
202        Ok(())
203    }
204}
205
206#[derive(Debug, Default)]
207pub struct MemoryCertStore(DashMap<String, (PrivateKey, Vec<Certificate>)>);
208
209#[async_trait]
210impl CertStore for MemoryCertStore {
211    async fn get_cert(&self, domain: &str) -> io::Result<Option<(PrivateKey, Vec<Certificate>)>> {
212        self.0.get_cert(domain).await
213    }
214
215    async fn put_cert(
216        &self,
217        domain: &str,
218        key: PrivateKey,
219        cert: Vec<Certificate>,
220    ) -> io::Result<()> {
221        self.0.put_cert(domain, key, cert).await
222    }
223}
224
225#[async_trait]
226impl OrderStore for DashMap<String, HashSet<Order>> {
227    async fn list_orders(&self, domain: &str) -> io::Result<HashSet<Order>> {
228        let orders = self
229            .get(domain)
230            .map(|item| item.value().clone())
231            .unwrap_or_default();
232        Ok(orders)
233    }
234
235    async fn upsert_order(&self, domain: &str, order: Order) -> io::Result<()> {
236        let mut orders = self.entry(domain.to_string()).or_default();
237        orders.replace(order);
238        Ok(())
239    }
240
241    async fn remove_order(&self, domain: &str, order_url: &str) -> io::Result<()> {
242        self.entry(domain.to_string())
243            .and_modify(|orders| {
244                orders.remove(&Order {
245                    url: order_url.to_string(),
246                    status: OrderStatus::Ready,
247                    expires: None,
248                });
249            })
250            .or_default();
251        Ok(())
252    }
253}
254
255#[derive(Debug, Default)]
256pub struct MemoryOrderStore(DashMap<String, HashSet<Order>>);
257
258#[async_trait]
259impl OrderStore for MemoryOrderStore {
260    async fn list_orders(&self, domain: &str) -> io::Result<HashSet<Order>> {
261        self.0.list_orders(domain).await
262    }
263
264    async fn upsert_order(&self, domain: &str, order: Order) -> io::Result<()> {
265        self.0.upsert_order(domain, order).await
266    }
267
268    async fn remove_order(&self, domain: &str, order_url: &str) -> io::Result<()> {
269        self.0.remove_order(domain, order_url).await
270    }
271}
272
273#[async_trait]
274impl AccountStore for DashMap<String, PrivateKey> {
275    async fn get_account(&self, directory: &str) -> io::Result<Option<PrivateKey>> {
276        Ok(self.get(directory).map(|item| item.clone_key()))
277    }
278
279    async fn put_account(&self, directory: &str, key: PrivateKey) -> io::Result<()> {
280        self.insert(directory.to_owned(), key);
281        Ok(())
282    }
283}
284
285#[derive(Debug, Default, Clone)]
286pub struct MemoryAccountStore(Arc<DashMap<String, PrivateKey>>);
287
288#[async_trait]
289impl AccountStore for MemoryAccountStore {
290    async fn get_account(&self, directory: &str) -> io::Result<Option<PrivateKey>> {
291        self.0.get_account(directory).await
292    }
293
294    async fn put_account(&self, directory: &str, key: PrivateKey) -> io::Result<()> {
295        self.0.put_account(directory, key).await
296    }
297}
298
299#[derive(Debug, Default)]
300pub struct MemoryAuthChallengeStore {
301    store: DashMap<String, Arc<RwLock<AuthChallenge>>>,
302}
303
304pub struct MemoryAuthChallengeStoreGuard(OwnedRwLockWriteGuard<AuthChallenge>);
305
306#[async_trait]
307impl AuthChallengeStore for MemoryAuthChallengeStore {
308    type LockGuard = MemoryAuthChallengeStoreGuard;
309
310    async fn get_challenge(&self, domain: &str) -> io::Result<Option<AuthChallenge>> {
311        match self.store.get(domain) {
312            Some(entry) => Ok(Some(entry.value().clone().read().await.clone())),
313            None => Ok(None),
314        }
315    }
316
317    async fn lock(&self, domain: &str) -> io::Result<Self::LockGuard> {
318        self.store
319            .entry(domain.to_owned())
320            .or_default()
321            .clone()
322            .try_write_owned()
323            .map(MemoryAuthChallengeStoreGuard)
324            .map_err(|_| io::Error::other("could not arquire lock"))
325    }
326
327    async fn unlock(&self, domain: &str) -> io::Result<()> {
328        self.store.remove(domain);
329        Ok(())
330    }
331}
332
333#[async_trait]
334impl AuthChallengeDomainLock for MemoryAuthChallengeStoreGuard {
335    async fn put_challenge(&mut self, challenge: AuthChallenge) -> io::Result<()> {
336        *self.0 = challenge;
337        Ok(())
338    }
339}
340
341pub struct CachedCertStore<S> {
342    store: S,
343    cache: MemoryCertStore,
344}
345
346impl<S: CertStore> CachedCertStore<S> {
347    pub fn new(store: S) -> Self {
348        CachedCertStore {
349            store,
350            cache: MemoryCertStore::default(),
351        }
352    }
353}
354
355pub trait CachedCertStoreExt {
356    fn cached(self) -> CachedCertStore<Self>
357    where
358        Self: Sized;
359}
360
361impl<C: CertStore + 'static> CachedCertStoreExt for C {
362    fn cached(self) -> CachedCertStore<Self> {
363        CachedCertStore::new(self)
364    }
365}
366
367#[async_trait]
368impl<S: CertStore> CertStore for CachedCertStore<S> {
369    async fn get_cert(&self, domain: &str) -> io::Result<Option<(PrivateKey, Vec<Certificate>)>> {
370        if let Some(cached) = self.cache.get_cert(domain).await? {
371            return Ok(Some(cached));
372        }
373
374        if let Some((key, cert)) = self.store.get_cert(domain).await? {
375            trace!(domain, "cert not cached, caching now");
376            self.cache
377                .put_cert(domain, key.clone_key(), cert.clone())
378                .await?;
379            return Ok(Some((key, cert)));
380        }
381
382        Ok(None)
383    }
384
385    async fn put_cert(
386        &self,
387        domain: &str,
388        key: PrivateKey,
389        cert: Vec<Certificate>,
390    ) -> io::Result<()> {
391        trace!(domain, "caching cert");
392        try_join!(
393            self.store.put_cert(domain, key.clone_key(), cert.clone()),
394            self.cache.put_cert(domain, key, cert),
395        )?;
396        Ok(())
397    }
398}
399
400pub struct CertExpirationTimeStore<S> {
401    store: S,
402}
403
404fn cert_validity(cert: &[Certificate]) -> Option<SystemTime> {
405    cert.iter()
406        .filter_map(|cert| X509Certificate::from_der(cert).ok())
407        .map(|cert| cert.tbs_certificate.validity)
408        .map(|val| val.not_after.to_system_time())
409        .min()
410}
411
412impl<S: CertStore> CertExpirationTimeStore<S> {
413    pub fn new(store: S) -> Self {
414        CertExpirationTimeStore { store }
415    }
416}
417
418pub trait CertExpirationTimeStoreExt {
419    fn with_validity_check(self) -> CertExpirationTimeStore<Self>
420    where
421        Self: Sized;
422}
423
424impl<C: CertStore + 'static> CertExpirationTimeStoreExt for C {
425    fn with_validity_check(self) -> CertExpirationTimeStore<Self> {
426        CertExpirationTimeStore::new(self)
427    }
428}
429
430#[async_trait]
431impl<S: CertStore> CertStore for CertExpirationTimeStore<S> {
432    async fn get_cert(&self, domain: &str) -> io::Result<Option<(PrivateKey, Vec<Certificate>)>> {
433        match self.store.get_cert(domain).await? {
434            Some(cert) => match cert_validity(&cert.1) {
435                Some(validity) if validity < SystemTime::now() => {
436                    let valid_until = validity
437                        .duration_since(SystemTime::UNIX_EPOCH)
438                        .unwrap_or_default()
439                        .as_secs();
440                    trace!(domain, until = valid_until, "cert is expired");
441                    Ok(None)
442                }
443                Some(_validity) => Ok(Some(cert)),
444                None => Ok(None),
445            },
446            None => Ok(None),
447        }
448    }
449
450    async fn put_cert(
451        &self,
452        domain: &str,
453        key: PrivateKey,
454        cert: Vec<Certificate>,
455    ) -> io::Result<()> {
456        self.store.put_cert(domain, key, cert).await
457    }
458}
459
460pub struct SingleAccountStore(PrivateKey);
461
462impl SingleAccountStore {
463    pub fn new(key: PrivateKey) -> Self {
464        SingleAccountStore(key)
465    }
466}
467
468#[async_trait]
469impl AccountStore for SingleAccountStore {
470    async fn get_account(&self, _directory: &str) -> io::Result<Option<PrivateKey>> {
471        Ok(Some(self.0.clone_key()))
472    }
473
474    async fn put_account(&self, _directory: &str, _key: PrivateKey) -> io::Result<()> {
475        Ok(())
476    }
477}
478
479#[cfg(test)]
480mod test {
481    use indoc::indoc;
482    use papaleguas::OrderStatus;
483    use rustls_pki_types::pem::{PemObject, SectionKind};
484
485    use crate::store::{
486        AuthChallenge, AuthChallengeDomainLock, AuthChallengeStore, CertStore,
487        MemoryAuthChallengeStore, MemoryCertStore, MemoryOrderStore, OrderStore,
488    };
489
490    use super::{Certificate, Order, PrivateKey};
491
492    #[tokio::test]
493    async fn test_memory_store() {
494        let pkey = indoc! {"
495            -----BEGIN PRIVATE KEY-----
496            MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCq1WvODxLHgRNw
497            Fq7rHh9gCfCEtbN7iE2W6arQ+zYPVWiNQrKNyBqe9n2Ao/77EBnhKzJ3YrVBesGs
498            b+DE/mMXIR/2skchNTX314zaZ13fIn/QnQBtsnh3uzwfk9dFe2Z2v9WSWzumPXoP
499            UyEVt8OShW3kfjRM7WNu8IDubU1SiskRUym86fJCqIEPIwf0EkXN9Lt7fU+00X2A
500            A3Bay3uepg0uaPDmdiWwnTSXYaY4JxIVJ6V1ntzjpuRHaVEfXzCpcaWLBpJbr+uT
501            Le2RBfa6Pa7QjlY6moYAwaDfoF0Kk8U4tpV5X4Fx4wWNhPqUg8Y6NY3WJYXlB6fu
502            uDn+DPFrAgMBAAECggEASjJHkEebsGqvNo+jiRqcJeorPHhua8jXaiQyvHFfGWnO
503            7wt44Xt3lHMaLzULGZ/0nYdVc+S7NKVMWMh+pxCVmQYaC9uCaTnjJrHHy1P5wWAK
504            g2CtPve0usvnYQ+k/9iIuCq5Z8eYMKuix+UjCXu2xXyOLh9iN8ci2Jw8Y1G1s5M9
505            vk5MW4lvtb/WTAh6jTRXHMdx1RHjY8nGtf+eYu52uYm0ZWMh+H7zGzApCX1mpPKr
506            lMwwzGLIUcrBZ1Q98yRsdnOr5ErzWoRH44k0+CfmpWsnoWtWUbWM4WUeQDEywx6k
507            8aAkuVQRKvKem3ifoPG8mAjij6sfV/v19ffusn/KAQKBgQDG5z8JTUGTxYar4H/D
508            Gi1bMI9atzDdbsE499rWpPZ5BuwtSDmMACS5lWu8pT/YSELNOu5PtKK6H41ZADLW
509            kGItzCOcPQTOAAdnD5T29+jVG3hnfQCIBEksa97uWGamX00P21qUAeRStWaKf//O
510            dL6h9W7zP04tYah/zwQ5n4EcMQKBgQDb3234Di8/RQPLAJm2VoXCT/1+cRcgfKCm
511            YvmmNOzPlGmYrSHx9khlZUXdTy2aZj0NGHaWJPbE5sCVnsw7dqXPibJ1TYN/PIHJ
512            X1MYQjnHRkFZDpk/fSd8xl6ZcRHTjehhd8qbyZHTFIUHX59oC3e+uwNg862fkoYH
513            TAsp3OesWwKBgQDDKtDNncLE7sKwD/8NP7hVjBZ92tbV0AFElt9iUkeOhd5kqEPf
514            PZzLhPRMDJHS9USnADYqe4JYwvD87Zb0toO/kFk4yx7Vy214EPAITUVnJidE1IEa
515            9amfLtF2acN/aG/DKWd9Z0XUai6No/8rY55SaPNPN0TMftDJaCYrLHmRYQKBgFX9
516            kQWljobhF/Wp23P7bL6tCAgOdKwI8c+BAAAnzMH2WkIS3CbEWlYFgIhoMf6jo5be
517            jWp1NGmXkZQykc9jvL9pK/lCgn4djOjTtizTocM0z9PjqL2y1eGvt0mtdfpWEp8j
518            +YJqF/UEnm5e0HohmghnHZAqXSn+ZRqve+I4egbnAoGBALeuy9MMaLQEL4HzGyvy
519            C2U5FYAohdbw05WSfuvE8weluvND2DbQvNXq/0MAt3D0AviGTR9k0zpO+OS/nLnv
520            nuRgPCQl8N7RLKQqKf/grE9LAlZGj8pajn7ARhutjVs9z7CYQU8zthyUIvrqTLqQ
521            b41I4V1EVPutE18LGpgWFRfJ
522            -----END PRIVATE KEY-----
523        "};
524
525        let cert = indoc! {"
526            -----BEGIN CERTIFICATE-----
527            MIIDazCCAlOgAwIBAgIUVE8Tzvqz/Sd9VdOf94+FygbEMeIwDQYJKoZIhvcNAQEL
528            BQAwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
529            GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNTAzMDkxMzUyMjFaFw0yNjAz
530            MDkxMzUyMjFaMEUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
531            HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
532            AQUAA4IBDwAwggEKAoIBAQCq1WvODxLHgRNwFq7rHh9gCfCEtbN7iE2W6arQ+zYP
533            VWiNQrKNyBqe9n2Ao/77EBnhKzJ3YrVBesGsb+DE/mMXIR/2skchNTX314zaZ13f
534            In/QnQBtsnh3uzwfk9dFe2Z2v9WSWzumPXoPUyEVt8OShW3kfjRM7WNu8IDubU1S
535            iskRUym86fJCqIEPIwf0EkXN9Lt7fU+00X2AA3Bay3uepg0uaPDmdiWwnTSXYaY4
536            JxIVJ6V1ntzjpuRHaVEfXzCpcaWLBpJbr+uTLe2RBfa6Pa7QjlY6moYAwaDfoF0K
537            k8U4tpV5X4Fx4wWNhPqUg8Y6NY3WJYXlB6fuuDn+DPFrAgMBAAGjUzBRMB0GA1Ud
538            DgQWBBSXhnoVxEQENyRiCooUeIov7R7yLDAfBgNVHSMEGDAWgBSXhnoVxEQENyRi
539            CooUeIov7R7yLDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCB
540            4oNLXCd6gP8MlOyaYA9NZEfihNOZ/lg24UAtTs92btWYpsERqIm3cuRQ/mhpUnYR
541            rr4yzIHY3LzG2pK1LjbEIStRjCsPb/fCLcxx9tffxweiwpY+AxjdO4R/v9bFjxk4
542            sfb8h0ls7idqJOzU43PfTbHLaiKaPITw3TBNi5tn88bGag4iWIUdFTXbInL603Pz
543            R/g27O0Q3ohsA07C0i+GZbdtR1mghSlXj3m7bnAVDyCac670AE33c5dYKiyudRXB
544            17dFJhag9cNIXgCIaoEGMmqByuZVCbZshJb1ac3sP3bk7LR35TPm0DmL4ReiycJg
545            0W3rtqDKWRuaPAS8WMZw
546            -----END CERTIFICATE-----
547        "};
548
549        let store = MemoryCertStore::default();
550
551        let pkey = PrivateKey::from_pem(SectionKind::PrivateKey, pkey.as_bytes().to_vec()).unwrap();
552        let cert = vec![Certificate::from_pem_slice(cert.as_bytes()).unwrap()];
553
554        assert!(store.get_cert("lol.wut").await.unwrap().is_none());
555
556        store
557            .put_cert("lol.wut", pkey.clone_key(), cert.clone())
558            .await
559            .unwrap();
560
561        let (stored_key, stored_cert) = store.get_cert("lol.wut").await.unwrap().unwrap();
562        assert_eq!(stored_key, pkey);
563        assert_eq!(stored_cert, cert);
564    }
565
566    #[tokio::test]
567    async fn test_order_memory_store() {
568        let store = MemoryOrderStore::default();
569
570        let order1 = Order {
571            url: "http://order/1".to_string(),
572            status: OrderStatus::Ready,
573            expires: None,
574        };
575
576        let order2 = Order {
577            url: "http://order/2".to_string(),
578            status: OrderStatus::Invalid,
579            expires: None,
580        };
581
582        let order3 = Order {
583            url: "http://order/3".to_string(),
584            status: OrderStatus::Invalid,
585            expires: None,
586        };
587
588        store.upsert_order("lol.com", order1.clone()).await.unwrap();
589        store.upsert_order("lol.com", order2.clone()).await.unwrap();
590        store.upsert_order("wut.com", order3.clone()).await.unwrap();
591
592        let orders = store.list_orders("lol.com").await.unwrap();
593        assert!(orders.contains(&order1));
594        assert!(orders.contains(&order2));
595        assert!(!orders.contains(&order3));
596
597        let orders = store.list_orders("wut.com").await.unwrap();
598        assert!(orders.contains(&order3));
599
600        store.remove_order("lol.com", &order1.url).await.unwrap();
601        let orders = store.list_orders("lol.com").await.unwrap();
602        assert!(!orders.contains(&order1));
603        assert!(orders.contains(&order2));
604
605        let orders = store.list_orders("wut.com").await.unwrap();
606        assert!(orders.contains(&order3));
607        store
608            .upsert_order(
609                "wut.com",
610                Order {
611                    status: OrderStatus::Valid,
612                    ..order3.clone()
613                },
614            )
615            .await
616            .unwrap();
617        let orders = store.list_orders("wut.com").await.unwrap();
618        assert_eq!(orders.get(&order3).unwrap().status, OrderStatus::Valid);
619    }
620
621    #[tokio::test]
622    async fn test_auth_store_lock() {
623        let store = MemoryAuthChallengeStore::default();
624
625        let mut lock1 = store.lock("lol.wut").await.unwrap();
626        assert!(store.lock("lol.wut").await.is_err());
627
628        let mut lock2 = store.lock("wtf.wut").await.unwrap();
629        assert!(store.lock("wtf.wut").await.is_err());
630
631        lock1
632            .put_challenge(AuthChallenge::new().with_tls_alpn01("1"))
633            .await
634            .unwrap();
635        lock2
636            .put_challenge(
637                AuthChallenge::new()
638                    .with_http01("token", "chall")
639                    .with_tls_alpn01("tls"),
640            )
641            .await
642            .unwrap();
643
644        drop(lock1);
645        assert_eq!(
646            store
647                .get_challenge("lol.wut")
648                .await
649                .unwrap()
650                .unwrap()
651                .tls_alpn01_challenge(),
652            Some("1")
653        );
654
655        drop(lock2);
656        assert_eq!(
657            store
658                .get_challenge("wtf.wut")
659                .await
660                .unwrap()
661                .unwrap()
662                .http01_challenge(),
663            Some(("token", "chall"))
664        );
665        assert_eq!(
666            store
667                .get_challenge("wtf.wut")
668                .await
669                .unwrap()
670                .unwrap()
671                .tls_alpn01_challenge(),
672            Some("tls")
673        );
674
675        assert!(store.get_challenge("other.wut").await.unwrap().is_none());
676    }
677
678    #[tokio::test]
679    async fn test_auth_store_unlock() {
680        let store = MemoryAuthChallengeStore::default();
681
682        let _lock = store.lock("lol.wut").await.unwrap();
683        store.unlock("lol.wut").await.unwrap();
684        let _lock = store.lock("lol.wut").await.unwrap();
685    }
686}