diff options
author | tholenst <tholenst@google.com> | 2023-07-10 02:51:27 -0700 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2023-07-10 02:52:29 -0700 |
commit | 2ebb31438de23adfa14d198aef6b3f432c3507e8 (patch) | |
tree | d243bedb5d12ddcaa2f5156614407c18eac65d51 | |
parent | d59bd73d0265b71fef52b4badb3e55b2737f8b56 (diff) | |
download | tink-2ebb31438de23adfa14d198aef6b3f432c3507e8.tar.gz |
Add "KeysetHandle#equalsKeyset".
This allows to compare two keysets, which is useful when one wants to have a guarantee that the two keysets are equal (for example, if one gets one from a KMS and the other from disk, but wants no change).
#tinkPublicApiChange
PiperOrigin-RevId: 546812109
4 files changed, 310 insertions, 1 deletions
diff --git a/java_src/src/main/java/com/google/crypto/tink/Key.java b/java_src/src/main/java/com/google/crypto/tink/Key.java index 9dffabfe4..92311a0e1 100644 --- a/java_src/src/main/java/com/google/crypto/tink/Key.java +++ b/java_src/src/main/java/com/google/crypto/tink/Key.java @@ -58,10 +58,15 @@ public abstract class Key { public abstract Integer getIdRequirementOrNull(); /** - * Returns true if the key is equal to the passed in key. + * Returns true if the key is guaranteed to be equal to {@code other}. * * <p>Implementations are required to do this in constant time. * + * <p>Note: this is allowed to return false even if two keys are guaranteed to represent the same + * function, but are represented differently. For example, a key is allowed to internally store + * the number of zero-bytes used as padding when a large number is represented as a byte array, + * and use this in the comparison. + * * <p>Note: Tink {@code Key} objects should typically not override {@code hashCode} (because it * could risk leaking key material). Hence, they typically also should not override {@code * equals}. diff --git a/java_src/src/main/java/com/google/crypto/tink/KeysetHandle.java b/java_src/src/main/java/com/google/crypto/tink/KeysetHandle.java index 68db88242..64ff5bd89 100644 --- a/java_src/src/main/java/com/google/crypto/tink/KeysetHandle.java +++ b/java_src/src/main/java/com/google/crypto/tink/KeysetHandle.java @@ -472,6 +472,22 @@ public final class KeysetHandle { public boolean isPrimary() { return isPrimary; } + + private boolean equalsEntry(Entry other) { + if (other.isPrimary != isPrimary) { + return false; + } + if (!other.keyStatus.equals(keyStatus)) { + return false; + } + if (other.id != id) { + return false; + } + if (!other.key.equalsKey(key)) { + return false; + } + return true; + } } private static KeyStatus parseStatus(KeyStatusType in) throws GeneralSecurityException { @@ -1194,4 +1210,39 @@ public final class KeysetHandle { return null; } } + + /** + * Returns true if this keyset is equal to {@code other}, ignoring monitoring annotations. + * + * <p>Note: this may return false even if the keysets represent the same set of functions. For + * example, this can happen if the keys store zero-byte padding of a {@link java.math.BigInteger}, + * which are irrelevant to the function computed. Currently, keysets can also be invalid in which + * case this will return false. + */ + boolean equalsKeyset(KeysetHandle other) { + if (size() != other.size()) { + return false; + } + boolean primaryFound = false; + for (int i = 0; i < size(); ++i) { + Entry thisEntry = entries.get(i); + Entry otherEntry = other.entries.get(i); + if (thisEntry == null) { + // Can only happen for invalid keyset + return false; + } + if (otherEntry == null) { + // Can only happen for invalid keyset + return false; + } + if (!thisEntry.equalsEntry(otherEntry)) { + return false; + } + primaryFound |= thisEntry.isPrimary; + } + if (!primaryFound) { + return false; + } + return true; + } } diff --git a/java_src/src/test/java/com/google/crypto/tink/BUILD.bazel b/java_src/src/test/java/com/google/crypto/tink/BUILD.bazel index 1a8c0842a..adba38e7a 100644 --- a/java_src/src/test/java/com/google/crypto/tink/BUILD.bazel +++ b/java_src/src/test/java/com/google/crypto/tink/BUILD.bazel @@ -323,6 +323,8 @@ java_test( "//src/main/java/com/google/crypto/tink:tink_proto_keyset_format", "//src/main/java/com/google/crypto/tink/aead:aead_config", "//src/main/java/com/google/crypto/tink/aead:aes_eax_key_manager", + "//src/main/java/com/google/crypto/tink/aead:x_cha_cha20_poly1305_key", + "//src/main/java/com/google/crypto/tink/aead:x_cha_cha20_poly1305_parameters", "//src/main/java/com/google/crypto/tink/internal:internal_configuration", "//src/main/java/com/google/crypto/tink/internal:key_parser", "//src/main/java/com/google/crypto/tink/internal:key_status_type_proto_converter", diff --git a/java_src/src/test/java/com/google/crypto/tink/KeysetHandleTest.java b/java_src/src/test/java/com/google/crypto/tink/KeysetHandleTest.java index 6e6684952..5a508cb78 100644 --- a/java_src/src/test/java/com/google/crypto/tink/KeysetHandleTest.java +++ b/java_src/src/test/java/com/google/crypto/tink/KeysetHandleTest.java @@ -18,12 +18,16 @@ package com.google.crypto.tink; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; import com.google.common.truth.Expect; import com.google.crypto.tink.aead.AeadConfig; import com.google.crypto.tink.aead.AesEaxKeyManager; +import com.google.crypto.tink.aead.XChaCha20Poly1305Key; +import com.google.crypto.tink.aead.XChaCha20Poly1305Parameters; import com.google.crypto.tink.internal.InternalConfiguration; import com.google.crypto.tink.internal.KeyParser; import com.google.crypto.tink.internal.KeyStatusTypeProtoConverter; @@ -1705,4 +1709,251 @@ public class KeysetHandleTest { assertThat(keysetHandleMac.computeMac(plaintext)).isEqualTo(registryMac.computeMac(plaintext)); } + + @Test + public void keysetEquality_singleKeyEquals_returnsTrue() throws Exception { + SecretBytes bytes = SecretBytes.randomBytes(32); + + KeysetHandle keysetHandle1 = + KeysetHandle.newBuilder() + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes)) + .withFixedId(101) + .makePrimary()) + .build(); + KeysetHandle keysetHandle2 = + KeysetHandle.newBuilder() + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes)) + .withFixedId(101) + .makePrimary()) + .build(); + + assertTrue(keysetHandle1.equalsKeyset(keysetHandle2)); + } + + @Test + public void keysetEquality_singleKeyDifferentKeys_returnsFalse() throws Exception { + SecretBytes bytes = SecretBytes.randomBytes(32); + + KeysetHandle keysetHandle1 = + KeysetHandle.newBuilder() + .addEntry( + KeysetHandle.importKey( + XChaCha20Poly1305Key.create( + XChaCha20Poly1305Parameters.Variant.TINK, bytes, 101)) + .withFixedId(101) + .makePrimary()) + .build(); + KeysetHandle keysetHandle2 = + KeysetHandle.newBuilder() + .addEntry( + KeysetHandle.importKey( + XChaCha20Poly1305Key.create( + XChaCha20Poly1305Parameters.Variant.CRUNCHY, bytes, 101)) + .withFixedId(101) + .makePrimary()) + .build(); + + assertFalse(keysetHandle1.equalsKeyset(keysetHandle2)); + } + + @Test + public void keysetEquality_singleKeyDifferentId_returnsFalse() throws Exception { + SecretBytes bytes = SecretBytes.randomBytes(32); + + KeysetHandle keysetHandle1 = + KeysetHandle.newBuilder() + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes)) + .withFixedId(102) + .makePrimary()) + .build(); + KeysetHandle keysetHandle2 = + KeysetHandle.newBuilder() + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes)) + .withFixedId(103) + .makePrimary()) + .build(); + + assertFalse(keysetHandle1.equalsKeyset(keysetHandle2)); + } + + @Test + public void keysetEquality_twoKeysEquals_returnsTrue() throws Exception { + SecretBytes bytes1 = SecretBytes.randomBytes(32); + SecretBytes bytes2 = SecretBytes.randomBytes(32); + + KeysetHandle keysetHandle1 = + KeysetHandle.newBuilder() + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes1)) + .withFixedId(101) + .makePrimary()) + .addEntry(KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes2)).withFixedId(102)) + .build(); + KeysetHandle keysetHandle2 = + KeysetHandle.newBuilder() + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes1)) + .withFixedId(101) + .makePrimary()) + .addEntry(KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes2)).withFixedId(102)) + .build(); + + assertTrue(keysetHandle1.equalsKeyset(keysetHandle2)); + } + + @Test + public void keysetEquality_twoKeysDifferentPrimaries_returnsFalse() throws Exception { + SecretBytes bytes1 = SecretBytes.randomBytes(32); + SecretBytes bytes2 = SecretBytes.randomBytes(32); + + KeysetHandle keysetHandle1 = + KeysetHandle.newBuilder() + .addEntry(KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes1)).withFixedId(101)) + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes2)) + .withFixedId(102) + .makePrimary()) + .build(); + KeysetHandle keysetHandle2 = + KeysetHandle.newBuilder() + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes1)) + .withFixedId(101) + .makePrimary()) + .addEntry(KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes2)).withFixedId(102)) + .build(); + + assertFalse(keysetHandle1.equalsKeyset(keysetHandle2)); + } + + @Test + public void keysetEquality_twoKeysDifferentOrder_returnsFalse() throws Exception { + SecretBytes bytes1 = SecretBytes.randomBytes(32); + SecretBytes bytes2 = SecretBytes.randomBytes(32); + + KeysetHandle keysetHandle1 = + KeysetHandle.newBuilder() + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes1)) + .withFixedId(101) + .makePrimary()) + .addEntry(KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes2)).withFixedId(102)) + .build(); + KeysetHandle keysetHandle2 = + KeysetHandle.newBuilder() + .addEntry(KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes2)).withFixedId(102)) + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes1)) + .withFixedId(101) + .makePrimary()) + .build(); + + assertFalse(keysetHandle1.equalsKeyset(keysetHandle2)); + } + + @Test + public void keysetEquality_twoKeysDifferentStatuses_returnsFalse() throws Exception { + SecretBytes bytes1 = SecretBytes.randomBytes(32); + SecretBytes bytes2 = SecretBytes.randomBytes(32); + + KeysetHandle keysetHandle1 = + KeysetHandle.newBuilder() + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes1)) + .withFixedId(101) + .makePrimary()) + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes2)) + .withFixedId(102) + .setStatus(KeyStatus.DISABLED)) + .build(); + KeysetHandle keysetHandle2 = + KeysetHandle.newBuilder() + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes1)) + .withFixedId(101) + .makePrimary()) + .addEntry(KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes2)).withFixedId(102)) + .build(); + + assertFalse(keysetHandle1.equalsKeyset(keysetHandle2)); + } + + @Test + public void keysetEquality_twoKeysDifferentSizes_returnsFalse() throws Exception { + SecretBytes bytes1 = SecretBytes.randomBytes(32); + SecretBytes bytes2 = SecretBytes.randomBytes(32); + + KeysetHandle keysetHandle1 = + KeysetHandle.newBuilder() + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes1)) + .withFixedId(101) + .makePrimary()) + .addEntry(KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes2)).withFixedId(102)) + .build(); + KeysetHandle keysetHandle2 = + KeysetHandle.newBuilder() + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes1)) + .withFixedId(101) + .makePrimary()) + .build(); + + assertFalse(keysetHandle1.equalsKeyset(keysetHandle2)); + } + + @Test + public void keysetEquality_unparseableStatus_returnsFalse() throws Exception { + Keyset.Key key1 = + TestUtil.createKey( + TestUtil.createHmacKeyData("01234567890123456".getBytes(UTF_8), 16), + 42, + KeyStatusType.UNKNOWN_STATUS, + OutputPrefixType.TINK); + KeysetHandle badKeyset = KeysetHandle.fromKeyset(TestUtil.createKeyset(key1)); + assertFalse(badKeyset.equalsKeyset(badKeyset)); + } + + @Test + public void keysetEquality_noPrimary_returnsFalse() throws Exception { + Keyset.Key key1 = + TestUtil.createKey( + TestUtil.createHmacKeyData("01234567890123456".getBytes(UTF_8), 16), + 42, + KeyStatusType.ENABLED, + OutputPrefixType.TINK); + Keyset keyset = TestUtil.createKeyset(key1); + KeysetHandle badKeyset = + KeysetHandle.fromKeyset(Keyset.newBuilder(keyset).setPrimaryKeyId(77).build()); + assertFalse(badKeyset.equalsKeyset(badKeyset)); + } + + @Test + public void keysetEquality_monitoringAnnotationIgnored_returnsTrue() throws Exception { + SecretBytes bytes = SecretBytes.randomBytes(32); + + KeysetHandle keysetHandle1 = + KeysetHandle.newBuilder() + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes)) + .withFixedId(101) + .makePrimary()) + .setMonitoringAnnotations(MonitoringAnnotations.newBuilder().add("k1", "v1").build()) + .build(); + KeysetHandle keysetHandle2 = + KeysetHandle.newBuilder() + .addEntry( + KeysetHandle.importKey(XChaCha20Poly1305Key.create(bytes)) + .withFixedId(101) + .makePrimary()) + .setMonitoringAnnotations(MonitoringAnnotations.newBuilder().add("k2", "v2").build()) + .build(); + + assertTrue(keysetHandle1.equalsKeyset(keysetHandle2)); + } } |