use std::convert::TryFrom;
use rpki::{
ca::{csr::RpkiCaCsr, provisioning::RequestResourceLimit},
crypto::{KeyIdentifier, PublicKey},
repository::{
cert::{KeyUsage, Overclaim, TbsCert},
resources::ResourceSet,
x509::{Name, Time, Validity},
Cert,
},
uri,
};
use crate::{
commons::{
api::{IssuedCertificate, ReceivedCert},
crypto::KrillSigner,
error::Error,
util::AllowedUri,
KrillResult,
},
daemon::ca::CertifiedKey,
};
pub type CaRepository = uri::Rsync;
pub type RpkiManifest = uri::Rsync;
pub type RpkiNotify = uri::Https;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CsrInfo {
ca_repository: CaRepository,
rpki_manifest: RpkiManifest,
rpki_notify: Option<RpkiNotify>,
key: PublicKey,
}
impl CsrInfo {
pub fn new(
ca_repository: CaRepository,
rpki_manifest: RpkiManifest,
rpki_notify: Option<RpkiNotify>,
key: PublicKey,
) -> Self {
CsrInfo {
ca_repository,
rpki_manifest,
rpki_notify,
key,
}
}
pub fn ca_repository(&self) -> &CaRepository {
&self.ca_repository
}
pub fn global_uris(&self) -> bool {
self.ca_repository.seems_global_uri()
&& self.rpki_manifest.seems_global_uri()
&& self
.rpki_notify
.as_ref()
.map(|uri| uri.seems_global_uri())
.unwrap_or_else(|| true)
}
pub fn unpack(self) -> (CaRepository, RpkiManifest, Option<RpkiNotify>, PublicKey) {
(self.ca_repository, self.rpki_manifest, self.rpki_notify, self.key)
}
pub fn key_id(&self) -> KeyIdentifier {
self.key.key_identifier()
}
}
impl TryFrom<&RpkiCaCsr> for CsrInfo {
type Error = Error;
fn try_from(csr: &RpkiCaCsr) -> KrillResult<CsrInfo> {
csr.verify_signature().map_err(Error::invalid_csr)?;
let ca_repository = csr
.ca_repository()
.cloned()
.ok_or_else(|| Error::invalid_csr("missing ca repository"))?;
let rpki_manifest = csr
.rpki_manifest()
.cloned()
.ok_or_else(|| Error::invalid_csr("missing rpki manifest"))?;
let rpki_notify = csr.rpki_notify().cloned();
let key = csr.public_key().clone();
Ok(CsrInfo {
ca_repository,
rpki_manifest,
rpki_notify,
key,
})
}
}
pub struct SignSupport;
impl SignSupport {
pub fn make_issued_cert(
csr: CsrInfo,
resources: &ResourceSet,
limit: RequestResourceLimit,
signing_key: &CertifiedKey,
validity: Validity,
signer: &KrillSigner,
) -> KrillResult<IssuedCertificate> {
let signing_cert = signing_key.incoming_cert();
let resources = limit.apply_to(resources)?;
if !signing_cert.resources().contains(&resources) {
return Err(Error::MissingResources);
}
let request = CertRequest::Ca(csr, validity);
let tbs = Self::make_tbs_cert(&resources, signing_cert, request, signer)?;
let cert = signer.sign_cert(tbs, signing_key.key_id())?;
let uri = signing_cert.uri_for_object(&cert);
IssuedCertificate::create(cert, uri, resources, limit)
.map_err(|e| Error::Custom(format!("Signed certificate has issue: {}", e)))
}
pub fn make_rta_ee_cert(
resources: &ResourceSet,
signing_key: &CertifiedKey,
validity: Validity,
pub_key: PublicKey,
signer: &KrillSigner,
) -> KrillResult<Cert> {
let signing_cert = signing_key.incoming_cert();
let request = CertRequest::Ee(pub_key, validity);
let tbs = Self::make_tbs_cert(resources, signing_cert, request, signer)?;
let cert = signer.sign_cert(tbs, signing_key.key_id())?;
Ok(cert)
}
fn make_tbs_cert(
resources: &ResourceSet,
signing_cert: &ReceivedCert,
request: CertRequest,
signer: &KrillSigner,
) -> KrillResult<TbsCert> {
let serial = signer.random_serial()?;
let issuer = signing_cert.subject().clone();
let validity = match &request {
CertRequest::Ca(_, validity) => *validity,
CertRequest::Ee(_, validity) => *validity,
};
let pub_key = match &request {
CertRequest::Ca(info, _) => info.key.clone(),
CertRequest::Ee(key, _) => key.clone(),
};
let subject = Some(Name::from_pub_key(&pub_key));
let key_usage = match &request {
CertRequest::Ca(_, _) => KeyUsage::Ca,
CertRequest::Ee(_, _) => KeyUsage::Ee,
};
let overclaim = Overclaim::Refuse;
let mut cert = TbsCert::new(serial, issuer, validity, subject, pub_key, key_usage, overclaim);
let asns = resources.to_as_resources();
if asns.is_inherited() || !asns.to_blocks().unwrap().is_empty() {
cert.set_as_resources(asns);
}
let ipv4 = resources.to_ip_resources_v4();
if ipv4.is_inherited() || !ipv4.to_blocks().unwrap().is_empty() {
cert.set_v4_resources(ipv4);
}
let ipv6 = resources.to_ip_resources_v6();
if ipv6.is_inherited() || !ipv6.to_blocks().unwrap().is_empty() {
cert.set_v6_resources(ipv6);
}
cert.set_authority_key_identifier(Some(signing_cert.key_identifier()));
cert.set_ca_issuer(Some(signing_cert.uri().clone()));
cert.set_crl_uri(Some(signing_cert.crl_uri()));
match request {
CertRequest::Ca(csr, _) => {
let (ca_repository, rpki_manifest, rpki_notify, _pub_key) = csr.unpack();
cert.set_basic_ca(Some(true));
cert.set_ca_repository(Some(ca_repository));
cert.set_rpki_manifest(Some(rpki_manifest));
cert.set_rpki_notify(rpki_notify);
}
CertRequest::Ee(_, _) => {
}
}
Ok(cert)
}
pub fn sign_validity_weeks(weeks: i64) -> Validity {
let from = Time::five_minutes_ago();
let until = Time::now() + chrono::Duration::weeks(weeks);
Validity::new(from, until)
}
pub fn sign_validity_days(days: i64) -> Validity {
let from = Time::five_minutes_ago();
let until = Time::now() + chrono::Duration::days(days);
Validity::new(from, until)
}
}
#[allow(clippy::large_enum_variant)]
enum CertRequest {
Ca(CsrInfo, Validity),
Ee(PublicKey, Validity),
}