Browse Source

Stop to require the `orders` field on account creation

RFC 8555 states that:
 - when an account is successfully created, the server "returns this
   account object" (section 7.3);
 - the `orders` field in account objects is mandatory (section 7.1.2).

Despite that, Boulder does not returns the `orders` field when an
account is created. This non-standard behavior prevented ACMEd from
creating account and testing them for existence.

In order to allow ACMEd to retrieve certificates from CAs using Boulder,
the `orders` field is no longer mandatory and the account existence is
tested when the order is requested.
https://github.com/letsencrypt/boulder/issues/3335
pull/39/head
Rodolphe Breard 4 years ago
parent
commit
0db5e6898f
  1. 3
      CHANGELOG.md
  2. 23
      acmed/src/account.rs
  3. 6
      acmed/src/account/storage.rs
  4. 32
      acmed/src/acme_proto.rs
  5. 38
      acmed/src/acme_proto/account.rs
  6. 9
      acmed/src/http.rs

3
CHANGELOG.md

@ -19,6 +19,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Some subject attributes can now be specified. - Some subject attributes can now be specified.
- Support for NIST P-521 certificates and account keys. - Support for NIST P-521 certificates and account keys.
### Fixed
- Support for Let's Encrypt non-standard account creation object.
## [0.11.0] - 2020-09-19 ## [0.11.0] - 2020-09-19

23
acmed/src/account.rs

