Browse Source

Allow accounts to be updated

The previous strategy for accounts management on endpoints was to send
an account creation request every time in order to retrieve the account
URL. Although it works on most cases, the contact information or key
update wasn't handled correctly.

The only drawback of this new way of managing accounts is that, if the
endpoints drops the account, ACMEd will fail to renew any certificate
for this account on this endpoint. Previously, it would have simply
created a new account. This should be fixed soon.
pull/39/head
Rodolphe Breard 4 years ago
parent
commit
9b12e88ae1
  1. 27
      acmed/src/account.rs
  2. 4
      acmed/src/acme_proto.rs
  3. 63
      acmed/src/acme_proto/account.rs
  4. 26
      acmed/src/acme_proto/http.rs
  5. 4
      acmed/src/acme_proto/structs.rs
  6. 21
      acmed/src/acme_proto/structs/account.rs
  7. 22
      acmed/src/jws.rs

27
acmed/src/account.rs

@ -1,4 +1,4 @@
use crate::acme_proto::account::register_account;
use crate::acme_proto::account::{register_account, update_account_contacts, update_account_key};
use crate::endpoint::Endpoint;
use crate::logs::HasLogger;
use crate::storage::FileManager;
@ -130,6 +130,17 @@ impl Account {
}
}
pub fn get_past_key(&self, key_hash: &[u8]) -> Result<&AccountKey, Error> {
let key_hash = key_hash.to_vec();
for key in &self.past_keys {
let past_key_hash = hash_key(key)?;
if past_key_hash == key_hash {
return Ok(key);
}
}
Err("key not found".into())
}
pub fn load(
file_manager: &FileManager,
name: &str,
@ -183,7 +194,21 @@ impl Account {
endpoint: &mut Endpoint,
root_certs: &[String],
) -> Result<(), Error> {
let acc_ep = self.get_endpoint(&endpoint.name)?;
if !acc_ep.account_url.is_empty() {
let ct_hash = hash_contacts(&self.contacts);
let key_hash = hash_key(&self.current_key)?;
let contacts_changed = ct_hash != acc_ep.contacts_hash;
let key_changed = key_hash != acc_ep.key_hash;
if contacts_changed {
update_account_contacts(endpoint, root_certs, self)?;
}
if key_changed {
update_account_key(endpoint, root_certs, self)?;
}
} else {
register_account(endpoint, root_certs, self)?;
}
Ok(())
}

4
acmed/src/acme_proto.rs

@ -58,6 +58,7 @@ impl PartialEq<structs::Challenge> for Challenge {
}
}
#[macro_export]
macro_rules! set_data_builder {
($account: ident, $endpoint_name: ident, $data: expr) => {
|n: &str, url: &str| {
@ -138,8 +139,7 @@ pub fn request_certificate(
// Tell the server the challenge has been completed
let chall_url = challenge.get_url();
let data_builder = set_data_builder!(account, endpoint_name, b"{}");
let _ =
http::post_challenge_response(endpoint, root_certs, &data_builder, &chall_url)?;
let _ = http::post_no_response(endpoint, root_certs, &data_builder, &chall_url)?;
}
}

63
acmed/src/acme_proto/account.rs

@ -1,9 +1,10 @@
use crate::account::Account as BaseAccount;
use crate::acme_proto::http;
use crate::acme_proto::structs::Account;
use crate::acme_proto::structs::{Account, AccountKeyRollover, AccountUpdate};
use crate::endpoint::Endpoint;
use crate::jws::encode_jwk;
use crate::jws::{encode_jwk, encode_jwk_no_nonce, encode_kid};
use crate::logs::HasLogger;
use crate::set_data_builder;
use acme_common::error::Error;
pub fn register_account(
@ -36,3 +37,61 @@ pub fn register_account(
account.info(&format!("account created on endpoint {}", &endpoint.name));
Ok(())
}
pub fn update_account_contacts(
endpoint: &mut Endpoint,
root_certs: &[String],
account: &mut BaseAccount,
) -> Result<(), Error> {
let endpoint_name = endpoint.name.clone();
account.debug(&format!(
"updating account contacts on endpoint {}...",
&endpoint_name
));
let new_contacts: Vec<String> = account.contacts.iter().map(|c| c.to_string()).collect();
let acc_up_struct = AccountUpdate::new(&new_contacts);
let acc_up_struct = serde_json::to_string(&acc_up_struct)?;
let data_builder = set_data_builder!(account, endpoint_name, acc_up_struct.as_bytes());
let url = account.get_endpoint(&endpoint_name)?.account_url.clone();
http::post_no_response(endpoint, root_certs, &data_builder, &url)?;
account.update_contacts_hash(&endpoint_name)?;
account.save()?;
account.info(&format!(
"account contacts updated on endpoint {}",
&endpoint_name
));
Ok(())
}
pub fn update_account_key(
endpoint: &mut Endpoint,
root_certs: &[String],
account: &mut BaseAccount,
) -> Result<(), Error> {
let endpoint_name = endpoint.name.clone();
account.debug(&format!(
"updating account key on endpoint {}...",
&endpoint_name
));
let url = endpoint.dir.key_change.clone();
let ep = account.get_endpoint(&endpoint_name)?;
let old_account_key = account.get_past_key(&ep.key_hash)?;
let old_key = &old_account_key.key;
let rollover_struct = AccountKeyRollover::new(account, &old_key)?;
let rollover_struct = serde_json::to_string(&rollover_struct)?;
let rollover_payload = encode_jwk_no_nonce(
&old_key,
&old_account_key.signature_algorithm,
rollover_struct.as_bytes(),
&url,
)?;
let data_builder = set_data_builder!(account, endpoint_name, rollover_payload.as_bytes());
http::post_no_response(endpoint, root_certs, &data_builder, &url)?;
account.update_key_hash(&endpoint_name)?;
account.save()?;
account.info(&format!(
"account key updated on endpoint {}",
&endpoint_name
));
Ok(())
}

26
acmed/src/acme_proto/http.rs

@ -26,6 +26,19 @@ pub fn refresh_directory(endpoint: &mut Endpoint, root_certs: &[String]) -> Resu
Ok(())
}
pub fn post_no_response<F>(
endpoint: &mut Endpoint,
root_certs: &[String],
data_builder: &F,
url: &str,
) -> Result<(), Error>
where
F: Fn(&str, &str) -> Result<String, Error>,
{
let _ = http::post_jose(endpoint, root_certs, &url, data_builder)?;
Ok(())
}
pub fn new_account<F>(
endpoint: &mut Endpoint,
root_certs: &[String],
@ -78,19 +91,6 @@ where
Ok(auth)
}
pub fn post_challenge_response<F>(
endpoint: &mut Endpoint,
root_certs: &[String],
data_builder: &F,
url: &str,
) -> Result<(), Error>
where
F: Fn(&str, &str) -> Result<String, Error>,
{
let _ = http::post_jose(endpoint, root_certs, &url, data_builder)?;
Ok(())
}
pub fn pool_authorization<F, S>(
endpoint: &mut Endpoint,
root_certs: &[String],

4
acmed/src/acme_proto/structs.rs

@ -18,7 +18,9 @@ mod directory;
mod error;
mod order;
pub use account::{Account, AccountDeactivation, AccountResponse, AccountUpdate};
pub use account::{
Account, AccountDeactivation, AccountKeyRollover, AccountResponse, AccountUpdate,
};
pub use authorization::{Authorization, AuthorizationStatus, Challenge};
pub use deserialize_from_str;
pub use directory::Directory;

21
acmed/src/acme_proto/structs/account.rs

@ -1,6 +1,8 @@
use crate::endpoint::Endpoint;
use acme_common::crypto::KeyPair;
use acme_common::error::Error;
use serde::{Deserialize, Serialize};
use serde_json::value::Value;
use std::str::FromStr;
#[derive(Serialize)]
@ -33,15 +35,12 @@ pub struct AccountResponse {
deserialize_from_str!(AccountResponse);
// TODO: implement account update
#[allow(dead_code)]
#[derive(Serialize)]
pub struct AccountUpdate {
pub contact: Vec<String>,
}
impl AccountUpdate {
#[allow(dead_code)]
pub fn new(contact: &[String]) -> Self {
AccountUpdate {
contact: contact.into(),
@ -49,6 +48,22 @@ impl AccountUpdate {
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountKeyRollover {
pub account: String,
pub old_key: Value,
}
impl AccountKeyRollover {
pub fn new(account: &crate::account::Account, old_key: &KeyPair) -> Result<Self, Error> {
Ok(AccountKeyRollover {
account: account.name.clone(),
old_key: old_key.jwk_public_key()?,
})
}
}
// TODO: implement account deactivation
#[allow(dead_code)]
#[derive(Serialize)]

22
acmed/src/jws.rs

@ -11,6 +11,13 @@ struct JwsData {
signature: String,
}
#[derive(Serialize)]
struct JwsProtectedHeaderJwkNoNonce {
alg: String,
jwk: Value,
url: String,
}
#[derive(Serialize)]
struct JwsProtectedHeaderJwk {
alg: String,
@ -64,6 +71,21 @@ pub fn encode_jwk(
get_data(key_pair, sign_alg, &protected, payload)
}
pub fn encode_jwk_no_nonce(
key_pair: &KeyPair,
sign_alg: &JwsSignatureAlgorithm,
payload: &[u8],
url: &str,
) -> Result<String, Error> {
let protected = JwsProtectedHeaderJwkNoNonce {
alg: sign_alg.to_string(),
jwk: key_pair.jwk_public_key()?,
url: url.into(),
};
let protected = serde_json::to_string(&protected)?;
get_data(key_pair, sign_alg, &protected, payload)
}
pub fn encode_kid(
key_pair: &KeyPair,
sign_alg: &JwsSignatureAlgorithm,

Loading…
Cancel
Save