use std::collections::HashMap;
use rpki::{
ca::{idexchange::ChildHandle, provisioning::ResourceClassName},
crypto::KeyIdentifier,
repository::resources::ResourceSet,
};
use crate::{
commons::{
api::{ChildCaInfo, ChildState, IdCertInfo, IssuedCertificate, SuspendedCert, UnsuspendedCert},
crypto::{KrillSigner, SignSupport},
error::Error,
KrillResult,
},
daemon::{
ca::{CertifiedKey, ChildCertificateUpdates},
config::IssuanceTimingConfig,
},
};
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[allow(clippy::large_enum_variant)]
#[serde(rename_all = "snake_case")]
pub enum UsedKeyState {
Current(ResourceClassName),
Revoked,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ChildDetails {
#[serde(default)]
state: ChildState,
id_cert: IdCertInfo,
resources: ResourceSet,
used_keys: HashMap<KeyIdentifier, UsedKeyState>,
}
impl ChildDetails {
pub fn new(id_cert: IdCertInfo, resources: ResourceSet) -> Self {
ChildDetails {
state: ChildState::Active,
id_cert,
resources,
used_keys: HashMap::new(),
}
}
pub fn is_suspended(&self) -> bool {
self.state == ChildState::Suspended
}
pub fn suspend(&mut self) {
self.state = ChildState::Suspended;
}
pub fn unsuspend(&mut self) {
self.state = ChildState::Active;
}
pub fn id_cert(&self) -> &IdCertInfo {
&self.id_cert
}
pub fn set_id_cert(&mut self, id_cert: IdCertInfo) {
self.id_cert = id_cert;
}
pub fn resources(&self) -> &ResourceSet {
&self.resources
}
pub fn set_resources(&mut self, resources: ResourceSet) {
self.resources = resources;
}
pub fn issued(&self, rcn: &ResourceClassName) -> Vec<KeyIdentifier> {
let mut res = vec![];
for (ki, used_key_state) in self.used_keys.iter() {
if let UsedKeyState::Current(found_rcn) = used_key_state {
if found_rcn == rcn {
res.push(*ki)
}
}
}
res
}
pub fn is_issued(&self, ki: &KeyIdentifier) -> bool {
matches!(self.used_keys.get(ki), Some(UsedKeyState::Current(_)))
}
pub fn add_issue_response(&mut self, rcn: ResourceClassName, ki: KeyIdentifier) {
self.used_keys.insert(ki, UsedKeyState::Current(rcn));
}
pub fn add_revoke_response(&mut self, ki: KeyIdentifier) {
self.used_keys.insert(ki, UsedKeyState::Revoked);
}
pub fn verify_key_allowed(&self, ki: &KeyIdentifier, rcn: &ResourceClassName) -> KrillResult<()> {
if let Some(last_response) = self.used_keys.get(ki) {
let allowed = match last_response {
UsedKeyState::Revoked => false,
UsedKeyState::Current(found) => found == rcn,
};
if !allowed {
return Err(Error::KeyUseAttemptReuse);
}
}
Ok(())
}
}
impl From<ChildDetails> for ChildCaInfo {
fn from(details: ChildDetails) -> Self {
ChildCaInfo::new(details.state, details.id_cert, details.resources)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Children {
inner: HashMap<ChildHandle, ChildDetails>,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct ChildCertificates {
#[serde(alias = "inner")] issued: HashMap<KeyIdentifier, IssuedCertificate>,
#[serde(skip_serializing_if = "HashMap::is_empty", default = "HashMap::new")]
suspended: HashMap<KeyIdentifier, SuspendedCert>,
}
impl ChildCertificates {
pub fn is_empty(&self) -> bool {
self.issued.is_empty() && self.suspended.is_empty()
}
pub fn certificate_issued(&mut self, issued: IssuedCertificate) {
let ki = issued.key_identifier();
self.issued.insert(ki, issued);
}
pub fn certificate_unsuspended(&mut self, unsuspended: UnsuspendedCert) {
let ki = unsuspended.key_identifier();
self.suspended.remove(&ki);
self.issued.insert(ki, unsuspended.into_converted());
}
pub fn certificate_suspended(&mut self, suspended: SuspendedCert) {
let ki = suspended.key_identifier();
self.issued.remove(&ki);
self.suspended.insert(ki, suspended);
}
pub fn key_revoked(&mut self, key: &KeyIdentifier) {
self.issued.remove(key);
self.suspended.remove(key);
}
pub fn get_issued(&self, ki: &KeyIdentifier) -> Option<&IssuedCertificate> {
self.issued.get(ki)
}
pub fn get_suspended(&self, ki: &KeyIdentifier) -> Option<&SuspendedCert> {
self.suspended.get(ki)
}
pub fn current(&self) -> impl Iterator<Item = &IssuedCertificate> {
self.issued.values()
}
pub fn activate_key(
&self,
new_key: &CertifiedKey,
issuance_timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<ChildCertificateUpdates> {
let mut updates = ChildCertificateUpdates::default();
for issued in self.issued.values() {
updates.issue(self.re_issue(issued, None, new_key, issuance_timing, signer)?);
}
for suspended in self.suspended.values() {
updates.suspend(
self.re_issue(&suspended.convert(), None, new_key, issuance_timing, signer)?
.into_converted(),
);
}
Ok(updates)
}
pub fn shrink_overclaiming(
&self,
updated_key: &CertifiedKey,
issuance_timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<ChildCertificateUpdates> {
let mut updates = ChildCertificateUpdates::default();
let updated_resources = updated_key.incoming_cert().resources();
for issued in self.issued.values() {
if let Some(reduced_set) = issued.reduced_applicable_resources(updated_resources) {
if reduced_set.is_empty() {
updates.remove(issued.key_identifier());
} else {
updates.issue(self.re_issue(issued, Some(reduced_set), updated_key, issuance_timing, signer)?);
}
}
}
for suspended in self.suspended.values() {
if let Some(reduced_set) = suspended.reduced_applicable_resources(updated_resources) {
if reduced_set.is_empty() {
updates.remove(suspended.key_identifier());
} else {
updates.suspend(
self.re_issue(
&suspended.convert(),
Some(reduced_set),
updated_key,
issuance_timing,
signer,
)?
.into_converted(),
);
}
}
}
Ok(updates)
}
fn re_issue(
&self,
previous: &IssuedCertificate,
updated_resources: Option<ResourceSet>,
signing_key: &CertifiedKey,
issuance_timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<IssuedCertificate> {
let csr_info = previous.csr_info().clone();
let resource_set = updated_resources.unwrap_or_else(|| previous.resources().clone());
let limit = previous.limit().clone();
let re_issued = SignSupport::make_issued_cert(
csr_info,
&resource_set,
limit,
signing_key,
issuance_timing.new_child_cert_validity(),
signer,
)?;
Ok(re_issued)
}
pub fn expiring(&self, issuance_timing: &IssuanceTimingConfig) -> Vec<&IssuedCertificate> {
self.issued
.values()
.filter(|issued| issued.validity().not_after() < issuance_timing.new_child_cert_issuance_threshold())
.collect()
}
pub fn overclaiming(&self, resources: &ResourceSet) -> Vec<&IssuedCertificate> {
self.issued
.values()
.filter(|issued| !resources.contains(issued.resources()))
.collect()
}
}