You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

322 lines
8.4 KiB

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;
use acme_common::crypto::{gen_keypair, HashFunction, JwsSignatureAlgorithm, KeyPair, KeyType};
use acme_common::error::Error;
use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
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,
}
impl FromStr for AccountContactType {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s.to_lowercase().as_str() {
"mailfrom" => Ok(AccountContactType::Mailfrom),
_ => Err(format!("{}: unknown contact type.", s).into()),
}
}
}
impl fmt::Display for AccountContactType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
AccountContactType::Mailfrom => "mailfrom",
};
write!(f, "{}", s)
}
}
#[derive(Clone, Debug)]
pub struct AccountKey {
pub creation_date: SystemTime,
pub key: KeyPair,
pub signature_algorithm: JwsSignatureAlgorithm,
}
impl AccountKey {
fn new(key_type: KeyType, signature_algorithm: JwsSignatureAlgorithm) -> Result<Self, Error> {
Ok(AccountKey {
creation_date: SystemTime::now(),
key: gen_keypair(key_type)?,
signature_algorithm,
})
}
}
#[derive(Clone, Debug, Hash)]
pub struct AccountEndpoint {
pub creation_date: SystemTime,
pub account_url: String,
pub orders_url: String,
pub key_hash: Vec<u8>,
pub contacts_hash: Vec<u8>,
pub external_account_hash: Vec<u8>,
}
impl AccountEndpoint {
pub fn new() -> Self {
AccountEndpoint {
creation_date: SystemTime::UNIX_EPOCH,
account_url: String::new(),
orders_url: String::new(),
key_hash: Vec::new(),
contacts_hash: Vec::new(),
external_account_hash: Vec::new(),
}
}
}
#[derive(Clone, Debug)]
pub struct Account {
pub name: String,
pub endpoints: HashMap<String, AccountEndpoint>,
pub contacts: Vec<contact::AccountContact>,
pub current_key: AccountKey,
pub past_keys: Vec<AccountKey>,
pub file_manager: FileManager,
pub external_account: Option<ExternalAccount>,
}
impl HasLogger for Account {
fn warn(&self, msg: &str) {
log::warn!("account \"{}\": {}", &self.name, msg);
}
fn info(&self, msg: &str) {
log::info!("account \"{}\": {}", &self.name, msg);
}
fn debug(&self, msg: &str) {
log::debug!("account \"{}\": {}", &self.name, msg);
}
fn trace(&self, msg: &str) {
log::trace!("account \"{}\": {}", &self.name, msg);
}
}
impl Account {
pub fn get_endpoint_mut(&mut self, endpoint_name: &str) -> Result<&mut AccountEndpoint, Error> {
match self.endpoints.get_mut(endpoint_name) {
Some(ep) => Ok(ep),
None => {
let msg = format!(
"\"{}\": unknown endpoint for account \"{}\"",
endpoint_name, self.name
);
Err(msg.into())
}
}
}
pub fn get_endpoint(&self, endpoint_name: &str) -> Result<&AccountEndpoint, Error> {
match self.endpoints.get(endpoint_name) {
Some(ep) => Ok(ep),
None => {
let msg = format!(
"\"{}\": unknown endpoint for account \"{}\"",
endpoint_name, self.name
);
Err(msg.into())
}
}
}
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,
contacts: &[(String, String)],
key_type: &Option<String>,
signature_algorithm: &Option<String>,
external_account: &Option<ExternalAccount>,
) -> Result<Self, Error> {
let contacts = contacts
.iter()
.map(|(k, v)| contact::AccountContact::new(k, v))
.collect::<Result<Vec<contact::AccountContact>, Error>>()?;
let key_type = match key_type {
Some(kt) => kt.parse()?,
None => crate::DEFAULT_ACCOUNT_KEY_TYPE,
};
let signature_algorithm = match signature_algorithm {
Some(sa) => sa.parse()?,
None => key_type.get_default_signature_alg(),
};
key_type.check_alg_compatibility(&signature_algorithm)?;
let account = match storage::fetch(file_manager, name)? {
Some(mut a) => {
a.update_keys(key_type, signature_algorithm)?;
a.contacts = contacts;
a.external_account = external_account.to_owned();
a
}
None => {
let account = Account {
name: name.to_string(),
endpoints: HashMap::new(),
contacts,
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
}
};
Ok(account)
}
pub fn add_endpoint_name(&mut self, endpoint_name: &str) {
self.endpoints
.entry(endpoint_name.to_string())
.or_insert_with(AccountEndpoint::new);
}
pub fn synchronize(&mut self, endpoint: &mut Endpoint) -> Result<(), Error> {
let acc_ep = self.get_endpoint(&endpoint.name)?;
if !acc_ep.account_url.is_empty() {
if let Some(ec) = &self.external_account {
let external_account_hash = hash_external_account(ec);
if external_account_hash != acc_ep.external_account_hash {
let msg = format!(
"external account changed on endpoint \"{}\"",
&endpoint.name
);
self.info(&msg);
register_account(endpoint, self)?;
return Ok(());
}
}
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, self)?;
}
if key_changed {
update_account_key(endpoint, self)?;
}
} else {
register_account(endpoint, self)?;
}
Ok(())
}
pub fn register(&mut self, endpoint: &mut Endpoint) -> Result<(), Error> {
register_account(endpoint, self)
}
pub fn save(&self) -> Result<(), Error> {
storage::save(&self.file_manager, self)
}
pub fn set_account_url(&mut self, endpoint_name: &str, account_url: &str) -> Result<(), Error> {
let mut ep = self.get_endpoint_mut(endpoint_name)?;
ep.account_url = account_url.to_string();
Ok(())
}
pub fn set_orders_url(&mut self, endpoint_name: &str, orders_url: &str) -> Result<(), Error> {
let mut ep = self.get_endpoint_mut(endpoint_name)?;
ep.orders_url = orders_url.to_string();
Ok(())
}
pub fn update_key_hash(&mut self, endpoint_name: &str) -> Result<(), Error> {
let key = self.current_key.clone();
let mut ep = self.get_endpoint_mut(endpoint_name)?;
ep.key_hash = hash_key(&key)?;
Ok(())
}
pub fn update_contacts_hash(&mut self, endpoint_name: &str) -> Result<(), Error> {
let ct = self.contacts.clone();
let mut ep = self.get_endpoint_mut(endpoint_name)?;
ep.contacts_hash = hash_contacts(&ct);
Ok(())
}
pub fn update_external_account_hash(&mut self, endpoint_name: &str) -> Result<(), Error> {
if let Some(ec) = &self.external_account {
let ec = ec.clone();
let mut ep = self.get_endpoint_mut(endpoint_name)?;
ep.external_account_hash = hash_external_account(&ec);
}
Ok(())
}
fn update_keys(
&mut self,
key_type: KeyType,
signature_algorithm: JwsSignatureAlgorithm,
) -> Result<(), Error> {
if self.current_key.key.key_type != key_type
|| self.current_key.signature_algorithm != signature_algorithm
{
self.debug("account key has been changed in the configuration, creating a new one...");
self.past_keys.push(self.current_key.to_owned());
self.current_key = AccountKey::new(key_type, signature_algorithm)?;
self.save()?;
let msg = format!(
"new {} account key created, using {} as signing algorithm",
key_type, signature_algorithm
);
self.info(&msg);
} else {
self.trace("account key is up to date");
}
Ok(())
}
}
fn hash_contacts(contacts: &[contact::AccountContact]) -> Vec<u8> {
let msg = contacts
.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>()
.join("")
.into_bytes();
HashFunction::Sha256.hash(&msg)
}
fn hash_key(key: &AccountKey) -> Result<Vec<u8>, Error> {
let pem = key.key.public_key_to_pem()?;
Ok(HashFunction::Sha256.hash(&pem))
}
fn hash_external_account(ec: &ExternalAccount) -> Vec<u8> {
let mut msg = ec.key.clone();
msg.extend(ec.identifier.as_bytes());
HashFunction::Sha256.hash(&msg)
}