xref: /aosp_15_r20/external/tink/java_src/src/test/java/com/google/crypto/tink/jwt/JwtTest.java (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
1 // Copyright 2022 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 //
15 ////////////////////////////////////////////////////////////////////////////////
16 
17 package com.google.crypto.tink.jwt;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static org.junit.Assert.assertThrows;
21 
22 import com.google.crypto.tink.DeterministicAead;
23 import com.google.crypto.tink.InsecureSecretKeyAccess;
24 import com.google.crypto.tink.KeyTemplates;
25 import com.google.crypto.tink.KeysetHandle;
26 import com.google.crypto.tink.TinkJsonProtoKeysetFormat;
27 import com.google.crypto.tink.daead.DeterministicAeadConfig;
28 import java.security.GeneralSecurityException;
29 import java.time.Clock;
30 import java.time.Instant;
31 import org.junit.BeforeClass;
32 import org.junit.experimental.theories.DataPoints;
33 import org.junit.experimental.theories.FromDataPoints;
34 import org.junit.experimental.theories.Theories;
35 import org.junit.experimental.theories.Theory;
36 import org.junit.runner.RunWith;
37 
38 /** Unit tests for the jwt package. Uses only the public API. */
39 @RunWith(Theories.class)
40 public final class JwtTest {
41 
42   @BeforeClass
setUp()43   public static void setUp() throws Exception {
44     JwtMacConfig.register();
45     DeterministicAeadConfig.register(); // Needed for getPrimitiveFromNonMacKeyset_throws.
46   }
47 
48   @DataPoints("jwt_mac_templates")
49   public static final String[] TEMPLATES =
50       new String[] {
51         "JWT_HS256",
52         "JWT_HS256_RAW",
53         "JWT_HS384",
54         "JWT_HS384_RAW",
55         "JWT_HS512",
56         "JWT_HS512_RAW",
57       };
58 
59   @Theory
createComputeVerifyJwtMac(@romDataPoints"jwt_mac_templates") String templateName)60   public void createComputeVerifyJwtMac(@FromDataPoints("jwt_mac_templates") String templateName)
61       throws Exception {
62     KeysetHandle handle = KeysetHandle.generateNew(KeyTemplates.get(templateName));
63     JwtMac jwtMac = handle.getPrimitive(JwtMac.class);
64     Instant now = Clock.systemUTC().instant();
65     RawJwt rawJwt =
66         RawJwt.newBuilder()
67             .setIssuer("issuer")
68             .addAudience("audience")
69             .setSubject("subject")
70             .addStringClaim("claimName", "claimValue")
71             .setExpiration(now.plusSeconds(100))
72             .build();
73     String token = jwtMac.computeMacAndEncode(rawJwt);
74 
75     JwtValidator validator =
76         JwtValidator.newBuilder().expectIssuer("issuer").expectAudience("audience").build();
77     VerifiedJwt verifiedJwt = jwtMac.verifyMacAndDecode(token, validator);
78     assertThat(verifiedJwt.getSubject()).isEqualTo("subject");
79     assertThat(verifiedJwt.getStringClaim("claimName")).isEqualTo("claimValue");
80 
81     String expiredToken =
82         jwtMac.computeMacAndEncode(
83             RawJwt.newBuilder()
84                 .setIssuer("issuer")
85                 .addAudience("audience")
86                 .setExpiration(now.minusSeconds(100))
87                 .build());
88     assertThrows(
89         GeneralSecurityException.class, () -> jwtMac.verifyMacAndDecode(expiredToken, validator));
90 
91     String tokenWithInvalidIssuer =
92         jwtMac.computeMacAndEncode(
93             RawJwt.newBuilder()
94                 .setIssuer("invalid")
95                 .addAudience("audience")
96                 .setSubject("subject")
97                 .addStringClaim("claimName", "claimValue")
98                 .setExpiration(now.minusSeconds(100))
99                 .build());
100     assertThrows(
101         GeneralSecurityException.class,
102         () -> jwtMac.verifyMacAndDecode(tokenWithInvalidIssuer, validator));
103 
104     String tokenWithInvalidAudience =
105         jwtMac.computeMacAndEncode(
106             RawJwt.newBuilder()
107                 .setIssuer("issuer")
108                 .addAudience("invalid")
109                 .setSubject("subject")
110                 .addStringClaim("claimName", "claimValue")
111                 .setExpiration(now.minusSeconds(100))
112                 .build());
113     assertThrows(
114         GeneralSecurityException.class,
115         () -> jwtMac.verifyMacAndDecode(tokenWithInvalidAudience, validator));
116 
117     KeysetHandle otherHandle = KeysetHandle.generateNew(KeyTemplates.get(templateName));
118     JwtMac otherJwtMac = otherHandle.getPrimitive(JwtMac.class);
119     assertThrows(
120         GeneralSecurityException.class, () -> otherJwtMac.verifyMacAndDecode(token, validator));
121 
122     assertThrows(
123         GeneralSecurityException.class, () -> jwtMac.verifyMacAndDecode("invalid", validator));
124     assertThrows(
125         GeneralSecurityException.class, () -> jwtMac.verifyMacAndDecode("", validator));
126   }
127 
128   // A keyset with one JWT MAC key, serialized in Tink's JSON format.
129   private static final String JSON_JWT_MAC_KEYSET =
130       ""
131           + "{"
132           + "  \"primaryKeyId\": 1685620571,"
133           + "  \"key\": ["
134           + "    {"
135           + "      \"keyData\": {"
136           + "        \"typeUrl\": \"type.googleapis.com/google.crypto.tink.JwtHmacKey\","
137           + "        \"value\": \"GiDmRwUiwKDsPHd+2mSHwlLfzvkgoV5meopVKp+GCbhHthAB\","
138           + "        \"keyMaterialType\": \"SYMMETRIC\""
139           + "      },"
140           + "      \"status\": \"ENABLED\","
141           + "      \"keyId\": 1685620571,"
142           + "      \"outputPrefixType\": \"TINK\""
143           + "    }"
144           + "  ]"
145           + "}";
146 
147   @Theory
readKeysetComputeVerifyJwtMac_success()148   public void readKeysetComputeVerifyJwtMac_success() throws Exception {
149     KeysetHandle handle =
150         TinkJsonProtoKeysetFormat.parseKeyset(JSON_JWT_MAC_KEYSET, InsecureSecretKeyAccess.get());
151     Instant now = Clock.systemUTC().instant();
152     JwtMac jwtMac = handle.getPrimitive(JwtMac.class);
153     RawJwt rawJwt =
154         RawJwt.newBuilder()
155             .setIssuer("issuer")
156             .addAudience("audience")
157             .setSubject("subject")
158             .setExpiration(now.plusSeconds(100))
159             .build();
160     String token = jwtMac.computeMacAndEncode(rawJwt);
161 
162     JwtValidator validator =
163         JwtValidator.newBuilder().expectIssuer("issuer").expectAudience("audience").build();
164     VerifiedJwt verifiedJwt = jwtMac.verifyMacAndDecode(token, validator);
165     assertThat(verifiedJwt.getSubject()).isEqualTo("subject");
166   }
167 
168   // A keyset with multiple keys. The first key is the same as in JSON_AEAD_KEYSET.
169   private static final String JSON_JWT_MAC_KEYSET_WITH_MULTIPLE_KEYS =
170       ""
171       + "{"
172       + "  \"primaryKeyId\": 648866621,"
173       + "  \"key\": ["
174       + "    {"
175       + "      \"keyData\": {"
176       + "        \"typeUrl\": \"type.googleapis.com/google.crypto.tink.JwtHmacKey\","
177       + "        \"value\": \"GiDmRwUiwKDsPHd+2mSHwlLfzvkgoV5meopVKp+GCbhHthAB\","
178       + "        \"keyMaterialType\": \"SYMMETRIC\""
179       + "      },"
180       + "      \"status\": \"ENABLED\","
181       + "      \"keyId\": 1685620571,"
182       + "      \"outputPrefixType\": \"TINK\""
183       + "    },"
184       + "    {"
185       + "      \"keyData\": {"
186       + "        \"typeUrl\": \"type.googleapis.com/google.crypto.tink.JwtHmacKey\","
187       + "        \"value\":"
188       + "\"GjBP5UIYeH40mAliduNPdvnkGqJci3mRpxjSHZ6jkBQ7ppuOGwpyBqsLobFspZOR+y0QAg==\","
189       + "        \"keyMaterialType\": \"SYMMETRIC\""
190       + "      },"
191       + "      \"status\": \"ENABLED\","
192       + "      \"keyId\": 648866621,"
193       + "      \"outputPrefixType\": \"RAW\""
194       + "    },"
195       + "    {"
196       + "      \"keyData\": {"
197       + "        \"typeUrl\": \"type.googleapis.com/google.crypto.tink.JwtHmacKey\","
198       + "        \"value\": \"GkAjSoAXaQXhp8oHfEBdPUxKLWIA1hYNc+905NFRt0tYbDcje8LlPdmfVi8"
199       + "Xno7+U1xc0EPPxGFGfKPcIetKccgoEAM=\","
200       + "        \"keyMaterialType\": \"SYMMETRIC\""
201       + "      },"
202       + "      \"status\": \"ENABLED\","
203       + "      \"keyId\": 923678323,"
204       + "      \"outputPrefixType\": \"TINK\""
205       + "    }"
206       + "  ]"
207       + "}";
208 
209   @Theory
multipleKeysReadKeysetComputeVerifyJwtMac_success()210   public void multipleKeysReadKeysetComputeVerifyJwtMac_success()
211       throws Exception {
212     KeysetHandle handle =
213         TinkJsonProtoKeysetFormat.parseKeyset(
214             JSON_JWT_MAC_KEYSET_WITH_MULTIPLE_KEYS, InsecureSecretKeyAccess.get());
215     Instant now = Clock.systemUTC().instant();
216     JwtMac jwtMac = handle.getPrimitive(JwtMac.class);
217     RawJwt rawJwt =
218         RawJwt.newBuilder()
219             .setIssuer("issuer")
220             .addAudience("audience")
221             .setSubject("subject")
222             .setExpiration(now.plusSeconds(100))
223             .build();
224     String token = jwtMac.computeMacAndEncode(rawJwt);
225     JwtValidator validator =
226         JwtValidator.newBuilder().expectIssuer("issuer").expectAudience("audience").build();
227     VerifiedJwt verifiedJwt = jwtMac.verifyMacAndDecode(token, validator);
228     assertThat(verifiedJwt.getSubject()).isEqualTo("subject");
229 
230     // Also test that jwtMac can verify tokens computed with a non-primary key. We use
231     // JSON_JWT_MAC_KEYSET to compute a tag with the first key.
232     KeysetHandle handle1 =
233         TinkJsonProtoKeysetFormat.parseKeyset(
234             JSON_JWT_MAC_KEYSET, InsecureSecretKeyAccess.get());
235     JwtMac jwtMac1 = handle1.getPrimitive(JwtMac.class);
236     String token1 = jwtMac1.computeMacAndEncode(rawJwt);
237     assertThat(jwtMac.verifyMacAndDecode(token1, validator).getSubject()).isEqualTo("subject");
238   }
239 
240   // A keyset with a valid DeterministicAead key. This keyset can't be used with the Mac primitive.
241   private static final String JSON_DAEAD_KEYSET =
242       ""
243           + "{"
244           + "  \"primaryKeyId\": 961932622,"
245           + "  \"key\": ["
246           + "    {"
247           + "      \"keyData\": {"
248           + "        \"typeUrl\": \"type.googleapis.com/google.crypto.tink.AesSivKey\","
249           + "        \"keyMaterialType\": \"SYMMETRIC\","
250           + "        \"value\": \"EkCJ9r5iwc5uxq5ugFyrHXh5dijTa7qalWUgZ8Gf08RxNd545FjtLMYL7ObcaFtCS"
251           + "kvV2+7u6F2DN+kqUjAfkf2W\""
252           + "      },"
253           + "      \"outputPrefixType\": \"TINK\","
254           + "      \"keyId\": 961932622,"
255           + "      \"status\": \"ENABLED\""
256           + "    }"
257           + "  ]"
258           + "}";
259 
260   @Theory
getPrimitiveFromNonMacKeyset_throws()261   public void getPrimitiveFromNonMacKeyset_throws() throws Exception {
262     KeysetHandle handle =
263         TinkJsonProtoKeysetFormat.parseKeyset(
264             JSON_DAEAD_KEYSET, InsecureSecretKeyAccess.get());
265     // Test that the keyset can create a DeterministicAead primitive, but not a JwtMac.
266     Object unused = handle.getPrimitive(DeterministicAead.class);
267     assertThrows(GeneralSecurityException.class, () -> handle.getPrimitive(JwtMac.class));
268   }
269 
270   // TODO(juerg): Add tests for Jwt signatures.
271 }
272