use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Arc;
use chrono::Duration;
use log::debug;
use rpki::{rrdp, uri};
use rpki::ca::idexchange::CaHandle;
use rpki::ca::provisioning::ResourceClassName;
use rpki::ca::publication::Base64;
use rpki::crypto::{DigestAlgorithm, KeyIdentifier};
use rpki::repository::crl::{Crl, TbsCertList};
use rpki::repository::manifest::{FileAndHash, Manifest, ManifestContent};
use rpki::repository::sigobj::SignedObjectBuilder;
use rpki::repository::x509::{Name, Serial, Time, Validity};
use serde::{Deserialize, Serialize};
use url::Url;
use crate::api::admin::{PublishedFile, RepositoryContact};
use crate::api::ca::{
CertInfo, IssuedCertificate, ObjectName, ReceivedCert, Revocation,
Revocations,
};
use crate::api::roa::RoaInfo;
use crate::commons::KrillResult;
use crate::commons::crypto::KrillSigner;
use crate::commons::error::Error;
use crate::commons::eventsourcing::PreSaveEventListener;
use crate::commons::storage::{Ident, KeyValueStore};
use crate::constants::CA_OBJECTS_NS;
use crate::config::IssuanceTimingConfig;
use super::aspa::{AspaInfo, AspaObjectsUpdates};
use super::bgpsec::{BgpSecCertInfo, BgpSecCertificateUpdates};
use super::certauth::CertAuth;
use super::child::ChildCertificateUpdates;
use super::events::CertAuthEvent;
use super::keys::CertifiedKey;
use super::roa::RoaUpdates;
#[derive(Debug)]
pub struct CaObjectsStore {
store: KeyValueStore,
signer: Arc<KrillSigner>,
issuance_timing: IssuanceTimingConfig,
}
impl CaObjectsStore {
pub fn create(
storage_uri: &Url,
issuance_timing: IssuanceTimingConfig,
signer: Arc<KrillSigner>,
) -> KrillResult<Self> {
let store = KeyValueStore::create(storage_uri, CA_OBJECTS_NS)?;
Ok(CaObjectsStore {
store,
signer,
issuance_timing,
})
}
}
impl PreSaveEventListener<CertAuth> for CaObjectsStore {
fn listen(
&self,
ca: &CertAuth,
events: &[CertAuthEvent],
) -> KrillResult<()> {
self.with_ca_objects(ca.handle(), |objects| {
let mut force_reissue = false;
for event in events {
match event {
CertAuthEvent::RoasUpdated {
resource_class_name,
updates,
} => {
objects.update_roas(resource_class_name, updates)?;
force_reissue = true;
}
CertAuthEvent::AspaObjectsUpdated {
resource_class_name,
updates,
} => {
objects.update_aspas(resource_class_name, updates)?;
force_reissue = true;
}
CertAuthEvent::BgpSecCertificatesUpdated {
resource_class_name,
updates,
} => {
objects.update_bgpsec_certs(
resource_class_name,
updates,
)?;
force_reissue = true;
}
CertAuthEvent::ChildCertificatesUpdated {
resource_class_name,
updates,
} => {
objects.update_certs(resource_class_name, updates)?;
force_reissue = true;
}
CertAuthEvent::KeyPendingToActive {
resource_class_name,
current_key,
} => {
objects.add_class(
resource_class_name,
current_key,
&self.issuance_timing,
&self.signer,
)?;
}
CertAuthEvent::KeyPendingToNew {
resource_class_name,
new_key,
} => {
objects.keyroll_stage(
resource_class_name,
new_key,
&self.issuance_timing,
&self.signer,
)?;
}
CertAuthEvent::KeyRollActivated {
resource_class_name,
..
} => {
objects.keyroll_activate(resource_class_name)?;
force_reissue = true;
}
CertAuthEvent::KeyRollFinished {
resource_class_name,
} => {
objects.keyroll_finish(resource_class_name)?;
}
CertAuthEvent::CertificateReceived {
resource_class_name,
rcvd_cert,
..
} => {
objects.update_received_cert(
resource_class_name,
rcvd_cert,
)?;
}
CertAuthEvent::ResourceClassRemoved {
resource_class_name,
..
} => {
objects.remove_class(resource_class_name);
force_reissue = true;
}
CertAuthEvent::RepoUpdated { contact } => {
objects.update_repo(contact);
force_reissue = true;
}
_ => {}
}
}
objects.re_issue(
force_reissue, &self.issuance_timing, &self.signer
)?;
Ok(())
})
}
}
impl CaObjectsStore {
fn key(ca: &CaHandle) -> Box<Ident> {
Ident::builder(
Ident::from_handle(ca).into_owned()
).finish_with_extension(
const { Ident::make("json") }
)
}
pub fn cas(&self) -> KrillResult<Vec<CaHandle>> {
Ok(
self.store.keys(None, ".json")?.iter().filter_map(|k| {
let name = k.as_str().strip_suffix(".json")?;
CaHandle::from_str(name).ok()
}).collect()
)
}
pub fn remove_ca(&self, ca: &CaHandle) -> KrillResult<()> {
let ca_key = Self::key(ca);
self.store.execute(None, |kv| {
if kv.has(None, &ca_key)? {
kv.delete(None, &ca_key)
} else {
Ok(())
}
}).map_err(Error::KeyValueError)
}
pub fn ca_objects(&self, ca: &CaHandle) -> KrillResult<CaObjects> {
match self.store.get(
None, &Self::key(ca)
).map_err(Error::KeyValueError)? {
None => Ok(CaObjects::new(ca.clone())),
Some(objects) => Ok(objects),
}
}
pub fn with_ca_objects<F, T>(
&self,
ca: &CaHandle,
op: F,
) -> KrillResult<T>
where
F: Fn(&mut CaObjects) -> KrillResult<T>,
{
self.store.execute(None, |kv| {
let key = Self::key(ca);
let mut objects: CaObjects = match kv.get(None, &key)? {
Some(value) => value,
None => CaObjects::new(ca.clone()),
};
match op(&mut objects) {
Err(e) => Ok(Err(e)),
Ok(t) => {
kv.store(None, &key, &objects)?;
Ok(Ok(t))
}
}
}).map_err(Error::KeyValueError)?
}
pub fn reissue_if_needed(
&self,
force: bool,
ca_handle: &CaHandle,
) -> KrillResult<bool> {
debug!("Re-issue for CA {ca_handle} using force: {force}");
self.with_ca_objects(ca_handle, |objects| {
objects.re_issue(
force,
&self.issuance_timing,
&self.signer,
)
})
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CaObjects {
ca: CaHandle,
repo: Option<RepositoryContact>,
classes: HashMap<ResourceClassName, ResourceClassObjects>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
deprecated_repos: Vec<DeprecatedRepository>,
}
impl CaObjects {
pub fn new(
ca: CaHandle,
) -> Self {
CaObjects {
ca,
repo: None,
classes: HashMap::new(),
deprecated_repos: Vec::new(),
}
}
pub fn from_parts(
ca: CaHandle,
repo: Option<RepositoryContact>,
classes: HashMap<ResourceClassName, ResourceClassObjects>,
deprecated_repos: Vec<DeprecatedRepository>,
) -> Self {
CaObjects {
ca,
repo,
classes,
deprecated_repos,
}
}
#[allow(clippy::mutable_key_type)]
pub fn repo_elements_map(
&self,
) -> HashMap<RepositoryContact, Vec<PublishedFile>> {
let mut res = HashMap::new();
if let Some(repo) = &self.repo {
res.insert(repo.clone(), vec![]);
for resource_class_objects in self.classes.values() {
resource_class_objects.add_elements(&mut res, repo);
}
}
res
}
pub fn all_publish_elements(&self) -> Vec<PublishedFile> {
let mut all_elements = vec![];
for elements in self.repo_elements_map().values_mut() {
all_elements.append(elements);
}
all_elements
}
pub fn deprecated_repos(
&self
) -> impl Iterator<Item = &DeprecatedRepository> + '_ {
self.deprecated_repos.iter()
}
pub fn deprecated_repo_remove(&mut self, to_remove: &RepositoryContact) {
self.deprecated_repos.retain(|current| current.contact() != to_remove)
}
pub fn deprecated_repo_inc_clean_attempts(
&mut self,
contact: &RepositoryContact,
) {
for current in self.deprecated_repos.iter_mut() {
if current.contact() == contact {
current.inc_clean_attempts()
}
}
}
}
impl CaObjects {
fn add_class(
&mut self,
class_name: &ResourceClassName,
key: &CertifiedKey,
timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<()> {
if self.classes.contains_key(class_name) {
return Err(Error::publishing("Duplicate resource class"))
}
self.classes.insert(
class_name.clone(),
ResourceClassObjects::create(key, timing, signer)?,
);
Ok(())
}
fn remove_class(&mut self, class_name: &ResourceClassName) {
let old_repo_opt = self.classes.get(class_name).and_then(|rco| {
rco.old_repo()
}).cloned();
self.classes.remove(class_name);
if let Some(old_repo) = old_repo_opt {
self.deprecate_repo_if_no_longer_used(old_repo);
}
}
fn get_class_mut(
&mut self,
rcn: &ResourceClassName,
) -> KrillResult<&mut ResourceClassObjects> {
self.classes.get_mut(rcn).ok_or_else(|| {
Error::publishing("Missing resource class")
})
}
fn keyroll_stage(
&mut self,
rcn: &ResourceClassName,
key: &CertifiedKey,
timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<()> {
self.get_class_mut(rcn)?.keyroll_stage(key, timing, signer)
}
fn keyroll_activate(
&mut self,
rcn: &ResourceClassName,
) -> KrillResult<()> {
self.get_class_mut(rcn)?.keyroll_activate()
}
fn keyroll_finish(&mut self, rcn: &ResourceClassName) -> KrillResult<()> {
let resource_class_objects = self.get_class_mut(rcn)?;
if let Some(old_repo) = resource_class_objects.keyroll_finish()? {
self.deprecate_repo_if_no_longer_used(old_repo);
}
Ok(())
}
fn update_roas(
&mut self,
rcn: &ResourceClassName,
roa_updates: &RoaUpdates,
) -> KrillResult<()> {
self.get_class_mut(rcn)
.map(|rco| rco.update_roas(roa_updates))
}
fn update_aspas(
&mut self,
rcn: &ResourceClassName,
updates: &AspaObjectsUpdates,
) -> KrillResult<()> {
self.get_class_mut(rcn).map(|rco| rco.update_aspas(updates))
}
fn update_bgpsec_certs(
&mut self,
rcn: &ResourceClassName,
updates: &BgpSecCertificateUpdates,
) -> KrillResult<()> {
self.get_class_mut(rcn).map(|rco| rco.update_bgpsec_certs(updates))
}
fn update_certs(
&mut self,
rcn: &ResourceClassName,
cert_updates: &ChildCertificateUpdates,
) -> KrillResult<()> {
self.get_class_mut(rcn).map(|rco| rco.update_certs(cert_updates))
}
fn update_received_cert(
&mut self,
rcn: &ResourceClassName,
cert: &ReceivedCert,
) -> KrillResult<()> {
self.get_class_mut(rcn)?.update_received_cert(cert)
}
fn re_issue(
&mut self,
force: bool,
timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<bool> {
let hours = timing.publish_hours_before_next();
let mut required = false;
for (_, resource_class_objects) in self.classes.iter_mut() {
if force || resource_class_objects.requires_re_issuance(hours) {
required = true;
resource_class_objects.reissue(timing, signer)?;
}
}
Ok(required)
}
fn update_repo(&mut self, repo: &RepositoryContact) {
if let Some(old) = &self.repo {
for resource_class_objects in self.classes.values_mut() {
resource_class_objects.set_old_repo(old);
}
}
self.repo = Some(repo.clone());
}
fn deprecate_repo_if_no_longer_used(
&mut self,
old_repo: RepositoryContact,
) {
if !self.has_old_repo(&old_repo) {
self.deprecated_repos
.push(DeprecatedRepository::new(old_repo, 0));
}
}
fn has_old_repo(&self, old_repo: &RepositoryContact) -> bool {
self.classes.values().any(|rco| rco.has_old_repo(old_repo))
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct DeprecatedRepository {
contact: RepositoryContact,
clean_attempts: usize,
}
impl DeprecatedRepository {
pub fn new(contact: RepositoryContact, clean_attempts: usize) -> Self {
DeprecatedRepository {
contact,
clean_attempts,
}
}
pub fn contact(&self) -> &RepositoryContact {
&self.contact
}
pub fn into_contact(self) -> RepositoryContact {
self.contact
}
pub fn clean_attempts(&self) -> usize {
self.clean_attempts
}
pub fn inc_clean_attempts(&mut self) {
self.clean_attempts += 1;
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ResourceClassObjects {
keys: ResourceClassKeyState,
}
impl ResourceClassObjects {
pub fn new(keys: ResourceClassKeyState) -> Self {
ResourceClassObjects { keys }
}
fn create(
key: &CertifiedKey,
timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<Self> {
let current_set = KeyObjectSet::create(key, timing, signer)?;
Ok(ResourceClassObjects {
keys: ResourceClassKeyState::Current(CurrentKeyState {
current_set,
}),
})
}
#[allow(clippy::mutable_key_type)]
fn add_elements(
&self,
map: &mut HashMap<RepositoryContact, Vec<PublishedFile>>,
dflt_repo: &RepositoryContact,
) {
match &self.keys {
ResourceClassKeyState::Current(state) => {
state.current_set.add_elements(map, dflt_repo)
}
ResourceClassKeyState::Staging(state) => {
state.current_set.add_elements(map, dflt_repo);
state.staging_set.add_elements(map, dflt_repo);
}
ResourceClassKeyState::Old(state) => {
state.current_set.add_elements(map, dflt_repo);
state.old_set.add_elements(map, dflt_repo);
}
}
}
fn keyroll_stage(
&mut self,
key: &CertifiedKey,
timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<()> {
let current_set = match &self.keys {
ResourceClassKeyState::Current(state) => {
state.current_set.clone()
}
_ => {
return Err(Error::publishing(
"published resource class in the wrong key state",
))
}
};
let staging_set = KeyObjectSet::create(key, timing, signer)?;
self.keys = ResourceClassKeyState::Staging(StagingKeyState {
staging_set,
current_set,
});
Ok(())
}
fn keyroll_activate(&mut self) -> KrillResult<()> {
self.keys = match &self.keys {
ResourceClassKeyState::Staging(state) => {
let old_set = state.current_set.retire()?;
let current_set = state.staging_set.clone();
ResourceClassKeyState::Old(OldKeyState {
current_set,
old_set,
})
}
_ => {
return Err(Error::publishing(
"published resource class in the wrong key state",
))
}
};
Ok(())
}
fn keyroll_finish(&mut self) -> KrillResult<Option<RepositoryContact>> {
match self.keys.clone() {
ResourceClassKeyState::Old(old) => {
let current_set = old.current_set;
self.keys = ResourceClassKeyState::current(current_set);
Ok(old.old_set.old_repo)
}
_ => Err(Error::publishing(
"published resource class in the wrong key state",
)),
}
}
fn update_received_cert(
&mut self,
updated_cert: &ReceivedCert,
) -> KrillResult<()> {
self.keys.update_received_cert(updated_cert)
}
fn update_roas(&mut self, roa_updates: &RoaUpdates) {
match &mut self.keys {
ResourceClassKeyState::Current(state) => {
state.current_set.update_roas(roa_updates)
}
ResourceClassKeyState::Staging(state) => {
state.current_set.update_roas(roa_updates)
}
ResourceClassKeyState::Old(state) => {
state.current_set.update_roas(roa_updates)
}
}
}
fn update_aspas(&mut self, updates: &AspaObjectsUpdates) {
match &mut self.keys {
ResourceClassKeyState::Current(state) => {
state.current_set.update_aspas(updates)
}
ResourceClassKeyState::Staging(state) => {
state.current_set.update_aspas(updates)
}
ResourceClassKeyState::Old(state) => {
state.current_set.update_aspas(updates)
}
}
}
fn update_bgpsec_certs(&mut self, updates: &BgpSecCertificateUpdates) {
match &mut self.keys {
ResourceClassKeyState::Current(state) => {
state.current_set.update_bgpsec_certs(updates)
}
ResourceClassKeyState::Staging(state) => {
state.current_set.update_bgpsec_certs(updates)
}
ResourceClassKeyState::Old(state) => {
state.current_set.update_bgpsec_certs(updates)
}
}
}
fn update_certs(&mut self, cert_updates: &ChildCertificateUpdates) {
match &mut self.keys {
ResourceClassKeyState::Current(state) => {
state.current_set.update_certs(cert_updates)
}
ResourceClassKeyState::Staging(state) => {
state.current_set.update_certs(cert_updates)
}
ResourceClassKeyState::Old(state) => {
state.current_set.update_certs(cert_updates)
}
}
}
fn requires_re_issuance(&self, hours: i64) -> bool {
match &self.keys {
ResourceClassKeyState::Current(state) => {
state.current_set.requires_reissuance(hours)
}
ResourceClassKeyState::Old(state) => {
state.old_set.requires_reissuance(hours)
|| state.current_set.requires_reissuance(hours)
}
ResourceClassKeyState::Staging(state) => {
state.staging_set.requires_reissuance(hours)
|| state.current_set.requires_reissuance(hours)
}
}
}
fn reissue(
&mut self,
timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<()> {
match &mut self.keys {
ResourceClassKeyState::Current(state) => {
state.current_set.reissue(timing, signer)
}
ResourceClassKeyState::Staging(state) => {
state.staging_set.reissue(timing, signer)?;
state.current_set.reissue(timing, signer)
}
ResourceClassKeyState::Old(state) => {
state.old_set.reissue(timing, signer)?;
state.current_set.reissue(timing, signer)
}
}
}
fn set_old_repo(&mut self, repo: &RepositoryContact) {
match &mut self.keys {
ResourceClassKeyState::Current(state) => {
state.current_set.set_old_repo(repo)
}
ResourceClassKeyState::Staging(state) => {
state.staging_set.set_old_repo(repo);
state.current_set.set_old_repo(repo);
}
ResourceClassKeyState::Old(state) => {
state.old_set.set_old_repo(repo);
state.current_set.set_old_repo(repo);
}
}
}
fn has_old_repo(&self, repo: &RepositoryContact) -> bool {
match &self.keys {
ResourceClassKeyState::Current(state) => {
state.current_set.old_repo() == Some(repo)
}
ResourceClassKeyState::Staging(state) => {
state.staging_set.old_repo() == Some(repo)
|| state.current_set.old_repo() == Some(repo)
}
ResourceClassKeyState::Old(state) => {
state.old_set.old_repo() == Some(repo)
|| state.current_set.old_repo() == Some(repo)
}
}
}
fn old_repo(&self) -> Option<&RepositoryContact> {
match &self.keys {
ResourceClassKeyState::Current(state) => {
state.current_set.old_repo()
}
ResourceClassKeyState::Staging(state) => state
.staging_set
.old_repo()
.or_else(|| state.current_set.old_repo()),
ResourceClassKeyState::Old(state) => state
.old_set
.old_repo()
.or_else(|| state.current_set.old_repo()),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResourceClassKeyState {
Current(CurrentKeyState),
Staging(StagingKeyState),
Old(OldKeyState),
}
impl ResourceClassKeyState {
pub fn current(current_set: KeyObjectSet) -> Self {
ResourceClassKeyState::Current(CurrentKeyState { current_set })
}
pub fn staging(
staging_set: KeyObjectSet,
current_set: KeyObjectSet,
) -> Self {
ResourceClassKeyState::Staging(StagingKeyState {
staging_set,
current_set,
})
}
pub fn old(current_set: KeyObjectSet, old_set: KeyObjectSet) -> Self {
ResourceClassKeyState::Old(OldKeyState {
current_set,
old_set,
})
}
fn update_received_cert(
&mut self,
cert: &ReceivedCert,
) -> KrillResult<()> {
match self {
ResourceClassKeyState::Current(state) => {
state.current_set.update_signing_cert(cert)
}
ResourceClassKeyState::Staging(state) => {
if state.staging_set.update_signing_cert(cert).is_ok() {
Ok(())
} else {
state.current_set.update_signing_cert(cert)
}
}
ResourceClassKeyState::Old(state) => {
if state.old_set.update_signing_cert(cert).is_ok() {
Ok(())
} else {
state.current_set.update_signing_cert(cert)
}
}
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CurrentKeyState {
current_set: KeyObjectSet,
}
impl CurrentKeyState {
pub fn new(current_set: KeyObjectSet) -> Self {
CurrentKeyState { current_set }
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct StagingKeyState {
staging_set: KeyObjectSet,
current_set: KeyObjectSet,
}
impl StagingKeyState {
pub fn new(staging_set: KeyObjectSet, current_set: KeyObjectSet) -> Self {
StagingKeyState {
staging_set,
current_set,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct OldKeyState {
current_set: KeyObjectSet,
old_set: KeyObjectSet,
}
impl OldKeyState {
pub fn new(current_set: KeyObjectSet, old_set: KeyObjectSet) -> Self {
OldKeyState {
current_set,
old_set,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct KeyObjectSet {
signing_cert: ReceivedCert,
revision: ObjectSetRevision,
revocations: Revocations,
manifest: PublishedManifest,
crl: PublishedCrl,
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
published_objects: HashMap<ObjectName, PublishedObject>,
#[serde(skip_serializing_if = "Option::is_none")]
old_repo: Option<RepositoryContact>,
}
impl KeyObjectSet {
pub fn new(
signing_cert: ReceivedCert,
revision: ObjectSetRevision,
revocations: Revocations,
manifest: PublishedManifest,
crl: PublishedCrl,
published_objects: HashMap<ObjectName, PublishedObject>,
old_repo: Option<RepositoryContact>,
) -> Self {
KeyObjectSet {
signing_cert,
revision,
revocations,
manifest,
crl,
published_objects,
old_repo,
}
}
fn create(
key: &CertifiedKey,
timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<Self> {
let signing_cert = key.incoming_cert().clone();
let signing_key = signing_cert.key_identifier();
let issuer = signing_cert.subject.clone();
let revocations = Revocations::default();
let revision = ObjectSetRevision::create(timing.publish_next());
let published_objects = HashMap::new();
let crl = PublishedCrl::build(
signing_key,
issuer,
&revocations,
revision,
signer,
)?;
let manifest = ManifestBuilder::new(revision)
.with_objects(&crl, &published_objects)
.build_new_mft(&signing_cert, signer)
.map(|m| m.into())?;
Ok(KeyObjectSet {
signing_cert,
revision,
revocations,
manifest,
crl,
published_objects,
old_repo: None,
})
}
#[allow(clippy::mutable_key_type)]
fn add_elements(
&self,
map: &mut HashMap<RepositoryContact, Vec<PublishedFile>>,
dflt_repo: &RepositoryContact,
) {
let repo = self.old_repo.as_ref().unwrap_or(dflt_repo);
let crl_uri = self.signing_cert.crl_uri();
let mft_uri = self.signing_cert.mft_uri();
let elements = map.entry(repo.clone()).or_default();
elements.push(self.manifest.published_file(mft_uri));
elements.push(self.crl.published_file(crl_uri));
for (name, object) in &self.published_objects {
elements.push(PublishedFile {
uri: self.signing_cert.uri_for_name(name),
base64: object.base64.clone(),
});
}
}
pub fn requires_reissuance(&self, hours: i64) -> bool {
Time::now() > self.next_update() - Duration::hours(hours)
}
pub fn next_update(&self) -> Time {
self.revision.next_update
}
fn update_signing_cert(
&mut self,
cert: &ReceivedCert,
) -> KrillResult<()> {
if self.signing_cert.key_identifier() == cert.key_identifier() {
self.signing_cert = cert.clone();
Ok(())
}
else {
Err(Error::PublishingObjects(format!(
"received new cert for unknown key id: {}",
cert.key_identifier()
)))
}
}
fn update_roas(&mut self, roa_updates: &RoaUpdates) {
for (name, roa_info) in roa_updates.added_roas() {
let published_object = PublishedObject::for_roa(
name.clone(), roa_info
);
if let Some(old) = self.published_objects.insert(
name, published_object
) {
self.revocations.add(old.revoke());
}
}
for name in roa_updates.removed_roas() {
if let Some(old) = self.published_objects.remove(&name) {
self.revocations.add(old.revoke());
}
}
}
fn update_aspas(&mut self, updates: &AspaObjectsUpdates) {
for aspa_info in updates.updated() {
let name = ObjectName::aspa_from_customer(aspa_info.customer());
let published_object =
PublishedObject::for_aspa(name.clone(), aspa_info);
if let Some(old) =
self.published_objects.insert(name, published_object)
{
self.revocations.add(old.revoke());
}
}
for removed in updates.removed() {
let name = ObjectName::aspa_from_customer(*removed);
if let Some(old) = self.published_objects.remove(&name) {
self.revocations.add(old.revoke());
}
}
}
fn update_bgpsec_certs(&mut self, updates: &BgpSecCertificateUpdates) {
for bgpsec_cert_info in updates.updated() {
let published_object =
PublishedObject::for_bgpsec_cert_info(bgpsec_cert_info);
if let Some(old) = self
.published_objects
.insert(bgpsec_cert_info.name(), published_object)
{
self.revocations.add(old.revoke());
}
}
for removed in updates.removed() {
let name = ObjectName::from(removed);
if let Some(old) = self.published_objects.remove(&name) {
self.revocations.add(old.revoke());
}
}
}
fn update_certs(&mut self, cert_updates: &ChildCertificateUpdates) {
for removed in &cert_updates.removed {
let name = ObjectName::from_key(removed, "cer");
if let Some(old) = self.published_objects.remove(&name) {
self.revocations.add(old.revoke());
}
}
for issued in &cert_updates.issued {
let published_object = PublishedObject::for_cert_info(issued);
if let Some(old) = self
.published_objects
.insert(issued.name.clone(), published_object)
{
self.revocations.add(old.revoke());
}
}
for cert in &cert_updates.unsuspended {
let published_object = PublishedObject::for_cert_info(cert);
self
.published_objects
.insert(cert.name.clone(), published_object);
}
for suspended in &cert_updates.suspended {
if let Some(old) = self.published_objects.remove(&suspended.name)
{
self.revocations.add(old.revoke());
}
}
}
fn reissue(
&mut self,
timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<()> {
debug!(
"Will re-issue for key: {}. Current revision: {} and next \
update: {}",
self.signing_cert.key_identifier(),
self.revision.number,
self.revision.next_update.to_rfc3339()
);
self.revision.next(timing.publish_next(), None);
self.revocations.remove_expired();
let signing_key = self.signing_cert.key_identifier();
let issuer = self.signing_cert.subject.clone();
self.crl = PublishedCrl::build(
signing_key,
issuer,
&self.revocations,
self.revision,
signer,
)?;
self.manifest = ManifestBuilder::new(self.revision)
.with_objects(&self.crl, &self.published_objects)
.build_new_mft(&self.signing_cert, signer)
.map(|m| m.into())?;
Ok(())
}
fn retire(&self) -> KrillResult<KeyObjectSet> {
let mut revocations = self.revocations.clone();
for object in self.published_objects.values() {
revocations.add(object.revoke());
}
revocations.remove_expired();
let retired_set = KeyObjectSet {
signing_cert: self.signing_cert.clone(),
revision: self.revision,
revocations,
manifest: self.manifest.clone(),
crl: self.crl.clone(),
published_objects: HashMap::new(),
old_repo: self.old_repo.clone(),
};
Ok(retired_set)
}
fn set_old_repo(&mut self, repo: &RepositoryContact) {
self.old_repo = Some(repo.clone())
}
fn old_repo(&self) -> Option<&RepositoryContact> {
self.old_repo.as_ref()
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ObjectSetRevision {
number: u64,
this_update: Time,
next_update: Time,
}
impl ObjectSetRevision {
pub fn new(number: u64, this_update: Time, next_update: Time) -> Self {
ObjectSetRevision {
number,
this_update,
next_update,
}
}
fn create(next_update: Time) -> Self {
ObjectSetRevision {
number: 1,
this_update: Time::five_minutes_ago(),
next_update,
}
}
pub fn number(&self) -> u64 {
self.number
}
pub fn this_update(&self) -> Time {
self.this_update
}
pub fn next_update(&self) -> Time {
self.next_update
}
pub fn next(
&mut self,
next_update: Time,
mft_number_override: Option<u64>,
) {
if let Some(forced_next) = mft_number_override {
self.number = forced_next;
} else {
self.number += 1;
}
self.this_update = Time::five_minutes_ago();
self.next_update = next_update;
}
}
pub type PublishedCert = IssuedCertificate;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PublishedItem<T> {
name: ObjectName,
base64: Base64,
hash: rrdp::Hash,
serial: Serial,
expires: Time,
marker: std::marker::PhantomData<T>,
}
impl<T> PublishedItem<T> {
pub fn new(
name: ObjectName,
base64: Base64,
serial: Serial,
expires: Time,
) -> Self {
let hash = base64.to_hash();
PublishedItem {
name,
base64,
hash,
serial,
expires,
marker: std::marker::PhantomData,
}
}
pub fn published_file(&self, uri: uri::Rsync) -> PublishedFile {
PublishedFile { uri, base64: self.base64.clone() }
}
pub fn revoke(&self) -> Revocation {
Revocation::new(self.serial, self.expires)
}
}
pub type PublishedManifest = PublishedItem<PublishedItemManifest>;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PublishedItemManifest;
impl From<Manifest> for PublishedManifest {
fn from(mft: Manifest) -> Self {
PublishedItem::new(
ObjectName::from(&mft),
Base64::from(&mft),
mft.cert().serial_number(),
mft.next_update(),
)
}
}
pub type PublishedCrl = PublishedItem<PublishedItemCrl>;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PublishedItemCrl;
impl PublishedCrl {
pub fn build(
aki: KeyIdentifier,
issuer: Name,
revocations: &Revocations,
revision: ObjectSetRevision,
signer: &KrillSigner,
) -> KrillResult<Self> {
let serial_number = Serial::from(revision.number);
let crl = TbsCertList::new(
Default::default(),
issuer,
revision.this_update,
revision.next_update,
revocations.to_crl_entries(),
aki,
serial_number,
);
let crl = signer.sign_crl(crl, &aki)?;
Ok(crl.into())
}
}
impl From<Crl> for PublishedCrl {
fn from(crl: Crl) -> Self {
PublishedItem::new(
ObjectName::from(&crl),
Base64::from(&crl),
crl.crl_number(), crl.next_update(),
)
}
}
pub type PublishedObject = PublishedItem<PublishedItemOther>;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PublishedItemOther;
impl PublishedObject {
pub fn for_roa(name: ObjectName, roa_info: &RoaInfo) -> Self {
PublishedObject::new(
name,
roa_info.base64.clone(),
roa_info.serial,
roa_info.expires(),
)
}
pub fn for_aspa(name: ObjectName, aspa_info: &AspaInfo) -> Self {
PublishedObject::new(
name,
aspa_info.base64.clone(),
aspa_info.serial,
aspa_info.expires(),
)
}
pub fn for_cert_info<T>(cert: &CertInfo<T>) -> Self {
PublishedObject::new(
cert.name.clone(),
cert.base64.clone(),
cert.serial,
cert.expires(),
)
}
pub fn for_bgpsec_cert_info(cert: &BgpSecCertInfo) -> Self {
PublishedObject::new(
cert.name(),
cert.base64.clone(),
cert.serial,
cert.expires,
)
}
}
#[allow(clippy::mutable_key_type)]
pub struct ManifestBuilder {
revision: ObjectSetRevision,
entries: HashMap<ObjectName, rrdp::Hash>,
}
impl ManifestBuilder {
pub fn new(revision: ObjectSetRevision) -> Self {
ManifestBuilder {
revision,
entries: HashMap::new(),
}
}
#[allow(clippy::mutable_key_type)]
pub fn with_objects(
mut self,
crl: &PublishedCrl,
published_objects: &HashMap<ObjectName, PublishedObject>,
) -> Self {
self.entries.insert(crl.name.clone(), crl.hash);
for (name, object) in published_objects {
self.entries.insert(name.clone(), object.hash);
}
self
}
pub fn build_new_mft(
self,
signing_cert: &ReceivedCert,
signer: &KrillSigner,
) -> KrillResult<Manifest> {
let mft_uri = signing_cert.mft_uri();
let crl_uri = signing_cert.crl_uri();
let aia = &signing_cert.uri;
let aki = signing_cert.key_identifier();
let serial_number = Serial::from(self.revision.number);
let entries =
self.entries.iter().map(|(k, v)| FileAndHash::new(k, v));
let manifest: Manifest = {
let mft_content = ManifestContent::new(
serial_number,
self.revision.this_update,
self.revision.next_update,
DigestAlgorithm::default(),
entries,
);
let mut object_builder = SignedObjectBuilder::new(
signer.random_serial()?,
Validity::new(
self.revision.this_update,
self.revision.next_update,
),
crl_uri,
aia.clone(),
mft_uri,
);
object_builder.set_issuer(Some(signing_cert.subject.clone()));
object_builder.set_signing_time(Time::now());
signer.sign_manifest(mft_content, object_builder, &aki)?
};
Ok(manifest)
}
}