Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm
at main 3.0 kB view raw
1use elliptic_curve::SecretKey; 2use jose_jwk::{Class, Jwk, Key, Parameters}; 3use jsonwebtoken::{Algorithm, EncodingKey, Header, encode, errors::Error as JWTError}; 4use pkcs8::DecodePrivateKey; 5use serde::Serialize; 6use std::fs; 7use std::io::Error as IOError; 8use std::path::Path; 9use std::string::FromUtf8Error; 10use std::time::{Duration, SystemTime, UNIX_EPOCH}; 11use thiserror::Error; 12 13#[derive(Debug, Error)] 14pub enum TokensSetupError { 15 #[error("failed to read private key")] 16 ReadPrivateKey(IOError), 17 #[error("failed to retrieve private key: {0}")] 18 PrivateKey(JWTError), 19 #[error("failed to read private key")] 20 ReadJwks(IOError), 21 #[error("failed to retrieve jwks: {0}")] 22 DecodeJwks(FromUtf8Error), 23} 24 25#[derive(Debug, Error)] 26pub enum TokenMintingError { 27 #[error("failed to mint: {0}")] 28 EncodingError(#[from] JWTError), 29} 30 31pub struct Tokens { 32 encoding_key: EncodingKey, 33 jwk: Jwk, 34} 35 36impl Tokens { 37 pub fn from_files(priv_f: impl AsRef<Path>) -> Result<Self, TokensSetupError> { 38 let private_key_data: Vec<u8> = 39 fs::read(priv_f).map_err(TokensSetupError::ReadPrivateKey)?; 40 let encoding_key = 41 EncodingKey::from_ec_pem(&private_key_data).map_err(TokensSetupError::PrivateKey)?; 42 43 let jwk_key_string = String::from_utf8(private_key_data).unwrap(); 44 let mut jwk = SecretKey::<p256::NistP256>::from_pkcs8_pem(&jwk_key_string) 45 .map(|secret_key| Jwk { 46 key: Key::from(&secret_key.into()), 47 prm: Parameters { 48 kid: Some("who-am-i-00".to_string()), 49 cls: Some(Class::Signing), 50 ..Default::default() 51 }, 52 }) 53 .expect("to get private key"); 54 55 // CRITICAL: this is what turns the private jwk into a public one: the 56 // `d` parameter is the secret for an EC key; a pubkey just has no `d`. 57 // 58 // this feels baaaadd but hey we're just copying atrium 59 // https://github.com/atrium-rs/atrium/blob/b48810f84d83d037ee89b79b8566df9e0f2a6dae/atrium-oauth/src/keyset.rs#L41 60 let Key::Ec(ref mut ec) = jwk.key else { 61 unimplemented!() 62 }; 63 ec.d = None; // CRITICAL 64 65 Ok(Self { encoding_key, jwk }) 66 } 67 68 pub fn mint(&self, t: impl ToString) -> Result<String, TokenMintingError> { 69 let sub = t.to_string(); 70 71 let dt_now = SystemTime::now() 72 .duration_since(UNIX_EPOCH) 73 .expect("unix epoch is in the past"); 74 let dt_exp = dt_now + Duration::from_secs(30 * 86_400); 75 let exp = dt_exp.as_secs(); 76 77 let mut header = Header::new(Algorithm::ES256); 78 header.kid = Some("who-am-i-00".to_string()); 79 // todo: consider setting jku? 80 81 Ok(encode(&header, &Claims { sub, exp }, &self.encoding_key)?) 82 } 83 84 pub fn jwk(&self) -> Jwk { 85 self.jwk.clone() 86 } 87} 88 89#[derive(Debug, Serialize)] 90struct Claims { 91 sub: String, 92 exp: u64, 93}