aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortholenst <tholenst@google.com>2023-07-10 02:51:27 -0700
committerCopybara-Service <copybara-worker@google.com>2023-07-10 02:52:29 -0700
commit2ebb31438de23adfa14d198aef6b3f432c3507e8 (patch)
treed243bedb5d12ddcaa2f5156614407c18eac65d51
parentd59bd73d0265b71fef52b4badb3e55b2737f8b56 (diff)
downloadtink-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
-rw-r--r--java_src/src/main/java/com/google/crypto/tink/Key.java7
-rw-r--r--java_src/src/main/java/com/google/crypto/tink/KeysetHandle.java51
-rw-r--r--java_src/src/test/java/com/google/crypto/tink/BUILD.bazel2
-rw-r--r--java_src/src/test/java/com/google/crypto/tink/KeysetHandleTest.java251
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));
+ }
}