xref: /aosp_15_r20/external/tink/testing/cross_language/jwt_test.py (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
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