1*e7b1675dSTing-Kang Chang# Copyright 2021 Google LLC 2*e7b1675dSTing-Kang Chang# 3*e7b1675dSTing-Kang Chang# Licensed under the Apache License, Version 2.0 (the "License"); 4*e7b1675dSTing-Kang Chang# you may not use this file except in compliance with the License. 5*e7b1675dSTing-Kang Chang# You may obtain a copy of the License at 6*e7b1675dSTing-Kang Chang# 7*e7b1675dSTing-Kang Chang# http://www.apache.org/licenses/LICENSE-2.0 8*e7b1675dSTing-Kang Chang# 9*e7b1675dSTing-Kang Chang# Unless required by applicable law or agreed to in writing, software 10*e7b1675dSTing-Kang Chang# distributed under the License is distributed on an "AS IS" BASIS, 11*e7b1675dSTing-Kang Chang# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*e7b1675dSTing-Kang Chang# See the License for the specific language governing permissions and 13*e7b1675dSTing-Kang Chang# limitations under the License. 14*e7b1675dSTing-Kang Chang"""Cross-language tests for the JWT primitives.""" 15*e7b1675dSTing-Kang Chang 16*e7b1675dSTing-Kang Changimport datetime 17*e7b1675dSTing-Kang Changimport json 18*e7b1675dSTing-Kang Chang 19*e7b1675dSTing-Kang Changfrom absl.testing import absltest 20*e7b1675dSTing-Kang Changfrom absl.testing import parameterized 21*e7b1675dSTing-Kang Chang 22*e7b1675dSTing-Kang Changimport tink 23*e7b1675dSTing-Kang Changfrom tink import jwt 24*e7b1675dSTing-Kang Chang 25*e7b1675dSTing-Kang Changfrom util import testing_servers 26*e7b1675dSTing-Kang Changfrom util import utilities 27*e7b1675dSTing-Kang Chang 28*e7b1675dSTing-Kang ChangSUPPORTED_LANGUAGES = testing_servers.SUPPORTED_LANGUAGES_BY_PRIMITIVE['jwt'] 29*e7b1675dSTing-Kang Chang 30*e7b1675dSTing-Kang Chang 31*e7b1675dSTing-Kang Changdef setUpModule(): 32*e7b1675dSTing-Kang Chang testing_servers.start('jwt') 33*e7b1675dSTing-Kang Chang 34*e7b1675dSTing-Kang Chang 35*e7b1675dSTing-Kang Changdef tearDownModule(): 36*e7b1675dSTing-Kang Chang testing_servers.stop() 37*e7b1675dSTing-Kang Chang 38*e7b1675dSTing-Kang Chang 39*e7b1675dSTing-Kang Changclass JwtTest(parameterized.TestCase): 40*e7b1675dSTing-Kang Chang 41*e7b1675dSTing-Kang Chang @parameterized.parameters(utilities.tinkey_template_names_for(jwt.JwtMac)) 42*e7b1675dSTing-Kang Chang def test_compute_verify_jwt_mac(self, key_template_name): 43*e7b1675dSTing-Kang Chang supported_langs = utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME[ 44*e7b1675dSTing-Kang Chang key_template_name] 45*e7b1675dSTing-Kang Chang self.assertNotEmpty(supported_langs) 46*e7b1675dSTing-Kang Chang key_template = utilities.KEY_TEMPLATE[key_template_name] 47*e7b1675dSTing-Kang Chang # Take the first supported language to generate the keyset. 48*e7b1675dSTing-Kang Chang keyset = testing_servers.new_keyset(supported_langs[0], key_template) 49*e7b1675dSTing-Kang Chang supported_jwt_macs = [] 50*e7b1675dSTing-Kang Chang for lang in supported_langs: 51*e7b1675dSTing-Kang Chang supported_jwt_macs.append( 52*e7b1675dSTing-Kang Chang testing_servers.remote_primitive(lang, keyset, jwt.JwtMac)) 53*e7b1675dSTing-Kang Chang now = datetime.datetime.now(tz=datetime.timezone.utc) 54*e7b1675dSTing-Kang Chang raw_jwt = jwt.new_raw_jwt( 55*e7b1675dSTing-Kang Chang issuer='issuer', 56*e7b1675dSTing-Kang Chang expiration=now + datetime.timedelta(seconds=100)) 57*e7b1675dSTing-Kang Chang for p in supported_jwt_macs: 58*e7b1675dSTing-Kang Chang compact = p.compute_mac_and_encode(raw_jwt) 59*e7b1675dSTing-Kang Chang validator = jwt.new_validator(expected_issuer='issuer', fixed_now=now) 60*e7b1675dSTing-Kang Chang for p2 in supported_jwt_macs: 61*e7b1675dSTing-Kang Chang verified_jwt = p2.verify_mac_and_decode(compact, validator) 62*e7b1675dSTing-Kang Chang self.assertEqual(verified_jwt.issuer(), 'issuer') 63*e7b1675dSTing-Kang Chang 64*e7b1675dSTing-Kang Chang @parameterized.parameters( 65*e7b1675dSTing-Kang Chang utilities.tinkey_template_names_for(jwt.JwtPublicKeySign)) 66*e7b1675dSTing-Kang Chang def test_jwt_public_key_sign_verify(self, key_template_name): 67*e7b1675dSTing-Kang Chang supported_langs = utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME[ 68*e7b1675dSTing-Kang Chang key_template_name] 69*e7b1675dSTing-Kang Chang key_template = utilities.KEY_TEMPLATE[key_template_name] 70*e7b1675dSTing-Kang Chang self.assertNotEmpty(supported_langs) 71*e7b1675dSTing-Kang Chang # Take the first supported language to generate the private keyset. 72*e7b1675dSTing-Kang Chang private_keyset = testing_servers.new_keyset(supported_langs[0], 73*e7b1675dSTing-Kang Chang key_template) 74*e7b1675dSTing-Kang Chang supported_signers = {} 75*e7b1675dSTing-Kang Chang for lang in supported_langs: 76*e7b1675dSTing-Kang Chang supported_signers[lang] = testing_servers.remote_primitive( 77*e7b1675dSTing-Kang Chang lang, private_keyset, jwt.JwtPublicKeySign) 78*e7b1675dSTing-Kang Chang public_keyset = testing_servers.public_keyset('java', private_keyset) 79*e7b1675dSTing-Kang Chang supported_verifiers = {} 80*e7b1675dSTing-Kang Chang for lang in supported_langs: 81*e7b1675dSTing-Kang Chang supported_verifiers[lang] = testing_servers.remote_primitive( 82*e7b1675dSTing-Kang Chang lang, public_keyset, jwt.JwtPublicKeyVerify) 83*e7b1675dSTing-Kang Chang now = datetime.datetime.now(tz=datetime.timezone.utc) 84*e7b1675dSTing-Kang Chang raw_jwt = jwt.new_raw_jwt( 85*e7b1675dSTing-Kang Chang issuer='issuer', expiration=now + datetime.timedelta(seconds=100)) 86*e7b1675dSTing-Kang Chang for signer in supported_signers.values(): 87*e7b1675dSTing-Kang Chang compact = signer.sign_and_encode(raw_jwt) 88*e7b1675dSTing-Kang Chang validator = jwt.new_validator(expected_issuer='issuer', fixed_now=now) 89*e7b1675dSTing-Kang Chang for verifier in supported_verifiers.values(): 90*e7b1675dSTing-Kang Chang verified_jwt = verifier.verify_and_decode(compact, validator) 91*e7b1675dSTing-Kang Chang self.assertEqual(verified_jwt.issuer(), 'issuer') 92*e7b1675dSTing-Kang Chang 93*e7b1675dSTing-Kang Chang @parameterized.parameters( 94*e7b1675dSTing-Kang Chang utilities.tinkey_template_names_for(jwt.JwtPublicKeySign)) 95*e7b1675dSTing-Kang Chang def test_jwt_public_key_sign_export_import_verify(self, key_template_name): 96*e7b1675dSTing-Kang Chang supported_langs = utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME[ 97*e7b1675dSTing-Kang Chang key_template_name] 98*e7b1675dSTing-Kang Chang self.assertNotEmpty(supported_langs) 99*e7b1675dSTing-Kang Chang key_template = utilities.KEY_TEMPLATE[key_template_name] 100*e7b1675dSTing-Kang Chang # Take the first supported language to generate the private keyset. 101*e7b1675dSTing-Kang Chang private_keyset = testing_servers.new_keyset(supported_langs[0], 102*e7b1675dSTing-Kang Chang key_template) 103*e7b1675dSTing-Kang Chang now = datetime.datetime.now(tz=datetime.timezone.utc) 104*e7b1675dSTing-Kang Chang raw_jwt = jwt.new_raw_jwt( 105*e7b1675dSTing-Kang Chang issuer='issuer', expiration=now + datetime.timedelta(seconds=100)) 106*e7b1675dSTing-Kang Chang validator = jwt.new_validator(expected_issuer='issuer', fixed_now=now) 107*e7b1675dSTing-Kang Chang 108*e7b1675dSTing-Kang Chang for lang1 in supported_langs: 109*e7b1675dSTing-Kang Chang # in lang1: sign token and export public keyset to a JWK set 110*e7b1675dSTing-Kang Chang signer = testing_servers.remote_primitive(lang1, private_keyset, 111*e7b1675dSTing-Kang Chang jwt.JwtPublicKeySign) 112*e7b1675dSTing-Kang Chang compact = signer.sign_and_encode(raw_jwt) 113*e7b1675dSTing-Kang Chang public_keyset = testing_servers.public_keyset(lang1, private_keyset) 114*e7b1675dSTing-Kang Chang public_jwk_set = testing_servers.jwk_set_from_keyset(lang1, public_keyset) 115*e7b1675dSTing-Kang Chang for lang2 in supported_langs: 116*e7b1675dSTing-Kang Chang # in lang2: import the public JWK set and verify the token 117*e7b1675dSTing-Kang Chang public_keyset = testing_servers.jwk_set_to_keyset(lang2, public_jwk_set) 118*e7b1675dSTing-Kang Chang verifier = testing_servers.remote_primitive(lang2, public_keyset, 119*e7b1675dSTing-Kang Chang jwt.JwtPublicKeyVerify) 120*e7b1675dSTing-Kang Chang verified_jwt = verifier.verify_and_decode(compact, validator) 121*e7b1675dSTing-Kang Chang self.assertEqual(verified_jwt.issuer(), 'issuer') 122*e7b1675dSTing-Kang Chang 123*e7b1675dSTing-Kang Chang # Additional tests for the "kid" property of the JWK and the "kid" 124*e7b1675dSTing-Kang Chang # header of the token. Either of them may be missing, but they must not 125*e7b1675dSTing-Kang Chang # have different values. 126*e7b1675dSTing-Kang Chang jwks = json.loads(public_jwk_set) 127*e7b1675dSTing-Kang Chang has_kid = 'kid' in jwks['keys'][0] 128*e7b1675dSTing-Kang Chang if has_kid: 129*e7b1675dSTing-Kang Chang # Change the "kid" property of the JWK. 130*e7b1675dSTing-Kang Chang jwks['keys'][0]['kid'] = 'unknown kid' 131*e7b1675dSTing-Kang Chang public_keyset = testing_servers.jwk_set_to_keyset( 132*e7b1675dSTing-Kang Chang lang2, json.dumps(jwks)) 133*e7b1675dSTing-Kang Chang verifier = testing_servers.remote_primitive(lang2, public_keyset, 134*e7b1675dSTing-Kang Chang jwt.JwtPublicKeyVerify) 135*e7b1675dSTing-Kang Chang with self.assertRaises( 136*e7b1675dSTing-Kang Chang tink.TinkError, 137*e7b1675dSTing-Kang Chang msg='%s accepts tokens with an incorrect kid unexpectedly' % 138*e7b1675dSTing-Kang Chang lang2): 139*e7b1675dSTing-Kang Chang verifier.verify_and_decode(compact, validator) 140*e7b1675dSTing-Kang Chang 141*e7b1675dSTing-Kang Chang # Remove the "kid" property of the JWK. 142*e7b1675dSTing-Kang Chang del jwks['keys'][0]['kid'] 143*e7b1675dSTing-Kang Chang public_keyset = testing_servers.jwk_set_to_keyset( 144*e7b1675dSTing-Kang Chang lang2, json.dumps(jwks)) 145*e7b1675dSTing-Kang Chang verifier = testing_servers.remote_primitive(lang2, public_keyset, 146*e7b1675dSTing-Kang Chang jwt.JwtPublicKeyVerify) 147*e7b1675dSTing-Kang Chang verified_jwt = verifier.verify_and_decode(compact, validator) 148*e7b1675dSTing-Kang Chang self.assertEqual(verified_jwt.issuer(), 'issuer') 149*e7b1675dSTing-Kang Chang else: 150*e7b1675dSTing-Kang Chang # Add a "kid" property of the JWK. 151*e7b1675dSTing-Kang Chang jwks['keys'][0]['kid'] = 'unknown kid' 152*e7b1675dSTing-Kang Chang public_keyset = testing_servers.jwk_set_to_keyset( 153*e7b1675dSTing-Kang Chang lang2, json.dumps(jwks)) 154*e7b1675dSTing-Kang Chang verifier = testing_servers.remote_primitive(lang2, public_keyset, 155*e7b1675dSTing-Kang Chang jwt.JwtPublicKeyVerify) 156*e7b1675dSTing-Kang Chang verified_jwt = verifier.verify_and_decode(compact, validator) 157*e7b1675dSTing-Kang Chang self.assertEqual(verified_jwt.issuer(), 'issuer') 158*e7b1675dSTing-Kang Chang 159*e7b1675dSTing-Kang Chang 160*e7b1675dSTing-Kang Changif __name__ == '__main__': 161*e7b1675dSTing-Kang Chang absltest.main() 162