1use std::sync::Arc;
2
3use base64::prelude::*;
4use zeroize::Zeroizing;
5
6use crate::{
7 api::{ApiAccount, ApiDirectory, ApiIdentifier, ApiOrder, ApiRevocation},
8 cert::Certificate,
9 order::{NewOrder, Order},
10 req::req_expect_header,
11 trans::Transport,
12};
13
14mod acme_key;
15
16pub(crate) use self::acme_key::AcmeKey;
17
18#[derive(Clone, Debug)]
19pub(crate) struct AccountInner {
20 pub transport: Transport,
21 pub api_account: ApiAccount,
22 pub api_directory: ApiDirectory,
23}
24
25#[derive(Clone)]
40pub struct Account {
41 inner: Arc<AccountInner>,
42}
43
44impl Account {
45 pub(crate) fn new(
46 transport: Transport,
47 api_account: ApiAccount,
48 api_directory: ApiDirectory,
49 ) -> Self {
50 Account {
51 inner: Arc::new(AccountInner {
52 transport,
53 api_account,
54 api_directory,
55 }),
56 }
57 }
58
59 pub fn acme_signing_key_pem(&self) -> eyre::Result<Zeroizing<String>> {
63 self.inner.transport.acme_key().to_pem()
64 }
65
66 pub async fn new_order(
79 &self,
80 primary_name: &str,
81 alt_names: &[&str],
82 ) -> eyre::Result<NewOrder> {
83 let prim_arr = [primary_name];
85 let domains = prim_arr.iter().chain(alt_names);
86 let identifiers = domains
87 .map(|&domain| ApiIdentifier {
88 _type: "dns".to_owned(),
89 value: domain.to_owned(),
90 })
91 .collect();
92 let order = ApiOrder {
93 identifiers,
94 ..Default::default()
95 };
96
97 let new_order_url = self.inner.api_directory.new_order.as_str();
98
99 let res = self.inner.transport.call(new_order_url, &order).await?;
100 let order_url = req_expect_header(&res, "location")?;
101 let api_order = res.json::<ApiOrder>().await?;
102
103 let order = Order::new(&self.inner, api_order, order_url);
104 Ok(NewOrder { order })
105 }
106
107 pub async fn revoke_certificate(
109 &self,
110 cert: &Certificate,
111 reason: RevocationReason,
112 ) -> eyre::Result<()> {
113 let certificate = BASE64_URL_SAFE_NO_PAD.encode(cert.certificate_der()?);
115
116 let revoc = ApiRevocation {
117 certificate,
118 reason: reason as usize,
119 };
120
121 let url = &self.inner.api_directory.revoke_cert;
122 self.inner.transport.call(url, &revoc).await?;
123
124 Ok(())
125 }
126
127 pub fn api_account(&self) -> &ApiAccount {
129 &self.inner.api_account
130 }
131}
132
133pub enum RevocationReason {
137 Unspecified = 0,
138 KeyCompromise = 1,
139 CACompromise = 2,
140 AffiliationChanged = 3,
141 Superseded = 4,
142 CessationOfOperation = 5,
143 CertificateHold = 6,
144 RemoveFromCRL = 8,
146 PrivilegeWithdrawn = 9,
147 AACompromise = 10,
148}
149
150#[cfg(test)]
151mod tests {
152 use crate::{Directory, DirectoryUrl};
153
154 #[tokio::test]
155 async fn test_create_order() {
156 let server = crate::test::with_directory_server();
157
158 let url = DirectoryUrl::Other(&server.dir_url);
159 let dir = Directory::from_url(url).await.unwrap();
160
161 let acc = dir
162 .register_account(Some(vec!["mailto:foo@bar.com".to_owned()]))
163 .await
164 .unwrap();
165
166 let _order = acc.new_order("acmetest.example.com", &[]).await.unwrap();
167 }
168}