From 42d2705a8464e05bce66e9b0821132c2616160b3 Mon Sep 17 00:00:00 2001 From: "Z. Charles Dziura" Date: Tue, 18 Mar 2025 13:45:11 -0400 Subject: [PATCH] Include the key-rotater project in the monorepo --- key-rotater/.gitignore | 1 + key-rotater/Cargo.toml | 10 ++++++ key-rotater/README.md | 9 +++++ key-rotater/src/main.rs | 80 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 key-rotater/.gitignore create mode 100644 key-rotater/Cargo.toml create mode 100644 key-rotater/README.md create mode 100644 key-rotater/src/main.rs diff --git a/key-rotater/.gitignore b/key-rotater/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/key-rotater/.gitignore @@ -0,0 +1 @@ +/target diff --git a/key-rotater/Cargo.toml b/key-rotater/Cargo.toml new file mode 100644 index 0000000..5b0c58a --- /dev/null +++ b/key-rotater/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "debt-pirate-key-rotater" +version = "0.1.0" +edition = "2021" + +[dependencies] +base64 = "0.22.1" +clap = { version = "4.5", features = ["derive"] } +humantime = "2.1" +pasetors = "0.7" diff --git a/key-rotater/README.md b/key-rotater/README.md new file mode 100644 index 0000000..5d163ae --- /dev/null +++ b/key-rotater/README.md @@ -0,0 +1,9 @@ +# Debt Pirate Key Rotater + +Creates new PASETO keys for use in the API + +## Example + +``` +$ dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | xargs cargo run -- +``` diff --git a/key-rotater/src/main.rs b/key-rotater/src/main.rs new file mode 100644 index 0000000..ca06352 --- /dev/null +++ b/key-rotater/src/main.rs @@ -0,0 +1,80 @@ +use std::{ + env, + io::{self, BufRead, Write}, + process, + time::{Duration, SystemTime}, +}; + +use base64::prelude::*; +use humantime::format_rfc3339_seconds; +use pasetors::{ + keys::SymmetricKey, + paserk::{FormatAsPaserk, Id}, + version4::V4, +}; + +fn main() { + let encoded_secret = read_secret(); + let secret = if let Ok(secret) = decode_secret(encoded_secret) { + secret + } else { + eprintln!("Not a valid 32-byte, base64 encoded string."); + process::exit(1); + }; + + let (id, key) = generate_local_key(secret.as_slice()); + let mut id_string = String::new(); + id.fmt(&mut id_string).unwrap(); + + let one_year: Duration = Duration::from_secs_f64(60_f64 * 60_f64 * 24_f64 * 365.25_f64); + let expiration = format_rfc3339_seconds(SystemTime::now() + one_year); + + println!("ID: {id_string}\nKey: {key}\nExpiration: {expiration}"); +} + +fn read_secret() -> String { + let secret_arg = env::args().skip(1).last(); + + let secret = if let Some(secret) = secret_arg { + secret + } else { + print!("Enter a 32-byte, base64 encoded string: "); + let _ = io::stdout().flush(); + + let mut buffer = String::with_capacity(32); + let stdin = io::stdin(); + + stdin.lock().read_line(&mut buffer).unwrap(); + buffer + .strip_suffix("\r\n") + .or(buffer.strip_suffix("\n")) + .map(|buffer| buffer.to_owned()) + .unwrap_or(buffer) + }; + + secret +} + +fn decode_secret(secret: String) -> Result, ()> { + BASE64_STANDARD + .decode(secret) + .map_err(|_| ()) + .and_then(|secret| { + if secret.len() != 32 { + Err(()) + } else { + Ok(secret) + } + }) +} + +fn generate_local_key(secret: &[u8]) -> (Id, String) { + let key = SymmetricKey::::from(secret).unwrap(); + + let mut serialized_key = String::with_capacity(54); + let _ = key.fmt(&mut serialized_key); + + let id = Id::from(&key); + + (id, serialized_key) +}