mirror of
https://github.com/NLnetLabs/krill.git
synced 2026-09-28 04:14:53 +02:00
1233 lines
42 KiB
Rust
1233 lines
42 KiB
Rust
use std::collections::HashMap;
|
|
use std::marker::PhantomData;
|
|
use std::ops::{Deref, DerefMut};
|
|
use std::sync::{Arc, RwLock};
|
|
|
|
use bytes::Bytes;
|
|
use chrono::Duration;
|
|
|
|
use rpki::cert::{KeyUsage, Overclaim, TbsCert};
|
|
use rpki::crypto::{PublicKey, PublicKeyFormat};
|
|
use rpki::x509::{Name, Serial, Time, Validity};
|
|
|
|
use krill_commons::api::admin::{
|
|
Handle, ParentCaContact, PubServerContact, Token, UpdateChildRequest,
|
|
};
|
|
use krill_commons::api::ca::{
|
|
AddedObject, CaParentsInfo, CertAuthInfo, CertifiedKey, ChildCaDetails, CurrentObject,
|
|
IssuedCert, ObjectName, ObjectsDelta, ParentCaInfo, PublicationDelta, RcvdCert, RepoInfo,
|
|
ResourceSet, Revocation, TrustAnchorInfo, TrustAnchorLocator, UpdatedObject, WithdrawnObject,
|
|
};
|
|
use krill_commons::api::{
|
|
self, EncodedHash, EntitlementClass, Entitlements, IssuanceRequest, IssuanceResponse,
|
|
RevocationRequest, RevocationResponse, SigningCert, DFLT_CLASS,
|
|
};
|
|
use krill_commons::eventsourcing::{Aggregate, StoredEvent};
|
|
use krill_commons::remote::builder::{IdCertBuilder, SignedMessageBuilder};
|
|
use krill_commons::remote::id::IdCert;
|
|
use krill_commons::remote::rfc6492;
|
|
use krill_commons::remote::rfc8183::ChildRequest;
|
|
use krill_commons::remote::sigmsg::SignedMessage;
|
|
use krill_commons::util::softsigner::KeyId;
|
|
|
|
use crate::ca::{
|
|
self, ChildHandle, Cmd, CmdDet, Error, Evt, EvtDet, Ini, ParentHandle, ResourceClass,
|
|
ResourceClassName, Result, SignSupport, Signer,
|
|
};
|
|
|
|
//------------ Rfc8183Id ---------------------------------------------------
|
|
|
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
|
pub struct Rfc8183Id {
|
|
key: KeyId,
|
|
cert: IdCert,
|
|
}
|
|
|
|
impl Rfc8183Id {
|
|
pub fn generate<S: Signer>(signer: &mut S) -> Result<Self> {
|
|
let key = signer
|
|
.create_key(PublicKeyFormat::default())
|
|
.map_err(|e| Error::SignerError(e.to_string()))?;
|
|
let cert = IdCertBuilder::new_ta_id_cert(&key, signer.deref())
|
|
.map_err(|e| Error::SignerError(e.to_string()))?;
|
|
Ok(Rfc8183Id { key, cert })
|
|
}
|
|
}
|
|
|
|
//------------ CaType ------------------------------------------------------
|
|
|
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
|
#[allow(clippy::large_enum_variant)]
|
|
pub enum CaType {
|
|
Child,
|
|
Ta(CertifiedKey, TrustAnchorLocator),
|
|
}
|
|
|
|
//------------ CaParents ---------------------------------------------------
|
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
#[allow(clippy::large_enum_variant)]
|
|
pub enum CaParents {
|
|
SelfSigned(CertifiedKey, TrustAnchorLocator),
|
|
Parents(HashMap<Handle, ParentCa>),
|
|
}
|
|
|
|
impl CaParents {
|
|
fn as_info(&self) -> CaParentsInfo {
|
|
match self {
|
|
CaParents::SelfSigned(key, tal) => CaParentsInfo::SelfSigned(key.clone(), tal.clone()),
|
|
CaParents::Parents(map) => {
|
|
let mut map_info = HashMap::new();
|
|
|
|
for (handle, parent) in map {
|
|
map_info.insert(handle.clone(), parent.as_info());
|
|
}
|
|
|
|
CaParentsInfo::Parents(map_info)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_self_signed(&self) -> bool {
|
|
match self {
|
|
CaParents::SelfSigned(_, _) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
fn assert_parent_new(&self, parent: &Handle) -> Result<()> {
|
|
match self {
|
|
CaParents::SelfSigned(_, _) => Err(Error::NotAllowedForTa),
|
|
CaParents::Parents(map) => {
|
|
if map.contains_key(parent) {
|
|
Err(Error::DuplicateParent(parent.clone()))
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn insert(&mut self, handle: Handle, parent: ParentCa) -> Result<()> {
|
|
match self {
|
|
CaParents::SelfSigned(_, _) => Err(Error::NotAllowedForTa),
|
|
CaParents::Parents(map) => {
|
|
map.insert(handle, parent);
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get(&self, handle: &Handle) -> Result<&ParentCa> {
|
|
match self {
|
|
CaParents::SelfSigned(_, _) => Err(Error::NotAllowedForTa),
|
|
CaParents::Parents(map) => Ok(map
|
|
.get(handle)
|
|
.ok_or_else(|| Error::UnknownParent(handle.clone()))?),
|
|
}
|
|
}
|
|
|
|
fn get_mut(&mut self, handle: &Handle) -> Result<&mut ParentCa> {
|
|
match self {
|
|
CaParents::SelfSigned(_, _) => Err(Error::NotAllowedForTa),
|
|
CaParents::Parents(map) => Ok(map
|
|
.get_mut(handle)
|
|
.ok_or_else(|| Error::UnknownParent(handle.clone()))?),
|
|
}
|
|
}
|
|
|
|
fn ta_key_mut(&mut self) -> Result<&mut CertifiedKey> {
|
|
match self {
|
|
CaParents::SelfSigned(key, _) => Ok(key),
|
|
CaParents::Parents(_map) => Err(Error::NotTa),
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------ CertAuth ----------------------------------------------------
|
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
pub struct CertAuth<S: Signer> {
|
|
handle: Handle,
|
|
version: u64,
|
|
|
|
token: Token, // The admin token to access this CertAuth
|
|
id: Rfc8183Id, // Used for RFC 6492 (up-down) and RFC 8181 (publication)
|
|
|
|
base_repo: RepoInfo,
|
|
parents: CaParents,
|
|
pubserver: PubServerContact, // TODO, allow remote
|
|
|
|
children: HashMap<Handle, ChildCaDetails>,
|
|
|
|
phantom_signer: PhantomData<S>,
|
|
}
|
|
|
|
impl<S: Signer> Aggregate for CertAuth<S> {
|
|
type Command = Cmd<S>;
|
|
type Event = Evt;
|
|
type InitEvent = Ini;
|
|
type Error = Error;
|
|
|
|
fn init(event: Ini) -> Result<Self> {
|
|
let (handle, _version, details) = event.unwrap();
|
|
|
|
let (token, id, base_repo, ca_type) = details.unwrap();
|
|
|
|
if ca_type == CaType::Child && handle == Handle::from("ta") {
|
|
return Err(Error::NameReservedTa);
|
|
}
|
|
|
|
let parents = match ca_type {
|
|
CaType::Child => CaParents::Parents(HashMap::new()),
|
|
CaType::Ta(key, tal) => CaParents::SelfSigned(key, tal),
|
|
};
|
|
let pubserver = PubServerContact::embedded();
|
|
|
|
let children = HashMap::new();
|
|
|
|
Ok(CertAuth {
|
|
handle,
|
|
version: 1,
|
|
|
|
token,
|
|
id,
|
|
|
|
base_repo,
|
|
parents,
|
|
pubserver,
|
|
|
|
children,
|
|
|
|
phantom_signer: PhantomData,
|
|
})
|
|
}
|
|
|
|
fn version(&self) -> u64 {
|
|
self.version
|
|
}
|
|
|
|
fn apply(&mut self, event: Evt) {
|
|
self.version += 1;
|
|
match event.into_details() {
|
|
//-----------------------------------------------------------------------
|
|
// Being a parent
|
|
//-----------------------------------------------------------------------
|
|
EvtDet::ChildAdded(child, details) => {
|
|
self.children.insert(child, details);
|
|
}
|
|
EvtDet::ChildCertificateIssued(child, response) => {
|
|
let (class_name, _, _, issued) = response.unwrap();
|
|
let child = self.children.get_mut(&child).unwrap();
|
|
child.add_cert(&class_name, issued);
|
|
}
|
|
EvtDet::ChildKeyRevoked(child, response) => {
|
|
let child = self.children.get_mut(&child).unwrap();
|
|
child.revoke_key(response);
|
|
}
|
|
EvtDet::ChildUpdatedToken(child, token) => {
|
|
let child = self.children.get_mut(&child).unwrap();
|
|
child.set_token(token);
|
|
}
|
|
EvtDet::ChildUpdatedIdCert(child, cert) => {
|
|
let child = self.children.get_mut(&child).unwrap();
|
|
child.set_id_cert(cert);
|
|
}
|
|
EvtDet::ChildUpdatedResourceClass(child, class, resources) => {
|
|
let child = self.children.get_mut(&child).unwrap();
|
|
child.set_resources_for_class(&class, resources)
|
|
}
|
|
EvtDet::ChildRemovedResourceClass(_child, _name) => unimplemented!(),
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Being a child
|
|
//-----------------------------------------------------------------------
|
|
EvtDet::ParentAdded(handle, info) => {
|
|
let parent = ParentCa::without_resource(info);
|
|
self.parents.insert(handle, parent).unwrap();
|
|
}
|
|
EvtDet::ResourceClassAdded(parent, name, rc) => {
|
|
// Evt cannot occur without parent existing
|
|
self.parents
|
|
.get_mut(&parent)
|
|
.unwrap()
|
|
.resources
|
|
.insert(name, rc);
|
|
}
|
|
EvtDet::ResourceClassRemoved(_parent, _name, _delta) => unimplemented!(),
|
|
EvtDet::CertificateRequested(parent, req, status) => {
|
|
let class = req.class_name().to_owned();
|
|
self.parents
|
|
.get_mut(&parent)
|
|
.unwrap()
|
|
.resources
|
|
.get_mut(&class)
|
|
.unwrap()
|
|
.add_request(status, req)
|
|
}
|
|
EvtDet::CertificateReceived(parent, class_name, key_id, cert) => {
|
|
let parent = self.parent_mut(&parent).unwrap();
|
|
let rc = parent.class_mut(&class_name).unwrap();
|
|
rc.received_cert(key_id, cert);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Key Roll
|
|
//-----------------------------------------------------------------------
|
|
EvtDet::KeyRollPendingKeyAdded(parent, class_name, key_id) => {
|
|
let parent = self.parent_mut(&parent).unwrap();
|
|
let rc = parent.class_mut(&class_name).unwrap();
|
|
rc.pending_key_added(key_id);
|
|
}
|
|
EvtDet::KeyRollActivated(parent, class_name, revoke_req) => {
|
|
let parent = self.parent_mut(&parent).unwrap();
|
|
let rc = parent.class_mut(&class_name).unwrap();
|
|
rc.new_key_activated(revoke_req);
|
|
}
|
|
EvtDet::KeyRollFinished(parent, class_name, _delta) => {
|
|
let parent = self.parent_mut(&parent).unwrap();
|
|
let rc = parent.class_mut(&class_name).unwrap();
|
|
rc.old_key_removed();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// General functions
|
|
//-----------------------------------------------------------------------
|
|
EvtDet::Published(parent, class_name, delta_map) => {
|
|
let parent = self.parent_mut(&parent).unwrap();
|
|
let rc = parent.class_mut(&class_name).unwrap();
|
|
for (key_id, delta) in delta_map.into_iter() {
|
|
rc.apply_delta(delta, key_id);
|
|
}
|
|
}
|
|
EvtDet::TaPublished(delta) => {
|
|
let ta_key = self.ta_key_mut().unwrap();
|
|
ta_key.apply_delta(delta);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn process_command(&self, command: Cmd<S>) -> ca::Result<Vec<Evt>> {
|
|
match command.into_details() {
|
|
// being a parent
|
|
CmdDet::AddChild(child, token, id_cert_opt, resources) => {
|
|
self.add_child(child, token, id_cert_opt, resources)
|
|
}
|
|
CmdDet::UpdateChild(child, req) => self.update_child(&child, req),
|
|
CmdDet::CertifyChild(child, request, token, signer) => {
|
|
self.certify_child(child, request, token, signer)
|
|
}
|
|
CmdDet::RevokeKeyForChild(child, request, signer) => {
|
|
self.revoke_child_key(child, request, signer)
|
|
}
|
|
|
|
// being a child
|
|
CmdDet::AddParent(parent, info) => self.add_parent(parent, info),
|
|
CmdDet::UpdateEntitlements(parent, entitlements, signer) => {
|
|
self.update_entitlements(parent, entitlements, signer)
|
|
}
|
|
CmdDet::UpdateRcvdCert(parent, class_name, rcvd_cert, signer) => {
|
|
self.update_received_cert(parent, class_name, rcvd_cert, signer)
|
|
}
|
|
|
|
// Key rolls
|
|
CmdDet::KeyRollInitiate(duration, signer) => self.keyroll_initiate(duration, signer),
|
|
CmdDet::KeyRollActivate(duration, signer) => self.keyroll_activate(duration, signer),
|
|
CmdDet::KeyRollFinish(parent, response) => self.keyroll_finish(parent, response),
|
|
|
|
// Republish
|
|
CmdDet::Republish(signer) => self.republish(signer),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// # Data presentation
|
|
///
|
|
impl<S: Signer> CertAuth<S> {
|
|
pub fn as_ta_info(&self) -> Result<TrustAnchorInfo> {
|
|
if let CaParents::SelfSigned(key, tal) = &self.parents {
|
|
let resources = key.incoming_cert().resources().clone();
|
|
let repo_info = self.base_repo.clone();
|
|
let children = self.children.clone();
|
|
let cert = key.incoming_cert().clone();
|
|
let tal = tal.clone();
|
|
|
|
Ok(TrustAnchorInfo::new(
|
|
resources, repo_info, children, cert, tal,
|
|
))
|
|
} else {
|
|
unimplemented!()
|
|
}
|
|
}
|
|
|
|
pub fn as_ca_info(&self) -> CertAuthInfo {
|
|
let handle = self.handle.clone();
|
|
let base_repo = self.base_repo.clone();
|
|
let parents = self.parents.as_info();
|
|
let children = self.children.clone();
|
|
|
|
CertAuthInfo::new(handle, base_repo, parents, children)
|
|
}
|
|
|
|
pub fn child_request(&self) -> ChildRequest {
|
|
ChildRequest::new(self.handle.clone(), self.id.cert.clone())
|
|
}
|
|
|
|
pub fn id_cert(&self) -> &IdCert {
|
|
&self.id.cert
|
|
}
|
|
pub fn id_key(&self) -> &KeyId {
|
|
&self.id.key
|
|
}
|
|
pub fn handle(&self) -> &Handle {
|
|
&self.handle
|
|
}
|
|
}
|
|
|
|
/// # Being a parent
|
|
///
|
|
impl<S: Signer> CertAuth<S> {
|
|
pub fn verify_rfc6492(&self, msg: SignedMessage) -> Result<(rfc6492::Message, Token)> {
|
|
let content = rfc6492::Message::from_signed_message(&msg)?;
|
|
|
|
let child_handle = Handle::from(content.sender());
|
|
let child = self.get_child(&child_handle)?;
|
|
|
|
let child_cert = child
|
|
.id_cert()
|
|
.ok_or_else(|| Error::Unauthorized(child_handle))?;
|
|
msg.validate(child_cert)
|
|
.map_err(|_| Error::InvalidRfc6492)?;
|
|
|
|
let token = child.token().clone();
|
|
|
|
Ok((content, token))
|
|
}
|
|
|
|
pub fn sign_rfc6492_response(&self, msg: rfc6492::Message, signer: &S) -> Result<Bytes> {
|
|
let key = &self.id.key;
|
|
Ok(SignedMessageBuilder::create(key, signer, msg.into_bytes())
|
|
.map_err(Error::signer)?
|
|
.as_bytes())
|
|
}
|
|
|
|
/// List entitlements (section 3.3.2 of RFC6492). Return an error if
|
|
/// the child is not authorized -- or unknown etc.
|
|
///
|
|
/// Only supported in TAs until issue #25 is implemented.
|
|
pub fn list(&self, child_handle: &Handle, token: &Token) -> Result<api::Entitlements> {
|
|
// TODO: Support arbitrary resource classes. See issue #25.
|
|
let dflt_entitlement_class = self.entitlement_class(child_handle, DFLT_CLASS, token)?;
|
|
|
|
Ok(Entitlements::new(vec![dflt_entitlement_class]))
|
|
}
|
|
|
|
/// Returns an issuance response for a child and a specific resource
|
|
/// class name and public key for the issued certificate.
|
|
pub fn issuance_response(
|
|
&self,
|
|
child_handle: &Handle,
|
|
class_name: &str,
|
|
pub_key: &PublicKey,
|
|
token: &Token,
|
|
) -> Result<api::IssuanceResponse> {
|
|
let entitlement_class = self.entitlement_class(child_handle, class_name, token)?;
|
|
|
|
entitlement_class
|
|
.into_issuance_response(pub_key)
|
|
.ok_or_else(|| Error::NoIssuedCert)
|
|
}
|
|
|
|
/// Returns the EntitlementClass for this child for the given class name.
|
|
fn entitlement_class(
|
|
&self,
|
|
child_handle: &Handle,
|
|
class_name: &str,
|
|
token: &Token,
|
|
) -> Result<api::EntitlementClass> {
|
|
let child = self.get_authorised_child(child_handle, token)?;
|
|
|
|
let child_resources = child
|
|
.resources_for_class(class_name)
|
|
.ok_or_else(|| Error::MissingResources)?;
|
|
|
|
let until = child_resources.not_after();
|
|
let issued = child_resources.certs().cloned().collect();
|
|
|
|
let cert = match &self.parents {
|
|
CaParents::SelfSigned(key, _tal) => key.incoming_cert(),
|
|
CaParents::Parents(_) => unimplemented!("Issue #25 (delegate from CA)"),
|
|
};
|
|
let cert = SigningCert::new(cert.uri().clone(), cert.cert().clone());
|
|
|
|
Ok(EntitlementClass::new(
|
|
class_name.to_string(),
|
|
cert,
|
|
child_resources.resources().clone(),
|
|
until,
|
|
issued,
|
|
))
|
|
}
|
|
|
|
/// Returns an authorized child, or an error if the child is not
|
|
/// authorized or unknown.
|
|
pub fn get_authorised_child(
|
|
&self,
|
|
child_handle: &Handle,
|
|
token: &Token,
|
|
) -> Result<&ChildCaDetails> {
|
|
let child = self.get_child(child_handle)?;
|
|
|
|
if token != child.token() {
|
|
Err(Error::Unauthorized(child_handle.clone()))
|
|
} else {
|
|
Ok(child)
|
|
}
|
|
}
|
|
|
|
/// Returns a child, or an error if the child is unknown.
|
|
pub fn get_child(&self, child: &Handle) -> Result<&ChildCaDetails> {
|
|
match self.children.get(child) {
|
|
None => Err(Error::UnknownChild(child.clone())),
|
|
Some(child) => Ok(child),
|
|
}
|
|
}
|
|
|
|
/// Adds the child, returns an error if the child is a duplicate,
|
|
/// or if the resources are not held by this CA, or (until #25) if
|
|
/// this CA is not a TA.
|
|
fn add_child(
|
|
&self,
|
|
child: ChildHandle,
|
|
token: Token,
|
|
id_cert: Option<IdCert>,
|
|
resources: ResourceSet,
|
|
) -> ca::Result<Vec<Evt>> {
|
|
// check that
|
|
// 1) the resource set is not empty
|
|
if resources.is_empty() {
|
|
return Err(Error::MustHaveResources);
|
|
}
|
|
|
|
// 2) the resources are held by me
|
|
match &self.parents {
|
|
CaParents::SelfSigned(key, _tal) => {
|
|
if !key.incoming_cert().resources().contains(&resources) {
|
|
return Err(Error::MissingResources);
|
|
}
|
|
}
|
|
CaParents::Parents(_map) => {
|
|
unimplemented!("#25 Issue #25 (delegate from CA)");
|
|
}
|
|
}
|
|
|
|
// 3) there is no existing child by this name
|
|
if self.has_child(&child) {
|
|
return Err(Error::DuplicateChild(child));
|
|
}
|
|
|
|
// TODO: Handle add child to normal CA (issue #25)
|
|
let mut child_details = ChildCaDetails::new(token, id_cert);
|
|
child_details.add_new_resource_class(DFLT_CLASS, resources);
|
|
|
|
Ok(vec![EvtDet::child_added(
|
|
&self.handle,
|
|
self.version,
|
|
child,
|
|
child_details,
|
|
)])
|
|
}
|
|
|
|
/// Certifies a child, unless:
|
|
/// = the child is unknown,
|
|
/// = the child is not authorised,
|
|
/// = the csr is invalid,
|
|
/// = the limit exceeds the child allocation,
|
|
/// = the signer throws up..
|
|
///
|
|
/// This CA is not a TA (until #25)
|
|
fn certify_child(
|
|
&self,
|
|
child: Handle,
|
|
request: IssuanceRequest,
|
|
token: Token,
|
|
signer: Arc<RwLock<S>>,
|
|
) -> ca::Result<Vec<Evt>> {
|
|
let (class_name, limit, csr) = request.unwrap();
|
|
|
|
let issuing_key = match &self.parents {
|
|
CaParents::SelfSigned(key, _tal) => key,
|
|
CaParents::Parents(_) => unimplemented!("Issue #25 (delegate from CA)"),
|
|
};
|
|
|
|
let issuing_cert = issuing_key.incoming_cert();
|
|
|
|
// verify child and resources
|
|
let child_resources = self
|
|
.get_authorised_child(&child, &token)?
|
|
.resources_for_class(&class_name)
|
|
.ok_or_else(|| Error::MissingResourceClass)?;
|
|
|
|
if child_resources.resources().is_empty() {
|
|
return Err(Error::MissingResources);
|
|
}
|
|
|
|
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 issuer = issuing_cert.cert().subject().clone();
|
|
|
|
let validity = Validity::new(
|
|
Time::now() - Duration::minutes(3),
|
|
child_resources.not_after(),
|
|
);
|
|
|
|
let subject = Some(Name::from_pub_key(csr.public_key()));
|
|
let pub_key = csr.public_key().clone();
|
|
|
|
let key_usage = KeyUsage::Ca;
|
|
let overclaim = Overclaim::Refuse;
|
|
|
|
let mut cert = TbsCert::new(
|
|
serial, issuer, validity, subject, pub_key, key_usage, overclaim,
|
|
);
|
|
cert.set_basic_ca(Some(true));
|
|
|
|
// Note! The issuing CA is not authoritative over *where* the child CA
|
|
// may publish. I.e. it will sign over any claimed URIs by the child,
|
|
// and assume that they will not be able to do anything malicious,
|
|
// 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();
|
|
|
|
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_as_resources(Some(resources.to_as_resources()));
|
|
cert.set_v4_resources(Some(resources.to_ip_resources_v4()));
|
|
cert.set_v6_resources(Some(resources.to_ip_resources_v6()));
|
|
|
|
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())
|
|
.map_err(Error::signer)?
|
|
};
|
|
|
|
let cert_uri = issuing_cert.uri_for_object(&cert);
|
|
|
|
IssuedCert::new(cert_uri, limit, resources.clone(), cert)
|
|
};
|
|
|
|
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(),
|
|
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])
|
|
}
|
|
|
|
fn update_child(&self, child_handle: &Handle, req: UpdateChildRequest) -> ca::Result<Vec<Evt>> {
|
|
let (token_opt, cert_opt, resources_opt) = req.unwrap();
|
|
|
|
let mut version = self.version;
|
|
let mut res = vec![];
|
|
|
|
let child = self.get_child(child_handle)?;
|
|
|
|
if let Some(token) = token_opt {
|
|
res.push(EvtDet::child_updated_token(
|
|
&self.handle,
|
|
version,
|
|
child_handle.clone(),
|
|
token,
|
|
));
|
|
version += 1;
|
|
}
|
|
|
|
if let Some(id_cert) = cert_opt {
|
|
res.push(EvtDet::child_updated_cert(
|
|
&self.handle,
|
|
version,
|
|
child_handle.clone(),
|
|
id_cert,
|
|
));
|
|
}
|
|
|
|
if let Some(resources) = resources_opt {
|
|
let mut my_resources = HashMap::new();
|
|
match &self.parents {
|
|
CaParents::Parents(_parents_map) => unimplemented!("Issue #25"),
|
|
CaParents::SelfSigned(key, _tal) => {
|
|
my_resources.insert(DFLT_CLASS, key.incoming_cert().resources());
|
|
}
|
|
}
|
|
|
|
let all_my_resources = my_resources
|
|
.values()
|
|
.fold(ResourceSet::default(), |acc, res| acc.union(res));
|
|
|
|
if !all_my_resources.contains(&resources) {
|
|
return Err(Error::MissingResources);
|
|
}
|
|
|
|
// Map the new child resources to classes
|
|
let mut child_entitlements = HashMap::new();
|
|
for (class_name, resources_for_class) in my_resources.into_iter() {
|
|
let child_resources_for_class = resources_for_class.intersection(&resources);
|
|
if !child_resources_for_class.is_empty() {
|
|
child_entitlements.insert(class_name.to_string(), child_resources_for_class);
|
|
}
|
|
}
|
|
|
|
// Get the current child resources
|
|
let mut child_resources = HashMap::new();
|
|
for (class_name, child_rc) in child.resources().iter() {
|
|
child_resources.insert(class_name, child_rc.resources());
|
|
}
|
|
|
|
// 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,
|
|
Some(current_resources) => current_resources != &entitled_resource_set,
|
|
} {
|
|
res.push(EvtDet::child_updated_resources(
|
|
&self.handle,
|
|
version,
|
|
child_handle.clone(),
|
|
class_name,
|
|
entitled_resource_set,
|
|
));
|
|
version += 1;
|
|
}
|
|
}
|
|
|
|
for class_name in child_resources.keys() {
|
|
res.push(EvtDet::child_updated_resources(
|
|
&self.handle,
|
|
version,
|
|
child_handle.clone(),
|
|
class_name.to_string(),
|
|
ResourceSet::default(),
|
|
));
|
|
version += 1;
|
|
}
|
|
}
|
|
|
|
Ok(res)
|
|
}
|
|
|
|
/// Revokes a key for a child. So, add all certs for the key to the CRL, and withdraw
|
|
/// the .cer file for it.
|
|
fn revoke_child_key(
|
|
&self,
|
|
child: ChildHandle,
|
|
request: RevocationRequest,
|
|
signer: Arc<RwLock<S>>,
|
|
) -> ca::Result<Vec<Evt>> {
|
|
// verify child and resources
|
|
let class_name = request.class_name();
|
|
let child_resources = self
|
|
.get_child(&child)?
|
|
.resources_for_class(class_name)
|
|
.ok_or_else(|| Error::MissingResourceClass)?;
|
|
|
|
let ca_key = match &self.parents {
|
|
CaParents::SelfSigned(key, _) => key,
|
|
CaParents::Parents(_map) => unimplemented!("Issue #25"),
|
|
};
|
|
|
|
// TODO For #25 find correct namespace for matching RC
|
|
let name_space = "";
|
|
|
|
if let Some(last_cert) = child_resources.cert(request.key()) {
|
|
let response = request.into();
|
|
|
|
let name = ObjectName::from(last_cert.cert());
|
|
let current_object = CurrentObject::from(last_cert.cert());
|
|
let withdrawn = WithdrawnObject::for_current(name, ¤t_object);
|
|
|
|
let revocations = vec![Revocation::from(last_cert.cert())];
|
|
|
|
let mut objects_delta = ObjectsDelta::new(self.base_repo.ca_repository(name_space));
|
|
objects_delta.withdraw(withdrawn);
|
|
|
|
let pub_delta = SignSupport::publish(
|
|
signer,
|
|
ca_key,
|
|
&self.base_repo,
|
|
name_space,
|
|
objects_delta,
|
|
revocations,
|
|
)
|
|
.map_err(Error::signer)?;
|
|
|
|
let revoked = EvtDet::child_revoke_key(&self.handle, self.version, child, response);
|
|
let published = EvtDet::published_ta(&self.handle, self.version + 1, pub_delta);
|
|
|
|
Ok(vec![revoked, published])
|
|
} else {
|
|
Err(Error::NoIssuedCert)
|
|
}
|
|
}
|
|
|
|
/// Returns `true` if the child is known, `false` otherwise. No errors.
|
|
fn has_child(&self, child_handle: &Handle) -> bool {
|
|
self.children.contains_key(child_handle)
|
|
}
|
|
}
|
|
|
|
/// # Being a child
|
|
///
|
|
impl<S: Signer> CertAuth<S> {
|
|
/// Returns true if this CertAuth is set up as a TA.
|
|
pub fn is_ta(&self) -> bool {
|
|
self.parents.is_self_signed()
|
|
}
|
|
/// List all parents
|
|
pub fn parents(&self) -> Result<Vec<(Handle, ParentCa)>> {
|
|
match &self.parents {
|
|
CaParents::SelfSigned(_, _) => Err(Error::NotAllowedForTa),
|
|
CaParents::Parents(map) => Ok(map.iter().map(|e| (e.0.clone(), e.1.clone())).collect()),
|
|
}
|
|
}
|
|
|
|
pub fn parent(&self, parent: &Handle) -> Result<&ParentCa> {
|
|
self.parents.get(parent)
|
|
}
|
|
|
|
fn parent_mut(&mut self, parent: &Handle) -> Result<&mut ParentCa> {
|
|
self.parents.get_mut(parent)
|
|
}
|
|
|
|
fn ta_key_mut(&mut self) -> Result<&mut CertifiedKey> {
|
|
self.parents.ta_key_mut()
|
|
}
|
|
|
|
/// Adds a parent. This method will return an error in case a parent
|
|
/// by this name (handle) is already known.
|
|
fn add_parent(&self, parent: Handle, info: ParentCaContact) -> ca::Result<Vec<Evt>> {
|
|
self.parents.assert_parent_new(&parent)?;
|
|
|
|
Ok(vec![EvtDet::parent_added(
|
|
&self.handle,
|
|
self.version,
|
|
parent,
|
|
info,
|
|
)])
|
|
}
|
|
|
|
/// Get all the current open certificate requests for a parent.
|
|
/// Returns an empty list if the parent is not found.
|
|
pub fn cert_requests(&self, parent_handle: &ParentHandle) -> Vec<IssuanceRequest> {
|
|
let mut res = vec![];
|
|
|
|
if let Ok(parent) = self.parent(parent_handle) {
|
|
for (_class_name, rc) in parent.resources.iter() {
|
|
res.append(&mut rc.cert_requests())
|
|
}
|
|
}
|
|
|
|
res
|
|
}
|
|
|
|
fn make_request_events(
|
|
&self,
|
|
version: &mut u64,
|
|
parent: &ParentHandle,
|
|
entitlement: &EntitlementClass,
|
|
rc: &ResourceClass,
|
|
signer: &Arc<RwLock<S>>,
|
|
) -> Result<Vec<Evt>> {
|
|
let req_details_list =
|
|
rc.request_certs(parent.clone(), entitlement, &self.base_repo, &signer)?;
|
|
|
|
let mut res = vec![];
|
|
for details in req_details_list.into_iter() {
|
|
res.push(StoredEvent::new(&self.handle, *version, details));
|
|
*version += 1;
|
|
}
|
|
Ok(res)
|
|
}
|
|
|
|
/// Returns the open revocation requests for the given parent.
|
|
pub fn revoke_requests(&self, parent: &ParentHandle) -> Vec<&RevocationRequest> {
|
|
let mut res = vec![];
|
|
if let Ok(parent) = self.parent(parent) {
|
|
for (_class_name, rc) in parent.resources.iter() {
|
|
if let Some(req) = rc.revoke_request() {
|
|
res.push(req)
|
|
}
|
|
}
|
|
}
|
|
res
|
|
}
|
|
|
|
/// This processes entitlements from a parent, and updates the known
|
|
/// entitlement(s) and/or requests certificate(s) as needed. In case
|
|
/// there are no changes in entitlements and certificates, this method
|
|
/// will result in 0 events - i.e. it is then a no-op.
|
|
fn update_entitlements(
|
|
&self,
|
|
parent_handle: Handle,
|
|
entitlements: Entitlements,
|
|
signer: Arc<RwLock<S>>,
|
|
) -> ca::Result<Vec<Evt>> {
|
|
let mut res = vec![];
|
|
|
|
let parent = self.parent(&parent_handle)?;
|
|
|
|
// Check if there is a resource class for each entitlement
|
|
let mut version = self.version;
|
|
|
|
// Check if there are any current resource classes, now removed
|
|
// from the entitlements. In which case we will have to clean them
|
|
// up and un-publish everything there was.
|
|
let current_resource_classes = &parent.resources;
|
|
let entitled_classes: Vec<&str> = entitlements
|
|
.classes()
|
|
.iter()
|
|
.map(|c| c.class_name())
|
|
.collect();
|
|
for (name, class) in current_resource_classes
|
|
.iter()
|
|
.filter(|(name, _class)| !entitled_classes.contains(&name.as_str()))
|
|
{
|
|
let delta = class.withdraw(&self.base_repo);
|
|
|
|
res.push(EvtDet::resource_class_removed(
|
|
&self.handle,
|
|
version,
|
|
parent_handle.clone(),
|
|
name.clone(),
|
|
delta,
|
|
));
|
|
version += 1;
|
|
}
|
|
|
|
for ent in entitlements.classes() {
|
|
let name = ent.class_name();
|
|
|
|
if let Some(rc) = parent.resources.get(name) {
|
|
res.append(&mut self.make_request_events(
|
|
&mut version,
|
|
&parent_handle,
|
|
ent,
|
|
rc,
|
|
&signer,
|
|
)?);
|
|
} else {
|
|
// Create a resource class with a pending key
|
|
let key_id = {
|
|
signer
|
|
.write()
|
|
.unwrap()
|
|
.create_key(PublicKeyFormat::default())
|
|
.map_err(Error::signer)?
|
|
};
|
|
|
|
let ns = format!("{}-{}", &parent_handle, name);
|
|
let rc = ResourceClass::create(ns, key_id);
|
|
let rc_add_version = version;
|
|
version += 1;
|
|
|
|
let mut request_events =
|
|
self.make_request_events(&mut version, &parent_handle, ent, &rc, &signer)?;
|
|
|
|
let added = EvtDet::resource_class_added(
|
|
&self.handle,
|
|
rc_add_version,
|
|
parent_handle.clone(),
|
|
name.to_string(),
|
|
rc,
|
|
);
|
|
|
|
res.push(added);
|
|
res.append(&mut request_events);
|
|
}
|
|
}
|
|
Ok(res)
|
|
}
|
|
|
|
/// This method updates the received certificate for the given parent
|
|
/// and resource class, and will return an error if either is unknown.
|
|
///
|
|
/// It will generate an event for the certificate that is received, and
|
|
/// if it was received for a pending key it will return an event to promote
|
|
/// the pending key appropriately, finally it will also return a
|
|
/// publication event for the matching key if publication is needed.
|
|
///
|
|
/// In future, when ROAs and delegating certificates are supported, this
|
|
/// should be updated to also generate appropriate events for changes
|
|
/// affecting these objects if needed - e.g. because resources were lost
|
|
/// and ROAs/Certs would be become invalid.
|
|
fn update_received_cert(
|
|
&self,
|
|
parent_handle: Handle,
|
|
class_name: ResourceClassName,
|
|
rcvd_cert: RcvdCert,
|
|
signer: Arc<RwLock<S>>,
|
|
) -> ca::Result<Vec<Evt>> {
|
|
debug!(
|
|
"CA {}: Updating received cert for class: {}",
|
|
self.handle, class_name
|
|
);
|
|
let parent = self.parent(&parent_handle)?;
|
|
let rc = parent.class(&class_name)?;
|
|
let evt_details = rc.update_received_cert(
|
|
rcvd_cert,
|
|
&self.base_repo,
|
|
parent_handle,
|
|
class_name,
|
|
signer,
|
|
)?;
|
|
|
|
let mut res = vec![];
|
|
let mut version = self.version;
|
|
|
|
for details in evt_details.into_iter() {
|
|
res.push(StoredEvent::new(&self.handle, version, details));
|
|
version += 1;
|
|
}
|
|
|
|
Ok(res)
|
|
}
|
|
}
|
|
|
|
/// # Key Rolls
|
|
///
|
|
impl<S: Signer> CertAuth<S> {
|
|
fn keyroll_initiate(&self, duration: Duration, signer: Arc<RwLock<S>>) -> ca::Result<Vec<Evt>> {
|
|
match &self.parents {
|
|
CaParents::SelfSigned(_, _) => Ok(vec![]), // pending IETF standard.
|
|
CaParents::Parents(map) => {
|
|
let mut signer = signer.write().unwrap();
|
|
let mut version = self.version;
|
|
let mut res = vec![];
|
|
|
|
for (parent_handle, parent) in map.iter() {
|
|
for (class_name, class) in parent.resources().iter() {
|
|
for details in class
|
|
.keyroll_initiate(
|
|
parent_handle.clone(),
|
|
class_name.clone(),
|
|
&self.base_repo,
|
|
duration,
|
|
signer.deref_mut(),
|
|
)?
|
|
.into_iter()
|
|
{
|
|
res.push(StoredEvent::new(self.handle(), version, details));
|
|
version += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(res)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn keyroll_activate(&self, staging: Duration, signer: Arc<RwLock<S>>) -> ca::Result<Vec<Evt>> {
|
|
match &self.parents {
|
|
CaParents::SelfSigned(_, _) => Ok(vec![]),
|
|
CaParents::Parents(map) => {
|
|
let signer = signer.read().unwrap();
|
|
let mut version = self.version;
|
|
let mut res = vec![];
|
|
|
|
for (parent_handle, parent) in map.iter() {
|
|
for (class_name, class) in parent.resources().iter() {
|
|
for details in class
|
|
.keyroll_activate(
|
|
parent_handle.clone(),
|
|
class_name.clone(),
|
|
staging,
|
|
signer.deref(),
|
|
)?
|
|
.into_iter()
|
|
{
|
|
res.push(StoredEvent::new(self.handle(), version, details));
|
|
version += 1;
|
|
}
|
|
}
|
|
}
|
|
Ok(res)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn keyroll_finish(
|
|
&self,
|
|
parent_h: ParentHandle,
|
|
response: RevocationResponse,
|
|
) -> ca::Result<Vec<Evt>> {
|
|
match &self.parents {
|
|
CaParents::SelfSigned(_, _) => Ok(vec![]),
|
|
CaParents::Parents(map) => {
|
|
let (class_name, _key_id) = response.unpack();
|
|
let parent = map
|
|
.get(&parent_h)
|
|
.ok_or_else(|| Error::UnknownParent(parent_h.clone()))?;
|
|
|
|
let rc = parent
|
|
.resources()
|
|
.get(&class_name)
|
|
.ok_or_else(|| Error::UnknownResourceClass(class_name.clone()))?;
|
|
|
|
let finish_details = rc.keyroll_finish(parent_h, class_name, &self.base_repo)?;
|
|
|
|
Ok(vec![StoredEvent::new(
|
|
self.handle(),
|
|
self.version,
|
|
finish_details,
|
|
)])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// # Publishing
|
|
///
|
|
impl<S: Signer> CertAuth<S> {
|
|
fn republish_delta_for_key(
|
|
key: &CertifiedKey,
|
|
repo_info: &RepoInfo,
|
|
name_space: &str,
|
|
signer: Arc<RwLock<S>>,
|
|
) -> Result<PublicationDelta> {
|
|
let ca_repo = repo_info.ca_repository(name_space);
|
|
let objects_delta = ObjectsDelta::new(ca_repo);
|
|
SignSupport::publish(signer, key, repo_info, name_space, objects_delta, vec![])
|
|
.map_err(Error::signer)
|
|
}
|
|
|
|
/// Republish objects for this CA
|
|
pub fn republish(&self, signer: Arc<RwLock<S>>) -> ca::Result<Vec<Evt>> {
|
|
let mut res = vec![];
|
|
match &self.parents {
|
|
CaParents::SelfSigned(key, _tal) => {
|
|
if key.needs_publication() {
|
|
let delta =
|
|
Self::republish_delta_for_key(key, &self.base_repo, "", signer.clone())?;
|
|
|
|
res.push(EvtDet::published_ta(&self.handle, self.version, delta))
|
|
}
|
|
}
|
|
CaParents::Parents(_map) => error!("Republishing CAs not implemented"),
|
|
}
|
|
Ok(res)
|
|
}
|
|
}
|
|
|
|
//------------ ParentCa ------------------------------------------------------
|
|
|
|
/// This type defines a parent for a CA and includes the information
|
|
/// needed to contact it, as well as a map of all the ResourceClass-es
|
|
/// that the CA has under this parent.
|
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
pub struct ParentCa {
|
|
contact: ParentCaContact,
|
|
resources: HashMap<ResourceClassName, ResourceClass>,
|
|
}
|
|
|
|
impl ParentCa {
|
|
fn resources(&self) -> &HashMap<ResourceClassName, ResourceClass> {
|
|
&self.resources
|
|
}
|
|
|
|
fn as_info(&self) -> ParentCaInfo {
|
|
let mut resources_info = HashMap::new();
|
|
|
|
for el in self.resources.iter() {
|
|
resources_info.insert(el.0.clone(), el.1.as_info());
|
|
}
|
|
|
|
ParentCaInfo::new(self.contact.clone(), resources_info)
|
|
}
|
|
|
|
pub fn without_resource(contact: ParentCaContact) -> Self {
|
|
ParentCa {
|
|
contact,
|
|
resources: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn contact(&self) -> &ParentCaContact {
|
|
&self.contact
|
|
}
|
|
|
|
fn class(&self, class_name: &str) -> Result<&ResourceClass> {
|
|
self.resources
|
|
.get(class_name)
|
|
.ok_or_else(|| Error::UnknownResourceClass(class_name.to_string()))
|
|
}
|
|
|
|
fn class_mut(&mut self, class_name: &str) -> Result<&mut ResourceClass> {
|
|
self.resources
|
|
.get_mut(class_name)
|
|
.ok_or_else(|| Error::UnknownResourceClass(class_name.to_string()))
|
|
}
|
|
}
|