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