From 0280e6eaec7ac8b0d74e9e38e8fd50837f7abfb9 Mon Sep 17 00:00:00 2001 From: Tim Bruijnzeels Date: Tue, 9 Jul 2019 12:17:10 +0200 Subject: [PATCH] Simplify: let TA and CA use same codebase 'CertAuth' --- ca/src/ca.rs | 779 ++++++++++++++++++++++++++++++++++-- ca/src/caserver.rs | 61 ++- ca/src/lib.rs | 122 +++--- ca/src/publishing.rs | 32 +- ca/src/signing.rs | 17 +- ca/src/trustanchor.rs | 687 ------------------------------- commons/src/api/ca.rs | 11 +- commons/src/util/test.rs | 2 +- daemon/src/krillserver.rs | 10 +- daemon/tests/ca_under_ta.rs | 2 +- 10 files changed, 862 insertions(+), 861 deletions(-) delete mode 100644 ca/src/trustanchor.rs diff --git a/ca/src/ca.rs b/ca/src/ca.rs index 877a8f08..c3d70e18 100644 --- a/ca/src/ca.rs +++ b/ca/src/ca.rs @@ -4,20 +4,48 @@ use std::marker::PhantomData; use std::ops::Deref; use std::sync::{Arc, RwLock}; +use chrono::Duration; +use rand::Rng; + +use rpki::cert::{Cert, TbsCert, KeyUsage, Overclaim}; use rpki::crypto::{PublicKey, PublicKeyFormat}; use rpki::csr::Csr; +use rpki::uri; +use rpki::x509::{Serial, Validity, Time, Name}; -use krill_commons::api::{EntitlementClass, Entitlements, IssuanceRequest}; -use krill_commons::api::admin::{Handle, ParentCaContact, Token}; +use krill_commons::api::{ + self, + DFLT_CLASS, + EncodedHash, + EntitlementClass, + Entitlements, + IssuanceRequest, + SigningCert, +}; +use krill_commons::api::admin::{ + Handle, + ParentCaContact, + Token +}; use krill_commons::api::ca::{ + AddedObject, AllCurrentObjects, CertifiedKey, + ChildCa, + ChildCaDetails, + CurrentObject, + CurrentObjects, + IssuedCert, KeyRef, + ObjectName, ObjectsDelta, PublicationDelta, RcvdCert, RepoInfo, ResourceSet, + TrustAnchorInfo, + TrustAnchorLocator, + UpdatedObject, }; use krill_commons::eventsourcing::{ Aggregate, @@ -27,17 +55,29 @@ use krill_commons::eventsourcing::{ }; use krill_commons::util::softsigner::SignerKeyId; -use crate::trustanchor::CaSigner; -use crate::signing::CaSignSupport; +use crate::signing::{CaSigner, CaSignSupport}; pub const CA_NS: &str = "cas"; +const TA_NAME: &str = "ta"; // reserved for TA + +pub fn ta_handle() -> Handle { + Handle::from(TA_NAME) +} + //------------ CertAuthInit -------------------------------------------------- pub type CaIni = StoredEvent; +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[allow(clippy::large_enum_variant)] +pub enum CaType { + Child, + Ta(CertifiedKey, TrustAnchorLocator) +} + #[derive(Clone, Debug, Deserialize, Serialize)] -pub struct CaIniDet(Token, RepoInfo); +pub struct CaIniDet(Token, RepoInfo, CaType); impl CaIniDet { pub fn init( @@ -48,9 +88,71 @@ impl CaIniDet { CaIni::new( handle, 0, - CaIniDet(token, info) + CaIniDet(token, info, CaType::Child) ) } + + pub fn init_ta( + handle: &Handle, + token: Token, + info: RepoInfo, + + ta_aia: uri::Rsync, + ta_uris: Vec, + + key: SignerKeyId, + signer: Arc>, + ) -> CaRes { + let resources = ResourceSet::all_resources(); + let ta_cert = Self::mk_ta_cer(&info, &resources, &key, signer)?; + let tal = TrustAnchorLocator::new(ta_uris, &ta_cert); + let key = CertifiedKey::new(key, RcvdCert::new(ta_cert, ta_aia)); + + Ok(CaIni::new( + handle, + 0, + CaIniDet(token, info, CaType::Ta(key, tal)) + )) + } + + fn mk_ta_cer( + repo_info: &RepoInfo, + resources: &ResourceSet, + key: &S::KeyId, + signer: Arc> + ) -> CaRes { + let serial: Serial = rand::thread_rng().gen::().into(); + + let signer = signer.read().unwrap(); + + let pub_key = signer.get_key_info(&key).map_err(Error::signer)?; + let name = pub_key.to_subject_name(); + + let mut cert = TbsCert::new( + serial, + name.clone(), + Validity::new(Time::now(), Time::years_from_now(100)), + Some(name), + pub_key.clone(), + KeyUsage::Ca, + Overclaim::Refuse + ); + + cert.set_basic_ca(Some(true)); + + cert.set_ca_repository(Some(repo_info.ca_repository(""))); + cert.set_rpki_manifest(Some(repo_info.rpki_manifest("", &pub_key.key_identifier()))); + cert.set_rpki_notify(Some(repo_info.rpki_notify())); + + cert.set_as_resources(Some(resources.asn().clone())); + cert.set_v4_resources(Some(resources.v4().deref().clone())); + cert.set_v6_resources(Some(resources.v6().deref().clone())); + + cert.into_cert( + signer.deref(), + key + ).map_err(Error::signer) + } } @@ -112,7 +214,11 @@ pub struct CertReceived { #[derive(Clone, Debug, Deserialize, Serialize)] #[allow(clippy::large_enum_variant)] pub enum CaEvtDet { - // Parent Events + // Being a parent Events + ChildAdded(ChildCa), + CertificateIssued(Handle, String, IssuedCert), + + // Being a child Events ParentAdded(ParentHandle, ParentCaContact), ResourceClassAdded(ParentHandle, ResourceClassName, ResourceClass), @@ -124,12 +230,13 @@ pub enum CaEvtDet { PendingKeyActivated(ParentHandle, ResourceClassName, RcvdCert), // Publishing - Published(ParentHandle, ResourceClassName, KeyStatus, PublicationDelta) + Published(ParentHandle, ResourceClassName, KeyStatus, PublicationDelta), + TaPublished(PublicationDelta) } impl CaEvtDet { /// This marks a parent as added to the CA. - pub fn parent_added( + fn parent_added( handle: &Handle, version: u64, parent_handle: ParentHandle, @@ -143,7 +250,7 @@ impl CaEvtDet { } /// This marks a resource class as added under a parent for the CA. - pub fn resource_class_added( + fn resource_class_added( handle: &Handle, version: u64, parent_handle: ParentHandle, @@ -165,7 +272,7 @@ impl CaEvtDet { /// then gets a new certificate, it will send a command to the CA with /// the new certificate to mark it as received, and take other /// appriopiate actions (key life cycle, publication). - pub fn certificate_requested( + fn certificate_requested( handle: &Handle, version: u64, cert_issue_req: CertRequested @@ -179,7 +286,7 @@ impl CaEvtDet { /// This marks a certificate as received for the key of the given status /// in a given resource class under a parent. - pub fn certificate_received( + fn certificate_received( handle: &Handle, version: u64, received: CertReceived @@ -198,7 +305,7 @@ impl CaEvtDet { /// Note that key roll management is going to be implemented in the near /// future and then there will also be appropriate events for all the /// stages in a key roll. - pub fn pending_activated( + fn pending_activated( handle: &Handle, version: u64, parent: ParentHandle, @@ -214,7 +321,7 @@ impl CaEvtDet { /// This marks a delta as published for a key under a resource class /// under a parent CA. - pub fn published( + fn published( handle: &Handle, version: u64, parent: ParentHandle, @@ -228,6 +335,48 @@ impl CaEvtDet { CaEvtDet::Published(parent, class_name, key_status, delta) ) } + + fn child_added( + handle: &Handle, + version: u64, + child: ChildCa + ) -> CaEvt { + StoredEvent::new( + handle, + version, + CaEvtDet::ChildAdded(child) + ) + } + + fn certificate_issued( + handle: &Handle, + version: u64, + child_handle: Handle, + class_name: &str, + cert: IssuedCert + ) -> CaEvt { + StoredEvent::new( + handle, + version, + CaEvtDet::CertificateIssued( + child_handle, + class_name.to_string(), + cert + ) + ) + } + + fn published_ta( + handle: &Handle, + version: u64, + delta: PublicationDelta + ) -> CaEvt { + StoredEvent::new( + handle, + version, + CaEvtDet::TaPublished(delta) + ) + } } @@ -241,6 +390,11 @@ type ResourceClassName = String; #[derive(Clone, Debug)] #[allow(clippy::large_enum_variant)] pub enum CaCmdDet { + // Being a parent + AddChild(Handle, Token, ResourceSet), + CertifyChild(Handle, Csr, Option, Token, Arc>), + + // Being a child AddParent(ParentHandle, ParentCaContact), UpdateEntitlements(ParentHandle, Entitlements, Arc>), UpdateRcvdCert( @@ -248,7 +402,10 @@ pub enum CaCmdDet { ResourceClassName, RcvdCert, Arc> - ) + ), + + // General + Republish(Arc>) } impl CommandDetails for CaCmdDet { @@ -256,6 +413,42 @@ impl CommandDetails for CaCmdDet { } impl CaCmdDet { + + /// 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( + handle: &Handle, + child_handle: Handle, + child_token: Token, + child_resources: ResourceSet, + ) -> CaCmd { + SentCommand::new( + handle, + None, + CaCmdDet::AddChild(child_handle, child_token, child_resources) + ) + } + + + /// 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( + handle: &Handle, + child_handle: Handle, + csr: Csr, + limit: Option, + token: Token, + signer: Arc> + ) -> CaCmd { + SentCommand::new( + handle, + None, + CaCmdDet::CertifyChild(child_handle, csr, limit, token, signer) + ) + } + + pub fn add_parent( handle: &Handle, name: &str, @@ -303,6 +496,80 @@ impl CaCmdDet { ) ) } + + + pub fn publish( + handle: &Handle, + signer: Arc> + ) -> CaCmd { + SentCommand::new( + handle, + None, + CaCmdDet::Republish(signer) + ) + } +} + + +//------------ CaParents --------------------------------------------------- + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[allow(clippy::large_enum_variant)] +pub enum CaParents { + SelfSigned(CertifiedKey, TrustAnchorLocator), + Parents(HashMap) +} + +impl CaParents { + fn is_self_signed(&self) -> bool { + match self { + CaParents::SelfSigned(_,_) => true, + _ => false + } + } + + fn assert_parent_new(&self, parent: &Handle) -> CaRes<()> { + 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) -> CaRes<()> { + match self { + CaParents::SelfSigned(_,_) => Err(Error::NotAllowedForTa), + CaParents::Parents(map) => { + map.insert(handle, parent); + Ok(()) + } + } + } + + fn get(&self, handle: &Handle) -> CaRes<&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) -> CaRes<&mut ParentCa> { + match self { + CaParents::SelfSigned(_,_) => Err(Error::NotAllowedForTa), + CaParents::Parents(map) => Ok( + map.get_mut(handle) + .ok_or_else(|| Error::UnknownParent(handle.clone()))? + ) + } + } } @@ -316,7 +583,9 @@ pub struct CertAuth { token: Token, // The admin token for this CertAuth base_repo: RepoInfo, - parents: HashMap, + parents: CaParents, + + children: HashMap, phantom_signer: PhantomData } @@ -336,16 +605,30 @@ impl Aggregate for CertAuth { let token = details.0; let base_repo = details.1; + let ca_type = details.2; - let parents = HashMap::new(); + if ca_type == CaType::Child && handle == Handle::from(TA_NAME) { + return Err(Error::NameReservedTa) + } + + let parents = match ca_type { + CaType::Child => CaParents::Parents(HashMap::new()), + CaType::Ta(key, tal) => CaParents::SelfSigned(key, tal) + }; + + let children = HashMap::new(); Ok(CertAuth { handle, version: 1, token, + base_repo, + parents, + children, + phantom_signer: PhantomData }) } @@ -357,9 +640,20 @@ impl Aggregate for CertAuth { fn apply(&mut self, event: CaEvt) { self.version += 1; match event.into_details() { + // Being a parent + CaEvtDet::ChildAdded(child) => { + let (handle, details) = child.unwrap(); + self.children.insert(handle, details); + }, + CaEvtDet::CertificateIssued(child, class_name, issued_cert) => { + let child = self.children.get_mut(&child).unwrap(); + child.add_cert(&class_name, issued_cert); + }, + + // Being a child CaEvtDet::ParentAdded(handle, info) => { let parent = ParentCa::without_resource(info); - self.parents.insert(handle, parent); + self.parents.insert(handle, parent).unwrap(); }, CaEvtDet::ResourceClassAdded(parent, name, rc) => { // Evt cannot occur without parent existing @@ -383,11 +677,16 @@ impl Aggregate for CertAuth { CaEvtDet::CertificateReceived(_rcvd) => { unimplemented!() }, + + // General functions CaEvtDet::Published(parent, class_name, status, delta) => { let mut parent = self.parent_mut(parent).unwrap(); let mut rc = parent.class_mut(&class_name).unwrap(); let mut ck = rc.get_key_mut(&status).unwrap(); ck.apply_delta(delta); + }, + CaEvtDet::TaPublished(_delta) => { + } } @@ -395,6 +694,15 @@ impl Aggregate for CertAuth { fn process_command(&self, command: CaCmd) -> CaEvtsRes { match command.into_details() { + // being a parent + CaCmdDet::AddChild(child, token, resources) => { + self.add_child(child, token, resources) + }, + CaCmdDet::CertifyChild(child, csr, limit, token, signer) => { + self.certify_child(child, csr, limit, token, signer) + } + + // being a child CaCmdDet::AddParent(parent, info) => { self.add_parent(parent,info) }, @@ -403,49 +711,410 @@ impl Aggregate for CertAuth { }, CaCmdDet::UpdateRcvdCert(parent, class_name, rcvd_cert, signer) => { self.update_received_cert(parent, class_name, rcvd_cert, signer) + }, + + // general CA functions + CaCmdDet::Republish(signer) => { + self.republish(signer) } } } } + +/// # Data presentation +/// +impl CertAuth { + pub fn as_ta_info(&self) -> CaRes { + 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!() + } + } +} + /// # Publishing /// impl CertAuth { /// Returns all current objects for all parents, resource classes and keys pub fn current_objects(&self) -> AllCurrentObjects { - AllCurrentObjects::empty() + let mut objects = AllCurrentObjects::empty(); + + match &self.parents { + CaParents::SelfSigned(key, _tal) => { + objects.add_name_space(DFLT_CLASS, key.current_set().objects()) + }, + CaParents::Parents(parents) => { + for parent in parents.values() { + for rc in parent.resources.values() { + let ns = rc.name_space(); + if let Some(new_objects) = rc.new_objects() { + objects.add_name_space(ns, new_objects); + } + if let Some(current_objects) = rc.current_objects() { + objects.add_name_space(ns, current_objects); + } + if let Some(revoke_objects) = rc.revoke_objects() { + objects.add_name_space(ns, revoke_objects); + } + } + } + } + } + + objects + } + + fn republish_delta_for_key( + key: &CertifiedKey, + repo_info: &RepoInfo, + name_space: &str, + signer: Arc> + ) -> CaRes { + let ca_repo = repo_info.ca_repository(name_space); + let objects_delta = ObjectsDelta::new(ca_repo); + CaSignSupport::publish( + signer, + key, + repo_info, + name_space, + objects_delta + ).map_err(Error::signer) + } + + /// Republish objects for this CA + pub fn republish(&self, signer: Arc>) -> CaEvtsRes { + 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(CaEvtDet::published_ta( + &self.handle, + self.version, + delta + )) + } + }, + CaParents::Parents(_map) => { + unimplemented!() + } + } + Ok(res) } } -/// # Manage parents & resources under parents +/// # Being a parent /// impl CertAuth { + /// 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 + ) -> CaRes { + let child = self.get_authorised_child(child_handle, token)?; + + let child_resources = child.resources(DFLT_CLASS) + .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") + }; + let resources = cert.resources().clone(); + let cert = SigningCert::new(cert.uri().clone(), cert.cert().clone()); + + Ok(Entitlements::with_default_class( + cert, resources, 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 + ) -> CaRes<&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) -> CaRes<&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, + handle: Handle, + token: Token, + resources: ResourceSet + ) -> CaEvtsRes { + // check that + // 1) 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!("Issue #25"); + } + } + + // 2) there is no existing child by this name + if self.has_child(&handle) { + return Err(Error::DuplicateChild(handle)) + } + + // TODO: Handle add child to normal CA (issue #25) + let mut child = ChildCa::without_resources(handle, token); + child.add_resources(DFLT_CLASS, resources); + + + Ok(vec![CaEvtDet::child_added( + &self.handle, + self.version, + child + )]) + } + + /// 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, + csr: Csr, + limit: Option, + token: Token, + signer: Arc> + ) -> CaEvtsRes { + let issuing_key = match &self.parents { + CaParents::SelfSigned(key, _tal) => key, + CaParents::Parents(_) => unimplemented!("Issue #25") + }; + + let issuing_cert = issuing_key.incoming_cert(); + + // verify child and resources + let child_resources = self.get_authorised_child(&child, &token)? + .resources(DFLT_CLASS) + .ok_or_else(|| Error::MissingResources)?; + + let resources = match limit.as_ref() { + Some(limit) => { + if child_resources.resources().contains(limit) { + limit + } else { + return Err(Error::MissingResources) + } + }, + None => child_resources.resources() + }; + 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()); + + // 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.asn().clone())); + cert.set_v4_resources(Some(resources.v4().deref().clone())); + cert.set_v6_resources(Some(resources.v6().deref().clone())); + + 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, resources.clone(), cert) + }; + + let version = self.version; + let cert_object = CurrentObject::from(issued_cert.cert()); + + let issued_event = CaEvtDet::certificate_issued( + &self.handle, + version, + child, + DFLT_CLASS, + issued_cert.clone() + ); + + 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 = CaEvtDet::published_ta( + &self.handle, + version + 1, + CaSignSupport::publish( + signer, + issuing_key, + &self.base_repo, + "", + delta + ).map_err(Error::signer)? + ); + + Ok(vec![issued_event, publish_event]) + } + + /// 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 CertAuth { + /// 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) -> impl Iterator{ - self.parents.iter() + pub fn parents(&self) -> CaRes> { + match &self.parents { + CaParents::SelfSigned(_,_) => Err(Error::NotAllowedForTa), + CaParents::Parents(map) => { + Ok(map.iter().map(|e| (e.0.clone(), e.1.clone())).collect()) + } + } } fn parent(&self, parent: Handle) -> CaRes<&ParentCa> { - self.parents.get(&parent).ok_or_else(|| Error::UnknownParent(parent)) + self.parents.get(&parent) } fn parent_mut(&mut self, parent: Handle) -> CaRes<&mut ParentCa> { - self.parents.get_mut(&parent).ok_or_else(|| Error::UnknownParent(parent)) + self.parents.get_mut(&parent) } /// 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) -> CaEvtsRes { - if self.parents.contains_key(&parent) { - Err(Error::DuplicateParent(parent)) - } else { - Ok(vec![CaEvtDet::parent_added( - &self.handle, - self.version, - parent, - info - )]) - } + + self.parents.assert_parent_new(&parent)?; + + Ok(vec![CaEvtDet::parent_added( + &self.handle, + self.version, + parent, + info + )]) } /// This processes entitlements from a parent, and updates the known @@ -795,6 +1464,21 @@ impl ResourceClass { revoke_key: None } } + + pub fn name_space(&self) -> &str { &self.name_space } + + pub fn new_objects(&self) -> Option<&CurrentObjects> { + self.new_key.as_ref().map(|k| k.current_set().objects()) + } + + pub fn current_objects(&self) -> Option<&CurrentObjects> { + self.current_key.as_ref().map(|k| k.current_set().objects()) + } + + pub fn revoke_objects(&self) -> Option<&CurrentObjects> { + self.revoke_key.as_ref().map(|k| k.current_set().objects()) + } + } /// # Request certificates @@ -981,6 +1665,9 @@ pub enum KeyStatus { #[derive(Debug, Display)] pub enum Error { + #[display(fmt = "Functionality not supported for TA.")] + NotAllowedForTa, + #[display(fmt = "Duplicate parent added: {}", _0)] DuplicateParent(Handle), @@ -990,6 +1677,25 @@ pub enum Error { #[display(fmt = "Got response for unknown resource class: {}", _0)] UnknownResourceClass(String), + // Child related errors + #[display(fmt = "Name reserved for embedded TA.")] + NameReservedTa, + + #[display(fmt = "Child {} already exists.", _0)] + DuplicateChild(Handle), + + #[display(fmt = "Unknown child {}.", _0)] + UnknownChild(Handle), + + #[display(fmt = "Unauthorized child {}", _0)] + Unauthorized(Handle), + + #[display(fmt = "Not all child resources are held by TA")] + MissingResources, + + #[display(fmt = "Invalidly CSR for child {}: {}.", _0, _1)] + InvalidCsr(Handle, String), + #[display(fmt = "No key held by CA matching issued certificate: {}", _0)] NoKeyMatch(KeyRef), @@ -1004,6 +1710,11 @@ impl Error { pub fn signer(e: impl Display) -> Self { Error::SignerError(e.to_string()) } + + pub fn invalid_csr(handle: &Handle, msg: &str) -> Self { + Error::InvalidCsr(handle.clone(), msg.to_string()) + } + } impl std::error::Error for Error {} diff --git a/ca/src/caserver.rs b/ca/src/caserver.rs index cfe9c923..ea4a354f 100644 --- a/ca/src/caserver.rs +++ b/ca/src/caserver.rs @@ -1,6 +1,7 @@ use std::io; use std::path::PathBuf; use std::sync::{Arc, RwLock}; +use std::ops::Deref; use rpki::crypto::PublicKeyFormat; use rpki::uri; @@ -32,25 +33,18 @@ use crate::ca::{ CaCmdDet, CaIniDet, CaEvtDet, - ParentCa, }; -use crate::trustanchor::{ - self, - TA_NS, +use crate::{ ta_handle, CaSigner, - TrustAnchor, - TaCmdDet, - TaIniDet, + PubClients }; -use crate::PubClients; //------------ CaServer ------------------------------------------------------ pub struct CaServer { signer: Arc>, - ta_store: Arc>>, ca_store: Arc>> } @@ -64,22 +58,18 @@ impl CaServer { pub_clients: Arc, signer: S ) -> CaResult { - let mut ta_store = DiskAggregateStore::>::new(work_dir, TA_NS)?; - ta_store.add_listener(pub_clients.clone()); - let mut ca_store = DiskAggregateStore::>::new(work_dir, CA_NS)?; ca_store.add_listener(pub_clients); Ok(CaServer { signer: Arc::new(RwLock::new(signer)), - ta_store: Arc::new(ta_store), ca_store: Arc::new(ca_store) }) } /// Gets the TrustAnchor, if present. Returns an error if the TA is uninitialized. - pub fn get_trust_anchor(&self) -> CaResult>, S> { - self.ta_store + pub fn get_trust_anchor(&self) -> CaResult>, S> { + self.ca_store .get_latest(&ta_handle()) .map_err(|_| Error::TrustAnchorNotInitialisedError) } @@ -92,7 +82,7 @@ impl CaServer { ta_uris: Vec ) -> CaResult<(), S> { let handle = ta_handle(); - if self.ta_store.has(&handle) { + if self.ca_store.has(&handle) { Err(Error::TrustAnchorInitialisedError) } else { let key = { @@ -101,8 +91,11 @@ impl CaServer { .map_err(Error::SignerError)? }; - let init = TaIniDet::init_with_all_resources( + let token = Token::random(self.signer.read().unwrap().deref()); + + let init = CaIniDet::init_ta( &handle, + token, info, ta_aia, ta_uris, @@ -110,7 +103,7 @@ impl CaServer { self.signer.clone() )?; - self.ta_store.add(init)?; + self.ca_store.add(init)?; Ok(()) } @@ -131,21 +124,21 @@ impl CaServer { // if there is a TA, publish it let ta_handle = ta_handle(); - if ! self.ta_store.has(&ta_handle) { + if ! self.ca_store.has(&ta_handle) { debug!("No embedded TA present"); return Ok(()) // bail out w/o error in case there is no embedded TA } - if let Ok(ta) = self.ta_store.get_latest(&ta_handle) { + if let Ok(ta) = self.ca_store.get_latest(&ta_handle) { debug!("Publishing TA"); - let ta_republish = TaCmdDet::republish( + let ta_republish = CaCmdDet::publish( &ta_handle, self.signer.clone() ); let events = ta.process_command(ta_republish)?; if ! events.is_empty() { - self.ta_store.update(&ta_handle, ta, events)?; + self.ca_store.update(&ta_handle, ta, events)?; } } else { error!("TA present, but could not be loaded"); @@ -166,7 +159,7 @@ impl CaServer { let ta = self.get_trust_anchor()?; let ta_handle = ta_handle(); - let add_child = TaCmdDet::::add_child( + let add_child = CaCmdDet::::add_child( &ta_handle, handle, token.clone(), @@ -174,7 +167,7 @@ impl CaServer { ); let events = ta.process_command(add_child)?; - self.ta_store.update(&ta_handle, ta, events)?; + self.ca_store.update(&ta_handle, ta, events)?; Ok(()) } @@ -235,7 +228,7 @@ impl CaServer { unimplemented!("Issue for multiple classes from CAs, issue #25") } - let cmd = TaCmdDet::certify_child( + let cmd = CaCmdDet::certify_child( parent, child.clone(), csr, @@ -245,7 +238,7 @@ impl CaServer { ); let events = ta.process_command(cmd)?; - let ta = self.ta_store.update(parent, ta, events)?; + let ta = self.ca_store.update(parent, ta, events)?; // Get the newly issued cert for the child. Unwrap here is safe. let child = ta.get_child(child).unwrap(); @@ -308,11 +301,12 @@ impl CaServer { let mut child = self.ca_store.get_latest(handle)?; - let parents: Vec<(Handle, ParentCa)> = child.parents() - .map(|e| (e.0.clone(), e.1.clone())) - .collect(); + // If this is a TA, then just return.. there is not updating + if child.is_ta() { + return Ok(()) + } - for (parent_handle, parent) in parents { + for (parent_handle, parent) in child.parents()? { let entitlements = match parent.contact() { ParentCaContact::RemoteKrill(_uri, _token) => { @@ -420,9 +414,6 @@ pub enum Error { #[display(fmt = "{}", _0)] IoError(io::Error), - #[display(fmt = "{}", _0)] - TrustAnchorError(trustanchor::Error), - #[display(fmt = "TrustAnchor was already initialised")] TrustAnchorInitialisedError, @@ -449,10 +440,6 @@ impl From for Error { fn from(e: io::Error) -> Self { Error::IoError(e) } } -impl From for Error { - fn from(e: trustanchor::Error) -> Self { Error::TrustAnchorError(e) } -} - impl From for Error { fn from(e: ca::Error) -> Self { Error::CaError(e) } } diff --git a/ca/src/lib.rs b/ca/src/lib.rs index 57e3f297..29b3d94a 100644 --- a/ca/src/lib.rs +++ b/ca/src/lib.rs @@ -14,18 +14,19 @@ extern crate rpki; extern crate krill_commons; mod ca; +pub use ca::ta_handle; mod caserver; -pub use caserver::CaServer; -pub use caserver::Error as CaServerError; - -pub mod trustanchor; +pub use self::caserver::CaServer; +pub use self::caserver::Error as CaServerError; mod signing; +pub use self::signing::CaSigner; +pub use self::signing::CaSignSupport; mod publishing; -pub use publishing::PubClients; -pub use publishing::Error as PubClientError; +pub use self::publishing::PubClients; +pub use self::publishing::Error as PubClientError; //------------ Tests --------------------------------------------------------- @@ -62,15 +63,8 @@ mod tests { test_under_tmp, }; - use crate::trustanchor::{ - ta_handle, - TA_NS, - TaCmdDet, - TaEvtDet, - TaIniDet, - TrustAnchor, - }; use crate::ca::{ + ta_handle, CA_NS, CertAuth, CaIniDet, @@ -84,12 +78,11 @@ mod tests { } #[test] - fn ca_under_ta() { + fn init_ta() { test_under_tmp(|d| { - let ta_store = DiskAggregateStore::>::new( - &d, TA_NS + let ca_store = DiskAggregateStore::>::new( + &d, CA_NS ).unwrap(); - let handle = ta_handle(); let ta_repo_info = { let base_uri = rsync("rsync://localhost/repo/ta/"); @@ -97,6 +90,10 @@ mod tests { RepoInfo::new(base_uri, rrdp_uri) }; + let ta_handle = ta_handle(); + let ta_token = Token::from("ta"); + + let ta_uri = https("https://localhost/tal/ta.cer"); let ta_aia = rsync("rsync://localhost/repo/ta.cer"); @@ -105,51 +102,23 @@ mod tests { let signer = Arc::new(RwLock::new(signer)); // - // --- Create TA - // - // Expect: - // - TA initialised + // --- Create TA and publish // - let init = TaIniDet::init_with_all_resources( - &handle, + let ta_ini = CaIniDet::init_ta( + &ta_handle, + ta_token.clone(), ta_repo_info, + ta_aia, vec![ta_uri], key, + signer.clone() ).unwrap(); - - ta_store.add(init).unwrap(); - let ta = ta_store.get_latest(&handle).unwrap(); - - let publish_cmd = TaCmdDet::republish(&handle, signer.clone()); - let events = ta.process_command(publish_cmd).unwrap(); - let ta = ta_store.update(&handle, ta, events).unwrap(); - - // - // --- Add Child to TA - // - // Expect: - // - Child added to TA - // - - let child_handle = Handle::from("child"); - let child_token = Token::from("child"); - let child_rs = ResourceSet::from_strs("", "10.0.0.0/16", "").unwrap(); - - let cmd = TaCmdDet::add_child( - &handle, - child_handle.clone(), - child_token.clone(), - child_rs - ); - - let events = ta.process_command(cmd).unwrap(); - let ta = ta_store.update(&handle, ta, events).unwrap(); - - assert_eq!(1, ta.as_info().children().len()); + ca_store.add(ta_ini).unwrap(); + let ta = ca_store.get_latest(&ta_handle).unwrap(); // // --- Create Child CA @@ -157,6 +126,9 @@ mod tests { // Expect: // - Child CA initialised // + let child_handle = Handle::from("child"); + let child_token = Token::from("child"); + let child_rs = ResourceSet::from_strs("", "10.0.0.0/16", "").unwrap(); let ca_repo_info = { let base_uri = rsync("rsync://localhost/repo/ca/"); @@ -164,10 +136,6 @@ mod tests { RepoInfo::new(base_uri, rrdp_uri) }; - let ca_store = DiskAggregateStore::>::new( - &d, CA_NS - ).unwrap(); - let ca_ini = CaIniDet::init( &child_handle, child_token.clone(), @@ -177,6 +145,23 @@ mod tests { ca_store.add(ca_ini).unwrap(); let child = ca_store.get_latest(&child_handle).unwrap(); + // + // --- Add Child to TA + // + // Expect: + // - Child added to TA + // + + let cmd = CaCmdDet::add_child( + &ta_handle, + child_handle.clone(), + child_token.clone(), + child_rs + ); + + let events = ta.process_command(cmd).unwrap(); + let ta = ca_store.update(&ta_handle, ta, events).unwrap(); + // // --- Add TA as parent to child CA // @@ -184,16 +169,14 @@ mod tests { // - Parent added // - let parent_handle = Handle::from("parent"); - let parent = ParentCaContact::for_embedded( - parent_handle.clone(), + ta_handle.clone(), child_token.clone() ); let add_parent = CaCmdDet::add_parent( &child_handle, - parent_handle.as_str(), + ta_handle.as_str(), parent ); @@ -213,7 +196,7 @@ mod tests { let upd_ent = CaCmdDet::upd_entitlements( &child_handle, - &parent_handle, + &ta_handle, entitlements, signer.clone() ); @@ -232,7 +215,7 @@ mod tests { assert_eq!("all", &class_name); assert_eq!(None, limit); if let ParentCaContact::Embedded(handle, token) = parent_info { - assert_eq!(parent_handle, handle); + assert_eq!(ta_handle, handle); assert_eq!(child_token, token); } else { panic!("Expected embedded contact") @@ -246,8 +229,9 @@ mod tests { // - Publication // - let ta_cmd = TaCmdDet::certify_child( - &handle, child_handle.clone(), + let ta_cmd = CaCmdDet::certify_child( + &ta_handle, + child_handle.clone(), csr, limit, child_token.clone(), @@ -256,10 +240,10 @@ mod tests { let ta_events = ta.process_command(ta_cmd).unwrap(); let issued_evt = ta_events[0].clone().into_details(); - let _ta = ta_store.update(&handle, ta, ta_events).unwrap(); + let _ta = ca_store.update(&ta_handle, ta, ta_events).unwrap(); let (h, n, issued) = match issued_evt { - TaEvtDet::CertificateIssued(h, n, c) => (h, n, c), + CaEvtDet::CertificateIssued(h, n, c) => (h, n, c), _ => panic!("Expected issued certificate.") }; assert_eq!(child_handle, h); @@ -275,7 +259,7 @@ mod tests { let rcvd_cert = RcvdCert::from(issued); let upd_rcvd = CaCmdDet::upd_received_cert( - &child_handle, &parent_handle, DFLT_CLASS, rcvd_cert, signer.clone() + &child_handle, &ta_handle, DFLT_CLASS, rcvd_cert, signer.clone() ); let events = child.process_command(upd_rcvd).unwrap(); diff --git a/ca/src/publishing.rs b/ca/src/publishing.rs index e718ccf9..424ee147 100644 --- a/ca/src/publishing.rs +++ b/ca/src/publishing.rs @@ -24,13 +24,9 @@ use krill_commons::eventsourcing::{ }; use krill_commons::util::httpclient; -use crate::trustanchor::{ - CaSigner, - TrustAnchor, - TaEvt, - TaEvtDet -}; -use ca::{CertAuth, CaEvt, CaEvtDet}; +use crate::ca::{CertAuth, CaEvt, CaEvtDet}; +use crate::signing::CaSigner; + //------------ PubClientInit ------------------------------------------------- @@ -185,26 +181,16 @@ impl PubClients { } } -/// Implement listening for TrustAnchor Published events. -impl EventListener> for PubClients { - fn listen(&self, ta: &TrustAnchor, event: &TaEvt) { - if let TaEvtDet::Published(delta) = event.details() { - debug!("PubClients: Observed delta for publishing"); - - let current_objects = ta.current_objects(); - self.publish( - event.handle(), - current_objects, - delta.objects().clone().into() - ); - } - } -} /// Implement listening for CertAuth Published events. impl EventListener> for PubClients { fn listen(&self, ca: &CertAuth, event: &CaEvt) { - if let CaEvtDet::Published(_,_,_,delta) = event.details() { + + if let Some(delta) = match event.details() { + CaEvtDet::Published(_,_,_, delta) => Some(delta), + CaEvtDet::TaPublished(delta) => Some(delta), + _ => None + } { debug!("Pubclients: publishing for {}", event.handle()); let current_objects = ca.current_objects(); diff --git a/ca/src/signing.rs b/ca/src/signing.rs index 12a3bccb..9c45f214 100644 --- a/ca/src/signing.rs +++ b/ca/src/signing.rs @@ -1,13 +1,15 @@ //! Support for signing mft, crl, certificates, roas.. //! Common objects for TAs and CAs -use std::sync::{Arc, RwLock}; +use std::fmt::Debug; use std::ops::Deref; +use std::sync::{Arc, RwLock}; use bytes::Bytes; +use serde::Serialize; use rpki::crl::{Crl, TbsCertList}; use rpki::manifest::{Manifest, ManifestContent, FileAndHash}; -use rpki::crypto::{DigestAlgorithm, KeyIdentifier, SigningError}; +use rpki::crypto::{DigestAlgorithm, KeyIdentifier, SigningError, Signer}; use rpki::crypto::signer::KeyError; use rpki::sigobj::SignedObjectBuilder; use rpki::x509::{Serial, Time, Validity}; @@ -24,9 +26,18 @@ use krill_commons::api::ca::{ UpdatedObject, }; -use crate::trustanchor::CaSigner; +use krill_commons::util::softsigner::SignerKeyId; +//------------ CaSigner ------------------------------------------------------ + +pub trait CaSigner: Signer + Clone + Debug + Serialize + Sized + Sync + Send +'static {} +impl + Clone + Debug + Serialize + Sized + Sync + Send + 'static > CaSigner for T {} + + +//------------ CaSignSupport ------------------------------------------------- + +/// Support signing by CAs pub struct CaSignSupport; impl CaSignSupport { diff --git a/ca/src/trustanchor.rs b/ca/src/trustanchor.rs deleted file mode 100644 index 20434c4e..00000000 --- a/ca/src/trustanchor.rs +++ /dev/null @@ -1,687 +0,0 @@ -use std::collections::HashMap; -use std::fmt::{Debug, Display}; -use std::marker::PhantomData; -use std::ops::Deref; -use std::sync::{Arc, RwLock}; - -use chrono::Duration; -use serde::Serialize; -use rand::Rng; - -use rpki::cert::{ - Cert, - KeyUsage, - Overclaim, - TbsCert, -}; -use rpki::crypto::Signer; -use rpki::csr::Csr; -use rpki::uri; -use rpki::x509::{Serial, Time, Validity, Name}; - -use krill_commons::api; -use krill_commons::api::{ - DFLT_CLASS, - EncodedHash, - Entitlements, - SigningCert, -}; -use krill_commons::api::admin::{ - Handle, - Token -}; -use krill_commons::api::ca::{ - AddedObject, - AllCurrentObjects, - CertifiedKey, - ChildCa, - ChildCaDetails, - CurrentObject, - IssuedCert, - ObjectName, - ObjectsDelta, - PublicationDelta, - RcvdCert, - RepoInfo, - ResourceSet, - TrustAnchorInfo, - TrustAnchorLocator, - UpdatedObject, -}; -use krill_commons::eventsourcing::{ - Aggregate, - CommandDetails, - SentCommand, - StoredEvent, -}; -use krill_commons::util::softsigner::SignerKeyId; - -use crate::signing::CaSignSupport; - -pub const TA_NS: &str = "trustanchors"; -pub const TA_ID: &str = "ta"; - -pub fn ta_handle() -> Handle { - Handle::from(TA_ID) -} - -//------------ CaSigner ------------------------------------------------------ - -pub trait CaSigner: Signer + Clone + Debug + Serialize + Sized + Sync + Send +'static {} -impl + Clone + Debug + Serialize + Sized + Sync + Send + 'static > CaSigner for T {} - - -//------------ TrustAnchorInit ----------------------------------------------- - -pub type TaIni = StoredEvent; - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct TaIniDet { - repo_info: RepoInfo, - - children: Vec, - - current_key: CertifiedKey, - tal: TrustAnchorLocator, -} - -impl TaIniDet { - - /// Generates all the details for a Trust Anchor with all resources. - pub fn init_with_all_resources( - handle: &Handle, - repo_info: RepoInfo, - - ta_aia: uri::Rsync, - ta_uris: Vec, - - key: SignerKeyId, - signer: Arc>, - ) -> TaResult { - let resources = ResourceSet::all_resources(); - let ta_cert = Self::mk_ta_cer(&repo_info, &resources, &key, signer)?; - let tal = TrustAnchorLocator::new(ta_uris, &ta_cert); - let current_key = CertifiedKey::new(key, RcvdCert::new(ta_cert, ta_aia)); - - Ok(StoredEvent::new( - &handle, - 0, - TaIniDet { - repo_info, - children: vec![], - current_key, - tal - } - )) - } - - fn mk_ta_cer( - repo_info: &RepoInfo, - resources: &ResourceSet, - key: &S::KeyId, - signer: Arc> - ) -> TaResult { - let serial: Serial = rand::thread_rng().gen::().into(); - - let signer = signer.read().unwrap(); - - let pub_key = signer.get_key_info(&key) - .map_err(|_| Error::MissingKey)?; - let name = pub_key.to_subject_name(); - - let mut cert = TbsCert::new( - serial, - name.clone(), - Validity::new(Time::now(), Time::years_from_now(100)), - Some(name), - pub_key.clone(), - KeyUsage::Ca, - Overclaim::Refuse - ); - - cert.set_basic_ca(Some(true)); - - cert.set_ca_repository(Some(repo_info.ca_repository(""))); - cert.set_rpki_manifest(Some(repo_info.rpki_manifest("", &pub_key.key_identifier()))); - cert.set_rpki_notify(Some(repo_info.rpki_notify())); - - cert.set_as_resources(Some(resources.asn().clone())); - cert.set_v4_resources(Some(resources.v4().deref().clone())); - cert.set_v6_resources(Some(resources.v6().deref().clone())); - - cert.into_cert( - signer.deref(), - key - ).map_err(Error::signer) - } -} - - -//------------ TrustAnchorEvent ---------------------------------------------- - -pub type TaEvt = StoredEvent; - -#[allow(clippy::large_enum_variant)] -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub enum TaEvtDet { - Published(PublicationDelta), - ChildAdded(ChildCa), - CertificateIssued(Handle, String, IssuedCert) -} - - -impl TaEvtDet { - fn published( - handle: &Handle, - version: u64, - delta: PublicationDelta - ) -> TaEvt { - Self::with_details(handle, version, TaEvtDet::Published(delta)) - } - - fn child_added(handle: &Handle, version: u64, child: ChildCa) -> TaEvt { - Self::with_details(handle, version, TaEvtDet::ChildAdded(child)) - } - - fn certificate_issued( - handle: &Handle, - version: u64, - child_handle: Handle, - class_name: &str, - cert: IssuedCert - ) -> TaEvt { - Self::with_details( - handle, - version, - TaEvtDet::CertificateIssued( - child_handle, - class_name.to_string(), - cert - ) - ) - } - - fn with_details( - handle: &Handle, - version: u64, - details: TaEvtDet - ) -> TaEvt { - TaEvt::new(&handle, version, details) - } -} - -//------------ TrustAnchorCommand -------------------------------------------- - -pub type TaCmd = SentCommand>; - -#[derive(Clone, Debug)] -#[allow(clippy::large_enum_variant)] -pub enum TaCmdDet { - Republish(Arc>), - AddChild(ChildCa), - CertifyChild(Handle, Csr, Option, Token, Arc>) -} - -impl CommandDetails for TaCmdDet { - type Event = TaEvt; -} - -impl TaCmdDet { - pub fn republish(handle: &Handle, signer: Arc>) -> TaCmd { - SentCommand::new( - handle, - None, - TaCmdDet::Republish(signer) - ) - } - - pub fn add_child( - handle: &Handle, - child_handle: Handle, - child_token: Token, - child_resources: ResourceSet, - ) -> TaCmd { - let mut child = ChildCa::without_resources(child_handle, child_token); - child.add_resources(DFLT_CLASS, child_resources); - - SentCommand::new( - handle, - None, - TaCmdDet::AddChild(child) - ) - } - - pub fn certify_child( - handle: &Handle, - child_handle: Handle, - csr: Csr, - limit: Option, - token: Token, - signer: Arc> - ) -> TaCmd { - SentCommand::new( - handle, - None, - TaCmdDet::CertifyChild(child_handle, csr, limit, token, signer) - ) - } -} - - -//------------ TrustResult --------------------------------------------------- - -/// Helper type for TrustAnchor results -type TaResult = Result; -type TaEvtsRes = TaResult>; - -//------------ TrustAnchor --------------------------------------------------- - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct TrustAnchor { - handle: Handle, - version: u64, - - repo_info: RepoInfo, - current_key: CertifiedKey, - tal: TrustAnchorLocator, - - children: HashMap, - - phantom_signer: PhantomData -} - -impl TrustAnchor { - pub fn as_info(&self) -> TrustAnchorInfo { - TrustAnchorInfo::new( - self.resources().clone(), - self.repo_info.clone(), - self.children.clone(), - self.tal.clone() - ) - } - - pub fn tal(&self) -> &TrustAnchorLocator { - &self.tal - } - - pub fn cert(&self) -> &RcvdCert { - self.current_key.incoming_cert() - } - - pub fn resources(&self) -> &ResourceSet { - &self.current_key.incoming_cert().resources() - } - - pub fn repo_info(&self) -> &RepoInfo { - &self.repo_info - } - - pub fn current_objects(&self) -> AllCurrentObjects { - AllCurrentObjects::for_name_space( - "", - self.current_key.current_set().objects() - ) - } - - fn republish(&self, signer: Arc>) -> TaEvtsRes { - if !self.current_key.needs_publication() { - debug!("TA does not need to be republished"); - return Ok(vec![]) - } - - let ca_repo = self.repo_info.ca_repository(""); - let delta = ObjectsDelta::new(ca_repo); - - let delta = CaSignSupport::publish( - signer, - &self.current_key, - self.repo_info(), - "", - delta - ).map_err(Error::signer)?; - - Ok(vec![TaEvtDet::published(&self.handle, self.version, delta)]) - } - -} - -/// # Child CA Support -impl TrustAnchor { - - /// Returns an authorized child, or an error if the child is not - /// authorized or unknown. - pub fn get_authorised_child( - &self, - child: &Handle, - token: &Token - ) -> TaResult<&ChildCaDetails> { - let child = self.get_child(child)?; - - if token != child.token() { - Err(Error::Unauthorized) - } else { - Ok(child) - } - } - - pub fn get_child(&self, child: &Handle) -> TaResult<&ChildCaDetails> { - match self.children.get(child) { - None => Err(Error::UnknownChild(child.clone())), - Some(child) => Ok(child) - } - } - - fn has_child(&self, handle: &Handle) -> bool { - self.children.contains_key(handle) - } - - - fn add_child(&self, child: ChildCa) -> TaEvtsRes { - // check that - // 1) the resources are held by the TA - // 2) there is no existing child by this name - let my_res = self.current_key.incoming_cert().resources(); - for res in child.details().resource_sets() { - if ! my_res.contains(res) { - return Err(Error::MissingResources) - } - } - - if self.has_child(child.handle()) { - return Err(Error::DuplicateChild(child.handle().clone())) - } - - Ok(vec![TaEvtDet::child_added(&self.handle, self.version, child)]) - } - - /// List entitlements (section 3.3.2 of RFC6492). Return an error if - /// the child is not authorized -- or unknown etc. - pub fn list( - &self, - child_handle: &Handle, - token: &Token - ) -> TaResult { - let child = self.get_authorised_child(child_handle, token)?; - - let child_resources = child.resources(DFLT_CLASS) - .ok_or_else(|| Error::ChildLacksResources(child_handle.clone()))?; - - let until = child_resources.not_after(); - let issued = child_resources.certs().cloned().collect(); - - let cert = self.current_key.incoming_cert(); - let resources = cert.resources().clone(); - let cert = SigningCert::new(cert.uri().clone(), cert.cert().clone()); - - - Ok(Entitlements::with_default_class( - cert, resources, until, issued - )) - } - - - /// Certify a child CA. Returns the events that should be applied to this - /// CA. Meant to be called by issuing a 'CertifyChild' command. - fn certify_child( - &self, - child: Handle, - csr: Csr, - limit: Option, - token: Token, - signer: Arc> - ) -> TaEvtsRes { - // verify child and resources - let child_resources = self.get_authorised_child(&child, &token)? - .resources(DFLT_CLASS) - .ok_or_else(|| Error::ChildLacksResources(child.clone()))?; - - let resources = match limit.as_ref() { - Some(limit) => { - if child_resources.resources().contains(limit) { - limit - } else { - return Err(Error::ChildOverclaims(child.clone())) - } - }, - None => child_resources.resources() - }; - 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()); - - // create new cert - let issued_cert = { - let issuing_cert = self.current_key.incoming_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.asn().clone())); - cert.set_v4_resources(Some(resources.v4().deref().clone())); - cert.set_v6_resources(Some(resources.v6().deref().clone())); - - cert.set_authority_key_identifier( - Some(issuing_cert.cert().subject_key_identifier()) - ); - - let cert = { - cert.into_cert( - signer.read().unwrap().deref(), - self.current_key.key_id() - ).map_err(Error::signer)? - }; - - let cert_uri = issuing_cert.uri_for_object(&cert); - - IssuedCert::new(cert_uri, resources.clone(), cert) - }; - - let version = self.version; - let cert_object = CurrentObject::from(issued_cert.cert()); - - let issued_event = TaEvtDet::certificate_issued( - &self.handle, - version, - child, - DFLT_CLASS, - issued_cert.clone() - ); - - let delta = { - let ca_repo = self.repo_info.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 = TaEvtDet::published( - &self.handle, - version + 1, - CaSignSupport::publish( - signer, - &self.current_key, - &self.repo_info, - "", - delta - ).map_err(Error::signer)? - ); - - Ok(vec![issued_event, publish_event]) - } -} - -impl Aggregate for TrustAnchor { - type Command = TaCmd; - type Event = TaEvt; - type InitEvent = TaIni; - type Error = Error; - - fn init(event: Self::InitEvent) -> Result { - let (handle, _version, init) = event.unwrap(); - let version = 1; // after applying init - - let repo_info = init.repo_info; - let current_key = init.current_key; - let tal = init.tal; - - let children = HashMap::new(); - - Ok( - TrustAnchor { - handle, - version, - repo_info, - current_key, - tal, - children, - phantom_signer: PhantomData - } - ) - } - - fn version(&self) -> u64 { - self.version - } - - fn apply(&mut self, event: TaEvt) { - self.version += 1; - match event.into_details() { - - TaEvtDet::Published(delta) => { - self.current_key.apply_delta(delta); - }, - - TaEvtDet::ChildAdded(child) => { - let (handle, details) = child.unwrap(); - self.children.insert(handle, details); - }, - - TaEvtDet::CertificateIssued(child, class_name, issued_cert) => { - let child = self.children.get_mut(&child).unwrap(); - child.add_cert(&class_name, issued_cert) - } - } - } - - fn process_command(&self, cmd: TaCmd) -> TaEvtsRes { - match cmd.into_details() { - - TaCmdDet::Republish(signer) => { - self.republish(signer) - }, - - TaCmdDet::AddChild(child) => { - self.add_child(child) - }, - - TaCmdDet::CertifyChild(child, csr, limit, token, signer) => { - self.certify_child(child, csr, limit, token, signer) - } - } - } -} - - - -//------------ Error --------------------------------------------------------- - -/// Trust Anchor Errors -#[derive(Debug, Display)] -pub enum Error { - #[display(fmt = "Cannot find key.")] - MissingKey, - - #[display(fmt = "Error while signing: {}", _0)] - SignerError(String), - - #[display(fmt = "Resource Authority was not initialised.")] - NotInitialised, - - #[display(fmt = "Not all child resources are held by TA")] - MissingResources, - - #[display(fmt = "Child {} already exists.", _0)] - DuplicateChild(Handle), - - #[display(fmt = "Unknown child {}.", _0)] - UnknownChild(Handle), - - #[display(fmt = "Child {} has no default resource class.", _0)] - ChildLacksResources(Handle), - - #[display(fmt = "Child {} asks resources beyond entitlement.", _0)] - ChildOverclaims(Handle), - - #[display(fmt = "Invalidly CSR for child {}: {}.", _0, _1)] - InvalidCsr(Handle, String), - - #[display(fmt = "Unauthorized request")] - Unauthorized, -} - -impl Error { - pub fn signer(e: impl Display) -> Self { - Error::SignerError(e.to_string()) - } - - pub fn invalid_csr(handle: &Handle, msg: &str) -> Self { - Error::InvalidCsr(handle.clone(), msg.to_string()) - } -} - - -impl std::error::Error for Error {} diff --git a/commons/src/api/ca.rs b/commons/src/api/ca.rs index 753a53f4..029751ea 100644 --- a/commons/src/api/ca.rs +++ b/commons/src/api/ca.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use std::fmt; +use std::ops::Deref; use std::str; use std::str::FromStr; @@ -44,7 +45,6 @@ use crate::rpki::manifest::{ FileAndHash, Manifest, }; -use std::ops::Deref; //------------ ChildCa ------------------------------------------------------- @@ -1080,7 +1080,8 @@ pub struct TrustAnchorInfo { resources: ResourceSet, repo_info: RepoInfo, children: HashMap, - tal: TrustAnchorLocator + cert: RcvdCert, + tal: TrustAnchorLocator } impl TrustAnchorInfo { @@ -1088,6 +1089,7 @@ impl TrustAnchorInfo { resources: ResourceSet, repo_info: RepoInfo, children: HashMap, + cert: RcvdCert, tal: TrustAnchorLocator ) -> Self { @@ -1095,6 +1097,7 @@ impl TrustAnchorInfo { resources, repo_info, children, + cert, tal } } @@ -1111,6 +1114,10 @@ impl TrustAnchorInfo { &self.children } + pub fn cert(&self) -> &RcvdCert { + &self.cert + } + pub fn tal(&self) -> &TrustAnchorLocator { &self.tal } diff --git a/commons/src/util/test.rs b/commons/src/util/test.rs index 2675cccf..0582eaee 100644 --- a/commons/src/util/test.rs +++ b/commons/src/util/test.rs @@ -20,7 +20,7 @@ pub fn test_under_tmp(op: F) where F: FnOnce(PathBuf) -> () { op(dir); - fs::remove_dir_all(path).unwrap(); + let _result = fs::remove_dir_all(path); } /// This method sets up a random subdirectory and returns it. It is diff --git a/daemon/src/krillserver.rs b/daemon/src/krillserver.rs index 698ba3f5..11bc9d78 100644 --- a/daemon/src/krillserver.rs +++ b/daemon/src/krillserver.rs @@ -6,8 +6,7 @@ use std::sync::Arc; use bcder::Captured; use rpki::uri; -use krill_ca::{CaServer, CaServerError, PubClients, PubClientError}; -use krill_ca::trustanchor::{ta_handle}; +use krill_ca::{ta_handle, CaServer, CaServerError, PubClients, PubClientError}; use krill_commons::api::{publication, Entitlements, IssuanceRequest}; use krill_commons::api::admin; use krill_commons::api::admin::{Handle, Token, PubServerInfo, CertAuthInit, CertAuthPubMode, ParentCaContact, AddChildRequest, ParentCaInfo}; @@ -253,11 +252,14 @@ impl KrillServer { /// impl KrillServer { pub fn ta_info(&self) -> Option { - self.caserver.get_trust_anchor().map(|ta| ta.as_info()).ok() + match self.caserver.get_trust_anchor() { + Ok(ta) => ta.as_ta_info().ok(), + _ => None + } } pub fn trust_anchor_cert(&self) -> Option { - self.caserver.get_trust_anchor().map(|ta| ta.cert().clone()).ok() + self.ta_info().map(|ta| ta.cert().clone()) } pub fn ta_init(&mut self) -> EmptyRes { diff --git a/daemon/tests/ca_under_ta.rs b/daemon/tests/ca_under_ta.rs index 6ad8ff49..4a21aeba 100644 --- a/daemon/tests/ca_under_ta.rs +++ b/daemon/tests/ca_under_ta.rs @@ -4,7 +4,7 @@ extern crate krill_commons; extern crate krill_pubc; extern crate krill_ca; -use krill_ca::trustanchor::ta_handle; +use krill_ca::ta_handle; use krill_client::options::{ CaCommand, Command,