1 // Copyright 2020 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.InsecureSecretKeyAccess; 23 import com.google.crypto.tink.KeyTemplate; 24 import com.google.crypto.tink.KeyTemplates; 25 import com.google.crypto.tink.KeysetHandle; 26 import com.google.crypto.tink.KeysetManager; 27 import com.google.crypto.tink.RegistryConfiguration; 28 import com.google.crypto.tink.TinkProtoKeysetFormat; 29 import com.google.crypto.tink.internal.MonitoringAnnotations; 30 import com.google.crypto.tink.internal.MutableMonitoringRegistry; 31 import com.google.crypto.tink.internal.testing.FakeMonitoringClient; 32 import com.google.crypto.tink.proto.Keyset; 33 import com.google.crypto.tink.proto.OutputPrefixType; 34 import com.google.protobuf.ExtensionRegistryLite; 35 import java.security.GeneralSecurityException; 36 import java.time.Clock; 37 import java.time.Instant; 38 import java.time.temporal.ChronoUnit; 39 import java.util.List; 40 import org.junit.Before; 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 import org.junit.runners.JUnit4; 44 45 /** Tests for JwtMacWrapper. */ 46 @RunWith(JUnit4.class) 47 public class JwtMacWrapperTest { 48 49 @Before setUp()50 public void setUp() throws GeneralSecurityException { 51 JwtMacConfig.register(); 52 } 53 54 @Test test_wrapNoPrimary_throws()55 public void test_wrapNoPrimary_throws() throws Exception { 56 // The old KeysetManager API allows keysets without primary key. 57 // The KeysetHandle.Builder does not allow this and can't be used in this test. 58 KeyTemplate template = KeyTemplates.get("JWT_HS256"); 59 KeysetManager manager = KeysetManager.withEmptyKeyset().add(template); 60 KeysetHandle handle = manager.getKeysetHandle(); 61 assertThrows( 62 GeneralSecurityException.class, 63 () -> handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class)); 64 } 65 66 @Test test_wrapLegacy_throws()67 public void test_wrapLegacy_throws() throws Exception { 68 KeyTemplate rawTemplate = KeyTemplates.get("JWT_HS256_RAW"); 69 // Convert the normal, raw template into a template with output prefix type LEGACY 70 KeysetHandle handle = KeysetHandle.generateNew(rawTemplate); 71 Keyset keyset = 72 Keyset.parseFrom( 73 TinkProtoKeysetFormat.serializeKeyset(handle, InsecureSecretKeyAccess.get()), 74 ExtensionRegistryLite.getEmptyRegistry()); 75 Keyset.Builder legacyKeysetBuilder = keyset.toBuilder(); 76 legacyKeysetBuilder.setKey( 77 0, legacyKeysetBuilder.getKey(0).toBuilder().setOutputPrefixType(OutputPrefixType.LEGACY)); 78 KeysetHandle legacyHandle = 79 TinkProtoKeysetFormat.parseKeyset( 80 legacyKeysetBuilder.build().toByteArray(), InsecureSecretKeyAccess.get()); 81 assertThrows( 82 GeneralSecurityException.class, 83 () -> legacyHandle.getPrimitive(RegistryConfiguration.get(), JwtMac.class)); 84 } 85 86 @Test test_wrapSingleRawKey_works()87 public void test_wrapSingleRawKey_works() throws Exception { 88 KeyTemplate template = KeyTemplates.get("JWT_HS256_RAW"); 89 KeysetHandle handle = KeysetHandle.generateNew(template); 90 91 JwtMac jwtMac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class); 92 RawJwt rawToken = RawJwt.newBuilder().setJwtId("id123").withoutExpiration().build(); 93 String signedCompact = jwtMac.computeMacAndEncode(rawToken); 94 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 95 VerifiedJwt verifiedToken = jwtMac.verifyMacAndDecode(signedCompact, validator); 96 assertThat(verifiedToken.getJwtId()).isEqualTo("id123"); 97 } 98 99 @Test test_wrapSingleTinkKey_works()100 public void test_wrapSingleTinkKey_works() throws Exception { 101 KeyTemplate tinkTemplate = KeyTemplates.get("JWT_HS256"); 102 KeysetHandle handle = KeysetHandle.generateNew(tinkTemplate); 103 JwtMac jwtMac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class); 104 RawJwt rawJwt = RawJwt.newBuilder().setJwtId("id123").withoutExpiration().build(); 105 String signedCompact = jwtMac.computeMacAndEncode(rawJwt); 106 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 107 VerifiedJwt verifiedToken = jwtMac.verifyMacAndDecode(signedCompact, validator); 108 assertThat(verifiedToken.getJwtId()).isEqualTo("id123"); 109 } 110 111 @Test test_wrapMultipleRawKeys()112 public void test_wrapMultipleRawKeys() throws Exception { 113 KeysetHandle oldHandle = 114 KeysetHandle.newBuilder() 115 .addEntry( 116 KeysetHandle.generateEntryFromParametersName("JWT_HS256_RAW") 117 .withRandomId() 118 .makePrimary()) 119 .build(); 120 KeysetHandle newHandle = 121 KeysetHandle.newBuilder(oldHandle) 122 .addEntry( 123 KeysetHandle.generateEntryFromParametersName("JWT_HS256_RAW") 124 .withRandomId() 125 .makePrimary()) 126 .build(); 127 128 JwtMac oldJwtMac = oldHandle.getPrimitive(RegistryConfiguration.get(), JwtMac.class); 129 JwtMac newJwtMac = newHandle.getPrimitive(RegistryConfiguration.get(), JwtMac.class); 130 131 RawJwt rawToken = RawJwt.newBuilder().setJwtId("jwtId").withoutExpiration().build(); 132 String oldSignedCompact = oldJwtMac.computeMacAndEncode(rawToken); 133 String newSignedCompact = newJwtMac.computeMacAndEncode(rawToken); 134 135 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 136 assertThat(oldJwtMac.verifyMacAndDecode(oldSignedCompact, validator).getJwtId()) 137 .isEqualTo("jwtId"); 138 assertThat(newJwtMac.verifyMacAndDecode(oldSignedCompact, validator).getJwtId()) 139 .isEqualTo("jwtId"); 140 assertThat(newJwtMac.verifyMacAndDecode(newSignedCompact, validator).getJwtId()) 141 .isEqualTo("jwtId"); 142 assertThrows( 143 GeneralSecurityException.class, 144 () -> oldJwtMac.verifyMacAndDecode(newSignedCompact, validator)); 145 } 146 147 @Test test_wrapMultipleTinkKeys()148 public void test_wrapMultipleTinkKeys() throws Exception { 149 KeysetHandle oldHandle = 150 KeysetHandle.newBuilder() 151 .addEntry( 152 KeysetHandle.generateEntryFromParametersName("JWT_HS256") 153 .withRandomId() 154 .makePrimary()) 155 .build(); 156 KeysetHandle newHandle = 157 KeysetHandle.newBuilder(oldHandle) 158 .addEntry( 159 KeysetHandle.generateEntryFromParametersName("JWT_HS256") 160 .withRandomId() 161 .makePrimary()) 162 .build(); 163 164 JwtMac oldJwtMac = oldHandle.getPrimitive(RegistryConfiguration.get(), JwtMac.class); 165 JwtMac newJwtMac = newHandle.getPrimitive(RegistryConfiguration.get(), JwtMac.class); 166 167 RawJwt rawToken = RawJwt.newBuilder().setJwtId("jwtId").withoutExpiration().build(); 168 String oldSignedCompact = oldJwtMac.computeMacAndEncode(rawToken); 169 String newSignedCompact = newJwtMac.computeMacAndEncode(rawToken); 170 171 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 172 assertThat(oldJwtMac.verifyMacAndDecode(oldSignedCompact, validator).getJwtId()) 173 .isEqualTo("jwtId"); 174 assertThat(newJwtMac.verifyMacAndDecode(oldSignedCompact, validator).getJwtId()) 175 .isEqualTo("jwtId"); 176 assertThat(newJwtMac.verifyMacAndDecode(newSignedCompact, validator).getJwtId()) 177 .isEqualTo("jwtId"); 178 assertThrows( 179 GeneralSecurityException.class, 180 () -> oldJwtMac.verifyMacAndDecode(newSignedCompact, validator)); 181 } 182 183 @Test wrongKey_throwsInvalidSignatureException()184 public void wrongKey_throwsInvalidSignatureException() throws Exception { 185 KeysetHandle keysetHandle = KeysetHandle.generateNew(KeyTemplates.get("JWT_HS256")); 186 JwtMac jwtMac = keysetHandle.getPrimitive(RegistryConfiguration.get(), JwtMac.class); 187 RawJwt rawJwt = RawJwt.newBuilder().withoutExpiration().build(); 188 String compact = jwtMac.computeMacAndEncode(rawJwt); 189 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 190 191 KeysetHandle wrongKeysetHandle = KeysetHandle.generateNew(KeyTemplates.get("JWT_HS256")); 192 JwtMac wrongJwtMac = wrongKeysetHandle.getPrimitive(RegistryConfiguration.get(), JwtMac.class); 193 assertThrows( 194 GeneralSecurityException.class, () -> wrongJwtMac.verifyMacAndDecode(compact, validator)); 195 } 196 197 @Test wrongIssuer_throwsInvalidException()198 public void wrongIssuer_throwsInvalidException() throws Exception { 199 KeysetHandle keysetHandle = KeysetHandle.generateNew(KeyTemplates.get("JWT_HS256")); 200 JwtMac jwtMac = keysetHandle.getPrimitive(RegistryConfiguration.get(), JwtMac.class); 201 RawJwt rawJwt = RawJwt.newBuilder().setIssuer("Justus").withoutExpiration().build(); 202 String compact = jwtMac.computeMacAndEncode(rawJwt); 203 JwtValidator validator = 204 JwtValidator.newBuilder().allowMissingExpiration().expectIssuer("Peter").build(); 205 assertThrows(JwtInvalidException.class, () -> jwtMac.verifyMacAndDecode(compact, validator)); 206 } 207 208 @Test expiredCompact_throwsExpiredException()209 public void expiredCompact_throwsExpiredException() throws Exception { 210 KeysetHandle keysetHandle = KeysetHandle.generateNew(KeyTemplates.get("JWT_HS256")); 211 JwtMac jwtMac = keysetHandle.getPrimitive(RegistryConfiguration.get(), JwtMac.class); 212 Instant now = Clock.systemUTC().instant().truncatedTo(ChronoUnit.SECONDS); 213 RawJwt rawJwt = 214 RawJwt.newBuilder() 215 .setExpiration(now.minusSeconds(100)) // exipired 100 seconds ago 216 .setIssuedAt(now.minusSeconds(200)) 217 .build(); 218 String compact = jwtMac.computeMacAndEncode(rawJwt); 219 JwtValidator validator = JwtValidator.newBuilder().build(); 220 assertThrows(JwtInvalidException.class, () -> jwtMac.verifyMacAndDecode(compact, validator)); 221 } 222 223 @Test notYetValidCompact_throwsNotBeforeException()224 public void notYetValidCompact_throwsNotBeforeException() throws Exception { 225 KeysetHandle keysetHandle = KeysetHandle.generateNew(KeyTemplates.get("JWT_HS256")); 226 JwtMac jwtMac = keysetHandle.getPrimitive(RegistryConfiguration.get(), JwtMac.class); 227 228 Instant now = Clock.systemUTC().instant().truncatedTo(ChronoUnit.SECONDS); 229 RawJwt rawJwt = 230 RawJwt.newBuilder() 231 .setNotBefore(now.plusSeconds(3600)) // is valid in 1 hour, but not before 232 .setIssuedAt(now) 233 .withoutExpiration() 234 .build(); 235 String compact = jwtMac.computeMacAndEncode(rawJwt); 236 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 237 assertThrows(JwtInvalidException.class, () -> jwtMac.verifyMacAndDecode(compact, validator)); 238 } 239 240 @Test testWithoutAnnotations_hasNoMonitoring()241 public void testWithoutAnnotations_hasNoMonitoring() throws Exception { 242 FakeMonitoringClient fakeMonitoringClient = new FakeMonitoringClient(); 243 MutableMonitoringRegistry.globalInstance().clear(); 244 MutableMonitoringRegistry.globalInstance().registerMonitoringClient(fakeMonitoringClient); 245 246 KeysetHandle keysetHandle = 247 KeysetHandle.newBuilder() 248 .addEntry( 249 KeysetHandle.generateEntryFromParametersName("JWT_HS256") 250 .makePrimary() 251 .withFixedId(42)) 252 .build(); 253 JwtMac jwtMac = keysetHandle.getPrimitive(RegistryConfiguration.get(), JwtMac.class); 254 RawJwt rawJwt = RawJwt.newBuilder().setJwtId("id123").withoutExpiration().build(); 255 String signedCompact = jwtMac.computeMacAndEncode(rawJwt); 256 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 257 VerifiedJwt verifiedToken = jwtMac.verifyMacAndDecode(signedCompact, validator); 258 assertThat(verifiedToken.getJwtId()).isEqualTo("id123"); 259 assertThrows( 260 GeneralSecurityException.class, () -> jwtMac.verifyMacAndDecode("invalid", validator)); 261 262 assertThat(fakeMonitoringClient.getLogEntries()).isEmpty(); 263 assertThat(fakeMonitoringClient.getLogFailureEntries()).isEmpty(); 264 } 265 266 @Test testWithAnnotations_hasMonitoring()267 public void testWithAnnotations_hasMonitoring() throws Exception { 268 FakeMonitoringClient fakeMonitoringClient = new FakeMonitoringClient(); 269 MutableMonitoringRegistry.globalInstance().clear(); 270 MutableMonitoringRegistry.globalInstance().registerMonitoringClient(fakeMonitoringClient); 271 272 MonitoringAnnotations annotations = 273 MonitoringAnnotations.newBuilder().add("annotation_name", "annotation_value").build(); 274 KeysetHandle keysetHandle = 275 KeysetHandle.newBuilder() 276 .addEntry( 277 KeysetHandle.generateEntryFromParametersName("JWT_HS256") 278 .makePrimary() 279 .withFixedId(42)) 280 .setMonitoringAnnotations(annotations) 281 .build(); 282 283 JwtMac jwtMac = keysetHandle.getPrimitive(RegistryConfiguration.get(), JwtMac.class); 284 RawJwt rawJwt = RawJwt.newBuilder().setJwtId("id123").withoutExpiration().build(); 285 String signedCompact = jwtMac.computeMacAndEncode(rawJwt); 286 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 287 VerifiedJwt verifiedToken = jwtMac.verifyMacAndDecode(signedCompact, validator); 288 assertThat(verifiedToken.getJwtId()).isEqualTo("id123"); 289 assertThrows( 290 GeneralSecurityException.class, () -> jwtMac.verifyMacAndDecode("invalid", validator)); 291 292 List<FakeMonitoringClient.LogEntry> logEntries = fakeMonitoringClient.getLogEntries(); 293 assertThat(logEntries).hasSize(2); 294 FakeMonitoringClient.LogEntry computeEntry = logEntries.get(0); 295 assertThat(computeEntry.getKeyId()).isEqualTo(42); 296 assertThat(computeEntry.getPrimitive()).isEqualTo("jwtmac"); 297 assertThat(computeEntry.getApi()).isEqualTo("compute"); 298 assertThat(computeEntry.getNumBytesAsInput()).isEqualTo(1); 299 assertThat(computeEntry.getKeysetInfo().getAnnotations()).isEqualTo(annotations); 300 301 FakeMonitoringClient.LogEntry verifyEntry = logEntries.get(1); 302 assertThat(verifyEntry.getKeyId()).isEqualTo(42); 303 assertThat(verifyEntry.getPrimitive()).isEqualTo("jwtmac"); 304 assertThat(verifyEntry.getApi()).isEqualTo("verify"); 305 assertThat(verifyEntry.getNumBytesAsInput()).isEqualTo(1); 306 assertThat(verifyEntry.getKeysetInfo().getAnnotations()).isEqualTo(annotations); 307 308 List<FakeMonitoringClient.LogFailureEntry> failures = 309 fakeMonitoringClient.getLogFailureEntries(); 310 assertThat(failures).hasSize(1); 311 FakeMonitoringClient.LogFailureEntry verifyFailure = failures.get(0); 312 assertThat(verifyFailure.getPrimitive()).isEqualTo("jwtmac"); 313 assertThat(verifyFailure.getApi()).isEqualTo("verify"); 314 assertThat(verifyFailure.getKeysetInfo().getPrimaryKeyId()).isEqualTo(42); 315 assertThat(verifyFailure.getKeysetInfo().getAnnotations()).isEqualTo(annotations); 316 } 317 } 318