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; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static java.nio.charset.StandardCharsets.UTF_8; 21 import static org.junit.Assert.assertThrows; 22 23 import com.google.crypto.tink.aead.AeadConfig; 24 import com.google.crypto.tink.aead.AesGcmParameters; 25 import com.google.crypto.tink.mac.MacConfig; 26 import com.google.crypto.tink.proto.KeyData; 27 import com.google.crypto.tink.proto.KeyStatusType; 28 import com.google.crypto.tink.proto.Keyset; 29 import com.google.crypto.tink.proto.OutputPrefixType; 30 import com.google.crypto.tink.signature.SignatureConfig; 31 import com.google.crypto.tink.subtle.Hex; 32 import com.google.protobuf.ByteString; 33 import java.io.ByteArrayOutputStream; 34 import java.security.GeneralSecurityException; 35 import org.junit.BeforeClass; 36 import org.junit.Test; 37 import org.junit.runner.RunWith; 38 import org.junit.runners.JUnit4; 39 40 @RunWith(JUnit4.class) 41 public final class TinkProtoKeysetFormatTest { 42 43 @BeforeClass setUp()44 public static void setUp() throws GeneralSecurityException { 45 MacConfig.register(); 46 AeadConfig.register(); 47 SignatureConfig.register(); 48 } 49 assertKeysetHandleAreEqual(KeysetHandle keysetHandle1, KeysetHandle keysetHandle2)50 private void assertKeysetHandleAreEqual(KeysetHandle keysetHandle1, KeysetHandle keysetHandle2) 51 throws Exception { 52 assertThat(keysetHandle2.equalsKeyset(keysetHandle1)).isTrue(); 53 } 54 generateKeyset()55 private KeysetHandle generateKeyset() throws GeneralSecurityException { 56 return KeysetHandle.newBuilder() 57 .addEntry( 58 KeysetHandle.generateEntryFromParametersName("HMAC_SHA256_128BITTAG") 59 .withRandomId() 60 .makePrimary()) 61 .addEntry( 62 KeysetHandle.generateEntryFromParametersName("HMAC_SHA256_128BITTAG_RAW") 63 .withRandomId()) 64 .addEntry( 65 KeysetHandle.generateEntryFromParametersName("HMAC_SHA256_256BITTAG") 66 .withRandomId() 67 .setStatus(KeyStatus.DESTROYED)) 68 .addEntry( 69 KeysetHandle.generateEntryFromParametersName("HMAC_SHA256_256BITTAG_RAW") 70 .withRandomId() 71 .setStatus(KeyStatus.DISABLED)) 72 .addEntry(KeysetHandle.generateEntryFromParametersName("AES256_CMAC").withRandomId()) 73 .build(); 74 } 75 generatePublicKeyset()76 private KeysetHandle generatePublicKeyset() throws GeneralSecurityException { 77 return KeysetHandle.newBuilder() 78 .addEntry( 79 KeysetHandle.generateEntryFromParametersName("ECDSA_P256_RAW") 80 .withRandomId() 81 .setStatus(KeyStatus.DISABLED)) 82 .addEntry( 83 KeysetHandle.generateEntryFromParametersName("ECDSA_P256").withRandomId().makePrimary()) 84 .addEntry( 85 KeysetHandle.generateEntryFromParametersName("ECDSA_P521") 86 .withRandomId() 87 .setStatus(KeyStatus.DESTROYED)) 88 .build() 89 .getPublicKeysetHandle(); 90 } 91 generateAead()92 private Aead generateAead() throws GeneralSecurityException { 93 KeysetHandle handle = 94 KeysetHandle.newBuilder() 95 .addEntry( 96 KeysetHandle.generateEntryFromParametersName("AES128_CTR_HMAC_SHA256") 97 .withRandomId() 98 .makePrimary()) 99 .build(); 100 return handle.getPrimitive(RegistryConfiguration.get(), Aead.class); 101 } 102 103 @Test serializeAndParse_successWithSameKeyset()104 public void serializeAndParse_successWithSameKeyset() throws Exception { 105 KeysetHandle keysetHandle = generateKeyset(); 106 107 byte[] serializedKeyset = 108 TinkProtoKeysetFormat.serializeKeyset(keysetHandle, InsecureSecretKeyAccess.get()); 109 KeysetHandle parseKeysetHandle = 110 TinkProtoKeysetFormat.parseKeyset(serializedKeyset, InsecureSecretKeyAccess.get()); 111 112 assertKeysetHandleAreEqual(keysetHandle, parseKeysetHandle); 113 } 114 115 @Test serializeKeyset_withoutInsecureSecretKeyAccess_fails()116 public void serializeKeyset_withoutInsecureSecretKeyAccess_fails() throws Exception { 117 KeysetHandle keysetHandle = generateKeyset(); 118 119 assertThrows( 120 NullPointerException.class, 121 () -> TinkProtoKeysetFormat.serializeKeyset(keysetHandle, null)); 122 } 123 124 @Test parseKeyset_withoutInsecureSecretKeyAccess_fails()125 public void parseKeyset_withoutInsecureSecretKeyAccess_fails() throws Exception { 126 byte[] serializedKeyset = 127 TinkProtoKeysetFormat.serializeKeyset(generateKeyset(), InsecureSecretKeyAccess.get()); 128 129 assertThrows( 130 NullPointerException.class, 131 () -> TinkProtoKeysetFormat.parseKeyset(serializedKeyset, null)); 132 } 133 134 @Test parseInvalidSerializedKeyset_fails()135 public void parseInvalidSerializedKeyset_fails() throws Exception { 136 byte[] invalidSerializedKeyset = "invalid".getBytes(UTF_8); 137 assertThrows( 138 GeneralSecurityException.class, 139 () -> 140 TinkProtoKeysetFormat.parseKeyset( 141 invalidSerializedKeyset, InsecureSecretKeyAccess.get())); 142 } 143 144 @Test parsingKeysetWithUnknownStatus_doesNotThrowButGetAtThrows()145 public void parsingKeysetWithUnknownStatus_doesNotThrowButGetAtThrows() throws Exception { 146 Keyset keyset = 147 Keyset.newBuilder() 148 .addKey( 149 Keyset.Key.newBuilder() 150 .setKeyData( 151 KeyData.newBuilder() 152 .setValue(ByteString.copyFromUtf8("value")) 153 .setTypeUrl("unknown") 154 .setKeyMaterialType(KeyData.KeyMaterialType.SYMMETRIC) 155 .build()) 156 .setStatus(KeyStatusType.UNKNOWN_STATUS) 157 .setKeyId(123) 158 .setOutputPrefixType(OutputPrefixType.TINK) 159 .build()) 160 .setPrimaryKeyId(123) 161 .build(); 162 KeysetHandle handle = 163 TinkProtoKeysetFormat.parseKeyset(keyset.toByteArray(), InsecureSecretKeyAccess.get()); 164 assertThrows(IllegalStateException.class, () -> handle.getAt(0)); 165 166 // re-parse the KeysetHandle, as suggested in documentation of getAt. 167 assertThrows(GeneralSecurityException.class, () -> KeysetHandle.newBuilder(handle).build()); 168 } 169 170 @Test parsingKeysetWithNonAsciiTypeUrl_doesNotThrowButGetAtThrows()171 public void parsingKeysetWithNonAsciiTypeUrl_doesNotThrowButGetAtThrows() throws Exception { 172 Keyset keyset = 173 Keyset.newBuilder() 174 .addKey( 175 Keyset.Key.newBuilder() 176 .setKeyData( 177 KeyData.newBuilder() 178 .setValue(ByteString.copyFromUtf8("value")) 179 .setTypeUrl("\t") 180 .setKeyMaterialType(KeyData.KeyMaterialType.SYMMETRIC) 181 .build()) 182 .setStatus(KeyStatusType.ENABLED) 183 .setKeyId(123) 184 .setOutputPrefixType(OutputPrefixType.TINK) 185 .build()) 186 .setPrimaryKeyId(123) 187 .build(); 188 KeysetHandle handle = 189 TinkProtoKeysetFormat.parseKeyset(keyset.toByteArray(), InsecureSecretKeyAccess.get()); 190 assertThrows(IllegalStateException.class, () -> handle.getAt(0)); 191 192 // re-parse the KeysetHandle, as suggested in documentation of getAt. 193 assertThrows(GeneralSecurityException.class, () -> KeysetHandle.newBuilder(handle).build()); 194 } 195 196 @Test serializeEncryptedAndParseEncrypted_successWithSameKeyset()197 public void serializeEncryptedAndParseEncrypted_successWithSameKeyset() throws Exception { 198 Aead keyEncryptionAead = generateAead(); 199 KeysetHandle keysetHandle = generateKeyset(); 200 byte[] associatedData = "associatedData".getBytes(UTF_8); 201 202 byte[] serializedKeyset = 203 TinkProtoKeysetFormat.serializeEncryptedKeyset( 204 keysetHandle, keyEncryptionAead, associatedData); 205 KeysetHandle parseKeysetHandle = 206 TinkProtoKeysetFormat.parseEncryptedKeyset( 207 serializedKeyset, keyEncryptionAead, associatedData); 208 209 assertKeysetHandleAreEqual(keysetHandle, parseKeysetHandle); 210 } 211 212 @Test parseEncryptedKeysetWithInvalidKey_fails()213 public void parseEncryptedKeysetWithInvalidKey_fails() throws Exception { 214 Aead keyEncryptionAead = generateAead(); 215 Aead invalidKeyEncryptionAead = generateAead(); 216 KeysetHandle keysetHandle = generateKeyset(); 217 byte[] associatedData = "associatedData".getBytes(UTF_8); 218 219 byte[] serializedKeyset = 220 TinkProtoKeysetFormat.serializeEncryptedKeyset( 221 keysetHandle, keyEncryptionAead, associatedData); 222 223 assertThrows( 224 GeneralSecurityException.class, 225 () -> 226 TinkProtoKeysetFormat.parseEncryptedKeyset( 227 serializedKeyset, invalidKeyEncryptionAead, associatedData)); 228 } 229 230 @Test parseEncryptedKeysetWithInvalidAssociatedData_fails()231 public void parseEncryptedKeysetWithInvalidAssociatedData_fails() throws Exception { 232 Aead keyEncryptionAead = generateAead(); 233 KeysetHandle keysetHandle = generateKeyset(); 234 235 byte[] serializedKeyset = 236 TinkProtoKeysetFormat.serializeEncryptedKeyset( 237 keysetHandle, keyEncryptionAead, "associatedData".getBytes(UTF_8)); 238 239 assertThrows( 240 GeneralSecurityException.class, 241 () -> 242 TinkProtoKeysetFormat.parseEncryptedKeyset( 243 serializedKeyset, keyEncryptionAead, "invalidAssociatedData".getBytes(UTF_8))); 244 } 245 246 @Test serializeAndParseWithoutSecret_successWithSameKeyset()247 public void serializeAndParseWithoutSecret_successWithSameKeyset() throws Exception { 248 KeysetHandle publicKeysetHandle = generatePublicKeyset(); 249 250 byte[] serializedKeyset = 251 TinkProtoKeysetFormat.serializeKeysetWithoutSecret(publicKeysetHandle); 252 KeysetHandle parsePublicKeysetHandle = 253 TinkProtoKeysetFormat.parseKeysetWithoutSecret(serializedKeyset); 254 255 assertKeysetHandleAreEqual(publicKeysetHandle, parsePublicKeysetHandle); 256 } 257 258 @Test serializeWithoutSecret_keysetWithSecretKeys_fails()259 public void serializeWithoutSecret_keysetWithSecretKeys_fails() throws Exception { 260 KeysetHandle secretKeysetHandle = generateKeyset(); 261 262 assertThrows( 263 GeneralSecurityException.class, 264 () -> 265 TinkProtoKeysetFormat.serializeKeysetWithoutSecret(secretKeysetHandle)); 266 } 267 268 @Test parseWithoutSecret_keysetWithSecretKeys_fails()269 public void parseWithoutSecret_keysetWithSecretKeys_fails() throws Exception { 270 KeysetHandle secretKeysetHandle = generateKeyset(); 271 byte[] serializedSecretKeyset = 272 TinkProtoKeysetFormat.serializeKeyset(secretKeysetHandle, InsecureSecretKeyAccess.get()); 273 274 assertThrows( 275 GeneralSecurityException.class, 276 () -> 277 TinkProtoKeysetFormat.parseKeysetWithoutSecret(serializedSecretKeyset)); 278 } 279 280 @Test parseWithoutSecretInvalidSerializedKeyset_fails()281 public void parseWithoutSecretInvalidSerializedKeyset_fails() throws Exception { 282 byte[] invalidSerializedKeyset = "invalid".getBytes(UTF_8); 283 assertThrows( 284 GeneralSecurityException.class, 285 () -> TinkProtoKeysetFormat.parseKeysetWithoutSecret(invalidSerializedKeyset)); 286 } 287 288 @Test serializeKeyset_worksWithCleartextKeysetHandleReadAndBinaryKeysetReader()289 public void serializeKeyset_worksWithCleartextKeysetHandleReadAndBinaryKeysetReader() 290 throws Exception { 291 KeysetHandle keysetHandle = generateKeyset(); 292 293 byte[] serializedKeyset = 294 TinkProtoKeysetFormat.serializeKeyset(keysetHandle, InsecureSecretKeyAccess.get()); 295 296 KeysetHandle parseKeysetHandle = 297 CleartextKeysetHandle.read(BinaryKeysetReader.withBytes(serializedKeyset)); 298 299 assertKeysetHandleAreEqual(keysetHandle, parseKeysetHandle); 300 } 301 302 @Test parseKeyset_worksWithCleartextKeysetHandleWriteAndBinaryKeysetWriter()303 public void parseKeyset_worksWithCleartextKeysetHandleWriteAndBinaryKeysetWriter() 304 throws Exception { 305 KeysetHandle keysetHandle = generateKeyset(); 306 307 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 308 CleartextKeysetHandle.write(keysetHandle, BinaryKeysetWriter.withOutputStream(outputStream)); 309 byte[] serializedKeyset = outputStream.toByteArray(); 310 311 KeysetHandle parseKeysetHandle = 312 TinkProtoKeysetFormat.parseKeyset(serializedKeyset, InsecureSecretKeyAccess.get()); 313 314 assertKeysetHandleAreEqual(keysetHandle, parseKeysetHandle); 315 } 316 317 @Test serializeKeysetWithoutSecret_worksWithKeysetHandleReadNoSecretAndBinaryKeysetReader()318 public void serializeKeysetWithoutSecret_worksWithKeysetHandleReadNoSecretAndBinaryKeysetReader() 319 throws Exception { 320 KeysetHandle publicKeysetHandle = generatePublicKeyset(); 321 322 byte[] serializedKeyset = 323 TinkProtoKeysetFormat.serializeKeysetWithoutSecret(publicKeysetHandle); 324 325 KeysetHandle parsePublicKeysetHandle = 326 KeysetHandle.readNoSecret(BinaryKeysetReader.withBytes(serializedKeyset)); 327 328 assertKeysetHandleAreEqual(publicKeysetHandle, parsePublicKeysetHandle); 329 } 330 331 @Test parseKeysetWithoutSecret_worksWithKeysetHandleWriteNoSecretAndBinaryKeysetWriter()332 public void parseKeysetWithoutSecret_worksWithKeysetHandleWriteNoSecretAndBinaryKeysetWriter() 333 throws Exception { 334 KeysetHandle publicKeysetHandle = generatePublicKeyset(); 335 336 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 337 publicKeysetHandle.writeNoSecret(BinaryKeysetWriter.withOutputStream(outputStream)); 338 byte[] serializedKeyset = outputStream.toByteArray(); 339 340 KeysetHandle parsePublicKeysetHandle = 341 TinkProtoKeysetFormat.parseKeysetWithoutSecret(serializedKeyset); 342 343 assertKeysetHandleAreEqual(publicKeysetHandle, parsePublicKeysetHandle); 344 } 345 346 @Test serializeEncrypted_worksWithKeysetHandleReadWithAssociatedDataAndBinaryKeysetReader()347 public void serializeEncrypted_worksWithKeysetHandleReadWithAssociatedDataAndBinaryKeysetReader() 348 throws Exception { 349 Aead keyEncryptionAead = generateAead(); 350 KeysetHandle keysetHandle = generateKeyset(); 351 byte[] associatedData = "associatedData".getBytes(UTF_8); 352 353 byte[] serializedKeyset = 354 TinkProtoKeysetFormat.serializeEncryptedKeyset( 355 keysetHandle, keyEncryptionAead, associatedData); 356 357 KeysetHandle parseKeysetHandle = 358 KeysetHandle.readWithAssociatedData( 359 BinaryKeysetReader.withBytes(serializedKeyset), keyEncryptionAead, associatedData); 360 361 assertKeysetHandleAreEqual(keysetHandle, parseKeysetHandle); 362 } 363 364 @Test parseEncrypted_worksWithKeysetHandleWriteWithAssociatedDataAndBinaryKeysetWriter()365 public void parseEncrypted_worksWithKeysetHandleWriteWithAssociatedDataAndBinaryKeysetWriter() 366 throws Exception { 367 Aead keyEncryptionAead = generateAead(); 368 KeysetHandle keysetHandle = generateKeyset(); 369 byte[] associatedData = "associatedData".getBytes(UTF_8); 370 371 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 372 keysetHandle.writeWithAssociatedData( 373 BinaryKeysetWriter.withOutputStream(outputStream), keyEncryptionAead, associatedData); 374 byte[] serializedKeyset = outputStream.toByteArray(); 375 376 KeysetHandle parseKeysetHandle = 377 TinkProtoKeysetFormat.parseEncryptedKeyset( 378 serializedKeyset, keyEncryptionAead, associatedData); 379 380 assertKeysetHandleAreEqual(keysetHandle, parseKeysetHandle); 381 } 382 383 @Test parseKeysetFromTestVector()384 public void parseKeysetFromTestVector() 385 throws Exception { 386 // This was generated in Python using the BinaryKeysetWriter. It contains one HMAC key. 387 byte[] serializedKeyset = 388 Hex.decode( 389 "0895e59bcc0612680a5c0a2e747970652e676f6f676c65617069732e636f6d2f676f6f676c652e63" 390 + "727970746f2e74696e6b2e486d61634b657912281a20cca20f02278003b3513f5d01759ac1302f7d" 391 + "883f2f4a40025532ee1b11f9e587120410100803180110011895e59bcc062001"); 392 KeysetHandle handle = 393 TinkProtoKeysetFormat.parseKeyset(serializedKeyset, InsecureSecretKeyAccess.get()); 394 Mac mac = handle.getPrimitive(RegistryConfiguration.get(), Mac.class); 395 mac.verifyMac(Hex.decode("016986f2956092d259136923c6f4323557714ec499"), "data".getBytes(UTF_8)); 396 } 397 398 @Test parseEncryptedKeysetFromTestVector()399 public void parseEncryptedKeysetFromTestVector() throws Exception { 400 // This is the same test vector as in KeysetHandleTest. 401 // An AEAD key, with which we encrypted the mac keyset below. 402 final byte[] serializedKeysetEncryptionKeyset = 403 Hex.decode( 404 "08cd9bdff30312540a480a30747970652e676f6f676c65617069732e636f6d2f676f6f676c652e63727970" 405 + "746f2e74696e6b2e41657347636d4b657912121a1082bbe6de4bf9a7655305615af46e594c180110" 406 + "0118cd9bdff3032001"); 407 KeysetHandle keysetEncryptionHandle = 408 TinkProtoKeysetFormat.parseKeyset( 409 serializedKeysetEncryptionKeyset, InsecureSecretKeyAccess.get()); 410 Aead keysetEncryptionAead = 411 keysetEncryptionHandle.getPrimitive(RegistryConfiguration.get(), Aead.class); 412 413 // A keyset that contains one HMAC key, encrypted with the above, using associatedData 414 final byte[] encryptedSerializedKeyset = 415 Hex.decode( 416 "129101013e77cdcd28f57ffb418afa7f25d48a74efe720246e9aa538f33a702888bb7c48bce0e5a016a0c8" 417 + "e9085066d67c7c7fb40dceb176a3a10c7f7ab30c564dd8e2d918a2fc2d2e9a0245c537ff6d1fd756" 418 + "ff9d6de5cf4eb7f229de215e6e892f32fd703d0c9c3d2168813ad5bbc6ce108fcbfed0d9e3b14faa" 419 + "e3e3789a891346d983b1ecca082f0546163351339aa142f574"); 420 final byte[] associatedData = "associatedData".getBytes(UTF_8); 421 422 KeysetHandle handle = 423 TinkProtoKeysetFormat.parseEncryptedKeyset( 424 encryptedSerializedKeyset, keysetEncryptionAead, associatedData); 425 426 Mac mac = handle.getPrimitive(RegistryConfiguration.get(), Mac.class); 427 final byte[] message = "data".getBytes(UTF_8); 428 final byte[] tag = Hex.decode("018f2d72de5055e622591fcf0fb85a7b4158e96f68"); 429 mac.verifyMac(tag, message); 430 } 431 432 @Test serializationOverhead()433 public void serializationOverhead() throws Exception { 434 int ivSize = 12; 435 int keySize = 16; 436 int tagSize = 16; 437 AesGcmParameters aesGcm128Parameters = 438 AesGcmParameters.builder() 439 .setIvSizeBytes(ivSize) 440 .setKeySizeBytes(keySize) 441 .setTagSizeBytes(tagSize) 442 .setVariant(AesGcmParameters.Variant.NO_PREFIX) 443 .build(); 444 KeysetHandle keysetHandle = KeysetHandle.generateNew(aesGcm128Parameters); 445 Aead keyEncryptionAead = 446 KeysetHandle.generateNew(aesGcm128Parameters) 447 .getPrimitive(RegistryConfiguration.get(), Aead.class); 448 byte[] serializedKeyset = 449 TinkProtoKeysetFormat.serializeKeyset(keysetHandle, InsecureSecretKeyAccess.get()); 450 451 byte[] rawEncryptedKeyset = keyEncryptionAead.encrypt(serializedKeyset, null); 452 453 byte[] encryptedKeyset = 454 TinkProtoKeysetFormat.serializeEncryptedKeyset(keysetHandle, keyEncryptionAead, null); 455 // {@code encryptedKeyset} is a serialized protocol buffer that wraps the encrypted keyset bytes 456 // as a protobuf bytes field. So, it should only be slightly larger than {@code 457 // rawEncryptedKeyset}. 458 assertThat(encryptedKeyset.length).isLessThan(rawEncryptedKeyset.length + 6); 459 } 460 } 461