mirror of
https://github.com/NLnetLabs/krill.git
synced 2026-09-28 20:34:54 +02:00
895 lines
31 KiB
Rust
895 lines
31 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use chrono::Duration;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use rpki::cert::Cert;
|
|
use rpki::crypto::KeyIdentifier;
|
|
use rpki::x509::Time;
|
|
|
|
use crate::commons::api::{
|
|
AddedObject, CurrentObject, CurrentObjects, EntitlementClass, HexEncodedHash, IssuanceRequest,
|
|
IssuanceResponse, IssuedCert, ObjectName, ObjectsDelta, ParentHandle, RcvdCert, ReplacedObject,
|
|
RepoInfo, RequestResourceLimit, ResourceClassInfo, ResourceClassName, ResourceSet, Revocation,
|
|
RevocationRequest, RevokedObject, RouteAuthorization, SigningCert, UpdatedObject,
|
|
WithdrawnObject,
|
|
};
|
|
use crate::daemon::ca::events::RoaUpdates;
|
|
use crate::daemon::ca::signing::CsrInfo;
|
|
use crate::daemon::ca::{
|
|
self, ta_handle, AddedOrUpdated, Certificates, CertifiedKey, CrlBuilder, CurrentKey,
|
|
CurrentObjectSetDelta, Error, EvtDet, KeyState, ManifestBuilder, NewKey, OldKey, PendingKey,
|
|
Result, RoaInfo, Roas, SignSupport, Signer,
|
|
};
|
|
|
|
//------------ ResourceClass -----------------------------------------------
|
|
|
|
/// A CA may have multiple parents, e.g. two RIRs, and it may not get all its
|
|
/// resource entitlements in one set, but in a number of so-called "resource
|
|
/// classes".
|
|
///
|
|
/// Each ResourceClass has a namespace, which can be anything, but for Krill
|
|
/// is based on the name of the parent ca, and the name of the resource class
|
|
/// under that parent.
|
|
///
|
|
/// Furthermore a resource class manages the key life cycle, and certificates
|
|
/// for each key, as well as products that need to be issued by the 'current'
|
|
/// key for this class.
|
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
|
pub struct ResourceClass {
|
|
name: ResourceClassName,
|
|
name_space: String,
|
|
|
|
parent_handle: ParentHandle,
|
|
parent_rc_name: ResourceClassName,
|
|
|
|
roas: Roas,
|
|
certificates: Certificates,
|
|
|
|
last_key_change: Time,
|
|
key_state: KeyState,
|
|
}
|
|
|
|
/// # Creating new instances
|
|
///
|
|
impl ResourceClass {
|
|
/// Creates a new ResourceClass with a single pending key only.
|
|
pub fn create(
|
|
name: ResourceClassName,
|
|
name_space: String,
|
|
parent_handle: ParentHandle,
|
|
parent_rc_name: ResourceClassName,
|
|
pending_key: KeyIdentifier,
|
|
) -> Self {
|
|
ResourceClass {
|
|
name,
|
|
name_space,
|
|
parent_handle,
|
|
parent_rc_name,
|
|
roas: Roas::default(),
|
|
certificates: Certificates::default(),
|
|
last_key_change: Time::now(),
|
|
key_state: KeyState::create(pending_key),
|
|
}
|
|
}
|
|
|
|
pub fn for_ta(parent_rc_name: ResourceClassName, pending_key: KeyIdentifier) -> Self {
|
|
ResourceClass {
|
|
name: parent_rc_name.clone(),
|
|
name_space: parent_rc_name.to_string(),
|
|
parent_handle: ta_handle(),
|
|
parent_rc_name,
|
|
roas: Roas::default(),
|
|
certificates: Certificates::default(),
|
|
last_key_change: Time::now(),
|
|
key_state: KeyState::create(pending_key),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// # Data Access
|
|
///
|
|
impl ResourceClass {
|
|
pub fn name_space(&self) -> &str {
|
|
&self.name_space
|
|
}
|
|
|
|
/// Returns the name of the parent where we got this RC from.
|
|
pub fn parent_handle(&self) -> &ParentHandle {
|
|
&self.parent_handle
|
|
}
|
|
|
|
/// Returns the name that the parent uses for this RC.
|
|
pub fn parent_rc_name(&self) -> &ResourceClassName {
|
|
&self.parent_rc_name
|
|
}
|
|
|
|
/// Adds a request to an existing key for future reference.
|
|
pub fn add_request(&mut self, key_id: KeyIdentifier, req: IssuanceRequest) {
|
|
self.key_state.add_request(key_id, req);
|
|
}
|
|
|
|
/// Returns the current certificate, if there is any
|
|
pub fn current_certificate(&self) -> Option<&RcvdCert> {
|
|
self.current_key().map(|k| k.incoming_cert())
|
|
}
|
|
|
|
/// Returns the current resources for this resource class
|
|
pub fn current_resources(&self) -> Option<&ResourceSet> {
|
|
self.current_certificate().map(|c| c.resources())
|
|
}
|
|
|
|
/// Returns a reference to current key for this RC, if there is any.
|
|
pub fn current_key(&self) -> Option<&CurrentKey> {
|
|
match &self.key_state {
|
|
KeyState::Active(current)
|
|
| KeyState::RollPending(_, current)
|
|
| KeyState::RollNew(_, current)
|
|
| KeyState::RollOld(current, _) => Some(current),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn get_current_key(&self) -> Result<&CurrentKey> {
|
|
self.current_key()
|
|
.ok_or_else(|| Error::ResourceClassNoCurrentKey)
|
|
}
|
|
|
|
/// Gets the new key for a key roll, or returns an error if there is none.
|
|
pub fn get_new_key(&self) -> Result<&NewKey> {
|
|
if let KeyState::RollNew(new_key, _) = &self.key_state {
|
|
Ok(new_key)
|
|
} else {
|
|
Err(Error::ResourceClassNoNewKey)
|
|
}
|
|
}
|
|
|
|
fn current_objects(&self) -> CurrentObjects {
|
|
let mut current_objects = CurrentObjects::default();
|
|
|
|
for (auth, roa_info) in self.roas.iter() {
|
|
let roa = roa_info.roa();
|
|
current_objects.insert(ObjectName::from(auth), CurrentObject::from(roa));
|
|
}
|
|
|
|
for issued in self.certificates.current() {
|
|
let cert = issued.cert();
|
|
current_objects.insert(ObjectName::from(cert), CurrentObject::from(cert));
|
|
}
|
|
|
|
fn add_mft_and_crl(objects: &mut CurrentObjects, key: &CertifiedKey) {
|
|
let mft = key.current_set().manifest();
|
|
objects.insert(ObjectName::from(mft), CurrentObject::from(mft));
|
|
|
|
let crl = key.current_set().crl();
|
|
objects.insert(ObjectName::from(crl), CurrentObject::from(crl));
|
|
}
|
|
|
|
match &self.key_state {
|
|
KeyState::Pending(_) => {} // nothing to add
|
|
KeyState::Active(current) => {
|
|
add_mft_and_crl(&mut current_objects, current);
|
|
}
|
|
KeyState::RollPending(_, current) => {
|
|
add_mft_and_crl(&mut current_objects, current);
|
|
}
|
|
KeyState::RollNew(new, current) => {
|
|
add_mft_and_crl(&mut current_objects, new);
|
|
add_mft_and_crl(&mut current_objects, current);
|
|
}
|
|
KeyState::RollOld(current, old) => {
|
|
add_mft_and_crl(&mut current_objects, current);
|
|
add_mft_and_crl(&mut current_objects, old);
|
|
}
|
|
}
|
|
|
|
current_objects
|
|
}
|
|
|
|
/// Returns a ResourceClassInfo for this, which contains all the
|
|
/// same data, but which does not have any behaviour.
|
|
pub fn as_info(&self) -> ResourceClassInfo {
|
|
ResourceClassInfo::new(
|
|
self.name_space.clone(),
|
|
self.parent_handle.clone(),
|
|
self.key_state.as_info(),
|
|
self.current_objects(),
|
|
)
|
|
}
|
|
}
|
|
|
|
/// # Request certificates
|
|
///
|
|
impl ResourceClass {
|
|
/// Returns event details for receiving the certificate.
|
|
pub fn update_received_cert<S: Signer>(
|
|
&self,
|
|
rcvd_cert: RcvdCert,
|
|
repo_info: &RepoInfo,
|
|
signer: &S,
|
|
) -> ca::Result<Vec<EvtDet>> {
|
|
// If this is for a pending key, then we need to promote this key
|
|
|
|
let rcvd_cert_ki = rcvd_cert.cert().subject_key_identifier();
|
|
|
|
fn create_active_key_and_delta<S: Signer>(
|
|
rcvd_cert: RcvdCert,
|
|
signer: &S,
|
|
) -> ca::Result<(CertifiedKey, ObjectsDelta)> {
|
|
let mut delta = ObjectsDelta::new(rcvd_cert.ca_repository().clone());
|
|
let active_key = CertifiedKey::create(rcvd_cert, signer)?;
|
|
|
|
match active_key.current_set().manifest_info().added_or_updated() {
|
|
AddedOrUpdated::Added(added) => delta.add(added),
|
|
_ => panic!("New active key cannot have update."),
|
|
}
|
|
|
|
match active_key.current_set().crl_info().added_or_updated() {
|
|
AddedOrUpdated::Added(added) => delta.add(added),
|
|
_ => panic!("New active key cannot have update."),
|
|
}
|
|
Ok((active_key, delta))
|
|
}
|
|
|
|
match &self.key_state {
|
|
KeyState::Pending(pending) => {
|
|
if rcvd_cert_ki != pending.key_id() {
|
|
Err(ca::Error::NoKeyMatch(rcvd_cert_ki))
|
|
} else {
|
|
let (active_key, delta) = create_active_key_and_delta(rcvd_cert, signer)?;
|
|
Ok(vec![EvtDet::KeyPendingToActive(
|
|
self.name.clone(),
|
|
active_key,
|
|
delta,
|
|
)])
|
|
}
|
|
}
|
|
KeyState::Active(current) => {
|
|
self.update_rcvd_cert_current(current, rcvd_cert, repo_info, signer)
|
|
}
|
|
KeyState::RollPending(pending, current) => {
|
|
if rcvd_cert_ki == pending.key_id() {
|
|
let (active_key, delta) = create_active_key_and_delta(rcvd_cert, signer)?;
|
|
Ok(vec![EvtDet::KeyPendingToNew(
|
|
self.name.clone(),
|
|
active_key,
|
|
delta,
|
|
)])
|
|
} else {
|
|
self.update_rcvd_cert_current(current, rcvd_cert, repo_info, signer)
|
|
}
|
|
}
|
|
KeyState::RollNew(new, current) => {
|
|
if rcvd_cert_ki == new.key_id() {
|
|
Ok(vec![EvtDet::CertificateReceived(
|
|
self.name.clone(),
|
|
rcvd_cert_ki,
|
|
rcvd_cert,
|
|
)])
|
|
} else {
|
|
self.update_rcvd_cert_current(current, rcvd_cert, repo_info, signer)
|
|
}
|
|
}
|
|
KeyState::RollOld(current, _old) => {
|
|
// We will never request a new certificate for an old key
|
|
self.update_rcvd_cert_current(current, rcvd_cert, repo_info, signer)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn update_rcvd_cert_current<S: Signer>(
|
|
&self,
|
|
current: &CurrentKey,
|
|
rcvd_cert: RcvdCert,
|
|
repo_info: &RepoInfo,
|
|
signer: &S,
|
|
) -> ca::Result<Vec<EvtDet>> {
|
|
let rcvd_cert_ki = rcvd_cert.cert().subject_key_identifier();
|
|
if rcvd_cert_ki != current.key_id() {
|
|
return Err(ca::Error::NoKeyMatch(rcvd_cert_ki));
|
|
}
|
|
|
|
let rcvd_resources = rcvd_cert.resources().clone();
|
|
|
|
let mut res = vec![];
|
|
res.push(EvtDet::CertificateReceived(
|
|
self.name.clone(),
|
|
rcvd_cert_ki,
|
|
rcvd_cert,
|
|
));
|
|
|
|
if &rcvd_resources != current.incoming_cert().resources() {
|
|
let publish_mode = PublishMode::UpdatedResources(rcvd_resources);
|
|
let authorizations: Vec<RouteAuthorization> =
|
|
self.roas.authorizations().cloned().collect();
|
|
res.append(&mut self.republish(
|
|
authorizations.as_slice(),
|
|
repo_info,
|
|
&publish_mode,
|
|
signer,
|
|
)?)
|
|
}
|
|
|
|
Ok(res)
|
|
}
|
|
|
|
/// Request certificates for any key that needs it.
|
|
pub fn make_request_events<S: Signer>(
|
|
&self,
|
|
entitlement: &EntitlementClass,
|
|
base_repo: &RepoInfo,
|
|
signer: &S,
|
|
) -> Result<Vec<EvtDet>> {
|
|
self.key_state.request_certs(
|
|
self.name.clone(),
|
|
entitlement,
|
|
base_repo,
|
|
&self.name_space,
|
|
signer,
|
|
)
|
|
}
|
|
|
|
/// This function returns all current certificate requests.
|
|
pub fn cert_requests(&self) -> Vec<IssuanceRequest> {
|
|
self.key_state.cert_requests()
|
|
}
|
|
|
|
/// Returns the revocation request for the old key, if it exists.
|
|
pub fn revoke_request(&self) -> Option<&RevocationRequest> {
|
|
self.key_state.revoke_request()
|
|
}
|
|
}
|
|
|
|
/// # Publishing
|
|
///
|
|
impl ResourceClass {
|
|
/// Applies a publication delta to the appropriate key in this resource class.
|
|
pub fn apply_delta(&mut self, delta: CurrentObjectSetDelta, key_id: KeyIdentifier) {
|
|
self.key_state.apply_delta(delta, key_id);
|
|
}
|
|
|
|
/// Publish/update/withdraw objects under the key, determined by the
|
|
/// [PublishMode]. Will revoke updated and withdrawn objects under the
|
|
/// correct key as well, i.e. when activating a new key objects will
|
|
/// be re-published and updated in terms of publication, but will only
|
|
/// be revoked under the old key.
|
|
pub fn publish_objects<S: Signer>(
|
|
&self,
|
|
repo_info: &RepoInfo,
|
|
objects_delta: ObjectsDelta,
|
|
new_revocations: Vec<Revocation>,
|
|
mode: &PublishMode,
|
|
signer: &S,
|
|
) -> ca::Result<EvtDet> {
|
|
let mut key_pub_map = HashMap::new();
|
|
|
|
let (publish_key, other_key_opt) = match mode {
|
|
PublishMode::Normal | PublishMode::UpdatedResources(_) => {
|
|
let other_key_opt = match &self.key_state {
|
|
KeyState::RollNew(new, _) => Some(new),
|
|
KeyState::RollOld(_, old) => Some(old.key()),
|
|
_ => None,
|
|
};
|
|
(self.get_current_key()?, other_key_opt)
|
|
}
|
|
PublishMode::KeyRollActivation => (self.get_new_key()?, Some(self.get_current_key()?)),
|
|
};
|
|
|
|
let (publish_key_revocations, other_key_revocations) = match mode {
|
|
PublishMode::Normal | PublishMode::UpdatedResources(_) => (new_revocations, vec![]),
|
|
PublishMode::KeyRollActivation => (vec![], new_revocations),
|
|
};
|
|
|
|
let publish_key_delta = self
|
|
.make_current_set_delta(publish_key, objects_delta, publish_key_revocations, signer)
|
|
.map_err(Error::signer)?;
|
|
|
|
key_pub_map.insert(publish_key.key_id().clone(), publish_key_delta);
|
|
|
|
if let Some(other_key) = other_key_opt {
|
|
let ns = self.name_space();
|
|
let delta = ObjectsDelta::new(repo_info.ca_repository(ns));
|
|
|
|
let other_delta = self
|
|
.make_current_set_delta(other_key, delta, other_key_revocations, signer)
|
|
.map_err(ca::Error::signer)?;
|
|
|
|
key_pub_map.insert(other_key.key_id().clone(), other_delta);
|
|
}
|
|
|
|
Ok(EvtDet::ObjectSetUpdated(self.name.clone(), key_pub_map))
|
|
}
|
|
|
|
fn needs_publication(&self, mode: &PublishMode) -> bool {
|
|
match mode {
|
|
PublishMode::Normal => self.get_current_key().unwrap().close_to_next_update(),
|
|
_ => true,
|
|
}
|
|
}
|
|
|
|
/// Republish all keys in this class (that want it). Also update
|
|
/// ROAs as needed.
|
|
pub fn republish<S: Signer>(
|
|
&self,
|
|
authorizations: &[RouteAuthorization],
|
|
repo_info: &RepoInfo,
|
|
mode: &PublishMode,
|
|
signer: &S,
|
|
) -> ca::Result<Vec<EvtDet>> {
|
|
let mut res = vec![];
|
|
|
|
let ns = self.name_space();
|
|
let mut delta = ObjectsDelta::new(repo_info.ca_repository(ns));
|
|
let mut revocations = vec![];
|
|
|
|
let roa_updates = self.update_roas(authorizations, mode, signer)?;
|
|
if roa_updates.contains_changes() {
|
|
for added in roa_updates.added().into_iter() {
|
|
delta.add(added);
|
|
}
|
|
for update in roa_updates.updated().into_iter() {
|
|
delta.update(update);
|
|
}
|
|
for withdraw in roa_updates.withdrawn().into_iter() {
|
|
delta.withdraw(withdraw);
|
|
}
|
|
revocations.append(&mut roa_updates.revocations());
|
|
|
|
res.push(EvtDet::RoasUpdated(self.name.clone(), roa_updates));
|
|
}
|
|
|
|
if !delta.is_empty() || !revocations.is_empty() || self.needs_publication(mode) {
|
|
res.push(self.publish_objects(&repo_info, delta, revocations, mode, signer)?);
|
|
}
|
|
|
|
Ok(res)
|
|
}
|
|
|
|
/// Create a publish event details including the revocations, update, withdrawals needed
|
|
/// for updating child certificates.
|
|
pub fn republish_certs<S: Signer>(
|
|
&self,
|
|
issued_certs: Vec<&IssuedCert>,
|
|
removed_certs: Vec<&Cert>,
|
|
repo_info: &RepoInfo,
|
|
signer: &S,
|
|
) -> Result<HashMap<KeyIdentifier, CurrentObjectSetDelta>> {
|
|
let issuing_key = self.get_current_key()?;
|
|
let name_space = self.name_space();
|
|
|
|
let mut revocations = vec![];
|
|
for cert in removed_certs.iter() {
|
|
revocations.push(Revocation::from(*cert));
|
|
}
|
|
for issued in issued_certs.iter() {
|
|
if let Some(replaced) = issued.replaces() {
|
|
revocations.push(replaced.revocation());
|
|
}
|
|
}
|
|
|
|
let ca_repo = repo_info.ca_repository(name_space);
|
|
let mut objects_delta = ObjectsDelta::new(ca_repo);
|
|
|
|
for removed in removed_certs.into_iter() {
|
|
objects_delta.withdraw(WithdrawnObject::from(removed));
|
|
}
|
|
for issued in issued_certs.into_iter() {
|
|
match issued.replaces() {
|
|
None => objects_delta.add(AddedObject::from(issued.cert())),
|
|
Some(replaced) => objects_delta.update(UpdatedObject::for_cert(
|
|
issued.cert(),
|
|
replaced.hash().clone(),
|
|
)),
|
|
}
|
|
}
|
|
|
|
let set_delta = self
|
|
.make_current_set_delta(issuing_key, objects_delta, revocations, signer)
|
|
.map_err(Error::signer)?;
|
|
|
|
let mut res = HashMap::new();
|
|
res.insert(issuing_key.key_id().clone(), set_delta);
|
|
Ok(res)
|
|
}
|
|
|
|
fn make_current_set_delta<S: Signer>(
|
|
&self,
|
|
signing_key: &CertifiedKey,
|
|
mut objects_delta: ObjectsDelta,
|
|
mut new_revocations: Vec<Revocation>,
|
|
signer: &S,
|
|
) -> ca::Result<CurrentObjectSetDelta> {
|
|
let signing_cert = signing_key.incoming_cert();
|
|
let signing_pub_key = signer
|
|
.get_key_info(signing_key.key_id())
|
|
.map_err(ca::Error::signer)?;
|
|
|
|
let current_set = signing_key.current_set();
|
|
let current_revocations = current_set.revocations().clone();
|
|
let number = current_set.number() + 1;
|
|
|
|
let current_mft = current_set.manifest();
|
|
let current_mft_hash = HexEncodedHash::from(current_mft);
|
|
let current_crl = current_set.crl();
|
|
let current_crl_hash = HexEncodedHash::from(current_crl);
|
|
|
|
new_revocations.push(Revocation::from(current_mft));
|
|
|
|
// Create a new CRL
|
|
let (crl_info, revocations_delta) = CrlBuilder::build(
|
|
current_revocations,
|
|
new_revocations,
|
|
number,
|
|
Some(current_crl_hash),
|
|
&signing_pub_key,
|
|
signer,
|
|
)?;
|
|
|
|
match crl_info.added_or_updated() {
|
|
AddedOrUpdated::Added(added) => objects_delta.add(added),
|
|
AddedOrUpdated::Updated(updated) => objects_delta.update(updated),
|
|
}
|
|
|
|
// For the new manifest:
|
|
//
|
|
// List all current files, i.e.
|
|
// - the new CRL
|
|
// - current ROAs
|
|
// - current Certs
|
|
// - applying the delta
|
|
let issued = self.certificates.current();
|
|
let roas = self.roas.iter();
|
|
|
|
let manifest_info = ManifestBuilder::new(&crl_info, issued, roas, &objects_delta).build(
|
|
&signing_pub_key,
|
|
signing_cert,
|
|
number,
|
|
Some(current_mft_hash),
|
|
signer,
|
|
)?;
|
|
|
|
match manifest_info.added_or_updated() {
|
|
AddedOrUpdated::Added(added) => objects_delta.add(added),
|
|
AddedOrUpdated::Updated(updated) => objects_delta.update(updated),
|
|
}
|
|
|
|
Ok(CurrentObjectSetDelta::new(
|
|
number,
|
|
revocations_delta,
|
|
manifest_info,
|
|
crl_info,
|
|
objects_delta,
|
|
))
|
|
}
|
|
}
|
|
|
|
/// # Removing a resource class
|
|
///
|
|
impl ResourceClass {
|
|
/// Returns withdraws for all current objects, for when this resource class
|
|
/// needs to be removed.
|
|
pub fn withdraw(&self, base_repo: &RepoInfo) -> ObjectsDelta {
|
|
let base_repo = base_repo.ca_repository(self.name_space());
|
|
let mut delta = ObjectsDelta::new(base_repo);
|
|
|
|
for withdraw in self.current_objects().withdraw().into_iter() {
|
|
delta.withdraw(withdraw);
|
|
}
|
|
delta
|
|
}
|
|
|
|
/// Returns revocation requests for all certified keys in this resource class.
|
|
pub fn revoke<S: Signer>(&self, signer: &S) -> ca::Result<Vec<RevocationRequest>> {
|
|
self.key_state.revoke(self.name.clone(), signer)
|
|
}
|
|
}
|
|
|
|
/// # Key Life Cycle and Receiving Certificates
|
|
///
|
|
impl ResourceClass {
|
|
/// This function marks a certificate as received.
|
|
pub fn received_cert(&mut self, key_id: KeyIdentifier, cert: RcvdCert) {
|
|
// if there is a pending key, then we need to do some promotions..
|
|
match &mut self.key_state {
|
|
KeyState::Pending(_pending) => panic!("Would have received KeyPendingToActive event"),
|
|
KeyState::Active(current) => {
|
|
current.set_incoming_cert(cert);
|
|
}
|
|
KeyState::RollPending(_pending, current) => {
|
|
current.set_incoming_cert(cert);
|
|
}
|
|
KeyState::RollNew(new, current) => {
|
|
if new.key_id() == &key_id {
|
|
new.set_incoming_cert(cert);
|
|
} else {
|
|
current.set_incoming_cert(cert);
|
|
}
|
|
}
|
|
KeyState::RollOld(current, old) => {
|
|
if current.key_id() == &key_id {
|
|
current.set_incoming_cert(cert);
|
|
} else {
|
|
old.set_incoming_cert(cert);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Adds a pending key.
|
|
pub fn pending_key_added(&mut self, key_id: KeyIdentifier) {
|
|
match &self.key_state {
|
|
KeyState::Active(current) => {
|
|
let pending = PendingKey::new(key_id);
|
|
self.key_state = KeyState::RollPending(pending, current.clone())
|
|
}
|
|
_ => panic!("Should never create event to add key when roll in progress"),
|
|
}
|
|
}
|
|
|
|
/// Moves a pending key to new
|
|
pub fn pending_key_to_new(&mut self, new: CertifiedKey) {
|
|
match &self.key_state {
|
|
KeyState::RollPending(_pending, current) => {
|
|
self.key_state = KeyState::RollNew(new, current.clone());
|
|
}
|
|
_ => panic!("Cannot move pending to new, if state is not roll pending"),
|
|
}
|
|
}
|
|
|
|
/// Moves a pending key to current
|
|
pub fn pending_key_to_active(&mut self, new: CertifiedKey) {
|
|
match &self.key_state {
|
|
KeyState::Pending(_pending) => {
|
|
self.key_state = KeyState::Active(new);
|
|
}
|
|
_ => panic!("Cannot move pending to active, if state is not pending"),
|
|
}
|
|
}
|
|
|
|
/// Activates the new key
|
|
pub fn new_key_activated(&mut self, revoke_req: RevocationRequest) {
|
|
match &self.key_state {
|
|
KeyState::RollNew(new, current) => {
|
|
let old_key = OldKey::new(current.clone(), revoke_req);
|
|
self.key_state = KeyState::RollOld(new.clone(), old_key);
|
|
}
|
|
_ => panic!("Should never create event to activate key when no roll in progress"),
|
|
}
|
|
}
|
|
|
|
/// Removes the old key, we return the to the state where there is one active key.
|
|
pub fn old_key_removed(&mut self) {
|
|
match &self.key_state {
|
|
KeyState::RollOld(current, _old) => {
|
|
self.key_state = KeyState::Active(current.clone());
|
|
}
|
|
_ => panic!("Should never create event to remove old key, when there is none"),
|
|
}
|
|
}
|
|
|
|
/// Initiate a key roll
|
|
pub fn keyroll_initiate<S: Signer>(
|
|
&self,
|
|
base_repo: &RepoInfo,
|
|
duration: Duration,
|
|
signer: &mut S,
|
|
) -> ca::Result<Vec<EvtDet>> {
|
|
if self.last_key_change + duration > Time::now() {
|
|
return Ok(vec![]);
|
|
}
|
|
|
|
self.key_state.keyroll_initiate(
|
|
self.name.clone(),
|
|
self.parent_rc_name.clone(),
|
|
base_repo,
|
|
&self.name_space,
|
|
signer,
|
|
)
|
|
}
|
|
|
|
/// Activate a new key, if it's been longer than the staging period.
|
|
pub fn keyroll_activate<S: Signer>(
|
|
&self,
|
|
repo_info: &RepoInfo,
|
|
staging: Duration,
|
|
signer: &S,
|
|
) -> ca::Result<Vec<EvtDet>> {
|
|
if !self.key_state.has_new_key() || self.last_key_change + staging > Time::now() {
|
|
return Ok(vec![]);
|
|
}
|
|
|
|
let mut res = vec![];
|
|
|
|
let authorizations: Vec<RouteAuthorization> = self.roas.authorizations().cloned().collect();
|
|
|
|
res.push(self.key_state.keyroll_activate(
|
|
self.name.clone(),
|
|
self.parent_rc_name.clone(),
|
|
signer,
|
|
)?);
|
|
|
|
res.append(&mut self.republish(
|
|
authorizations.as_slice(),
|
|
repo_info,
|
|
&PublishMode::KeyRollActivation,
|
|
signer,
|
|
)?);
|
|
|
|
Ok(res)
|
|
}
|
|
|
|
/// Finish a key roll, withdraw the old key
|
|
pub fn keyroll_finish(&self, base_repo: &RepoInfo) -> ca::Result<EvtDet> {
|
|
match &self.key_state {
|
|
KeyState::RollOld(_current, old) => {
|
|
let mut objects_delta =
|
|
ObjectsDelta::new(base_repo.ca_repository(self.name_space()));
|
|
|
|
let crl_info = old.current_set().crl_info();
|
|
objects_delta.withdraw(crl_info.withdraw());
|
|
|
|
let mft_info = old.current_set().manifest_info();
|
|
objects_delta.withdraw(mft_info.withdraw());
|
|
|
|
Ok(EvtDet::KeyRollFinished(self.name.clone(), objects_delta))
|
|
}
|
|
_ => Err(Error::InvalidKeyStatus),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// # Issuing certificates
|
|
///
|
|
impl ResourceClass {
|
|
/// Makes a single CA certificate and wraps it in an issuance response.
|
|
///
|
|
/// Will use the intersection of the requested child resources, and the
|
|
/// resources actually held by the this resource class. An error will be
|
|
/// returned if a ResourceRequestLimit was used that includes resources
|
|
/// that are not in this intersection.
|
|
///
|
|
/// Note that this certificate still needs to be added to this RC by
|
|
/// calling the update_certs function.
|
|
pub fn issue_cert<S: Signer>(
|
|
&self,
|
|
csr: CsrInfo,
|
|
child_resources: &ResourceSet,
|
|
limit: RequestResourceLimit,
|
|
mode: &PublishMode,
|
|
signer: &S,
|
|
) -> ca::Result<IssuanceResponse> {
|
|
let signing_key = match mode {
|
|
PublishMode::Normal | PublishMode::UpdatedResources(_) => self.get_current_key()?,
|
|
PublishMode::KeyRollActivation => self.get_new_key()?,
|
|
};
|
|
|
|
let parent_resources = match mode {
|
|
PublishMode::UpdatedResources(resources) => resources,
|
|
_ => signing_key.incoming_cert().resources(),
|
|
};
|
|
|
|
let resources = parent_resources.intersection(child_resources);
|
|
let replaces = self
|
|
.certificates
|
|
.get(&csr.key_id())
|
|
.map(ReplacedObject::from);
|
|
|
|
let issued =
|
|
SignSupport::make_issued_cert(csr, &resources, limit, replaces, signing_key, signer)?;
|
|
|
|
let signing_cert = SigningCert::from(signing_key.incoming_cert());
|
|
|
|
Ok(IssuanceResponse::new(
|
|
self.name.clone(),
|
|
signing_cert,
|
|
resources,
|
|
issued.cert().validity().not_after(),
|
|
issued,
|
|
))
|
|
}
|
|
|
|
/// Stores an [IssuedCert](krill_commons.api.ca.IssuedCert)
|
|
pub fn certificate_issued(&mut self, issued: IssuedCert) {
|
|
self.certificates.certificate_issued(issued);
|
|
}
|
|
|
|
/// Removes a revoked key.
|
|
pub fn key_revoked(&mut self, key: &KeyIdentifier) {
|
|
self.certificates.key_revoked(key);
|
|
}
|
|
}
|
|
|
|
/// # ROAs
|
|
///
|
|
impl ResourceClass {
|
|
/// Updates the ROAs in accordance with the current authorizations, and
|
|
/// the target resources and key determined by the PublishMode.
|
|
pub fn update_roas<S: Signer>(
|
|
&self,
|
|
auths: &[RouteAuthorization],
|
|
mode: &PublishMode,
|
|
signer: &S,
|
|
) -> ca::Result<RoaUpdates> {
|
|
let mut updates = RoaUpdates::default();
|
|
|
|
let key = match mode {
|
|
PublishMode::Normal | PublishMode::UpdatedResources(_) => self.get_current_key()?,
|
|
PublishMode::KeyRollActivation => self.get_new_key()?,
|
|
};
|
|
|
|
let resources = match mode {
|
|
PublishMode::Normal => key.incoming_cert().resources(),
|
|
PublishMode::UpdatedResources(resources) => resources,
|
|
PublishMode::KeyRollActivation => self.get_current_key()?.incoming_cert().resources(),
|
|
};
|
|
|
|
// Remove any ROAs no longer in auths, or no longer in resources.
|
|
for (current_auth, roa_info) in self.roas.iter() {
|
|
if !auths.contains(current_auth) || !resources.contains(¤t_auth.prefix().into()) {
|
|
updates.remove(*current_auth, RevokedObject::from(roa_info.roa()));
|
|
}
|
|
}
|
|
|
|
for auth in auths {
|
|
// if the auth is not in this resource class, just skip it.
|
|
if !resources.contains(&auth.prefix().into()) {
|
|
continue;
|
|
}
|
|
|
|
match self.roas.get(auth) {
|
|
None => {
|
|
// NO ROA yet, so create one.
|
|
let roa = Roas::make_roa(auth, key.incoming_cert(), key.key_id(), signer)?;
|
|
|
|
updates.update(*auth, RoaInfo::new_roa(roa));
|
|
}
|
|
Some(roa) => {
|
|
// Re-issue if the ROA is getting close to its expiration time, or if we are
|
|
// activating the new key.
|
|
let expiring =
|
|
roa.roa().cert().validity().not_after() < Time::now() + Duration::weeks(4);
|
|
let activating = mode == &PublishMode::KeyRollActivation;
|
|
|
|
if expiring || activating {
|
|
let new_roa =
|
|
Roas::make_roa(auth, key.incoming_cert(), key.key_id(), signer)?;
|
|
|
|
updates.update(*auth, RoaInfo::updated_roa(roa, new_roa));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(updates)
|
|
}
|
|
|
|
/// Marks the ROAs as updated from a RoaUpdated event.
|
|
pub fn roas_updated(&mut self, updates: RoaUpdates) {
|
|
self.roas.updated(updates);
|
|
}
|
|
}
|
|
|
|
//------------ PublishMode -------------------------------------------------
|
|
|
|
/// Describes which kind of publication we're after:
|
|
///
|
|
/// Normal: Use the current key and resources. ROAs are re-issued and revoked
|
|
/// under the current key - if needed.
|
|
///
|
|
/// UpdatedResources: Use the current key, but with the new resource set that
|
|
/// this key is about to be updated with.
|
|
///
|
|
/// PendingKeyActivation: The pending key will be activated, to either a new
|
|
/// (init) or the current (roll) key, and needs to be published.
|
|
///
|
|
/// KeyActivation: Publish ROAs and certificates under the new key, and revoke
|
|
/// them under the old key - which will be revoked shortly.
|
|
///
|
|
#[derive(Clone, Eq, PartialEq)]
|
|
#[allow(clippy::large_enum_variant)]
|
|
pub enum PublishMode {
|
|
Normal,
|
|
UpdatedResources(ResourceSet),
|
|
KeyRollActivation,
|
|
}
|