mirror of
https://github.com/NLnetLabs/krill.git
synced 2026-09-22 09:24:55 +02:00
2332 lines
96 KiB
Rust
2332 lines
96 KiB
Rust
use std::{collections::HashMap, convert::TryFrom, ops::Deref, str::FromStr, sync::Arc};
|
|
|
|
use bytes::Bytes;
|
|
use chrono::Duration;
|
|
|
|
use rpki::{
|
|
ca::{
|
|
idexchange::{self, ServiceUri},
|
|
idexchange::{CaHandle, ChildHandle, ParentHandle},
|
|
provisioning,
|
|
provisioning::{
|
|
IssuanceRequest, ProvisioningCms, ResourceClassListResponse, ResourceClassName, RevocationRequest,
|
|
RevocationResponse,
|
|
},
|
|
publication,
|
|
publication::{ListReply, Publish, PublishDelta, Update, Withdraw},
|
|
},
|
|
crypto::KeyIdentifier,
|
|
repository::resources::ResourceSet,
|
|
uri,
|
|
};
|
|
|
|
use crate::{
|
|
commons::{
|
|
actor::Actor,
|
|
api::{
|
|
rrdp::PublishElement, BgpSecCsrInfoList, BgpSecDefinitionUpdates, IdCertInfo, ParentServerInfo,
|
|
PublicationServerInfo, RoaConfigurationUpdates, Timestamp,
|
|
},
|
|
api::{
|
|
AddChildRequest, AspaCustomer, AspaDefinitionList, AspaDefinitionUpdates, AspaProvidersUpdate,
|
|
CaCommandDetails, CaCommandResult, CertAuthList, CertAuthSummary, ChildCaInfo, CommandHistory,
|
|
CommandHistoryCriteria, ParentCaContact, ParentCaReq, ReceivedCert, RepositoryContact, RtaName,
|
|
StoredEffect, UpdateChildRequest,
|
|
},
|
|
crypto::KrillSigner,
|
|
error::Error,
|
|
eventsourcing::{Aggregate, AggregateStore, CommandKey},
|
|
util::{cmslogger::CmsLogger, httpclient},
|
|
KrillResult,
|
|
},
|
|
constants::{CASERVER_DIR, STATUS_DIR, TA_PROXY_SERVER_DIR, TA_SIGNER_SERVER_DIR},
|
|
daemon::{
|
|
auth::common::permissions::Permission,
|
|
auth::Handle,
|
|
ca::{
|
|
CaObjectsStore, CaStatus, CertAuth, Cmd, CmdDet, DeprecatedRepository, IniDet, ResourceTaggedAttestation,
|
|
RtaContentRequest, RtaPrepareRequest, StatusStore,
|
|
},
|
|
config::Config,
|
|
mq::{now, TaskQueue},
|
|
ta::{
|
|
self, ta_handle, TrustAnchorProxy, TrustAnchorProxyCommand, TrustAnchorSignedRequest,
|
|
TrustAnchorSignedResponse, TrustAnchorSigner, TrustAnchorSignerCommand, TrustAnchorSignerInfo,
|
|
TrustAnchorSignerInitCommand, TA_NAME,
|
|
},
|
|
},
|
|
pubd::RepositoryManager,
|
|
};
|
|
|
|
//------------ CaManager -----------------------------------------------------
|
|
|
|
pub struct CaManager {
|
|
// Used to manage CAs
|
|
ca_store: AggregateStore<CertAuth>,
|
|
|
|
// Used to manage objects for CAs. Also shared with the ca_store as well
|
|
// as a listener so that it can create manifests and CRLs as needed. Accessed
|
|
// here for publishing.
|
|
ca_objects_store: Arc<CaObjectsStore>,
|
|
|
|
// Keep track of CA parent and CA repository interaction status.
|
|
status_store: StatusStore,
|
|
|
|
// We may have a TA Proxy that we need to manage. Many functions are
|
|
// similar to CA operations, so it makes sense to manage this as a
|
|
// special kind of CA here.
|
|
ta_proxy_store: Option<AggregateStore<TrustAnchorProxy>>,
|
|
|
|
// We may also have a local TA signer - in case we are running in
|
|
// testbed or benchmarking mode - so that we can do all TA signing
|
|
// without the need for user interactions through the API and
|
|
// TA signer CLI.
|
|
ta_signer_store: Option<AggregateStore<TrustAnchorSigner>>,
|
|
|
|
// shared task queue:
|
|
// - listens for events in the ca_store
|
|
// - processed by the Scheduler
|
|
// - can be used here to schedule tasks through the api
|
|
tasks: Arc<TaskQueue>,
|
|
|
|
config: Arc<Config>,
|
|
signer: Arc<KrillSigner>,
|
|
|
|
// System actor is used for (scheduled or triggered) system actions where
|
|
// we have no operator actor context.
|
|
system_actor: Actor,
|
|
}
|
|
|
|
impl CaManager {
|
|
/// Builds a new CaServer. Will return an error if the CA store cannot be initialized.
|
|
pub async fn build(
|
|
config: Arc<Config>,
|
|
tasks: Arc<TaskQueue>,
|
|
signer: Arc<KrillSigner>,
|
|
system_actor: Actor,
|
|
) -> KrillResult<Self> {
|
|
// Create the AggregateStore for the event-sourced `CertAuth` structures that handle
|
|
// most CA functions.
|
|
let mut ca_store = AggregateStore::<CertAuth>::disk(&config.data_dir, CASERVER_DIR)?;
|
|
|
|
if config.always_recover_data {
|
|
// If the user chose to 'always recover data' then do so.
|
|
// This is slow, but it will ensure that all commands and events are accounted for,
|
|
// and there are no incomplete changes where some but not all files for a change were
|
|
// written to disk.
|
|
ca_store.recover()?;
|
|
} else if let Err(e) = ca_store.warm() {
|
|
// Otherwise we just tried to 'warm' the cache. This serves two purposes:
|
|
// 1. this ensures that all `CertAuth` structs are available in memory
|
|
// 2. this ensures that there are no apparent data issues
|
|
//
|
|
// If there are issues, then complain and try to recover.
|
|
error!(
|
|
"Could not warm up cache, data seems corrupt. Will try to recover!! Error was: {}",
|
|
e
|
|
);
|
|
ca_store.recover()?;
|
|
}
|
|
|
|
// Create the `CaObjectStore` that is responsible for maintaining CA objects: the `CaObjects`
|
|
// for a CA gets copies of all ROAs and issued certificates from the `CertAuth` and is responsible
|
|
// for manifests and CRL generation.
|
|
let ca_objects_store = Arc::new(CaObjectsStore::disk(
|
|
&config.data_dir,
|
|
config.issuance_timing.clone(),
|
|
signer.clone(),
|
|
)?);
|
|
|
|
// Register the `CaObjectsStore` as a pre-save listener to the 'ca_store' so that it can update
|
|
// its ROAs and issued certificates and/or generate manifests and CRLs when relevant changes
|
|
// occur in a `CertAuth`.
|
|
ca_store.add_pre_save_listener(ca_objects_store.clone());
|
|
|
|
// Register the `MessageQueue` as a post-save listener to 'ca_store' so that relevant changes in
|
|
// a `CertAuth` can trigger follow up actions. Most importantly: synchronize with a parent CA or
|
|
// the RPKI repository.
|
|
ca_store.add_post_save_listener(tasks.clone());
|
|
|
|
// Create TA proxy store if we need it.
|
|
let ta_proxy_store = if config.ta_proxy_enabled() {
|
|
let mut store = AggregateStore::<TrustAnchorProxy>::disk(&config.data_dir, TA_PROXY_SERVER_DIR)?;
|
|
|
|
// We need to listen for proxy events so that we can schedule:
|
|
// 1. publication on updates
|
|
// 2. re-sync for local children when the proxy has new responses
|
|
// 3. signing by the Trust Anchor Signer when there are requests [in testbed mode]
|
|
store.add_post_save_listener(tasks.clone());
|
|
Some(store)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let ta_signer_store = if config.ta_signer_enabled() {
|
|
Some(AggregateStore::disk(&config.data_dir, TA_SIGNER_SERVER_DIR)?)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// Create the status store which will maintain the last known connection status between each CA
|
|
// and their parent(s) and repository.
|
|
let status_store = StatusStore::new(&config.data_dir, STATUS_DIR)?;
|
|
|
|
Ok(CaManager {
|
|
ca_store,
|
|
ca_objects_store,
|
|
status_store,
|
|
ta_proxy_store,
|
|
ta_signer_store,
|
|
tasks,
|
|
config,
|
|
signer,
|
|
system_actor,
|
|
})
|
|
}
|
|
|
|
pub fn testbed_enabled(&self) -> bool {
|
|
self.config.testbed().is_some()
|
|
}
|
|
|
|
/// Send a command to a CA
|
|
async fn send_ca_command(&self, cmd: Cmd) -> KrillResult<Arc<CertAuth>> {
|
|
self.ca_store.command(cmd)
|
|
}
|
|
|
|
/// Republish the embedded TA and CAs if needed, i.e. if they are close
|
|
/// to their next update time.
|
|
pub async fn republish_all(&self, force: bool) -> KrillResult<Vec<CaHandle>> {
|
|
self.ca_objects_store.reissue_all(force)
|
|
}
|
|
}
|
|
|
|
/// # Trust Anchor Support
|
|
///
|
|
impl CaManager {
|
|
/// Send a command to the TA Proxy. Errors if ta support is not enabled
|
|
async fn send_ta_proxy_command(&self, cmd: TrustAnchorProxyCommand) -> KrillResult<Arc<TrustAnchorProxy>> {
|
|
self.ta_proxy_store
|
|
.as_ref()
|
|
.ok_or_else(|| Error::custom("ta_support_enabled is false"))?
|
|
.command(cmd)
|
|
}
|
|
|
|
/// Send a command to the TA Proxy. Errors if ta support is not enabled
|
|
async fn send_ta_signer_command(&self, cmd: TrustAnchorSignerCommand) -> KrillResult<Arc<TrustAnchorSigner>> {
|
|
self.ta_signer_store
|
|
.as_ref()
|
|
.ok_or_else(|| Error::custom("ta_signer_enabled is false"))?
|
|
.command(cmd)
|
|
}
|
|
|
|
/// Gets the Trust Anchor Proxy, if present. Returns an error if the TA is uninitialized.
|
|
pub async fn get_trust_anchor_proxy(&self) -> KrillResult<Arc<TrustAnchorProxy>> {
|
|
let ta_handle = ta::ta_handle();
|
|
self.ta_proxy_store
|
|
.as_ref()
|
|
.ok_or_else(|| Error::custom("TA proxy not enabled"))?
|
|
.get_latest(&ta_handle)
|
|
.map_err(Error::AggregateStoreError)
|
|
}
|
|
|
|
/// Gets the Trust Anchor Signer, if present. Returns an error if the TA is uninitialized.
|
|
pub async fn get_trust_anchor_signer(&self) -> KrillResult<Arc<TrustAnchorSigner>> {
|
|
let ta_handle = ta::ta_handle();
|
|
self.ta_signer_store
|
|
.as_ref()
|
|
.ok_or_else(|| Error::custom("TA signer not enabled"))?
|
|
.get_latest(&ta_handle)
|
|
.map_err(Error::AggregateStoreError)
|
|
}
|
|
|
|
/// Initialises the (one) Trust Anchor proxy.
|
|
///
|
|
/// Returns an error if:
|
|
/// - ta_support_enabled is false
|
|
/// - the proxy was already initialised
|
|
pub async fn ta_proxy_init(&self) -> KrillResult<()> {
|
|
let ta_handle = ta::ta_handle();
|
|
|
|
let ta_proxy_store = self
|
|
.ta_proxy_store
|
|
.as_ref()
|
|
.ok_or_else(|| Error::custom("ta_support_enabled must be true in config"))?;
|
|
|
|
if ta_proxy_store.has(&ta_handle)? {
|
|
Err(Error::TaAlreadyInitialized)
|
|
} else {
|
|
// Initialise proxy
|
|
let proxy_init = TrustAnchorProxy::create_init(ta_handle, &self.signer)?;
|
|
ta_proxy_store.add(proxy_init)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Initialises the embedded Trust Anchor Signer (for testbed).
|
|
/// This assumes that the one and only local Trust Anchor Proxy exists and
|
|
/// is to be associated with this signer.
|
|
pub async fn ta_signer_init(&self, tal_https: Vec<uri::Https>, tal_rsync: uri::Rsync) -> KrillResult<()> {
|
|
let ta_signer_store = self
|
|
.ta_signer_store
|
|
.as_ref()
|
|
.ok_or_else(|| Error::custom("ta_signer_enabled must be true in config"))?;
|
|
|
|
let handle = ta_handle();
|
|
|
|
if ta_signer_store.has(&handle)? {
|
|
Err(Error::TaAlreadyInitialized)
|
|
} else {
|
|
// Create Signer
|
|
let repo_contact = self.ta_proxy_repository_contact().await?;
|
|
let proxy_id = self.ta_proxy_id().await?;
|
|
|
|
let signer_init_cmd = TrustAnchorSignerInitCommand {
|
|
handle,
|
|
proxy_id,
|
|
repo_info: repo_contact.repo_info().clone(),
|
|
tal_https,
|
|
tal_rsync,
|
|
private_key_pem: None,
|
|
signer: self.signer.clone(),
|
|
};
|
|
|
|
let signer_init = TrustAnchorSigner::create_init(signer_init_cmd)?;
|
|
ta_signer_store.add(signer_init)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub async fn ta_proxy_id(&self) -> KrillResult<IdCertInfo> {
|
|
self.get_trust_anchor_proxy().await.map(|proxy| proxy.id().clone())
|
|
}
|
|
|
|
/// Gets the publisher request for the Trust Anchor proxy.
|
|
/// Returns an error if the proxy is not initialised.
|
|
pub async fn ta_proxy_publisher_request(&self) -> KrillResult<idexchange::PublisherRequest> {
|
|
self.get_trust_anchor_proxy()
|
|
.await
|
|
.map(|proxy| proxy.publisher_request())
|
|
}
|
|
|
|
/// Add the repository to Trust Anchor proxy.
|
|
/// Returns an error if the proxy is not enabled or already has a repository.
|
|
pub async fn ta_proxy_repository_update(&self, contact: RepositoryContact, actor: &Actor) -> KrillResult<()> {
|
|
let add_repo_cmd = TrustAnchorProxyCommand::add_repo(&ta::ta_handle(), contact, actor);
|
|
self.send_ta_proxy_command(add_repo_cmd).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns the repository contact for the proxy, or an error if there is
|
|
/// no proxy, or no repository configured for it.
|
|
pub async fn ta_proxy_repository_contact(&self) -> KrillResult<RepositoryContact> {
|
|
self.get_trust_anchor_proxy()
|
|
.await?
|
|
.repository()
|
|
.cloned()
|
|
.ok_or(Error::TaProxyHasNoRepository)
|
|
}
|
|
|
|
/// Adds the associated signer to the proxy.
|
|
///
|
|
/// Errors if:
|
|
/// - there is no proxy
|
|
/// - the proxy has a signer
|
|
pub async fn ta_proxy_signer_add(&self, info: TrustAnchorSignerInfo, actor: &Actor) -> KrillResult<()> {
|
|
let add_signer_cmd = TrustAnchorProxyCommand::add_signer(&ta_handle(), info, actor);
|
|
self.send_ta_proxy_command(add_signer_cmd).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Create a new request for the signer.
|
|
///
|
|
/// Errors if:
|
|
/// - there is no proxy
|
|
/// - the proxy already has a request
|
|
pub async fn ta_proxy_signer_make_request(&self, actor: &Actor) -> KrillResult<TrustAnchorSignedRequest> {
|
|
let cmd = TrustAnchorProxyCommand::make_signer_request(&ta_handle(), actor);
|
|
let proxy = self.send_ta_proxy_command(cmd).await?;
|
|
|
|
proxy.get_signer_request(&self.signer)
|
|
}
|
|
|
|
/// Create a new request for the signer.
|
|
///
|
|
/// Errors if:
|
|
/// - there is no proxy
|
|
/// - the proxy already has a request
|
|
pub async fn ta_proxy_signer_get_request(&self) -> KrillResult<TrustAnchorSignedRequest> {
|
|
self.get_trust_anchor_proxy().await?.get_signer_request(&self.signer)
|
|
}
|
|
|
|
/// Process a sign response from the signer.
|
|
///
|
|
/// Errors if:
|
|
/// - there is no proxy
|
|
/// - there is no matching request
|
|
pub async fn ta_proxy_signer_process_response(
|
|
&self,
|
|
response: TrustAnchorSignedResponse,
|
|
actor: &Actor,
|
|
) -> KrillResult<()> {
|
|
let cmd = TrustAnchorProxyCommand::process_signer_response(&ta_handle(), response, actor);
|
|
self.send_ta_proxy_command(cmd).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Initializes an embedded trust anchor with all resources.
|
|
pub async fn ta_init_fully_embedded(
|
|
&self,
|
|
ta_aia: uri::Rsync,
|
|
ta_uris: Vec<uri::Https>,
|
|
repo_manager: &Arc<RepositoryManager>,
|
|
actor: &Actor,
|
|
) -> KrillResult<()> {
|
|
let ta_handle = ta::ta_handle();
|
|
|
|
// Initialise proxy
|
|
self.ta_proxy_init().await?;
|
|
|
|
// Add repository
|
|
let pub_req = self.ta_proxy_publisher_request().await?;
|
|
|
|
// Create publisher
|
|
repo_manager.create_publisher(pub_req, actor)?;
|
|
let repository_response = repo_manager.repository_response(&ta_handle.convert())?;
|
|
|
|
// Add repository to proxy
|
|
let contact = RepositoryContact::for_response(repository_response).map_err(Error::rfc8183)?;
|
|
self.ta_proxy_repository_update(contact, &self.system_actor).await?;
|
|
|
|
// Initialise signer
|
|
self.ta_signer_init(ta_uris, ta_aia).await?;
|
|
|
|
// Add signer to proxy
|
|
let signer_info = self.get_trust_anchor_signer().await?.get_signer_info();
|
|
self.ta_proxy_signer_add(signer_info, &self.system_actor).await?;
|
|
|
|
self.sync_ta_proxy_signer_if_possible().await?;
|
|
self.cas_repo_sync_single(repo_manager, &ta_handle).await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// # CA instances and identity
|
|
///
|
|
impl CaManager {
|
|
/// Initializes a CA without a repo, no parents, no children, no nothing
|
|
pub fn init_ca(&self, handle: &CaHandle) -> KrillResult<()> {
|
|
if handle == &ta_handle() || handle.as_str() == "version" {
|
|
Err(Error::TaNameReserved)
|
|
} else if self.ca_store.has(handle)? {
|
|
Err(Error::CaDuplicate(handle.clone()))
|
|
} else {
|
|
// Initialize the CA in self.ca_store, but note that there is no need to create
|
|
// a new CA entry in self.ca_objects_store or self.status_store, because they will
|
|
// generate empty default entries if needed.
|
|
let init = IniDet::init(handle, self.signer.deref())?;
|
|
self.ca_store.add(init)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Updates the self-signed ID certificate for a CA. Use this with care as
|
|
/// RFC 8183 only talks about initial ID exchanges in the form of XML files.
|
|
/// It does not talk about updating identity certificates and keys. Krill supports
|
|
/// that a new ID key pair and certificate is generated, and has functions to update
|
|
/// this for a parent, a child, a repo and a publisher, but other implementations may
|
|
/// not support that identities are updated after initialization.
|
|
pub async fn ca_update_id(&self, handle: CaHandle, actor: &Actor) -> KrillResult<()> {
|
|
let cmd = CmdDet::update_id(&handle, self.signer.clone(), actor);
|
|
self.send_ca_command(cmd).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Get the CAs that the given actor is permitted to see.
|
|
pub fn ca_list(&self, actor: &Actor) -> KrillResult<CertAuthList> {
|
|
Ok(CertAuthList::new(
|
|
self.ca_store
|
|
.list()?
|
|
.into_iter()
|
|
.filter(|handle| matches!(actor.is_allowed(Permission::CA_READ, Handle::from(handle)), Ok(true)))
|
|
.map(CertAuthSummary::new)
|
|
.collect(),
|
|
))
|
|
}
|
|
|
|
/// Gets a CA by the given handle, returns an `Err(ServerError::UnknownCA)` if it
|
|
/// does not exist.
|
|
pub async fn get_ca(&self, handle: &CaHandle) -> KrillResult<Arc<CertAuth>> {
|
|
self.ca_store
|
|
.get_latest(handle)
|
|
.map_err(|_| Error::CaUnknown(handle.clone()))
|
|
}
|
|
|
|
/// Checks whether a CA by the given handle exists.
|
|
pub fn has_ca(&self, handle: &CaHandle) -> KrillResult<bool> {
|
|
self.ca_store.has(handle).map_err(Error::AggregateStoreError)
|
|
}
|
|
|
|
/// Gets current CA status
|
|
pub async fn get_ca_status(&self, ca: &CaHandle) -> KrillResult<CaStatus> {
|
|
if self.has_ca(ca)? {
|
|
Ok(self.status_store.get_ca_status(ca))
|
|
} else {
|
|
Err(Error::CaUnknown(ca.clone()))
|
|
}
|
|
}
|
|
|
|
/// Delete a CA. Let it do best effort revocation requests and withdraw
|
|
/// all its objects first. Note that any children of this CA will be left
|
|
/// orphaned, and they will only learn of this sad fact when they choose
|
|
/// to call home.
|
|
pub async fn delete_ca(
|
|
&self,
|
|
repo_manager: &RepositoryManager,
|
|
ca_handle: &CaHandle,
|
|
actor: &Actor,
|
|
) -> KrillResult<()> {
|
|
warn!("Deleting CA '{}' as requested by: {}", ca_handle, actor);
|
|
|
|
let ca = self.get_ca(ca_handle).await?;
|
|
|
|
// Request revocations from all parents - best effort
|
|
info!(
|
|
"Will try to request revocations from all parents CA '{}' before removing it.",
|
|
ca_handle
|
|
);
|
|
for parent in ca.parents() {
|
|
if let Err(e) = self.ca_parent_revoke(ca_handle, parent).await {
|
|
warn!(
|
|
"Removing CA '{}', but could not send revoke requests to parent '{}': {}",
|
|
ca_handle, parent, e
|
|
);
|
|
}
|
|
}
|
|
|
|
// Clean all repos - again best effort
|
|
info!(
|
|
"Will try to clean up all repositories for CA '{}' before removing it.",
|
|
ca_handle
|
|
);
|
|
let mut repos: Vec<RepositoryContact> = self
|
|
.ca_repo_elements(ca_handle)
|
|
.await?
|
|
.into_iter()
|
|
.map(|(contact, _)| contact)
|
|
.collect();
|
|
|
|
for deprecated in self.ca_deprecated_repos(ca_handle)? {
|
|
repos.push(deprecated.into());
|
|
}
|
|
|
|
for repo_contact in repos {
|
|
if self
|
|
.ca_repo_sync(repo_manager, ca_handle, ca.id_cert(), &repo_contact, vec![])
|
|
.await
|
|
.is_err()
|
|
{
|
|
info!(
|
|
"Could not clean up deprecated repository. This is fine - objects there are no longer referenced."
|
|
);
|
|
}
|
|
}
|
|
|
|
self.ca_store.drop_aggregate(ca_handle)?;
|
|
self.status_store.remove_ca(ca_handle)?;
|
|
self.tasks.remove_tasks_for_ca(ca_handle);
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// # CA History
|
|
///
|
|
impl CaManager {
|
|
/// Gets the history for a CA.
|
|
pub async fn ca_history(&self, handle: &CaHandle, crit: CommandHistoryCriteria) -> KrillResult<CommandHistory> {
|
|
Ok(self.ca_store.command_history(handle, crit)?)
|
|
}
|
|
|
|
/// Shows the details for a CA command.
|
|
pub fn ca_command_details(&self, handle: &CaHandle, command: CommandKey) -> KrillResult<CaCommandDetails> {
|
|
let command = self.ca_store.get_command(handle, &command)?;
|
|
|
|
let effect = command.effect().clone();
|
|
match effect {
|
|
StoredEffect::Error { msg } => Ok(CaCommandDetails::new(command, CaCommandResult::error(msg))),
|
|
StoredEffect::Success { events } => {
|
|
let mut stored_events = vec![];
|
|
for version in events {
|
|
let evt = self.ca_store.get_event(handle, version)?.ok_or_else(|| {
|
|
Error::Custom(format!("Cannot find evt: {} in history for CA: {}", version, handle))
|
|
})?;
|
|
stored_events.push(evt);
|
|
}
|
|
|
|
Ok(CaCommandDetails::new(command, CaCommandResult::events(stored_events)))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// # CAs as parents
|
|
///
|
|
impl CaManager {
|
|
/// Adds a child under a CA. If the `AddChildRequest` contains resources not held
|
|
/// by this CA, then an `Error::CaChildExtraResources` is returned.
|
|
pub async fn ca_add_child(
|
|
&self,
|
|
ca: &CaHandle,
|
|
req: AddChildRequest,
|
|
service_uri: &uri::Https,
|
|
actor: &Actor,
|
|
) -> KrillResult<idexchange::ParentResponse> {
|
|
info!("CA '{}' process add child request: {}", &ca, &req);
|
|
if ca.as_str() != TA_NAME {
|
|
let (child_handle, child_res, id_cert) = req.unpack();
|
|
|
|
let add_child = CmdDet::child_add(ca, child_handle.clone(), id_cert.into(), child_res, actor);
|
|
self.send_ca_command(add_child).await?;
|
|
self.ca_parent_response(ca, child_handle, service_uri).await
|
|
} else {
|
|
let child_handle = req.handle().clone();
|
|
let add_child_cmd = TrustAnchorProxyCommand::add_child(ca, req, actor);
|
|
self.send_ta_proxy_command(add_child_cmd).await?;
|
|
self.ca_parent_response(ca, child_handle, service_uri).await
|
|
}
|
|
}
|
|
|
|
/// Show details for a child under the CA.
|
|
pub async fn ca_show_child(&self, ca: &CaHandle, child: &ChildHandle) -> KrillResult<ChildCaInfo> {
|
|
trace!("Finding details for CA: {} under parent: {}", child, ca);
|
|
let ca = self.get_ca(ca).await?;
|
|
ca.get_child(child).map(|details| details.clone().into())
|
|
}
|
|
|
|
/// Show a contact for a child.
|
|
pub async fn ca_parent_contact(
|
|
&self,
|
|
ca_handle: &CaHandle,
|
|
child_handle: ChildHandle,
|
|
service_uri: &uri::Https,
|
|
) -> KrillResult<ParentCaContact> {
|
|
let service_uri = Self::service_uri_for_ca(service_uri, ca_handle);
|
|
let ca = self.get_ca(ca_handle).await?;
|
|
|
|
let server_info = ParentServerInfo::new(service_uri, ca_handle.convert(), child_handle, ca.id_cert().clone());
|
|
Ok(ParentCaContact::for_parent_server_info(server_info))
|
|
}
|
|
|
|
/// Gets an RFC8183 Parent Response for the child.
|
|
pub async fn ca_parent_response(
|
|
&self,
|
|
ca_handle: &CaHandle,
|
|
child_handle: ChildHandle,
|
|
service_uri: &uri::Https,
|
|
) -> KrillResult<idexchange::ParentResponse> {
|
|
let service_uri = Self::service_uri_for_ca(service_uri, ca_handle);
|
|
let id_cert: publication::Base64 = if ca_handle.as_str() != TA_NAME {
|
|
let ca = self.get_ca(ca_handle).await?;
|
|
ca.get_child(&child_handle)?; // ensure the child is known
|
|
ca.id_cert().base64().clone()
|
|
} else {
|
|
let proxy = self.get_trust_anchor_proxy().await?;
|
|
proxy.get_child(&child_handle)?;
|
|
proxy.id().base64().clone()
|
|
};
|
|
|
|
Ok(idexchange::ParentResponse::new(
|
|
id_cert,
|
|
ca_handle.convert(),
|
|
child_handle,
|
|
service_uri,
|
|
None,
|
|
))
|
|
}
|
|
|
|
fn service_uri_for_ca(base_uri: &uri::Https, ca_handle: &CaHandle) -> ServiceUri {
|
|
let service_uri = format!("{}rfc6492/{}", base_uri, ca_handle);
|
|
let service_uri = uri::Https::from_string(service_uri).unwrap();
|
|
ServiceUri::Https(service_uri)
|
|
}
|
|
|
|
/// Update a child under this CA. The submitted `UpdateChildRequest` can contain a
|
|
/// new `IdCert`, or `ResourceSet`, or both. When resources are updated, the existing
|
|
/// resource entitlements are replaced by the new value - i.e. this is not a delta
|
|
/// and it affects all Internet Number Resource (INR) types (IPv4, IPV6, ASN). Setting
|
|
/// resource entitlements beyond the resources held by the parent CA will return
|
|
/// an `Error::CaChildExtraResources`.
|
|
pub async fn ca_child_update(
|
|
&self,
|
|
ca: &CaHandle,
|
|
child: ChildHandle,
|
|
req: UpdateChildRequest,
|
|
actor: &Actor,
|
|
) -> KrillResult<()> {
|
|
let (id_opt, resources_opt, suspend_opt) = req.unpack();
|
|
|
|
if let Some(id) = id_opt {
|
|
self.send_ca_command(CmdDet::child_update_id(ca, child.clone(), id.into(), actor))
|
|
.await?;
|
|
}
|
|
if let Some(resources) = resources_opt {
|
|
self.send_ca_command(CmdDet::child_update_resources(ca, child.clone(), resources, actor))
|
|
.await?;
|
|
}
|
|
if let Some(suspend) = suspend_opt {
|
|
if suspend {
|
|
self.send_ca_command(CmdDet::child_suspend_inactive(ca, child, actor))
|
|
.await?;
|
|
} else {
|
|
self.send_ca_command(CmdDet::child_unsuspend(ca, child, actor)).await?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Removes a child from this CA. This will also ensure that certificates issued to the child
|
|
/// are revoked and withdrawn.
|
|
pub async fn ca_child_remove(&self, ca: &CaHandle, child: ChildHandle, actor: &Actor) -> KrillResult<()> {
|
|
self.status_store.remove_child(ca, &child)?;
|
|
self.send_ca_command(CmdDet::child_remove(ca, child, actor)).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Processes an RFC 6492 request sent to this CA:
|
|
/// - parses the message bytes
|
|
/// - validates the request
|
|
/// - processes the child request
|
|
/// - signs a response and returns the bytes
|
|
pub async fn rfc6492(
|
|
&self,
|
|
ca_handle: &CaHandle,
|
|
msg_bytes: Bytes,
|
|
user_agent: Option<String>,
|
|
actor: &Actor,
|
|
) -> KrillResult<Bytes> {
|
|
if ca_handle.as_str() == TA_NAME {
|
|
return Err(Error::custom("Remote RFC 6492 to TA is not supported"));
|
|
}
|
|
|
|
let ca = self.get_ca(ca_handle).await?;
|
|
|
|
let req_msg = self.rfc6492_unwrap_request(&ca, &msg_bytes)?;
|
|
|
|
// Create a logger for CMS (avoid cloning recipient)
|
|
let cms_logger = CmsLogger::for_rfc6492_rcvd(
|
|
self.config.rfc6492_log_dir.as_ref(),
|
|
req_msg.recipient(),
|
|
req_msg.sender(),
|
|
);
|
|
|
|
let res_msg = self
|
|
.rfc6492_process_request(ca_handle, req_msg, user_agent, actor)
|
|
.await;
|
|
|
|
match res_msg {
|
|
Ok(msg) => {
|
|
let should_log_cms = !msg.is_list_response();
|
|
let reply_bytes = ca.sign_rfc6492_response(msg, self.signer.deref())?;
|
|
|
|
if should_log_cms {
|
|
cms_logger.received(&msg_bytes)?;
|
|
cms_logger.reply(&reply_bytes)?;
|
|
}
|
|
|
|
Ok(reply_bytes)
|
|
}
|
|
Err(e) => {
|
|
cms_logger.received(&msg_bytes)?;
|
|
cms_logger.err(&e)?;
|
|
|
|
Err(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Process an rfc6492 message and create an unsigned response
|
|
pub async fn rfc6492_process_request(
|
|
&self,
|
|
ca_handle: &CaHandle,
|
|
req_msg: provisioning::Message,
|
|
user_agent: Option<String>,
|
|
actor: &Actor,
|
|
) -> KrillResult<provisioning::Message> {
|
|
let (sender, _recipient, payload) = req_msg.unpack();
|
|
|
|
let child_handle = sender.convert();
|
|
|
|
// If the child was suspended, because it was inactive, then we can now conclude
|
|
// that it's become active again. So unsuspend it first, before processing the request
|
|
// further.
|
|
//
|
|
// The TA will never suspend children, and does not support it.
|
|
if ca_handle.as_str() != TA_NAME {
|
|
let ca = self.get_ca(ca_handle).await?;
|
|
|
|
let child_ca = ca.get_child(&child_handle)?;
|
|
if child_ca.is_suspended() {
|
|
info!(
|
|
"Child '{}' under CA '{}' became active again, will unsuspend it.",
|
|
child_handle,
|
|
ca.handle()
|
|
);
|
|
let req = UpdateChildRequest::unsuspend();
|
|
self.ca_child_update(ca.handle(), child_handle.clone(), req, actor)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
let res_msg = match payload {
|
|
provisioning::Payload::Revoke(req) => self.revoke(ca_handle, child_handle.clone(), req, actor).await,
|
|
provisioning::Payload::List => self.list(ca_handle, &child_handle).await,
|
|
provisioning::Payload::Issue(req) => self.issue(ca_handle, child_handle.clone(), req, actor).await,
|
|
_ => Err(Error::custom("Unsupported RFC6492 message")),
|
|
};
|
|
|
|
// Set child status
|
|
match &res_msg {
|
|
Ok(_) => {
|
|
self.status_store
|
|
.set_child_success(ca_handle, &child_handle, user_agent)?;
|
|
}
|
|
Err(e) => {
|
|
self.status_store
|
|
.set_child_failure(ca_handle, &child_handle, user_agent, e)?;
|
|
}
|
|
}
|
|
|
|
res_msg
|
|
}
|
|
|
|
/// Unpack and validate a request message
|
|
fn rfc6492_unwrap_request(&self, ca: &CertAuth, msg_bytes: &Bytes) -> KrillResult<provisioning::Message> {
|
|
match ProvisioningCms::decode(msg_bytes.as_ref()) {
|
|
Ok(msg) => ca.verify_rfc6492(msg),
|
|
Err(e) => Err(Error::custom(format!(
|
|
"Could not decode RFC6492 message for: {}, err: {}",
|
|
ca.handle(),
|
|
e
|
|
))),
|
|
}
|
|
}
|
|
|
|
/// List the entitlements for a child: 3.3.2 of RFC 6492.
|
|
async fn list(&self, ca_handle: &CaHandle, child: &ChildHandle) -> KrillResult<provisioning::Message> {
|
|
let list_response = if ca_handle.as_str() != TA_NAME {
|
|
self.get_ca(ca_handle).await?.list(child, &self.config.issuance_timing)
|
|
} else {
|
|
self.get_trust_anchor_proxy()
|
|
.await?
|
|
.entitlements(child, &self.config.issuance_timing)
|
|
.map(|entitlements| ResourceClassListResponse::new(vec![entitlements]))
|
|
}?;
|
|
|
|
Ok(provisioning::Message::list_response(
|
|
ca_handle.convert(),
|
|
child.convert(),
|
|
list_response,
|
|
))
|
|
}
|
|
|
|
/// Issue a Certificate in response to an RFC 6492 Certificate Issuance request sent by a child.
|
|
///
|
|
/// See: https://tools.ietf.org/html/rfc6492#section3.4.1-2
|
|
async fn issue(
|
|
&self,
|
|
ca_handle: &CaHandle,
|
|
child: ChildHandle,
|
|
issue_req: IssuanceRequest,
|
|
actor: &Actor,
|
|
) -> KrillResult<provisioning::Message> {
|
|
if ca_handle.as_str() == TA_NAME {
|
|
let request = ta::ProvisioningRequest::Issuance(issue_req);
|
|
self.ta_slow_rfc6492_request(ca_handle, child, request, actor).await
|
|
} else {
|
|
let class_name = issue_req.class_name();
|
|
let pub_key = issue_req.csr().public_key();
|
|
|
|
let cmd = CmdDet::child_certify(
|
|
ca_handle,
|
|
child.clone(),
|
|
issue_req.clone(),
|
|
self.config.clone(),
|
|
self.signer.clone(),
|
|
actor,
|
|
);
|
|
|
|
let ca = self.send_ca_command(cmd).await?;
|
|
|
|
// The updated CA will now include the newly issued certificate.
|
|
let response = ca.issuance_response(&child, class_name, pub_key, &self.config.issuance_timing)?;
|
|
|
|
Ok(provisioning::Message::issue_response(
|
|
ca_handle.convert(),
|
|
child.into_converted(),
|
|
response,
|
|
))
|
|
}
|
|
}
|
|
|
|
/// Process an RFC 6492 revocation request sent by a child.
|
|
/// See: https://tools.ietf.org/html/rfc6492#section3.5.1-2
|
|
async fn revoke(
|
|
&self,
|
|
ca_handle: &CaHandle,
|
|
child: ChildHandle,
|
|
revoke_request: RevocationRequest,
|
|
actor: &Actor,
|
|
) -> KrillResult<provisioning::Message> {
|
|
if ca_handle.as_str() == TA_NAME {
|
|
let request = ta::ProvisioningRequest::Revocation(revoke_request);
|
|
self.ta_slow_rfc6492_request(ca_handle, child, request, actor).await
|
|
} else {
|
|
let res = RevocationResponse::from(&revoke_request); // response provided that no errors are returned
|
|
let msg = provisioning::Message::revoke_response(ca_handle.convert(), child.convert(), res);
|
|
|
|
let cmd = CmdDet::child_revoke_key(ca_handle, child, revoke_request, actor);
|
|
self.send_ca_command(cmd).await?;
|
|
|
|
Ok(msg)
|
|
}
|
|
}
|
|
|
|
/// Processes a 'slow' RFC 6492 request to the TA: an issue or revoke request
|
|
/// which will require the Trust Anchor Signer to use the TA key.
|
|
async fn ta_slow_rfc6492_request(
|
|
&self,
|
|
ta_handle: &CaHandle,
|
|
child: ChildHandle,
|
|
request: ta::ProvisioningRequest,
|
|
actor: &Actor,
|
|
) -> KrillResult<provisioning::Message> {
|
|
let proxy = self.get_trust_anchor_proxy().await?;
|
|
if let Some(response) = proxy.response_for_child(&child, &request)? {
|
|
// Great, we have a pending response. We can give the response to the child
|
|
// and remove it from the proxy.
|
|
let response = response
|
|
.clone()
|
|
.to_provisioning_message(ta_handle.convert(), child.convert());
|
|
|
|
let cmd = TrustAnchorProxyCommand::give_child_response(ta_handle, child, request.key_identifier(), actor);
|
|
self.send_ta_proxy_command(cmd).await?;
|
|
|
|
Ok(response)
|
|
} else if proxy.matching_open_request(&child, &request)? {
|
|
// already scheduled.. should not happen with krill children
|
|
// but return 1101 just in case
|
|
provisioning::Message::not_performed_response(
|
|
ta_handle.convert(),
|
|
child.convert(),
|
|
provisioning::NotPerformedResponse::err_1101(),
|
|
)
|
|
.map_err(|_| Error::custom("creation of not performed response should never fail"))
|
|
} else {
|
|
// we will need schedule this one and return a 1104 not performed response
|
|
let cmd = TrustAnchorProxyCommand::add_child_request(ta_handle, child.clone(), request, actor);
|
|
self.send_ta_proxy_command(cmd).await?;
|
|
|
|
provisioning::Message::not_performed_response(
|
|
ta_handle.convert(),
|
|
child.into_converted(),
|
|
provisioning::NotPerformedResponse::err_1104(),
|
|
)
|
|
.map_err(|_| Error::custom("creation of not performed response should never fail"))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// # CAs as children
|
|
///
|
|
impl CaManager {
|
|
/// Adds a new parent, or updates an existing parent of a CA. Adding a parent will trigger that the
|
|
/// CA connects to this new parent in order to learn its resource entitlements and set up the resource
|
|
/// class(es) under it, and request certificate(s).
|
|
pub async fn ca_parent_add_or_update(
|
|
&self,
|
|
handle: CaHandle,
|
|
parent_req: ParentCaReq,
|
|
actor: &Actor,
|
|
) -> KrillResult<()> {
|
|
let ca = self.get_ca(&handle).await?;
|
|
|
|
let (parent, response) = parent_req.unpack();
|
|
let contact = ParentCaContact::for_rfc8183_parent_response(response)
|
|
.map_err(|e| Error::CaParentResponseInvalid(handle.clone(), e.to_string()))?;
|
|
|
|
let cmd = if !ca.parent_known(&parent) {
|
|
CmdDet::add_parent(&handle, parent, contact, actor)
|
|
} else {
|
|
CmdDet::update_parent(&handle, parent, contact, actor)
|
|
};
|
|
|
|
self.send_ca_command(cmd).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Removes a parent from a CA, this will trigger that best effort revocations of existing
|
|
/// keys under this parent are requested. Any resource classes under the parent will be removed
|
|
/// and all relevant content will be withdrawn from the repository.
|
|
pub async fn ca_parent_remove(&self, handle: CaHandle, parent: ParentHandle, actor: &Actor) -> KrillResult<()> {
|
|
// best effort, request revocations for any remaining keys under this parent.
|
|
if let Err(e) = self.ca_parent_revoke(&handle, &parent).await {
|
|
warn!(
|
|
"Removing parent '{}' from CA '{}', but could not send revoke requests: {}",
|
|
parent, handle, e
|
|
);
|
|
}
|
|
|
|
self.status_store.remove_parent(&handle, &parent)?;
|
|
|
|
let upd = CmdDet::remove_parent(&handle, parent, actor);
|
|
self.send_ca_command(upd).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Send revocation requests for a parent of a CA when the parent is removed.
|
|
pub async fn ca_parent_revoke(&self, handle: &CaHandle, parent: &ParentHandle) -> KrillResult<()> {
|
|
let ca = self.get_ca(handle).await?;
|
|
let revoke_requests = ca.revoke_under_parent(parent, &self.signer)?;
|
|
self.send_revoke_requests(handle, parent, revoke_requests).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Schedule refreshing all CAs as soon as possible:
|
|
///
|
|
/// Note: this function can be called manually through the API, but normally the
|
|
/// CA refresh process is replanned on the task queue automatically.
|
|
pub async fn cas_schedule_refresh_all(&self) {
|
|
if let Ok(cas) = self.ca_store.list() {
|
|
for ca_handle in cas {
|
|
self.cas_schedule_refresh_single(ca_handle).await;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Refresh a single CA with its parents, and possibly suspend inactive children.
|
|
pub async fn cas_schedule_refresh_single(&self, ca_handle: CaHandle) {
|
|
self.ca_schedule_sync_parents(&ca_handle).await;
|
|
}
|
|
|
|
/// Schedule check suspending any children under all CAs as soon as possible:
|
|
///
|
|
/// Note: this function can be called manually through the API, but normally this
|
|
/// is replanned on the task queue automatically IF suspension is enabled.
|
|
pub fn cas_schedule_suspend_all(&self) {
|
|
if self.config.suspend_child_after_inactive_seconds().is_some() {
|
|
if let Ok(cas) = self.ca_store.list() {
|
|
for ca_handle in cas {
|
|
self.tasks.suspend_children(ca_handle, now());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Suspend child CAs
|
|
pub async fn ca_suspend_inactive_children(&self, ca_handle: &CaHandle, started: Timestamp, actor: &Actor) {
|
|
// Set threshold hours if it was configured AND this server has been started
|
|
// longer ago than the hours specified. Otherwise we risk that *all* children
|
|
// without prior recorded status are suspended on upgrade, or that *all* children
|
|
// are suspended if the server had been down for more than the threshold hours.
|
|
let threshold_seconds = self
|
|
.config
|
|
.suspend_child_after_inactive_seconds()
|
|
.filter(|secs| started < Timestamp::now_minus_seconds(*secs));
|
|
|
|
// suspend inactive children, if so configured
|
|
if let Some(threshold_seconds) = threshold_seconds {
|
|
if let Ok(ca_status) = self.get_ca_status(ca_handle).await {
|
|
let connections = ca_status.get_children_connection_stats();
|
|
|
|
for child in connections.suspension_candidates(threshold_seconds) {
|
|
let threshold_string = if threshold_seconds >= 3600 {
|
|
format!("{} hours", threshold_seconds / 3600)
|
|
} else {
|
|
format!("{} seconds", threshold_seconds)
|
|
};
|
|
|
|
info!(
|
|
"Child '{}' under CA '{}' was inactive for more than {}. Will suspend it.",
|
|
child, ca_handle, threshold_string
|
|
);
|
|
if let Err(e) = self.status_store.set_child_suspended(ca_handle, &child) {
|
|
panic!("System level error encountered while updating ca status: {}", e);
|
|
}
|
|
|
|
let req = UpdateChildRequest::suspend();
|
|
if let Err(e) = self.ca_child_update(ca_handle, child, req, actor).await {
|
|
error!("Could not suspend inactive child, error: {}", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Synchronizes a CA with its parents - up to the configures batch size.
|
|
/// Remaining parents will be done in a future run.
|
|
async fn ca_schedule_sync_parents(&self, ca_handle: &CaHandle) {
|
|
if let Ok(ca) = self.get_ca(ca_handle).await {
|
|
// get updates from parents
|
|
{
|
|
if ca.nr_parents() <= self.config.ca_refresh_parents_batch_size {
|
|
// Nr of parents is below batch size, so just process all of them
|
|
for parent in ca.parents() {
|
|
self.tasks.sync_parent(ca_handle.clone(), parent.clone(), now());
|
|
}
|
|
} else {
|
|
// more parents than the batch size exist, so get candidates based on
|
|
// the known parent statuses for this CA.
|
|
let status = self.status_store.get_ca_status(ca_handle);
|
|
|
|
for parent in status
|
|
.parents()
|
|
.sync_candidates(ca.parents().collect(), self.config.ca_refresh_parents_batch_size)
|
|
{
|
|
self.tasks.sync_parent(ca_handle.clone(), parent, now());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Synchronizes a CA with one of its parents:
|
|
/// - send pending requests if present; otherwise
|
|
/// - get and process updated entitlements
|
|
///
|
|
/// Note: if new request events are generated as a result of processing updated entitlements
|
|
/// then they will trigger that this synchronization is called again so that the pending
|
|
/// requests can be sent.
|
|
pub async fn ca_sync_parent(&self, handle: &CaHandle, parent: &ParentHandle, actor: &Actor) -> KrillResult<()> {
|
|
let ca = self.get_ca(handle).await?;
|
|
|
|
if ca.has_pending_requests(parent) {
|
|
self.send_requests(handle, parent, actor).await
|
|
} else {
|
|
self.get_updates_from_parent(handle, parent, actor).await
|
|
}
|
|
}
|
|
|
|
/// Synchronise the Trust Anchor Proxy with the Signer - it the Signer is local.
|
|
pub async fn sync_ta_proxy_signer_if_possible(&self) -> KrillResult<()> {
|
|
let ta_handle = ta_handle();
|
|
|
|
if let Ok(mut proxy) = self.get_trust_anchor_proxy().await {
|
|
if let Ok(mut signer) = self.get_trust_anchor_signer().await {
|
|
// make sign request in proxy
|
|
let sign_request_cmd = TrustAnchorProxyCommand::make_signer_request(&ta_handle, &self.system_actor);
|
|
proxy = self.send_ta_proxy_command(sign_request_cmd).await?;
|
|
|
|
// get sign request for signer
|
|
let signed_request = proxy.get_signer_request(&self.signer)?;
|
|
let request_nonce = signed_request.content().nonce.clone(); // remember so we can retrieve it
|
|
|
|
// let signer process request
|
|
let signer_process_request_cmd = TrustAnchorSignerCommand::make_process_request_command(
|
|
&ta_handle,
|
|
signed_request,
|
|
self.signer.clone(),
|
|
&self.system_actor,
|
|
);
|
|
signer = self.send_ta_signer_command(signer_process_request_cmd).await?;
|
|
|
|
// get the response from the signer and give it to the proxy
|
|
let exchange = signer.get_exchange(&request_nonce).unwrap();
|
|
let proxy_process_response_cmd = TrustAnchorProxyCommand::process_signer_response(
|
|
&ta_handle,
|
|
exchange.clone().response,
|
|
&self.system_actor,
|
|
);
|
|
self.send_ta_proxy_command(proxy_process_response_cmd).await?;
|
|
} else {
|
|
warn!("There is at least one pending request for the TA signer. Plan a signing session!")
|
|
}
|
|
} else {
|
|
debug!("Sync TA proxy signer was called without a TA proxy.. this is rather odd..")
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Try to get updates from a specific parent of a CA.
|
|
async fn get_updates_from_parent(
|
|
&self,
|
|
handle: &CaHandle,
|
|
parent: &ParentHandle,
|
|
actor: &Actor,
|
|
) -> KrillResult<()> {
|
|
if handle != &ta_handle() {
|
|
let ca = self.get_ca(handle).await?;
|
|
|
|
// Return an error if the repository was not configured yet.
|
|
ca.repository_contact()?;
|
|
|
|
let ca = self.get_ca(handle).await?;
|
|
let parent_contact = ca.parent(parent)?;
|
|
let entitlements = self
|
|
.get_entitlements_from_contact(handle, parent, parent_contact, true)
|
|
.await?;
|
|
|
|
self.update_entitlements(handle, parent.clone(), entitlements, actor)
|
|
.await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Sends requests to a specific parent for the CA matching handle.
|
|
async fn send_requests(&self, handle: &CaHandle, parent: &ParentHandle, actor: &Actor) -> KrillResult<()> {
|
|
self.send_revoke_requests_handle_responses(handle, parent, actor)
|
|
.await?;
|
|
self.send_cert_requests_handle_responses(handle, parent, actor).await
|
|
}
|
|
|
|
async fn send_revoke_requests_handle_responses(
|
|
&self,
|
|
handle: &CaHandle,
|
|
parent: &ParentHandle,
|
|
actor: &Actor,
|
|
) -> KrillResult<()> {
|
|
let child = self.get_ca(handle).await?;
|
|
let requests = child.revoke_requests(parent);
|
|
|
|
let revoke_responses = self.send_revoke_requests(handle, parent, requests).await?;
|
|
|
|
for (rcn, revoke_responses) in revoke_responses.into_iter() {
|
|
for response in revoke_responses.into_iter() {
|
|
let cmd = CmdDet::key_roll_finish(handle, rcn.clone(), response, actor);
|
|
self.send_ca_command(cmd).await?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn send_revoke_requests(
|
|
&self,
|
|
handle: &CaHandle,
|
|
parent: &ParentHandle,
|
|
revoke_requests: HashMap<ResourceClassName, Vec<RevocationRequest>>,
|
|
) -> KrillResult<HashMap<ResourceClassName, Vec<RevocationResponse>>> {
|
|
let child = self.get_ca(handle).await?;
|
|
|
|
let server_info = child.parent(parent)?.parent_server_info();
|
|
let parent_uri = server_info.service_uri();
|
|
|
|
match self
|
|
.send_revoke_requests_rfc6492(
|
|
revoke_requests,
|
|
&child.id_cert().public_key().key_identifier(),
|
|
server_info,
|
|
)
|
|
.await
|
|
{
|
|
Err(e) => {
|
|
self.status_store.set_parent_failure(handle, parent, parent_uri, &e)?;
|
|
Err(e)
|
|
}
|
|
Ok(res) => {
|
|
self.status_store.set_parent_last_updated(handle, parent, parent_uri)?;
|
|
Ok(res)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn send_revoke_unexpected_key(
|
|
&self,
|
|
handle: &CaHandle,
|
|
rcn: ResourceClassName,
|
|
revocation: RevocationRequest,
|
|
) -> KrillResult<HashMap<ResourceClassName, Vec<RevocationResponse>>> {
|
|
let child = self.ca_store.get_latest(handle)?;
|
|
let parent = child.parent_for_rc(&rcn)?;
|
|
let mut requests = HashMap::new();
|
|
requests.insert(rcn, vec![revocation]);
|
|
|
|
self.send_revoke_requests(handle, parent, requests).await
|
|
}
|
|
|
|
async fn send_revoke_requests_rfc6492(
|
|
&self,
|
|
revoke_requests: HashMap<ResourceClassName, Vec<RevocationRequest>>,
|
|
signing_key: &KeyIdentifier,
|
|
server_info: &ParentServerInfo,
|
|
) -> KrillResult<HashMap<ResourceClassName, Vec<RevocationResponse>>> {
|
|
let mut revoke_map = HashMap::new();
|
|
|
|
for (rcn, revoke_requests) in revoke_requests.into_iter() {
|
|
let mut revocations = vec![];
|
|
for req in revoke_requests.into_iter() {
|
|
let sender = server_info.child_handle().convert();
|
|
let recipient = server_info.parent_handle().convert();
|
|
|
|
let revoke = provisioning::Message::revoke(sender, recipient, req.clone());
|
|
|
|
let response = self
|
|
.send_rfc6492_and_validate_response(revoke, server_info, signing_key)
|
|
.await?;
|
|
|
|
let payload = response.into_payload();
|
|
let payload_type = payload.payload_type();
|
|
|
|
match payload {
|
|
provisioning::Payload::RevokeResponse(revoke_response) => revocations.push(revoke_response),
|
|
provisioning::Payload::ErrorResponse(e) => {
|
|
if e.status() == 1101 || e.status() == 1104 {
|
|
// If we get one of the following responses:
|
|
// 1101 already processing request
|
|
// 1104 request scheduled for processing
|
|
//
|
|
// Then we asked the parent, but don't have a revocation response yet.
|
|
//
|
|
// This is okay.. there is nothing to do but ask again later. This should really
|
|
// only happen for a CA that operates under the *local* Trust Anchor. The Krill
|
|
// TA uses a 'proxy' part for online functions, such as talking to children,
|
|
// and a 'signer' part for signing, which may happen offline - and much later.
|
|
//
|
|
// By not adding any response to the returned hash we ensure that the old key
|
|
// remains in use (for a manifest and CRL only) until we get the revocation response
|
|
// when we ask later.
|
|
//
|
|
// When the local TA 'proxy' receives new signed responses from the 'signer' then it
|
|
// will trigger all local children to sync again. That time, they should see a response.
|
|
} else if e.status() == 1301 || e.status() == 1302 {
|
|
// If we get one of the following responses:
|
|
// 1301 revoke - no such resource class
|
|
// 1302 revoke - no such key
|
|
//
|
|
// Then we can consider this revocation redundant from the parent side, so just add it
|
|
// as revoked to this CA and move on. While this may be unexpected this is unlikely to
|
|
// be a problem. If we would keep insisting that the parent revokes a key they already
|
|
// revoked, then we can end up in a stuck loop.
|
|
//
|
|
// More importantly we should re-sync things if we get 12** errors to certificate sign
|
|
// requests, but that is done in another function.
|
|
let revoke_response = (&req).into();
|
|
revocations.push(revoke_response)
|
|
} else {
|
|
return Err(Error::Rfc6492NotPerformed(e));
|
|
}
|
|
}
|
|
_ => {
|
|
return Err(Error::custom(format!(
|
|
"Got unexpected response type '{}' to revoke query",
|
|
payload_type
|
|
)))
|
|
}
|
|
}
|
|
}
|
|
|
|
revoke_map.insert(rcn, revocations);
|
|
}
|
|
|
|
Ok(revoke_map)
|
|
}
|
|
|
|
async fn send_cert_requests_handle_responses(
|
|
&self,
|
|
ca_handle: &CaHandle,
|
|
parent: &ParentHandle,
|
|
actor: &Actor,
|
|
) -> KrillResult<()> {
|
|
let ca = self.get_ca(ca_handle).await?;
|
|
let requests = ca.cert_requests(parent);
|
|
let signing_key = ca.id_cert().public_key().key_identifier();
|
|
let server_info = ca.parent(parent)?.parent_server_info();
|
|
|
|
// We may need to do work for multiple resource class and there may therefore be
|
|
// multiple errors. We want to keep track of those, rather than bailing out on the
|
|
// first error, because an issue in one resource class does not necessarily mean
|
|
// that there should be an issue in the the others.
|
|
//
|
|
// Of course for most CAs there will only be one resource class under a parent,
|
|
// but we need to be prepared to deal with N classes.
|
|
let mut errors = vec![];
|
|
|
|
for (rcn, requests) in requests.into_iter() {
|
|
// We could have multiple requests in a single resource class (multiple keys during rollover)
|
|
for req in requests {
|
|
let sender = server_info.child_handle().convert();
|
|
let recipient = server_info.parent_handle().convert();
|
|
|
|
let msg = provisioning::Message::issue(sender, recipient, req);
|
|
|
|
match self
|
|
.send_rfc6492_and_validate_response(msg, server_info, &signing_key)
|
|
.await
|
|
{
|
|
Err(e) => {
|
|
// If any of the requests for an RC results in an error, then
|
|
// record the error and break the loop. We will sync again.
|
|
errors.push(Error::CaParentSyncError(
|
|
ca_handle.clone(),
|
|
parent.clone(),
|
|
rcn.clone(),
|
|
e.to_string(),
|
|
));
|
|
break;
|
|
}
|
|
Ok(response) => {
|
|
let payload = response.into_payload();
|
|
let payload_type = payload.payload_type();
|
|
|
|
match payload {
|
|
provisioning::Payload::IssueResponse(response) => {
|
|
// Update the received certificate.
|
|
//
|
|
// In a typical exchange we will only have one key under an RC under a
|
|
// parent. During a key roll there may be multiple keys and requests. It
|
|
// is still fine to update the received certificate for key "A" even if we
|
|
// would get an error for the request for key "B". The reason is such an
|
|
// *unlikely* failure would still trigger an appropriate response at
|
|
// the resource class level in the next loop iteration below.
|
|
let issued = response.into_issued();
|
|
let (uri, limit, cert) = issued.unpack();
|
|
|
|
match ResourceSet::try_from(&cert) {
|
|
Err(e) => {
|
|
// Cannot get resources from the issued certificate. This should
|
|
// never happen, but it would occur if the parent gave us a certificate
|
|
// with 'inherited' resources. This may be allowed under RFC 6492,
|
|
// or rather.. it's not strictly disallowed as perhaps it should be?
|
|
//
|
|
// In any case.. report the error - but we do not expect that this
|
|
// will happen in the wild.
|
|
|
|
// push the error for reporting, this will also trigger that the CA will
|
|
// sync with its parent again - and then it will just find revocation
|
|
// requests for this RC - which are sent on a best effort basis
|
|
errors.push(Error::CaParentSyncError(
|
|
ca_handle.clone(),
|
|
parent.clone(),
|
|
rcn.clone(),
|
|
format!("cannot parse resources on received certificate, error: {}", e),
|
|
));
|
|
break;
|
|
}
|
|
Ok(resources) => {
|
|
match ReceivedCert::create(cert, uri, resources, limit) {
|
|
Err(e) => {
|
|
errors.push(Error::CaParentSyncError(
|
|
ca_handle.clone(),
|
|
parent.clone(),
|
|
rcn.clone(),
|
|
format!("cannot use issued certificate, error: {}", e),
|
|
));
|
|
break;
|
|
}
|
|
Ok(rcvd_cert) => {
|
|
if let Err(e) = self
|
|
.send_ca_command(CmdDet::upd_received_cert(
|
|
ca_handle,
|
|
rcn.clone(),
|
|
rcvd_cert,
|
|
self.config.clone(),
|
|
self.signer.clone(),
|
|
actor,
|
|
))
|
|
.await
|
|
{
|
|
// Note that sending the command to update a received certificate
|
|
// cannot fail unless there are bigger issues like this being the wrong
|
|
// response for this resource class. This would be extremely odd because
|
|
// we only just asked the resource class which request to send. Still, in
|
|
// order to handle this the most graceful way we can, we should just drop
|
|
// this resource class and report an error. If there are are still resource
|
|
// entitlements under the parent for this resource class, then a new class
|
|
// will be automatically created when we synchronize the entitlements again.
|
|
|
|
let reason =
|
|
format!("cannot process received certificate! error: {}", e);
|
|
|
|
self.send_ca_command(CmdDet::drop_resource_class(
|
|
ca_handle,
|
|
rcn.clone(),
|
|
reason.clone(),
|
|
self.signer.clone(),
|
|
actor,
|
|
))
|
|
.await?;
|
|
|
|
// push the error for reporting, this will also trigger that the CA will
|
|
// sync with its parent again - and then it will just find revocation
|
|
// requests for this RC - which are sent on a best effort basis
|
|
errors.push(Error::CaParentSyncError(
|
|
ca_handle.clone(),
|
|
parent.clone(),
|
|
rcn.clone(),
|
|
reason,
|
|
));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
provisioning::Payload::ErrorResponse(not_performed) => {
|
|
match not_performed.status() {
|
|
1101 | 1104 => {
|
|
// If we get one of the following responses:
|
|
// 1101 already processing request
|
|
// 1104 request scheduled for processing
|
|
//
|
|
// Then we asked the parent, but don't have a signed certificate yet.
|
|
//
|
|
// This is okay.. there is nothing to do but ask again later. This should really
|
|
// only happen for a CA that operates under the *local* Trust Anchor. The Krill
|
|
// TA uses a 'proxy' part for online functions, such as talking to children,
|
|
// and a 'signer' part for signing, which may happen offline - and much later.
|
|
//
|
|
// If the local TA 'proxy' receives new signed responses from the 'signer' then it
|
|
// will trigger all local children to sync again. That time, they should see a response.
|
|
}
|
|
1201 | 1202 => {
|
|
// Okay, so it looks like the parent *just* told the CA that it was entitled
|
|
// to certain resources in a resource class and now in response to certificate
|
|
// sign request they say the resource class is gone (1201), or there are no resources
|
|
// in it (1202). This can happen as a result of a race condition if the child CA
|
|
// was asking the entitlements just moments before the parent removed them.
|
|
|
|
let reason = "parent removed entitlement to resource class".to_string();
|
|
|
|
self.send_ca_command(CmdDet::drop_resource_class(
|
|
ca_handle,
|
|
rcn.clone(),
|
|
reason.clone(),
|
|
self.signer.clone(),
|
|
actor,
|
|
))
|
|
.await?;
|
|
|
|
// push the error for reporting, this will also trigger that the CA will
|
|
// sync with its parent again - and then it will just find revocation
|
|
// requests for this RC - which are sent on a best effort basis
|
|
errors.push(Error::CaParentSyncError(
|
|
ca_handle.clone(),
|
|
parent.clone(),
|
|
rcn.clone(),
|
|
reason,
|
|
));
|
|
break;
|
|
}
|
|
1204 => {
|
|
// The parent says that the CA is re-using a key across RCs. Krill CAs never
|
|
// re-use keys - so this is extremely unlikely. Still there seems to be a
|
|
// disagreement and in this case the parent has the last word. Recovering by
|
|
// dropping all keys in the RC and making a new pending key should be possible,
|
|
// but it's complicated with regards to corner cases: e.g. what if we were in
|
|
// the middle of key roll..
|
|
//
|
|
// So, the most straightforward way to deal with this is by dropping this current
|
|
// RC altogether. Then the CA will find its resource entitlements in a future
|
|
// synchronization with the parent and just create a new RC - and issue all
|
|
// eligible certificates and ROAs under it.
|
|
|
|
let reason = "parent claims we are re-using keys".to_string();
|
|
self.send_ca_command(CmdDet::drop_resource_class(
|
|
ca_handle,
|
|
rcn.clone(),
|
|
reason.clone(),
|
|
self.signer.clone(),
|
|
actor,
|
|
))
|
|
.await?;
|
|
|
|
// push the error for reporting, this will also trigger that the CA will
|
|
// sync with its parent again - and then it will just find revocation
|
|
// requests for this RC - which are sent on a best effort basis
|
|
errors.push(Error::CaParentSyncError(
|
|
ca_handle.clone(),
|
|
parent.clone(),
|
|
rcn.clone(),
|
|
reason,
|
|
));
|
|
break;
|
|
}
|
|
_ => {
|
|
// Other not performed responses can be due to temporary issues at the
|
|
// parent (e.g. it had an internal error of some kind), or because of
|
|
// protocol version mismatches and such (in future maybe?).
|
|
//
|
|
// In any event we cannot take any action to recover, so just report
|
|
// them and let the schedular try to sync with the parent again.
|
|
let issue = format!(
|
|
"parent returned not performed response to certificate request: {}",
|
|
not_performed
|
|
);
|
|
errors.push(Error::CaParentSyncError(
|
|
ca_handle.clone(),
|
|
parent.clone(),
|
|
rcn.clone(),
|
|
issue,
|
|
));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
let issue =
|
|
format!("unexpected response type '{}' to a certificate request", payload_type);
|
|
errors.push(Error::CaParentSyncError(
|
|
ca_handle.clone(),
|
|
parent.clone(),
|
|
rcn.clone(),
|
|
issue,
|
|
));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let uri = server_info.service_uri();
|
|
if errors.is_empty() {
|
|
self.status_store.set_parent_last_updated(ca_handle, parent, uri)?;
|
|
|
|
Ok(())
|
|
} else {
|
|
let e = if errors.len() == 1 {
|
|
errors.pop().unwrap()
|
|
} else {
|
|
Error::Multiple(errors)
|
|
};
|
|
|
|
self.status_store.set_parent_failure(ca_handle, parent, uri, &e)?;
|
|
|
|
Err(e)
|
|
}
|
|
}
|
|
|
|
/// Updates the CA resource classes, if entitlements are different from
|
|
/// what the CA currently has under this parent. Returns [`Ok(true)`] in
|
|
/// case there were any updates, implying that there will be open requests
|
|
/// for the parent CA.
|
|
async fn update_entitlements(
|
|
&self,
|
|
ca: &CaHandle,
|
|
parent: ParentHandle,
|
|
entitlements: ResourceClassListResponse,
|
|
actor: &Actor,
|
|
) -> KrillResult<bool> {
|
|
let current_version = self.get_ca(ca).await?.version();
|
|
|
|
let update_entitlements_command =
|
|
CmdDet::update_entitlements(ca, parent, entitlements, self.signer.clone(), actor);
|
|
|
|
let new_version = self.send_ca_command(update_entitlements_command).await?.version();
|
|
|
|
Ok(new_version > current_version)
|
|
}
|
|
|
|
pub async fn get_entitlements_from_contact(
|
|
&self,
|
|
ca: &CaHandle,
|
|
parent: &ParentHandle,
|
|
contact: &ParentCaContact,
|
|
existing_parent: bool,
|
|
) -> KrillResult<ResourceClassListResponse> {
|
|
let server_info = contact.parent_server_info();
|
|
let uri = server_info.service_uri();
|
|
|
|
let result = self.get_entitlements_rfc6492(ca, server_info).await;
|
|
|
|
match &result {
|
|
Err(error) => {
|
|
if existing_parent {
|
|
// only update the status store with errors for existing parents
|
|
// otherwise we end up with entries if a new parent is rejected because
|
|
// of the error.
|
|
self.status_store.set_parent_failure(ca, parent, uri, error)?;
|
|
}
|
|
}
|
|
Ok(entitlements) => {
|
|
self.status_store
|
|
.set_parent_entitlements(ca, parent, uri, entitlements)?;
|
|
}
|
|
}
|
|
result
|
|
}
|
|
|
|
async fn get_entitlements_rfc6492(
|
|
&self,
|
|
handle: &CaHandle,
|
|
server_info: &ParentServerInfo,
|
|
) -> KrillResult<ResourceClassListResponse> {
|
|
debug!(
|
|
"Getting entitlements for CA '{}' from parent '{}'",
|
|
handle,
|
|
server_info.parent_handle()
|
|
);
|
|
|
|
let child = self.ca_store.get_latest(handle)?;
|
|
|
|
// create a list request
|
|
let sender = server_info.child_handle().convert();
|
|
let recipient = server_info.parent_handle().convert();
|
|
|
|
let list = provisioning::Message::list(sender, recipient);
|
|
|
|
let response = self
|
|
.send_rfc6492_and_validate_response(list, server_info, &child.id_cert().public_key().key_identifier())
|
|
.await?;
|
|
|
|
let payload = response.into_payload();
|
|
let payload_type = payload.payload_type();
|
|
|
|
match payload {
|
|
provisioning::Payload::ErrorResponse(np) => Err(Error::Custom(format!("Not performed: {}", np))),
|
|
provisioning::Payload::ListResponse(response) => Ok(response),
|
|
_ => Err(Error::custom(format!(
|
|
"Got unexpected response type '{}' to list query",
|
|
payload_type
|
|
))),
|
|
}
|
|
}
|
|
|
|
async fn send_rfc6492_and_validate_response(
|
|
&self,
|
|
message: provisioning::Message,
|
|
server_info: &ParentServerInfo,
|
|
signing_key: &KeyIdentifier,
|
|
) -> KrillResult<provisioning::Message> {
|
|
let service_uri = server_info.service_uri();
|
|
if let Some(parent) = Self::local_parent(service_uri, &self.config.service_uri()) {
|
|
let ca_handle = parent.into_converted();
|
|
let user_agent = Some("local-child".to_string());
|
|
|
|
self.rfc6492_process_request(&ca_handle, message, user_agent, &self.system_actor)
|
|
.await
|
|
} else {
|
|
// Set up a logger for CMS exchanges. Note that this logger is always set
|
|
// up and used, but.. it will only actually save files in case the given
|
|
// rfc6492_log_dir is Some.
|
|
let cms_logger = CmsLogger::for_rfc6492_sent(
|
|
self.config.rfc6492_log_dir.as_ref(),
|
|
message.sender(),
|
|
message.recipient(),
|
|
);
|
|
|
|
let cms = self.signer.create_rfc6492_cms(message, signing_key)?.to_bytes();
|
|
|
|
let res_bytes = self
|
|
.post_protocol_cms_binary(&cms, service_uri, provisioning::CONTENT_TYPE, &cms_logger)
|
|
.await?;
|
|
|
|
match ProvisioningCms::decode(&res_bytes) {
|
|
Err(e) => {
|
|
cms_logger.err(format!("Could not decode CMS: {}", e))?;
|
|
Err(Error::Rfc6492(e))
|
|
}
|
|
Ok(cms) => match cms.validate(server_info.id_cert().public_key()) {
|
|
Err(e) => {
|
|
cms_logger.err(format!("Response invalid: {}", e))?;
|
|
Err(Error::Rfc6492(e))
|
|
}
|
|
Ok(()) => Ok(cms.into_message()),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn post_protocol_cms_binary(
|
|
&self,
|
|
msg: &Bytes,
|
|
service_uri: &ServiceUri,
|
|
content_type: &str,
|
|
cms_logger: &CmsLogger,
|
|
) -> KrillResult<Bytes> {
|
|
cms_logger.sent(msg)?;
|
|
|
|
let timeout = self.config.post_protocol_msg_timeout_seconds;
|
|
|
|
match httpclient::post_binary_with_full_ua(service_uri.as_str(), msg, content_type, timeout).await {
|
|
Err(e) => {
|
|
cms_logger.err(format!("Error posting CMS: {}", e))?;
|
|
Err(Error::HttpClientError(e))
|
|
}
|
|
Ok(bytes) => {
|
|
cms_logger.reply(&bytes)?;
|
|
Ok(bytes)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns the handle of the local parent for this specific ServiceUri, and the
|
|
/// configured base (service) URI. Provided that this indeed maps back to this
|
|
/// same server and it is an RFC 6492 style Krill URI.
|
|
pub fn local_parent(service_uri: &ServiceUri, base_uri: &uri::Https) -> Option<ParentHandle> {
|
|
match &service_uri {
|
|
ServiceUri::Http(_) => None,
|
|
ServiceUri::Https(service_uri) => {
|
|
let service_uri = service_uri.as_str();
|
|
let base_uri = base_uri.as_str();
|
|
|
|
if let Some(path) = service_uri.strip_prefix(base_uri) {
|
|
if let Some(ca_name) = path.strip_prefix("rfc6492/") {
|
|
return ParentHandle::from_str(ca_name).ok();
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// # Publishing
|
|
///
|
|
impl CaManager {
|
|
/// Schedule synchronizing all CAs with their repositories.
|
|
pub fn cas_schedule_repo_sync_all(&self, actor: &Actor) {
|
|
match self.ca_list(actor) {
|
|
Ok(ca_list) => {
|
|
for ca in ca_list.cas() {
|
|
self.cas_schedule_repo_sync(ca.handle().clone());
|
|
}
|
|
}
|
|
Err(e) => error!("Could not get CA list! {}", e),
|
|
}
|
|
}
|
|
|
|
/// Schedule synchronizing all CAs with their repositories.
|
|
pub fn cas_schedule_repo_sync(&self, ca: CaHandle) {
|
|
self.tasks.sync_repo(ca, now());
|
|
}
|
|
|
|
/// Synchronize a CA with its repositories.
|
|
///
|
|
/// Note typically a CA will have only one active repository, but in case
|
|
/// there are multiple during a migration, this function will ensure that
|
|
/// they are all synchronized.
|
|
///
|
|
/// In case the CA had deprecated repositories, then a clean up will be
|
|
/// attempted. I.e. the CA will try to withdraw all objects from the deprecated
|
|
/// repository. If this clean up fails then the number of clean-up attempts
|
|
/// for the repository in question is incremented, and this function will
|
|
/// fail. When there have been 5 failed attempts, then the old repository
|
|
/// is assumed to be unreachable and it will be dropped - i.e. the CA will
|
|
/// no longer try to clean up objects.
|
|
pub async fn cas_repo_sync_single(
|
|
&self,
|
|
repo_manager: &RepositoryManager,
|
|
ca_handle: &CaHandle,
|
|
) -> KrillResult<()> {
|
|
// Note that this is a no-op for new CAs which do not yet have any repository configured.
|
|
if ca_handle.as_str() == TA_NAME {
|
|
let proxy = self.get_trust_anchor_proxy().await?;
|
|
let id = proxy.id();
|
|
let repo = proxy.repository().ok_or(Error::TaProxyHasNoRepository)?;
|
|
let objects = proxy.get_trust_anchor_objects()?.publish_elements()?;
|
|
|
|
self.ca_repo_sync(repo_manager, ca_handle, id, repo, objects).await
|
|
} else {
|
|
let ca = self.get_ca(ca_handle).await?;
|
|
for (repo_contact, objects) in self.ca_repo_elements(ca_handle).await? {
|
|
self.ca_repo_sync(repo_manager, ca_handle, ca.id_cert(), &repo_contact, objects)
|
|
.await?;
|
|
}
|
|
|
|
// Clean-up of old repos
|
|
for deprecated in self.ca_deprecated_repos(ca_handle)? {
|
|
info!(
|
|
"Will try to clean up deprecated repository '{}' for CA '{}'",
|
|
deprecated.contact(),
|
|
ca_handle
|
|
);
|
|
|
|
if let Err(e) = self
|
|
.ca_repo_sync(repo_manager, ca_handle, ca.id_cert(), deprecated.contact(), vec![])
|
|
.await
|
|
{
|
|
warn!("Could not clean up deprecated repository: {}", e);
|
|
|
|
if deprecated.clean_attempts() < 5 {
|
|
self.ca_deprecated_repo_increment_clean_attempts(ca_handle, deprecated.contact())?;
|
|
return Err(e);
|
|
}
|
|
}
|
|
|
|
self.ca_deprecated_repo_remove(ca_handle, deprecated.contact())?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::mutable_key_type)]
|
|
async fn ca_repo_sync(
|
|
&self,
|
|
repo_manager: &RepositoryManager,
|
|
ca_handle: &CaHandle,
|
|
id_cert: &IdCertInfo,
|
|
repo_contact: &RepositoryContact,
|
|
publish_elements: Vec<PublishElement>,
|
|
) -> KrillResult<()> {
|
|
info!("CA '{}' sends list query to repo", ca_handle);
|
|
let list_reply = self
|
|
.send_rfc8181_list(repo_manager, ca_handle, id_cert, repo_contact.server_info())
|
|
.await?;
|
|
|
|
let elements: HashMap<_, _> = list_reply.into_elements().into_iter().map(|el| el.unpack()).collect();
|
|
|
|
let mut all_objects: HashMap<_, _> = publish_elements.into_iter().map(|el| el.unpack()).collect();
|
|
|
|
let mut delta = PublishDelta::empty();
|
|
|
|
for (uri, hash) in elements.into_iter() {
|
|
match all_objects.remove(&uri) {
|
|
Some(base64) => {
|
|
if base64.to_hash() != hash {
|
|
delta.add_update(Update::new(None, uri, base64, hash))
|
|
}
|
|
}
|
|
None => delta.add_withdraw(Withdraw::new(None, uri, hash)),
|
|
}
|
|
}
|
|
|
|
for (uri, base64) in all_objects {
|
|
delta.add_publish(Publish::new(None, uri, base64));
|
|
}
|
|
|
|
if !delta.is_empty() {
|
|
info!("CA '{}' sends delta", ca_handle);
|
|
self.send_rfc8181_delta(repo_manager, ca_handle, id_cert, repo_contact.server_info(), delta)
|
|
.await?;
|
|
debug!("CA '{}' sent delta", ca_handle);
|
|
} else {
|
|
info!("CA '{}' has nothing to publish", ca_handle);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Get the current objects for a CA for each repository that it's using.
|
|
///
|
|
/// Notes:
|
|
/// - typically a CA will use only one repository, but during migrations there may be multiple.
|
|
/// - these object may not have been published (yet) - check `ca_repo_status`.
|
|
pub async fn ca_repo_elements(
|
|
&self,
|
|
ca: &CaHandle,
|
|
) -> KrillResult<HashMap<RepositoryContact, Vec<PublishElement>>> {
|
|
Ok(self.ca_objects_store.ca_objects(ca)?.repo_elements_map())
|
|
}
|
|
|
|
/// Get deprecated repositories so that they can be cleaned.
|
|
pub fn ca_deprecated_repos(&self, ca: &CaHandle) -> KrillResult<Vec<DeprecatedRepository>> {
|
|
Ok(self.ca_objects_store.ca_objects(ca)?.deprecated_repos().clone())
|
|
}
|
|
|
|
/// Remove a deprecated repo
|
|
pub fn ca_deprecated_repo_remove(&self, ca: &CaHandle, to_remove: &RepositoryContact) -> KrillResult<()> {
|
|
self.ca_objects_store.with_ca_objects(ca, |objects| {
|
|
objects.deprecated_repo_remove(to_remove);
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
/// Increase the clean attempt counter for a deprecated repository
|
|
pub fn ca_deprecated_repo_increment_clean_attempts(
|
|
&self,
|
|
ca: &CaHandle,
|
|
contact: &RepositoryContact,
|
|
) -> KrillResult<()> {
|
|
self.ca_objects_store.with_ca_objects(ca, |objects| {
|
|
objects.deprecated_repo_inc_clean_attempts(contact);
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
/// Update repository where a CA publishes.
|
|
pub async fn update_repo(
|
|
&self,
|
|
repo_manager: &RepositoryManager,
|
|
ca_handle: CaHandle,
|
|
new_contact: RepositoryContact,
|
|
check_repo: bool,
|
|
actor: &Actor,
|
|
) -> KrillResult<()> {
|
|
let ca = self.get_ca(&ca_handle).await?;
|
|
if check_repo {
|
|
// First verify that this repository can be reached and responds to a list request.
|
|
self.send_rfc8181_list(repo_manager, &ca_handle, ca.id_cert(), new_contact.server_info())
|
|
.await
|
|
.map_err(|e| Error::CaRepoIssue(ca_handle.clone(), e.to_string()))?;
|
|
}
|
|
let cmd = CmdDet::update_repo(&ca_handle, new_contact, self.signer.clone(), actor);
|
|
self.send_ca_command(cmd).await?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn send_rfc8181_list(
|
|
&self,
|
|
repo_manager: &RepositoryManager,
|
|
ca_handle: &CaHandle,
|
|
id_cert: &IdCertInfo,
|
|
server_info: &PublicationServerInfo,
|
|
) -> KrillResult<ListReply> {
|
|
let uri = server_info.service_uri();
|
|
let signing_key = id_cert.public_key().key_identifier();
|
|
|
|
let message = publication::Message::list_query();
|
|
|
|
let reply = match self
|
|
.send_rfc8181_and_validate_response(repo_manager, message, server_info, ca_handle, &signing_key)
|
|
.await
|
|
{
|
|
Ok(reply) => reply,
|
|
Err(e) => {
|
|
self.status_store.set_status_repo_failure(ca_handle, uri.clone(), &e)?;
|
|
return Err(e);
|
|
}
|
|
};
|
|
|
|
match reply {
|
|
publication::Reply::List(list_reply) => {
|
|
self.status_store.set_status_repo_success(ca_handle, uri.clone())?;
|
|
Ok(list_reply)
|
|
}
|
|
publication::Reply::Success => {
|
|
let err = Error::custom("Got success reply to list query?!");
|
|
self.status_store
|
|
.set_status_repo_failure(ca_handle, uri.clone(), &err)?;
|
|
Err(err)
|
|
}
|
|
publication::Reply::ErrorReply(e) => {
|
|
let err = Error::Custom(format!("Got error reply: {}", e));
|
|
self.status_store
|
|
.set_status_repo_failure(ca_handle, uri.clone(), &err)?;
|
|
Err(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn send_rfc8181_delta(
|
|
&self,
|
|
repo_manager: &RepositoryManager,
|
|
ca_handle: &CaHandle,
|
|
id_cert: &IdCertInfo,
|
|
server_info: &PublicationServerInfo,
|
|
delta: PublishDelta,
|
|
) -> KrillResult<()> {
|
|
let uri = server_info.service_uri();
|
|
let signing_key = id_cert.public_key().key_identifier();
|
|
|
|
let message = publication::Message::delta(delta.clone());
|
|
|
|
let reply = match self
|
|
.send_rfc8181_and_validate_response(repo_manager, message, server_info, ca_handle, &signing_key)
|
|
.await
|
|
{
|
|
Ok(reply) => reply,
|
|
Err(e) => {
|
|
self.status_store.set_status_repo_failure(ca_handle, uri.clone(), &e)?;
|
|
return Err(e);
|
|
}
|
|
};
|
|
|
|
match reply {
|
|
publication::Reply::Success => {
|
|
self.status_store
|
|
.set_status_repo_published(ca_handle, uri.clone(), delta)?;
|
|
Ok(())
|
|
}
|
|
publication::Reply::ErrorReply(e) => {
|
|
let err = Error::Custom(format!("Got error reply: {}", e));
|
|
self.status_store
|
|
.set_status_repo_failure(ca_handle, uri.clone(), &err)?;
|
|
Err(err)
|
|
}
|
|
publication::Reply::List(_) => {
|
|
let err = Error::custom("Got list reply to delta query?!");
|
|
self.status_store
|
|
.set_status_repo_failure(ca_handle, uri.clone(), &err)?;
|
|
Err(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn send_rfc8181_and_validate_response(
|
|
&self,
|
|
repo_manager: &RepositoryManager,
|
|
message: publication::Message,
|
|
server_info: &PublicationServerInfo,
|
|
ca_handle: &CaHandle,
|
|
signing_key: &KeyIdentifier,
|
|
) -> KrillResult<publication::Reply> {
|
|
let repo_service_uri = server_info.service_uri();
|
|
|
|
if repo_service_uri
|
|
.as_str()
|
|
.starts_with(self.config.service_uri().as_str())
|
|
{
|
|
// this maps back to *this* Krill instance
|
|
let query = message.as_query()?;
|
|
let publisher_handle = ca_handle.convert();
|
|
let response = repo_manager.rfc8181_message(&publisher_handle, query)?;
|
|
response.as_reply().map_err(Error::Rfc8181)
|
|
} else {
|
|
// Set up a logger for CMS exchanges. Note that this logger is always set
|
|
// up and used, but.. it will only actually save files in case the given
|
|
// rfc8181_log_dir is Some.
|
|
let cms_logger = CmsLogger::for_rfc8181_sent(self.config.rfc8181_log_dir.as_ref(), ca_handle);
|
|
|
|
let cms = self.signer.create_rfc8181_cms(message, signing_key)?.to_bytes();
|
|
|
|
let res_bytes = self
|
|
.post_protocol_cms_binary(&cms, repo_service_uri, publication::CONTENT_TYPE, &cms_logger)
|
|
.await?;
|
|
|
|
match publication::PublicationCms::decode(&res_bytes) {
|
|
Err(e) => {
|
|
cms_logger.err(format!("Could not decode CMS: {}", e))?;
|
|
Err(Error::Rfc8181(e))
|
|
}
|
|
Ok(cms) => match cms.validate(server_info.public_key()) {
|
|
Err(e) => {
|
|
cms_logger.err(format!("Response invalid: {}", e))?;
|
|
Err(Error::Rfc8181(e))
|
|
}
|
|
Ok(()) => cms.into_message().as_reply().map_err(Error::Rfc8181),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// # Autonomous System Provider Authorization functions
|
|
///
|
|
impl CaManager {
|
|
/// Show current ASPA definitions for this CA.
|
|
pub async fn ca_aspas_definitions_show(&self, ca: CaHandle) -> KrillResult<AspaDefinitionList> {
|
|
let ca = self.get_ca(&ca).await?;
|
|
Ok(ca.aspas_definitions_show())
|
|
}
|
|
|
|
/// Add a new ASPA definition for this CA and the customer ASN in the update.
|
|
pub async fn ca_aspas_definitions_update(
|
|
&self,
|
|
ca: CaHandle,
|
|
updates: AspaDefinitionUpdates,
|
|
actor: &Actor,
|
|
) -> KrillResult<()> {
|
|
self.send_ca_command(CmdDet::aspas_definitions_update(
|
|
&ca,
|
|
updates,
|
|
self.config.clone(),
|
|
self.signer.clone(),
|
|
actor,
|
|
))
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Update the ASPA definition for this CA and the customer ASN in the update.
|
|
pub async fn ca_aspas_update_aspa(
|
|
&self,
|
|
ca: CaHandle,
|
|
customer: AspaCustomer,
|
|
update: AspaProvidersUpdate,
|
|
actor: &Actor,
|
|
) -> KrillResult<()> {
|
|
self.send_ca_command(CmdDet::aspas_update_aspa(
|
|
&ca,
|
|
customer,
|
|
update,
|
|
self.config.clone(),
|
|
self.signer.clone(),
|
|
actor,
|
|
))
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// # BGPSec functions
|
|
///
|
|
impl CaManager {
|
|
pub async fn ca_bgpsec_definitions_show(&self, ca: CaHandle) -> KrillResult<BgpSecCsrInfoList> {
|
|
let ca = self.get_ca(&ca).await?;
|
|
Ok(ca.bgpsec_definitions_show())
|
|
}
|
|
|
|
pub async fn ca_bgpsec_definitions_update(
|
|
&self,
|
|
ca: CaHandle,
|
|
updates: BgpSecDefinitionUpdates,
|
|
actor: &Actor,
|
|
) -> KrillResult<()> {
|
|
self.send_ca_command(CmdDet::bgpsec_update_definitions(
|
|
&ca,
|
|
updates,
|
|
self.config.clone(),
|
|
self.signer.clone(),
|
|
actor,
|
|
))
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// # Route Authorization functions
|
|
///
|
|
impl CaManager {
|
|
/// Update the routes authorized by a CA. This will trigger that ROAs
|
|
/// are made in the resource classes that contain the prefixes. If the
|
|
/// update is rejected, e.g. because the CA does not have the necessary
|
|
/// prefixes then an `Error::RoaDeltaError` will be returned.
|
|
/// If the update is successful, new manifest(s) and CRL(s) will be created,
|
|
/// and resynchronization between the CA and its repository will be triggered.
|
|
/// Finally note that ROAs may be issues on a per prefix basis, or aggregated
|
|
/// by ASN based on the defaults or values configured.
|
|
pub async fn ca_routes_update(
|
|
&self,
|
|
ca: CaHandle,
|
|
updates: RoaConfigurationUpdates,
|
|
actor: &Actor,
|
|
) -> KrillResult<()> {
|
|
self.send_ca_command(CmdDet::route_authorizations_update(
|
|
&ca,
|
|
updates,
|
|
self.config.clone(),
|
|
self.signer.clone(),
|
|
actor,
|
|
))
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Re-issue about to expire objects in all CAs. This is a no-op in case
|
|
/// ROAs do not need re-issuance. If new objects are created they will also
|
|
/// be published (event will trigger that MFT and CRL are also made, and
|
|
/// and the CA in question synchronizes with its repository).
|
|
///
|
|
/// Note: this does not re-issue issued CA certificates, because child
|
|
/// CAs are expected to note extended validity eligibility and request
|
|
/// updated certificates themselves.
|
|
pub async fn renew_objects_all(&self, actor: &Actor) -> KrillResult<()> {
|
|
for ca in self.ca_store.list()? {
|
|
let cmd = Cmd::new(
|
|
&ca,
|
|
None,
|
|
CmdDet::RouteAuthorizationsRenew(self.config.clone(), self.signer.clone()),
|
|
actor,
|
|
);
|
|
|
|
if let Err(e) = self.send_ca_command(cmd).await {
|
|
error!("Renewing ROAs for CA '{}' failed with error: {}", ca, e);
|
|
}
|
|
|
|
let cmd = Cmd::new(
|
|
&ca,
|
|
None,
|
|
CmdDet::AspasRenew(self.config.clone(), self.signer.clone()),
|
|
actor,
|
|
);
|
|
|
|
if let Err(e) = self.send_ca_command(cmd).await {
|
|
error!("Renewing ASPAs for CA '{}' failed with error: {}", ca, e);
|
|
}
|
|
|
|
let cmd = Cmd::new(
|
|
&ca,
|
|
None,
|
|
CmdDet::BgpSecRenew(self.config.clone(), self.signer.clone()),
|
|
actor,
|
|
);
|
|
|
|
if let Err(e) = self.send_ca_command(cmd).await {
|
|
error!("Renewing BGPSec certificates for CA '{}' failed with error: {}", ca, e);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Force the re-issuance of all ROAs in all CAs. This function was added
|
|
/// because we need to re-issue ROAs in Krill 0.9.3 to force that a short
|
|
/// subject CN is used for the EE certificate: i.e. the SKI rather than the
|
|
/// full public key. But there may also be other cases in future where
|
|
/// forcing to re-issue ROAs may be useful.
|
|
pub async fn force_renew_roas_all(&self, actor: &Actor) -> KrillResult<()> {
|
|
for ca in self.ca_store.list()? {
|
|
let cmd = Cmd::new(
|
|
&ca,
|
|
None,
|
|
CmdDet::RouteAuthorizationsForceRenew(self.config.clone(), self.signer.clone()),
|
|
actor,
|
|
);
|
|
if let Err(e) = self.send_ca_command(cmd).await {
|
|
error!("Renewing ROAs for CA '{}' failed with error: {}", ca, e);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// # Resource Tagged Attestation functions
|
|
///
|
|
impl CaManager {
|
|
/// Sign a one-off single-signed RTA
|
|
pub async fn rta_sign(
|
|
&self,
|
|
ca: CaHandle,
|
|
name: RtaName,
|
|
request: RtaContentRequest,
|
|
actor: &Actor,
|
|
) -> KrillResult<()> {
|
|
let cmd = CmdDet::rta_sign(&ca, name, request, self.signer.clone(), actor);
|
|
self.send_ca_command(cmd).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Prepare a multi-singed RTA
|
|
pub async fn rta_multi_prep(
|
|
&self,
|
|
ca: &CaHandle,
|
|
name: RtaName,
|
|
request: RtaPrepareRequest,
|
|
actor: &Actor,
|
|
) -> KrillResult<()> {
|
|
let cmd = CmdDet::rta_multi_prep(ca, name, request, self.signer.clone(), actor);
|
|
self.send_ca_command(cmd).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Co-sign an existing RTA
|
|
pub async fn rta_multi_cosign(
|
|
&self,
|
|
ca: CaHandle,
|
|
name: RtaName,
|
|
rta: ResourceTaggedAttestation,
|
|
actor: &Actor,
|
|
) -> KrillResult<()> {
|
|
let cmd = CmdDet::rta_multi_sign(&ca, name, rta, self.signer.clone(), actor);
|
|
self.send_ca_command(cmd).await?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// CA Key Roll functions
|
|
///
|
|
impl CaManager {
|
|
/// Initiate an RFC 6489 key roll for all active keys in a CA older than the specified duration.
|
|
pub async fn ca_keyroll_init(&self, handle: CaHandle, max_age: Duration, actor: &Actor) -> KrillResult<()> {
|
|
let init_key_roll = CmdDet::key_roll_init(&handle, max_age, self.signer.clone(), actor);
|
|
self.send_ca_command(init_key_roll).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Activate a new key, as part of the key roll process (RFC 6489). Only new keys that
|
|
/// have an age equal to or greater than the staging period are promoted. The RFC mandates
|
|
/// a staging period of 24 hours, but we may use a shorter period for testing and/or emergency
|
|
/// manual key rolls.
|
|
pub async fn ca_keyroll_activate(&self, handle: CaHandle, staging: Duration, actor: &Actor) -> KrillResult<()> {
|
|
let activate_cmd = CmdDet::key_roll_activate(&handle, staging, self.config.clone(), self.signer.clone(), actor);
|
|
self.send_ca_command(activate_cmd).await?;
|
|
Ok(())
|
|
}
|
|
}
|