1use super::{OAuthClientAssertion, OAuthClientAssertionContext, OAuthClientAssertionGetter};
3use base64::{
4 Engine,
5 alphabet::URL_SAFE,
6 engine::{
7 DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig, general_purpose::URL_SAFE_NO_PAD,
8 },
9};
10use rsa::{
11 pkcs8::DecodePrivateKey,
12 signature::{RandomizedSigner, SignatureEncoding, Signer},
13};
14use serde_json::{Value, json};
15use std::sync::Arc;
16
17const ALGORITHMS: &[&str] = &[
18 "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "ES256", "ES384", "ES512", "EdDSA",
19];
20
21#[derive(Clone, Default)]
25pub struct OAuthPrivateKeyJwtOptions {
26 pub private_key_jwk: Option<Value>,
27 pub private_key_pem: Option<String>,
28 pub algorithm: Option<String>,
29 pub kid: Option<String>,
30 pub expires_in: Option<f64>,
32}
33impl std::fmt::Debug for OAuthPrivateKeyJwtOptions {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 f.debug_struct("OAuthPrivateKeyJwtOptions")
36 .finish_non_exhaustive()
37 }
38}
39impl OAuthPrivateKeyJwtOptions {
40 pub fn into_assertion(self) -> Result<OAuthClientAssertion, String> {
45 let _algorithm = self.resolved_algorithm()?;
46 Ok(OAuthClientAssertion(Arc::new(self)))
47 }
48 fn resolved_algorithm(&self) -> Result<&str, String> {
49 if self.private_key_jwk.as_ref().is_none_or(Value::is_null)
50 && self.private_key_pem.as_deref().is_none_or(str::is_empty)
51 {
52 return Err("private_key_jwt requires either privateKeyJwk or privateKeyPem".into());
53 }
54 let embedded = self
55 .private_key_jwk
56 .as_ref()
57 .and_then(|k| k.get("alg"))
58 .and_then(Value::as_str);
59 for algorithm in [
60 self.algorithm.as_deref().filter(|s| !s.is_empty()),
61 embedded,
62 ]
63 .into_iter()
64 .flatten()
65 {
66 if !ALGORITHMS.contains(&algorithm) {
67 return Err("Unsupported private_key_jwt signing algorithm".into());
68 }
69 }
70 if let (Some(explicit), Some(embedded)) = (
71 self.algorithm.as_deref().filter(|s| !s.is_empty()),
72 embedded,
73 ) && explicit != embedded
74 {
75 return Err("JWK alg does not match configured algorithm".into());
76 }
77 Ok(self.algorithm.as_deref().or(embedded).unwrap_or("RS256"))
78 }
79 fn sign(&self, algorithm: &str, message: &[u8]) -> Result<Vec<u8>, String> {
80 let jwk = self.private_key_jwk.as_ref().filter(|v| !v.is_null());
81 let pem = self.private_key_pem.as_deref().unwrap_or_default();
82 if let Some(jwk) = jwk {
83 if jwk.get("ext").is_some_and(|value| !value.is_boolean()) {
84 return Err("JWK ext must be a boolean".into());
85 }
86 if let Some(ops) = jwk.get("key_ops")
87 && !ops.as_array().is_some_and(|ops| {
88 ops.iter().any(|op| op.as_str() == Some("sign"))
89 && ops.iter().enumerate().all(|(index, op)| {
90 op.is_string() && ops.iter().take(index).all(|previous| previous != op)
91 })
92 })
93 {
94 return Err("JWK does not permit signing".into());
95 }
96 let (kty, curve) = match algorithm {
97 "EdDSA" => ("OKP", Some("Ed25519")),
98 "ES256" => ("EC", Some("P-256")),
99 "ES384" => ("EC", Some("P-384")),
100 "ES512" => ("EC", Some("P-521")),
101 _ => ("RSA", None),
102 };
103 if jwk.get("kty").and_then(Value::as_str) != Some(kty)
104 || curve.is_some_and(|c| jwk.get("crv").and_then(Value::as_str) != Some(c))
105 {
106 return Err("JWK does not match signing algorithm".into());
107 }
108 }
109 if algorithm.starts_with("RS") || algorithm.starts_with("PS") {
110 let key = if let Some(jwk) = jwk {
111 for name in ["dp", "dq", "qi"] {
113 let _component = field(jwk, name)?;
114 }
115 rsa::RsaPrivateKey::from_components(
116 rsa::BigUint::from_bytes_be(&field(jwk, "n")?),
117 rsa::BigUint::from_bytes_be(&field(jwk, "e")?),
118 rsa::BigUint::from_bytes_be(&field(jwk, "d")?),
119 vec![
120 rsa::BigUint::from_bytes_be(&field(jwk, "p")?),
121 rsa::BigUint::from_bytes_be(&field(jwk, "q")?),
122 ],
123 )
124 .map_err(key_error)?
125 } else {
126 rsa::RsaPrivateKey::from_pkcs8_pem(pem).map_err(key_error)?
127 };
128 use rsa::traits::PublicKeyParts;
129 if key.n().bits() < 2048 {
130 return Err("RSA modulus length must be at least 2048 bits".into());
131 }
132 macro_rules! rsa_sign {
133 ($digest:ty) => {
134 if algorithm.starts_with("RS") {
135 rsa::pkcs1v15::SigningKey::<$digest>::new(key)
136 .sign(message)
137 .to_vec()
138 } else {
139 rsa::pss::SigningKey::<$digest>::new(key)
140 .sign_with_rng(&mut rsa::rand_core::OsRng, message)
141 .to_vec()
142 }
143 };
144 }
145 return Ok(match algorithm {
146 "RS256" | "PS256" => rsa_sign!(rsa::sha2::Sha256),
147 "RS384" | "PS384" => rsa_sign!(rsa::sha2::Sha384),
148 _ => rsa_sign!(rsa::sha2::Sha512),
149 });
150 }
151 macro_rules! ec_sign {
152 ($curve:ident) => {{
153 use $curve::ecdsa::signature::RandomizedSigner as _;
154 use $curve::elliptic_curve::pkcs8::DecodePrivateKey as _;
155 use $curve::elliptic_curve::sec1::ToSec1Point as _;
156
157 let key = if let Some(jwk) = jwk {
158 $curve::SecretKey::from_slice(&field(jwk, "d")?).map_err(key_error)?
159 } else {
160 $curve::SecretKey::from_pkcs8_pem(pem).map_err(key_error)?
161 };
162 if let Some(jwk) = jwk {
163 let public = key.public_key().to_sec1_point(false);
164 if public.x().map(|x| x.as_slice()) != Some(field(jwk, "x")?.as_slice())
165 || public.y().map(|y| y.as_slice()) != Some(field(jwk, "y")?.as_slice())
166 {
167 return Err("EC private/public key mismatch".into());
168 }
169 }
170 let signer = $curve::ecdsa::SigningKey::from_slice(key.to_bytes().as_slice())
171 .map_err(key_error)?;
172 let signature: $curve::ecdsa::Signature = signer
173 .try_sign_with_rng(&mut rand::rng(), message)
174 .map_err(key_error)?;
175 signature.to_bytes().to_vec()
176 }};
177 }
178 Ok(match algorithm {
179 "ES256" => ec_sign!(p256),
180 "ES384" => ec_sign!(p384),
181 "ES512" => ec_sign!(p521),
182 "EdDSA" => {
183 use ed25519_dalek::Signer;
184 let key = if let Some(jwk) = jwk {
185 let seed: [u8; 32] = field(jwk, "d")?
186 .try_into()
187 .map_err(|_| "Invalid Ed25519 seed")?;
188 ed25519_dalek::SigningKey::from_bytes(&seed)
189 } else {
190 use ed25519_dalek::pkcs8::DecodePrivateKey as _;
191 ed25519_dalek::SigningKey::from_pkcs8_pem(pem).map_err(key_error)?
192 };
193 key.sign(message).to_bytes().to_vec()
194 }
195 _ => return Err("Unsupported private_key_jwt signing algorithm".into()),
196 })
197 }
198}
199#[async_trait::async_trait]
200impl OAuthClientAssertionGetter for OAuthPrivateKeyJwtOptions {
201 async fn get_client_assertion(
202 &self,
203 context: OAuthClientAssertionContext,
204 ) -> Result<String, String> {
205 let algorithm = self.resolved_algorithm()?;
206 let mut header = json!({"alg":algorithm,"typ":"JWT"});
207 let kid = self.kid.as_deref().map(str::to_owned).or_else(|| {
208 self.private_key_jwk
209 .as_ref()
210 .and_then(|k| k.get("kid"))
211 .and_then(Value::as_str)
212 .map(str::to_owned)
213 });
214 if let Some(kid) = kid.filter(|k| !k.is_empty())
215 && let Some(object) = header.as_object_mut()
216 {
217 drop(object.insert("kid".into(), Value::String(kid)));
218 }
219 let now = chrono::Utc::now().timestamp();
220 let exp = now as f64 + self.expires_in.unwrap_or(120.0);
221 if !exp.is_finite() {
222 return Err("Invalid client assertion expiration time".into());
223 }
224 let claims = json!({"iss":context.client_id,"sub":context.client_id,"aud":context.token_endpoint,"iat":now,"exp":exp,"jti":uuid::Uuid::new_v4().to_string()});
225 let message = format!(
226 "{}.{}",
227 URL_SAFE_NO_PAD.encode(serde_json::to_vec(&header).map_err(key_error)?),
228 URL_SAFE_NO_PAD.encode(serde_json::to_vec(&claims).map_err(key_error)?)
229 );
230 Ok(format!(
231 "{message}.{}",
232 URL_SAFE_NO_PAD.encode(self.sign(algorithm, message.as_bytes())?)
233 ))
234 }
235}
236fn field(key: &Value, name: &str) -> Result<Vec<u8>, String> {
237 GeneralPurpose::new(
238 &URL_SAFE,
239 GeneralPurposeConfig::new()
240 .with_decode_padding_mode(DecodePaddingMode::Indifferent)
241 .with_decode_allow_trailing_bits(true),
242 )
243 .decode(
244 key.get(name)
245 .and_then(Value::as_str)
246 .ok_or("Missing JWK field")?,
247 )
248 .map_err(key_error)
249}
250fn key_error(_: impl std::fmt::Display) -> String {
251 "Client assertion key operation failed".into()
252}