Skip to content

Commit 3928c05

Browse files
better bad data input handling (#5)
1 parent afb7713 commit 3928c05

2 files changed

Lines changed: 70 additions & 4 deletions

File tree

src/planet_auth/oidc/multi_validator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ def _select_validator(self, token) -> Auth:
231231
# PyJWT does not seem to raise if the issuer is explicitly None, even when
232232
# verify_iss was selected.
233233
raise InvalidTokenException(message="Cannot validate token that does not include an issuer ('iss') claim")
234+
if not isinstance(issuer, str):
235+
raise InvalidTokenException(
236+
message=f"Issuer claim ('iss') must be a of string type. '{type(issuer).__name__}' type was detected."
237+
)
234238

235239
validator = self._trusted.get(issuer)
236240
if validator:

tests/test_planet_auth/unit/auth/auth_clients/oidc/test_multi_validator.py

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
import inspect
1616
import jwt.utils
1717
import secrets
18+
import time
19+
import uuid
20+
import re
1821
from unittest import mock
1922

2023
import pytest
@@ -33,11 +36,16 @@
3336
TEST_PRIMARY_ISSUER = "test_primary_issuer"
3437
TEST_PRIMARY_SIGNING_KEY = tdata_resource_file_path("keys/keypair1_priv_nopassword.test_pem")
3538
TEST_PRIMARY_PUB_KEY = tdata_resource_file_path("keys/keypair1_pub_jwk.json")
39+
3640
TEST_SECONDARY_ISSUER = "test_secondary_issuer"
3741
TEST_SECONDARY_SIGNING_KEY = tdata_resource_file_path("keys/keypair2_priv_nopassword.test_pem")
3842
TEST_SECONDARY_PUB_KEY = tdata_resource_file_path("keys/keypair2_pub_jwk.json")
39-
TEST_AUDIENCE = "test_audience"
43+
4044
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"
4149

4250
TEST_TOKEN_TTL = 60
4351

@@ -135,9 +143,8 @@ def validate_access_token_local(
135143
"audiences": [TEST_AUDIENCE],
136144
"stub_authority_ttl": TEST_TOKEN_TTL,
137145
"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,
141148
}
142149
untrusted_issuer_config = StubOidcClientConfig(**untrusted_issuer_config_dict)
143150

@@ -290,6 +297,61 @@ def test_malformed_token_4(self):
290297
with pytest.raises(InvalidTokenException):
291298
under_test.validate_access_token(token=fake_jwt)
292299

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+
293355
def test_missing_signature(self):
294356
# QE TC11 - JWT without a signature
295357
test_case_name = inspect.currentframe().f_code.co_name

0 commit comments

Comments
 (0)