use std::fmt;
use std::{collections::HashMap, fmt::Debug};
use rpki::{uri, rrdp};
use rpki::ca::idexchange::CaHandle;
use rpki::ca::publication::Base64;
use rpki::repository::aspa::{Aspa, AspaBuilder};
use rpki::repository::resources::ResourceSet;
use rpki::repository::sigobj::SignedObjectBuilder;
use rpki::repository::x509::{Serial, Time, Validity};
use serde::{Deserialize, Serialize};
use crate::api::aspa::{
AspaDefinition, AspaDefinitionUpdates, AspaProvidersUpdate, CustomerAsn
};
use crate::api::ca::ObjectName;
use crate::commons::KrillResult;
use crate::commons::crypto::KrillSigner;
use crate::commons::error::Error;
use crate::config::{Config, IssuanceTimingConfig};
use super::events::CertAuthEvent;
use super::keys::CertifiedKey;
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct AspaDefinitions {
attestations: HashMap<CustomerAsn, AspaDefinition>,
}
impl AspaDefinitions {
pub fn add_or_replace(&mut self, aspa_def: AspaDefinition) {
self.attestations.insert(aspa_def.customer, aspa_def);
}
pub fn remove(&mut self, customer: CustomerAsn) {
self.attestations.remove(&customer);
}
pub fn apply_update(
&mut self,
customer: CustomerAsn,
update: &AspaProvidersUpdate,
) {
if let Some(current) = self.attestations.get_mut(&customer) {
current.apply_update(update);
if current.providers.is_empty() {
self.attestations.remove(&customer);
}
}
else {
let mut def = AspaDefinition { customer, providers: vec![] };
def.apply_update(update);
self.attestations.insert(customer, def);
}
}
pub fn iter(&self) -> impl Iterator<Item = &AspaDefinition> {
self.attestations.values()
}
pub fn process_updates(
&self,
handle: &CaHandle,
all_resources: &ResourceSet,
updates: AspaDefinitionUpdates,
) -> KrillResult<(Self, Vec<CertAuthEvent>)> {
let mut events = Vec::new();
let mut all_aspas = self.clone();
for customer in updates.remove {
if !all_aspas.has(customer) {
return Err(Error::AspaCustomerUnknown(
handle.clone(),
customer,
));
}
events.push(CertAuthEvent::AspaConfigRemoved { customer });
all_aspas.remove(customer);
}
for aspa_config in updates.add_or_replace {
let customer = aspa_config.customer;
if aspa_config.providers.is_empty() {
return Err(Error::AspaProvidersEmpty(
handle.clone(),
customer,
));
}
if aspa_config.customer_used_as_provider() {
return Err(Error::AspaCustomerAsProvider(
handle.clone(),
customer,
));
}
if aspa_config.contains_duplicate_providers() {
return Err(Error::AspaProvidersDuplicates(
handle.clone(),
customer,
));
}
if !all_resources.contains_asn(customer) {
return Err(Error::AspaCustomerAsNotEntitled(
handle.clone(),
customer,
));
}
all_aspas.add_or_replace(aspa_config.clone());
match self.get(customer) {
None => {
events.push(
CertAuthEvent::AspaConfigAdded { aspa_config }
)
}
Some(existing) => {
let added = aspa_config
.providers
.iter()
.filter(|new_provider| {
!existing.providers.contains(new_provider)
})
.copied()
.collect();
let removed = existing
.providers
.iter()
.filter(|existing| {
!aspa_config.providers.contains(existing)
})
.copied()
.collect();
let update = AspaProvidersUpdate { added, removed };
if !update.is_empty() {
events.push(CertAuthEvent::AspaConfigUpdated {
customer, update
})
}
}
}
}
Ok((all_aspas, events))
}
}
impl AspaDefinitions {
pub fn get(&self, customer: CustomerAsn) -> Option<&AspaDefinition> {
self.attestations.get(&customer)
}
pub fn has(&self, customer: CustomerAsn) -> bool {
self.attestations.contains_key(&customer)
}
pub fn is_empty(&self) -> bool {
self.attestations.is_empty()
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct AspaObjects(HashMap<CustomerAsn, AspaInfo>);
impl AspaObjects {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn create_updates(
&self,
all_aspa_defs: &AspaDefinitions,
certified_key: &CertifiedKey,
config: &Config,
signer: &KrillSigner,
) -> KrillResult<AspaObjectsUpdates> {
let mut object_updates = AspaObjectsUpdates::default();
let resources = &certified_key.incoming_cert().resources;
for relevant_aspa in all_aspa_defs
.iter()
.filter(|aspa| resources.contains_asn(aspa.customer))
{
let need_to_issue = self
.0
.get(&relevant_aspa.customer)
.map(|existing| existing.definition != *relevant_aspa)
.unwrap_or(true);
if need_to_issue {
let aspa_info = self.make_aspa(
relevant_aspa.clone(),
certified_key,
&config.issuance_timing,
signer,
)?;
object_updates.updated.push(aspa_info);
}
}
for &customer in self.0.keys() {
if !all_aspa_defs.has(customer)
|| !resources.contains_asn(customer)
{
object_updates.removed.push(customer);
}
}
Ok(object_updates)
}
pub fn create_renewal(
&self,
certified_key: &CertifiedKey,
renew_threshold: Option<Time>,
issuance_timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<AspaObjectsUpdates> {
let mut updates = AspaObjectsUpdates::default();
for aspa in self.0.values() {
let renew = renew_threshold
.map(|threshold| aspa.expires() < threshold)
.unwrap_or(true);
if renew {
let aspa_definition = aspa.definition.clone();
let new_aspa = self.make_aspa(
aspa_definition,
certified_key,
issuance_timing,
signer,
)?;
updates.updated.push(new_aspa);
}
}
Ok(updates)
}
fn make_aspa(
&self,
aspa_def: AspaDefinition,
certified_key: &CertifiedKey,
issuance_timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<AspaInfo> {
let name = ObjectName::from(&aspa_def);
let aspa_builder = AspaBuilder::new(
aspa_def.customer,
aspa_def.providers.clone(),
).map_err(|e| {
Error::Custom(format!("Cannot use aspa config: {e}"))
})?;
let object_builder = {
let mut object_builder = SignedObjectBuilder::new(
signer.random_serial()?,
issuance_timing.new_aspa_validity(),
certified_key.incoming_cert().crl_uri(),
certified_key.incoming_cert().uri.clone(),
certified_key.incoming_cert().uri_for_name(&name),
);
object_builder.set_issuer(
Some(certified_key.incoming_cert().subject.clone()));
object_builder.set_signing_time(Time::now());
object_builder
};
let aspa = signer.sign_aspa(
aspa_builder,
object_builder,
&certified_key.key_id(),
)?;
Ok(AspaInfo::new(aspa_def, aspa))
}
pub fn apply_updates(&mut self, updates: AspaObjectsUpdates) {
for aspa_info in updates.updated {
self.0.insert(aspa_info.customer(), aspa_info);
}
for customer in updates.removed {
self.0.remove(&customer);
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AspaInfo {
pub definition: AspaDefinition,
pub validity: Validity,
pub serial: Serial,
pub uri: uri::Rsync,
pub base64: Base64,
pub hash: rrdp::Hash,
}
impl AspaInfo {
pub fn new(definition: AspaDefinition, aspa: Aspa) -> Self {
let validity = aspa.cert().validity();
let serial = aspa.cert().serial_number();
let uri = aspa.cert().signed_object().unwrap().clone();
let base64 = Base64::from(&aspa);
let hash = base64.to_hash();
AspaInfo {
definition,
validity,
serial,
uri,
base64,
hash,
}
}
pub fn customer(&self) -> CustomerAsn {
self.definition.customer
}
pub fn expires(&self) -> Time {
self.validity.not_after()
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct AspaObjectsUpdates {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
updated: Vec<AspaInfo>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
removed: Vec<CustomerAsn>,
}
impl AspaObjectsUpdates {
pub fn is_empty(&self) -> bool {
self.updated.is_empty() && self.removed.is_empty()
}
pub fn updated(&self) -> &[AspaInfo] {
&self.updated
}
pub fn removed(&self) -> &[CustomerAsn] {
&self.removed
}
}
impl fmt::Display for AspaObjectsUpdates {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if !self.updated.is_empty() {
write!(f, " updated:")?;
for upd in &self.updated {
write!(
f, " {}",
ObjectName::aspa_from_customer(upd.customer())
)?;
}
}
if !self.removed.is_empty() {
write!(f, " removed:")?;
for rem in &self.removed {
write!(f,
" {}",
ObjectName::aspa_from_customer(*rem)
)?;
}
}
Ok(())
}
}