Force update resources for child CA.

This commit is contained in:
Tim Bruijnzeels
2019-08-14 16:15:09 +02:00
parent c91abfc3ec
commit 84bdd0c4fa
11 changed files with 598 additions and 148 deletions
+12 -3
View File
@@ -177,8 +177,13 @@ impl Options {
.help("Update the delegated IPv6 resources: e.g. 2001:db8::/32")
.required(false)
)
.arg(Arg::with_name("force")
.short("f")
.long("force")
.takes_value(false)
.help("Force resource shrink now.")
.required(false)
)
)
)
)
@@ -441,7 +446,11 @@ impl Options {
Some(resources)
};
let req = UpdateChildRequest::new(cert, resources);
let req = if m.is_present("force") {
UpdateChildRequest::force(cert, resources)
} else {
UpdateChildRequest::graceful(cert, resources)
};
command = Command::TrustAnchor(TrustAnchorCommand::UpdateChild(handle, req))
}
+19 -2
View File
@@ -405,14 +405,31 @@ pub enum ChildAuthRequest {
pub struct UpdateChildRequest {
id_cert: Option<IdCert>,
resources: Option<ResourceSet>,
force: bool,
}
impl UpdateChildRequest {
pub fn new(id_cert: Option<IdCert>, resources: Option<ResourceSet>) -> Self {
UpdateChildRequest { id_cert, resources }
pub fn graceful(id_cert: Option<IdCert>, resources: Option<ResourceSet>) -> Self {
UpdateChildRequest {
id_cert,
resources,
force: false,
}
}
pub fn force(id_cert: Option<IdCert>, resources: Option<ResourceSet>) -> Self {
UpdateChildRequest {
id_cert,
resources,
force: true,
}
}
pub fn unpack(self) -> (Option<IdCert>, Option<ResourceSet>) {
(self.id_cert, self.resources)
}
pub fn is_force(&self) -> bool {
self.force
}
}
+149 -18
View File
@@ -10,6 +10,8 @@ use std::{fmt, ops};
use bytes::Bytes;
use chrono::Duration;
use serde::de;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use rpki::cert::Cert;
use rpki::crypto::KeyIdentifier;
@@ -92,6 +94,10 @@ impl ChildCaDetails {
&self.resources
}
pub fn remove_resource(&mut self, class_name: &str) {
self.resources.remove(class_name);
}
/// This function will update the resource entitlements for an existing class
/// or create a new class if needed
pub fn set_resources_for_class(&mut self, class: &str, resources: ResourceSet) {
@@ -127,20 +133,56 @@ impl ChildCaDetails {
}
/// This type defines a reference to PublicKey for easy storage and lookup.
#[derive(Clone, Debug, Deserialize, Display, Eq, Hash, PartialEq, Serialize)]
pub struct KeyRef(String);
#[derive(Clone, Debug, Display, Eq, Hash, PartialEq)]
pub struct KeyRef(KeyIdentifier);
impl From<&KeyIdentifier> for KeyRef {
fn from(ki: &KeyIdentifier) -> Self {
let hex = ki.into_hex();
let s = unsafe { str::from_utf8_unchecked(&hex) };
KeyRef(s.to_string())
KeyRef(ki.clone())
}
}
impl From<KeyIdentifier> for KeyRef {
fn from(ki: KeyIdentifier) -> Self {
KeyRef(ki)
}
}
impl From<&KeyRef> for KeyIdentifier {
fn from(kr: &KeyRef) -> Self {
kr.0.clone()
}
}
impl From<KeyRef> for KeyIdentifier {
fn from(kr: KeyRef) -> Self {
kr.0
}
}
impl From<&Cert> for KeyRef {
fn from(c: &Cert) -> Self {
Self::from(&c.subject_key_identifier())
KeyRef(c.subject_key_identifier())
}
}
impl Serialize for KeyRef {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.to_string().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for KeyRef {
fn deserialize<D>(deserializer: D) -> Result<KeyRef, D::Error>
where
D: Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
let ki = KeyIdentifier::from_str(&string).map_err(de::Error::custom)?;
Ok(KeyRef(ki))
}
}
@@ -154,7 +196,7 @@ impl From<&Cert> for KeyRef {
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ChildResources {
resources: ResourceSet,
since: Time,
shrink_pending: Option<Time>,
not_after: Time,
certs: HashMap<KeyRef, IssuedCert>,
}
@@ -163,7 +205,7 @@ impl ChildResources {
pub fn new(resources: ResourceSet) -> Self {
ChildResources {
resources,
since: Time::now(),
shrink_pending: None,
not_after: Time::next_year(),
certs: HashMap::new(),
}
@@ -174,8 +216,10 @@ impl ChildResources {
}
pub fn set_resources(&mut self, resources: ResourceSet) {
if !resources.contains(&self.resources) {
self.shrink_pending = Some(Time::now());
}
self.resources = resources;
self.since = Time::now();
}
/// Give back the not_after time that would be used on newly
@@ -195,21 +239,43 @@ impl ChildResources {
}
}
pub fn certs(&self) -> impl Iterator<Item = &IssuedCert> {
pub fn certs_iter(&self) -> impl Iterator<Item = &IssuedCert> {
self.certs.values()
}
pub fn certs(&self) -> &HashMap<KeyRef, IssuedCert> {
&self.certs
}
pub fn cert(&self, key_id: &KeyIdentifier) -> Option<&IssuedCert> {
let key_ref = KeyRef::from(key_id);
self.certs.get(&key_ref)
}
pub fn add_cert(&mut self, cert: IssuedCert) {
let key_ref = KeyRef::from(cert.cert());
// We can assume that the correct not_after date was used when the certificate
// was issued - because the `not_after` function was called. Therefore we can update
// the not_after value for this child resource class for future use.
self.not_after = cert.cert().validity().not_after();
self.resources = ResourceSet::try_from(cert.cert()).unwrap();
// Update the certificate for this key, or insert it for a new key.
let key_ref = KeyRef::from(cert.cert());
self.certs.insert(key_ref, cert);
// If a shrink was pending, check that it's still applicable.
if self.shrink_pending.is_some()
&& self
.certs
.values()
.find(|c| !self.resources.contains(c.resource_set()))
.is_none()
{
self.shrink_pending = None;
}
}
pub fn shrink_pending(&self) -> Option<Time> {
self.shrink_pending
}
pub fn revoke(&mut self, key_id: &KeyIdentifier) {
@@ -218,6 +284,42 @@ impl ChildResources {
}
}
//------------ ReplacedObject ------------------------------------------------
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ReplacedObject {
revocation: Revocation,
hash: EncodedHash,
}
impl ReplacedObject {
pub fn new(revocation: Revocation, hash: EncodedHash) -> Self {
ReplacedObject { revocation, hash }
}
pub fn revocation(&self) -> Revocation {
self.revocation
}
pub fn hash(&self) -> &EncodedHash {
&self.hash
}
}
impl From<&Cert> for ReplacedObject {
fn from(c: &Cert) -> Self {
let revocation = Revocation::from(c);
let hash = EncodedHash::from_content(c.to_captured().as_slice());
ReplacedObject { revocation, hash }
}
}
impl From<&IssuedCert> for ReplacedObject {
fn from(issued: &IssuedCert) -> Self {
Self::from(issued.cert())
}
}
//------------ IssuedCert ----------------------------------------------------
/// This type defines an issued certificate, including its publication
@@ -236,6 +338,7 @@ pub struct IssuedCert {
limit: RequestResourceLimit, // the limit on the request
resource_set: ResourceSet,
cert: Cert,
replaces: Option<ReplacedObject>,
}
impl IssuedCert {
@@ -244,12 +347,14 @@ impl IssuedCert {
limit: RequestResourceLimit,
resource_set: ResourceSet,
cert: Cert,
replaces: Option<ReplacedObject>,
) -> Self {
IssuedCert {
uri,
limit,
resource_set,
cert,
replaces,
}
}
@@ -269,6 +374,9 @@ impl IssuedCert {
pub fn cert(&self) -> &Cert {
&self.cert
}
pub fn replaces(&self) -> Option<&ReplacedObject> {
self.replaces.as_ref()
}
}
impl PartialEq for IssuedCert {
@@ -769,7 +877,8 @@ impl CurrentObjects {
/// Returns publish's for all objects in this set.
pub fn publish(&self, base_uri: &RepoInfo, name_space: &str) -> Vec<Publish> {
let ca_repo = base_uri.ca_repository(name_space);
self.0.iter()
self.0
.iter()
.map(|(name, object)| {
Publish::new(None, ca_repo.join(name.as_bytes()), object.content.clone())
})
@@ -1065,6 +1174,14 @@ impl AddedObject {
}
}
impl From<&Cert> for AddedObject {
fn from(cert: &Cert) -> Self {
let name = ObjectName::from(cert);
let object = CurrentObject::from(cert);
AddedObject { name, object }
}
}
//------------ UpdatedObject -------------------------------------------------
/// A new object that replaces an earlier version by this name.
@@ -1079,6 +1196,12 @@ impl UpdatedObject {
pub fn new(name: ObjectName, object: CurrentObject, old: EncodedHash) -> Self {
UpdatedObject { name, object, old }
}
pub fn for_cert(new: &Cert, old: EncodedHash) -> Self {
let name = ObjectName::from(new);
let object = CurrentObject::from(new);
UpdatedObject { name, object, old }
}
}
//------------ WithdrawnObject -----------------------------------------------
@@ -1099,6 +1222,14 @@ impl WithdrawnObject {
}
}
impl From<&Cert> for WithdrawnObject {
fn from(c: &Cert) -> Self {
let name = ObjectName::from(c);
let hash = EncodedHash::from_content(c.to_captured().as_slice());
WithdrawnObject { name, hash }
}
}
//------------ ResourceSet ---------------------------------------------------
/// This type defines a set of Internet Number Resources.
@@ -1423,7 +1554,7 @@ impl CertAuthInfo {
match &self.parents {
CaParentsInfo::SelfSigned(key, _tal) => {
res.append(&mut key.current_set.objects().publish(self.base_repo(), ""));
},
}
CaParentsInfo::Parents(map) => {
for (_parent, parent_info) in map.iter() {
for rc in parent_info.resources().values() {
@@ -1436,7 +1567,6 @@ impl CertAuthInfo {
res
}
}
/// This type contains public data about parents of a CA
@@ -1501,8 +1631,9 @@ impl ResourceClassInfo {
pub fn objects(&self) -> CurrentObjects {
let mut res = CurrentObjects::default();
match &self.keys {
ResourceClassKeysInfo::Pending(_) => {},
ResourceClassKeysInfo::Active(current) | ResourceClassKeysInfo::RollPending(_, current)=> {
ResourceClassKeysInfo::Pending(_) => {}
ResourceClassKeysInfo::Active(current)
| ResourceClassKeysInfo::RollPending(_, current) => {
res = res + current.current_set().objects().clone();
}
ResourceClassKeysInfo::RollNew(new, current) => {
+3 -2
View File
@@ -303,7 +303,8 @@ impl Qry {
a.exhausted()?;
let ski = KeyIdentifier::try_from(ski_bytes.as_ref()).map_err(|_| Error::InvalidSki)?;
let ski =
KeyIdentifier::try_from(ski_bytes.as_slice()).map_err(|_| Error::InvalidSki)?;
Ok(RevocationRequest::new(class_name.to_string(), ski))
})
}
@@ -579,7 +580,7 @@ impl Res {
let cert = Self::decode_cert(r)?;
let resource_set = ResourceSet::try_from(&cert)?;
Ok(IssuedCert::new(cert_url, limit, resource_set, cert))
Ok(IssuedCert::new(cert_url, limit, resource_set, cert, None))
})
}
+254 -71
View File
@@ -6,7 +6,7 @@ use std::sync::{Arc, RwLock};
use bytes::Bytes;
use chrono::Duration;
use rpki::cert::{KeyUsage, Overclaim, TbsCert};
use rpki::cert::{Cert, KeyUsage, Overclaim, TbsCert};
use rpki::crypto::{PublicKey, PublicKeyFormat};
use rpki::x509::{Name, Serial, Time, Validity};
@@ -15,11 +15,12 @@ use krill_commons::api::admin::{
};
use krill_commons::api::ca::{
AddedObject, CaParentsInfo, CertAuthInfo, CertifiedKey, ChildCaDetails, CurrentObject,
IssuedCert, ObjectName, ObjectsDelta, ParentCaInfo, PublicationDelta, RcvdCert, RepoInfo,
ResourceSet, Revocation, TrustAnchorInfo, TrustAnchorLocator, UpdatedObject, WithdrawnObject,
IssuedCert, ObjectName, ObjectsDelta, ParentCaInfo, PublicationDelta, RcvdCert, ReplacedObject,
RepoInfo, ResourceSet, Revocation, TrustAnchorInfo, TrustAnchorLocator, UpdatedObject,
WithdrawnObject,
};
use krill_commons::api::{
self, EncodedHash, EntitlementClass, Entitlements, IssuanceRequest, IssuanceResponse,
self, EntitlementClass, Entitlements, IssuanceRequest, IssuanceResponse, RequestResourceLimit,
RevocationRequest, RevocationResponse, SigningCert, DFLT_CLASS,
};
use krill_commons::eventsourcing::{Aggregate, StoredEvent};
@@ -34,6 +35,7 @@ use crate::ca::{
self, ChildHandle, Cmd, CmdDet, Error, Evt, EvtDet, Ini, ParentHandle, ResourceClass,
ResourceClassName, Result, SignSupport, Signer,
};
use crate::ca::signing::CertSiaInfo;
//------------ Rfc8183Id ---------------------------------------------------
@@ -233,7 +235,10 @@ impl<S: Signer> Aggregate for CertAuth<S> {
let child = self.children.get_mut(&child).unwrap();
child.set_resources_for_class(&class, resources)
}
EvtDet::ChildRemovedResourceClass(_child, _name) => unimplemented!(),
EvtDet::ChildRemovedResourceClass(child, name) => {
let child = self.children.get_mut(&child).unwrap();
child.remove_resource(name.as_str());
}
//-----------------------------------------------------------------------
// Being a child
@@ -312,16 +317,17 @@ impl<S: Signer> Aggregate for CertAuth<S> {
fn process_command(&self, command: Cmd<S>) -> ca::Result<Vec<Evt>> {
match command.into_details() {
// being a parent
CmdDet::AddChild(child, id_cert_opt, resources) => {
CmdDet::ChildAdd(child, id_cert_opt, resources) => {
self.add_child(child, id_cert_opt, resources)
}
CmdDet::UpdateChild(child, req) => self.update_child(&child, req),
CmdDet::CertifyChild(child, request, signer) => {
CmdDet::ChildUpdate(child, req) => self.update_child(&child, req),
CmdDet::ChildCertify(child, request, signer) => {
self.certify_child(child, request, signer)
}
CmdDet::RevokeKeyForChild(child, request, signer) => {
CmdDet::ChildRevokeKey(child, request, signer) => {
self.revoke_child_key(child, request, signer)
}
CmdDet::ChildShrink(child, grace, signer) => self.shrink_child(&child, grace, signer),
// being a child
CmdDet::AddParent(parent, info) => self.add_parent(parent, info),
@@ -463,7 +469,7 @@ impl<S: Signer> CertAuth<S> {
}
let until = child_resources.not_after();
let issued = child_resources.certs().cloned().collect();
let issued = child_resources.certs_iter().cloned().collect();
let cert = match &self.parents {
CaParents::SelfSigned(key, _tal) => key.incoming_cert(),
@@ -548,6 +554,59 @@ impl<S: Signer> CertAuth<S> {
) -> ca::Result<Vec<Evt>> {
let (class_name, limit, csr) = request.unwrap();
csr.validate()
.map_err(|_| Error::invalid_csr(&child, "invalid signature"))?;
let sia_info = {
let ca_repository = csr
.ca_repository()
.cloned()
.ok_or_else(|| Error::invalid_csr(&child, "Missing CA repository uri"))?;
let rpki_manifest = csr
.rpki_manifest()
.cloned()
.ok_or_else(|| Error::invalid_csr(&child, "Missing rpki manifest uri"))?;
let rpki_notify = csr.rpki_notify().cloned();
CertSiaInfo::new(ca_repository, rpki_manifest, rpki_notify)
};
let pub_key = csr.public_key().clone();
let issue_response = self.issue_child_certificate(
&child,
&class_name,
pub_key,
sia_info,
limit,
signer.read().unwrap().deref(),
)?;
let publication_delta = self.update_published_child_certificates(
&class_name,
vec![issue_response.issued()],
vec![],
signer,
)?;
let issued_event =
EvtDet::child_certificate_issued(&self.handle, self.version, child, issue_response);
let publish_event = EvtDet::published_ta(&self.handle, self.version + 1, publication_delta);
Ok(vec![issued_event, publish_event])
}
/// Issue a new child certificate.
fn issue_child_certificate(
&self,
child: &ChildHandle,
class_name: &ResourceClassName,
pub_key: PublicKey,
sia_info: CertSiaInfo,
limit: RequestResourceLimit,
signer: &S,
) -> Result<IssuanceResponse> {
let issuing_key = match &self.parents {
CaParents::SelfSigned(key, _tal) => key,
CaParents::Parents(_) => unimplemented!("Issue #25 (delegate from CA)"),
@@ -565,26 +624,17 @@ impl<S: Signer> CertAuth<S> {
return Err(Error::MissingResources);
}
let current_cert = child_resources.cert(&pub_key.key_identifier());
let replaces = current_cert.map(ReplacedObject::from);
let resources = child_resources
.resources()
.apply_limit(&limit)
.map_err(|_| Error::MissingResources)?;
csr.validate()
.map_err(|_| Error::invalid_csr(&child, "invalid signature"))?;
// TODO: Check for key-re-use, ultimately return 1204 (RFC6492 3.4.1)
let current_cert = child_resources.cert(&csr.public_key().key_identifier());
// Check if we need to revoke
let mut revocations = vec![];
if let Some(issued) = current_cert {
revocations.push(Revocation::from(issued.cert()))
}
// create new cert
let issued_cert = {
let serial = { Serial::random(signer.read().unwrap().deref()).map_err(Error::signer)? };
let serial = { Serial::random(signer).map_err(Error::signer)? };
let issuer = issuing_cert.cert().subject().clone();
let validity = Validity::new(
@@ -592,8 +642,7 @@ impl<S: Signer> CertAuth<S> {
child_resources.not_after(),
);
let subject = Some(Name::from_pub_key(csr.public_key()));
let pub_key = csr.public_key().clone();
let subject = Some(Name::from_pub_key(&pub_key));
let key_usage = KeyUsage::Ca;
let overclaim = Overclaim::Refuse;
@@ -609,20 +658,13 @@ impl<S: Signer> CertAuth<S> {
// because the publication server for those URIs should verify the
// identity of the publisher, and that RPs will not invalidate the
// content of another CA's repo, if they it is wrongfully claimed.
let ca_repository = csr
.ca_repository()
.ok_or_else(|| Error::invalid_csr(&child, "missing ca repo"))?;
let rpki_manifest = csr
.rpki_manifest()
.ok_or_else(|| Error::invalid_csr(&child, "missing mft uri"))?;
let rpki_notify = csr.rpki_notify();
let (ca_repository, rpki_manifest, rpki_notify) = sia_info.unpack();
cert.set_ca_issuer(Some(issuing_cert.uri().clone()));
cert.set_crl_uri(Some(issuing_cert.crl_uri()));
cert.set_ca_repository(Some(ca_repository.clone()));
cert.set_rpki_manifest(Some(rpki_manifest.clone()));
cert.set_rpki_notify(rpki_notify.cloned());
cert.set_ca_repository(Some(ca_repository));
cert.set_rpki_manifest(Some(rpki_manifest));
cert.set_rpki_notify(rpki_notify);
cert.set_as_resources(Some(resources.to_as_resources()));
cert.set_v4_resources(Some(resources.to_ip_resources_v4()));
@@ -631,55 +673,195 @@ impl<S: Signer> CertAuth<S> {
cert.set_authority_key_identifier(Some(issuing_cert.cert().subject_key_identifier()));
let cert = {
cert.into_cert(signer.read().unwrap().deref(), issuing_key.key_id())
cert.into_cert(signer, issuing_key.key_id())
.map_err(Error::signer)?
};
let cert_uri = issuing_cert.uri_for_object(&cert);
IssuedCert::new(cert_uri, limit, resources.clone(), cert)
IssuedCert::new(cert_uri, limit, resources.clone(), cert, replaces)
};
let version = self.version;
let cert_object = CurrentObject::from(issued_cert.cert());
let signing_cert = SigningCert::from(issuing_cert);
let response = IssuanceResponse::new(
DFLT_CLASS.to_string(),
Ok(IssuanceResponse::new(
class_name.clone(),
signing_cert,
resources,
issued_cert.cert().validity().not_after(),
issued_cert.clone(),
);
let issued_event = EvtDet::child_certificate_issued(&self.handle, version, child, response);
let delta = {
let ca_repo = self.base_repo.ca_repository("");
let mut delta = ObjectsDelta::new(ca_repo);
let cert_name = ObjectName::from(issued_cert.cert());
match current_cert {
None => delta.add(AddedObject::new(cert_name, cert_object)),
Some(old) => {
let old_hash = EncodedHash::from_content(old.cert().to_captured().as_slice());
delta.update(UpdatedObject::new(cert_name, cert_object, old_hash));
}
}
delta
};
let publish_event = EvtDet::published_ta(
&self.handle,
version + 1,
SignSupport::publish(signer, issuing_key, &self.base_repo, "", delta, revocations)
.map_err(Error::signer)?,
);
Ok(vec![issued_event, publish_event])
issued_cert,
))
}
/// Create a publish event details including the revocations, update, withdrawals needed
/// for updating child certificates.
pub fn update_published_child_certificates(
&self,
_class_name: &ResourceClassName, // Issue #25
issued_certs: Vec<&IssuedCert>,
removed_certs: Vec<&Cert>,
signer: Arc<RwLock<S>>,
) -> Result<PublicationDelta> {
let issuing_key = match &self.parents {
CaParents::SelfSigned(key, _tal) => key,
CaParents::Parents(_) => unimplemented!("Issue #25 (delegate from CA)"),
};
let name_space = ""; // TODO: #25 use name space for RC
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 = self.base_repo.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(),
)),
}
}
SignSupport::publish(
signer,
issuing_key,
&self.base_repo,
name_space,
objects_delta,
revocations,
)
.map_err(Error::signer)
}
/// Shrink a child if it has any overclaiming certificates and the grace period has passed.
///
/// When shrinking the parent will remove and completely revoke any resource classes for
/// which there are no more resources. And it will shrink certificates where resources are
/// lost, i.e. it will revoke the current certificate and issue a new certificate with the
/// new resource set on it.
///
/// Note: We could also go for the intersection of the currently entitled resources and the
/// resources on the old certificate, but.. this is useful only really if the child CA
/// deliberately asked for a certificate with a sub-set of resources (which is allowed, but
/// very uncommon, and unclear why it would be beneficial), and - more importantly - it
/// creates a corner case where there are new entitled resources but there is no intersection
/// with the old resource set.
fn shrink_child(
&self,
child_handle: &ChildHandle,
grace: Duration,
signer: Arc<RwLock<S>>,
) -> ca::Result<Vec<Evt>> {
let child = self.get_child(child_handle)?;
let mut events = vec![];
debug!("Checking if child {} needs shrinking", child_handle);
for (class_name, child_resources) in child.resources().iter() {
if let Some(pending_time) = child_resources.shrink_pending() {
if pending_time + grace <= Time::now() {
let mut issuance_responses = vec![];
let mut removed = vec![];
let new_resources = child_resources.resources();
if new_resources.is_empty() {
info!(
"Removing resource class '{}' for child '{}'",
class_name, child_handle
);
// Remove resource set and revoke all certs
for (keyref, issued) in child_resources.certs().iter() {
let revocation =
RevocationResponse::new(class_name.clone(), keyref.into());
events.push(EvtDet::ChildKeyRevoked(child_handle.clone(), revocation));
removed.push(issued.cert())
}
events.push(EvtDet::ChildRemovedResourceClass(
child_handle.clone(),
class_name.clone(),
));
} else {
// Re-issue all certs that are overclaiming.
for issued_cert in child_resources.certs_iter() {
if !new_resources.contains(issued_cert.resource_set()) {
info!(
"Shrinking cert in resource class '{}' for child '{}' to '{}'",
class_name, child_handle, new_resources
);
let sia_info = {
let ca_repo =
issued_cert.cert().ca_repository().cloned().unwrap();
let mft_uri =
issued_cert.cert().rpki_manifest().cloned().unwrap();
let not_opt = issued_cert.cert().rpki_notify().cloned();
CertSiaInfo::new(ca_repo, mft_uri, not_opt)
};
let pub_key = issued_cert.cert().subject_public_key_info().clone();
issuance_responses.push(self.issue_child_certificate(
child_handle,
class_name,
pub_key,
sia_info,
RequestResourceLimit::default(),
signer.read().unwrap().deref(),
)?);
}
}
}
let issued = issuance_responses.iter().map(|res| res.issued()).collect();
let publication_delta = self.update_published_child_certificates(
class_name,
issued,
removed,
signer.clone(),
)?;
for response in issuance_responses.into_iter() {
events.push(EvtDet::ChildCertificateIssued(
child_handle.clone(),
response,
));
}
events.push(EvtDet::TaPublished(publication_delta));
}
}
}
let mut version = self.version;
let events = events
.into_iter()
.map(|details| {
version += 1;
StoredEvent::new(self.handle(), version - 1, details)
})
.collect();
Ok(events)
}
/// Updates child IdCert and/or Resource entitlements.
///
/// Note: this does not yet revoke / reissue / republish anything. If the 'force' option was
/// used in the update request, then shrink_child should be called with a grace period that
/// is effective immediately.
fn update_child(&self, child_handle: &Handle, req: UpdateChildRequest) -> ca::Result<Vec<Evt>> {
let (cert_opt, resources_opt) = req.unpack();
@@ -730,6 +912,7 @@ impl<S: Signer> CertAuth<S> {
}
// Determine for each whether the entitlement is changed, added, or removed
for (class_name, entitled_resource_set) in child_entitlements.into_iter() {
if match child_resources.remove(&class_name) {
None => true,
+27 -12
View File
@@ -24,13 +24,15 @@ pub enum CmdDet<S: Signer> {
// ------------------------------------------------------------
// Add a new child under this parent CA
AddChild(ChildHandle, Option<IdCert>, ResourceSet),
ChildAdd(ChildHandle, Option<IdCert>, ResourceSet),
// Update some details for an existing child, e.g. resources.
UpdateChild(ChildHandle, UpdateChildRequest),
ChildUpdate(ChildHandle, UpdateChildRequest),
// Process an issuance request by an existing child.
CertifyChild(ChildHandle, IssuanceRequest, Arc<RwLock<S>>),
ChildCertify(ChildHandle, IssuanceRequest, Arc<RwLock<S>>),
// Process a revoke request by an existing child.
RevokeKeyForChild(ChildHandle, RevocationRequest, Arc<RwLock<S>>),
ChildRevokeKey(ChildHandle, RevocationRequest, Arc<RwLock<S>>),
// Shrink child (only has events in case child is overclaiming)
ChildShrink(ChildHandle, Duration, Arc<RwLock<S>>),
// ------------------------------------------------------------
// Being a child (only allowed if this CA is not self-signed)
@@ -81,7 +83,7 @@ impl<S: Signer> CmdDet<S> {
/// Adds a child to this CA. Will return an error in case you try
/// to give the child resources not held by the CA. And until issue
/// #25 is implemented, returns an error when the CA is not a TA.
pub fn add_child(
pub fn child_add(
handle: &Handle,
child_handle: Handle,
child_id_cert: Option<IdCert>,
@@ -90,21 +92,21 @@ impl<S: Signer> CmdDet<S> {
eventsourcing::SentCommand::new(
handle,
None,
CmdDet::AddChild(child_handle, child_id_cert, child_resources),
CmdDet::ChildAdd(child_handle, child_id_cert, child_resources),
)
}
pub fn update_child(
pub fn child_update(
handle: &Handle,
child_handle: ChildHandle,
req: UpdateChildRequest,
) -> Cmd<S> {
eventsourcing::SentCommand::new(handle, None, CmdDet::UpdateChild(child_handle, req))
eventsourcing::SentCommand::new(handle, None, CmdDet::ChildUpdate(child_handle, req))
}
/// Certify a child. Will return an error in case the child is
/// unknown, or in case resources are not held by the child.
pub fn certify_child(
pub fn child_certify(
handle: &Handle,
child_handle: ChildHandle,
request: IssuanceRequest,
@@ -113,12 +115,12 @@ impl<S: Signer> CmdDet<S> {
eventsourcing::SentCommand::new(
handle,
None,
CmdDet::CertifyChild(child_handle, request, signer),
CmdDet::ChildCertify(child_handle, request, signer),
)
}
/// Revoke a key for a child.
pub fn revoke_key_for_child(
pub fn child_revoke_key(
handle: &Handle,
child_handle: ChildHandle,
request: RevocationRequest,
@@ -127,7 +129,20 @@ impl<S: Signer> CmdDet<S> {
eventsourcing::SentCommand::new(
handle,
None,
CmdDet::RevokeKeyForChild(child_handle, request, signer),
CmdDet::ChildRevokeKey(child_handle, request, signer),
)
}
pub fn child_shrink(
handle: &Handle,
child_handle: ChildHandle,
grace: Duration,
signer: Arc<RwLock<S>>,
) -> Cmd<S> {
eventsourcing::SentCommand::new(
handle,
None,
CmdDet::ChildShrink(child_handle, grace, signer),
)
}
+37 -18
View File
@@ -129,7 +129,7 @@ impl<S: Signer> CaServer<S> {
ChildAuthRequest::Rfc8183(req) => Some(req.id_cert().clone()),
};
let add_child = CmdDet::add_child(&ta_handle, handle.clone(), id_cert, resources);
let add_child = CmdDet::child_add(&ta_handle, handle.clone(), id_cert, resources);
let events = ta.process_command(add_child)?;
let ta = self.ca_store.update(&ta_handle, ta, events)?;
@@ -172,13 +172,27 @@ impl<S: Signer> CaServer<S> {
child: ChildHandle,
req: UpdateChildRequest,
) -> ServerResult<(), S> {
debug!("Finding details for {} under TA", child);
let ta = self.get_trust_anchor()?;
debug!("Updating details for {} under TA", child);
let mut ta = self.get_trust_anchor()?;
let ta_handle = ca::ta_handle();
let events = ta.process_command(CmdDet::update_child(&ta_handle, child, req))?;
let force = req.is_force();
let events = ta.process_command(CmdDet::child_update(&ta_handle, child.clone(), req))?;
if !events.is_empty() {
self.ca_store.update(&ta_handle, ta, events)?;
ta = self.ca_store.update(&ta_handle, ta, events)?;
if force {
let events = ta.process_command(CmdDet::child_shrink(
&ta_handle,
child,
Duration::seconds(0),
self.signer.clone(),
))?;
if !events.is_empty() {
self.ca_store.update(&ta_handle, ta, events)?;
}
}
}
Ok(())
@@ -276,7 +290,7 @@ impl<S: Signer> CaServer<S> {
unimplemented!("Issue for multiple classes from CAs, issue #25")
}
let cmd = CmdDet::certify_child(
let cmd = CmdDet::child_certify(
parent,
child.clone(),
issue_req.clone(),
@@ -303,8 +317,7 @@ impl<S: Signer> CaServer<S> {
) -> ServerResult<RevocationResponse, S> {
let res = (&revoke_request).into(); // response provided that no errors are returned earlier
let cmd =
CmdDet::revoke_key_for_child(ca_handle, child, revoke_request, self.signer.clone());
let cmd = CmdDet::child_revoke_key(ca_handle, child, revoke_request, self.signer.clone());
let ca = self.get_ca(ca_handle)?;
let events = ca.process_command(cmd)?;
@@ -425,13 +438,15 @@ impl<S: Signer> CaServer<S> {
self.send_cert_requests_handle_responses(handle, parent)
}
fn send_revoke_requests_handle_responses(&self, handle: &Handle, parent: &ParentHandle) -> ServerResult<(), S> {
fn send_revoke_requests_handle_responses(
&self,
handle: &Handle,
parent: &ParentHandle,
) -> ServerResult<(), S> {
let mut child = self.ca_store.get_latest(handle)?;
let requests = child.revoke_requests(parent);
let revoke_responses = self.send_revoke_requests(
handle, parent, requests
)?;
let revoke_responses = self.send_revoke_requests(handle, parent, requests)?;
for response in revoke_responses.into_iter() {
let cmd = CmdDet::key_roll_finish(handle, parent.clone(), response);
@@ -446,7 +461,7 @@ impl<S: Signer> CaServer<S> {
&self,
handle: &Handle,
parent: &ParentHandle,
requests: Vec<&RevocationRequest>
requests: Vec<&RevocationRequest>,
) -> ServerResult<Vec<RevocationResponse>, S> {
let child = self.ca_store.get_latest(handle)?;
match child.parent(parent)?.contact() {
@@ -469,7 +484,7 @@ impl<S: Signer> CaServer<S> {
let mut revocation_responses = vec![];
for req in revoke_requests.into_iter() {
let cmd = CmdDet::revoke_key_for_child(
let cmd = CmdDet::child_revoke_key(
parent_h,
handle.clone(),
req.clone(),
@@ -516,7 +531,11 @@ impl<S: Signer> CaServer<S> {
Ok(res)
}
fn send_cert_requests_handle_responses(&self, handle: &Handle, parent: &ParentHandle) -> ServerResult<(), S> {
fn send_cert_requests_handle_responses(
&self,
handle: &Handle,
parent: &ParentHandle,
) -> ServerResult<(), S> {
let mut child = self.ca_store.get_latest(handle)?;
let cert_requests = child.cert_requests(parent);
@@ -561,7 +580,7 @@ impl<S: Signer> CaServer<S> {
let class_name = req.class_name().to_string();
let pub_key = req.csr().public_key().clone();
let cmd = CmdDet::certify_child(parent_h, handle.clone(), req, self.signer.clone());
let cmd = CmdDet::child_certify(parent_h, handle.clone(), req, self.signer.clone());
let events = parent.process_command(cmd)?;
parent = self.ca_store.update(parent_h, parent, events)?;
@@ -844,7 +863,7 @@ mod tests {
// - Child added to TA
//
let cmd = CmdDet::add_child(&ta_handle, child_handle.clone(), None, child_rs);
let cmd = CmdDet::child_add(&ta_handle, child_handle.clone(), None, child_rs);
let events = ta.process_command(cmd).unwrap();
let ta = ca_store.update(&ta_handle, ta, events).unwrap();
@@ -906,7 +925,7 @@ mod tests {
let request = IssuanceRequest::new(DFLT_CLASS.to_string(), limit, csr);
let ta_cmd =
CmdDet::certify_child(&ta_handle, child_handle.clone(), request, signer.clone());
CmdDet::child_certify(&ta_handle, child_handle.clone(), request, signer.clone());
let ta_events = ta.process_command(ta_cmd).unwrap();
let issued_evt = ta_events[0].clone().into_details();
+31
View File
@@ -10,6 +10,7 @@ use rpki::crypto::signer::KeyError;
use rpki::crypto::{self, DigestAlgorithm, KeyIdentifier, SigningError};
use rpki::manifest::{FileAndHash, Manifest, ManifestContent};
use rpki::sigobj::SignedObjectBuilder;
use rpki::uri;
use rpki::x509::{Serial, Time, Validity};
use krill_commons::api::ca::{
@@ -23,6 +24,36 @@ use krill_commons::util::softsigner::KeyId;
pub trait Signer: crypto::Signer<KeyId = KeyId> + Clone + Sized + Sync + Send + 'static {}
impl<T: crypto::Signer<KeyId = KeyId> + Clone + Sized + Sync + Send + 'static> Signer for T {}
//------------ CertSiaInfo ---------------------------------------------------
pub type CaRepository = uri::Rsync;
pub type RpkiManifest = uri::Rsync;
pub type RpkiNotify = uri::Https;
pub struct CertSiaInfo {
ca_repository: CaRepository,
rpki_manifest: RpkiManifest,
rpki_notify: Option<RpkiNotify>,
}
impl CertSiaInfo {
pub fn new(
ca_repository: CaRepository,
rpki_manifest: RpkiManifest,
rpki_notify: Option<RpkiNotify>,
) -> Self {
CertSiaInfo {
ca_repository,
rpki_manifest,
rpki_notify,
}
}
pub fn unpack(self) -> (CaRepository, RpkiManifest, Option<RpkiNotify>) {
(self.ca_repository, self.rpki_manifest, self.rpki_notify)
}
}
//------------ CaSignSupport -------------------------------------------------
/// Support signing by CAs
+1 -1
View File
@@ -7,9 +7,9 @@ use std::collections::VecDeque;
use std::fmt;
use std::sync::RwLock;
use krill_commons::api::RevocationRequest;
use krill_commons::api::admin::Handle;
use krill_commons::api::publication::PublishDelta;
use krill_commons::api::RevocationRequest;
use krill_commons::eventsourcing;
use crate::ca::{CertAuth, Evt, EvtDet, ParentHandle, Signer};
+4 -2
View File
@@ -58,12 +58,14 @@ fn make_event_sh(
}
QueueEvent::ResourceClassRemoved(handle, parent, revocations) => {
let revocations = revocations.iter().collect();
if caserver.send_revoke_requests(&handle, &parent, revocations).is_err() {
if caserver
.send_revoke_requests(&handle, &parent, revocations)
.is_err()
{
info!("Could not revoke key for removed resource class. This is not \
an issue, because typically the parent will revoke our keys pro-actively, \
just before removing the resource class entitlements.");
}
}
QueueEvent::ParentAdded(handle, parent) => {
if let Err(e) = caserver.get_updates_from_parent(&handle, &parent) {
+61 -19
View File
@@ -57,7 +57,18 @@ fn add_child_to_ta_rfc6492(
}
fn update_child(handle: &Handle, resources: &ResourceSet) {
let req = UpdateChildRequest::new(None, Some(resources.clone()));
let req = UpdateChildRequest::graceful(None, Some(resources.clone()));
match krill_admin(Command::TrustAnchor(TrustAnchorCommand::UpdateChild(
handle.clone(),
req,
))) {
ApiResponse::Empty => {}
_ => panic!("Expected empty ok response"),
}
}
fn force_update_child(handle: &Handle, resources: &ResourceSet) {
let req = UpdateChildRequest::force(None, Some(resources.clone()));
match krill_admin(Command::TrustAnchor(TrustAnchorCommand::UpdateChild(
handle.clone(),
req,
@@ -108,22 +119,7 @@ fn wait_for_resources_on_current_key(handle: &Handle, resources: &ResourceSet) {
wait_for(
30,
"cms child did not get its resource certificate",
move || {
let cms_ca_info = ca_details(handle);
if let CaParentsInfo::Parents(parents) = cms_ca_info.parents() {
if let Some(parent) = parents.get(&ta_handle()) {
if let Some(rc) = parent.resources().get("all") {
if let Some(current_resources) = rc.current_resources() {
if resources == current_resources {
return true;
}
}
}
}
}
false
},
move || &ca_current_resources(handle) == resources,
)
}
@@ -180,11 +176,47 @@ fn wait_for_resource_class_to_disappear(handle: &Handle) {
fn wait_for_ta_to_have_number_of_issued_certs(number: usize) {
wait_for(30, "TA has wrong amount of issued certs", || {
let ta = ca_details(&ta_handle());
ta.published_objects().len() == 2 + number
ta_issued_certs() == number
})
}
fn ta_issued_certs() -> usize {
let ta = ca_details(&ta_handle());
ta.published_objects().len() - 2
}
fn ta_issued_resources(child: &Handle) -> ResourceSet {
let ta = ca_details(&ta_handle());
let child = ta.children().get(child).unwrap();
if let Some(resources) = child.resources().get("all") {
for cert in resources.certs_iter() {
return cert.resource_set().clone();
}
}
ResourceSet::default()
}
fn ca_current_resources(handle: &Handle) -> ResourceSet {
let ca = ca_details(handle);
if let CaParentsInfo::Parents(parents) = ca.parents() {
if let Some(parent) = parents.get(&ta_handle()) {
if let Some(rc) = parent.resources().get("all") {
match rc.keys() {
ResourceClassKeysInfo::Active(current)
| ResourceClassKeysInfo::RollPending(_, current)
| ResourceClassKeysInfo::RollNew(_, current)
| ResourceClassKeysInfo::RollOld(current, _) => {
return current.incoming_cert().resources().clone()
}
_ => {}
}
}
}
}
ResourceSet::default()
}
#[test]
fn ca_under_ta() {
test_with_krill_server(|_d| {
@@ -242,5 +274,15 @@ fn ca_under_ta() {
wait_for_resource_class_to_disappear(&cms_child_handle);
wait_for_ta_to_have_number_of_issued_certs(1);
let emb_child_resources = ResourceSet::from_strs("", "192.168.0.0/24", "").unwrap();
force_update_child(&emb_child_handle, &emb_child_resources);
assert_eq!(ta_issued_resources(&emb_child_handle), emb_child_resources);
wait_for_resources_on_current_key(&emb_child_handle, &emb_child_resources);
let emb_child_resources = ResourceSet::default();
force_update_child(&emb_child_handle, &emb_child_resources);
assert_eq!(0, ta_issued_certs());
wait_for_resource_class_to_disappear(&emb_child_handle);
});
}