Support RFC6492 Issuance Report XML, and use it internally as well. (Issue #13)

This commit is contained in:
Tim Bruijnzeels
2019-07-16 12:16:24 +02:00
parent 63ddaec0b0
commit 6b2d64e4f8
9 changed files with 494 additions and 140 deletions
+112 -45
View File
@@ -13,7 +13,7 @@ use rpki::csr::Csr;
use rpki::uri;
use rpki::x509::{Serial, Validity, Time, Name};
use krill_commons::api::{self, DFLT_CLASS, EncodedHash, EntitlementClass, Entitlements, IssuanceRequest, SigningCert, RequestResourceLimit};
use krill_commons::api::{self, DFLT_CLASS, EncodedHash, EntitlementClass, Entitlements, IssuanceRequest, SigningCert, RequestResourceLimit, IssuanceResponse};
use krill_commons::api::admin::{
Handle,
ParentCaContact,
@@ -134,39 +134,50 @@ impl CaIniDet {
pub type CaEvt = StoredEvent<CaEvtDet>;
//------------ CertIssued ---------------------------------------------------
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct CertIssued {
child: Handle,
response: IssuanceResponse
}
impl CertIssued {
pub fn unwrap(self) -> (Handle, IssuanceResponse) {
(self.child, self.response)
}
}
//------------ CertRequested -----------------------------------------------
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct CertRequested {
parent: ParentCaContact,
class_name: String,
resource_limit: RequestResourceLimit,
csr: Csr
request: IssuanceRequest
}
impl CertRequested {
pub fn unwrap(self) -> (ParentCaContact, String, RequestResourceLimit, Csr) {
(self.parent, self.class_name, self.resource_limit, self.csr)
pub fn unwrap(self) -> (ParentCaContact, IssuanceRequest) {
(self.parent, self.request)
}
pub fn parent(&self) -> &ParentCaContact {
&self.parent
}
pub fn class_name(&self) -> &str {
&self.class_name
self.request.class_name()
}
pub fn resource_limit(&self) -> &RequestResourceLimit {
&self.resource_limit
pub fn limit(&self) -> &RequestResourceLimit {
self.request.limit()
}
pub fn csr(&self) -> &Csr {
&self.csr
self.request.csr()
}
}
impl Into<IssuanceRequest> for CertRequested {
fn into(self) -> IssuanceRequest {
IssuanceRequest::new(
self.class_name, self.resource_limit, self.csr
)
self.request
}
}
@@ -189,7 +200,7 @@ pub struct CertReceived {
pub enum CaEvtDet {
// Being a parent Events
ChildAdded(ChildCa),
CertificateIssued(Handle, String, IssuedCert),
CertificateIssued(CertIssued),
// Being a child Events
ParentAdded(ParentHandle, ParentCaContact),
@@ -324,18 +335,12 @@ impl CaEvtDet {
fn certificate_issued(
handle: &Handle,
version: u64,
child_handle: Handle,
class_name: &str,
cert: IssuedCert
cert_issued: CertIssued
) -> CaEvt {
StoredEvent::new(
handle,
version,
CaEvtDet::CertificateIssued(
child_handle,
class_name.to_string(),
cert
)
CaEvtDet::CertificateIssued(cert_issued)
)
}
@@ -365,7 +370,7 @@ type ResourceClassName = String;
pub enum CaCmdDet<S: CaSigner> {
// Being a parent
AddChild(Handle, Token, ResourceSet),
CertifyChild(Handle, Csr, RequestResourceLimit, Token, Arc<RwLock<S>>),
CertifyChild(Handle, IssuanceRequest, Token, Arc<RwLock<S>>),
// Being a child
AddParent(ParentHandle, ParentCaContact),
@@ -409,15 +414,14 @@ impl<S: CaSigner> CaCmdDet<S> {
pub fn certify_child(
handle: &Handle,
child_handle: Handle,
csr: Csr,
limit: RequestResourceLimit,
request: IssuanceRequest,
token: Token,
signer: Arc<RwLock<S>>
) -> CaCmd<S> {
SentCommand::new(
handle,
None,
CaCmdDet::CertifyChild(child_handle, csr, limit, token, signer)
CaCmdDet::CertifyChild(child_handle, request, token, signer)
)
}
@@ -642,9 +646,13 @@ impl<S: CaSigner> Aggregate for CertAuth<S> {
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);
CaEvtDet::CertificateIssued(cert_issued) => {
let (child_hndl, response) = cert_issued.unwrap();
let (class_name, _, _, issued) = response.unwrap();
let child = self.children.get_mut(&child_hndl).unwrap();
child.add_cert(&class_name, issued);
},
// Being a child
@@ -660,7 +668,7 @@ impl<S: CaSigner> Aggregate for CertAuth<S> {
CaEvtDet::CertificateRequested(req) => {
info!(
"Certificate requested for class {} from {}",
req.class_name,
req.class_name(),
req.parent
);
// do nothing, this should be picked up by listener and sent
@@ -696,8 +704,8 @@ impl<S: CaSigner> Aggregate for CertAuth<S> {
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)
CaCmdDet::CertifyChild(child, request, token, signer) => {
self.certify_child(child, request, token, signer)
}
// being a child
@@ -843,9 +851,47 @@ impl<S: CaSigner> CertAuth<S> {
child_handle: &Handle,
token: &Token
) -> CaRes<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
) -> CaRes<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
) -> CaRes<api::EntitlementClass> {
let child = self.get_authorised_child(child_handle, token)?;
let child_resources = child.resources_for_class(DFLT_CLASS)
let child_resources = child.resources_for_class(class_name)
.ok_or_else(|| Error::MissingResources)?;
let until = child_resources.not_after();
@@ -858,8 +904,12 @@ impl<S: CaSigner> CertAuth<S> {
let resources = cert.resources().clone();
let cert = SigningCert::new(cert.uri().clone(), cert.cert().clone());
Ok(Entitlements::with_default_class(
cert, resources, until, issued
Ok(EntitlementClass::new(
class_name.to_string(),
cert,
resources,
until,
issued
))
}
@@ -942,11 +992,12 @@ impl<S: CaSigner> CertAuth<S> {
fn certify_child(
&self,
child: Handle,
csr: Csr,
limit: RequestResourceLimit,
request: IssuanceRequest,
token: Token,
signer: Arc<RwLock<S>>
) -> CaEvtsRes {
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)")
@@ -956,7 +1007,7 @@ impl<S: CaSigner> CertAuth<S> {
// verify child and resources
let child_resources = self.get_authorised_child(&child, &token)?
.resources_for_class(DFLT_CLASS)
.resources_for_class(&class_name)
.ok_or_else(|| Error::MissingResources)?;
let resources = limit.resolve(child_resources.resources())
@@ -1034,12 +1085,23 @@ impl<S: CaSigner> CertAuth<S> {
let version = self.version;
let cert_object = CurrentObject::from(issued_cert.cert());
let signing_cert = SigningCert::from(issuing_cert);
let cert_issued = CertIssued {
child,
response: IssuanceResponse::new(
DFLT_CLASS.to_string(),
signing_cert,
resources,
issued_cert.cert().validity().not_after(),
issued_cert.clone()
)
};
let issued_event = CaEvtDet::certificate_issued(
&self.handle,
version,
child,
DFLT_CLASS,
issued_cert.clone()
cert_issued
);
let delta = {
@@ -1145,7 +1207,7 @@ impl<S: CaSigner> CertAuth<S> {
let mut version = self.version;
for ent in entitlements.classes() {
let name = ent.name();
let name = ent.class_name();
if let Some(_rc) = parent.resources.get(name) {
// Check whether a new certificate
@@ -1179,9 +1241,11 @@ impl<S: CaSigner> CertAuth<S> {
let cert_issue_req = CertRequested {
parent: parent.contact.clone(),
class_name: ent.name().to_string(),
resource_limit: RequestResourceLimit::default(),
csr
request: IssuanceRequest::new(
ent.class_name().to_string(),
RequestResourceLimit::default(),
csr
)
};
let added = CaEvtDet::resource_class_added(
@@ -1731,6 +1795,9 @@ pub enum Error {
#[display(fmt = "Child CA MUST have resources.")]
MusthaveResources,
#[display(fmt = "No issued cert matching pub key in resource class.")]
NoIssuedCert,
#[display(fmt = "Invalidly CSR for child {}: {}.", _0, _1)]
InvalidCsr(Handle, String),
+19 -19
View File
@@ -6,11 +6,7 @@ use std::ops::Deref;
use rpki::crypto::PublicKeyFormat;
use rpki::uri;
use krill_commons::api::{
DFLT_CLASS,
Entitlements,
IssuanceRequest,
};
use krill_commons::api::{DFLT_CLASS, Entitlements, IssuanceRequest, IssuanceResponse};
use krill_commons::api::admin::{
AddChildRequest,
Handle,
@@ -213,16 +209,14 @@ impl<S: CaSigner> CaServer<S> {
child: &Handle,
issue_req: IssuanceRequest,
token: Token,
) -> CaResult<IssuedCert, S> {
) -> CaResult<IssuanceResponse, S> {
if parent != & ta_handle() {
unimplemented!("https://github.com/NLnetLabs/krill/issues/25");
} else {
let ta = self.get_trust_anchor()?;
// class name can be ignored for TA, only uses one.
let (class_name, limit, csr) = issue_req.unwrap();
let pub_key = csr.public_key().clone();
let class_name = issue_req.class_name();
let pub_key = issue_req.csr().public_key();
if class_name != DFLT_CLASS {
unimplemented!("Issue for multiple classes from CAs, issue #25")
@@ -231,21 +225,24 @@ impl<S: CaSigner> CaServer<S> {
let cmd = CaCmdDet::certify_child(
parent,
child.clone(),
csr,
limit,
token,
issue_req.clone(),
token.clone(),
self.signer.clone()
);
let events = ta.process_command(cmd)?;
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();
let resources = child.resources_for_class(&class_name).unwrap();
let issued_cert = resources.cert(&pub_key).unwrap();
// New entitlements will include this resource class, and
// the newly issued certificate.
let response = ta.issuance_response(
child,
&class_name,
&pub_key,
&token
)?;
Ok(issued_cert.clone())
Ok(response)
}
}
@@ -369,12 +366,15 @@ impl<S: CaSigner> CaServer<S> {
ParentCaContact::Embedded(parent_handle, token) => {
for cert_req in cert_reqs {
let class_name = cert_req.class_name().to_string();
let issued = self.issue(
let issue_res = self.issue(
parent_handle,
&handle,
cert_req,
token.clone()
)?;
let (_,_,_, issued) = issue_res.unwrap();
issued_certs.push((class_name, issued));
}
}
+17 -8
View File
@@ -40,7 +40,7 @@ mod tests {
use rpki::crypto::signer::Signer;
use rpki::crypto::PublicKeyFormat;
use krill_commons::api::DFLT_CLASS;
use krill_commons::api::{DFLT_CLASS, IssuanceRequest};
use krill_commons::api::admin::{
Handle,
Token,
@@ -212,7 +212,8 @@ mod tests {
_ => panic!("Expected Csr")
};
let (parent_info, class_name, limit, csr) = req.unwrap();
let (parent_info, issuance_req) = req.unwrap();
let (class_name, limit, csr) = issuance_req.unwrap();
assert_eq!("all", &class_name);
assert!(limit.is_empty());
if let ParentCaContact::Embedded(handle, token) = parent_info {
@@ -230,11 +231,14 @@ mod tests {
// - Publication
//
let request = IssuanceRequest::new(
DFLT_CLASS.to_string(), limit, csr
);
let ta_cmd = CaCmdDet::certify_child(
&ta_handle,
child_handle.clone(),
csr,
limit,
request,
child_token.clone(),
signer.clone()
);
@@ -243,12 +247,17 @@ mod tests {
let issued_evt = ta_events[0].clone().into_details();
let _ta = ca_store.update(&ta_handle, ta, ta_events).unwrap();
let (h, n, issued) = match issued_evt {
CaEvtDet::CertificateIssued(h, n, c) => (h, n, c),
let issued = match issued_evt {
CaEvtDet::CertificateIssued(issued) => issued,
_ => panic!("Expected issued certificate.")
};
assert_eq!(child_handle, h);
assert_eq!(DFLT_CLASS, n);
let (handle, issuance_res) = issued.unwrap();
let (class_name, _, _, issued) = issuance_res.unwrap();
assert_eq!(child_handle, handle);
assert_eq!(DFLT_CLASS, class_name);
//
// --- Return issued certificate to child CA
+198 -36
View File
@@ -1,18 +1,20 @@
use std::io;
use std::str::FromStr;
use std::convert::TryFrom;
use chrono::{Utc, DateTime, SecondsFormat};
use serde::export::fmt::Display;
use rpki::cert::Cert;
use rpki::crypto::KeyIdentifier;
use rpki::csr::Csr;
use rpki::resources::{AsResources, Ipv4Resources, Ipv6Resources};
use rpki::uri;
use rpki::x509::Time;
use krill_commons::util::xml::{XmlReader, XmlReaderErr, AttributesError, XmlWriter};
use krill_commons::api::{Entitlements, EntitlementClass, SigningCert, RequestResourceLimit, IssuanceRequest};
use krill_commons::api::{Entitlements, EntitlementClass, SigningCert, RequestResourceLimit, IssuanceRequest, IssuanceResponse, RevocationRequest};
use krill_commons::api::ca::{ResourceSet, ResSetErr, IssuedCert};
use rpki::cert::Cert;
use rpki::resources::{AsResources, Ipv4Resources, Ipv6Resources};
use serde::export::fmt::Display;
use rpki::csr::Csr;
use krill_commons::util::xml::{XmlReader, XmlReaderErr, AttributesError, XmlWriter};
//------------ Consts --------------------------------------------------------
@@ -23,6 +25,8 @@ const NS: &str = "http://www.apnic.net/specs/rescerts/up-down/";
const TYPE_LIST_QRY: &str = "list";
const TYPE_LIST_RES: &str = "list_response";
const TYPE_ISSUE_QRY: &str = "issue";
const TYPE_ISSUE_RES: &str = "issue_response";
const TYPE_REVOKE_QRY: &str = "revoke";
//------------ Message -------------------------------------------------------
@@ -47,9 +51,19 @@ impl Message {
let content = Content::Res(Res::List(entitlements));
Message { sender, recipient, content}
}
pub fn revoke(
sender: String,
recipient: String,
revocation: RevocationRequest
) -> Self {
let content = Content::Qry(Qry::Revoke(revocation));
Message { sender, recipient, content }
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum Content {
Qry(Qry),
Res(Res)
@@ -82,10 +96,10 @@ impl Message {
a.exhausted()?;
let content = match msg_type.as_ref() {
TYPE_LIST_QRY | TYPE_ISSUE_QRY => {
TYPE_LIST_QRY | TYPE_ISSUE_QRY | TYPE_REVOKE_QRY => {
Ok(Content::Qry(Qry::decode(&msg_type, r)?))
},
TYPE_LIST_RES => {
TYPE_LIST_RES | TYPE_ISSUE_RES => {
Ok(Content::Res(Res::decode(&msg_type, r)?))
}
_ => Err(Error::UnknownMessageType)
@@ -139,9 +153,11 @@ impl Message {
/// This type defines the various RFC6492 queries.
#[derive(Clone, Debug, Eq, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum Qry {
List,
Issue(IssuanceRequest)
Issue(IssuanceRequest),
Revoke(RevocationRequest)
}
@@ -149,7 +165,8 @@ impl Qry {
fn msg_type(&self) -> &str {
match self {
Qry::List => TYPE_LIST_QRY,
Qry::Issue(_) => TYPE_ISSUE_QRY
Qry::Issue(_) => TYPE_ISSUE_QRY,
Qry::Revoke(_) => TYPE_REVOKE_QRY
}
}
@@ -159,14 +176,31 @@ impl Qry {
) -> Result<Self, Error> where R: io::Read {
match msg_type {
TYPE_LIST_QRY => Ok(Qry::List),
TYPE_ISSUE_QRY => Self::decode_issue(r),
TYPE_ISSUE_QRY => Ok(Qry::Issue(Self::decode_issue(r)?)),
TYPE_REVOKE_QRY => Ok(Qry::Revoke(Self::decode_revoke(r)?)),
_ => Err(Error::UnknownMessageType)
}
}
fn decode_revoke<R>(
r: &mut XmlReader<R>
) -> Result<RevocationRequest, Error> where R: io::Read {
r.take_named_element("key", |mut a, r| {
let class_name = a.take_req("class_name")?;
a.exhausted()?;
let ski_bytes = r.take_bytes_url_safe_pad()?;
let ski = KeyIdentifier::try_from(ski_bytes.as_ref())
.map_err(|_| Error::InvalidSki)?;
Ok(RevocationRequest::new(class_name.to_string(), ski))
})
}
fn decode_issue<R>(
r: &mut XmlReader<R>
) -> Result<Qry, Error> where R: io::Read {
) -> Result<IssuanceRequest, Error> where R: io::Read {
r.take_named_element("request", |mut a, r|{
let class_name = a.take_req("class_name")?;
let mut limit = RequestResourceLimit::default();
@@ -192,11 +226,11 @@ impl Qry {
let csr_bytes = r.take_bytes_characters()?;
let csr = Csr::decode(csr_bytes).map_err(|_| Error::InvalidCsr)?;
Ok(Qry::Issue(IssuanceRequest::new(
Ok(IssuanceRequest::new(
class_name.to_string(),
limit,
csr
)))
))
})
}
@@ -206,7 +240,8 @@ impl Qry {
) -> Result<(), io::Error> {
match self {
Qry::List => w.empty(),
Qry::Issue(issue_req) => Self::encode_issue(issue_req, w)
Qry::Issue(issue_req) => Self::encode_issue(issue_req, w),
Qry::Revoke(rev) => Self::encode_revoke(rev, w)
}
}
@@ -235,11 +270,22 @@ impl Qry {
let mut attrs_str = vec![];
attrs_str.push(("class_name", class_name));
for (k, v) in &attrs_strings {
attrs_str.push((k.clone(), v.as_str()));
attrs_str.push((k, v.as_str()));
}
w.put_element("request", Some(attrs_str.as_slice()), |w| {
w.put_blob(&csr)
w.put_base64_std(&csr)
})
}
fn encode_revoke<W: io::Write>(
rev: &RevocationRequest,
w: &mut XmlWriter<W>
) -> Result<(), io::Error> {
let att = [ ("class_name", rev.class_name() )];
let bytes = rev.key().as_slice();
w.put_element("key", Some(&att), |w| {
w.put_base64_url_safe(bytes)
})
}
}
@@ -249,14 +295,17 @@ impl Qry {
/// This type defines the various RFC6492 queries.
#[derive(Clone, Debug, Eq, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum Res {
List(Entitlements)
List(Entitlements),
Issue(IssuanceResponse)
}
impl Res {
fn msg_type(&self) -> &str {
match self {
Res::List(_) => TYPE_LIST_RES
Res::List(_) => TYPE_LIST_RES,
Res::Issue(_) => TYPE_ISSUE_RES
}
}
@@ -269,10 +318,53 @@ impl Res {
let entitlements = Self::decode_entitlements(r)?;
Ok(Res::List(entitlements))
},
TYPE_ISSUE_RES => {
let issuance_response = Self::decode_issue_response(r)?;
Ok(Res::Issue(issuance_response))
}
_ => Err(Error::UnknownMessageType)
}
}
fn decode_issue_response<R>(
r: &mut XmlReader<R>
) -> Result<IssuanceResponse, Error> where R: io::Read {
r.take_named_element("class", |mut a, r| {
let name = a.take_req("class_name")?;
let cert_url = uri::Rsync::from_str(
&a.take_req("cert_url")?
)?;
let asn = a.take_req("resource_set_as")?;
let v4 = a.take_req("resource_set_ipv4")?;
let v6 = a.take_req("resource_set_ipv6")?;
let resource_set = ResourceSet::from_strs(&asn, &v4, &v6)?;
let not_after = a.take_req("resource_set_notafter")?;
let not_after = DateTime::<Utc>::from_str(&not_after)?;
let not_after = Time::new(not_after);
a.exhausted()?;
let issued = Self::decode_issued_cert(r)?;
let cert = r.take_named_element("issuer", |a, r| {
a.exhausted()?;
Self::decode_cert(r)
})?;
let issuer = SigningCert::new(cert_url, cert);
Ok(IssuanceResponse::new(
name,
issuer,
resource_set,
not_after,
issued
))
})
}
fn decode_entitlements<R>(
r: &mut XmlReader<R>
) -> Result<Entitlements, Error> where R: io::Read {
@@ -387,7 +479,8 @@ impl Res {
w: &mut XmlWriter<W>
) -> Result<(), io::Error> {
match self {
Res::List(ents) => Self::encode_entitlements(ents, w)
Res::List(ents) => Self::encode_entitlements(ents, w),
Res::Issue(response) => Self::encode_issuance_response(response, w)
}
}
@@ -401,14 +494,47 @@ impl Res {
Ok(())
}
fn encode_issuance_response<W: io::Write>(
res: &IssuanceResponse,
w: &mut XmlWriter<W>
) -> Result<(), io::Error> {
Self::encode_class(
res.class_name(),
res.issuer().uri(),
res.not_after(),
res.resource_set(),
[res.issued().clone()].iter(),
res.issuer(),
w
)
}
fn encode_entitlement_class<W: io::Write>(
c: &EntitlementClass,
w: &mut XmlWriter<W>
) -> Result<(), io::Error> {
let cert_url = c.issuer().uri().to_string();
let not_after = c.not_after()
.to_rfc3339_opts(SecondsFormat::Secs, true);
let inrs = c.resource_set();
Self::encode_class(
c.class_name(),
c.issuer().uri(),
c.not_after(),
c.resource_set(),
c.issued().iter(),
c.issuer(),
w
)
}
fn encode_class<'a, W: io::Write>(
class_name: &str,
cert_url: &uri::Rsync,
not_after: Time,
inrs: &ResourceSet,
issued: impl Iterator<Item=&'a IssuedCert>,
issuer: &SigningCert,
w: &mut XmlWriter<W>
) -> Result<(), io::Error> {
let cert_url = cert_url.to_string();
let not_after = not_after.to_rfc3339_opts(SecondsFormat::Secs, true);
let asn = inrs.asn().to_string();
let v4 = inrs.v4().to_string();
@@ -417,21 +543,23 @@ impl Res {
let mut attrs = vec![];
attrs.push(("cert_url", cert_url.as_str()));
attrs.push(("class_name", c.name()));
attrs.push(("class_name", class_name));
attrs.push(("resource_set_as", asn.as_str()));
attrs.push(("resource_set_ipv4", v4.as_str()));
attrs.push(("resource_set_ipv6", v6.as_str()));
attrs.push(("resource_set_notafter", not_after.as_str()));
w.put_element("class", Some(&attrs), |w| {
for issued in c.issued() {
for issued in issued {
Self::encode_issued(issued, w)?;
}
let issuer_cert = c.issuer().cert().to_captured().into_bytes();
w.put_element("issuer", None, |w| { w.put_blob(&issuer_cert) })
let issuer_cert = issuer.cert().to_captured().into_bytes();
w.put_element("issuer", None, |w| { w.put_base64_std(&issuer_cert) })
})
}
fn encode_issued<W: io::Write>(
issued: &IssuedCert,
w: &mut XmlWriter<W>
@@ -456,13 +584,13 @@ impl Res {
attrs_strings.push(("resource_set_ipv6", v6.to_string()));
}
let mut attrs_str = vec![];
let mut attrs_str: Vec<(&str, &str)> = vec![];
for (k, v) in &attrs_strings {
attrs_str.push((k.clone(), v.as_str()));
attrs_str.push((k, v.as_str()));
}
w.put_element("certificate", Some(attrs_str.as_slice()), |w| {
w.put_blob(&cert_bytes)
w.put_base64_std(&cert_bytes)
})
}
@@ -500,10 +628,12 @@ pub enum Error {
#[display(fmt = "Could not parse encoded certificate.")]
InvalidCert,
#[display(fmt = "Could not parse encoded certificate request.")]
InvalidCsr,
#[display(fmt = "Could not parse SKI in revoke request.")]
InvalidSki,
#[display(fmt = "{}", _0)]
InrSyntax(String),
}
@@ -547,11 +677,14 @@ impl From<chrono::ParseError> for Error {
#[cfg(test)]
mod tests {
use super::*;
use std::str;
use sigmsg::SignedMessage;
use std::str::from_utf8_unchecked;
use crate::sigmsg::SignedMessage;
use crate::id::tests::test_id_certificate;
use super::*;
/// Test that the we can re-encode the object to xml, parse that
/// xml, and end up with an equal object.
fn assert_re_encode_equals(object: Message) {
@@ -598,10 +731,39 @@ mod tests {
}
#[test]
#[ignore]
fn parse_and_encode_issue_response() {
let xml = extract_xml(
include_bytes!("../test/remote/rpkid-rfc6492-issue_response.der")
);
let issue = Message::decode(xml.as_bytes()).unwrap();
assert_re_encode_equals(issue);
}
#[test]
fn encode_and_parse_revocation_request() {
// No example CMS found for this one, so just composing and
// reading the XML based on the RFC spec only.
let cert = test_id_certificate();
let sender = "child".to_string();
let rcpt = "parent".to_string();
let class = "all".to_string();
let ski = cert.subject_public_key_info().key_identifier();
let revocation = RevocationRequest::new(class, ski);
let rev = Message::revoke(sender, rcpt, revocation);
let decoded_rev = Message::decode(rev.encode_vec().as_slice()).unwrap();
assert_eq!(rev, decoded_rev);
}
#[test]
// #[ignore]
fn print_cms_content() {
let xml = extract_xml(
include_bytes!("../test/remote/rpkid-rfc6492-list_response.der")
include_bytes!("../test/remote/rpkid-rfc6492-issue_response.der")
);
eprintln!("{}", xml);
+2 -2
View File
@@ -115,7 +115,7 @@ impl PublisherRequest {
"publisher_bpki_ta",
None,
|w| {
w.put_blob(&self.id_cert.to_bytes())
w.put_base64_std(&self.id_cert.to_bytes())
}
)
}
@@ -341,7 +341,7 @@ impl RepositoryResponse {
"repository_bpki_ta",
None,
|w| {
w.put_blob(&self.id_cert.to_bytes())
w.put_base64_std(&self.id_cert.to_bytes())
}
)
}
+108 -6
View File
@@ -1,9 +1,10 @@
use api::ca::{ResourceSet, IssuedCert};
use api::ca::{ResourceSet, IssuedCert, RcvdCert};
use rpki::x509::Time;
use rpki::cert::{Cert, Overclaim};
use rpki::csr::Csr;
use rpki::uri;
use rpki::resources::{AsResources, Ipv4Resources, Ipv6Resources};
use rpki::crypto::{PublicKey, KeyIdentifier};
pub const DFLT_CLASS: &str = "all";
@@ -48,7 +49,7 @@ impl Entitlements {
) -> Self {
let name = DFLT_CLASS.to_string();
Entitlements { classes: vec![
EntitlementClass { name, issuer, resource_set, not_after, issued }
EntitlementClass { class_name: name, issuer, resource_set, not_after, issued }
]}
}
pub fn new(classes: Vec<EntitlementClass>) -> Self {
@@ -63,7 +64,7 @@ impl Entitlements {
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct EntitlementClass {
name: String,
class_name: String,
issuer: SigningCert,
resource_set: ResourceSet,
not_after: Time,
@@ -72,20 +73,51 @@ pub struct EntitlementClass {
impl EntitlementClass {
pub fn new(
name: String,
class_name: String,
issuer: SigningCert,
resource_set: ResourceSet,
not_after: Time,
issued: Vec<IssuedCert>
) -> Self {
EntitlementClass { name, issuer, resource_set, not_after, issued }
EntitlementClass { class_name, issuer, resource_set, not_after, issued }
}
pub fn name(&self) -> &str { &self.name }
fn unwrap(
self
) -> (String, SigningCert, ResourceSet, Time, Vec<IssuedCert>) {
(
self.class_name,
self.issuer,
self.resource_set,
self.not_after,
self.issued
)
}
pub fn class_name(&self) -> &str { &self.class_name }
pub fn issuer(&self) -> &SigningCert { &self.issuer }
pub fn resource_set(&self) -> &ResourceSet { &self.resource_set }
pub fn not_after(&self) -> Time { self.not_after }
pub fn issued(&self) -> &Vec<IssuedCert> { &self.issued }
/// Converts this into an IssuanceResponse for the given key. I.e. includes
/// the issued certificate matching the given public key only. Returns a
/// None if no match is found.
pub fn into_issuance_response(self, key: &PublicKey) -> Option<IssuanceResponse> {
let (class_name, issuer, resource_set, not_after, issued) = self.unwrap();
issued.into_iter()
.find(|issued| issued.cert().subject_public_key_info() == key)
.map(|issued| {
IssuanceResponse::new(
class_name,
issuer,
resource_set,
not_after,
issued
)
})
}
}
@@ -116,6 +148,15 @@ impl PartialEq for SigningCert {
impl Eq for SigningCert {}
impl From<&RcvdCert> for SigningCert {
fn from(c: &RcvdCert) -> Self {
SigningCert {
uri: c.uri().clone(),
cert: c.cert().clone()
}
}
}
//------------ IssuanceRequest -----------------------------------------------
@@ -157,6 +198,45 @@ impl PartialEq for IssuanceRequest {
impl Eq for IssuanceRequest {}
//------------ IssuanceResponse ----------------------------------------------
/// A Certificate Issuance Response equivalent to the one defined in
/// section 3.4.2 of RFC6492.
///
/// Note that this is like a single EntitlementClass response, except that
/// it includes the one certificate which has just been issued only.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct IssuanceResponse {
class_name: String,
issuer: SigningCert,
resource_set: ResourceSet, // resources allowed on a cert
not_after: Time,
issued: IssuedCert
}
impl IssuanceResponse {
pub fn new(
class_name: String,
issuer: SigningCert,
resource_set: ResourceSet, // resources allowed on a cert
not_after: Time,
issued: IssuedCert
) -> Self {
IssuanceResponse { class_name, issuer, resource_set, not_after, issued }
}
pub fn unwrap(self) -> (String, SigningCert, ResourceSet, IssuedCert) {
(self.class_name, self.issuer, self.resource_set, self.issued)
}
pub fn class_name(&self) -> &str { &self.class_name }
pub fn issuer(&self) -> &SigningCert { &self.issuer }
pub fn resource_set(&self) -> &ResourceSet { &self.resource_set }
pub fn not_after(&self) -> Time { self.not_after }
pub fn issued(&self) -> &IssuedCert { &self.issued }
}
//------------ RequestResourceLimit ------------------------------------------
/// The scope of resources that a child CA wants to have certified. By default
@@ -286,3 +366,25 @@ impl Default for RequestResourceLimit {
}
}
}
//------------ RevocationRequest ---------------------------------------------
/// This type represents a Certificate Revocation Request as
/// defined in section 3.5.1 of RFC6492.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RevocationRequest {
class_name: String,
key: KeyIdentifier
}
impl RevocationRequest {
pub fn new(class_name: String, key: KeyIdentifier) -> Self {
RevocationRequest { class_name, key}
}
pub fn class_name(&self) -> &str { &self.class_name }
pub fn key(&self) -> &KeyIdentifier { &self.key }
}
+35 -21
View File
@@ -218,11 +218,32 @@ impl <R: io::Read> XmlReader<R> {
/// Takes base64 encoded bytes from the next 'characters' event.
pub fn take_bytes_characters(&mut self) -> Result<Bytes, XmlReaderErr> {
let chars = Self::strip_whitespace(self.take_chars()?);
let b64 = base64::decode_config(&chars, base64::STANDARD_NO_PAD)?;
self.take_bytes(base64::STANDARD_NO_PAD)
}
/// Takes base64 encoded bytes from the next 'characters' event.
pub fn take_bytes_url_safe_pad(&mut self) -> Result<Bytes, XmlReaderErr> {
self.take_bytes(base64::URL_SAFE_NO_PAD)
}
fn take_bytes(
&mut self,
config: base64::Config
) -> Result<Bytes, XmlReaderErr> {
let chars = self.take_chars()?;
// strip whitespace and padding (we are liberal in what we accept here)
// TODO: Avoid allocation, pass in an AsRef<[u8]> that
// removes any whitespace on the fly.
let chars: Vec<u8> = chars.into_bytes()
.into_iter()
.filter(|c| !b" \n\t\r\x0b\x0c=".contains(c))
.collect();
let b64 = base64::decode_config(&chars, config)?;
Ok(Bytes::from(b64))
}
pub fn take_empty(&mut self) -> Result<(), XmlReaderErr> {
Ok(())
}
@@ -247,18 +268,6 @@ impl <R: io::Read> XmlReader<R> {
}
}
}
/// Convenience function to strip all whitespace from chars. E.g.
/// prior to decoding base64. I am quite sure this could be done
/// more efficiently.
///
/// Asked the
fn strip_whitespace(s: String) -> Vec<u8> {
s.into_bytes()
.into_iter()
.filter(|c| !b" \n\t\r\x0b\x0c".contains(c))
.collect()
}
}
impl XmlReader<fs::File> {
@@ -473,12 +482,17 @@ impl <W: io::Write> XmlWriter<W> {
Ok(())
}
/// Converts bytes to base64 encoded Characters as the content. Note
/// that you cannot have both Characters and other included elements.
/// This would be valid XML, but it's not used by any of the RPKI XML
/// structures.
pub fn put_blob(&mut self, bytes: &Bytes) -> Result<(), io::Error> {
let b64 = base64::encode(bytes);
/// Converts bytes to base64 encoded Characters as the content, using the
/// Standard character set, without padding.
pub fn put_base64_std(&mut self, bytes: &Bytes) -> Result<(), io::Error> {
let b64 = base64::encode_config(bytes, base64::STANDARD);
self.put_text(b64.as_ref())
}
/// Converts bytes to base64 encoded Characters as the content, using the
/// URL safe character set and padding.
pub fn put_base64_url_safe(&mut self, bytes: &[u8]) -> Result<(), io::Error> {
let b64 = base64::encode_config(bytes, base64::URL_SAFE);
self.put_text(b64.as_ref())
}
@@ -547,7 +561,7 @@ mod tests {
]),
|w| {
w.put_element("b", None, |w| {
w.put_blob(&Bytes::from("X"))
w.put_base64_std(&Bytes::from("X"))
})
}
)
+3 -3
View File
@@ -7,10 +7,10 @@ use bcder::Captured;
use rpki::uri;
use krill_ca::{ta_handle, CaServer, CaServerError, PubClients, PubClientError};
use krill_commons::api::{publication, Entitlements, IssuanceRequest};
use krill_commons::api::{publication, Entitlements, IssuanceRequest, IssuanceResponse};
use krill_commons::api::admin;
use krill_commons::api::admin::{Handle, Token, PubServerInfo, CertAuthInit, CertAuthPubMode, ParentCaContact, AddChildRequest, ParentCaReq};
use krill_commons::api::ca::{TrustAnchorInfo, RcvdCert, IssuedCert, CertAuthList, CertAuthInfo};
use krill_commons::api::ca::{TrustAnchorInfo, RcvdCert, CertAuthList, CertAuthInfo};
use krill_commons::util::softsigner::{OpenSslSigner, SignerError};
use krill_cms_proxy::api::ClientInfo;
use krill_cms_proxy::proxy;
@@ -432,7 +432,7 @@ impl KrillServer {
child: &Handle,
issue_req: IssuanceRequest,
auth: Auth
) -> KrillRes<IssuedCert> {
) -> KrillRes<IssuanceResponse> {
Ok(self.caserver.issue(
parent,
child,