From c5080f4986baba05329578443edb4ef1df8fdbbb Mon Sep 17 00:00:00 2001 From: Bartosz Przydatek <przydatek@google.com> Date: Thu, 23 Feb 2017 11:01:40 +0000 Subject: [PATCH] Adding Registry and related classes. Based on prototype from cl/1184 and Daniel's experimental code Change-Id: I0377e32aea55c3e85a40bc64eddf45c52846cac6 ORIGINAL_AUTHOR=Bartosz Przydatek <przydatek@google.com> GitOrigin-RevId: c80cbaaf25e74ab8a7595e5192628d0f5d993a4c --- .gitignore | 1 + WORKSPACE | 5 + java/BUILD | 42 ++- .../google/cloud/crypto/tink/KeyManager.java | 36 +++ .../cloud/crypto/tink/KeysetHandle.java | 23 ++ .../com/google/cloud/crypto/tink/Mac.java | 5 +- .../cloud/crypto/tink/PrimitiveSet.java | 95 +++++++ .../google/cloud/crypto/tink/Registry.java | 128 +++++++++ .../cloud/crypto/tink/PrimitiveSetTest.java | 74 +++++ .../cloud/crypto/tink/RegistryTest.java | 254 ++++++++++++++++++ proto/tink.proto | 2 +- 11 files changed, 658 insertions(+), 7 deletions(-) create mode 100644 java/src/main/java/com/google/cloud/crypto/tink/KeyManager.java create mode 100644 java/src/main/java/com/google/cloud/crypto/tink/KeysetHandle.java create mode 100644 java/src/main/java/com/google/cloud/crypto/tink/PrimitiveSet.java create mode 100644 java/src/main/java/com/google/cloud/crypto/tink/Registry.java create mode 100644 java/src/test/java/com/google/cloud/crypto/tink/PrimitiveSetTest.java create mode 100644 java/src/test/java/com/google/cloud/crypto/tink/RegistryTest.java diff --git a/.gitignore b/.gitignore index 20be77c86..8158434da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store /bazel-* *.swp +*~ diff --git a/WORKSPACE b/WORKSPACE index 97987a895..ee99a1899 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -5,6 +5,11 @@ git_repository( commit = "be63ed9cb3140ec23e4df5118fca9a3f98640cf6", ) +maven_jar( + name = "com_google_inject_guice", + artifact = "com.google.inject:guice:4.1.0", +) + load("@org_pubref_rules_protobuf//java:rules.bzl", "java_proto_repositories") java_proto_repositories() diff --git a/java/BUILD b/java/BUILD index 9bc6efcb3..747c0da26 100644 --- a/java/BUILD +++ b/java/BUILD @@ -1,3 +1,8 @@ +JAVACOPTS = [ + "-Xlint:unchecked", + "-Xlint:deprecation", +] + java_library( name = "signature", srcs = [ @@ -7,12 +12,23 @@ java_library( ) java_library( - name = "aead", - srcs = [ - "src/main/java/com/google/cloud/crypto/tink/Aead.java", + name = "core", + srcs = glob([ + "src/main/java/com/google/cloud/crypto/tink/*.java", + ]), + javacopts = JAVACOPTS, + deps = [ + "//proto:java_core", + "//proto:java_core_compile_imports", ], ) +COMMON_JAVA_DEPS = [ + "//java:core", + "//proto:java_core", + "//proto:java_core_compile_imports", +] + # tests java_test( @@ -36,3 +52,23 @@ java_test( "@junit_junit_4//jar", ], ) + +java_test( + name = "PrimitiveSetTest", + size = "small", + srcs = ["src/test/java/com/google/cloud/crypto/tink/PrimitiveSetTest.java"], + test_class = "com.google.cloud.crypto.tink.PrimitiveSetTest", + deps = COMMON_JAVA_DEPS + [ + "@junit_junit_4//jar", + ], +) + +java_test( + name = "RegistryTest", + size = "small", + srcs = ["src/test/java/com/google/cloud/crypto/tink/RegistryTest.java"], + test_class = "com.google.cloud.crypto.tink.RegistryTest", + deps = COMMON_JAVA_DEPS + [ + "@junit_junit_4//jar", + ], +) diff --git a/java/src/main/java/com/google/cloud/crypto/tink/KeyManager.java b/java/src/main/java/com/google/cloud/crypto/tink/KeyManager.java new file mode 100644 index 000000000..669d2b121 --- /dev/null +++ b/java/src/main/java/com/google/cloud/crypto/tink/KeyManager.java @@ -0,0 +1,36 @@ +package com.google.cloud.crypto.tink; + +import com.google.cloud.crypto.tink.TinkProto.KeyFormat; +import com.google.protobuf.Any; +import java.security.GeneralSecurityException; + +/** + * KeyManager "understands" keys of specific key type(s): it can generate keys + * of the supported type(s) and create primitives for supported keys. + * A key type is identified by the global name of the protocol buffer that holds + * the corresponding key material, and is given by {@code typeUrl}-field + * of {@code google.protobuf.Any}-protocol buffer. + */ +public interface KeyManager<Primitive> { + /** + * Constructs an instance of Primitive for the key given in {@code proto}. + * + * @returns the new constructed Primitive. + * @throws GeneralSecurityException if the key given in {@code proto} is corrupted + * or not supported. + */ + Primitive getPrimitive(Any proto) throws GeneralSecurityException; + + /** + * Generates a new key according to specification in {@code keyFormat}. + * + * @returns the new generated key. + * @throws GeneralSecurityException if the specified format is wrong or not supported. + */ + Any newKey(KeyFormat keyFormat) throws GeneralSecurityException; + + /** + * @returns true iff this KeyManager supports key type identified by {@code typeUrl}. + */ + boolean doesSupport(String typeUrl); +} diff --git a/java/src/main/java/com/google/cloud/crypto/tink/KeysetHandle.java b/java/src/main/java/com/google/cloud/crypto/tink/KeysetHandle.java new file mode 100644 index 000000000..aef73c2ca --- /dev/null +++ b/java/src/main/java/com/google/cloud/crypto/tink/KeysetHandle.java @@ -0,0 +1,23 @@ +package com.google.cloud.crypto.tink; + +import com.google.cloud.crypto.tink.TinkProto.Keyset; + +/** + * KeysetHandle provides abstracted access to Keysets, to limit the exposure + * of actual protocol buffers that hold sensitive key material. + * + * NOTE: this is an initial definition of this interface, which needs more work. + * It should probably be an abstract class which does not provide public access + * to the actual key material. + */ +public interface KeysetHandle { + /** + * @returns source of the key material of this keyset (e.g. Keystore, Cloud KMS). + */ + byte[] getSource(); + + /** + * @returns the actual keyset data. + */ + Keyset getKeyset(); +} diff --git a/java/src/main/java/com/google/cloud/crypto/tink/Mac.java b/java/src/main/java/com/google/cloud/crypto/tink/Mac.java index b92731f86..a801b8c1d 100644 --- a/java/src/main/java/com/google/cloud/crypto/tink/Mac.java +++ b/java/src/main/java/com/google/cloud/crypto/tink/Mac.java @@ -4,9 +4,8 @@ import java.security.GeneralSecurityException; /** * Interface for MACs (Message Authentication Codes). - * This interface should be used for authentication only, and not for other - * purposes (for example, it should not be used to generate pseudorandom - * bytes). + * This interface should be used for authentication only, and not for other purposes + * (for example, it should not be used to generate pseudorandom bytes). */ public interface Mac { /** diff --git a/java/src/main/java/com/google/cloud/crypto/tink/PrimitiveSet.java b/java/src/main/java/com/google/cloud/crypto/tink/PrimitiveSet.java new file mode 100644 index 000000000..8416989a3 --- /dev/null +++ b/java/src/main/java/com/google/cloud/crypto/tink/PrimitiveSet.java @@ -0,0 +1,95 @@ +package com.google.cloud.crypto.tink; + +import com.google.cloud.crypto.tink.TinkProto.Keyset; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * A container class for a set of primitives (i.e. implementations of cryptographic + * primitives offered by Tink). It provides also additional properties for the primitives + * it holds. In particular, one of the primitives in the set can be distinguished as + * "the primary" one. <p> + * + * PrimitiveSet is an auxiliary class used for supporting key rotation: primitives in a set + * correspond to keys in a keyset. Users will usually work with primitive instances, + * which essentially wrap primitive sets. For example an instance of an Aead-primitive + * for a given keyset holds a set of Aead-primitivies corresponding to the keys in the keyset, + * and uses the set members to do the actual crypto operations: to encrypt data the primary + * Aead-primitive from the set is used, and upon decryption the ciphertext's prefix + * determines the id of the primitive from the set. <p> + * + * PrimitiveSet is a public class to allow its use in implementations of custom primitives. + */ +public class PrimitiveSet<P> { + /** + * A single entry in the set. In addition to the actual primitive it holds also + * some extra information about the primitive. + * TODO(przydatek): update identifier to fit the key management mechanisms. + */ + public class Entry<P> { + private final P primitive; // the actual primitive + private final long identifier; // identifies the primitive within the set + private final Keyset.KeyStatus status; // the status of the key represented by the primitive + + public Entry(P primitive, long identifier, Keyset.KeyStatus status) { + this.primitive = primitive; + this.identifier = identifier; + this.status = status; + } + protected P getPrimitive() { + return this.primitive; + } + protected Keyset.KeyStatus getStatus() { + return status; + } + protected final long getIdentifier() { + return identifier; + } + } + + protected static <P> PrimitiveSet<P> newPrimitiveSet() { + return new PrimitiveSet<P>(); + } + + private ConcurrentMap<java.lang.Long, Entry<P>> primitives = + new ConcurrentHashMap<java.lang.Long, Entry<P>>(); + + private Entry<P> primary; + + /** + * @returns the number of primitives in this set. + */ + protected int size() { + return primitives.size(); + } + + /** + * @returns the entry with primitve identifed by {@code identifier}. + * TODO(przydatek): make it return List<Entry<P>> to be able to handle identifier collisions. + */ + protected Entry<P> getPrimitiveForId(long identifier) { + return primitives.get(identifier); + } + + /** + * @returns the entry with the primary primitive. + */ + protected Entry<P> getPrimary() { + return primary; + } + + /** + * @returns sets given Entry {@code primary} as the primary one. + */ + protected void setPrimary(Entry<P> primary) { + this.primary = primary; + } + + /** + * Creates an entry in the primitive table. + */ + protected void addPrimitive(P primitive, Keyset.Key key) { + Entry<P> entry = new Entry<P>(primitive, key.getKeyId(), key.getStatus()); + primitives.put(new Long(key.getKeyId()), entry); + } +} diff --git a/java/src/main/java/com/google/cloud/crypto/tink/Registry.java b/java/src/main/java/com/google/cloud/crypto/tink/Registry.java new file mode 100644 index 000000000..363659b4c --- /dev/null +++ b/java/src/main/java/com/google/cloud/crypto/tink/Registry.java @@ -0,0 +1,128 @@ +package com.google.cloud.crypto.tink; + +import com.google.cloud.crypto.tink.TinkProto.KeyFormat; +import com.google.cloud.crypto.tink.TinkProto.Keyset; +import com.google.cloud.crypto.tink.TinkProto.Keyset.KeyStatus; +import com.google.protobuf.Any; +import java.security.GeneralSecurityException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Registry for KeyMangers. <p> + * It is essentially a big container (map) that for each supported key type holds + * a corresponding KeyManager object, which "understands" the key type (i.e. the KeyManager + * can instantiate the primitive corresponding to given key, or can generate new keys + * of the supported key type). Registry is initialized at startup, and is later + * used to instantiate primitives for given keys or keysets. Keeping KeyManagers for all + * primitives in a single Registry (rather than having a separate KeyManager per primitive) + * enables modular construction of compound primitives from "simple" ones, e.g., + * AES-CTR-HMAC AEAD encryption uses IND-CPA encryption and a MAC. <p> + * + * Note that regular users will usually not work directly with Registry, but rather + * via primitive factories, which in the background query the Registry for specific + * KeyManagers. Registry is public though, to enable configurations with custom + * primitives and KeyManagers. + */ +public final class Registry { + public static final Registry INSTANCE = new Registry(); // Default registry used by factories. + + private final ConcurrentMap<String, KeyManager> keyManager = + new ConcurrentHashMap<String, KeyManager>(); // typeUrl -> KeyManager mapping + + + /** + * Creates an empty registry. + */ + protected Registry() {} + + /** + * Registers {@code manager} for the given {@code typeUrl}, assuming that there is + * no key manager registered for {@code typeUrl} yet. + * + * @throws GeneralSecurityException if there already exists a key manager for {@code typeUrl}. + * @throws NullPointerException if {@code manager} is null. + */ + public <Primitive> void registerKeyManager(String typeUrl, KeyManager<Primitive> manager) + throws GeneralSecurityException { + if (manager == null) { + throw new NullPointerException("Key manager must be non-null."); + } + if (keyManager.containsKey(typeUrl)) { + throw new GeneralSecurityException("Key manager for '" + + typeUrl + "' has been already registered."); + } + keyManager.put(typeUrl, manager); + } + + /** + * @returns a KeyManager for the given {@code typeUrl} (if found). + * + * TODO(przydatek): find a way for verifying the primitive type. + */ + @SuppressWarnings("unchecked") + public <Primitive> KeyManager<Primitive> getKeyManager(String typeUrl) + throws GeneralSecurityException { + KeyManager<Primitive> manager = keyManager.get(typeUrl); + if (manager == null) { + throw new GeneralSecurityException("Unsupported key type: " + typeUrl); + } + return manager; + } + + /** + * Convenience method for generating a new key for the specified {@code format}. + * It looks up a KeyManager identified by {@code format.key_type}, and calls + * managers {@code newKey(format)}-method. + * + * @returns a new key. + */ + public <Primitive> Any newKey(KeyFormat format) + throws GeneralSecurityException { + KeyManager<Primitive> manager = getKeyManager(format.getKeyType()); + return manager.newKey(format); + } + + /** + * Convenience method for creating a new primitive for the key given in {@code proto}. + * It looks up a KeyManager identified by {@code proto.type_url}, and calls + * managers {@code getPrimitive(proto)}-method. + * + * @returns a new primitive. + */ + public <Primitive> Primitive getPrimitive(Any proto) + throws GeneralSecurityException { + KeyManager<Primitive> manager = getKeyManager(proto.getTypeUrl()); + return manager.getPrimitive(proto); + } + + /** + * Creates a set of primitives corresponding to the keys with status=ENABLED in the keyset + * given in {@code keysetHandle}, assuming all the corresponding key managers are present + * (keys with status!=ENABLED are skipped). + * + * The returned set is usually later "wrapped" into a class that implements + * the corresponding Primitive-interface. + * + * @returns a PrimitiveSet with all instantiated primitives. + */ + public <Primitive> PrimitiveSet<Primitive> getPrimitives(KeysetHandle keysetHandle) + throws GeneralSecurityException { + // TODO(thaidn): cache it? + PrimitiveSet<Primitive> primitives = PrimitiveSet.newPrimitiveSet(); + for (Keyset.Key key : keysetHandle.getKeyset().getKeyList()) { + if (key.getStatus() == KeyStatus.ENABLED) { + Primitive primitive = getPrimitive(key.getKeyData()); + primitives.addPrimitive(primitive, key); + if (key.getKeyId() == keysetHandle.getKeyset().getPrimaryKeyId()) { + primitives.setPrimary(primitives.getPrimitiveForId(key.getKeyId())); + } + } + } + return primitives; + } + + // TODO(przydatek): add methods for providing custom KeyManager-objects + // Primitive getPrimitive(Any proto, KeyManager<Primitive> customManager); + // PrimitiveSet<Primitive> getPrimitive(KeysetHandle, KeyManager<Primitive> customManager); +} diff --git a/java/src/test/java/com/google/cloud/crypto/tink/PrimitiveSetTest.java b/java/src/test/java/com/google/cloud/crypto/tink/PrimitiveSetTest.java new file mode 100644 index 000000000..cef06372f --- /dev/null +++ b/java/src/test/java/com/google/cloud/crypto/tink/PrimitiveSetTest.java @@ -0,0 +1,74 @@ +package com.google.cloud.crypto.tink; + +import static org.junit.Assert.assertEquals; + +import com.google.cloud.crypto.tink.TinkProto.Keyset.Key; +import com.google.cloud.crypto.tink.TinkProto.Keyset.KeyStatus; + +import java.security.GeneralSecurityException; + +import org.junit.Test; + +/** + * Tests for PrimitiveSet. + */ +public class PrimitiveSetTest { + private class DummyMac1 implements Mac { + public DummyMac1() {} + @Override + public byte[] computeMac(byte[] data) throws GeneralSecurityException { + return this.getClass().getSimpleName().getBytes(); + } + @Override + public boolean verifyMac(byte[] mac, byte[] data) throws GeneralSecurityException { + return true; + } + } + + private class DummyMac2 implements Mac { + public DummyMac2() {} + @Override + public byte[] computeMac(byte[] data) throws GeneralSecurityException { + return this.getClass().getSimpleName().getBytes(); + } + @Override + public boolean verifyMac(byte[] mac, byte[] data) throws GeneralSecurityException { + return true; + } + } + + @Test + public void testBasicFunctionality() throws Exception { + PrimitiveSet<Mac> pset = PrimitiveSet.newPrimitiveSet(); + pset.addPrimitive(new DummyMac1(), + Key.newBuilder().setKeyId(1).setStatus(KeyStatus.ENABLED).build()); + pset.addPrimitive(new DummyMac2(), + Key.newBuilder().setKeyId(2).setStatus(KeyStatus.ENABLED).build()); + pset.addPrimitive(new DummyMac1(), + Key.newBuilder().setKeyId(3).setStatus(KeyStatus.DISABLED).build()); + pset.setPrimary(pset.getPrimitiveForId(2)); + PrimitiveSet<Mac>.Entry<Mac> entry = pset.getPrimitiveForId(1); + assertEquals(DummyMac1.class.getSimpleName(), + new String(entry.getPrimitive().computeMac(null))); + assertEquals(KeyStatus.ENABLED, entry.getStatus()); + assertEquals(1, entry.getIdentifier()); + + entry = pset.getPrimitiveForId(2); + assertEquals(DummyMac2.class.getSimpleName(), + new String(entry.getPrimitive().computeMac(null))); + assertEquals(KeyStatus.ENABLED, entry.getStatus()); + assertEquals(2, entry.getIdentifier()); + + entry = pset.getPrimitiveForId(3); + assertEquals(DummyMac1.class.getSimpleName(), + new String(entry.getPrimitive().computeMac(null))); + assertEquals(KeyStatus.DISABLED, entry.getStatus()); + assertEquals(3, entry.getIdentifier()); + + entry = pset.getPrimary(); + assertEquals(DummyMac2.class.getSimpleName(), + new String(entry.getPrimitive().computeMac(null))); + assertEquals(KeyStatus.ENABLED, entry.getStatus()); + assertEquals(2, entry.getIdentifier()); + } +} diff --git a/java/src/test/java/com/google/cloud/crypto/tink/RegistryTest.java b/java/src/test/java/com/google/cloud/crypto/tink/RegistryTest.java new file mode 100644 index 000000000..3f30004c9 --- /dev/null +++ b/java/src/test/java/com/google/cloud/crypto/tink/RegistryTest.java @@ -0,0 +1,254 @@ +package com.google.cloud.crypto.tink; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.crypto.tink.TinkProto.KeyFormat; +import com.google.cloud.crypto.tink.TinkProto.Keyset; +import com.google.cloud.crypto.tink.TinkProto.Keyset.KeyStatus; + +import com.google.protobuf.Any; +import java.security.GeneralSecurityException; +import java.util.concurrent.Future; + +import org.junit.Test; + +/** + * Tests for Registry. + */ +public class RegistryTest { + + private class DummyMac implements Mac { + private final String label; + public DummyMac(String label) { + this.label = label; + } + @Override + public byte[] computeMac(byte[] data) throws GeneralSecurityException { + return label.getBytes(); + } + @Override + public boolean verifyMac(byte[] mac, byte[] data) throws GeneralSecurityException { + return true; + } + } + + private class DummyAead implements Aead { + private final String label; + public DummyAead(String label) { + this.label = label; + } + @Override + public byte[] encrypt(byte[] plaintext, byte[] aad) throws GeneralSecurityException { + return label.getBytes(); + } + @Override + public byte[] decrypt(byte[] ciphertext, byte[] aad) throws GeneralSecurityException { + return label.getBytes(); + } + @Override + public Future<byte[]> asyncEncrypt(byte[] plaintext, byte[] aad) + throws GeneralSecurityException { + return null; + } + @Override + public Future<byte[]> asyncDecrypt(byte[] ciphertext, byte[] aad) + throws GeneralSecurityException { + return null; + } + } + + private class Mac1KeyManager implements KeyManager<Mac> { + public Mac1KeyManager() {} + + @Override + public Mac getPrimitive(Any proto) throws GeneralSecurityException { + return new DummyMac(this.getClass().getSimpleName()); + } + @Override + public Any newKey(KeyFormat format) throws GeneralSecurityException { + return Any.newBuilder().setTypeUrl(this.getClass().getSimpleName()).build(); + } + @Override + public boolean doesSupport(String typeUrl) { + return typeUrl == this.getClass().getSimpleName(); + } + } + + private class Mac2KeyManager implements KeyManager<Mac> { + public Mac2KeyManager() {} + + @Override + public Mac getPrimitive(Any proto) throws GeneralSecurityException { + return new DummyMac(this.getClass().getSimpleName()); + } + @Override + public Any newKey(KeyFormat format) throws GeneralSecurityException { + return Any.newBuilder().setTypeUrl(this.getClass().getSimpleName()).build(); + } + @Override + public boolean doesSupport(String typeUrl) { + return typeUrl == this.getClass().getSimpleName(); + } + } + + private class AeadKeyManager implements KeyManager<Aead> { + public AeadKeyManager() {} + + @Override + public Aead getPrimitive(Any proto) throws GeneralSecurityException { + return new DummyAead(this.getClass().getSimpleName()); + } + @Override + public Any newKey(KeyFormat format) throws GeneralSecurityException { + return Any.newBuilder().setTypeUrl(this.getClass().getSimpleName()).build(); + } + @Override + public boolean doesSupport(String typeUrl) { + return typeUrl == this.getClass().getSimpleName(); + } + } + + @Test + public void testKeyManagerRegistration() throws Exception { + Registry registry = new Registry(); + + String mac1TypeUrl = Mac1KeyManager.class.getSimpleName(); + String mac2TypeUrl = Mac2KeyManager.class.getSimpleName(); + String aeadTypeUrl = AeadKeyManager.class.getSimpleName(); + + // Register some key managers. + registry.registerKeyManager(mac1TypeUrl, new Mac1KeyManager()); + registry.registerKeyManager(mac2TypeUrl, new Mac2KeyManager()); + registry.registerKeyManager(aeadTypeUrl, new AeadKeyManager()); + + // Retrieve some key managers. + KeyManager<Mac> mac1Manager = registry.getKeyManager(mac1TypeUrl); + KeyManager<Mac> mac2Manager = registry.getKeyManager(mac2TypeUrl); + assertEquals(Mac1KeyManager.class, mac1Manager.getClass()); + assertEquals(Mac2KeyManager.class, mac2Manager.getClass()); + String computedMac = new String(mac1Manager.getPrimitive(null).computeMac(null)); + assertEquals(Mac1KeyManager.class.getSimpleName(), computedMac); + computedMac = new String(mac2Manager.getPrimitive(null).computeMac(null)); + assertEquals(Mac2KeyManager.class.getSimpleName(), computedMac); + + KeyManager<Aead> aeadManager = registry.getKeyManager(aeadTypeUrl); + assertEquals(AeadKeyManager.class, aeadManager.getClass()); + Aead aead = aeadManager.getPrimitive(null); + String ciphertext = new String(aead.encrypt("plaintext".getBytes(), null)); + assertEquals(AeadKeyManager.class.getSimpleName(), ciphertext); + // TODO(przydatek): add tests when the primitive of KeyManager does not match key type. + + String badTypeUrl = "bad type URL"; + try { + KeyManager<Mac> macManager = registry.getKeyManager(badTypeUrl); + } catch (GeneralSecurityException e) { + assertTrue(e.toString().contains("Unsupported")); + assertTrue(e.toString().contains(badTypeUrl)); + } + } + + @Test + public void testKeyAndPrimitiveCreation() throws Exception { + Registry registry = new Registry(); + + String mac1TypeUrl = Mac1KeyManager.class.getSimpleName(); + String mac2TypeUrl = Mac2KeyManager.class.getSimpleName(); + String aeadTypeUrl = AeadKeyManager.class.getSimpleName(); + + // Register some key managers. + registry.registerKeyManager(mac1TypeUrl, new Mac1KeyManager()); + registry.registerKeyManager(mac2TypeUrl, new Mac2KeyManager()); + registry.registerKeyManager(aeadTypeUrl, new AeadKeyManager()); + + // Create some keys and primitives. + KeyFormat format = KeyFormat.newBuilder().setKeyType(mac2TypeUrl).build(); + Any key = registry.newKey(format); + assertEquals(mac2TypeUrl, key.getTypeUrl()); + Mac mac = registry.getPrimitive(key); + String computedMac = new String(mac.computeMac(null)); + assertEquals(mac2TypeUrl, computedMac); + + format = KeyFormat.newBuilder().setKeyType(aeadTypeUrl).build(); + key = registry.newKey(format); + assertEquals(aeadTypeUrl, key.getTypeUrl()); + Aead aead = registry.getPrimitive(key); + String ciphertext = new String(aead.encrypt(null, null)); + assertEquals(aeadTypeUrl, ciphertext); + + // Create a keyset, and get a PrimitiveSet. + KeyFormat format1 = KeyFormat.newBuilder().setKeyType(mac1TypeUrl).build(); + KeyFormat format2 = KeyFormat.newBuilder().setKeyType(mac2TypeUrl).build(); + Any key1 = registry.newKey(format1); + Any key2 = registry.newKey(format1); + Any key3 = registry.newKey(format2); + KeysetHandle keysetHandle = new KeysetHandle() { + public byte[] getSource() { + return "keyset source".getBytes(); + } + public Keyset getKeyset() { + return Keyset.newBuilder() + .addKey(Keyset.Key.newBuilder() + .setKeyData(key1).setKeyId(1).setStatus(KeyStatus.ENABLED).build()) + .addKey(Keyset.Key.newBuilder() + .setKeyData(key2).setKeyId(2).setStatus(KeyStatus.ENABLED).build()) + .addKey(Keyset.Key.newBuilder() + .setKeyData(key3).setKeyId(3).setStatus(KeyStatus.ENABLED).build()) + .setPrimaryKeyId(2) + .build(); + } + }; + PrimitiveSet<Mac> macSet = registry.getPrimitives(keysetHandle); + assertEquals(3, macSet.size()); + computedMac = new String(macSet.getPrimary().getPrimitive().computeMac(null)); + assertEquals(mac1TypeUrl, computedMac); + + // Try a keyset with some keys non-ENABLED. + keysetHandle = new KeysetHandle() { + public byte[] getSource() { + return "keyset source".getBytes(); + } + public Keyset getKeyset() { + return Keyset.newBuilder() + .addKey(Keyset.Key.newBuilder() + .setKeyData(key1).setKeyId(1).setStatus(KeyStatus.DESTROYED).build()) + .addKey(Keyset.Key.newBuilder() + .setKeyData(key2).setKeyId(2).setStatus(KeyStatus.DISABLED).build()) + .addKey(Keyset.Key.newBuilder() + .setKeyData(key3).setKeyId(3).setStatus(KeyStatus.ENABLED).build()) + .setPrimaryKeyId(3) + .build(); + } + }; + macSet = registry.getPrimitives(keysetHandle); + assertEquals(1, macSet.size()); + computedMac = new String(macSet.getPrimary().getPrimitive().computeMac(null)); + assertEquals(mac2TypeUrl, computedMac); + } + + + @Test + public void testRegistryCollisions() throws Exception { + Registry registry = new Registry(); + String mac1TypeUrl = Mac1KeyManager.class.getSimpleName(); + String mac2TypeUrl = Mac2KeyManager.class.getSimpleName(); + + try { + registry.registerKeyManager(mac1TypeUrl, null); + } catch (NullPointerException e) { + assertTrue(e.toString().contains("must be non-null")); + } + + registry.registerKeyManager(mac1TypeUrl, new Mac1KeyManager()); + registry.registerKeyManager(mac2TypeUrl, new Mac2KeyManager()); + + try { + registry.registerKeyManager(mac1TypeUrl, new Mac1KeyManager()); + } catch (GeneralSecurityException e) { + assertTrue(e.toString().contains(mac1TypeUrl)); + assertTrue(e.toString().contains("already registered")); + } + } + + // TODO(przydatek): Add more tests for creation of PrimitiveSets. +} diff --git a/proto/tink.proto b/proto/tink.proto index 98475efa2..a00043b7a 100644 --- a/proto/tink.proto +++ b/proto/tink.proto @@ -89,5 +89,5 @@ message Keyset { // Actual keys in the Keyset. // Required. - repeated Key keys = 4; + repeated Key key = 4; } -- GitLab