assertions.rs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. //! Assertion dispatch tests: wrong `aud`, `jti` replay, `jwks_uri` SSRF guard.
  2. use super::{
  3. ContainerKey, TestError, TestResult, assertion_request, ctx_of, expect_invalid_client, harness,
  4. insert_client, stores_of,
  5. };
  6. use crate::db::TokenEndpointAuthMethod;
  7. use crate::error::{AppError, OAuthErrorCode};
  8. use crate::protocol::client_auth::assertion::validate_assertion;
  9. use crate::protocol::client_auth::authenticate_client;
  10. #[tokio::test]
  11. async fn client_auth_assertion_wrong_aud_rejected() -> TestResult {
  12. let (_container, mut h) = harness().await?;
  13. let endpoint = h.endpoint.clone();
  14. let client_id = "client-auth-ct-wrong-aud";
  15. let key = ContainerKey::generate("k1")?;
  16. insert_client(
  17. &h.pool,
  18. client_id,
  19. TokenEndpointAuthMethod::PrivateKeyJwt,
  20. None,
  21. None,
  22. Some(key.jwks.clone()),
  23. None,
  24. )
  25. .await?;
  26. let assertion = key.mint(client_id, "https://evil.example.com/token", "jti-aud-1")?;
  27. let req = assertion_request(client_id, &assertion);
  28. match authenticate_client(&mut stores_of(&mut h), &ctx_of(&endpoint), &req).await {
  29. Err(error) => expect_invalid_client(error, 400),
  30. Ok(_) => Err(TestError::Unexpected("wrong aud accepted".to_owned())),
  31. }
  32. }
  33. #[tokio::test]
  34. async fn client_auth_assertion_replayed_jti_rejected() -> TestResult {
  35. let (_container, mut h) = harness().await?;
  36. let endpoint = h.endpoint.clone();
  37. let client_id = "client-auth-ct-replay";
  38. let key = ContainerKey::generate("k1")?;
  39. insert_client(
  40. &h.pool,
  41. client_id,
  42. TokenEndpointAuthMethod::PrivateKeyJwt,
  43. None,
  44. None,
  45. Some(key.jwks.clone()),
  46. None,
  47. )
  48. .await?;
  49. let assertion = key.mint(client_id, &h.endpoint, "jti-replay-1")?;
  50. let req = assertion_request(client_id, &assertion);
  51. authenticate_client(&mut stores_of(&mut h), &ctx_of(&endpoint), &req)
  52. .await
  53. .map_err(|error| TestError::Unexpected(format!("first use rejected: {error}")))?;
  54. match authenticate_client(&mut stores_of(&mut h), &ctx_of(&endpoint), &req).await {
  55. Err(error) => expect_invalid_client(error, 400),
  56. Ok(_) => Err(TestError::Unexpected("replayed jti accepted".to_owned())),
  57. }
  58. }
  59. #[test]
  60. fn client_auth_assertion_aud_array_membership() -> TestResult {
  61. let key = ContainerKey::generate("k1")?;
  62. let client_id = "client-auth-aud-array";
  63. let endpoint = crate::protocol::client_auth::token_endpoint("https://auth.example.com");
  64. let now = time::OffsetDateTime::now_utc();
  65. let stamp = now.unix_timestamp();
  66. let mint_with_aud = |aud: serde_json::Value| {
  67. let claims = serde_json::json!({
  68. "iss": client_id,
  69. "sub": client_id,
  70. "aud": aud,
  71. "jti": "jti-array-1",
  72. "exp": stamp + 300,
  73. "iat": stamp,
  74. });
  75. jsonwebtoken::encode(
  76. &jsonwebtoken::Header::new(jsonwebtoken::Algorithm::EdDSA),
  77. &claims,
  78. &key.encoding,
  79. )
  80. .map_err(|error| TestError::Crypto(format!("mint failed: {error}")))
  81. };
  82. let member = mint_with_aud(serde_json::json!([endpoint, "https://other.example.com"]))?;
  83. validate_assertion(&member, &key.jwks, client_id, &endpoint, now)
  84. .map_err(|error| TestError::Unexpected(format!("array aud rejected: {error}")))?;
  85. let outsider = mint_with_aud(serde_json::json!(["https://evil.example.com/token"]))?;
  86. if validate_assertion(&outsider, &key.jwks, client_id, &endpoint, now).is_ok() {
  87. return Err(TestError::Unexpected(
  88. "array aud without endpoint accepted".to_owned(),
  89. ));
  90. }
  91. Ok(())
  92. }
  93. #[tokio::test]
  94. async fn client_auth_jwks_uri_private_ip_rejected() -> TestResult {
  95. let (_container, mut h) = harness().await?;
  96. let endpoint = h.endpoint.clone();
  97. let client_id = "client-auth-ct-ssrf";
  98. insert_client(
  99. &h.pool,
  100. client_id,
  101. TokenEndpointAuthMethod::PrivateKeyJwt,
  102. None,
  103. None,
  104. None,
  105. Some("https://127.0.0.1:9/jwks.json"),
  106. )
  107. .await?;
  108. let req = assertion_request(client_id, "aaa.bbb.ccc");
  109. match authenticate_client(&mut stores_of(&mut h), &ctx_of(&endpoint), &req).await {
  110. Err(error) => expect_invalid_client(error, 400),
  111. Ok(_) => Err(TestError::Unexpected("SSRF jwks_uri accepted".to_owned())),
  112. }
  113. }
  114. /// An encrypted `jwks_uri` decrypts before the SSRF-guarded fetch (a
  115. /// private-IP URI fails as `invalid_client`, proving the fetch ran on the
  116. /// plaintext); a corrupt `jwks_uri_enc` fails as 503 `PiiDecrypt`.
  117. #[tokio::test]
  118. async fn private_key_jwt_jwks_uri_fetch() -> TestResult {
  119. let (_container, mut h) = harness().await?;
  120. let endpoint = h.endpoint.clone();
  121. let client_id = "client-auth-ct-jwks-fetch";
  122. insert_client(
  123. &h.pool,
  124. client_id,
  125. TokenEndpointAuthMethod::PrivateKeyJwt,
  126. None,
  127. None,
  128. None,
  129. Some("https://127.0.0.1:9/jwks.json"),
  130. )
  131. .await?;
  132. let sealed: bool = sqlx::query_scalar(
  133. "SELECT jwks_uri_enc IS NOT NULL AND jwks_uri IS NULL FROM oauth_clients WHERE client_id = $1",
  134. )
  135. .bind(client_id)
  136. .fetch_one(&h.pool)
  137. .await
  138. .map_err(TestError::Database)?;
  139. if !sealed {
  140. return Err(TestError::Unexpected(
  141. "jwks_uri not encrypt-only at rest".to_owned(),
  142. ));
  143. }
  144. let req = assertion_request(client_id, "aaa.bbb.ccc");
  145. match authenticate_client(&mut stores_of(&mut h), &ctx_of(&endpoint), &req).await {
  146. Err(error) => expect_invalid_client(error, 400),
  147. Ok(_) => Err(TestError::Unexpected("SSRF jwks_uri accepted".to_owned())),
  148. }?;
  149. let corrupt: &[u8] = b"corrupt-blob";
  150. sqlx::query("UPDATE oauth_clients SET jwks_uri_enc = $1 WHERE client_id = $2")
  151. .bind(corrupt)
  152. .bind(client_id)
  153. .execute(&h.pool)
  154. .await?;
  155. let req = assertion_request(client_id, "aaa.bbb.ccc");
  156. match authenticate_client(&mut stores_of(&mut h), &ctx_of(&endpoint), &req).await {
  157. Ok(_) => Err(TestError::Unexpected(
  158. "corrupt jwks_uri_enc accepted".to_owned(),
  159. )),
  160. Err(error) => {
  161. if !matches!(error, AppError::PiiDecrypt) {
  162. return Err(TestError::Unexpected(format!(
  163. "corrupt jwks_uri_enc must be PiiDecrypt, got {}",
  164. error.code()
  165. )));
  166. }
  167. if error.code() != OAuthErrorCode::TemporarilyUnavailable || error.status().as_u16() != 503 {
  168. return Err(TestError::Unexpected("PiiDecrypt must be 503".to_owned()));
  169. }
  170. Ok(())
  171. }
  172. }
  173. }