|
15 | 15 | import inspect |
16 | 16 | import jwt.utils |
17 | 17 | import secrets |
| 18 | +import time |
| 19 | +import uuid |
| 20 | +import re |
18 | 21 | from unittest import mock |
19 | 22 |
|
20 | 23 | import pytest |
|
33 | 36 | TEST_PRIMARY_ISSUER = "test_primary_issuer" |
34 | 37 | TEST_PRIMARY_SIGNING_KEY = tdata_resource_file_path("keys/keypair1_priv_nopassword.test_pem") |
35 | 38 | TEST_PRIMARY_PUB_KEY = tdata_resource_file_path("keys/keypair1_pub_jwk.json") |
| 39 | + |
36 | 40 | TEST_SECONDARY_ISSUER = "test_secondary_issuer" |
37 | 41 | TEST_SECONDARY_SIGNING_KEY = tdata_resource_file_path("keys/keypair2_priv_nopassword.test_pem") |
38 | 42 | TEST_SECONDARY_PUB_KEY = tdata_resource_file_path("keys/keypair2_pub_jwk.json") |
39 | | -TEST_AUDIENCE = "test_audience" |
| 43 | + |
40 | 44 | TEST_UNTRUSTED_ISSUER = "test_untrusted_issuer" |
| 45 | +TEST_UNTRUSTED_SIGNING_KEY = tdata_resource_file_path("keys/keypair3_priv_nopassword.test_pem") |
| 46 | +TEST_UNTRUSTED_PUB_KEY = tdata_resource_file_path("keys/keypair3_pub_jwk.json") |
| 47 | + |
| 48 | +TEST_AUDIENCE = "test_audience" |
41 | 49 |
|
42 | 50 | TEST_TOKEN_TTL = 60 |
43 | 51 |
|
@@ -135,9 +143,8 @@ def validate_access_token_local( |
135 | 143 | "audiences": [TEST_AUDIENCE], |
136 | 144 | "stub_authority_ttl": TEST_TOKEN_TTL, |
137 | 145 | "stub_authority_access_token_audience": TEST_AUDIENCE, |
138 | | - "stub_authority_signing_key_file": TEST_PRIMARY_SIGNING_KEY, |
139 | | - # for the tests we use this for, it doesn't matter we reuse a signing key. |
140 | | - "stub_authority_pub_key_file": TEST_PRIMARY_PUB_KEY, |
| 146 | + "stub_authority_signing_key_file": TEST_UNTRUSTED_SIGNING_KEY, |
| 147 | + "stub_authority_pub_key_file": TEST_UNTRUSTED_PUB_KEY, |
141 | 148 | } |
142 | 149 | untrusted_issuer_config = StubOidcClientConfig(**untrusted_issuer_config_dict) |
143 | 150 |
|
@@ -290,6 +297,61 @@ def test_malformed_token_4(self): |
290 | 297 | with pytest.raises(InvalidTokenException): |
291 | 298 | under_test.validate_access_token(token=fake_jwt) |
292 | 299 |
|
| 300 | + def test_malformed_token_5_iss_liars(self): |
| 301 | + test_case_name = inspect.currentframe().f_code.co_name |
| 302 | + test_username = self.username(test_case_name) |
| 303 | + under_test = self.under_test__only_primary_validator() |
| 304 | + |
| 305 | + now = int(time.time()) |
| 306 | + token_body = { |
| 307 | + "iss": primary_issuer.token_builder.issuer, |
| 308 | + "sub": test_username, |
| 309 | + "aud": primary_issuer.token_builder.audience, |
| 310 | + "iat": now, |
| 311 | + "exp": now + 100, |
| 312 | + "jti": str(uuid.uuid4()), |
| 313 | + } |
| 314 | + token_header = { |
| 315 | + "alg": primary_issuer.token_builder.signing_key_algorithm, |
| 316 | + "kid": primary_issuer.token_builder.signing_key_id, |
| 317 | + } |
| 318 | + |
| 319 | + # TC 0 |
| 320 | + # Build a valid token first, just to make sure our test method is valid |
| 321 | + test_jwt = primary_issuer.token_builder.encode(body=token_body, extra_headers=token_header) |
| 322 | + under_test.validate_access_token(token=test_jwt) # No throw |
| 323 | + |
| 324 | + # TC 1 |
| 325 | + # Use the real signing key, but make the iss an invalid type. |
| 326 | + # You can make the argument this is still valid, because the signature is still |
| 327 | + # from a trusted issuer. But, we reject it based on bad structure. |
| 328 | + token_body["iss"] = [primary_issuer.token_builder.issuer, untrusted_issuer.token_builder.issuer] |
| 329 | + test_jwt = primary_issuer.token_builder.encode(body=token_body, extra_headers=token_header) |
| 330 | + with pytest.raises( |
| 331 | + InvalidTokenException, |
| 332 | + match=re.escape("Issuer claim ('iss') must be a of string type. 'list' type was detected."), |
| 333 | + ): |
| 334 | + under_test.validate_access_token(token=test_jwt) |
| 335 | + |
| 336 | + # TC 2 |
| 337 | + # Liar token. Untrusted issuer signing key claiming to be valid issuer |
| 338 | + token_body["iss"] = primary_issuer.token_builder.issuer |
| 339 | + test_jwt = untrusted_issuer.token_builder.encode(body=token_body, extra_headers=token_header) |
| 340 | + with pytest.raises( |
| 341 | + InvalidTokenException, match=re.escape("Signature verification failed (InvalidSignatureError)") |
| 342 | + ): |
| 343 | + under_test.validate_access_token(token=test_jwt) |
| 344 | + |
| 345 | + # TC 3 |
| 346 | + # Double-talk liar. Using the untrusted signing key, claiming to be ourselves and the trusted issuer. |
| 347 | + token_body["iss"] = [primary_issuer.token_builder.issuer, untrusted_issuer.token_builder.issuer] |
| 348 | + test_jwt = untrusted_issuer.token_builder.encode(body=token_body, extra_headers=token_header) |
| 349 | + with pytest.raises( |
| 350 | + InvalidTokenException, |
| 351 | + match=re.escape("Issuer claim ('iss') must be a of string type. 'list' type was detected."), |
| 352 | + ): |
| 353 | + under_test.validate_access_token(token=test_jwt) |
| 354 | + |
293 | 355 | def test_missing_signature(self): |
294 | 356 | # QE TC11 - JWT without a signature |
295 | 357 | test_case_name = inspect.currentframe().f_code.co_name |
|
0 commit comments