1use std::collections::HashMap;
10
11use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation};
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14
15use crate::bridges::{Bridge, BridgeError, BridgeKind};
16use crate::generated::{
17 ActorIdentity, ActorIdentity_IdentityVersion, ActorType, AuthorityRoot, AuthorityRoot_Kind,
18 PublicKey, PublicKey_Purpose, TrustLevel,
19};
20
21#[derive(Clone, Debug, Serialize, Deserialize)]
22pub struct OAuthBridgeConfig {
23 pub bridge_id: String,
24 pub trust_domain: String,
25 pub jwks: Jwks,
26 pub allowed_algorithms: Vec<String>,
27 pub issuer: String,
28 pub audience: Vec<String>,
29 #[serde(default = "default_clock_tolerance")]
30 pub clock_tolerance_seconds: u64,
31}
32
33fn default_clock_tolerance() -> u64 {
34 60
35}
36
37#[derive(Clone, Debug, Serialize, Deserialize)]
38pub struct Jwks {
39 pub keys: Vec<Jwk>,
40}
41
42#[derive(Clone, Debug, Serialize, Deserialize)]
45pub struct Jwk {
46 pub kty: String,
47 #[serde(default)]
48 pub alg: Option<String>,
49 #[serde(default)]
50 pub kid: Option<String>,
51 #[serde(default)]
52 pub crv: Option<String>,
53 #[serde(default)]
54 pub x: Option<String>,
55 #[serde(default)]
56 pub y: Option<String>,
57 #[serde(default)]
58 pub n: Option<String>,
59 #[serde(default)]
60 pub e: Option<String>,
61}
62
63#[derive(Clone, Debug, Serialize, Deserialize)]
64pub struct OAuthClaims {
65 pub iss: Option<String>,
66 pub sub: Option<String>,
67 pub aud: Option<Value>,
68 pub exp: Option<u64>,
69 pub iat: Option<u64>,
70 pub scope: Option<Value>,
71 #[serde(rename = "tf_actor_type", default)]
72 pub tf_actor_type: Option<String>,
73 #[serde(flatten)]
74 pub extra: HashMap<String, Value>,
75}
76
77#[derive(Clone, Debug)]
78pub struct OAuthVerificationResult {
79 pub identity: ActorIdentity,
80 pub capabilities: Vec<String>,
81 pub claims: OAuthClaims,
82}
83
84pub struct OAuthBridge {
85 cfg: OAuthBridgeConfig,
86}
87
88impl OAuthBridge {
89 pub fn new(cfg: OAuthBridgeConfig) -> Self {
90 OAuthBridge { cfg }
91 }
92
93 pub fn verify_token(&self, token: &str) -> Result<OAuthVerificationResult, BridgeError> {
94 if token.is_empty() {
95 return Err(BridgeError::InvalidInput("empty token".into()));
96 }
97 let header = decode_header(token)
98 .map_err(|e| BridgeError::Rejected(format!("malformed JWT: {}", e)))?;
99 let alg_name = format!("{:?}", header.alg);
100 if !self
101 .cfg
102 .allowed_algorithms
103 .iter()
104 .any(|a| a.eq_ignore_ascii_case(&alg_name))
105 {
106 return Err(BridgeError::Rejected(format!(
107 "algorithm {} not in allow-list",
108 alg_name
109 )));
110 }
111
112 let kid = header
113 .kid
114 .clone()
115 .ok_or_else(|| BridgeError::Rejected("JWT header missing kid".into()))?;
116 let jwk = self
117 .cfg
118 .jwks
119 .keys
120 .iter()
121 .find(|k| k.kid.as_deref() == Some(&kid))
122 .ok_or_else(|| BridgeError::Rejected(format!("no JWK with kid {}", kid)))?;
123 let key = decoding_key_for(jwk)?;
124
125 let mut validation = Validation::new(header.alg);
126 validation.set_issuer(&[self.cfg.issuer.as_str()]);
127 validation.set_audience(&self.cfg.audience);
128 validation.leeway = self.cfg.clock_tolerance_seconds;
129 validation.algorithms = vec![header.alg];
130
131 let data = decode::<OAuthClaims>(token, &key, &validation)
132 .map_err(|e| BridgeError::Rejected(format!("JWT verify failed: {}", e)))?;
133 let claims = data.claims;
134 let subject = claims
135 .sub
136 .clone()
137 .ok_or_else(|| BridgeError::Rejected("JWT missing sub claim".into()))?;
138 let actor_type_str = claims.tf_actor_type.as_deref().unwrap_or("human");
139 let actor_type = match actor_type_str {
140 "human" => ActorType::Human,
141 "agent" => ActorType::Agent,
142 "device" => ActorType::Device,
143 "service" => ActorType::Service,
144 "site" => ActorType::Site,
145 "organization" => ActorType::Organization,
146 other => {
147 return Err(BridgeError::Rejected(format!(
148 "unsupported tf_actor_type: {}",
149 other
150 )))
151 }
152 };
153 let encoded_subject = encode_subject(&subject);
154 let actor_id = format!(
155 "tf:actor:{}:{}/{}",
156 actor_type_str, self.cfg.trust_domain, encoded_subject
157 );
158
159 let identity = ActorIdentity {
160 identity_version: ActorIdentity_IdentityVersion::V1,
161 actor_id,
162 actor_type,
163 instance_id: None,
164 public_keys: vec![project_jwk_to_public_key(jwk)?],
165 trust_levels: vec![TrustLevel::T3],
166 authority_roots: vec![AuthorityRoot {
167 kind: AuthorityRoot_Kind::Organization,
168 id: self.cfg.issuer.clone(),
169 }],
170 attestations: None,
171 valid_from: claims
172 .iat
173 .map(timestamp)
174 .unwrap_or_else(|| timestamp(now_unix())),
175 valid_until: claims.exp.map(timestamp),
176 revocation_ref: None,
177 signature: None,
178 };
179
180 let capabilities = scopes_from_claims(&claims);
181
182 Ok(OAuthVerificationResult {
183 identity,
184 capabilities,
185 claims,
186 })
187 }
188}
189
190impl Bridge for OAuthBridge {
191 fn bridge_id(&self) -> &str {
192 &self.cfg.bridge_id
193 }
194 fn kind(&self) -> BridgeKind {
195 BridgeKind::Oauth
196 }
197 fn trust_domain(&self) -> &str {
198 &self.cfg.trust_domain
199 }
200}
201
202fn decoding_key_for(jwk: &Jwk) -> Result<DecodingKey, BridgeError> {
203 match jwk.kty.as_str() {
204 "EC" => {
205 let x = jwk
206 .x
207 .as_ref()
208 .ok_or_else(|| BridgeError::InvalidInput("EC JWK missing x".into()))?;
209 let y = jwk
210 .y
211 .as_ref()
212 .ok_or_else(|| BridgeError::InvalidInput("EC JWK missing y".into()))?;
213 DecodingKey::from_ec_components(x, y)
214 .map_err(|e| BridgeError::InvalidInput(format!("bad EC components: {}", e)))
215 }
216 "RSA" => {
217 let n = jwk
218 .n
219 .as_ref()
220 .ok_or_else(|| BridgeError::InvalidInput("RSA JWK missing n".into()))?;
221 let e = jwk
222 .e
223 .as_ref()
224 .ok_or_else(|| BridgeError::InvalidInput("RSA JWK missing e".into()))?;
225 DecodingKey::from_rsa_components(n, e)
226 .map_err(|e| BridgeError::InvalidInput(format!("bad RSA components: {}", e)))
227 }
228 "OKP" => {
229 let x = jwk
230 .x
231 .as_ref()
232 .ok_or_else(|| BridgeError::InvalidInput("OKP JWK missing x".into()))?;
233 DecodingKey::from_ed_components(x)
234 .map_err(|e| BridgeError::InvalidInput(format!("bad OKP components: {}", e)))
235 }
236 other => Err(BridgeError::InvalidInput(format!(
237 "unsupported kty {}",
238 other
239 ))),
240 }
241}
242
243fn encode_subject(s: &str) -> String {
244 let mut out = String::with_capacity(s.len());
247 for b in s.bytes() {
248 match b {
249 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
250 out.push(b as char);
251 }
252 _ => out.push_str(&format!("%{:02X}", b)),
253 }
254 }
255 out
256}
257
258fn scopes_from_claims(claims: &OAuthClaims) -> Vec<String> {
259 match &claims.scope {
260 Some(Value::String(s)) => s.split_whitespace().map(str::to_string).collect(),
261 Some(Value::Array(arr)) => arr
262 .iter()
263 .filter_map(|v| v.as_str().map(str::to_string))
264 .collect(),
265 _ => Vec::new(),
266 }
267}
268
269fn timestamp(t: u64) -> String {
270 let datetime = std::time::UNIX_EPOCH + std::time::Duration::from_secs(t);
272 let secs = datetime
273 .duration_since(std::time::UNIX_EPOCH)
274 .expect("post-epoch")
275 .as_secs() as i64;
276 let (year, month, day, hour, minute, second) = secs_to_ymdhms(secs);
278 format!(
279 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
280 year, month, day, hour, minute, second
281 )
282}
283
284fn now_unix() -> u64 {
285 std::time::SystemTime::now()
286 .duration_since(std::time::UNIX_EPOCH)
287 .unwrap_or_default()
288 .as_secs()
289}
290
291fn secs_to_ymdhms(secs: i64) -> (i32, u32, u32, u32, u32, u32) {
292 let days = secs.div_euclid(86_400);
294 let time = secs.rem_euclid(86_400);
295 let hour = (time / 3600) as u32;
296 let minute = ((time % 3600) / 60) as u32;
297 let second = (time % 60) as u32;
298
299 let z = days + 719_468;
300 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
301 let doe = (z - era * 146_097) as u64; let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
303 let y = yoe as i64 + era * 400;
304 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
305 let mp = (5 * doy + 2) / 153;
306 let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
307 let m = if mp < 10 {
308 (mp + 3) as u32
309 } else {
310 (mp - 9) as u32
311 };
312 let year = if m <= 2 { y + 1 } else { y };
313 (year as i32, m, d, hour, minute, second)
314}
315
316pub fn parse_algorithm(name: &str) -> Result<Algorithm, BridgeError> {
317 match name.to_ascii_uppercase().as_str() {
318 "ES256" => Ok(Algorithm::ES256),
319 "ES384" => Ok(Algorithm::ES384),
320 "RS256" => Ok(Algorithm::RS256),
321 "RS384" => Ok(Algorithm::RS384),
322 "RS512" => Ok(Algorithm::RS512),
323 "EDDSA" => Ok(Algorithm::EdDSA),
324 other => Err(BridgeError::InvalidInput(format!(
325 "unsupported algorithm: {}",
326 other
327 ))),
328 }
329}
330
331pub fn project_jwk_to_public_key(jwk: &Jwk) -> Result<PublicKey, BridgeError> {
335 use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD};
336 use base64::Engine;
337 let key_id = jwk
338 .kid
339 .clone()
340 .unwrap_or_else(|| "oauth-bridge-bearer".to_string());
341 match jwk.kty.as_str() {
342 "OKP" => {
343 let x = jwk
345 .x
346 .as_ref()
347 .ok_or_else(|| BridgeError::InvalidInput("OKP JWK missing x".into()))?;
348 let bytes = URL_SAFE_NO_PAD
349 .decode(x)
350 .map_err(|e| BridgeError::InvalidInput(format!("base64url x: {}", e)))?;
351 Ok(PublicKey {
352 key_id,
353 algorithm: "ed25519".into(),
354 public_key: STANDARD.encode(bytes),
355 purpose: PublicKey_Purpose::Signing,
356 valid_from: None,
357 valid_until: None,
358 })
359 }
360 "EC" => {
361 let x = jwk
362 .x
363 .as_ref()
364 .ok_or_else(|| BridgeError::InvalidInput("EC JWK missing x".into()))?;
365 let y = jwk
366 .y
367 .as_ref()
368 .ok_or_else(|| BridgeError::InvalidInput("EC JWK missing y".into()))?;
369 let xb = URL_SAFE_NO_PAD
370 .decode(x)
371 .map_err(|e| BridgeError::InvalidInput(format!("base64url x: {}", e)))?;
372 let yb = URL_SAFE_NO_PAD
373 .decode(y)
374 .map_err(|e| BridgeError::InvalidInput(format!("base64url y: {}", e)))?;
375 let mut sec1 = Vec::with_capacity(1 + xb.len() + yb.len());
376 sec1.push(0x04);
377 sec1.extend_from_slice(&xb);
378 sec1.extend_from_slice(&yb);
379 let crv = jwk.crv.as_deref().unwrap_or("");
380 let alg = match crv {
381 "P-256" => "p256",
382 "P-384" => "p384",
383 "P-521" => "p521",
384 _ => "ec",
385 };
386 Ok(PublicKey {
387 key_id,
388 algorithm: alg.into(),
389 public_key: STANDARD.encode(sec1),
390 purpose: PublicKey_Purpose::Signing,
391 valid_from: None,
392 valid_until: None,
393 })
394 }
395 "RSA" => {
396 let n = jwk
397 .n
398 .as_ref()
399 .ok_or_else(|| BridgeError::InvalidInput("RSA JWK missing n".into()))?;
400 let e = jwk
401 .e
402 .as_ref()
403 .ok_or_else(|| BridgeError::InvalidInput("RSA JWK missing e".into()))?;
404 let nb = URL_SAFE_NO_PAD
405 .decode(n)
406 .map_err(|err| BridgeError::InvalidInput(format!("base64url n: {}", err)))?;
407 let eb = URL_SAFE_NO_PAD
408 .decode(e)
409 .map_err(|err| BridgeError::InvalidInput(format!("base64url e: {}", err)))?;
410 let der = encode_rsa_spki(&nb, &eb);
411 Ok(PublicKey {
412 key_id,
413 algorithm: "rsa".into(),
414 public_key: STANDARD.encode(der),
415 purpose: PublicKey_Purpose::Signing,
416 valid_from: None,
417 valid_until: None,
418 })
419 }
420 other => Err(BridgeError::Unsupported(format!(
421 "unsupported JWK kty: {}",
422 other
423 ))),
424 }
425}
426
427fn encode_rsa_spki(n: &[u8], e: &[u8]) -> Vec<u8> {
428 let rsa_public_key = der_sequence(&[der_integer(n), der_integer(e)]);
429 let oid_rsa_encryption: [u8; 11] = [
430 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
431 ];
432 let null_params: [u8; 2] = [0x05, 0x00];
433 let alg_id = der_sequence(&[oid_rsa_encryption.to_vec(), null_params.to_vec()]);
434 let mut bit_string_body = Vec::with_capacity(1 + rsa_public_key.len());
435 bit_string_body.push(0x00);
436 bit_string_body.extend_from_slice(&rsa_public_key);
437 let mut bit_string = Vec::with_capacity(2 + bit_string_body.len());
438 bit_string.push(0x03);
439 bit_string.extend_from_slice(&der_len(bit_string_body.len()));
440 bit_string.extend_from_slice(&bit_string_body);
441 der_sequence(&[alg_id, bit_string])
442}
443
444fn der_sequence(parts: &[Vec<u8>]) -> Vec<u8> {
445 let body: Vec<u8> = parts.iter().flat_map(|p| p.clone()).collect();
446 let mut out = Vec::with_capacity(2 + body.len());
447 out.push(0x30);
448 out.extend_from_slice(&der_len(body.len()));
449 out.extend_from_slice(&body);
450 out
451}
452
453fn der_integer(bytes: &[u8]) -> Vec<u8> {
454 let mut start = 0usize;
455 while start < bytes.len() - 1 && bytes[start] == 0 {
456 start += 1;
457 }
458 let payload = &bytes[start..];
459 let needs_pad = payload[0] & 0x80 != 0;
460 let len = payload.len() + if needs_pad { 1 } else { 0 };
461 let mut out = Vec::with_capacity(2 + len);
462 out.push(0x02);
463 out.extend_from_slice(&der_len(len));
464 if needs_pad {
465 out.push(0x00);
466 }
467 out.extend_from_slice(payload);
468 out
469}
470
471fn der_len(n: usize) -> Vec<u8> {
472 if n < 0x80 {
473 return vec![n as u8];
474 }
475 let mut bytes = Vec::new();
476 let mut v = n;
477 while v > 0 {
478 bytes.insert(0, (v & 0xff) as u8);
479 v >>= 8;
480 }
481 let mut out = Vec::with_capacity(1 + bytes.len());
482 out.push(0x80 | bytes.len() as u8);
483 out.extend_from_slice(&bytes);
484 out
485}