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