use chrono::Duration;
use log::{debug, info};
use serde::{Deserialize, Serialize};
use rpki::ca::idexchange::{CaHandle, ParentHandle, RepoInfo};
use rpki::ca::provisioning::{
IssuanceRequest, RequestResourceLimit, ResourceClassEntitlements,
ResourceClassName, RevocationRequest,
};
use rpki::crypto::KeyIdentifier;
use rpki::repository::cert::Cert;
use rpki::repository::resources::ResourceSet;
use rpki::repository::x509::{Time, Validity};
use crate::api::ca::{
IssuedCertificate, ReceivedCert, ResourceClassInfo, SuspendedCert,
UnsuspendedCert,
};
use crate::api::roa::{RoaConfiguration, RoaInfo};
use crate::commons::KrillResult;
use crate::commons::crypto::{CsrInfo, KrillSigner, SignSupport};
use crate::commons::error::{Error, KrillError};
use crate::config::{Config, IssuanceTimingConfig};
use super::aspa::{AspaDefinitions, AspaObjects, AspaObjectsUpdates};
use super::bgpsec::{
BgpSecCertificates, BgpSecCertificateUpdates, BgpSecDefinitions
};
use super::child::ChildCertificates;
use super::events::{CertAuthEvent};
use super::keys::{CertifiedKey, CurrentKey, KeyState, OldKey, PendingKey};
use super::roa::{Roas, RoaUpdates, Routes};
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ResourceClass {
name: ResourceClassName,
name_space: String,
parent_handle: ParentHandle,
parent_rc_name: ResourceClassName,
#[serde(skip_serializing_if = "Roas::is_empty", default)]
roas: Roas,
#[serde(skip_serializing_if = "AspaObjects::is_empty", default)]
aspas: AspaObjects,
#[serde(skip_serializing_if = "BgpSecCertificates::is_empty", default)]
bgpsec_certificates: BgpSecCertificates,
#[serde(skip_serializing_if = "ChildCertificates::is_empty", default)]
certificates: ChildCertificates,
last_key_change: Time,
key_state: KeyState,
}
impl ResourceClass {
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(),
aspas: AspaObjects::default(),
certificates: ChildCertificates::default(),
bgpsec_certificates: BgpSecCertificates::default(),
last_key_change: Time::now(),
key_state: KeyState::create(pending_key),
}
}
}
impl ResourceClass {
pub fn parent_handle(&self) -> &ParentHandle {
&self.parent_handle
}
pub fn parent_rc_name(&self) -> &ResourceClassName {
&self.parent_rc_name
}
pub fn current_certificate(&self) -> Option<&ReceivedCert> {
self.current_key().map(|k| k.incoming_cert())
}
pub fn current_resources(&self) -> Option<&ResourceSet> {
self.current_certificate().map(|c| &c.resources)
}
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) -> KrillResult<&CurrentKey> {
self.current_key().ok_or(Error::KeyUseNoCurrentKey)
}
pub fn key_roll_possible(&self) -> bool {
matches!(&self.key_state, KeyState::Active(_))
}
pub fn to_info(&self) -> ResourceClassInfo {
ResourceClassInfo {
name_space: self.name_space.clone(),
parent_handle: self.parent_handle.clone(),
keys: self.key_state.to_info(),
}
}
}
impl ResourceClass {
pub fn set_old_repo(&mut self, repo: RepoInfo) {
self.key_state.set_old_repo_if_in_active_state(repo);
}
}
impl ResourceClass {
#[allow(clippy::too_many_arguments)]
pub fn process_received_cert(
&self,
handle: &CaHandle,
rcvd_cert: ReceivedCert,
all_routes: &Routes,
all_aspas: &AspaDefinitions,
all_bgpsecs: &BgpSecDefinitions,
config: &Config,
signer: &KrillSigner,
) -> KrillResult<Vec<CertAuthEvent>> {
let rcvd_cert_ki = rcvd_cert.key_identifier();
match &self.key_state {
KeyState::Pending(pending) => {
if rcvd_cert_ki != pending.key_id() {
return Err(Error::KeyUseNoMatch(rcvd_cert_ki))
}
self.process_rcvd_cert_pending(
handle, rcvd_cert, all_routes, all_aspas, all_bgpsecs,
config, signer
)
}
KeyState::Active(current) => {
self.process_rcvd_cert_current(
handle, current, rcvd_cert,
all_routes, all_aspas, all_bgpsecs,
config, signer,
)
}
KeyState::RollPending(pending, current) => {
if rcvd_cert_ki == pending.key_id() {
let new_key = CertifiedKey::create(rcvd_cert);
Ok(vec![CertAuthEvent::KeyPendingToNew {
resource_class_name: self.name.clone(),
new_key,
}])
}
else {
self.process_rcvd_cert_current(
handle, current, rcvd_cert,
all_routes, all_aspas, all_bgpsecs,
config, signer,
)
}
}
KeyState::RollNew(new, current) => {
if rcvd_cert_ki == new.key_id() {
Ok(vec![CertAuthEvent::CertificateReceived {
resource_class_name: self.name.clone(),
ki: rcvd_cert_ki,
rcvd_cert,
}])
}
else {
self.process_rcvd_cert_current(
handle, current, rcvd_cert,
all_routes, all_aspas, all_bgpsecs,
config, signer,
)
}
}
KeyState::RollOld(current, _old) => {
self.process_rcvd_cert_current(
handle, current, rcvd_cert,
all_routes, all_aspas, all_bgpsecs,
config, signer,
)
}
}
}
#[allow(clippy::too_many_arguments)]
fn process_rcvd_cert_pending(
&self,
handle: &CaHandle,
rcvd_cert: ReceivedCert,
all_routes: &Routes,
all_aspas: &AspaDefinitions,
all_bgpsecs: &BgpSecDefinitions,
config: &Config,
signer: &KrillSigner,
) -> KrillResult<Vec<CertAuthEvent>> {
info!(
"Received certificate for CA '{}' under RC '{}', with resources: \
'{}' valid until: '{}'",
handle,
self.name,
rcvd_cert.resources,
rcvd_cert.validity.not_after().to_rfc3339()
);
let current_key = CertifiedKey::create(rcvd_cert);
let roa_updates = self.roas.create_updates(
all_routes,
¤t_key,
config,
signer,
)?;
let aspa_updates = self.aspas.create_updates(
all_aspas,
¤t_key,
config,
signer,
)?;
let bgpsec_updates
= self.bgpsec_certificates.create_updates(
all_bgpsecs,
¤t_key,
config,
signer,
)?;
let mut events =
vec![CertAuthEvent::KeyPendingToActive {
resource_class_name: self.name.clone(),
current_key,
}];
if !roa_updates.is_empty() {
events.push(CertAuthEvent::RoasUpdated {
resource_class_name: self.name.clone(),
updates: roa_updates,
})
}
if !aspa_updates.is_empty() {
events.push(CertAuthEvent::AspaObjectsUpdated {
resource_class_name: self.name.clone(),
updates: aspa_updates,
})
}
if !bgpsec_updates.is_empty() {
events.push(
CertAuthEvent::BgpSecCertificatesUpdated {
resource_class_name: self.name.clone(),
updates: bgpsec_updates,
},
)
}
Ok(events)
}
#[allow(clippy::too_many_arguments)]
fn process_rcvd_cert_current(
&self,
handle: &CaHandle,
current_key: &CurrentKey,
rcvd_cert: ReceivedCert,
all_routes: &Routes,
all_aspas: &AspaDefinitions,
all_bgpsecs: &BgpSecDefinitions,
config: &Config,
signer: &KrillSigner,
) -> KrillResult<Vec<CertAuthEvent>> {
let ki = rcvd_cert.key_identifier();
if ki != current_key.key_id() {
return Err(KrillError::KeyUseNoMatch(ki));
}
let rcvd_resources = &rcvd_cert.resources;
let mut res = vec![CertAuthEvent::CertificateReceived {
resource_class_name: self.name.clone(),
ki,
rcvd_cert: rcvd_cert.clone(),
}];
let rcvd_resources_diff = rcvd_resources.difference(
¤t_key.incoming_cert().resources
);
if !rcvd_resources_diff.is_empty() {
info!(
"Received new certificate under CA '{}' under RC '{}' with \
changed resources: '{}', valid until: {}",
handle,
self.name,
rcvd_resources_diff,
rcvd_cert.validity.not_after().to_rfc3339()
);
let updated_key = CertifiedKey::create(rcvd_cert);
let updates = self.certificates.shrink_overclaiming(
updated_key.incoming_cert(),
&config.issuance_timing,
signer,
)?;
if !updates.is_empty() {
res.push(CertAuthEvent::ChildCertificatesUpdated {
resource_class_name: self.name.clone(),
updates,
});
}
{
let updates = self.roas.create_updates(
all_routes,
&updated_key,
config,
signer,
)?;
if !updates.is_empty() {
res.push(CertAuthEvent::RoasUpdated {
resource_class_name: self.name.clone(),
updates,
});
}
}
{
let updates = self.aspas.create_updates(
all_aspas,
&updated_key,
config,
signer,
)?;
if !updates.is_empty() {
res.push(CertAuthEvent::AspaObjectsUpdated {
resource_class_name: self.name.clone(),
updates,
})
}
}
{
let updates = self.bgpsec_certificates.create_updates(
all_bgpsecs,
&updated_key,
config,
signer,
)?;
if !updates.is_empty() {
res.push(CertAuthEvent::BgpSecCertificatesUpdated {
resource_class_name: self.name.clone(),
updates,
})
}
}
}
else {
info!(
"Received new certificate for CA '{}' under RC '{}', \
valid until: {}",
handle,
self.name,
rcvd_cert.validity.not_after().to_rfc3339()
)
}
Ok(res)
}
pub fn append_entitlement_events(
&self,
handle: &CaHandle,
entitlement: &ResourceClassEntitlements,
base_repo: &RepoInfo,
signer: &KrillSigner,
events: &mut Vec<CertAuthEvent>,
) -> KrillResult<()> {
self.key_state.append_entitlement_events(
handle,
self.name.clone(),
entitlement,
base_repo,
&self.name_space,
signer,
events,
)
}
pub fn cert_requests(&self) -> Vec<IssuanceRequest> {
self.key_state.cert_requests()
}
pub fn revoke_request(&self) -> Option<&RevocationRequest> {
self.key_state.revoke_request()
}
pub fn has_pending_requests(&self) -> bool {
!self.cert_requests().is_empty() || self.revoke_request().is_some()
}
}
impl ResourceClass {
pub fn revoke(
&self,
signer: &KrillSigner,
) -> KrillResult<Vec<RevocationRequest>> {
self.key_state.revoke(self.parent_rc_name.clone(), signer)
}
}
impl ResourceClass {
pub fn append_keyroll_initiate(
&self,
base_repo: &RepoInfo,
duration: Duration,
signer: &KrillSigner,
events: &mut Vec<CertAuthEvent>,
) -> KrillResult<bool> {
if duration > Duration::seconds(0)
&& self.last_key_change + duration > Time::now()
{
return Ok(false);
}
self.key_state.append_keyroll_initiate(
self.name.clone(),
self.parent_rc_name.clone(),
base_repo,
&self.name_space,
signer,
events,
)
}
pub fn append_keyroll_activate(
&self,
staging_time: Duration,
issuance_timing: &IssuanceTimingConfig,
signer: &KrillSigner,
events: &mut Vec<CertAuthEvent>,
) -> KrillResult<bool> {
if let Some(new_key) = self.key_state.new_key() {
if staging_time > Duration::seconds(0)
&& self.last_key_change + staging_time > Time::now()
{
Ok(false)
}
else {
self.key_state.append_keyroll_activate(
self.name.clone(),
self.parent_rc_name.clone(),
signer,
events,
)?;
let roa_updates = self.roas.create_renewal(
true,
new_key,
issuance_timing,
signer,
)?;
if !roa_updates.is_empty() {
events.push(CertAuthEvent::RoasUpdated {
resource_class_name: self.name.clone(),
updates: roa_updates,
});
}
let aspa_updates = self.aspas.create_renewal(
new_key,
None,
issuance_timing,
signer,
)?;
if !aspa_updates.is_empty() {
events.push(CertAuthEvent::AspaObjectsUpdated {
resource_class_name: self.name.clone(),
updates: aspa_updates,
})
}
let cert_updates = self.certificates.activate_key(
new_key.incoming_cert(),
issuance_timing,
signer,
)?;
if !cert_updates.is_empty() {
events.push(CertAuthEvent::ChildCertificatesUpdated {
resource_class_name: self.name.clone(),
updates: cert_updates,
});
}
let bgpsec_updates = self.bgpsec_certificates.create_renewal(
new_key,
None,
issuance_timing,
signer,
)?;
if !bgpsec_updates.is_empty() {
events.push(CertAuthEvent::BgpSecCertificatesUpdated {
resource_class_name: self.name.clone(),
updates: bgpsec_updates,
});
}
Ok(true)
}
}
else {
Ok(false)
}
}
pub fn process_keyroll_finish(&self) -> KrillResult<CertAuthEvent> {
match &self.key_state {
KeyState::RollOld(_current, _old) => {
Ok(CertAuthEvent::KeyRollFinished {
resource_class_name: self.name.clone(),
})
}
_ => Err(Error::KeyUseNoOldKey),
}
}
}
impl ResourceClass {
pub fn issue_cert(
&self,
csr: CsrInfo,
child_resources: &ResourceSet,
limit: RequestResourceLimit,
issuance_timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<IssuedCertificate> {
let signing_cert = self.get_current_key()?.incoming_cert();
let parent_resources = &signing_cert.resources;
let resources = parent_resources.intersection(child_resources);
let issued = SignSupport::make_issued_cert(
csr,
&resources,
limit,
signing_cert,
issuance_timing.new_child_cert_validity(),
signer,
)?;
Ok(issued)
}
pub fn issued(&self, ki: &KeyIdentifier) -> Option<&IssuedCertificate> {
self.certificates.get_issued(ki)
}
pub fn suspended(&self, ki: &KeyIdentifier) -> Option<&SuspendedCert> {
self.certificates.get_suspended(ki)
}
}
impl ResourceClass {
pub fn create_roa_updates(
&self,
routes: &Routes,
config: &Config,
signer: &KrillSigner,
) -> KrillResult<RoaUpdates> {
if let Ok(key) = self.get_current_key() {
let resources = &key.incoming_cert().resources;
let routes = routes.filter(resources);
self.roas.create_updates(&routes, key, config, signer)
}
else {
debug!("no ROAs to update - resource class has no current key");
Ok(RoaUpdates::default())
}
}
pub fn create_roa_renewal(
&self,
force: bool,
issuance_timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<RoaUpdates> {
if let Ok(key) = self.get_current_key() {
self.roas.create_renewal(force, key, issuance_timing, signer)
}
else {
debug!("no ROAs to renew - resource class has no current key");
Ok(RoaUpdates::default())
}
}
pub fn matching_roa_infos(
&self,
config: &RoaConfiguration,
) -> Vec<RoaInfo> {
self.roas.matching_roa_infos(config)
}
}
impl ResourceClass {
pub fn create_aspa_updates(
&self,
all_aspas: &AspaDefinitions,
config: &Config,
signer: &KrillSigner,
) -> KrillResult<AspaObjectsUpdates> {
if let Ok(key) = self.get_current_key() {
self.aspas.create_updates(all_aspas, key, config, signer)
}
else {
debug!("no ASPAs to update - resource class has no current key");
Ok(AspaObjectsUpdates::default())
}
}
pub fn create_aspa_renewal(
&self,
issuance_timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<AspaObjectsUpdates> {
if let Ok(key) = self.get_current_key() {
let renew_threshold =
Some(issuance_timing.new_aspa_issuance_threshold());
self.aspas.create_renewal(
key, renew_threshold, issuance_timing, signer
)
}
else {
debug!("no ASPAs to renew - resource class has no current key");
Ok(AspaObjectsUpdates::default())
}
}
}
impl ResourceClass {
pub fn create_bgpsec_updates(
&self,
definitions: &BgpSecDefinitions,
config: &Config,
signer: &KrillSigner,
) -> KrillResult<BgpSecCertificateUpdates> {
if let Ok(key) = self.get_current_key() {
self.bgpsec_certificates
.create_updates(definitions, key, config, signer)
}
else {
debug!(
"no BGPsec certificates to update - \
resource class has no current key"
);
Ok(BgpSecCertificateUpdates::default())
}
}
pub fn create_bgpsec_renewal(
&self,
issuance_timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<BgpSecCertificateUpdates> {
if let Ok(key) = self.get_current_key() {
let renew_threshold = Some(
issuance_timing.new_bgpsec_issuance_threshold()
);
self.bgpsec_certificates.create_renewal(
key,
renew_threshold,
issuance_timing,
signer,
)
}
else {
debug!(
"no BGPSec certificates to renew - \
resource class has no current key"
);
Ok(BgpSecCertificateUpdates::default())
}
}
}
impl ResourceClass {
pub fn create_rta_ee(
&self,
resources: &ResourceSet,
validity: Validity,
key: KeyIdentifier,
signer: &KrillSigner,
) -> KrillResult<Cert> {
let current = self.current_key().ok_or_else(|| {
Error::custom("No current key to sign RTA with")
})?;
if !current.incoming_cert().resources.contains(resources) {
return Err(Error::custom("Resources for RTA not held"));
}
let pub_key = signer.get_key_info(&key).map_err(Error::signer)?;
let ee = SignSupport::make_rta_ee_cert(
resources,
current.incoming_cert(), ¤t.key_id(),
validity, pub_key, signer,
)?;
Ok(ee)
}
}
impl ResourceClass {
pub fn apply_issuance_request(
&mut self,
key_id: KeyIdentifier,
req: IssuanceRequest,
) {
self.key_state.apply_issuance_request(key_id, req);
}
pub fn apply_received_cert(
&mut self,
key_id: KeyIdentifier,
cert: ReceivedCert,
) {
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);
}
}
}
}
pub fn apply_pending_key_id_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"
),
}
}
pub fn apply_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"
),
}
}
pub fn apply_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"
),
}
}
pub fn apply_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"
),
}
}
pub fn apply_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"
),
}
}
pub fn apply_added_issued_certificate(
&mut self, issued: IssuedCertificate
) {
self.certificates.add_issued_certificate(issued);
}
pub fn apply_unsuspend_certificate(
&mut self, unsuspended: UnsuspendedCert
) {
self.certificates.unsuspend_certificate(unsuspended);
}
pub fn apply_suspend_certificate(&mut self, suspended: SuspendedCert) {
self.certificates.suspend_certificate(suspended);
}
pub fn apply_removed_revoked_key(&mut self, key: &KeyIdentifier) {
self.certificates.remove_revoked_key(key);
}
pub fn apply_roa_updates(&mut self, updates: RoaUpdates) {
self.roas.apply_updates(updates);
}
pub fn apply_aspa_updates(&mut self, updates: AspaObjectsUpdates) {
self.aspas.apply_updates(updates)
}
pub fn apply_bgpsec_updates(
&mut self, updates: BgpSecCertificateUpdates,
) {
self.bgpsec_certificates.apply_updates(updates)
}
}
pub type DropReason = String;