A better Rust ATProto crate
1use crate::jose::create_signed_jwt; 2use crate::jose::jws::RegisteredHeader; 3use crate::jose::jwt::Claims; 4use jacquard_common::{CowStr, IntoStatic}; 5use jose_jwa::{Algorithm, Signing}; 6use jose_jwk::{Class, EcCurves, crypto}; 7use jose_jwk::{Jwk, JwkSet, Key}; 8use std::collections::HashSet; 9use thiserror::Error; 10 11#[derive(Error, Debug)] 12pub enum Error { 13 #[error("duplicate kid: {0}")] 14 DuplicateKid(String), 15 #[error("keys must not be empty")] 16 EmptyKeys, 17 #[error("key must have a `kid`")] 18 EmptyKid, 19 #[error("no signing key found for algorithms: {0:?}")] 20 NotFound(Vec<CowStr<'static>>), 21 #[error("key for signing must be a secret key")] 22 PublicKey, 23 #[error("crypto error: {0:?}")] 24 JwkCrypto(crypto::Error), 25 #[error(transparent)] 26 SerdeJson(#[from] serde_json::Error), 27} 28 29pub type Result<T> = core::result::Result<T, Error>; 30 31#[derive(Clone, Debug, Default, PartialEq, Eq)] 32pub struct Keyset(Vec<Jwk>); 33 34impl Keyset { 35 const PREFERRED_SIGNING_ALGORITHMS: [&'static str; 9] = [ 36 "EdDSA", "ES256K", "ES256", "PS256", "PS384", "PS512", "HS256", "HS384", "HS512", 37 ]; 38 pub fn public_jwks(&self) -> JwkSet { 39 let mut keys = Vec::with_capacity(self.0.len()); 40 for mut key in self.0.clone() { 41 match key.key { 42 Key::Ec(ref mut ec) => { 43 ec.d = None; 44 } 45 _ => unimplemented!(), 46 } 47 keys.push(key); 48 } 49 JwkSet { keys } 50 } 51 pub fn create_jwt(&self, algs: &[CowStr], claims: Claims) -> Result<CowStr<'static>> { 52 let Some(jwk) = self.find_key(algs, Class::Signing) else { 53 return Err(Error::NotFound(algs.to_vec().into_static())); 54 }; 55 self.create_jwt_with_key(jwk, claims) 56 } 57 fn find_key(&self, algs: &[CowStr], cls: Class) -> Option<&Jwk> { 58 let candidates = self 59 .0 60 .iter() 61 .filter_map(|key| { 62 if key.prm.cls.is_some_and(|c| c != cls) { 63 return None; 64 } 65 let alg = match &key.key { 66 Key::Ec(ec) => match ec.crv { 67 EcCurves::P256 => "ES256", 68 _ => unimplemented!(), 69 }, 70 _ => unimplemented!(), 71 }; 72 Some((alg, key)).filter(|(alg, _)| algs.contains(&CowStr::Borrowed(&alg))) 73 }) 74 .collect::<Vec<_>>(); 75 for pref_alg in Self::PREFERRED_SIGNING_ALGORITHMS { 76 for (alg, key) in &candidates { 77 if alg == &pref_alg { 78 return Some(key); 79 } 80 } 81 } 82 None 83 } 84 fn create_jwt_with_key(&self, key: &Jwk, claims: Claims) -> Result<CowStr<'static>> { 85 let kid = key.prm.kid.clone().unwrap(); 86 match crypto::Key::try_from(&key.key).map_err(Error::JwkCrypto)? { 87 crypto::Key::P256(crypto::Kind::Secret(secret_key)) => { 88 let mut header = RegisteredHeader::from(Algorithm::Signing(Signing::Es256)); 89 header.kid = Some(kid.into()); 90 Ok(create_signed_jwt(secret_key.into(), header.into(), claims)?) 91 } 92 _ => unimplemented!(), 93 } 94 } 95} 96 97impl TryFrom<Vec<Jwk>> for Keyset { 98 type Error = Error; 99 100 fn try_from(keys: Vec<Jwk>) -> Result<Self> { 101 if keys.is_empty() { 102 return Err(Error::EmptyKeys); 103 } 104 let mut v = Vec::with_capacity(keys.len()); 105 let mut hs = HashSet::with_capacity(keys.len()); 106 for key in keys { 107 if let Some(kid) = key.prm.kid.clone() { 108 if hs.contains(&kid) { 109 return Err(Error::DuplicateKid(kid)); 110 } 111 hs.insert(kid); 112 // ensure that the key is a secret key 113 if match crypto::Key::try_from(&key.key).map_err(Error::JwkCrypto)? { 114 crypto::Key::P256(crypto::Kind::Public(_)) => true, 115 crypto::Key::P256(crypto::Kind::Secret(_)) => false, 116 _ => unimplemented!(), 117 } { 118 return Err(Error::PublicKey); 119 } 120 v.push(key); 121 } else { 122 return Err(Error::EmptyKid); 123 } 124 } 125 Ok(Self(v)) 126 } 127}