diff --git a/Cargo.lock b/Cargo.lock index 8d5b6c3c..5ef9406f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1624,8 +1624,8 @@ dependencies = [ [[package]] name = "rpki" -version = "0.14.3-dev" -source = "git+https://github.com/NLnetLabs/rpki-rs/#61ce3bf5817d5c62eb38267b4bcdd1a7112d1f38" +version = "0.15.0-dev" +source = "git+https://github.com/NLnetLabs/rpki-rs/#666e09357493aec6f0dc030f0e387c756423e2cb" dependencies = [ "base64", "bcder", diff --git a/Cargo.toml b/Cargo.toml index ad403fa3..1acfc4a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ regex = { version = "1.5.5", optional = true, default_features = reqwest = { version = "0.11", features = ["json"] } rpassword = { version = "^5.0", optional = true } # rpki = { version = "0.13.2", features = [ "compat", "repository", "rrdp", "serde" ] } -rpki = { version = "0.14.3-dev", git = "https://github.com/NLnetLabs/rpki-rs/", features = [ "ca", "rrdp" ] } +rpki = { version = "0.15.0-dev", git = "https://github.com/NLnetLabs/rpki-rs/", features = [ "ca", "rrdp" ] } # rpki = { version = "0.14.3-dev", path = "../rpki-rs/", features = [ "ca", "repository", "rrdp" ] } scrypt = { version = "^0.6", optional = true, default-features = false } serde = { version = "^1.0", features = ["derive"] } diff --git a/README.md b/README.md index d31a9564..1bf01be3 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,23 @@ For more information please refer to the [documentation](https://krill.docs.nlne # Changelog +## 0.10.0 + +In this release we introduce the following feature: +- BGPSec Router Certificate Signing (CLI/API only) + +## 0.9.6 'Newer ROAs Please' + +This release fixes an issue introduced in 0.9.5 where the background job to +automatically renew ROAs was not added to Krill's task queue on startup. Thanks +to Alberto Leiva for finding this issue! + +All users who upgraded to 0.9.5 are advised to upgrade to this version as soon +as possible. Not doing so can lead to ROAs expiring and becoming invalid. If you +did not upgrade to 0.9.5 you are not affected by this issue. + +This release contains no other changes. + ## 0.9.5 'Have You considered these Upgrades?' This release is primarily intended to improve support for migrations of pre-0.9.0 diff --git a/defaults/roles.polar b/defaults/roles.polar index 4ef09564..e591bfa1 100644 --- a/defaults/roles.polar +++ b/defaults/roles.polar @@ -70,6 +70,7 @@ role_allow("readonly", action: Permission) if ROUTES_ANALYSIS, ASPAS_READ, ASPAS_ANALYSIS, + BGPSEC_READ, RTA_LIST, RTA_READ ]; @@ -101,6 +102,8 @@ role_allow("readwrite", action: Permission) if ASPAS_READ, ASPAS_UPDATE, ASPAS_ANALYSIS, + BGPSEC_READ, + BGPSEC_UPDATE, RTA_LIST, RTA_READ, RTA_UPDATE diff --git a/src/cli/client.rs b/src/cli/client.rs index 0fed0b40..a35eb21a 100644 --- a/src/cli/client.rs +++ b/src/cli/client.rs @@ -11,9 +11,9 @@ use crate::{ }, commons::{ api::{ - AllCertAuthIssues, AspaDefinitionUpdates, CaRepoDetails, CertAuthIssues, ChildCaInfo, - ChildrenConnectionStats, ParentCaContact, ParentStatuses, PublisherDetails, PublisherList, RepoStatus, - Token, + AllCertAuthIssues, AspaDefinitionUpdates, BgpSecDefinitionUpdates, CaRepoDetails, CertAuthIssues, + ChildCaInfo, ChildrenConnectionStats, ParentCaContact, ParentStatuses, PublisherDetails, PublisherList, + RepoStatus, Token, }, bgp::BgpAnalysisAdvice, error::KrillIoError, @@ -335,6 +335,26 @@ impl KrillClient { Ok(ApiResponse::BgpAnalysisSuggestions(suggestions)) } + CaCommand::BgpSecList(handle) => { + let uri = format!("api/v1/cas/{}/bgpsec", handle); + let bgpsec_list = get_json(&self.server, &self.token, &uri).await?; + Ok(ApiResponse::BgpSecDefinitions(bgpsec_list)) + } + + CaCommand::BgpSecAdd(handle, addition) => { + let uri = format!("api/v1/cas/{}/bgpsec", handle); + let update = BgpSecDefinitionUpdates::new(vec![addition], vec![]); + post_json(&self.server, &self.token, &uri, update).await?; + Ok(ApiResponse::Empty) + } + + CaCommand::BgpSecRemove(handle, removal) => { + let uri = format!("api/v1/cas/{}/bgpsec", handle); + let update = BgpSecDefinitionUpdates::new(vec![], vec![removal]); + post_json(&self.server, &self.token, &uri, update).await?; + Ok(ApiResponse::Empty) + } + CaCommand::AspasList(handle) => { let uri = format!("api/v1/cas/{}/aspas", handle); let aspas = get_json(&self.server, &self.token, &uri).await?; diff --git a/src/cli/options.rs b/src/cli/options.rs index 40e7aab7..a4a914d8 100644 --- a/src/cli/options.rs +++ b/src/cli/options.rs @@ -13,14 +13,15 @@ use clap::{App, Arg, ArgMatches, SubCommand}; use rpki::{ ca::{ + csr::BgpsecCsr, idcert::IdCert, idexchange, idexchange::{CaHandle, ChildHandle, ParentHandle, PublisherHandle}, }, + crypto::KeyIdentifier, repository::{ aspa::{DuplicateProviderAs, ProviderAs}, - crypto::KeyIdentifier, - resources::ResourceSet, + resources::{Asn, ResourceSet}, x509::Time, }, uri, @@ -31,8 +32,9 @@ use crate::{ commons::{ api::{ AddChildRequest, AspaCustomer, AspaDefinition, AspaDefinitionFormatError, AspaProvidersUpdate, - AuthorizationFmtError, CertAuthInit, ParentCaContact, ParentCaReq, PublicationServerUris, - RepositoryContact, RoaDefinition, RoaDefinitionUpdates, RtaName, Token, UpdateChildRequest, + AuthorizationFmtError, BgpSecAsnKey, BgpSecDefinition, CertAuthInit, ParentCaContact, ParentCaReq, + PublicationServerUris, RepositoryContact, RoaDefinition, RoaDefinitionUpdates, RtaName, Token, + UpdateChildRequest, }, crypto::SignSupport, error::KrillIoError, @@ -741,6 +743,77 @@ impl Options { app.subcommand(sub) } + fn make_cas_bgpsec_list_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { + let mut sub = SubCommand::with_name("list").about("Show current BGPSec configurations"); + + sub = Self::add_general_args(sub); + sub = Self::add_my_ca_arg(sub); + + app.subcommand(sub) + } + + fn make_cas_bgpsec_add_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { + let mut sub = SubCommand::with_name("add").about("Add BGPSec configurations"); + + sub = Self::add_general_args(sub); + sub = Self::add_my_ca_arg(sub); + + sub = sub + .arg( + Arg::with_name("asn") + .short("a") + .long("asn") + .value_name("ASN") + .help("The ASN of the router for the key used in the CSR. E.g. AS65000") + .required(true), + ) + .arg( + Arg::with_name("csr") + .long("csr") + .value_name("CSR") + .help("The file containing the DER encoded Certificate Sign Request") + .required(true), + ); + + app.subcommand(sub) + } + + fn make_cas_bgpsec_remove_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { + let mut sub = SubCommand::with_name("remove").about("Remove a BGPSec definition"); + + sub = Self::add_general_args(sub); + sub = Self::add_my_ca_arg(sub); + + sub = sub + .arg( + Arg::with_name("asn") + .short("a") + .long("asn") + .value_name("ASN") + .help("The ASN used in the BGPSec definition. E.g. AS65000") + .required(true), + ) + .arg( + Arg::with_name("key") + .long("key") + .value_name("key") + .help("The hex encoded key identifier used in the BGPSec definition") + .required(true), + ); + + app.subcommand(sub) + } + + fn make_cas_bgpsec_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { + let mut sub = SubCommand::with_name("bgpsec").about("Manage BGPSec certificates"); + + sub = Self::make_cas_bgpsec_list_sc(sub); + sub = Self::make_cas_bgpsec_add_sc(sub); + sub = Self::make_cas_bgpsec_remove_sc(sub); + + app.subcommand(sub) + } + #[cfg(feature = "aspa")] fn make_cas_aspas_add_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { let mut sub = SubCommand::with_name("add").about("Add or replace an ASPA configuration"); @@ -1269,6 +1342,7 @@ impl Options { app = Self::make_cas_parents_sc(app); app = Self::make_cas_keyroll_sc(app); app = Self::make_cas_routes_sc(app); + app = Self::make_cas_bgpsec_sc(app); app = Self::make_cas_repo_sc(app); app = Self::make_cas_issues_sc(app); app = Self::make_pubserver_sc(app); @@ -1824,6 +1898,69 @@ impl Options { } } + fn parse_matches_cas_bgpsec_list(matches: &ArgMatches) -> Result { + let general_args = GeneralArgs::from_matches(matches)?; + let my_ca = Self::parse_my_ca(matches)?; + + let command = Command::CertAuth(CaCommand::BgpSecList(my_ca)); + + Ok(Options::make(general_args, command)) + } + + fn parse_matches_cas_bgpsec_add(matches: &ArgMatches) -> Result { + let general_args = GeneralArgs::from_matches(matches)?; + let my_ca = Self::parse_my_ca(matches)?; + + let asn_str = matches.value_of("asn").unwrap(); + let asn = Asn::from_str(asn_str).map_err(|_| Error::invalid_asn(asn_str))?; + + let csr_file = matches.value_of("csr").unwrap(); + let csr_file_path = PathBuf::from(csr_file); + + let bytes = file::read(&csr_file_path) + .map_err(|e| Error::GeneralArgumentError(format!("Cannot read file '{}', error: {}", csr_file, e,)))?; + let csr = BgpsecCsr::decode(bytes.as_ref()) + .map_err(|e| Error::GeneralArgumentError(format!("Cannot parse CSR file '{}', error: {}", csr_file, e)))?; + + csr.validate() + .map_err(|_| Error::GeneralArgumentError(format!("CSR in file '{}' is not valid", csr_file)))?; + + let definition = BgpSecDefinition::new(asn, csr); + + let command = Command::CertAuth(CaCommand::BgpSecAdd(my_ca, definition)); + + Ok(Options::make(general_args, command)) + } + + fn parse_matches_cas_bgpsec_remove(matches: &ArgMatches) -> Result { + let general_args = GeneralArgs::from_matches(matches)?; + let my_ca = Self::parse_my_ca(matches)?; + + let asn_str = matches.value_of("asn").unwrap(); + let asn = Asn::from_str(asn_str).map_err(|_| Error::invalid_asn(asn_str))?; + + let key_str = matches.value_of("key").unwrap(); + let key = KeyIdentifier::from_str(key_str).map_err(|_| Error::general("Cannot parse key identifier"))?; + + let definition = BgpSecAsnKey::new(asn, key); + + let command = Command::CertAuth(CaCommand::BgpSecRemove(my_ca, definition)); + + Ok(Options::make(general_args, command)) + } + + fn parse_matches_cas_bgpsec(matches: &ArgMatches) -> Result { + if let Some(m) = matches.subcommand_matches("list") { + Self::parse_matches_cas_bgpsec_list(m) + } else if let Some(m) = matches.subcommand_matches("add") { + Self::parse_matches_cas_bgpsec_add(m) + } else if let Some(m) = matches.subcommand_matches("remove") { + Self::parse_matches_cas_bgpsec_remove(m) + } else { + Err(Error::UnrecognizedSubCommand) + } + } + fn parse_matches_cas_aspas_add(matches: &ArgMatches) -> Result { let general_args = GeneralArgs::from_matches(matches)?; let my_ca = Self::parse_my_ca(matches)?; @@ -2297,6 +2434,8 @@ impl Options { Self::parse_matches_cas_keyroll(m) } else if let Some(m) = matches.subcommand_matches("roas") { Self::parse_matches_cas_routes(m) + } else if let Some(m) = matches.subcommand_matches("bgpsec") { + Self::parse_matches_cas_bgpsec(m) } else if let Some(m) = matches.subcommand_matches("aspas") { Self::parse_matches_cas_aspas(m) } else if let Some(m) = matches.subcommand_matches("repo") { @@ -2385,6 +2524,11 @@ pub enum CaCommand { AspasUpdate(CaHandle, AspaCustomer, AspaProvidersUpdate), AspasRemove(CaHandle, AspaCustomer), + // BGPSec + BgpSecList(CaHandle), + BgpSecAdd(CaHandle, BgpSecDefinition), + BgpSecRemove(CaHandle, BgpSecAsnKey), + // Show details for this CA Show(CaHandle), ShowHistoryCommands(CaHandle, HistoryOptions), diff --git a/src/cli/report.rs b/src/cli/report.rs index 7d877833..2e9f7356 100644 --- a/src/cli/report.rs +++ b/src/cli/report.rs @@ -8,9 +8,10 @@ use rpki::ca::idexchange; use crate::{ commons::{ api::{ - AllCertAuthIssues, AspaDefinitionList, CaCommandDetails, CaRepoDetails, CertAuthInfo, CertAuthIssues, - CertAuthList, ChildCaInfo, ChildrenConnectionStats, CommandHistory, ParentCaContact, ParentStatuses, - PublisherDetails, PublisherList, RepoStatus, RoaDefinitions, RtaList, RtaPrepResponse, ServerInfo, + AllCertAuthIssues, AspaDefinitionList, BgpSecCsrInfoList, CaCommandDetails, CaRepoDetails, CertAuthInfo, + CertAuthIssues, CertAuthList, ChildCaInfo, ChildrenConnectionStats, CommandHistory, ParentCaContact, + ParentStatuses, PublisherDetails, PublisherList, RepoStatus, RoaDefinitions, RtaList, RtaPrepResponse, + ServerInfo, }, bgp::{BgpAnalysisAdvice, BgpAnalysisReport, BgpAnalysisSuggestion}, }, @@ -41,6 +42,9 @@ pub enum ApiResponse { // ASPA related AspaDefinitions(AspaDefinitionList), + // BGPSec related + BgpSecDefinitions(BgpSecCsrInfoList), + ParentCaContact(ParentCaContact), ParentStatuses(ParentStatuses), @@ -88,6 +92,7 @@ impl ApiResponse { ApiResponse::BgpAnalysisFull(table) => Ok(Some(table.report(fmt)?)), ApiResponse::BgpAnalysisSuggestions(suggestions) => Ok(Some(suggestions.report(fmt)?)), ApiResponse::AspaDefinitions(definitions) => Ok(Some(definitions.report(fmt)?)), + ApiResponse::BgpSecDefinitions(definitions) => Ok(Some(definitions.report(fmt)?)), ApiResponse::ParentCaContact(contact) => Ok(Some(contact.report(fmt)?)), ApiResponse::ParentStatuses(statuses) => Ok(Some(statuses.report(fmt)?)), ApiResponse::ChildInfo(info) => Ok(Some(info.report(fmt)?)), @@ -203,6 +208,8 @@ impl Report for BgpAnalysisSuggestion {} impl Report for AspaDefinitionList {} +impl Report for BgpSecCsrInfoList {} + impl Report for CaRepoDetails {} impl Report for RepoStatus {} diff --git a/src/commons/api/bgpsec.rs b/src/commons/api/bgpsec.rs new file mode 100644 index 00000000..85af5577 --- /dev/null +++ b/src/commons/api/bgpsec.rs @@ -0,0 +1,232 @@ +use std::{fmt, str::FromStr}; + +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + +use rpki::{ + ca::{csr::BgpsecCsr, publication::Base64}, + crypto::KeyIdentifier, + repository::resources::Asn, +}; + +use super::ObjectName; + +//------------ BgpSecDefinition -------------------------------------------- + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct BgpSecDefinition { + asn: Asn, + csr: BgpsecCsr, +} + +impl BgpSecDefinition { + pub fn new(asn: Asn, csr: BgpsecCsr) -> Self { + BgpSecDefinition { asn, csr } + } + + pub fn asn(&self) -> Asn { + self.asn + } + + pub fn csr(&self) -> &BgpsecCsr { + &self.csr + } +} + +impl PartialEq for BgpSecDefinition { + fn eq(&self, other: &Self) -> bool { + self.asn == other.asn && self.csr.to_captured().as_slice() == other.csr.to_captured().as_slice() + } +} + +impl Eq for BgpSecDefinition {} + +//------------ BgpSecAsnKey ------------------------------------------------ + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct BgpSecAsnKey { + asn: Asn, + key: KeyIdentifier, +} + +impl BgpSecAsnKey { + pub fn new(asn: Asn, key: KeyIdentifier) -> Self { + BgpSecAsnKey { asn, key } + } + + pub fn asn(&self) -> Asn { + self.asn + } + + pub fn key_identifier(&self) -> KeyIdentifier { + self.key + } +} + +impl fmt::Display for BgpSecAsnKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // We use a format similar to the recommendation for the router + // certificate subject from section 3.1.1 in RFC 8209. + write!(f, "ROUTER-{:08X}-{}", self.asn.into_u32(), self.key) + } +} + +impl From<&BgpSecDefinition> for BgpSecAsnKey { + fn from(def: &BgpSecDefinition) -> Self { + BgpSecAsnKey { + asn: def.asn(), + key: def.csr().public_key().key_identifier(), + } + } +} + +impl FromStr for BgpSecAsnKey { + type Err = BgpSecAsnKeyFmtError; + + fn from_str(s: &str) -> Result { + let s = s.strip_prefix("ROUTER-").ok_or(BgpSecAsnKeyFmtError)?; + + let parts: Vec<_> = s.split('-').collect(); + + if parts.len() != 2 { + return Err(BgpSecAsnKeyFmtError); + } + + let asn_hex = parts.get(0).ok_or(BgpSecAsnKeyFmtError)?; + let key_id_str = parts.get(1).ok_or(BgpSecAsnKeyFmtError)?; + + let asn_nr = u32::from_str_radix(asn_hex, 16).map_err(|_| BgpSecAsnKeyFmtError)?; + let asn = Asn::from_u32(asn_nr); + + let key = KeyIdentifier::from_str(key_id_str).map_err(|_| BgpSecAsnKeyFmtError)?; + + Ok(BgpSecAsnKey { asn, key }) + } +} + +#[derive(Clone, Debug)] +pub struct BgpSecAsnKeyFmtError; + +impl std::error::Error for BgpSecAsnKeyFmtError {} + +impl fmt::Display for BgpSecAsnKeyFmtError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Invalid BGPSec ASN and Key format. Expected: ROUTER--" + ) + } +} + +/// We use BgpSecAsnKey as (JSON) map keys and therefore we need it +/// to be serializable to a single simple string. +impl Serialize for BgpSecAsnKey { + fn serialize(&self, s: S) -> Result + where + S: Serializer, + { + self.to_string().serialize(s) + } +} + +/// We use BgpSecAsnKey as (JSON) map keys and therefore we need it +/// to be deserializable from a single simple string. +impl<'de> Deserialize<'de> for BgpSecAsnKey { + fn deserialize(d: D) -> Result + where + D: Deserializer<'de>, + { + let string = String::deserialize(d)?; + BgpSecAsnKey::from_str(string.as_str()).map_err(de::Error::custom) + } +} + +//------------ BgpSecDefinitionUpdates ------------------------------------- + +/// Contains BGPSec definition updates sent to the API. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct BgpSecDefinitionUpdates { + add: Vec, + remove: Vec, +} + +impl BgpSecDefinitionUpdates { + pub fn new(add: Vec, remove: Vec) -> Self { + BgpSecDefinitionUpdates { add, remove } + } + + pub fn unpack(self) -> (Vec, Vec) { + (self.add, self.remove) + } +} + +/// This type is shown through the API +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct BgpSecCsrInfo { + asn: Asn, + key_identifier: KeyIdentifier, + csr: Base64, +} + +impl BgpSecCsrInfo { + pub fn new(asn: Asn, key_identifier: KeyIdentifier, csr: Base64) -> Self { + BgpSecCsrInfo { + asn, + key_identifier, + csr, + } + } + + pub fn asn(&self) -> Asn { + self.asn + } + + pub fn key_identifier(&self) -> KeyIdentifier { + self.key_identifier + } + + pub fn csr(&self) -> &Base64 { + &self.csr + } + + pub fn object_name(&self) -> ObjectName { + ObjectName::bgpsec(self.asn, self.key_identifier) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct BgpSecCsrInfoList(Vec); + +impl BgpSecCsrInfoList { + pub fn new(list: Vec) -> Self { + BgpSecCsrInfoList(list) + } + + pub fn unpack(self) -> Vec { + self.0 + } +} + +impl fmt::Display for BgpSecCsrInfoList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "ASN, key identifier, CSR base64")?; + for info in self.0.iter() { + writeln!(f, "{}, {}, {}", info.asn, info.key_identifier, info.csr)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use super::BgpSecAsnKey; + + #[test] + fn bgp_sec_to_from_str() { + let string = "ROUTER-0000FDE8-17316903F0671229E8808BA8E8AB0105FA915A07"; + let key = BgpSecAsnKey::from_str(string).unwrap(); + let to_string = key.to_string(); + assert_eq!(string, &to_string); + } +} diff --git a/src/commons/api/ca.rs b/src/commons/api/ca.rs index 2624fd18..c50638f9 100644 --- a/src/commons/api/ca.rs +++ b/src/commons/api/ca.rs @@ -20,11 +20,11 @@ use rpki::{ }, publication::Base64, }, + crypto::KeyIdentifier, repository::{ aspa::Aspa, cert::Cert, crl::{Crl, CrlEntry}, - crypto::KeyIdentifier, manifest::Manifest, resources::{Asn, ResourceSet}, roa::Roa, @@ -34,6 +34,7 @@ use rpki::{ uri, }; +use crate::daemon::ca::BgpSecCertInfo; use crate::{ commons::{ api::{ @@ -46,6 +47,8 @@ use crate::{ daemon::ca::RouteAuthorization, }; +use super::BgpSecAsnKey; + //------------ IdCertPem ----------------------------------------------------- /// A PEM encoded IdCert and sha256 of the encoding, for easier @@ -524,6 +527,10 @@ impl ObjectName { pub fn aspa(customer: Asn) -> Self { ObjectName(format!("{}.asa", customer)) } + + pub fn bgpsec(asn: Asn, key: KeyIdentifier) -> Self { + ObjectName(format!("ROUTER-{:08X}-{}.cer", asn.into_u32(), key)) + } } impl From<&Cert> for ObjectName { @@ -571,6 +578,18 @@ impl From<&AspaDefinition> for ObjectName { } } +impl From<&BgpSecCertInfo> for ObjectName { + fn from(info: &BgpSecCertInfo) -> Self { + Self::bgpsec(info.asn(), info.public_key().key_identifier()) + } +} + +impl From<&BgpSecAsnKey> for ObjectName { + fn from(asn_key: &BgpSecAsnKey) -> Self { + Self::bgpsec(asn_key.asn(), asn_key.key_identifier()) + } +} + impl From<&str> for ObjectName { fn from(s: &str) -> Self { ObjectName(s.to_string()) @@ -640,6 +659,15 @@ impl From<&Aspa> for Revocation { } } +impl From<&BgpSecCertInfo> for Revocation { + fn from(info: &BgpSecCertInfo) -> Self { + Revocation { + serial: info.serial(), + expires: info.expires(), + } + } +} + //------------ Revocations --------------------------------------------------- #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -2102,7 +2130,7 @@ mod test { use bytes::Bytes; use std::convert::TryFrom; - use rpki::repository::crypto::PublicKeyFormat; + use rpki::crypto::PublicKeyFormat; use crate::{commons::crypto::OpenSslSigner, test}; diff --git a/src/commons/api/history.rs b/src/commons/api/history.rs index a6464b39..f2f6a1c0 100644 --- a/src/commons/api/history.rs +++ b/src/commons/api/history.rs @@ -7,7 +7,8 @@ use rpki::{ idexchange::{ChildHandle, MyHandle, ParentHandle, PublisherHandle, ServiceUri}, provisioning::{RequestResourceLimit, ResourceClassName, RevocationRequest}, }, - repository::{crypto::KeyIdentifier, resources::ResourceSet, x509::Time}, + crypto::KeyIdentifier, + repository::{resources::ResourceSet, x509::Time}, }; use crate::{ @@ -487,6 +488,7 @@ pub enum StorableCaCommand { AspaRemove { customer: AspaCustomer, }, + BgpSecDefinitionUpdates, // details in events RepoUpdate { service_uri: ServiceUri, }, @@ -597,6 +599,9 @@ impl WithStorableDetails for StorableCaCommand { StorableCaCommand::AspasUpdateExisting { .. } => CommandSummary::new("cmd-ca-aspas-update-existing", &self), StorableCaCommand::AspaRemove { .. } => CommandSummary::new("cmd-ca-aspas-remove", &self), + // BGPSec + StorableCaCommand::BgpSecDefinitionUpdates => CommandSummary::new("cmd-bgpsec-update", &self), + // REPO StorableCaCommand::RepoUpdate { service_uri } => { CommandSummary::new("cmd-ca-repo-update", &self).with_service_uri(service_uri) @@ -770,6 +775,11 @@ impl fmt::Display for StorableCaCommand { write!(f, "Remove ASPA for customer AS: {}", customer) } + // ------------------------------------------------------------ + // BGPSec Support + // ------------------------------------------------------------ + StorableCaCommand::BgpSecDefinitionUpdates => write!(f, "Update BGPSec definitions"), + // ------------------------------------------------------------ // Publishing // ------------------------------------------------------------ diff --git a/src/commons/api/mod.rs b/src/commons/api/mod.rs index a807ef76..23edaf0d 100644 --- a/src/commons/api/mod.rs +++ b/src/commons/api/mod.rs @@ -6,6 +6,9 @@ pub use self::admin::*; mod aspa; pub use self::aspa::*; +mod bgpsec; +pub use self::bgpsec::*; + mod ca; pub use self::ca::*; @@ -19,12 +22,15 @@ pub mod rrdp; use std::{collections::HashMap, fmt}; +use rpki::ca::csr::BgpsecCsr; use rpki::ca::provisioning::ResourceClassName; +use rpki::ca::publication::Base64; use serde::{Deserialize, Serialize}; use rpki::{ ca::idexchange::{CaHandle, ChildHandle, ParentHandle, PublisherHandle}, - repository::{crypto::KeyIdentifier, resources::Asn}, + crypto::KeyIdentifier, + repository::resources::Asn, }; use crate::{commons::error::RoaDeltaError, daemon::ca::RouteAuthorization}; @@ -112,6 +118,11 @@ impl ErrorResponse { self.with_arg("asn", asn) } + pub fn with_bgpsec_csr(self, csr: &BgpsecCsr) -> Self { + let base64 = Base64::from_content(csr.to_captured().as_slice()); + self.with_arg("bgpsec_csr", base64) + } + pub fn with_roa_delta_error(mut self, roa_delta_error: &RoaDeltaError) -> Self { self.delta_error = Some(roa_delta_error.clone()); self diff --git a/src/commons/crypto/signing/dispatch/krillsigner.rs b/src/commons/crypto/signing/dispatch/krillsigner.rs index ba355dfe..8721af4c 100644 --- a/src/commons/crypto/signing/dispatch/krillsigner.rs +++ b/src/commons/crypto/signing/dispatch/krillsigner.rs @@ -1,18 +1,23 @@ use std::{path::Path, sync::Arc, time::Duration}; use rpki::{ - ca::{idcert::IdCert, idexchange::RepoInfo, provisioning, publication}, + ca::{ + csr::{Csr, RpkiCaCsr}, + idcert::IdCert, + idexchange::RepoInfo, + provisioning, publication, + }, + crypto::{KeyIdentifier, PublicKey, PublicKeyFormat, RpkiSignature, RpkiSignatureAlgorithm, Signer}, repository::{ aspa::{Aspa, AspaBuilder}, cert::TbsCert, crl::{CrlEntry, TbsCertList}, - crypto::{KeyIdentifier, PublicKey, PublicKeyFormat, Signature, SignatureAlgorithm, Signer}, manifest::ManifestContent, roa::RoaBuilder, rta, sigobj::SignedObjectBuilder, x509::{Serial, Time, Validity}, - Cert, Crl, Csr, Manifest, Roa, + Cert, Crl, Manifest, Roa, }, }; @@ -217,22 +222,24 @@ impl KrillSigner { Serial::random(&self.router).map_err(crypto::Error::signer) } - pub fn sign + ?Sized>(&self, key_id: &KeyIdentifier, data: &D) -> CryptoResult { + pub fn sign + ?Sized>(&self, key_id: &KeyIdentifier, data: &D) -> CryptoResult { self.router - .sign(key_id, SignatureAlgorithm::default(), data) + .sign(key_id, RpkiSignatureAlgorithm::default(), data) .map_err(crypto::Error::signing) } - pub fn sign_one_off + ?Sized>(&self, data: &D) -> CryptoResult<(Signature, PublicKey)> { + pub fn sign_one_off + ?Sized>(&self, data: &D) -> CryptoResult<(RpkiSignature, PublicKey)> { self.router - .sign_one_off(SignatureAlgorithm::default(), data) + .sign_one_off(RpkiSignatureAlgorithm::default(), data) .map_err(crypto::Error::signer) } - pub fn sign_csr(&self, base_repo: &RepoInfo, name_space: &str, key: &KeyIdentifier) -> CryptoResult { - let pub_key = self.router.get_key_info(key).map_err(crypto::Error::key_error)?; - let mft_file_name = ObjectName::mft_for_key(&pub_key.key_identifier()); - let enc = Csr::construct( + pub fn sign_csr(&self, base_repo: &RepoInfo, name_space: &str, key: &KeyIdentifier) -> CryptoResult { + let signing_key_id = self.router.get_key_info(key).map_err(crypto::Error::key_error)?; + let mft_file_name = ObjectName::mft_for_key(&signing_key_id.key_identifier()); + + // The rpki-rs library returns a signed and encoded CSR for a CA certificate. + let signed_and_encoded_csr = Csr::construct_rpki_ca( &self.router, key, &base_repo.ca_repository(name_space).join(&[]).unwrap(), // force trailing slash @@ -240,7 +247,9 @@ impl KrillSigner { base_repo.rpki_notify(), ) .map_err(crypto::Error::signing)?; - Ok(Csr::decode(enc.as_slice())?) + + // Decode the encoded CSR again to get a typed RpkiCaCsr + Ok(RpkiCaCsr::decode(signed_and_encoded_csr.as_slice())?) } pub fn sign_cert(&self, tbs: TbsCert, key_id: &KeyIdentifier) -> CryptoResult { diff --git a/src/commons/crypto/signing/dispatch/signerinfo.rs b/src/commons/crypto/signing/dispatch/signerinfo.rs index 04f6c80b..21502698 100644 --- a/src/commons/crypto/signing/dispatch/signerinfo.rs +++ b/src/commons/crypto/signing/dispatch/signerinfo.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, fmt, path::Path, str::FromStr}; -use rpki::repository::crypto::{KeyIdentifier, PublicKey}; +use rpki::crypto::{KeyIdentifier, PublicKey}; use crate::{ commons::{ diff --git a/src/commons/crypto/signing/dispatch/signerprovider.rs b/src/commons/crypto/signing/dispatch/signerprovider.rs index 7065b759..d858fe5d 100644 --- a/src/commons/crypto/signing/dispatch/signerprovider.rs +++ b/src/commons/crypto/signing/dispatch/signerprovider.rs @@ -1,5 +1,6 @@ -use rpki::repository::crypto::{ - signer::KeyError, KeyIdentifier, PublicKey, PublicKeyFormat, Signature, SignatureAlgorithm, SigningError, +use rpki::crypto::{ + signer::{KeyError, SigningAlgorithm}, + KeyIdentifier, PublicKey, PublicKeyFormat, RpkiSignature, Signature, SignatureAlgorithm, SigningError, }; use crate::commons::crypto::{ @@ -110,7 +111,7 @@ impl SignerProvider { &self, signer_private_key_id: &str, challenge: &D, - ) -> Result { + ) -> Result { match self { SignerProvider::OpenSsl(_, signer) => signer.sign_registration_challenge(signer_private_key_id, challenge), #[cfg(feature = "hsm")] @@ -206,12 +207,17 @@ impl SignerProvider { } } - pub fn sign + ?Sized>( + pub fn sign + ?Sized>( &self, key: &KeyIdentifier, - algorithm: SignatureAlgorithm, + algorithm: Alg, data: &D, - ) -> Result> { + ) -> Result, SigningError> { + let signing_algorithm = algorithm.signing_algorithm(); + if !matches!(signing_algorithm, SigningAlgorithm::RsaSha256) { + return Err(SignerError::UnsupportedSigningAlg(signing_algorithm).into()); + } + match self { SignerProvider::OpenSsl(_, signer) => signer.sign(key, algorithm, data), #[cfg(feature = "hsm")] @@ -223,11 +229,16 @@ impl SignerProvider { } } - pub fn sign_one_off + ?Sized>( + pub fn sign_one_off + ?Sized>( &self, - algorithm: SignatureAlgorithm, + algorithm: Alg, data: &D, - ) -> Result<(Signature, PublicKey), SignerError> { + ) -> Result<(Signature, PublicKey), SignerError> { + let signing_algorithm = algorithm.signing_algorithm(); + if !matches!(signing_algorithm, SigningAlgorithm::RsaSha256) { + return Err(SignerError::UnsupportedSigningAlg(signing_algorithm)); + } + match self { SignerProvider::OpenSsl(_, signer) => signer.sign_one_off(algorithm, data), #[cfg(feature = "hsm")] diff --git a/src/commons/crypto/signing/dispatch/signerrouter.rs b/src/commons/crypto/signing/dispatch/signerrouter.rs index 832900bb..5beffb26 100644 --- a/src/commons/crypto/signing/dispatch/signerrouter.rs +++ b/src/commons/crypto/signing/dispatch/signerrouter.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use std::{collections::HashMap, sync::RwLock}; -use rpki::repository::crypto::{ +use rpki::crypto::{ signer::KeyError, KeyIdentifier, PublicKey, PublicKeyFormat, Signature, SignatureAlgorithm, Signer, SigningError, }; @@ -586,21 +586,21 @@ impl Signer for SignerRouter { self.get_signer_for_key(key_id)?.destroy_key(key_id) } - fn sign + ?Sized>( + fn sign + ?Sized>( &self, key_id: &KeyIdentifier, - algorithm: SignatureAlgorithm, + algorithm: Alg, data: &D, - ) -> Result> { + ) -> Result, SigningError> { self.bind_ready_signers(); self.get_signer_for_key(key_id)?.sign(key_id, algorithm, data) } - fn sign_one_off + ?Sized>( + fn sign_one_off + ?Sized>( &self, - algorithm: SignatureAlgorithm, + algorithm: Alg, data: &D, - ) -> Result<(Signature, PublicKey), Self::Error> { + ) -> Result<(Signature, PublicKey), Self::Error> { self.bind_ready_signers(); self.one_off_signer.sign_one_off(algorithm, data) } @@ -613,6 +613,8 @@ impl Signer for SignerRouter { #[cfg(all(test, feature = "hsm"))] pub mod tests { + use rpki::crypto::RpkiSignatureAlgorithm; + use crate::{ commons::crypto::{ dispatch::signerprovider::SignerFlags, @@ -639,7 +641,7 @@ pub mod tests { pub fn verify_that_a_usable_signer_is_registered_and_can_be_used() { test::test_under_tmp(|d| { #[allow(non_snake_case)] - let DEF_SIG_ALG = SignatureAlgorithm::default(); + let DEF_SIG_ALG = RpkiSignatureAlgorithm::default(); // Build a mock signer that is contactable and usable for the SignerRouter let call_counts = Arc::new(MockSignerCallCounts::new()); @@ -712,7 +714,7 @@ pub mod tests { router.destroy_key(&key_identifier).unwrap(); assert_eq!(1, call_counts.get(FnIdx::DestroyKey)); - let err = router.sign(&key_identifier, SignatureAlgorithm::default(), &out_buf); + let err = router.sign(&key_identifier, RpkiSignatureAlgorithm::default(), &out_buf); // TODO: Should this error from the SignerRouter actually be SigningError::KeyNotFound instead of // SigningError::Signer(SignerError::KeyNotFound)? assert!(matches!(err, Err(SigningError::Signer(SignerError::KeyNotFound)))); diff --git a/src/commons/crypto/signing/misc.rs b/src/commons/crypto/signing/misc.rs index 835357b7..ad145848 100644 --- a/src/commons/crypto/signing/misc.rs +++ b/src/commons/crypto/signing/misc.rs @@ -5,14 +5,14 @@ use std::convert::TryFrom; use bytes::Bytes; use rpki::{ - ca::provisioning::RequestResourceLimit, + ca::{csr::RpkiCaCsr, provisioning::RequestResourceLimit}, + crypto::{DigestAlgorithm, KeyIdentifier, PublicKey}, repository::{ cert::{KeyUsage, Overclaim, TbsCert}, - crypto::{DigestAlgorithm, KeyIdentifier, PublicKey}, manifest::FileAndHash, resources::ResourceSet, x509::{Name, Time, Validity}, - Cert, Crl, Csr, + Cert, Crl, }, uri, }; @@ -75,10 +75,10 @@ impl CsrInfo { } } -impl TryFrom<&Csr> for CsrInfo { +impl TryFrom<&RpkiCaCsr> for CsrInfo { type Error = Error; - fn try_from(csr: &Csr) -> KrillResult { + fn try_from(csr: &RpkiCaCsr) -> KrillResult { csr.validate().map_err(|_| Error::invalid_csr("invalid signature"))?; let ca_repository = csr .ca_repository() diff --git a/src/commons/crypto/signing/signers/error.rs b/src/commons/crypto/signing/signers/error.rs index fcb5dba7..bbfb9775 100644 --- a/src/commons/crypto/signing/signers/error.rs +++ b/src/commons/crypto/signing/signers/error.rs @@ -1,6 +1,7 @@ use std::{fmt, path::PathBuf}; use openssl::error::ErrorStack; +use rpki::crypto::signer::SigningAlgorithm; use crate::commons::error::KrillIoError; @@ -17,6 +18,7 @@ pub enum SignerError { PermanentlyUnusable, Pkcs11Error(String), TemporarilyUnavailable, + UnsupportedSigningAlg(SigningAlgorithm), } impl fmt::Display for SignerError { @@ -33,6 +35,10 @@ impl fmt::Display for SignerError { SignerError::PermanentlyUnusable => write!(f, "Signer is unusable"), SignerError::Pkcs11Error(e) => write!(f, "PKCS#11 Error: {}", e), SignerError::TemporarilyUnavailable => write!(f, "Signer is unavailable"), + SignerError::UnsupportedSigningAlg(key_format) => match key_format { + SigningAlgorithm::RsaSha256 => write!(f, "Signing with RSA not supported"), + SigningAlgorithm::EcdsaP256Sha256 => write!(f, "Signing with EcdsaP256 not supported"), + }, } } } diff --git a/src/commons/crypto/signing/signers/kmip/signer.rs b/src/commons/crypto/signing/signers/kmip/signer.rs index 000cc824..e44cb8a7 100644 --- a/src/commons/crypto/signing/signers/kmip/signer.rs +++ b/src/commons/crypto/signing/signers/kmip/signer.rs @@ -21,8 +21,11 @@ use openssl::ssl::SslStream; use r2d2::PooledConnection; use rpki::{ - repository::crypto::signer::KeyError, - repository::crypto::{KeyIdentifier, PublicKey, PublicKeyFormat, Signature, SignatureAlgorithm, SigningError}, + crypto::signer::KeyError, + crypto::{ + KeyIdentifier, PublicKey, PublicKeyFormat, RpkiSignature, RpkiSignatureAlgorithm, Signature, + SignatureAlgorithm, SigningError, + }, }; use crate::commons::{ @@ -329,8 +332,12 @@ impl KmipSigner { &self, signer_private_key_id: &str, challenge: &D, - ) -> Result { - self.sign_with_key(signer_private_key_id, SignatureAlgorithm::default(), challenge.as_ref()) + ) -> Result { + self.sign_with_key( + signer_private_key_id, + RpkiSignatureAlgorithm::default(), + challenge.as_ref(), + ) } } @@ -782,12 +789,12 @@ impl KmipSigner { Ok(public_key) } - pub(super) fn sign_with_key( + pub(super) fn sign_with_key( &self, private_key_id: &str, - algorithm: SignatureAlgorithm, + algorithm: Alg, data: &[u8], - ) -> Result { + ) -> Result, SignerError> { if algorithm.public_key_format() != PublicKeyFormat::Rsa { return Err(SignerError::KmipError(format!( "Algorithm '{:?}' not supported", @@ -797,7 +804,7 @@ impl KmipSigner { let signed = self.with_conn("sign", |conn| conn.sign(&private_key_id, data))?; - let sig = Signature::new(SignatureAlgorithm::default(), Bytes::from(signed.signature_data)); + let sig = Signature::new(algorithm, Bytes::from(signed.signature_data)); Ok(sig) } @@ -913,12 +920,12 @@ impl KmipSigner { res } - pub fn sign + ?Sized>( + pub fn sign + ?Sized>( &self, key_id: &KeyIdentifier, - algorithm: SignatureAlgorithm, + algorithm: Alg, data: &D, - ) -> Result> { + ) -> Result, SigningError> { let kmip_key_pair_ids = self.lookup_kmip_key_ids(key_id)?; let signature = self @@ -933,11 +940,11 @@ impl KmipSigner { Ok(signature) } - pub fn sign_one_off + ?Sized>( + pub fn sign_one_off + ?Sized>( &self, - algorithm: SignatureAlgorithm, + algorithm: Alg, data: &D, - ) -> Result<(Signature, PublicKey), SignerError> { + ) -> Result<(Signature, PublicKey), SignerError> { // TODO: Is it possible to use a KMIP batch request to implement the create, activate, sign, deactivate, delete // in one round-trip to the server? let (key, kmip_key_pair_ids) = self.build_key(PublicKeyFormat::Rsa)?; diff --git a/src/commons/crypto/signing/signers/mocksigner.rs b/src/commons/crypto/signing/signers/mocksigner.rs index a5646987..0e9a795f 100644 --- a/src/commons/crypto/signing/signers/mocksigner.rs +++ b/src/commons/crypto/signing/signers/mocksigner.rs @@ -11,8 +11,11 @@ use openssl::{ }; use rpki::{ - repository::crypto::signer::KeyError, - repository::crypto::{KeyIdentifier, PublicKey, PublicKeyFormat, Signature, SignatureAlgorithm, SigningError}, + crypto::signer::KeyError, + crypto::{ + signer::SigningAlgorithm, KeyIdentifier, PublicKey, PublicKeyFormat, RpkiSignature, RpkiSignatureAlgorithm, + Signature, SignatureAlgorithm, SigningError, + }, }; use crate::commons::crypto::{dispatch::signerinfo::SignerMapper, SignerError, SignerHandle}; @@ -119,10 +122,19 @@ impl MockSigner { Ok((public_key, pkey, key_identifier, internal_id)) } - fn sign_with_key + ?Sized>(pkey: &PKey, challenge: &D) -> Result { + fn sign_with_key + ?Sized>( + alg: Alg, + pkey: &PKey, + challenge: &D, + ) -> Result, SignerError> { + let signing_algorithm = alg.signing_algorithm(); + if !matches!(signing_algorithm, SigningAlgorithm::RsaSha256) { + return Err(SignerError::UnsupportedSigningAlg(signing_algorithm).into()); + } + let mut signer = ::openssl::sign::Signer::new(MessageDigest::sha256(), &pkey)?; signer.update(challenge.as_ref())?; - let signature = Signature::new(SignatureAlgorithm::default(), Bytes::from(signer.sign_to_vec()?)); + let signature = Signature::new(alg, Bytes::from(signer.sign_to_vec()?)); Ok(signature) } @@ -161,7 +173,7 @@ impl MockSigner { &self, signer_private_key_id: &str, challenge: &D, - ) -> Result { + ) -> Result { self.inc_fn_call_count(FnIdx::SignRegistrationChallenge); if let Some(err_cb) = &self.sign_registration_challenge_error_cb { let _ = (err_cb)(&self.fn_call_counts)?; @@ -169,7 +181,7 @@ impl MockSigner { let pkey = self.load_key(signer_private_key_id).ok_or(SignerError::KeyNotFound)?; // sign the given data using the loaded private key - let signature = Self::sign_with_key(&pkey, challenge)?; + let signature = Self::sign_with_key(RpkiSignatureAlgorithm::default(), &pkey, challenge)?; // return the generated signature to the caller Ok(signature) @@ -233,26 +245,26 @@ impl MockSigner { Ok(()) } - pub fn sign + ?Sized>( + pub fn sign + ?Sized>( &self, key_identifier: &KeyIdentifier, - _algorithm: SignatureAlgorithm, + algorithm: Alg, data: &D, - ) -> Result> { + ) -> Result, SigningError> { self.inc_fn_call_count(FnIdx::Sign); let internal_id = self.internal_id_from_key_identifier(key_identifier)?; let pkey = self.load_key(&internal_id).ok_or(SignerError::KeyNotFound)?; - Self::sign_with_key(&pkey, data).map_err(|err| SigningError::Signer(err)) + Self::sign_with_key(algorithm, &pkey, data).map_err(|err| SigningError::Signer(err)) } - pub fn sign_one_off + ?Sized>( + pub fn sign_one_off + ?Sized>( &self, - _algorithm: SignatureAlgorithm, + algorithm: Alg, data: &D, - ) -> Result<(Signature, PublicKey), SignerError> { + ) -> Result<(Signature, PublicKey), SignerError> { self.inc_fn_call_count(FnIdx::SignOneOff); let (public_key, pkey, _, internal_id) = self.build_key().unwrap(); - let signature = Self::sign_with_key(&pkey, data).unwrap(); + let signature = Self::sign_with_key(algorithm, &pkey, data).unwrap(); let _ = self.keys.write().unwrap().remove(&internal_id); Ok((signature, public_key)) } diff --git a/src/commons/crypto/signing/signers/pkcs11/signer.rs b/src/commons/crypto/signing/signers/pkcs11/signer.rs index 9a7abf98..26e9338a 100644 --- a/src/commons/crypto/signing/signers/pkcs11/signer.rs +++ b/src/commons/crypto/signing/signers/pkcs11/signer.rs @@ -20,8 +20,11 @@ use cryptoki::{ }; use rpki::{ - repository::crypto::signer::KeyError, - repository::crypto::{KeyIdentifier, PublicKey, PublicKeyFormat, Signature, SignatureAlgorithm, SigningError}, + crypto::signer::KeyError, + crypto::{ + KeyIdentifier, PublicKey, PublicKeyFormat, RpkiSignature, RpkiSignatureAlgorithm, Signature, + SignatureAlgorithm, SigningError, + }, }; use crate::commons::crypto::{ @@ -243,14 +246,14 @@ impl Pkcs11Signer { &self, key_id: &str, challenge: &D, - ) -> Result { + ) -> Result { let priv_handle = self .find_key(key_id, ObjectClass::PRIVATE_KEY) .map_err(|err| match err { KeyError::KeyNotFound => SignerError::KeyNotFound, KeyError::Signer(err) => err, })?; - self.sign_with_key(priv_handle, SignatureAlgorithm::default(), challenge.as_ref()) + self.sign_with_key(priv_handle, RpkiSignatureAlgorithm::default(), challenge.as_ref()) } } @@ -633,7 +636,7 @@ impl Pkcs11Signer { impl Pkcs11Signer { pub(super) fn remember_key_id( &self, - key_id: &rpki::repository::crypto::KeyIdentifier, + key_id: &rpki::crypto::KeyIdentifier, internal_key_id: String, ) -> Result<(), SignerError> { let readable_handle = self.handle.read().unwrap(); @@ -765,12 +768,12 @@ impl Pkcs11Signer { Ok(public_key) } - pub(super) fn sign_with_key( + pub(super) fn sign_with_key( &self, private_key_handle: ObjectHandle, - algorithm: SignatureAlgorithm, + algorithm: Alg, data: &[u8], - ) -> Result { + ) -> Result, SignerError> { if algorithm.public_key_format() != PublicKeyFormat::Rsa { return Err(SignerError::KmipError(format!( "Algorithm '{:?}' not supported", @@ -798,7 +801,7 @@ impl Pkcs11Signer { let signature_data = self.with_conn("sign", |conn| conn.sign(&mechanism, private_key_handle, data))?; - let sig = Signature::new(SignatureAlgorithm::default(), Bytes::from(signature_data)); + let sig = Signature::new(algorithm, Bytes::from(signature_data)); Ok(sig) } @@ -918,12 +921,12 @@ impl Pkcs11Signer { res } - pub fn sign + ?Sized>( + pub fn sign + ?Sized>( &self, key_id: &KeyIdentifier, - algorithm: SignatureAlgorithm, + algorithm: Alg, data: &D, - ) -> Result> { + ) -> Result, SigningError> { let internal_key_id = self.lookup_key_id(key_id)?; let priv_handle = self .find_key(&internal_key_id, ObjectClass::PRIVATE_KEY) @@ -936,11 +939,11 @@ impl Pkcs11Signer { .map_err(|err| SigningError::Signer(err)) } - pub fn sign_one_off + ?Sized>( + pub fn sign_one_off + ?Sized>( &self, - algorithm: SignatureAlgorithm, + algorithm: Alg, data: &D, - ) -> Result<(Signature, PublicKey), SignerError> { + ) -> Result<(Signature, PublicKey), SignerError> { let (key, pub_handle, priv_handle, _) = self.build_key(PublicKeyFormat::Rsa)?; let signature_res = self diff --git a/src/commons/crypto/signing/signers/probe.rs b/src/commons/crypto/signing/signers/probe.rs index 71ce98ee..cf2295d3 100644 --- a/src/commons/crypto/signing/signers/probe.rs +++ b/src/commons/crypto/signing/signers/probe.rs @@ -328,19 +328,19 @@ pub mod tests { assert_eq!(None, conn.last_probe_time()?); // The first call to .get() should trigger a probe - let _ = conn.status(|_, _| Err(ProbeError::AwaitingNextProbe)); + let _probe1 = conn.status(|_, _| Err(ProbeError::AwaitingNextProbe)); let t1 = conn.last_probe_time()?; assert!(t1.is_some()); // A call to .get() before the next probe interval should NOT result in an updated last probe time std::thread::sleep(Duration::from_millis(10)); - let _ = conn.status(|_, _| Err(ProbeError::AwaitingNextProbe)); + let _probe2 = conn.status(|_, _| Err(ProbeError::AwaitingNextProbe)); let t2 = conn.last_probe_time()?; assert!(t2 == t1); // A call to .get() after the next probe interval SHOULD result in an updated last probe time std::thread::sleep(Duration::from_millis(200)); - let _ = conn.status(|_, _| Err(ProbeError::AwaitingNextProbe)); + let _probe3 = conn.status(|_, _| Err(ProbeError::AwaitingNextProbe)); let t3 = conn.last_probe_time()?; assert!(t3 > t1); diff --git a/src/commons/crypto/signing/signers/softsigner.rs b/src/commons/crypto/signing/signers/softsigner.rs index 3460876a..8f31f226 100644 --- a/src/commons/crypto/signing/signers/softsigner.rs +++ b/src/commons/crypto/signing/signers/softsigner.rs @@ -19,8 +19,10 @@ use openssl::{ rsa::Rsa, }; -use rpki::repository::crypto::{ - signer::KeyError, KeyIdentifier, PublicKey, PublicKeyFormat, Signature, SignatureAlgorithm, SigningError, +use rpki::crypto::{ + signer::{KeyError, SigningAlgorithm}, + KeyIdentifier, PublicKey, PublicKeyFormat, RpkiSignature, RpkiSignatureAlgorithm, Signature, SignatureAlgorithm, + SigningError, }; use crate::{ @@ -112,10 +114,10 @@ impl OpenSslSigner { &self, signer_private_key_id: &str, challenge: &D, - ) -> Result { + ) -> Result { let key_id = KeyIdentifier::from_str(signer_private_key_id).map_err(|_| SignerError::KeyNotFound)?; let key_pair = self.load_key(&key_id)?; - let signature = Self::sign_with_key(key_pair.pkey.as_ref(), challenge)?; + let signature = Self::sign_with_key(key_pair.pkey.as_ref(), RpkiSignatureAlgorithm::default(), challenge)?; Ok(signature) } } @@ -165,11 +167,20 @@ impl OpenSslSigner { Ok(key_id) } - fn sign_with_key + ?Sized>(pkey: &PKeyRef, data: &D) -> Result { + fn sign_with_key + ?Sized>( + pkey: &PKeyRef, + algorithm: Alg, + data: &D, + ) -> Result, SignerError> { + let signing_algorithm = algorithm.signing_algorithm(); + if !matches!(signing_algorithm, SigningAlgorithm::RsaSha256) { + return Err(SignerError::UnsupportedSigningAlg(signing_algorithm)); + } + let mut signer = ::openssl::sign::Signer::new(MessageDigest::sha256(), pkey)?; signer.update(data.as_ref())?; - let signature = Signature::new(SignatureAlgorithm::default(), Bytes::from(signer.sign_to_vec()?)); + let signature = Signature::new(algorithm, Bytes::from(signer.sign_to_vec()?)); Ok(signature) } @@ -238,25 +249,23 @@ impl OpenSslSigner { Ok(()) } - pub fn sign + ?Sized>( + pub fn sign + ?Sized>( &self, key_id: &KeyIdentifier, - _algorithm: SignatureAlgorithm, + algorithm: Alg, data: &D, - ) -> Result> { + ) -> Result, SigningError> { let key_pair = self.load_key(key_id)?; - Self::sign_with_key(key_pair.pkey.as_ref(), data).map_err(SigningError::Signer) + Self::sign_with_key(key_pair.pkey.as_ref(), algorithm, data).map_err(SigningError::Signer) } - pub fn sign_one_off + ?Sized>( + pub fn sign_one_off + ?Sized>( &self, - _algorithm: SignatureAlgorithm, + algorithm: Alg, data: &D, - ) -> Result<(Signature, PublicKey), SignerError> { + ) -> Result<(Signature, PublicKey), SignerError> { let kp = OpenSslKeyPair::build()?; - - let signature = Self::sign_with_key(kp.pkey.as_ref(), data)?; - + let signature = Self::sign_with_key(kp.pkey.as_ref(), algorithm, data)?; let key = kp.subject_public_key_info()?; Ok((signature, key)) diff --git a/src/commons/error.rs b/src/commons/error.rs index 4b5c5105..7fbbbc65 100644 --- a/src/commons/error.rs +++ b/src/commons/error.rs @@ -11,7 +11,8 @@ use rpki::{ provisioning::ResourceClassName, publication, }, - repository::{crypto::KeyIdentifier, x509::ValidationError}, + crypto::KeyIdentifier, + repository::x509::ValidationError, uri, }; @@ -26,6 +27,8 @@ use crate::{ upgrades::PrepareUpgradeError, }; +use super::api::{BgpSecAsnKey, BgpSecDefinition}; + //------------ RoaDeltaError ----------------------------------------------- /// This type contains a detailed error report for a ROA delta @@ -273,6 +276,13 @@ pub enum Error { AspaProvidersUpdateEmpty(CaHandle, AspaCustomer), AspaProvidersUpdateConflict(CaHandle, AspaProvidersUpdateConflict), + //----------------------------------------------------------------- + // BGP Sec + //----------------------------------------------------------------- + BgpSecDefinitionUnknown(CaHandle, BgpSecAsnKey), + BgpSecDefinitionInvalidlySigned(CaHandle, BgpSecDefinition), + BgpSecDefinitionNotEntitled(CaHandle, BgpSecAsnKey), + //----------------------------------------------------------------- // Key Usage Issues //----------------------------------------------------------------- @@ -442,6 +452,14 @@ impl fmt::Display for Error { Error::AspaCustomerUnknown(_ca, asn) => write!(f, "No current ASPA exists for customer AS '{}'", asn), Error::AspaProvidersUpdateEmpty(_ca, asn) => write!(f, "Received empty update for ASPA for customer AS '{}'", asn), Error::AspaProvidersUpdateConflict(_ca, e) => write!(f, "ASPA delta rejected:\n\n'{}'", e), + + //----------------------------------------------------------------- + // BGPSec + //----------------------------------------------------------------- + Error::BgpSecDefinitionUnknown(_ca, key) => write!(f, "Cannot remove BGPSec CSR for unknown combination of ASN '{}' and key '{}'", key.asn(), key.key_identifier()), + Error::BgpSecDefinitionInvalidlySigned(_ca, def) => write!(f, "Invalidly signed BGPSec CSR remove BGPSec CSR for ASN '{}' and key '{}'", def.asn(), def.csr().public_key().key_identifier()), + Error::BgpSecDefinitionNotEntitled(_ca, key) => write!(f, "AS '{}' is not held by you", key.asn()), + //----------------------------------------------------------------- // Key Usage Issues @@ -830,6 +848,22 @@ impl Error { .with_ca(ca) .with_aspa_providers_conflict(conflict), + //----------------------------------------------------------------- + // BGP Sec + //----------------------------------------------------------------- + Error::BgpSecDefinitionUnknown(ca, key) => ErrorResponse::new("ca-bgpsec-unknown", &self) + .with_ca(ca) + .with_asn(key.asn()) + .with_key_identifier(&key.key_identifier()), + Error::BgpSecDefinitionInvalidlySigned(ca, def) => ErrorResponse::new("ca-bgpsec-invalidly-signed", &self) + .with_ca(ca) + .with_asn(def.asn()) + .with_key_identifier(&def.csr().public_key().key_identifier()) + .with_bgpsec_csr(def.csr()), + Error::BgpSecDefinitionNotEntitled(ca, key) => ErrorResponse::new("ca-bgpsec-not-entitled", &self) + .with_ca(ca) + .with_asn(key.asn()), + //----------------------------------------------------------------- // Key Usage Issues (key-*) //----------------------------------------------------------------- diff --git a/src/commons/util/ext_serde.rs b/src/commons/util/ext_serde.rs index e38e05b6..7294bbab 100644 --- a/src/commons/util/ext_serde.rs +++ b/src/commons/util/ext_serde.rs @@ -9,9 +9,9 @@ use log::LevelFilter; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use syslog::Facility; -use rpki::repository::{ +use rpki::{ crypto::PublicKey, - resources::{AsBlocks, IpBlocks}, + repository::resources::{AsBlocks, IpBlocks}, }; //------------ Bytes --------------------------------------------------------- diff --git a/src/commons/util/mod.rs b/src/commons/util/mod.rs index dd8aa245..323279fa 100644 --- a/src/commons/util/mod.rs +++ b/src/commons/util/mod.rs @@ -5,7 +5,7 @@ use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use bytes::Bytes; use rpki::{ - repository::crypto::DigestAlgorithm, + crypto::DigestAlgorithm, uri::{Https, Rsync}, }; diff --git a/src/daemon/auth/common/permissions.rs b/src/daemon/auth/common/permissions.rs index f5f2809f..6e61e1af 100644 --- a/src/daemon/auth/common/permissions.rs +++ b/src/daemon/auth/common/permissions.rs @@ -70,6 +70,8 @@ iterable_enum! { ASPAS_READ, ASPAS_UPDATE, ASPAS_ANALYSIS, + BGPSEC_READ, + BGPSEC_UPDATE, RTA_LIST, RTA_READ, RTA_UPDATE diff --git a/src/daemon/ca/bgpsec.rs b/src/daemon/ca/bgpsec.rs new file mode 100644 index 00000000..12956b44 --- /dev/null +++ b/src/daemon/ca/bgpsec.rs @@ -0,0 +1,318 @@ +use std::collections::HashMap; + +use rpki::{ + ca::{csr::BgpsecCsr, publication::Base64}, + crypto::PublicKey, + repository::{ + cert::{ExtendedKeyUsage, KeyUsage, Overclaim, TbsCert}, + resources::Asn, + x509::{Serial, Time}, + Cert, + }, +}; + +use crate::{ + commons::{ + api::{BgpSecAsnKey, BgpSecCsrInfo, BgpSecCsrInfoList}, + crypto::{KrillSigner, SignSupport}, + KrillResult, + }, + daemon::config::{Config, IssuanceTimingConfig}, +}; + +use super::{BgpSecCertificateUpdates, CertifiedKey}; + +//------------ BgpSecCertificates ------------------------------------------ + +/// The issued BGPSec certificates under a resource class in a CA. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct BgpSecCertificates(HashMap); + +impl BgpSecCertificates { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + /// Update issued BGPSec certificates + /// + /// Will issue new BGPSec certificates for definitions using the resources of + /// this certified key which did not yet exist. + /// + /// Will remove any existing BGPSec certificates which: + /// - are no longer present in the definitions; or + /// - for which the certified key no longer holds the asn. + /// + /// + /// Note that we pass in ALL BGPSec definitions, including definitions that may only + /// be eligible under another owning RC. + pub fn update( + &self, + definitions: &BgpSecDefinitions, + certified_key: &CertifiedKey, + config: &Config, + signer: &KrillSigner, + ) -> KrillResult { + let mut updates = BgpSecCertificateUpdates::default(); + + let resources = certified_key.incoming_cert().resources(); + let issuance_timing = &config.issuance_timing; + + // Issue BGPSec certificates for any ASN held by the certified key + // for which the required router key has not yet been certified. + for (key, csr) in definitions + .iter() + .filter(|(k, _)| !self.0.contains_key(k) && resources.contains_asn(k.asn())) + { + // resource held here, but BGPSec certificate was not yet issued. + let cert = self.make_bgpsec_cert(key.asn(), csr.key().clone(), certified_key, issuance_timing, signer)?; + updates.add_updated(cert); + } + + // Will remove any existing BGPSec certificates which: + // - are no longer present in the definitions; or + // - for which the certified key no longer holds the asn. + for (key, _) in self + .0 + .iter() + .filter(|(k, _)| !definitions.has(k) || !resources.contains_asn(k.asn())) + { + updates.add_removed(*key); + } + + Ok(updates) + } + + /// Re-new BGPSec certificates + /// + /// Used to renew certificates which would expire, in which case the renew_threshold + /// should be specified. Or, to re-issue all existing certificates during a key rollover + /// activation of a new certified_key - in which case the renew_threshold is expected to + /// be None, and the certified_key is expected to have changed. + pub fn renew( + &self, + certified_key: &CertifiedKey, + renew_threshold: Option