|
29 | 29 | import com.apple.foundationdb.record.logging.LogMessageKeys;
|
30 | 30 | import com.apple.foundationdb.record.metadata.RecordType;
|
31 | 31 | import com.apple.foundationdb.tuple.Tuple;
|
| 32 | +import com.apple.test.BooleanSource; |
32 | 33 | import com.google.common.base.Strings;
|
33 | 34 | import com.google.common.primitives.Bytes;
|
34 | 35 | import com.google.protobuf.Message;
|
|
38 | 39 | import org.junit.jupiter.params.ParameterizedTest;
|
39 | 40 | import org.junit.jupiter.params.provider.Arguments;
|
40 | 41 | import org.junit.jupiter.params.provider.MethodSource;
|
| 42 | +import org.junit.jupiter.params.provider.ValueSource; |
41 | 43 | import org.slf4j.Logger;
|
42 | 44 | import org.slf4j.LoggerFactory;
|
43 | 45 |
|
|
47 | 49 | import javax.crypto.SecretKey;
|
48 | 50 | import java.nio.ByteBuffer;
|
49 | 51 | import java.nio.ByteOrder;
|
| 52 | +import java.security.InvalidKeyException; |
50 | 53 | import java.security.Key;
|
51 | 54 | import java.security.NoSuchAlgorithmException;
|
52 | 55 | import java.security.SecureRandom;
|
@@ -369,21 +372,26 @@ public void unrecognizedEncoding() {
|
369 | 372 | assertEquals(15L, e.getLogInfo().get("encoding"));
|
370 | 373 | }
|
371 | 374 |
|
372 |
| - @Test |
373 |
| - public void encryptWhenSerializing() throws Exception { |
| 375 | + @ParameterizedTest |
| 376 | + @BooleanSource |
| 377 | + public void encryptWhenSerializing(boolean compressToo) throws Exception { |
374 | 378 | KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
375 | 379 | keyGen.init(128);
|
376 | 380 | SecretKey key = keyGen.generateKey();
|
377 | 381 | TransformedRecordSerializer<Message> serializer = TransformedRecordSerializerJCE.newDefaultBuilder()
|
378 | 382 | .setEncryptWhenSerializing(true)
|
379 | 383 | .setEncryptionKey(key)
|
| 384 | + .setCompressWhenSerializing(compressToo) |
| 385 | + .setCompressionLevel(9) |
380 | 386 | .setWriteValidationRatio(1.0)
|
381 | 387 | .build();
|
382 | 388 |
|
383 | 389 | MySimpleRecord mediumRecord = MySimpleRecord.newBuilder().setRecNo(1066L).setStrValueIndexed(SONNET_108).build();
|
384 | 390 | assertTrue(Bytes.indexOf(mediumRecord.toByteArray(), "brain".getBytes()) >= 0, "should contain clear text");
|
385 | 391 | byte[] serialized = serialize(serializer, mediumRecord);
|
386 |
| - assertEquals(TransformedRecordSerializerPrefix.PREFIX_ENCRYPTED, serialized[0]); |
| 392 | + assertEquals(compressToo ? TransformedRecordSerializerPrefix.PREFIX_COMPRESSED_THEN_ENCRYPTED |
| 393 | + : TransformedRecordSerializerPrefix.PREFIX_ENCRYPTED, |
| 394 | + serialized[0]); |
387 | 395 | assertFalse(Bytes.indexOf(serialized, "brain".getBytes()) >= 0, "should not contain clear text");
|
388 | 396 | Message deserialized = deserialize(serializer, Tuple.from(1066L), serialized);
|
389 | 397 | assertEquals(mediumRecord, deserialized);
|
@@ -457,8 +465,33 @@ public void corruptAnyBit() {
|
457 | 465 | }
|
458 | 466 | }
|
459 | 467 |
|
| 468 | + @ParameterizedTest |
| 469 | + @ValueSource(ints = {6, 10}) |
| 470 | + public void malformedVarintEncoding(int length) { |
| 471 | + RecordSerializationException e = assertThrows(RecordSerializationException.class, () -> { |
| 472 | + TransformedRecordSerializer<Message> serializer = TransformedRecordSerializer.newDefaultBuilder().build(); |
| 473 | + byte[] serialized = new byte[length]; |
| 474 | + Arrays.fill(serialized, (byte)0xFF); |
| 475 | + deserialize(serializer, Tuple.from(1066L), serialized); |
| 476 | + }); |
| 477 | + assertThat(e.getMessage(), containsString(length > 64 / 7 ? "transformation prefix too long" |
| 478 | + : "transformation prefix malformed")); |
| 479 | + } |
| 480 | + |
460 | 481 | @Test
|
461 |
| - public void encryptDifferentKeys() throws Exception { |
| 482 | + public void invalidKeyNumberEncoding() { |
| 483 | + RecordSerializationException e = assertThrows(RecordSerializationException.class, () -> { |
| 484 | + TransformedRecordSerializer<Message> serializer = TransformedRecordSerializer.newDefaultBuilder().build(); |
| 485 | + byte[] serialized = new byte[10]; |
| 486 | + TransformedRecordSerializerPrefix.writeVarint(serialized, |
| 487 | + TransformedRecordSerializerPrefix.PREFIX_ENCRYPTED + ((long)Integer.MAX_VALUE + 1 << 3)); |
| 488 | + deserialize(serializer, Tuple.from(1066L), serialized); |
| 489 | + }); |
| 490 | + assertThat(e.getMessage(), containsString("unrecognized transformation encoding")); |
| 491 | + } |
| 492 | + |
| 493 | + @Test |
| 494 | + public void encryptRollingKeys() throws Exception { |
462 | 495 | RollingKeyManager keyManager = new RollingKeyManager();
|
463 | 496 | TransformedRecordSerializer<Message> serializer = TransformedRecordSerializerJCE.newDefaultBuilder()
|
464 | 497 | .setEncryptWhenSerializing(true)
|
@@ -490,6 +523,121 @@ public void encryptDifferentKeys() throws Exception {
|
490 | 523 | assertEquals(records, deserialized);
|
491 | 524 | }
|
492 | 525 |
|
| 526 | + @Test |
| 527 | + public void cannotDecryptUnknownKey() throws Exception { |
| 528 | + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); |
| 529 | + keyGen.init(128); |
| 530 | + SecretKey key = keyGen.generateKey(); |
| 531 | + SecureRandom random = new SecureRandom(); |
| 532 | + TransformedRecordSerializer<Message> serializer = TransformedRecordSerializerJCE.newDefaultBuilder() |
| 533 | + .setEncryptWhenSerializing(true) |
| 534 | + .setKeyManager(new TransformedRecordSerializerKeyManager() { |
| 535 | + @Override |
| 536 | + public int getSerializationKey() { |
| 537 | + return 2; |
| 538 | + } |
| 539 | + |
| 540 | + @Override |
| 541 | + public Key getKey(final int keyNumber) { |
| 542 | + return key; |
| 543 | + } |
| 544 | + |
| 545 | + @Override |
| 546 | + public String getCipher(final int keyNumber) { |
| 547 | + return CipherPool.DEFAULT_CIPHER; |
| 548 | + } |
| 549 | + |
| 550 | + @Override |
| 551 | + public Random getRandom(final int keyNumber) { |
| 552 | + return random; |
| 553 | + } |
| 554 | + }) |
| 555 | + .setWriteValidationRatio(1.0) |
| 556 | + .build(); |
| 557 | + |
| 558 | + MySimpleRecord simpleRecord = MySimpleRecord.newBuilder().setRecNo(1066L).setStrValueIndexed("Hello").build(); |
| 559 | + RecordTypeUnion unionRecord = RecordTypeUnion.newBuilder().setMySimpleRecord(simpleRecord).build(); |
| 560 | + byte[] serialized = serialize(serializer, simpleRecord); |
| 561 | + TransformedRecordSerializer<Message> deserializer = TransformedRecordSerializerJCE.newDefaultBuilder() |
| 562 | + .setEncryptionKey(key) |
| 563 | + .build(); |
| 564 | + RecordSerializationException e = assertThrows(RecordSerializationException.class, |
| 565 | + () -> deserialize(deserializer, Tuple.from(1066L), serialized)); |
| 566 | + assertThat(e.getMessage(), containsString("only provide key number 0")); |
| 567 | + } |
| 568 | + |
| 569 | + @ParameterizedTest |
| 570 | + @BooleanSource |
| 571 | + public void cannotDecryptWithoutKey(boolean jce) throws Exception { |
| 572 | + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); |
| 573 | + keyGen.init(128); |
| 574 | + TransformedRecordSerializer<Message> serializer = TransformedRecordSerializerJCE.newDefaultBuilder() |
| 575 | + .setEncryptWhenSerializing(true) |
| 576 | + .setEncryptionKey(keyGen.generateKey()) |
| 577 | + .setWriteValidationRatio(1.0) |
| 578 | + .build(); |
| 579 | + MySimpleRecord simpleRecord = MySimpleRecord.newBuilder().setRecNo(1066L).setStrValueIndexed("Hello").build(); |
| 580 | + RecordTypeUnion unionRecord = RecordTypeUnion.newBuilder().setMySimpleRecord(simpleRecord).build(); |
| 581 | + byte[] serialized = serialize(serializer, simpleRecord); |
| 582 | + TransformedRecordSerializer<Message> deserializer; |
| 583 | + if (jce) { |
| 584 | + deserializer = TransformedRecordSerializerJCE.newDefaultBuilder() |
| 585 | + .setWriteValidationRatio(1.0) |
| 586 | + .build(); |
| 587 | + } else { |
| 588 | + deserializer = TransformedRecordSerializer.newDefaultBuilder() |
| 589 | + .setWriteValidationRatio(1.0) |
| 590 | + .build(); |
| 591 | + } |
| 592 | + RecordSerializationException e = assertThrows(RecordSerializationException.class, |
| 593 | + () -> deserialize(deserializer, Tuple.from(1066L), serialized)); |
| 594 | + assertThat(e.getMessage(), containsString(jce ? "missing encryption key or provider during decryption" |
| 595 | + : "this serializer cannot decrypt")); |
| 596 | + } |
| 597 | + |
| 598 | + @Test |
| 599 | + public void keyDoesNotMatchAlgorithm() throws Exception { |
| 600 | + KeyGenerator keyGen = KeyGenerator.getInstance("DES"); |
| 601 | + keyGen.init(56); |
| 602 | + TransformedRecordSerializer<Message> serializer = TransformedRecordSerializerJCE.newDefaultBuilder() |
| 603 | + .setEncryptWhenSerializing(true) |
| 604 | + .setEncryptionKey(keyGen.generateKey()) |
| 605 | + .setWriteValidationRatio(1.0) |
| 606 | + .build(); |
| 607 | + MySimpleRecord simpleRecord = MySimpleRecord.newBuilder().setRecNo(1066L).setStrValueIndexed("Hello").build(); |
| 608 | + RecordTypeUnion unionRecord = RecordTypeUnion.newBuilder().setMySimpleRecord(simpleRecord).build(); |
| 609 | + RecordSerializationException e = assertThrows(RecordSerializationException.class, |
| 610 | + () -> serialize(serializer, simpleRecord)); |
| 611 | + assertThat(e.getMessage(), containsString("encryption error")); |
| 612 | + assertThat(e.getCause(), instanceOf(InvalidKeyException.class)); |
| 613 | + assertThat(e.getCause().getMessage(), containsString("Wrong algorithm")); |
| 614 | + } |
| 615 | + |
| 616 | + @Test |
| 617 | + public void changeAlgorithm() throws Exception { |
| 618 | + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); |
| 619 | + keyGen.init(128); |
| 620 | + TransformedRecordSerializer<Message> serializer = TransformedRecordSerializerJCE.newDefaultBuilder() |
| 621 | + .setEncryptWhenSerializing(true) |
| 622 | + .setEncryptionKey(keyGen.generateKey()) |
| 623 | + .setWriteValidationRatio(1.0) |
| 624 | + .build(); |
| 625 | + MySimpleRecord simpleRecord = MySimpleRecord.newBuilder().setRecNo(1066L).setStrValueIndexed("Hello").build(); |
| 626 | + RecordTypeUnion unionRecord = RecordTypeUnion.newBuilder().setMySimpleRecord(simpleRecord).build(); |
| 627 | + byte[] serialized = serialize(serializer, simpleRecord); |
| 628 | + KeyGenerator keyGen2 = KeyGenerator.getInstance("DES"); |
| 629 | + keyGen2.init(56); |
| 630 | + TransformedRecordSerializer<Message> deserializer = TransformedRecordSerializerJCE.newDefaultBuilder() |
| 631 | + .setEncryptWhenSerializing(true) |
| 632 | + .setCipherName("DES") |
| 633 | + .setEncryptionKey(keyGen2.generateKey()) |
| 634 | + .setWriteValidationRatio(1.0) |
| 635 | + .build(); |
| 636 | + RecordSerializationException e = assertThrows(RecordSerializationException.class, |
| 637 | + () -> deserialize(deserializer, Tuple.from(1066L), serialized)); |
| 638 | + assertThat(e.getMessage(), containsString("decryption error")); |
| 639 | + } |
| 640 | + |
493 | 641 | private boolean isCompressed(byte[] serialized) {
|
494 | 642 | byte headerByte = serialized[0];
|
495 | 643 | return headerByte == TransformedRecordSerializerPrefix.PREFIX_COMPRESSED ||
|
|
0 commit comments