A better Rust ATProto crate
1use base64::Engine; 2use base64::engine::general_purpose::URL_SAFE_NO_PAD; 3use elliptic_curve::SecretKey; 4use jacquard_common::CowStr; 5use jose_jwk::{Key, crypto}; 6use rand::{CryptoRng, RngCore, rngs::ThreadRng}; 7use sha2::{Digest, Sha256}; 8use std::cmp::Ordering; 9 10use crate::{FALLBACK_ALG, types::OAuthAuthorizationServerMetadata}; 11 12pub fn generate_key(allowed_algos: &[CowStr]) -> Option<Key> { 13 for alg in allowed_algos { 14 #[allow(clippy::single_match)] 15 match alg.as_ref() { 16 "ES256" => { 17 return Some(Key::from(&crypto::Key::from( 18 SecretKey::<p256::NistP256>::random(&mut ThreadRng::default()), 19 ))); 20 } 21 _ => { 22 // TODO: Implement other algorithms? 23 } 24 } 25 } 26 None 27} 28 29pub fn generate_nonce() -> CowStr<'static> { 30 URL_SAFE_NO_PAD 31 .encode(get_random_values::<_, 16>(&mut ThreadRng::default())) 32 .into() 33} 34 35pub fn generate_verifier() -> CowStr<'static> { 36 URL_SAFE_NO_PAD 37 .encode(get_random_values::<_, 43>(&mut ThreadRng::default())) 38 .into() 39} 40 41pub fn get_random_values<R, const LEN: usize>(rng: &mut R) -> [u8; LEN] 42where 43 R: RngCore + CryptoRng, 44{ 45 let mut bytes = [0u8; LEN]; 46 rng.fill_bytes(&mut bytes); 47 bytes 48} 49 50// 256K > ES (256 > 384 > 512) > PS (256 > 384 > 512) > RS (256 > 384 > 512) > other (in original order) 51pub fn compare_algos(a: &CowStr, b: &CowStr) -> Ordering { 52 if a.as_ref() == "ES256K" { 53 return Ordering::Less; 54 } 55 if b.as_ref() == "ES256K" { 56 return Ordering::Greater; 57 } 58 for prefix in ["ES", "PS", "RS"] { 59 if let Some(stripped_a) = a.strip_prefix(prefix) { 60 if let Some(stripped_b) = b.strip_prefix(prefix) { 61 if let (Ok(len_a), Ok(len_b)) = 62 (stripped_a.parse::<u32>(), stripped_b.parse::<u32>()) 63 { 64 return len_a.cmp(&len_b); 65 } 66 } else { 67 return Ordering::Less; 68 } 69 } else if b.starts_with(prefix) { 70 return Ordering::Greater; 71 } 72 } 73 Ordering::Equal 74} 75 76pub fn generate_pkce() -> (CowStr<'static>, CowStr<'static>) { 77 // https://datatracker.ietf.org/doc/html/rfc7636#section-4.1 78 let verifier = generate_verifier(); 79 ( 80 URL_SAFE_NO_PAD 81 .encode(Sha256::digest(&verifier.as_str())) 82 .into(), 83 verifier, 84 ) 85} 86 87pub fn generate_dpop_key(metadata: &OAuthAuthorizationServerMetadata) -> Option<Key> { 88 let mut algs = metadata 89 .dpop_signing_alg_values_supported 90 .clone() 91 .unwrap_or(vec![FALLBACK_ALG.into()]); 92 algs.sort_by(compare_algos); 93 generate_key(&algs) 94}