use log::{debug, info, warn};
use rpki::ca::idexchange::{CaHandle, RepoInfo};
use rpki::ca::provisioning::{
IssuanceRequest, RequestResourceLimit, ResourceClassEntitlements,
ResourceClassName, RevocationRequest,
};
use rpki::crypto::KeyIdentifier;
use rpki::repository::{resources::ResourceSet, x509::Time};
use serde::{Deserialize, Serialize};
use crate::api::ca::{
ActiveInfo, CertifiedKeyInfo, PendingInfo, PendingKeyInfo, ReceivedCert,
ResourceClassKeysInfo, RollNewInfo, RollOldInfo, RollPendingInfo,
};
use crate::commons::KrillResult;
use crate::commons::crypto::KrillSigner;
use crate::commons::error::Error;
use super::events::CertAuthEvent;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CertifiedKey {
key_id: KeyIdentifier,
incoming_cert: ReceivedCert,
request: Option<IssuanceRequest>,
#[serde(skip_serializing_if = "Option::is_none")]
old_repo: Option<RepoInfo>,
}
impl CertifiedKey {
pub fn create(incoming_cert: ReceivedCert) -> Self {
Self {
key_id: incoming_cert.key_identifier(),
incoming_cert,
request: None,
old_repo: None,
}
}
pub fn new(
key_id: KeyIdentifier,
incoming_cert: ReceivedCert,
request: Option<IssuanceRequest>,
old_repo: Option<RepoInfo>
) -> Self {
Self { key_id, incoming_cert, request, old_repo }
}
pub fn key_id(&self) -> KeyIdentifier {
self.key_id
}
pub fn incoming_cert(&self) -> &ReceivedCert {
&self.incoming_cert
}
pub fn set_incoming_cert(&mut self, cert: ReceivedCert) {
self.request = None;
self.incoming_cert = cert;
}
pub fn to_info(&self) -> CertifiedKeyInfo {
CertifiedKeyInfo {
key_id: self.key_id,
incoming_cert: self.incoming_cert.clone(),
request: None
}
}
fn wants_update(
&self,
handle: &CaHandle,
rcn: &ResourceClassName,
new_resources: &ResourceSet,
new_not_after: Time,
) -> bool {
if !self.incoming_cert.ca_repository().ends_with("/") {
return true;
}
let resources_diff = new_resources.difference(
&self.incoming_cert.resources
);
if !resources_diff.is_empty() {
info!(
"Will request new certificate for CA '{handle}' \
under RC '{rcn}'. \
Resources have changed: '{resources_diff}'",
);
return true;
}
let not_after = self.incoming_cert.validity.not_after();
let now = Time::now().timestamp();
let remaining_seconds_on_current = not_after.timestamp() - now;
let remaining_seconds_on_eligible = new_not_after.timestamp() - now;
if remaining_seconds_on_eligible <= 0 {
warn!(
"Will NOT request certificate for CA '{}' under RC '{}', \
the eligible not after time is set in the past: {}",
handle,
rcn,
new_not_after.to_rfc3339()
);
false
}
else if remaining_seconds_on_current == remaining_seconds_on_eligible {
debug!(
"Will not request new certificate for CA '{handle}' \
under RC '{rcn}'. Resources and not after time are unchanged.",
);
false
}
else if remaining_seconds_on_current > 0
&& (remaining_seconds_on_eligible as f64
/ remaining_seconds_on_current as f64)
< 0.9_f64
{
warn!(
"Parent of CA '{handle}' *reduced* not after time for certificate \
under RC '{rcn}'. This is odd, but requesting new certificate.",
);
true
}
else if remaining_seconds_on_current <= 0
|| (remaining_seconds_on_eligible as f64
/ remaining_seconds_on_current as f64)
> 1.1_f64
|| (remaining_seconds_on_eligible
- remaining_seconds_on_current >= 604_800)
{
info!(
"Will request new certificate for CA '{}' under RC '{}'. \
Not-after time increased to: {}",
handle,
rcn,
new_not_after.to_rfc3339()
);
true
}
else if self.incoming_cert().resources == ResourceSet::all() {
debug!(
"It is technically too early for a new update, but \
requesting one anyway since it is the TA"
);
true
}
else {
debug!(
"Will not request new certificate for CA '{}' under RC '{}'. \
Remaining not after time changed by less than 10%. \
From {} to {}",
handle,
rcn,
not_after.to_rfc3339(),
new_not_after.to_rfc3339()
);
false
}
}
}
type NewKey = CertifiedKey;
pub type CurrentKey = CertifiedKey;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PendingKey {
key_id: KeyIdentifier,
request: Option<IssuanceRequest>,
}
impl PendingKey {
pub fn new(key_id: KeyIdentifier) -> Self {
PendingKey {
key_id,
request: None,
}
}
pub fn to_info(&self) -> PendingKeyInfo {
PendingKeyInfo { key_id: self.key_id }
}
pub fn key_id(&self) -> KeyIdentifier {
self.key_id
}
}
#[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)]
pub struct OldKey {
key: CertifiedKey,
revoke_req: RevocationRequest,
}
impl OldKey {
pub fn new(key: CertifiedKey, revoke_req: RevocationRequest) -> Self {
OldKey { key, revoke_req }
}
pub fn set_incoming_cert(&mut self, cert: ReceivedCert) {
self.key.set_incoming_cert(cert)
}
pub fn _revoke_req(&self) -> &RevocationRequest {
&self.revoke_req
}
}
#[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)]
#[allow(clippy::large_enum_variant)]
#[serde(rename_all = "snake_case")]
pub enum KeyState {
Pending(PendingKey),
Active(CurrentKey),
RollPending(PendingKey, CurrentKey),
RollNew(NewKey, CurrentKey),
RollOld(CurrentKey, OldKey),
}
impl KeyState {
pub fn create(pending_key: KeyIdentifier) -> Self {
KeyState::Pending(PendingKey::new(pending_key))
}
pub fn apply_issuance_request(
&mut self,
key_id: KeyIdentifier,
req: IssuanceRequest,
) {
match self {
KeyState::Pending(pending) => {
pending.request = Some(req)
}
KeyState::Active(current) => {
current.request = Some(req)
}
KeyState::RollPending(pending, current) => {
if pending.key_id == key_id {
pending.request = Some(req)
}
else {
current.request = Some(req)
}
}
KeyState::RollNew(new, current) => {
if new.key_id == key_id {
new.request = Some(req)
}
else {
current.request = Some(req)
}
}
KeyState::RollOld(current, old) => {
if current.key_id == key_id {
current.request = Some(req)
}
else {
old.key.request = Some(req)
}
}
}
}
pub fn revoke(
&self,
class_name: ResourceClassName,
signer: &KrillSigner,
) -> KrillResult<Vec<RevocationRequest>> {
match self {
KeyState::Pending(_pending) => {
Ok(vec![])
}
KeyState::Active(current) | KeyState::RollPending(_, current) => {
Ok(vec![
Self::revoke_key(class_name, current.key_id, signer)?,
])
}
KeyState::RollNew(new, current) => {
Ok(vec![
Self::revoke_key(
class_name.clone(),
new.key_id,
signer,
)?,
Self::revoke_key(class_name, current.key_id, signer)?,
])
}
KeyState::RollOld(current, old) => {
Ok(vec![
Self::revoke_key(class_name, current.key_id, signer)?,
old.revoke_req.clone()
])
}
}
}
fn revoke_key(
class_name: ResourceClassName,
key_id: KeyIdentifier,
signer: &KrillSigner,
) -> KrillResult<RevocationRequest> {
Ok(RevocationRequest::new(
class_name,
signer.get_key_info(
&key_id
).map_err(Error::signer)?.key_identifier()
))
}
#[allow(clippy::too_many_arguments)]
pub fn append_entitlement_events(
&self,
handle: &CaHandle,
rcn: ResourceClassName,
entitlement: &ResourceClassEntitlements,
base_repo: &RepoInfo,
name_space: &str,
signer: &KrillSigner,
events: &mut Vec<CertAuthEvent>,
) -> KrillResult<()> {
let mut keys_for_requests = vec![];
match self {
KeyState::Pending(pending) => {
keys_for_requests.push((base_repo, pending.key_id));
}
KeyState::Active(current) => {
if current.wants_update(
handle,
&rcn,
entitlement.resource_set(),
entitlement.not_after(),
) {
keys_for_requests.push((
current.old_repo.as_ref().unwrap_or(base_repo),
current.key_id,
));
}
}
KeyState::RollPending(pending, current) => {
keys_for_requests.push((base_repo, pending.key_id));
if current.wants_update(
handle,
&rcn,
entitlement.resource_set(),
entitlement.not_after(),
) {
keys_for_requests.push((
current.old_repo.as_ref().unwrap_or(base_repo),
current.key_id,
));
}
}
KeyState::RollNew(new, current) => {
if new.wants_update(
handle,
&rcn,
entitlement.resource_set(),
entitlement.not_after(),
) {
keys_for_requests.push((
new.old_repo.as_ref().unwrap_or(base_repo),
new.key_id,
));
}
if current.wants_update(
handle,
&rcn,
entitlement.resource_set(),
entitlement.not_after(),
) {
keys_for_requests.push((
current.old_repo.as_ref().unwrap_or(base_repo),
current.key_id
));
}
}
KeyState::RollOld(current, old) => {
if current.wants_update(
handle,
&rcn,
entitlement.resource_set(),
entitlement.not_after(),
) {
keys_for_requests.push((
current.old_repo.as_ref().unwrap_or(base_repo),
current.key_id,
));
}
if old.key.wants_update(
handle,
&rcn,
entitlement.resource_set(),
entitlement.not_after(),
) {
keys_for_requests.push((
old.key.old_repo.as_ref().unwrap_or(base_repo),
current.key_id,
));
}
}
}
for (base_repo, key_id) in keys_for_requests.into_iter() {
events.push(CertAuthEvent::CertificateRequested {
resource_class_name: rcn.clone(),
req: self.create_issuance_req(
base_repo,
name_space,
entitlement.class_name().clone(),
&key_id,
signer,
)?,
ki: key_id,
});
}
for key in entitlement
.issued_certs()
.iter()
.map(|c| c.cert().subject_key_identifier())
{
if !self.knows_key(key) {
let revoke_req = RevocationRequest::new(
entitlement.class_name().clone(),
key,
);
events.push(CertAuthEvent::UnexpectedKeyFound {
resource_class_name: rcn.clone(),
revoke_req,
});
}
}
Ok(())
}
fn create_issuance_req(
&self,
base_repo: &RepoInfo,
name_space: &str,
class_name: ResourceClassName,
key: &KeyIdentifier,
signer: &KrillSigner,
) -> KrillResult<IssuanceRequest> {
let csr = signer.sign_csr(base_repo, name_space, key)?;
Ok(IssuanceRequest::new(
class_name,
RequestResourceLimit::default(),
csr,
))
}
fn knows_key(&self, key_id: KeyIdentifier) -> bool {
match self {
KeyState::Pending(pending) => pending.key_id == key_id,
KeyState::Active(current) => current.key_id == key_id,
KeyState::RollPending(pending, current) => {
pending.key_id == key_id || current.key_id == key_id
}
KeyState::RollNew(new, current) => {
new.key_id == key_id || current.key_id == key_id
}
KeyState::RollOld(current, old) => {
current.key_id == key_id || old.key.key_id == key_id
}
}
}
pub fn cert_requests(&self) -> Vec<IssuanceRequest> {
let mut res = vec![];
match self {
KeyState::Pending(pending) => {
if let Some(r) = pending.request.as_ref() {
res.push(r.clone())
}
}
KeyState::Active(current) => {
if let Some(r) = current.request.as_ref() {
res.push(r.clone())
}
}
KeyState::RollPending(pending, current) => {
if let Some(r) = pending.request.as_ref() {
res.push(r.clone())
}
if let Some(r) = current.request.as_ref() {
res.push(r.clone())
}
}
KeyState::RollNew(new, current) => {
if let Some(r) = new.request.as_ref() {
res.push(r.clone())
}
if let Some(r) = current.request.as_ref() {
res.push(r.clone())
}
}
KeyState::RollOld(current, old) => {
if let Some(r) = current.request.as_ref() {
res.push(r.clone())
}
if let Some(r) = old.key.request.as_ref() {
res.push(r.clone())
}
}
}
res
}
pub fn revoke_request(&self) -> Option<&RevocationRequest> {
match self {
KeyState::RollOld(_current, old) => Some(&old.revoke_req),
_ => None,
}
}
pub fn to_info(&self) -> ResourceClassKeysInfo {
match self.clone() {
KeyState::Pending(p) => {
ResourceClassKeysInfo::Pending(PendingInfo {
pending_key: p.to_info(),
})
}
KeyState::Active(c) => {
ResourceClassKeysInfo::Active(ActiveInfo {
active_key: c.to_info(),
})
}
KeyState::RollPending(p, c) => {
ResourceClassKeysInfo::RollPending(RollPendingInfo {
pending_key: p.to_info(),
active_key: c.to_info(),
})
}
KeyState::RollNew(n, c) => {
ResourceClassKeysInfo::RollNew(RollNewInfo {
new_key: n.to_info(),
active_key: c.to_info(),
})
}
KeyState::RollOld(c, o) => {
ResourceClassKeysInfo::RollOld(RollOldInfo {
old_key: o.key.to_info(),
active_key: c.to_info(),
})
}
}
}
}
impl KeyState {
pub fn append_keyroll_initiate(
&self,
resource_class_name: ResourceClassName,
parent_class_name: ResourceClassName,
base_repo: &RepoInfo,
name_space: &str,
signer: &KrillSigner,
events: &mut Vec<CertAuthEvent>,
) -> KrillResult<bool> {
match self {
KeyState::Active(_current) => {
let pending_key_id = signer.create_key()?;
let req = self.create_issuance_req(
base_repo,
name_space,
parent_class_name,
&pending_key_id,
signer,
)?;
events.push(CertAuthEvent::KeyRollPendingKeyAdded {
resource_class_name: resource_class_name.clone(),
pending_key_id,
});
events.push(CertAuthEvent::CertificateRequested {
resource_class_name,
req,
ki: pending_key_id,
});
Ok(true)
}
_ => Ok(false),
}
}
pub fn append_keyroll_activate(
&self,
resource_class_name: ResourceClassName,
parent_class_name: ResourceClassName,
signer: &KrillSigner,
events: &mut Vec<CertAuthEvent>,
) -> KrillResult<()> {
match self {
KeyState::RollNew(new, current) => {
if new.request.is_some() || current.request.is_some() {
Err(Error::KeyRollActivatePendingRequests)
}
else {
let revoke_req = Self::revoke_key(
parent_class_name,
current.key_id,
signer,
)?;
events.push(CertAuthEvent::KeyRollActivated {
resource_class_name,
revoke_req,
});
Ok(())
}
}
_ => Err(Error::KeyUseNoNewKey),
}
}
pub fn new_key(&self) -> Option<&CertifiedKey> {
match self {
KeyState::RollNew(new, _) => Some(new),
_ => None,
}
}
}
impl KeyState {
pub fn set_old_repo_if_in_active_state(&mut self, repo: RepoInfo) {
if let KeyState::Active(current) = self {
current.old_repo = Some(repo);
}
}
}