Browse Source

Add external account binding

pull/39/head
Rodolphe Breard 4 years ago
parent
commit
8c116f0b55
  1. 1
      CHANGELOG.md
  2. 7
      README.md
  3. 10
      acmed/src/account.rs
  4. 38
      acmed/src/account/storage.rs
  5. 2
      acmed/src/acme_proto/account.rs
  6. 31
      acmed/src/acme_proto/structs/account.rs
  7. 43
      acmed/src/config.rs
  8. 5
      acmed/src/main.rs
  9. 21
      man/en/acmed.toml.5

1
CHANGELOG.md

@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- The `contacts` account configuration field has been added.
- External account binding.
### Changed
- The `email` account configuration field has been removed. In replacement, use the `contacts` field.

7
README.md

@ -28,10 +28,11 @@ The Automatic Certificate Management Environment (ACME), is an internet standard
- A pre-built set of hooks that can be used in most circumstances
- Run as a deamon: no need to set-up timers, crontab or other time-triggered process
- Retry of HTTPS request rejected with a badNonce or other recoverable errors
- Customizable HTTPS requests rate limits.
- Customizable HTTPS requests rate limits
- External account binding
- Optional key pair reuse (useful for [HPKP](https://en.wikipedia.org/wiki/HTTP_Public_Key_Pinning))
- For a given certificate, each domain name may be validated using a different challenge.
- A standalone server dedicated to the tls-alpn-01 challenge validation (tacd).
- For a given certificate, each domain name may be validated using a different challenge
- A standalone server dedicated to the tls-alpn-01 challenge validation (tacd)
## Planned features

10
acmed/src/account.rs

@ -14,6 +14,13 @@ use std::time::SystemTime;
mod contact;
mod storage;
#[derive(Clone, Debug)]
pub struct ExternalAccount {
pub identifier: String,
pub key: Vec<u8>,
pub signature_algorithm: JwsSignatureAlgorithm,
}
#[derive(Clone, Debug)]
pub enum AccountContactType {
Mailfrom,
@ -85,6 +92,7 @@ pub struct Account {
pub current_key: AccountKey,
pub past_keys: Vec<AccountKey>,
pub file_manager: FileManager,
pub external_account: Option<ExternalAccount>,
}
impl HasLogger for Account {
@ -149,6 +157,7 @@ impl Account {
contacts: &[(String, String)],
key_type: &Option<String>,
signature_algorithm: &Option<String>,
external_account: &Option<ExternalAccount>,
) -> Result<Self, Error> {
let contacts = contacts
.iter()
@ -177,6 +186,7 @@ impl Account {
current_key: AccountKey::new(key_type, signature_algorithm)?,
past_keys: Vec::new(),
file_manager: file_manager.clone(),
external_account: external_account.to_owned(),
};
account.debug("initializing a new account");
account

38
acmed/src/account/storage.rs

@ -1,5 +1,5 @@
use crate::account::contact::AccountContact;
use crate::account::{Account, AccountEndpoint, AccountKey};
use crate::account::{Account, AccountEndpoint, AccountKey, ExternalAccount};
use crate::storage::{account_files_exists, get_account_data, set_account_data, FileManager};
use acme_common::crypto::KeyPair;
use acme_common::error::Error;
@ -7,6 +7,31 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::SystemTime;
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct ExternalAccountStorage {
pub identifier: String,
pub key: Vec<u8>,
pub signature_algorithm: String,
}
impl ExternalAccountStorage {
fn new(external_account: &ExternalAccount) -> Self {
ExternalAccountStorage {
identifier: external_account.identifier.to_owned(),
key: external_account.key.to_owned(),
signature_algorithm: external_account.signature_algorithm.to_string(),
}
}
fn to_generic(&self) -> Result<ExternalAccount, Error> {
Ok(ExternalAccount {
identifier: self.identifier.to_owned(),
key: self.key.to_owned(),
signature_algorithm: self.signature_algorithm.parse()?,
})
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct AccountKeyStorage {
creation_date: SystemTime,
@ -70,6 +95,7 @@ struct AccountStorage {
contacts: Vec<(String, String)>,
current_key: AccountKeyStorage,
past_keys: Vec<AccountKeyStorage>,
external_account: Option<ExternalAccountStorage>,
}
pub fn fetch(file_manager: &FileManager, name: &str) -> Result<Option<Account>, Error> {
@ -93,6 +119,10 @@ pub fn fetch(file_manager: &FileManager, name: &str) -> Result<Option<Account>,
.iter()
.map(|k| k.to_generic())
.collect::<Result<Vec<AccountKey>, Error>>()?;
let external_account = match obj.external_account {
Some(a) => Some(a.to_generic()?),
None => None,
};
Ok(Some(Account {
name: obj.name,
endpoints,
@ -100,6 +130,7 @@ pub fn fetch(file_manager: &FileManager, name: &str) -> Result<Option<Account>,
current_key,
past_keys,
file_manager: file_manager.clone(),
external_account,
}))
} else {
Ok(None)
@ -122,12 +153,17 @@ pub fn save(file_manager: &FileManager, account: &Account) -> Result<(), Error>
.iter()
.map(|k| AccountKeyStorage::new(&k))
.collect::<Result<Vec<AccountKeyStorage>, Error>>()?;
let external_account = match &account.external_account {
Some(a) => Some(ExternalAccountStorage::new(&a)),
None => None,
};
let account_storage = AccountStorage {
name: account.name.to_owned(),
endpoints,
contacts,
current_key: AccountKeyStorage::new(&account.current_key)?,
past_keys,
external_account,
};
let encoded: Vec<u8> = bincode::serialize(&account_storage)
.map_err(|e| Error::from(&e.to_string()).prefix(&account.name))?;

2
acmed/src/acme_proto/account.rs

@ -39,7 +39,7 @@ pub fn register_account(
"creating account on endpoint \"{}\"...",
&endpoint.name
));
let account_struct = Account::new(account, endpoint);
let account_struct = Account::new(account, endpoint)?;
let account_struct = serde_json::to_string(&account_struct)?;
let acc_ref = &account_struct;
let kp_ref = &account.current_key.key;

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

@ -1,4 +1,5 @@
use crate::endpoint::Endpoint;
use crate::jws::encode_kid_mac;
use acme_common::crypto::KeyPair;
use acme_common::error::Error;
use serde::{Deserialize, Serialize};
@ -11,15 +12,37 @@ pub struct Account {
pub contact: Vec<String>,
pub terms_of_service_agreed: bool,
pub only_return_existing: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub external_account_binding: Option<Value>,
}
impl Account {
pub fn new(account: &crate::account::Account, endpoint: &Endpoint) -> Self {
Account {
pub fn new(account: &crate::account::Account, endpoint: &Endpoint) -> Result<Self, Error> {
let external_account_binding = match &account.external_account {
Some(a) => {
let k_ref = &a.key;
let signature_algorithm = &a.signature_algorithm;
let kid = &a.identifier;
let payload = account.current_key.key.jwk_public_key()?;
let payload = serde_json::to_string(&payload)?;
let data = encode_kid_mac(
k_ref,
signature_algorithm,
kid,
payload.as_bytes(),
&endpoint.dir.new_account,
)?;
let data: Value = serde_json::from_str(&data)?;
Some(data)
}
None => None,
};
Ok(Account {
contact: account.contacts.iter().map(|e| e.to_string()).collect(),
terms_of_service_agreed: endpoint.tos_agreed,
only_return_existing: false,
}
external_account_binding,
})
}
}
@ -29,7 +52,7 @@ pub struct AccountResponse {
pub status: String,
pub contact: Option<Vec<String>>,
pub terms_of_service_agreed: Option<bool>,
pub external_account_binding: Option<String>,
pub external_account_binding: Option<Value>,
pub orders: Option<String>,
}

43
acmed/src/config.rs

@ -2,7 +2,8 @@ use crate::duration::parse_duration;
use crate::hooks;
use crate::identifier::IdentifierType;
use crate::storage::FileManager;
use acme_common::crypto::{HashFunction, KeyType};
use acme_common::b64_decode;
use acme_common::crypto::{HashFunction, JwsSignatureAlgorithm, KeyType};
use acme_common::error::Error;
use glob::glob;
use log::info;
@ -272,6 +273,40 @@ pub struct Group {
pub hooks: Vec<String>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ExternalAccount {
pub identifier: String,
pub key: String,
pub signature_algorithm: Option<String>,
}
impl ExternalAccount {
pub fn to_generic(&self) -> Result<crate::account::ExternalAccount, Error> {
let signature_algorithm = match &self.signature_algorithm {
Some(a) => a.parse()?,
None => crate::DEFAULT_EXTERNAL_ACCOUNT_JWA,
};
match signature_algorithm {
JwsSignatureAlgorithm::Hs256
| JwsSignatureAlgorithm::Hs384
| JwsSignatureAlgorithm::Hs512 => {}
_ => {
return Err(format!(
"{}: invalid signature algorithm for external account binding",
signature_algorithm
)
.into());
}
};
Ok(crate::account::ExternalAccount {
identifier: self.identifier.to_owned(),
key: b64_decode(&self.key)?,
signature_algorithm,
})
}
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Account {
@ -280,6 +315,7 @@ pub struct Account {
pub key_type: Option<String>,
pub signature_algorithm: Option<String>,
pub hooks: Option<Vec<String>>,
pub external_account: Option<ExternalAccount>,
#[serde(default)]
pub env: HashMap<String, String>,
}
@ -306,12 +342,17 @@ impl Account {
.iter()
.map(|e| (e.get_type(), e.get_value()))
.collect();
let external_account = match &self.external_account {
Some(a) => Some(a.to_generic()?),
None => None,
};
crate::account::Account::load(
file_manager,
&self.name,
&contacts,
&self.key_type,
&self.signature_algorithm,
&external_account,
)
}
}

5
acmed/src/main.rs

@ -1,5 +1,7 @@
use crate::main_event_loop::MainEventLoop;
use acme_common::crypto::{HashFunction, KeyType, TLS_LIB_NAME, TLS_LIB_VERSION};
use acme_common::crypto::{
HashFunction, JwsSignatureAlgorithm, KeyType, TLS_LIB_NAME, TLS_LIB_VERSION,
};
use acme_common::logs::{set_log_system, DEFAULT_LOG_LEVEL};
use acme_common::{clean_pid_file, init_server};
use clap::{App, Arg};
@ -36,6 +38,7 @@ pub const DEFAULT_PK_FILE_MODE: u32 = 0o600;
pub const DEFAULT_ACCOUNT_FILE_MODE: u32 = 0o600;
pub const DEFAULT_KP_REUSE: bool = false;
pub const DEFAULT_ACCOUNT_KEY_TYPE: KeyType = KeyType::EcdsaP256;
pub const DEFAULT_EXTERNAL_ACCOUNT_JWA: JwsSignatureAlgorithm = JwsSignatureAlgorithm::Hs256;
pub const DEFAULT_POOL_NB_TRIES: usize = 20;
pub const DEFAULT_POOL_WAIT_SEC: u64 = 5;
pub const DEFAULT_HTTP_FAIL_NB_RETRY: usize = 10;

21
man/en/acmed.toml.5

@ -200,6 +200,27 @@ ES384
Table of environment variables that will be accessible from hooks.
.It Ic hooks Ar array
Names of hooks that will be called during operations on the account storage file. The hooks are guaranteed to be called sequentially in the declaration order.
.It Ic external_account Ar table
Table containing the information required to bind the account to an external one. Possible fields and values are:
.Bl -tag
.It Ic identifier Ar string
ASCII string identifying the key.
.It Ic key Ar string
Private key encoded in base64url without padding.
.It Ic signature_algorithm Ar string
Name of the signature algorithm used to sign the external account binding message sent to the endpoint as defined in
.Em RFC 7518 .
Possible values are:
.Bl -dash -compact
.It
HS256
.Aq default
.It
HS384
.It
HS512
.El
.El
.El
.It Ic certificate
Array of table representing a certificate that will be requested to a CA.

Loading…
Cancel
Save