use std::collections::HashMap;
use rpki::ca::provisioning::ResourceClassName;
use rpki::crypto::KeyIdentifier;
use rpki::repository::resources::ResourceSet;
use serde::{Deserialize, Serialize};
use crate::api::ca::{
ChildCaInfo, ChildState, IdCertInfo, IssuedCertificate, ReceivedCert,
SuspendedCert, UnsuspendedCert,
};
use crate::commons::KrillResult;
use crate::commons::crypto::{KrillSigner, SignSupport};
use crate::commons::error::Error;
use crate::config::IssuanceTimingConfig;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[allow(clippy::large_enum_variant)]
#[serde(rename_all = "snake_case")]
pub enum UsedKeyState {
#[serde(alias = "current")]
InUse(ResourceClassName),
Revoked,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ChildDetails {
#[serde(default)]
pub state: ChildState,
pub id_cert: IdCertInfo,
pub resources: ResourceSet,
pub used_keys: HashMap<KeyIdentifier, UsedKeyState>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub rcn_map: HashMap<ResourceClassName, ResourceClassName>,
}
impl ChildDetails {
pub fn new(id_cert: IdCertInfo, resources: ResourceSet) -> Self {
ChildDetails {
state: ChildState::Active,
id_cert,
resources,
used_keys: HashMap::new(),
rcn_map: HashMap::new(),
}
}
pub fn to_info(&self) -> ChildCaInfo {
ChildCaInfo {
state: self.state,
id_cert: self.id_cert.clone(),
entitled_resources: self.resources.clone(),
}
}
pub fn name_for_parent_rcn(
&self, name_in_parent: &ResourceClassName,
) -> ResourceClassName {
self.rcn_map.get(name_in_parent).unwrap_or(name_in_parent).clone()
}
pub fn parent_name_for_rcn(
&self,
name_in_child: &ResourceClassName,
) -> ResourceClassName {
self.rcn_map.iter()
.find(|(_k, v)| *v == name_in_child)
.map(|(k, _v)| k.clone())
.unwrap_or_else(|| name_in_child.clone())
}
pub fn issued(
&self,
parent_rcn: &ResourceClassName,
) -> Vec<KeyIdentifier> {
let mut res = vec![];
for (ki, used_key_state) in self.used_keys.iter() {
if
let UsedKeyState::InUse(found_rcn) = used_key_state
&& found_rcn == parent_rcn
{
res.push(*ki)
}
}
res
}
pub fn is_issued(&self, ki: &KeyIdentifier) -> bool {
matches!(self.used_keys.get(ki), Some(UsedKeyState::InUse(_)))
}
pub fn verify_key_allowed(
&self,
ki: &KeyIdentifier,
parent_rcn: &ResourceClassName,
) -> KrillResult<()> {
if let Some(last_response) = self.used_keys.get(ki) {
let allowed = match last_response {
UsedKeyState::Revoked => false,
UsedKeyState::InUse(found) => found == parent_rcn,
};
if !allowed {
return Err(Error::KeyUseAttemptReuse);
}
}
Ok(())
}
}
#[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 add_issued_certificate(&mut self, issued: IssuedCertificate) {
let ki = issued.key_identifier();
self.issued.insert(ki, issued);
}
pub fn unsuspend_certificate(&mut self, unsuspended: UnsuspendedCert) {
let ki = unsuspended.key_identifier();
self.suspended.remove(&ki);
self.issued.insert(ki, unsuspended.into_converted());
}
pub fn suspend_certificate(&mut self, suspended: SuspendedCert) {
let ki = suspended.key_identifier();
self.issued.remove(&ki);
self.suspended.insert(ki, suspended);
}
pub fn remove_revoked_key(&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 activate_key(
&self,
signing_cert: &ReceivedCert,
issuance_timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<ChildCertificateUpdates> {
let mut updates = ChildCertificateUpdates::default();
for issued in self.issued.values() {
updates.issued.push(self.re_issue(
issued,
None,
signing_cert,
issuance_timing,
signer,
)?);
}
for suspended in self.suspended.values() {
updates.suspended.push(
self.re_issue(
&suspended.to_converted(),
None,
signing_cert,
issuance_timing,
signer,
)?
.into_converted(),
);
}
Ok(updates)
}
pub fn shrink_overclaiming(
&self,
received_cert: &ReceivedCert,
issuance_timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<ChildCertificateUpdates> {
let mut updates = ChildCertificateUpdates::default();
let updated_resources = &received_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.removed.push(issued.key_identifier());
}
else {
updates.issued.push(self.re_issue(
issued,
Some(reduced_set),
received_cert,
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.removed.push(suspended.key_identifier());
}
else {
updates.suspended.push(
self.re_issue(
&suspended.to_converted(),
Some(reduced_set),
received_cert,
issuance_timing,
signer,
)?.into_converted(),
);
}
}
}
Ok(updates)
}
fn re_issue(
&self,
previous: &IssuedCertificate,
updated_resources: Option<ResourceSet>,
signing_cert: &ReceivedCert,
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_cert,
issuance_timing.new_child_cert_validity(),
signer,
)?;
Ok(re_issued)
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct ChildCertificateUpdates {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub issued: Vec<IssuedCertificate>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub removed: Vec<KeyIdentifier>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub suspended: Vec<SuspendedCert>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub unsuspended: Vec<UnsuspendedCert>,
}
impl ChildCertificateUpdates {
pub fn is_empty(&self) -> bool {
self.issued.is_empty()
&& self.removed.is_empty()
&& self.suspended.is_empty()
&& self.unsuspended.is_empty()
}
}