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