use std::{
io::{Read, stdin},
path::{Path, PathBuf},
};
use clap::Parser;
use fs_err::read;
use ts_crypto::{SigningKey, VerifyingKey};
use ts_error_stack::Report;
use ts_token::JsonWebKey;
#[derive(Parser)]
#[command(version, about = "CLI for working with JSON web keys", long_about = None)]
pub struct Args {
#[arg(short, long)]
alg: Option<String>,
key_path: PathBuf,
}
fn main() -> Result<(), Report<CliError>> {
let Args { alg, key_path } = Args::parse();
let alg: Option<&str> = alg.as_deref();
let pem = if key_path == Path::new("-") {
let mut buf = Vec::new();
stdin()
.read_to_end(&mut buf)
.map_err(|source| CreateError::ReadStdin { source })?;
buf
} else {
read(key_path).map_err(|source| CreateError::ReadKeyFile { source })?
};
let key = if let Some(key) = VerifyingKey::from_pem(&pem) {
key
} else if let Some(key) = SigningKey::from_pem(&pem) {
key.verifying_key()
} else {
return Err(CreateError::CannotParseKey.into());
};
let mut jwk = JsonWebKey::from(&key);
if let Some(alg) = alg {
jwk.alg = alg.to_string();
}
let key_json = serde_json::to_string_pretty(&jwk)
.map_err(|source| CreateError::SerializeKey { source })?;
println!("{key_json}");
Ok(())
}
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[error("creating the JSON web key failed")]
pub struct CliError(#[source] CreateError);
impl From<CreateError> for CliError {
fn from(value: CreateError) -> Self {
Self(value)
}
}
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum CreateError {
#[error("the key could not be read from stdin")]
ReadStdin { source: std::io::Error },
#[error("the key file could not be read")]
ReadKeyFile { source: std::io::Error },
#[error("the key couldn't be parsed, ensure it is PEM encoded and a supported type")]
CannotParseKey,
#[error("the JSON serialization failed")]
SerializeKey { source: serde_json::Error },
}
impl From<CreateError> for Report<CliError> {
fn from(value: CreateError) -> Self {
Self::from(CliError::from(value))
}
}