• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 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 import static org.junit.Assume.assumeTrue;
22 
23 import com.google.crypto.tink.DeterministicAead;
24 import com.google.crypto.tink.InsecureSecretKeyAccess;
25 import com.google.crypto.tink.KeyTemplates;
26 import com.google.crypto.tink.KeysetHandle;
27 import com.google.crypto.tink.RegistryConfiguration;
28 import com.google.crypto.tink.TinkJsonProtoKeysetFormat;
29 import com.google.crypto.tink.daead.DeterministicAeadConfig;
30 import com.google.crypto.tink.testing.TestUtil;
31 import java.security.GeneralSecurityException;
32 import java.time.Clock;
33 import java.time.Instant;
34 import org.junit.BeforeClass;
35 import org.junit.experimental.theories.DataPoints;
36 import org.junit.experimental.theories.FromDataPoints;
37 import org.junit.experimental.theories.Theories;
38 import org.junit.experimental.theories.Theory;
39 import org.junit.runner.RunWith;
40 
41 /** Tests the JWT signature primitives. Uses only the public API. */
42 @RunWith(Theories.class)
43 public final class JwtSignatureTest {
44 
45   @BeforeClass
setUp()46   public static void setUp() throws Exception {
47     JwtSignatureConfig.register();
48     DeterministicAeadConfig.register(); // Needed for getPrimitiveFromIncompatbileKeyset_throws.
49   }
50 
51   @DataPoints("jwt_signature_templates")
52   public static final String[] TEMPLATES =
53       new String[] {
54         "JWT_ES256", "JWT_ES512_RAW", "JWT_RS256_2048_F4", "JWT_PS256_3072_F4_RAW",
55       };
56 
57   @Theory
createSignVerifyJwt(@romDataPoints"jwt_signature_templates") String templateName)58   public void createSignVerifyJwt(@FromDataPoints("jwt_signature_templates") String templateName)
59       throws Exception {
60     if (TestUtil.isTsan()) {
61       // Only run for JWT_E256 under TSAN -- too slow otherwise.
62       assumeTrue(templateName.equals("JWT_ES256"));
63     }
64     KeysetHandle handle = KeysetHandle.generateNew(KeyTemplates.get(templateName));
65     JwtPublicKeySign jwtPublicKeySign =
66         handle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class);
67     Instant now = Clock.systemUTC().instant();
68     RawJwt rawJwt =
69         RawJwt.newBuilder()
70             .setIssuer("issuer")
71             .addAudience("audience")
72             .setSubject("subject")
73             .addStringClaim("claimName", "claimValue")
74             .setExpiration(now.plusSeconds(100))
75             .build();
76     String token = jwtPublicKeySign.signAndEncode(rawJwt);
77 
78     JwtPublicKeyVerify jwtPublicKeyVerify =
79         handle
80             .getPublicKeysetHandle()
81             .getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class);
82 
83     JwtValidator validator =
84         JwtValidator.newBuilder().expectIssuer("issuer").expectAudience("audience").build();
85     VerifiedJwt verifiedJwt = jwtPublicKeyVerify.verifyAndDecode(token, validator);
86     assertThat(verifiedJwt.getSubject()).isEqualTo("subject");
87     assertThat(verifiedJwt.getStringClaim("claimName")).isEqualTo("claimValue");
88 
89     String expiredToken =
90         jwtPublicKeySign.signAndEncode(
91             RawJwt.newBuilder()
92                 .setIssuer("issuer")
93                 .addAudience("audience")
94                 .setExpiration(now.minusSeconds(100))
95                 .build());
96     assertThrows(
97         GeneralSecurityException.class,
98         () -> jwtPublicKeyVerify.verifyAndDecode(expiredToken, validator));
99 
100     String tokenWithInvalidIssuer =
101         jwtPublicKeySign.signAndEncode(
102             RawJwt.newBuilder()
103                 .setIssuer("invalid")
104                 .addAudience("audience")
105                 .setSubject("subject")
106                 .addStringClaim("claimName", "claimValue")
107                 .setExpiration(now.minusSeconds(100))
108                 .build());
109     assertThrows(
110         GeneralSecurityException.class,
111         () -> jwtPublicKeyVerify.verifyAndDecode(tokenWithInvalidIssuer, validator));
112 
113     String tokenWithInvalidAudience =
114         jwtPublicKeySign.signAndEncode(
115             RawJwt.newBuilder()
116                 .setIssuer("issuer")
117                 .addAudience("invalid")
118                 .setSubject("subject")
119                 .addStringClaim("claimName", "claimValue")
120                 .setExpiration(now.minusSeconds(100))
121                 .build());
122     assertThrows(
123         GeneralSecurityException.class,
124         () -> jwtPublicKeyVerify.verifyAndDecode(tokenWithInvalidAudience, validator));
125 
126     KeysetHandle otherHandle = KeysetHandle.generateNew(KeyTemplates.get(templateName));
127     JwtPublicKeyVerify otherJwtPublicKeyVerify =
128         otherHandle
129             .getPublicKeysetHandle()
130             .getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class);
131     assertThrows(
132         GeneralSecurityException.class,
133         () -> otherJwtPublicKeyVerify.verifyAndDecode(token, validator));
134 
135     assertThrows(
136         GeneralSecurityException.class,
137         () -> jwtPublicKeyVerify.verifyAndDecode("invalid", validator));
138     assertThrows(
139         GeneralSecurityException.class, () -> jwtPublicKeyVerify.verifyAndDecode("", validator));
140   }
141 
142   // A keyset with one JWT public key sign keyset, serialized in Tink's JSON format.
143   private static final String JSON_JWT_PUBLIC_KEY_SIGN_KEYSET =
144       "{  \"primaryKeyId\": 1742360595,  \"key\": [    {      \"keyData\": {        \"typeUrl\":"
145           + " \"type.googleapis.com/google.crypto.tink.JwtEcdsaPrivateKey\",        \"value\":"
146           + " \"GiBgVYdAPg3Fa2FVFymGDYrI1trHMzVjhVNEMpIxG7t0HRJGIiBeoDMF9LS5BDCh6YgqE3DjHwWwnEKEI3WpPf8izEx1rRogbjQTXrTcw/1HKiiZm2Hqv41w7Vd44M9koyY/+VsP+SAQAQ==\","
147           + "        \"keyMaterialType\": \"ASYMMETRIC_PRIVATE\"      },      \"status\":"
148           + " \"ENABLED\",      \"keyId\": 1742360595,      \"outputPrefixType\": \"TINK\"    }  ]"
149           + "}";
150 
151   // A keyset with one JWT public key verify keyset, serialized in Tink's JSON format.
152   private static final String JSON_JWT_PUBLIC_KEY_VERIFY_KEYSET =
153       "{  \"primaryKeyId\": 1742360595,  \"key\": [    {      \"keyData\": {        \"typeUrl\":"
154           + " \"type.googleapis.com/google.crypto.tink.JwtEcdsaPublicKey\",        \"value\":"
155           + " \"EAEaIG40E1603MP9RyoomZth6r+NcO1XeODPZKMmP/lbD/kgIiBeoDMF9LS5BDCh6YgqE3DjHwWwnEKEI3WpPf8izEx1rQ==\","
156           + "        \"keyMaterialType\": \"ASYMMETRIC_PUBLIC\"      },      \"status\":"
157           + " \"ENABLED\",      \"keyId\": 1742360595,      \"outputPrefixType\": \"TINK\"    }  ]"
158           + "}";
159 
160   @Theory
readKeysetSignVerifyJwt_success()161   public void readKeysetSignVerifyJwt_success() throws Exception {
162     KeysetHandle privateHandle =
163         TinkJsonProtoKeysetFormat.parseKeyset(
164             JSON_JWT_PUBLIC_KEY_SIGN_KEYSET, InsecureSecretKeyAccess.get());
165     Instant now = Clock.systemUTC().instant();
166     JwtPublicKeySign jwtPublicKeySign =
167         privateHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class);
168     RawJwt rawJwt =
169         RawJwt.newBuilder()
170             .setIssuer("issuer")
171             .addAudience("audience")
172             .setSubject("subject")
173             .setExpiration(now.plusSeconds(100))
174             .build();
175     String token = jwtPublicKeySign.signAndEncode(rawJwt);
176 
177     KeysetHandle publicHandle =
178         TinkJsonProtoKeysetFormat.parseKeyset(
179             JSON_JWT_PUBLIC_KEY_VERIFY_KEYSET, InsecureSecretKeyAccess.get());
180     JwtPublicKeyVerify jwtPublicKeyVerify =
181         publicHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class);
182     JwtValidator validator =
183         JwtValidator.newBuilder().expectIssuer("issuer").expectAudience("audience").build();
184     VerifiedJwt verifiedJwt = jwtPublicKeyVerify.verifyAndDecode(token, validator);
185     assertThat(verifiedJwt.getSubject()).isEqualTo("subject");
186   }
187 
188   // A keyset with a valid DeterministicAead key. This keyset can't be used with the
189   // JwtPublicKeySign or JwtPublicKeyVerify primitive.
190   private static final String JSON_DAEAD_KEYSET =
191       ""
192           + "{"
193           + "  \"primaryKeyId\": 961932622,"
194           + "  \"key\": ["
195           + "    {"
196           + "      \"keyData\": {"
197           + "        \"typeUrl\": \"type.googleapis.com/google.crypto.tink.AesSivKey\","
198           + "        \"keyMaterialType\": \"SYMMETRIC\","
199           + "        \"value\": \"EkCJ9r5iwc5uxq5ugFyrHXh5dijTa7qalWUgZ8Gf08RxNd545FjtLMYL7ObcaFtCS"
200           + "kvV2+7u6F2DN+kqUjAfkf2W\""
201           + "      },"
202           + "      \"outputPrefixType\": \"TINK\","
203           + "      \"keyId\": 961932622,"
204           + "      \"status\": \"ENABLED\""
205           + "    }"
206           + "  ]"
207           + "}";
208 
209   @Theory
getPrimitiveFromIncompatbileKeyset_throws()210   public void getPrimitiveFromIncompatbileKeyset_throws() throws Exception {
211     KeysetHandle handle =
212         TinkJsonProtoKeysetFormat.parseKeyset(JSON_DAEAD_KEYSET, InsecureSecretKeyAccess.get());
213     Object unused = handle.getPrimitive(RegistryConfiguration.get(), DeterministicAead.class);
214     assertThrows(
215         GeneralSecurityException.class,
216         () -> handle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class));
217     assertThrows(
218         GeneralSecurityException.class,
219         () -> handle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class));
220   }
221 }
222