1use std::{collections::HashSet, iter, sync::Arc};
2
3use base64::prelude::*;
4use eyre::eyre;
5use zeroize::Zeroizing;
6
7use crate::{
8 api,
9 cert::Certificate,
10 order::{NewOrder, Order},
11 req::req_expect_header,
12 trans::Transport,
13};
14
15mod acme_key;
16
17pub(crate) use self::acme_key::AcmeKey;
18
19#[derive(Debug, Clone)]
20pub(crate) struct AccountInner {
21 pub transport: Transport,
22 pub api_account: api::Account,
23 pub api_directory: api::Directory,
24}
25
26#[derive(Debug, Clone)]
38pub struct Account {
39 inner: Arc<AccountInner>,
40}
41
42impl Account {
43 pub(crate) fn new(
44 transport: Transport,
45 api_account: api::Account,
46 api_directory: api::Directory,
47 ) -> Self {
48 Self {
49 inner: Arc::new(AccountInner {
50 transport,
51 api_account,
52 api_directory,
53 }),
54 }
55 }
56
57 pub fn acme_private_key_pem(&self) -> eyre::Result<Zeroizing<String>> {
61 self.inner.transport.acme_key().to_pem()
62 }
63
64 pub async fn new_order(
77 &self,
78 primary_name: &str,
79 alt_names: &[&str],
80 ) -> eyre::Result<NewOrder> {
81 let mut identifiers = Vec::new();
82 let mut domain_set = HashSet::new();
83
84 for domain in iter::once(primary_name).chain(alt_names.iter().copied()) {
85 if domain_set.insert(domain) {
87 identifiers.push(api::Identifier::dns(domain));
89 }
90 }
91
92 let order = api::Order::from_identifiers(identifiers);
93
94 let new_order_url = self.inner.api_directory.new_order.as_str();
95
96 let res = self.inner.transport.call_kid(new_order_url, &order).await?;
97 let order_url = req_expect_header(&res, "location")?;
98 let api_order = res.json::<api::Order>().await?;
99
100 let mut order = Order::new(&self.inner, order, order_url);
101 order.api_order.overwrite(api_order)?;
102 Ok(NewOrder { order })
103 }
104
105 pub async fn revoke_certificate(
107 &self,
108 cert: &Certificate,
109 reason: RevocationReason,
110 ) -> eyre::Result<()> {
111 let cert_chain = cert.certificate_chain()?;
112 let cert_ee = cert_chain
113 .first()
114 .ok_or_else(|| eyre!("no certificates in chain"))?;
115
116 let certificate = BASE64_URL_SAFE_NO_PAD.encode(cert_ee);
118
119 let reason = match reason {
120 RevocationReason::Unspecified => None,
124
125 reason => Some(reason as usize),
126 };
127
128 let revocation = api::Revocation::new(certificate, reason);
129
130 let url = &self.inner.api_directory.revoke_cert;
131 self.inner.transport.call_kid(url, &revocation).await?;
132
133 Ok(())
134 }
135
136 pub fn api_account(&self) -> &api::Account {
140 &self.inner.api_account
141 }
142}
143
144pub enum RevocationReason {
150 Unspecified = 0,
151 KeyCompromise = 1,
152 CACompromise = 2,
153 AffiliationChanged = 3,
154 Superseded = 4,
155 CessationOfOperation = 5,
156 CertificateHold = 6,
157 RemoveFromCRL = 8,
159 PrivilegeWithdrawn = 9,
160 AACompromise = 10,
161}
162
163#[cfg(test)]
164mod tests {
165 use crate::{Directory, DirectoryUrl};
166
167 #[tokio::test]
168 async fn test_create_order() {
169 let server = crate::test::with_directory_server();
170
171 let url = DirectoryUrl::Other(&server.dir_url);
172 let dir = Directory::fetch(url).await.unwrap();
173
174 let acc = dir
175 .register_account(Some(vec!["mailto:foo@bar.com".to_owned()]))
176 .await
177 .unwrap();
178
179 let _order = acc.new_order("acme-test.example.com", &[]).await.unwrap();
180 }
181}