A better Rust ATProto crate
103
fork

Configure Feed

Select the types of activity you want to include in your feed.

at pretty-codegen 115 lines 4.2 kB view raw
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 12/// Generate a fresh JWK secret key using the first algorithm from `allowed_algos` that is 13/// supported, returning `None` if none are supported. 14/// 15/// Currently only `ES256` (P-256 ECDSA) is implemented; other algorithm identifiers are skipped. 16pub fn generate_key(allowed_algos: &[CowStr]) -> Option<Key> { 17 for alg in allowed_algos { 18 #[allow(clippy::single_match)] 19 match alg.as_ref() { 20 "ES256" => { 21 return Some(Key::from(&crypto::Key::from( 22 SecretKey::<p256::NistP256>::random(&mut ThreadRng::default()), 23 ))); 24 } 25 _ => { 26 // TODO: Implement other algorithms? 27 } 28 } 29 } 30 None 31} 32 33/// Generate a cryptographically random 16-byte nonce encoded as base64url (no padding). 34pub fn generate_nonce() -> CowStr<'static> { 35 URL_SAFE_NO_PAD 36 .encode(get_random_values::<_, 16>(&mut ThreadRng::default())) 37 .into() 38} 39 40/// Generate a cryptographically random 43-byte PKCE code verifier encoded as base64url (no padding). 41pub fn generate_verifier() -> CowStr<'static> { 42 URL_SAFE_NO_PAD 43 .encode(get_random_values::<_, 43>(&mut ThreadRng::default())) 44 .into() 45} 46 47/// Fill a `LEN`-byte array with cryptographically random bytes from `rng`. 48pub fn get_random_values<R, const LEN: usize>(rng: &mut R) -> [u8; LEN] 49where 50 R: RngCore + CryptoRng, 51{ 52 let mut bytes = [0u8; LEN]; 53 rng.fill_bytes(&mut bytes); 54 bytes 55} 56 57/// Compare two algorithm identifier strings by preference order for DPoP key generation. 58/// 59/// The ordering is: ES256K > ES (256 > 384 > 512) > PS (256 > 384 > 512) > RS (256 > 384 > 512) > other. 60/// Algorithms within the same family are ordered by key length, preferring shorter (faster) keys first. 61pub fn compare_algos(a: &CowStr, b: &CowStr) -> Ordering { 62 if a.as_ref() == "ES256K" { 63 return Ordering::Less; 64 } 65 if b.as_ref() == "ES256K" { 66 return Ordering::Greater; 67 } 68 for prefix in ["ES", "PS", "RS"] { 69 if let Some(stripped_a) = a.strip_prefix(prefix) { 70 if let Some(stripped_b) = b.strip_prefix(prefix) { 71 if let (Ok(len_a), Ok(len_b)) = 72 (stripped_a.parse::<u32>(), stripped_b.parse::<u32>()) 73 { 74 return len_a.cmp(&len_b); 75 } 76 } else { 77 return Ordering::Less; 78 } 79 } else if b.starts_with(prefix) { 80 return Ordering::Greater; 81 } 82 } 83 Ordering::Equal 84} 85 86/// Generate a PKCE challenge/verifier pair. 87/// 88/// Returns `(challenge, verifier)` where `challenge` is the base64url-encoded SHA-256 hash 89/// of the verifier, per [RFC 7636 §4.1](https://datatracker.ietf.org/doc/html/rfc7636#section-4.1). 90/// The verifier must be kept secret and sent at the token endpoint; the challenge is sent at 91/// the authorization endpoint. 92pub fn generate_pkce() -> (CowStr<'static>, CowStr<'static>) { 93 // https://datatracker.ietf.org/doc/html/rfc7636#section-4.1 94 let verifier = generate_verifier(); 95 ( 96 URL_SAFE_NO_PAD 97 .encode(Sha256::digest(&verifier.as_str())) 98 .into(), 99 verifier, 100 ) 101} 102 103/// Generate a DPoP signing key compatible with the algorithms advertised by the authorization server. 104/// 105/// Reads `dpop_signing_alg_values_supported` from the server metadata, sorts by preference 106/// using [`compare_algos`], and attempts to generate a key for the most preferred supported 107/// algorithm. Falls back to [`crate::FALLBACK_ALG`] if the server does not advertise any algorithms. 108pub fn generate_dpop_key(metadata: &OAuthAuthorizationServerMetadata) -> Option<Key> { 109 let mut algs = metadata 110 .dpop_signing_alg_values_supported 111 .clone() 112 .unwrap_or(vec![FALLBACK_ALG.into()]); 113 algs.sort_by(compare_algos); 114 generate_key(&algs) 115}