@ -1,6 +1,4 @@
use crate::acme_proto::account::{
check_account_exists, register_account, update_account_contacts, update_account_key,
};
use crate::acme_proto::account::{register_account, update_account_contacts, update_account_key};
use crate::endpoint::Endpoint; use crate::endpoint::Endpoint;
use crate::logs::HasLogger; use crate::logs::HasLogger;
use crate::storage::FileManager; use crate::storage::FileManager;
@ -67,7 +65,7 @@ impl AccountKey {
pub struct AccountEndpoint { pub struct AccountEndpoint {
pub creation_date: SystemTime, pub creation_date: SystemTime,
pub account_url: String, pub account_url: String,
pub order_url: String,
pub orders_url: String,
pub key_hash: Vec<u8>, pub key_hash: Vec<u8>,
pub contacts_hash: Vec<u8>, pub contacts_hash: Vec<u8>,
pub external_account_hash: Vec<u8>, pub external_account_hash: Vec<u8>,
@ -78,7 +76,7 @@ impl AccountEndpoint {
AccountEndpoint { AccountEndpoint {
creation_date: SystemTime::UNIX_EPOCH, creation_date: SystemTime::UNIX_EPOCH,
account_url: String::new(), account_url: String::new(),
order_url: String::new(),
orders_url: String::new(),
key_hash: Vec::new(), key_hash: Vec::new(),
contacts_hash: Vec::new(), contacts_hash: Vec::new(),
external_account_hash: Vec::new(), external_account_hash: Vec::new(),
@ -233,15 +231,20 @@ impl Account {
if key_changed { if key_changed {
update_account_key(endpoint, root_certs, self)?; update_account_key(endpoint, root_certs, self)?;
} }
if !contacts_changed && !key_changed {
check_account_exists(endpoint, root_certs, self)?;
}
} else { } else {
register_account(endpoint, root_certs, self)?; register_account(endpoint, root_certs, self)?;
} }
Ok(()) Ok(())
} }
pub fn register(
&mut self,
endpoint: &mut Endpoint,
root_certs: &[String],
) -> Result<(), Error> {
register_account(endpoint, root_certs, self)
}
pub fn save(&self) -> Result<(), Error> { pub fn save(&self) -> Result<(), Error> {
storage::save(&self.file_manager, self) storage::save(&self.file_manager, self)
} }
@ -252,9 +255,9 @@ impl Account {
Ok(()) Ok(())
} }
pub fn set_order_url(&mut self, endpoint_name: &str, order_url: &str) -> Result<(), Error> {
pub fn set_orders_url(&mut self, endpoint_name: &str, orders_url: &str) -> Result<(), Error> {
let mut ep = self.get_endpoint_mut(endpoint_name)?; let mut ep = self.get_endpoint_mut(endpoint_name)?;
ep.order_url = order_url.to_string();
ep.orders_url = orders_url.to_string();
Ok(()) Ok(())
} }

6
acmed/src/account/storage.rs

@ -61,7 +61,7 @@ impl AccountKeyStorage {
struct AccountEndpointStorage { struct AccountEndpointStorage {
creation_date: SystemTime, creation_date: SystemTime,
account_url: String, account_url: String,
order_url: String,
orders_url: String,
key_hash: Vec<u8>, key_hash: Vec<u8>,
contacts_hash: Vec<u8>, contacts_hash: Vec<u8>,
external_account_hash: Vec<u8>, external_account_hash: Vec<u8>,
@ -72,7 +72,7 @@ impl AccountEndpointStorage {
AccountEndpointStorage { AccountEndpointStorage {
creation_date: account_endpoint.creation_date, creation_date: account_endpoint.creation_date,
account_url: account_endpoint.account_url.clone(), account_url: account_endpoint.account_url.clone(),
order_url: account_endpoint.order_url.clone(),
orders_url: account_endpoint.orders_url.clone(),
key_hash: account_endpoint.key_hash.clone(), key_hash: account_endpoint.key_hash.clone(),
contacts_hash: account_endpoint.contacts_hash.clone(), contacts_hash: account_endpoint.contacts_hash.clone(),
external_account_hash: account_endpoint.external_account_hash.clone(), external_account_hash: account_endpoint.external_account_hash.clone(),
@ -83,7 +83,7 @@ impl AccountEndpointStorage {
AccountEndpoint { AccountEndpoint {
creation_date: self.creation_date, creation_date: self.creation_date,
account_url: self.account_url.clone(), account_url: self.account_url.clone(),
order_url: self.order_url.clone(),
orders_url: self.orders_url.clone(),
key_hash: self.key_hash.clone(), key_hash: self.key_hash.clone(),
contacts_hash: self.contacts_hash.clone(), contacts_hash: self.contacts_hash.clone(),
external_account_hash: self.external_account_hash.clone(), external_account_hash: self.external_account_hash.clone(),

32
acmed/src/acme_proto.rs

@ -1,6 +1,6 @@
use crate::account::Account; use crate::account::Account;
use crate::acme_proto::structs::{ use crate::acme_proto::structs::{
ApiError, Authorization, AuthorizationStatus, NewOrder, Order, OrderStatus,
AcmeError, ApiError, Authorization, AuthorizationStatus, NewOrder, Order, OrderStatus,
}; };
use crate::certificate::Certificate; use crate::certificate::Certificate;
use crate::endpoint::Endpoint; use crate::endpoint::Endpoint;
@ -98,14 +98,28 @@ pub fn request_certificate(
account.synchronize(endpoint, root_certs)?; account.synchronize(endpoint, root_certs)?;
// Create a new order // Create a new order
let new_order = NewOrder::new(&cert.identifiers);
let new_order = serde_json::to_string(&new_order)?;
let data_builder = set_data_builder!(account, endpoint_name, new_order.as_bytes());
let (order, order_url) =
http::new_order(endpoint, root_certs, &data_builder).map_err(HttpError::in_err)?;
if let Some(e) = order.get_error() {
cert.warn(&e.prefix("Error").message);
}
let mut new_reg = false;
let (order, order_url) = loop {
let new_order = NewOrder::new(&cert.identifiers);
let new_order = serde_json::to_string(&new_order)?;
let data_builder = set_data_builder!(account, endpoint_name, new_order.as_bytes());
match http::new_order(endpoint, root_certs, &data_builder) {
Ok((order, order_url)) => {
if let Some(e) = order.get_error() {
cert.warn(&e.prefix("Error").message);
}
break (order, order_url);
}
Err(e) => {
if !new_reg && e.is_acme_err(AcmeError::AccountDoesNotExist) {
account.register(endpoint, root_certs)?;
new_reg = true;
} else {
return Err(HttpError::in_err(e));
}
}
};
};
// Begin iter over authorizations // Begin iter over authorizations
for auth_url in order.authorizations.iter() { for auth_url in order.authorizations.iter() {

38
acmed/src/acme_proto/account.rs

@ -5,7 +5,7 @@ use crate::endpoint::Endpoint;
use crate::http::HttpError; use crate::http::HttpError;
use crate::jws::{encode_jwk, encode_jwk_no_nonce, encode_kid}; use crate::jws::{encode_jwk, encode_jwk_no_nonce, encode_kid};
use crate::logs::HasLogger; use crate::logs::HasLogger;
use crate::{set_data_builder, set_empty_data_builder};
use crate::set_data_builder;
use acme_common::error::Error; use acme_common::error::Error;
macro_rules! create_account_if_does_not_exist { macro_rules! create_account_if_does_not_exist {
@ -49,12 +49,19 @@ pub fn register_account(
let (acc_rep, account_url) = let (acc_rep, account_url) =
http::new_account(endpoint, root_certs, &data_builder).map_err(HttpError::in_err)?; http::new_account(endpoint, root_certs, &data_builder).map_err(HttpError::in_err)?;
account.set_account_url(&endpoint.name, &account_url)?; account.set_account_url(&endpoint.name, &account_url)?;
let msg = format!(
"endpoint \"{}\": account \"{}\": the server has not provided an order URL upon account creation",
&endpoint.name, &account.name
);
let order_url = acc_rep.orders.ok_or_else(|| Error::from(&msg))?;
account.set_order_url(&endpoint.name, &order_url)?;
let orders_url = match acc_rep.orders {
Some(url) => url,
None => {
let msg = format!(
"endpoint \"{}\": account \"{}\": the server has not provided an order URL upon account creation",
&endpoint.name,
&account.name
);
account.warn(&msg);
String::new()
}
};
account.set_orders_url(&endpoint.name, &orders_url)?;
account.update_key_hash(&endpoint.name)?; account.update_key_hash(&endpoint.name)?;
account.update_contacts_hash(&endpoint.name)?; account.update_contacts_hash(&endpoint.name)?;
account.update_external_account_hash(&endpoint.name)?; account.update_external_account_hash(&endpoint.name)?;
@ -143,20 +150,3 @@ pub fn update_account_key(
)); ));
Ok(()) Ok(())
} }
pub fn check_account_exists(
endpoint: &mut Endpoint,
root_certs: &[String],
account: &mut BaseAccount,
) -> Result<(), Error> {
let endpoint_name = endpoint.name.clone();
let url = account.get_endpoint(&endpoint_name)?.order_url.clone();
let data_builder = set_empty_data_builder!(account, endpoint_name);
create_account_if_does_not_exist!(
http::post_jose_no_response(endpoint, root_certs, &data_builder, &url),
endpoint,
root_certs,
account
)?;
Ok(())
}

9
acmed/src/http.rs

@ -1,4 +1,4 @@
use crate::acme_proto::structs::HttpApiError;
use crate::acme_proto::structs::{AcmeError, HttpApiError};
use crate::endpoint::Endpoint; use crate::endpoint::Endpoint;
use acme_common::crypto::X509Certificate; use acme_common::crypto::X509Certificate;
use acme_common::error::Error; use acme_common::error::Error;
@ -58,6 +58,13 @@ impl HttpError {
HttpError::GenericError(e) => e, HttpError::GenericError(e) => e,
} }
} }
pub fn is_acme_err(&self, acme_error: AcmeError) -> bool {
match self {
HttpError::ApiError(aerr) => aerr.get_acme_type() == acme_error,
HttpError::GenericError(_) => false,
}
}
} }
impl From<Error> for HttpError { impl From<Error> for HttpError {

Loading…
Cancel
Save