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