A better Rust ATProto crate
102
fork

Configure Feed

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

at pretty-codegen 257 lines 10 kB view raw
1use crate::jose::jws::RegisteredHeader; 2use crate::jose::jwt::Claims; 3use crate::jose::signing; 4use jacquard_common::CowStr; 5use jose_jwa::{Algorithm, Signing}; 6use jose_jwk::{Class, EcCurves, OkpCurves, crypto}; 7use jose_jwk::{Jwk, JwkSet, Key}; 8use std::collections::HashSet; 9use thiserror::Error; 10 11/// Errors that can occur when constructing or using a [`Keyset`]. 12#[derive(Error, Debug)] 13#[non_exhaustive] 14pub enum Error { 15 /// Two keys in the set share the same `kid`, which would make key selection ambiguous. 16 #[error("duplicate kid: {0}")] 17 DuplicateKid(String), 18 /// A keyset with no keys cannot sign anything. 19 #[error("keys must not be empty")] 20 EmptyKeys, 21 /// Each key must carry a `kid` so it can be referenced in JWS headers. 22 #[error("key at index {0} must have a `kid`")] 23 EmptyKid(usize), 24 /// No key in the set matches any of the requested signing algorithms. 25 #[error("no signing key found for algorithms: {0:?}")] 26 NotFound(Vec<Signing>), 27 /// Only secret (private) keys may be used for signing; a public key was provided. 28 #[error("key for signing must be a secret key")] 29 PublicKey, 30 /// The key type or curve is not supported for signing. 31 #[error("unsupported key type for signing")] 32 UnsupportedKey, 33 /// The private key (`d` parameter) is missing from the JWK. 34 #[error("missing private key material")] 35 MissingPrivateKey, 36 /// An error from the underlying JWK cryptographic operation. 37 #[error("crypto error: {0:?}")] 38 JwkCrypto(crypto::Error), 39 /// The raw key bytes have an invalid length or format. 40 #[error("invalid key material: {0}")] 41 InvalidKey(String), 42 /// JSON serialization of a JWT header or claims payload failed. 43 #[error(transparent)] 44 SerdeJson(#[from] serde_json::Error), 45} 46 47/// Convenience result type for keyset operations. 48pub type Result<T> = core::result::Result<T, Error>; 49 50/// Signing algorithm preference order for AT Protocol OAuth. 51/// 52/// EdDSA and ES256K are preferred for their security properties, followed by 53/// the NIST curves. This order matches common AT Protocol server expectations. 54const PREFERRED_SIGNING_ALGORITHMS: [Signing; 4] = [ 55 Signing::EdDsa, 56 Signing::Es256K, 57 Signing::Es256, 58 Signing::Es384, 59]; 60 61/// A validated collection of JWK secret keys used for signing DPoP proofs and client assertions. 62/// 63/// Key selection follows [`PREFERRED_SIGNING_ALGORITHMS`] when multiple keys match. 64/// Supported algorithms: EdDSA (Ed25519), ES256K (secp256k1), ES256 (P-256), ES384 (P-384). 65#[derive(Clone, Debug, Default, PartialEq, Eq)] 66pub struct Keyset(Vec<Jwk>); 67 68impl Keyset { 69 /// Returns a [`JwkSet`] containing the public halves of all keys in this keyset. 70 pub fn public_jwks(&self) -> JwkSet { 71 let mut keys = Vec::with_capacity(self.0.len()); 72 for mut key in self.0.clone() { 73 match key.key { 74 Key::Ec(ref mut ec) => { 75 ec.d = None; 76 } 77 Key::Okp(ref mut okp) => { 78 okp.d = None; 79 } 80 _ => {} 81 } 82 keys.push(key); 83 } 84 JwkSet { keys } 85 } 86 87 /// Signs a JWT with the best available key that matches one of the requested algorithms. 88 /// 89 /// Returns [`Error::NotFound`] if no key in the keyset supports any of the given algorithms. 90 pub fn create_jwt(&self, algs: &[Signing], claims: Claims) -> Result<CowStr<'static>> { 91 let Some(jwk) = self.find_key(algs, Class::Signing) else { 92 return Err(Error::NotFound(algs.to_vec())); 93 }; 94 self.create_jwt_with_key(jwk, claims) 95 } 96 97 fn find_key(&self, algs: &[Signing], cls: Class) -> Option<&Jwk> { 98 let candidates = self 99 .0 100 .iter() 101 .filter_map(|key| { 102 if key.prm.cls.is_some_and(|c| c != cls) { 103 return None; 104 } 105 let alg = alg_for_key(&key.key)?; 106 Some((alg, key)).filter(|(alg, _)| algs.contains(alg)) 107 }) 108 .collect::<Vec<_>>(); 109 for pref_alg in PREFERRED_SIGNING_ALGORITHMS { 110 for (alg, key) in &candidates { 111 if *alg == pref_alg { 112 return Some(key); 113 } 114 } 115 } 116 None 117 } 118 119 fn create_jwt_with_key(&self, key: &Jwk, claims: Claims) -> Result<CowStr<'static>> { 120 let kid = key.prm.kid.clone().unwrap(); 121 match &key.key { 122 Key::Ec(ec) => { 123 let d = ec.d.as_ref().ok_or(Error::MissingPrivateKey)?; 124 let d_bytes: &[u8] = d.as_ref(); 125 match ec.crv { 126 EcCurves::P256 => { 127 let signing_key = p256::ecdsa::SigningKey::from_bytes(d_bytes.into()) 128 .map_err(|e| Error::InvalidKey(e.to_string()))?; 129 let mut header = RegisteredHeader::from(Algorithm::Signing(Signing::Es256)); 130 header.kid = Some(kid.into()); 131 Ok(signing::create_signed_jwt_es256( 132 signing_key, 133 header.into(), 134 claims, 135 )?) 136 } 137 EcCurves::P384 => { 138 let signing_key = p384::ecdsa::SigningKey::from_bytes(d_bytes.into()) 139 .map_err(|e| Error::InvalidKey(e.to_string()))?; 140 let mut header = RegisteredHeader::from(Algorithm::Signing(Signing::Es384)); 141 header.kid = Some(kid.into()); 142 Ok(signing::create_signed_jwt_es384( 143 signing_key, 144 header.into(), 145 claims, 146 )?) 147 } 148 EcCurves::P256K => { 149 let signing_key = k256::ecdsa::SigningKey::from_bytes(d_bytes.into()) 150 .map_err(|e| Error::InvalidKey(e.to_string()))?; 151 let mut header = 152 RegisteredHeader::from(Algorithm::Signing(Signing::Es256K)); 153 header.kid = Some(kid.into()); 154 Ok(signing::create_signed_jwt_es256k( 155 signing_key, 156 header.into(), 157 claims, 158 )?) 159 } 160 _ => Err(Error::UnsupportedKey), 161 } 162 } 163 Key::Okp(okp) => match okp.crv { 164 OkpCurves::Ed25519 => { 165 let d = okp.d.as_ref().ok_or(Error::MissingPrivateKey)?; 166 let d_bytes: &[u8] = d.as_ref(); 167 let signing_key = ed25519_dalek::SigningKey::try_from(d_bytes) 168 .map_err(|e| Error::InvalidKey(e.to_string()))?; 169 let mut header = RegisteredHeader::from(Algorithm::Signing(Signing::EdDsa)); 170 header.kid = Some(kid.into()); 171 Ok(signing::create_signed_jwt_eddsa( 172 signing_key, 173 header.into(), 174 claims, 175 )?) 176 } 177 _ => Err(Error::UnsupportedKey), 178 }, 179 _ => Err(Error::UnsupportedKey), 180 } 181 } 182} 183 184/// Returns the signing algorithm for the given JWK key type, if supported. 185fn alg_for_key(key: &Key) -> Option<Signing> { 186 match key { 187 Key::Ec(ec) => match ec.crv { 188 EcCurves::P256 => Some(Signing::Es256), 189 EcCurves::P384 => Some(Signing::Es384), 190 EcCurves::P256K => Some(Signing::Es256K), 191 _ => None, 192 }, 193 Key::Okp(okp) => match okp.crv { 194 OkpCurves::Ed25519 => Some(Signing::EdDsa), 195 _ => None, 196 }, 197 _ => None, 198 } 199} 200 201/// Parses a string-based algorithm name into a [`Signing`] variant, if it maps to 202/// an algorithm this crate supports. 203pub fn parse_signing_alg(s: &str) -> Option<Signing> { 204 match s { 205 "ES256" => Some(Signing::Es256), 206 "ES384" => Some(Signing::Es384), 207 "ES256K" => Some(Signing::Es256K), 208 "EdDSA" => Some(Signing::EdDsa), 209 _ => None, 210 } 211} 212 213impl TryFrom<Vec<Jwk>> for Keyset { 214 type Error = Error; 215 216 fn try_from(keys: Vec<Jwk>) -> Result<Self> { 217 if keys.is_empty() { 218 return Err(Error::EmptyKeys); 219 } 220 let mut v = Vec::with_capacity(keys.len()); 221 let mut hs = HashSet::with_capacity(keys.len()); 222 for (i, key) in keys.into_iter().enumerate() { 223 if let Some(kid) = key.prm.kid.clone() { 224 if hs.contains(&kid) { 225 return Err(Error::DuplicateKid(kid)); 226 } 227 hs.insert(kid); 228 229 // Validate that the key has private material and is a supported type. 230 match &key.key { 231 Key::Ec(ec) => { 232 if ec.d.is_none() { 233 return Err(Error::PublicKey); 234 } 235 if alg_for_key(&key.key).is_none() { 236 return Err(Error::UnsupportedKey); 237 } 238 } 239 Key::Okp(okp) => { 240 if okp.d.is_none() { 241 return Err(Error::PublicKey); 242 } 243 if alg_for_key(&key.key).is_none() { 244 return Err(Error::UnsupportedKey); 245 } 246 } 247 _ => return Err(Error::UnsupportedKey), 248 } 249 250 v.push(key); 251 } else { 252 return Err(Error::EmptyKid(i)); 253 } 254 } 255 Ok(Self(v)) 256 } 257}