use std::{error, fmt, fs, io};
use std::convert::Infallible;
use std::str::FromStr;
use rpki::ca::csr::BgpsecCsr;
use rpki::crypto::{KeyIdentifier, SignatureVerificationError};
use rpki::dep::bcder::decode::DecodeError;
use rpki::repository::resources::Asn;
use crate::cli::client::KrillClient;
use crate::cli::report::Report;
use crate::api;
use crate::commons::httpclient;
use super::ca;
#[derive(clap::Subcommand)]
#[allow(clippy::large_enum_variant)]
pub enum Command {
List(List),
Add(Add),
Remove(Remove),
}
impl Command {
pub async fn run(self, client: &KrillClient) -> Report {
match self {
Self::List(cmd) => cmd.run(client).await.into(),
Self::Add(cmd) => cmd.run(client).await.into(),
Self::Remove(cmd) => cmd.run(client).await.into(),
}
}
}
#[derive(clap::Args)]
pub struct List {
#[command(flatten)]
ca: ca::Handle,
}
impl List {
async fn run(
self, client: &KrillClient
) -> Result<api::bgpsec::BgpSecCsrInfoList, httpclient::Error> {
client.bgpsec_list(&self.ca.ca).await
}
}
#[derive(clap::Parser)]
pub struct Add {
#[command(flatten)]
ca: ca::Handle,
#[arg(long, short, value_name = "ASN")]
asn: Asn,
#[arg(long, value_name = "path")]
csr: BgpsecCsrFile,
}
impl Add {
async fn run(
self, client: &KrillClient
) -> Result<api::status::Success, httpclient::Error> {
client.bgpsec_update(
&self.ca.ca,
api::bgpsec::BgpSecDefinitionUpdates {
add: vec![
api::bgpsec::BgpSecDefinition {
asn: self.asn,
csr: self.csr.0,
}
],
remove: Vec::new(),
}
).await
}
}
#[derive(clap::Parser)]
pub struct Remove {
#[command(flatten)]
ca: ca::Handle,
#[arg(long, short, value_name = "ASN")]
asn: Asn,
#[arg(long, value_name = "key identifier")]
key: KeyIdentifier,
}
impl Remove {
async fn run(
self, client: &KrillClient
) -> Result<api::status::Success, httpclient::Error> {
client.bgpsec_update(
&self.ca.ca,
api::bgpsec::BgpSecDefinitionUpdates {
add: Vec::new(),
remove: vec![
api::bgpsec::BgpSecAsnKey {
asn: self.asn, key: self.key
}
],
}
).await
}
}
#[derive(Clone, Debug)]
struct BgpsecCsrFile(BgpsecCsr);
impl FromStr for BgpsecCsrFile {
type Err = BgpsecCsrFileError;
fn from_str(path: &str) -> Result<Self, Self::Err> {
let csr = BgpsecCsr::decode(
fs::read(path).map_err(|err| {
BgpsecCsrFileError::Io(path.into(), err)
})?.as_slice()
).map_err(|err| BgpsecCsrFileError::Decode(path.into(), err))?;
csr.verify_signature().map_err(|err| {
BgpsecCsrFileError::Verify(path.into(), err)
})?;
Ok(Self(csr))
}
}
#[derive(Debug)]
enum BgpsecCsrFileError {
Io(String, io::Error),
Decode(String, DecodeError<Infallible>),
Verify(String, SignatureVerificationError),
}
impl fmt::Display for BgpsecCsrFileError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Io(path, err) => {
write!(f, "Failed to read CSR file '{path}': {err}")
}
Self::Decode(path, err) => {
write!(f, "Failed to parse CSR file '{path}': {err}")
}
Self::Verify(path, err) => {
write!(
f, "Failed to verify signature in CSR file '{path}': {err}"
)
}
}
}
}
impl error::Error for BgpsecCsrFileError { }