skytool 0.1.0-pre.2

an experimental API client for BlueSky / ATProto
Documentation
use crate::{input, options::output};
use camino::{Utf8Path, Utf8PathBuf};
use clap::{Args, Parser, ValueEnum};
use rand_chacha::ChaCha20Rng;
use rand_core::{CryptoRngCore, SeedableRng};
use rand_hc::Hc128Rng;
use rand_seeder::Seeder;

#[derive(Debug, thiserror::Error, miette::Diagnostic)]
#[error(transparent)]
#[remain::sorted]
pub(crate) enum Error {
  EllipticCurve(#[from] elliptic_curve::Error),
  Input(#[from] input::Error),
  Io(#[from] std::io::Error),
}

#[derive(Debug, Clone, clap::Parser)]
pub(crate) struct Command {
  #[clap(flatten)]
  input: input::InputOrOptions<Options>,
  #[command(flatten)]
  output_file: output::OutputFile,
}

impl crate::Output for Options {}

#[derive(Debug, Clone, clap::Parser, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
#[command(about = "generates private keys")]
#[group(id = "key")]
#[remain::sorted]
#[rustfmt::skip]
#[schemars(rename = "KeyGenerationOptions")]
pub(crate) struct Options {
  #[arg(
    env  = "KEY_CURVE",
    long = "key-curve",
    id   = "key.curve",
    value_name    = "CURVE",
    default_value = "k256"
  )]
  curve: Curve,
  #[arg(
    env  = "OUTPUT_FORMAT",
    long = "output-format",
    id   = "output.format",
    value_name    = "FORMAT",
    default_value = "jwk",
    help_heading = "Output Options",
  )]
  format: Format,
  #[command(flatten)]
  rng: crate::options::rng::RngOptions,
}

impl Command {
  pub async fn run(self, valkey: &crate::integrations::fred::ValkeyOptions) -> Result<Box<dyn crate::Output>, Error> {
    let Self {
      input,
      output_file: output::OutputFile { path },
    } = self;

    let Options { curve, format, rng, .. } = input.consume()?;

    let mut rng = rng.to_crypto_rng();

    let key = SecretKey::new(&mut rng, &curve);

    let contents = format.encode(&key);

    match path {
      Some(path) => fs_err::write(path, contents)?,
      None => println!("{contents}"),
    }

    Ok(Box::new(()))
  }
}

#[derive(Debug, Clone, Copy, Default, clap::ValueEnum, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
#[clap(rename_all = "lowercase")]
#[remain::sorted]
pub enum Curve {
  #[default]
  K256,
  P256,
}

#[derive(Debug, Clone, Copy, Default, clap::ValueEnum, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
#[clap(rename_all = "lowercase")]
#[remain::sorted]
pub enum Format {
  #[default]
  Jwk,
}

impl Format {
  fn encode(&self, key: &SecretKey) -> String {
    match self {
      Self::Jwk => match key {
        SecretKey::K256(key) => key.to_jwk_string().to_string(),
        SecretKey::P256(key) => key.to_jwk_string().to_string(),
      },
    }
  }
}

#[derive(Debug, Clone)]
#[remain::sorted]
pub enum SecretKey {
  K256(k256::SecretKey),
  P256(p256::SecretKey),
}

impl SecretKey {
  fn new(rng: &mut impl CryptoRngCore, curve: &Curve) -> Self {
    match curve {
      Curve::K256 => Self::K256(k256::SecretKey::random(rng)),
      Curve::P256 => Self::P256(p256::SecretKey::random(rng)),
    }
  }
}