Files
NLnetLabs-krill/src/server/ca/publishing.rs
T
Martin HoffmannandGitHub 7b66408dc6 Mark types that are used for storing state. (#1286)
This PR sticks a warning on all types that are used when serializing
Krill’s state. This hopefully will serve as a reminder that they cannot
be changed without considering migrations.
2025-06-12 14:15:12 +02:00

1757 lines
55 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Management of objects published by a CA.
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::{Key, KeyValueStore, Scope, Segment};
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;
//------------ CaObjectsStore ----------------------------------------------
/// The component storing the latest objects for each CA.
///
/// By using a stateful store for this purpose we can generate manifests and
/// CRLs outside of the normal event-sourcing framework used to track the
/// history and state of CAs. I.e., we treat the frequent republish cycle as
/// something that does not intrinsically modify the CA itself.
///
/// In earlier generations of Krill the simple republish operation to generate
/// new manifests and CRLs was done through the event sourcing framework.
/// However, this led to excessive use of disk space, makes the history more
/// difficult to inspect, and causes issues with regards to replaying CA state
/// from scratch.
///
/// # Key-value store usage
///
/// The CA objects store uses the key-value store directly. It uses the
/// namespace defined by [`CA_OBJECTS_NS`], currently `"ca_objects"`. For
/// each CA, it keeps a single value under the single-element scope of the
/// CA’s handle suffixed by `.json`.
#[derive(Debug)]
pub struct CaObjectsStore {
/// The key-value store where objects are stored.
store: KeyValueStore,
/// The signer used when generate objects.
signer: Arc<KrillSigner>,
/// Configuration for timing of object creation.
issuance_timing: IssuanceTimingConfig,
}
impl CaObjectsStore {
/// Creates a new CA objects store using the given configuration.
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,
})
}
}
/// React to any events on a CA that cause the set of object to change.
impl PreSaveEventListener<CertAuth> for CaObjectsStore {
fn listen(
&self,
ca: &CertAuth,
events: &[CertAuthEvent],
) -> KrillResult<()> {
// Note that the `CertAuth` which is passed in has already been
// updated with the state changes contained in the event.
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,
)?;
// this in itself constitutes no need to force
// re-issuance if the new
// certificate triggered that the set of objects
// changed, e.g. because a ROA
// became overclaiming, then we would see another
// event for that which *will* result in forcing
// re-issuance.
}
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 {
/// Returns the key for the given CA to be used in the store.
fn key(ca: &CaHandle) -> Key {
// CA handles should always be a valid segment
Key::new_global(
Segment::parse_lossy(&format!("{}.json", ca))
)
}
/// Returns all CA handles present in the object store.
pub fn cas(&self) -> KrillResult<Vec<CaHandle>> {
Ok(
self.store.keys(&Scope::global(), ".json")?.iter().flat_map(|k| {
// Only add entries for which the first part can be parsed as
// a handle
let mut res = None;
if let Some(name) = k.name().as_str().strip_suffix(".json") {
if let Ok(handle) = CaHandle::from_str(name) {
res = Some(handle)
}
}
res
}).collect()
)
}
/// Removes a CA from the store.
pub fn remove_ca(&self, ca: &CaHandle) -> KrillResult<()> {
let ca_key = Self::key(ca);
self.store.execute(&Scope::global(), |kv| {
if kv.has(&ca_key)? {
kv.delete(&ca_key)
} else {
Ok(())
}
}).map_err(Error::KeyValueError)
}
/// Loads the CA objects for this CA.
///
/// If the CA isn’t present in the store yet, creates a new empty
/// CA objects value and returns it.
pub fn ca_objects(&self, ca: &CaHandle) -> KrillResult<CaObjects> {
let key = Self::key(ca);
match self.store.get(&key).map_err(Error::KeyValueError)? {
None => Ok(CaObjects::new(ca.clone())),
Some(objects) => Ok(objects),
}
}
/// Performs an action on the CA objects for a CA.
///
/// If the CA did not have any CA objects yet, one will be created. The
/// closure is executed within a store-wide write lock.
pub fn with_ca_objects<F, T>(
&self,
ca: &CaHandle,
op: F,
) -> KrillResult<T>
where
F: Fn(&mut CaObjects) -> KrillResult<T>,
{
self.store.execute(&Scope::global(), |kv| {
let key = Self::key(ca);
let mut objects: CaObjects = match kv.get(&key)? {
Some(value) => value,
None => CaObjects::new(ca.clone()),
};
match op(&mut objects) {
Err(e) => Ok(Err(e)),
Ok(t) => {
kv.store(&key, &objects)?;
Ok(Ok(t))
}
}
}).map_err(Error::KeyValueError)?
}
/// Re-issues manifests and CRLs for the CA if needed.
///
/// If `force` is `true`, forces a re-issue. Returns whether it did
/// re-issue.
pub fn reissue_if_needed(
&self,
force: bool,
ca_handle: &CaHandle,
) -> KrillResult<bool> {
debug!("Re-issue for CA {} using force: {}", ca_handle, force);
self.with_ca_objects(ca_handle, |objects| {
objects.re_issue(
force,
&self.issuance_timing,
&self.signer,
)
})
}
}
//------------CaObjects ------------------------------------------------------
/// All the published objects of a CA.
//
// *Warning:* This type is used in stored state.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CaObjects {
/// The handle of the CA.
ca: CaHandle,
/// The repository the CA publishes too.
///
/// This is `None` if there isn’t a repository assigned yet.
repo: Option<RepositoryContact>,
/// The resource classes and their published objects.
classes: HashMap<ResourceClassName, ResourceClassObjects>,
/// Repositories we consider deprecated and are trying to remove.
#[serde(skip_serializing_if = "Vec::is_empty", default)]
deprecated_repos: Vec<DeprecatedRepository>,
}
impl CaObjects {
/// Creates a new CA objects with only the handle assigned yet.
pub fn new(
ca: CaHandle,
) -> Self {
CaObjects {
ca,
repo: None,
classes: HashMap::new(),
deprecated_repos: Vec::new(),
}
}
/// Creates a CA objects using the given parts.
///
/// This is only used for upgrading.
pub fn from_parts(
ca: CaHandle,
repo: Option<RepositoryContact>,
classes: HashMap<ResourceClassName, ResourceClassObjects>,
deprecated_repos: Vec<DeprecatedRepository>,
) -> Self {
CaObjects {
ca,
repo,
classes,
deprecated_repos,
}
}
/// Returns all PublishedElements mapped to each RepositoryContact.
///
/// There could be more than one repository, although usually there
/// isn't.
#[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() {
// Note the map 'res' will get entries for other (old)
// repositories if there are any keys with
// such repositories.
resource_class_objects.add_elements(&mut res, repo);
}
}
res
}
/// Returns all PublishedFiles in all repositories.
pub fn all_publish_elements(&self) -> Vec<PublishedFile> {
let mut all_elements = vec![];
// slightly inefficient since we drop the RepositoryContact keys
// again, but this leverages existing code.
for elements in self.repo_elements_map().values_mut() {
all_elements.append(elements);
}
all_elements
}
/// Returns an iterator over all deprecated repos.
pub fn deprecated_repos(
&self
) -> impl Iterator<Item = &DeprecatedRepository> + '_ {
self.deprecated_repos.iter()
}
/// Removes a deprecated repository.
pub fn deprecated_repo_remove(&mut self, to_remove: &RepositoryContact) {
self.deprecated_repos.retain(|current| current.contact() != to_remove)
}
/// Increments the number of cleaning attempts for a deprecated repo.
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()
}
}
}
}
/// # Actions invoked by the event listener
///
/// Most of these methods are simple enough that they could be rolled into
/// the event listener directly. However, we plan to get rid of listening
/// altogether and these methods will somehow be called in `CertAuths` command
/// processing directly. So we might as well keep them for now.
impl CaObjects {
/// Adds a new resource class.
///
/// This returns an error in case the class already exists.
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(())
}
/// Removes a resource class.
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);
}
}
/// Returns the class objects for the given class.
///
/// Returns an error if they don’t exist.
fn get_class_mut(
&mut self,
rcn: &ResourceClassName,
) -> KrillResult<&mut ResourceClassObjects> {
self.classes.get_mut(rcn).ok_or_else(|| {
Error::publishing("Missing resource class")
})
}
/// Adds a staging key to the set.
///
/// This will fail in case the class is missing, or in case the class is
/// not in state 'current'.
fn keyroll_stage(
&mut self,
rcn: &ResourceClassName,
key: &CertifiedKey,
timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<()> {
self.get_class_mut(rcn)?.keyroll_stage(key, timing, signer)
}
/// Activates the keyset.
///
/// Retires the current set and promotes the staging set to current.
fn keyroll_activate(
&mut self,
rcn: &ResourceClassName,
) -> KrillResult<()> {
self.get_class_mut(rcn)?.keyroll_activate()
}
/// Finishes a keyroll
fn keyroll_finish(&mut self, rcn: &ResourceClassName) -> KrillResult<()> {
let resource_class_objects = self.get_class_mut(rcn)?;
// finish the key roll for this resource class objects. This will
// remove the old key, and return an old_repo if there was
// one.
if let Some(old_repo) = resource_class_objects.keyroll_finish()? {
self.deprecate_repo_if_no_longer_used(old_repo);
}
Ok(())
}
/// Updates the ROAs in the current set
fn update_roas(
&mut self,
rcn: &ResourceClassName,
roa_updates: &RoaUpdates,
) -> KrillResult<()> {
self.get_class_mut(rcn)
.map(|rco| rco.update_roas(roa_updates))
}
/// Updates the ASPAs in the current set
fn update_aspas(
&mut self,
rcn: &ResourceClassName,
updates: &AspaObjectsUpdates,
) -> KrillResult<()> {
self.get_class_mut(rcn).map(|rco| rco.update_aspas(updates))
}
/// Updates the BGPSec certificates in the current set
fn update_bgpsec_certs(
&mut self,
rcn: &ResourceClassName,
updates: &BgpSecCertificateUpdates,
) -> KrillResult<()> {
self.get_class_mut(rcn).map(|rco| rco.update_bgpsec_certs(updates))
}
/// Updates the issued certificates in the current set
fn update_certs(
&mut self,
rcn: &ResourceClassName,
cert_updates: &ChildCertificateUpdates,
) -> KrillResult<()> {
self.get_class_mut(rcn).map(|rco| rco.update_certs(cert_updates))
}
/// Updates the received certificate.
fn update_received_cert(
&mut self,
rcn: &ResourceClassName,
cert: &ReceivedCert,
) -> KrillResult<()> {
self.get_class_mut(rcn)?.update_received_cert(cert)
}
/// Reissues the MFT and CRL
///
/// If `force` is `true`, re-issuance will always be done. This is
/// to be used in case any of the content changed. Otherwise
/// re-issuance will only happen if it's close to the next update
/// time, or the AIA has changed. The latter may happen if the parent
/// migrated repositories.
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)
}
/// Updates the repository.
///
/// If the repository is being migrated, i.e. there already is a current
/// repository, then make sure that the current repository is preserved
/// as the old repository for existing keys.
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());
}
/// Marks a repository as deprecated unless it's in use by any key.
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))
}
}
//------------ DeprecatedRepository ------------------------------------------
/// A previously used repository that hasn’t been successfully cleaned out.
//
// *Warning:* This type is used in stored state.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct DeprecatedRepository {
/// The repository contact.
contact: RepositoryContact,
/// The number of times we‘ve tried to clean it out.
clean_attempts: usize,
}
impl DeprecatedRepository {
/// Creates a new deprecated repository.
pub fn new(contact: RepositoryContact, clean_attempts: usize) -> Self {
DeprecatedRepository {
contact,
clean_attempts,
}
}
/// Returns the repository contact.
pub fn contact(&self) -> &RepositoryContact {
&self.contact
}
/// Converts the value into the repository contact.
pub fn into_contact(self) -> RepositoryContact {
self.contact
}
/// Returns the number of cleaning attempts.
pub fn clean_attempts(&self) -> usize {
self.clean_attempts
}
/// Increases the number of cleaning attempts.
pub fn inc_clean_attempts(&mut self) {
self.clean_attempts += 1;
}
}
//------------ ResourceClassObjects ------------------------------------------
/// The objects for a resource class.
//
// *Warning:* This type is used in stored state.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ResourceClassObjects {
keys: ResourceClassKeyState,
}
impl ResourceClassObjects {
/// Creates a new value.
///
/// This is only used by upgrades.
pub fn new(keys: ResourceClassKeyState) -> Self {
ResourceClassObjects { keys }
}
/// Creates a new resource class objects set.
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,
}),
})
}
/// Adds all the elements for this resource class to the map.
///
/// It will use the default repository, or an optional old
/// repository if any of the keys had one as part of a repository
/// migration.
#[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> {
// Note: we can only have 1 old repo, because new repositories can
// only be introduced when there is no key roll in progress.
// So, it's not possible to introduce a second repo until the
// previous old_repo is rolled out completely.
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()),
}
}
}
//------------ ResourceClassKeyState -----------------------------------------
// *Warning:* This type is used in stored state.
#[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)
}
}
}
}
}
//------------ CurrentKeyState -----------------------------------------------
// *Warning:* This type is used in stored state.
#[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 }
}
}
//------------ StagingKeyState -----------------------------------------------
// *Warning:* This type is used in stored state.
#[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,
}
}
}
//------------ OldKeyState ---------------------------------------------------
// *Warning:* This type is used in stored state.
#[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,
}
}
}
//------------ KeyObjectSet ------------------------------------------------
/// Maintains the set of objects published for a key.
//
// *Warning:* This type is used in stored state.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct KeyObjectSet {
/// The latest received certificate for the owning key.
///
/// This is used when signing a new manifest and CRL.
signing_cert: ReceivedCert,
/// The revision of the set.
///
/// Its number and the "this update" and "next update" values used on the
/// manifest and CRL.
revision: ObjectSetRevision,
/// The revocations that need go on the CRL.
///
/// The CRL object has no convenient access to this, so we keep that
/// immutable. Whenever we re-issue, we create a new CRL using these
/// revocations.
///
/// When objects are replaced or removed we add a revocation. When
/// publishing revocations for expired certificates are removed.
revocations: Revocations,
/// The last manifest generated for this set.
///
/// When a set is first created, we will have a manifest and a CRL, but
/// it will have an empty map of "published_objects".
///
/// A new manifest is generated when we re-issue the set. This may happen
/// when published objects are added/updated/removed, or in case, well
/// some time before, the manifest and CRL would expire.
manifest: PublishedManifest,
/// The last CRL generated for this set.
///
/// We always generate the manifest and CRL together. When we re-issue a
/// set we first generate a new CRL which will revoke the previous
/// manifest. The CRL (name and hash) is included in the new manifest.
///
/// Strictly speaking this revocation could be considered redundant,
/// because the new CRL will not be considered valid (hash mismatch)
/// under the old manifest. So, a Relying Party will only consider
/// the CRL when it is using the new manifest.
crl: PublishedCrl,
/// The published objects of this set.
///
/// Will be empty if the owning key is not "current". I.e., this is empty
/// when a new KeyObjectSet is created (new staging key for a key roll,
/// or the first certified key under a new resource class).
///
/// The "current" key will see updates to the published objects.
///
/// When a key becomes "old" - just before it is subsequently removed -
/// when it is replaced as part of a key roll, then `retire` is called
/// on the set: all objects are revoked, and then this becomes empty
/// again.
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
published_objects: HashMap<ObjectName, PublishedObject>,
/// The old repository this key publishes to.
///
/// We implement repository migration as a key roll where the new
/// key uses the new (then default) repository. Existing keys will
/// keep track of the old repository contact using this following
/// field so that they can continue to publish / withdraw there,
/// until they (the owning key) are complete removed when the key
/// rollover is finished.
#[serde(skip_serializing_if = "Option::is_none")]
old_repo: Option<RepositoryContact>,
}
impl KeyObjectSet {
/// Creates a new key set from its components.
///
/// This is only used by upgrades.
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,
}
}
/// Creates a new key set for the given key.
///
/// Creates an initial manifest and CRL but keeps the list of published
/// objects empty.
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,
})
}
/// Adds all the elements for this set to the map which is passed on.
///
/// It will use the default repository unless this key had an old
/// repository set - as part of repository migration.
#[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(),
});
}
}
/// Returns whether the set needs re-issuance within the given hours.
pub fn requires_reissuance(&self, hours: i64) -> bool {
Time::now() > self.next_update() - Duration::hours(hours)
}
/// Returns the next update time.
pub fn next_update(&self) -> Time {
self.revision.next_update
}
/// Updates the signing certificate for the set.
///
/// Returns an error in case the KeyIdentifiers don't match.
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()
)))
}
}
/// Updates the ROAs.
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());
}
}
}
/// Updates the ASPAs.
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());
}
}
}
/// Updates the BGPset router key certificates.
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());
}
}
}
/// Updates the child CA certificates.
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 {
self.revocations.remove(&cert.revocation());
let published_object = PublishedObject::for_cert_info(cert);
if let Some(old) = self
.published_objects
.insert(cert.name.clone(), published_object)
{
// this should not happen, but just to be safe.
self.revocations.add(old.revoke());
}
}
for suspended in &cert_updates.suspended {
if let Some(old) = self.published_objects.remove(&suspended.name)
{
self.revocations.add(old.revoke());
}
}
}
/// Re-issues manifest and CRL.
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(())
}
/// Returns a retired key object set for this set.
///
/// Revokes and retires all signed objects.
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)
}
/// Sets the old repository for this object set.
fn set_old_repo(&mut self, repo: &RepositoryContact) {
self.old_repo = Some(repo.clone())
}
/// Returns the old repo for this object set if there is one.
fn old_repo(&self) -> Option<&RepositoryContact> {
self.old_repo.as_ref()
}
}
//------------ ObjectSetRevision ---------------------------------------------
/// The current revision information for a key object set.
//
// *Warning:* This type is used in stored state.
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ObjectSetRevision {
/// The manifest and CRL number.
number: u64,
/// The issue time of this manifest and CRL.
///
/// This is backdated 5 minutes to tolerate some clock skew.
this_update: Time,
/// The next update of this manifest and CRL.
next_update: Time,
}
impl ObjectSetRevision {
/// Creates a new revision with the given parameters.
pub fn new(number: u64, this_update: Time, next_update: Time) -> Self {
ObjectSetRevision {
number,
this_update,
next_update,
}
}
/// Creates an initial revision.
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
}
/// Updates the revision to the following revision.
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;
}
}
//------------ PublishedCert -------------------------------------------------
/// A published certificate.
pub type PublishedCert = IssuedCertificate;
//------------ PublishedItem -------------------------------------------------
/// Any item published in the repository.
///
/// The concrete type of object is provided through the marker type `T`. This
/// is only used to make sure we add objects in the right place.
//
// *Warning:* This type is used in stored state.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PublishedItem<T> {
/// The name of the object.
name: ObjectName,
/// The content of the object.
base64: Base64,
/// The RRDP hash of the object.
///
/// This is derived from `base64` but kept for faster access.
hash: rrdp::Hash,
/// The serial number of the certificate the object is signed with.
serial: Serial,
/// The expiry time of the object.
expires: Time,
/// A marker for the object type.
marker: std::marker::PhantomData<T>,
}
impl<T> PublishedItem<T> {
/// Creates a new published object.
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,
}
}
/// Returns a published file for the object.
pub fn published_file(&self, uri: uri::Rsync) -> PublishedFile {
PublishedFile { uri, base64: self.base64.clone() }
}
/// Returns a revocation for the object.
pub fn revoke(&self) -> Revocation {
Revocation::new(self.serial, self.expires)
}
}
//------------ PublishedManifest ---------------------------------------------
/// A published manifest.
//
// *Warning:* This type is used in stored state.
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(),
)
}
}
//------------ PublishedCrl --------------------------------------------------
/// A published CRL.
//
// *Warning:* This type is used in stored state.
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(), // Just use this, we won't actually revoke CRLs
crl.next_update(),
)
}
}
//------------ PublishedObject -----------------------------------------------
/// A generic published object.
//
// *Warning:* This type is used in stored state.
pub type PublishedObject = PublishedItem<PublishedItemOther>;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PublishedItemOther;
impl PublishedObject {
/// Creates a published ROA.
pub fn for_roa(name: ObjectName, roa_info: &RoaInfo) -> Self {
PublishedObject::new(
name,
roa_info.base64.clone(),
roa_info.serial,
roa_info.expires(),
)
}
/// Creates a published ASPA object.
pub fn for_aspa(name: ObjectName, aspa_info: &AspaInfo) -> Self {
PublishedObject::new(
name,
aspa_info.base64.clone(),
aspa_info.serial,
aspa_info.expires(),
)
}
/// Creates a published child CA certificate.
pub fn for_cert_info<T>(cert: &CertInfo<T>) -> Self {
PublishedObject::new(
cert.name.clone(),
cert.base64.clone(),
cert.serial,
cert.expires(),
)
}
/// Creates a published BGPsec router key certificate.
pub fn for_bgpsec_cert_info(cert: &BgpSecCertInfo) -> Self {
PublishedObject::new(
cert.name(),
cert.base64.clone(),
cert.serial,
cert.expires,
)
}
}
//------------ ManifestBuilder -----------------------------------------------
/// A helper type to create a manifest.
#[allow(clippy::mutable_key_type)]
pub struct ManifestBuilder {
/// The revision of the manifest.
revision: ObjectSetRevision,
/// The entries of the manifest.
entries: HashMap<ObjectName, rrdp::Hash>,
}
impl ManifestBuilder {
/// Creates a new builder with the given revision.
pub fn new(revision: ObjectSetRevision) -> Self {
ManifestBuilder {
revision,
entries: HashMap::new(),
}
}
/// Adds the given objects to the manifest’s entries.
#[allow(clippy::mutable_key_type)]
pub fn with_objects(
mut self,
crl: &PublishedCrl,
published_objects: &HashMap<ObjectName, PublishedObject>,
) -> Self {
// Add entry for CRL
self.entries.insert(crl.name.clone(), crl.hash);
// Add other objects
for (name, object) in published_objects {
self.entries.insert(name.clone(), object.hash);
}
self
}
/// Buidlds a new manifest.
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(Some(Time::now()));
signer.sign_manifest(mft_content, object_builder, &aki)?
};
Ok(manifest)
}
}