use std::path::PathBuf;
use clap::Args;
use ed25519_dalek::VerifyingKey;
use onomancy_core::anchor::doc::DocAnchor;
use onomancy_dnssec::{
certificate::Certificate, dns_name::DnsName, txt::generation_key::GenerationKey,
};
use onomancy_keyhive::{authority::KeyhiveAuthority, mint};
use onomancy_publish::{ceremony::rotate::Rotate as RotateCeremony, signer::Signer};
use crate::{
now_ms, plan_io,
seed::{self, SeedError},
};
#[derive(Debug, Args)]
pub(crate) struct Rotate {
#[arg(long)]
hostname: String,
#[arg(long)]
doc_key: Option<PathBuf>,
#[arg(long, conflicts_with = "doc_key")]
doc_seed: Option<String>,
#[arg(long)]
replaced: String,
#[arg(long)]
successor_key: Option<PathBuf>,
#[arg(long, conflicts_with = "successor_key")]
successor_seed: Option<String>,
#[arg(long)]
prior_cert: Option<PathBuf>,
#[arg(long, default_value = ".")]
out_dir: PathBuf,
}
impl Rotate {
pub(crate) fn run(&self) -> Result<(), RotateError> {
let hostname = DnsName::parse_display(&self.hostname)?;
let doc_key = seed::load(self.doc_seed.as_deref(), self.doc_key.as_deref())?;
let successor = seed::load(
self.successor_seed.as_deref(),
self.successor_key.as_deref(),
)?;
let replaced = parse_generation(&self.replaced)?;
let prior_lineage = match &self.prior_cert {
Some(path) => Certificate::decode(&std::fs::read(path)?)?
.lineage()
.to_vec(),
None => vec![],
};
let carriage = mint::generation_carriage(&doc_key, &successor)?;
let plan = RotateCeremony {
hostname,
document: DocAnchor::from(doc_key.verifying_key()),
replaced,
prior_lineage,
carriage,
}
.plan(
now_ms(),
&Signer::new(successor),
&Signer::new(doc_key),
&KeyhiveAuthority,
)?;
crate::block_on(plan_io::execute(&plan, &self.out_dir))??;
Ok(())
}
}
pub(crate) fn parse_generation(text: &str) -> Result<GenerationKey, NotAGenerationKey> {
let bytes = plan_io::parse_base64_key(text).ok_or(NotAGenerationKey)?;
let key = VerifyingKey::from_bytes(&bytes).map_err(|_| NotAGenerationKey)?;
Ok(GenerationKey::from(key))
}
#[derive(Debug, thiserror::Error)]
#[error("expected a generation key in its TXT g= spelling (base64 ed25519 point)")]
pub(crate) struct NotAGenerationKey;
#[derive(Debug, thiserror::Error)]
pub(crate) enum RotateError {
#[error(transparent)]
Ceremony(#[from] onomancy_publish::ceremony::CeremonyError),
#[error(transparent)]
Mint(#[from] onomancy_keyhive::mint::MintError),
#[error("prior certificate: {0}")]
Certificate(#[from] onomancy_dnssec::certificate::DecodeCertificateError),
#[error(transparent)]
Generation(#[from] NotAGenerationKey),
#[error("hostname: {0}")]
Hostname(#[from] onomancy_dnssec::dns_name::ParseDnsNameError),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Seed(#[from] SeedError),
}