acme2_eab/
account.rs

1use crate::directory::Directory;
2use crate::error::*;
3use crate::helpers::*;
4use crate::jws::jws;
5use crate::jws::Jwk;
6use openssl::pkey::PKey;
7use openssl::pkey::Private;
8use serde::Deserialize;
9use serde_json::json;
10use std::sync::Arc;
11use tracing::field;
12use tracing::instrument;
13use tracing::Level;
14use tracing::Span;
15
16/// The status of an [`Account`].
17///
18/// Possible values are "valid", "deactivated",
19/// and "revoked". The value "deactivated" should be used to indicate client-
20/// initiated deactivation whereas "revoked" should be used to indicate server-
21/// initiated deactivation.
22#[derive(Deserialize, Eq, PartialEq, Debug, Clone)]
23#[serde(rename_all = "camelCase")]
24pub enum AccountStatus {
25    Valid,
26    Deactivated,
27    Revoked,
28}
29
30#[derive(Debug, Clone)]
31pub(crate) struct ExternalAccountBinding {
32    /// Key identifier, in string form.
33    key_id: String,
34
35    /// HMAC private key.
36    private_key: PKey<Private>,
37}
38
39/// An ACME account. This is used to identify a subscriber to an ACME server.
40///
41/// This resource should be created through an [`AccountBuilder`].
42#[derive(Deserialize, Debug, Clone)]
43#[serde(rename_all = "camelCase")]
44pub struct Account {
45    #[serde(skip)]
46    pub(crate) directory: Option<Arc<Directory>>,
47
48    #[serde(skip)]
49    pub(crate) private_key: Option<PKey<Private>>,
50
51    #[serde(skip)]
52    pub(crate) eab_config: Option<ExternalAccountBinding>,
53
54    #[serde(skip)]
55    /// The account ID of this account.
56    pub id: String,
57
58    /// The status of this account.
59    pub status: AccountStatus,
60    /// An array of URLs that the server can use to contact the client for
61    /// issues related to this account.
62    pub contact: Option<Vec<String>>,
63    /// Including this field in a newAccount request, with a value of true,
64    /// indicates the client's agreement with the terms of service.
65    pub terms_of_service_agreed: Option<bool>,
66    // TODO(lucacasonato): enable this once LE supports it
67    // /// A URL from which a list of orders submitted by this account can be
68    // /// fetched
69    // #[serde(rename = "orders")]
70    // pub(crate) orders_url: Option<String>,
71}
72
73/// An builder that is used to create / retrieve an [`Account`] from the
74/// ACME server.
75#[derive(Debug)]
76pub struct AccountBuilder {
77    directory: Arc<Directory>,
78
79    private_key: Option<PKey<Private>>,
80    eab_config: Option<ExternalAccountBinding>,
81
82    contact: Option<Vec<String>>,
83    terms_of_service_agreed: Option<bool>,
84    only_return_existing: Option<bool>,
85}
86
87impl AccountBuilder {
88    /// This creates a new [`AccountBuilder`]. This can be used to create a new
89    /// account (if the server has not seen the private key before), or to retrieve
90    /// an existing account (using a previously used private key).
91    pub fn new(directory: Arc<Directory>) -> Self {
92        AccountBuilder {
93            directory,
94            private_key: None,
95            eab_config: None,
96            contact: None,
97            terms_of_service_agreed: None,
98            only_return_existing: None,
99        }
100    }
101
102    /// The private key that is used to sign requests to the ACME server. This
103    /// may not be the same as a certificate private key.
104    pub fn private_key(&mut self, private_key: PKey<Private>) -> &mut Self {
105        self.private_key = Some(private_key);
106        self
107    }
108
109    pub fn external_account_binding(
110        &mut self,
111        key_id: String,
112        private_key: PKey<Private>,
113    ) -> &mut Self {
114        self.eab_config = Some(ExternalAccountBinding {
115            key_id,
116            private_key,
117        });
118        self
119    }
120
121    /// The contact information for the account. For example this could be a
122    /// `vec!["email:hello@lcas.dev".to_string()]`. The supported contact types
123    /// vary from one ACME server to another.
124    pub fn contact(&mut self, contact: Vec<String>) -> &mut Self {
125        self.contact = Some(contact);
126        self
127    }
128
129    /// If you agree to the ACME server terms of service.
130    pub fn terms_of_service_agreed(&mut self, terms_of_service_agreed: bool) -> &mut Self {
131        self.terms_of_service_agreed = Some(terms_of_service_agreed);
132        self
133    }
134
135    /// Do not try to create a new account. If this is set, only an existing account
136    /// will be returned.
137    pub fn only_return_existing(&mut self, only_return_existing: bool) -> &mut Self {
138        self.only_return_existing = Some(only_return_existing);
139        self
140    }
141
142    /// This will create / retrieve an [`Account`] from the ACME server.
143    ///
144    /// If the [`AccountBuilder`] does not contain a private key, a new
145    /// 4096 bit RSA key will be generated (using the system random). If
146    /// a key is generated, it can be retrieved from the created [`Account`]
147    /// through the [`Account::private_key`] method.
148    #[instrument(level = Level::INFO, name = "acme2::AccountBuilder::build", err, skip(self), fields(contact = ?self.contact, terms_of_service_agreed = ?self.terms_of_service_agreed, only_return_existing = ?self.only_return_existing, private_key_id = field::Empty))]
149    pub async fn build(&mut self) -> Result<Arc<Account>, Error> {
150        let private_key = if let Some(private_key) = self.private_key.clone() {
151            private_key
152        } else {
153            gen_rsa_private_key(4096)?
154        };
155
156        let url = self.directory.new_account_url.clone();
157
158        let external_account_binding = if let Some(eab_config) = &self.eab_config {
159            let payload = serde_json::to_string(&Jwk::new(&private_key)).unwrap();
160
161            Some(jws(
162                &url,
163                None,
164                &payload,
165                &eab_config.private_key,
166                Some(eab_config.key_id.clone()),
167            )?)
168        } else {
169            None
170        };
171
172        let (res, headers) = self
173            .directory
174            .authenticated_request::<_, Account>(
175                &url,
176                json!({
177                  "contact": self.contact,
178                  "termsOfServiceAgreed": self.terms_of_service_agreed,
179                  "onlyReturnExisting": self.only_return_existing,
180                  // TODO: omit if None?
181                  "externalAccountBinding": external_account_binding,
182                }),
183                private_key.clone(),
184                None,
185            )
186            .await?;
187        let res: Result<Account, Error> = res.into();
188        let mut acc = res?;
189
190        let account_id = map_transport_err(
191            headers
192                .get(reqwest::header::LOCATION)
193                .ok_or_else(|| {
194                    transport_err("mandatory location header in newAccount not present")
195                })?
196                .to_str(),
197        )?
198        .to_string();
199        Span::current().record("account_id", &field::display(&account_id));
200
201        acc.directory = Some(self.directory.clone());
202        acc.private_key = Some(private_key);
203        acc.eab_config = self.eab_config.clone();
204        acc.id = account_id;
205        Ok(Arc::new(acc))
206    }
207}
208
209impl Account {
210    /// Retrieve the private key for this account.
211    pub fn private_key(&self) -> PKey<Private> {
212        self.private_key.clone().unwrap()
213    }
214}