Add encryption using Tink.

Introduce the HybridTinkEncryptedMessageMaker which implements the
EncryptedMessageMaker interface and encrypts observations using Tink.

Upgrade to the latest version of Tink we have available.

Change-Id: I6d2e8794fd53159049b4d1c617c82b7549c1515d
diff --git a/third_party/tink b/third_party/tink
index 35fc16f..a24b067 160000
--- a/third_party/tink
+++ b/third_party/tink
@@ -1 +1 @@
-Subproject commit 35fc16f7254b90d89b2ba23b569d541fe53511d9
+Subproject commit a24b06746b78a75562cfdad91c0c9a3084fd1599
diff --git a/util/BUILD.gn b/util/BUILD.gn
index 85c2adf..2a5a60d 100644
--- a/util/BUILD.gn
+++ b/util/BUILD.gn
@@ -8,17 +8,22 @@
   ]
 }
 
-static_library("encrypted_message_util") {
+source_set("encrypted_message_util") {
   sources = [
     "encrypted_message_util.cc",
     "encrypted_message_util.h",
   ]
-  configs += [ "//third_party/cobalt:cobalt_config" ]
+  configs -= [ "//build/config:no_rtti" ]
 
-  deps = [
+  public_configs = [ "//third_party/cobalt:cobalt_config" ]
+
+  public_deps = [
     ":status",
     "//garnet/public/lib/fxl",
     "//third_party/cobalt:cobalt_proto",
+    "//third_party/tink/cc:hybrid_encrypt",
+    "//third_party/tink/cc/hybrid:hybrid_config",
+    "//third_party/tink/cc:keyset_handle",
   ]
 }
 
diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt
index 7723832..0ecabae 100644
--- a/util/CMakeLists.txt
+++ b/util/CMakeLists.txt
@@ -14,7 +14,10 @@
 target_link_libraries(encrypted_message_util
                       cobalt_crypto
                       status
-                      statusor)
+                      statusor
+                      tink_cc_hybrid_encrypt
+                      tink_cc_hybrid_hybrid_config
+                      tink_cc_keyset_handle)
 add_cobalt_dependencies(encrypted_message_util)
 
 add_library(pem_util
@@ -38,7 +41,12 @@
                       encrypted_message_util
                       posix_file_system
                       consistent_proto_store
-                      consistent_proto_store_test_proto)
+                      consistent_proto_store_test_proto
+                      tink_cc_hybrid_encrypt
+                      tink_cc_hybrid_hybrid_config
+                      tink_cc_keyset_handle
+                      tink_cc_cleartext_keyset_handle
+                      tink_cc_hybrid_hybrid_key_templates)
 add_cobalt_test_dependencies(util_tests ${DIR_GTESTS})
 
 add_subdirectory(clearcut)
diff --git a/util/encrypted_message_util.cc b/util/encrypted_message_util.cc
index 3264afe..f48d773 100644
--- a/util/encrypted_message_util.cc
+++ b/util/encrypted_message_util.cc
@@ -10,6 +10,10 @@
 #include "./encrypted_message.pb.h"
 #include "./logging.h"
 #include "google/protobuf/message_lite.h"
+#include "third_party/tink/cc/hybrid/hybrid_config.h"
+#include "third_party/tink/cc/hybrid_encrypt.h"
+#include "third_party/tink/cc/keyset_handle.h"
+#include "util/crypto_util/base64.h"
 #include "util/crypto_util/cipher.h"
 #include "util/status.h"
 #include "util/status_codes.h"
@@ -20,8 +24,15 @@
 using ::cobalt::crypto::byte;
 using ::cobalt::crypto::HybridCipher;
 
+namespace {
+Status StatusFromTinkStatus(::crypto::tink::util::Status tink_status) {
+  return Status(StatusCode(tink_status.error_code()),
+                tink_status.error_message());
+}
+}  // namespace
+
 statusor::StatusOr<std::unique_ptr<EncryptedMessageMaker>>
-EncryptedMessageMaker::Make(const std::string& public_key_pem,
+EncryptedMessageMaker::Make(const std::string& public_key,
                             EncryptedMessage::EncryptionScheme scheme) {
   switch (scheme) {
     case EncryptedMessage::NONE:
@@ -29,10 +40,9 @@
                     "EncryptedMessageMaker: encryption_scheme NONE is not "
                     "allowed in production.");
     case EncryptedMessage::HYBRID_ECDH_V1:
-      return EncryptedMessageMaker::MakeHybridEcdh(public_key_pem);
+      return EncryptedMessageMaker::MakeHybridEcdh(public_key);
     case EncryptedMessage::HYBRID_TINK:
-      return Status(UNIMPLEMENTED,
-                    "HYBRID_TINK EncryptionScheme not implemented yet.");
+      return EncryptedMessageMaker::MakeHybridTink(public_key);
     default:
       return Status(INVALID_ARGUMENT,
                     "EncryptedMessageMaker: Unknown encryption_scheme.");
@@ -41,12 +51,11 @@
 
 statusor::StatusOr<std::unique_ptr<EncryptedMessageMaker>>
 EncryptedMessageMaker::MakeAllowUnencrypted(
-    const std::string& public_key_pem,
-    EncryptedMessage::EncryptionScheme scheme) {
+    const std::string& public_key, EncryptedMessage::EncryptionScheme scheme) {
   if (scheme == EncryptedMessage::NONE) {
     return EncryptedMessageMaker::MakeUnencrypted();
   }
-  return EncryptedMessageMaker::Make(public_key_pem, scheme);
+  return EncryptedMessageMaker::Make(public_key, scheme);
 }
 
 std::unique_ptr<EncryptedMessageMaker>
@@ -57,6 +66,42 @@
 }
 
 statusor::StatusOr<std::unique_ptr<EncryptedMessageMaker>>
+EncryptedMessageMaker::MakeHybridTink(const std::string& public_keyset_base64) {
+  if (public_keyset_base64.empty()) {
+    return Status(INVALID_ARGUMENT, "EncryptedMessageMaker: Empty key.");
+  }
+
+  auto status = ::crypto::tink::HybridConfig::Register();
+  if (!status.ok()) {
+    return StatusFromTinkStatus(status);
+  }
+
+  std::string public_keyset;
+  if (!crypto::Base64Decode(public_keyset_base64, &public_keyset)) {
+    return Status(INVALID_ARGUMENT,
+                  "EncryptedMessageMaker: Invalid base64-encoded keyset.");
+  }
+
+  auto read_result = ::crypto::tink::KeysetHandle::ReadNoSecret(public_keyset);
+  if (!read_result.ok()) {
+    return StatusFromTinkStatus(read_result.status());
+  }
+  auto keyset_handle = std::move(read_result.ValueOrDie());
+
+  auto primitive_result =
+      keyset_handle->GetPrimitive<::crypto::tink::HybridEncrypt>();
+  if (!primitive_result.ok()) {
+    return StatusFromTinkStatus(primitive_result.status());
+  }
+
+  std::unique_ptr<EncryptedMessageMaker> maker(
+      new HybridTinkEncryptedMessageMaker(
+          std::move(primitive_result.ValueOrDie())));
+
+  return maker;
+}
+
+statusor::StatusOr<std::unique_ptr<EncryptedMessageMaker>>
 EncryptedMessageMaker::MakeHybridEcdh(const std::string& public_key_pem) {
   auto cipher = std::make_unique<HybridCipher>();
   if (!cipher->set_public_key_pem(public_key_pem)) {
@@ -68,6 +113,34 @@
   return maker;
 }
 
+HybridTinkEncryptedMessageMaker::HybridTinkEncryptedMessageMaker(
+    std::unique_ptr<::crypto::tink::HybridEncrypt> encrypter)
+    : encrypter_(std::move(encrypter)) {}
+
+bool HybridTinkEncryptedMessageMaker::Encrypt(
+    const google::protobuf::MessageLite& message,
+    EncryptedMessage* encrypted_message) const {
+  if (!encrypted_message) {
+    return false;
+  }
+
+  std::string serialized_message;
+  message.SerializeToString(&serialized_message);
+
+  VLOG(5) << "EncryptedMessage: encryption_scheme=HYBRID_TINK.";
+
+  auto encrypted_result = encrypter_->Encrypt(serialized_message, "");
+  if (!encrypted_result.ok()) {
+    VLOG(5) << "EncryptedMessage: Tink could not encrypt message: "
+            << encrypted_result.status().error_message();
+    return false;
+  }
+  encrypted_message->set_ciphertext(encrypted_result.ValueOrDie());
+  encrypted_message->set_scheme(EncryptedMessage::HYBRID_TINK);
+
+  return true;
+}
+
 HybridEcdhEncryptedMessageMaker::HybridEcdhEncryptedMessageMaker(
     std::unique_ptr<HybridCipher> cipher)
     : cipher_(std::move(cipher)) {}
diff --git a/util/encrypted_message_util.h b/util/encrypted_message_util.h
index 0ef4b87..8332b85 100644
--- a/util/encrypted_message_util.h
+++ b/util/encrypted_message_util.h
@@ -20,6 +20,7 @@
 #include "./encrypted_message.pb.h"
 #include "google/protobuf/message_lite.h"
 #include "third_party/statusor/statusor.h"
+#include "third_party/tink/cc/hybrid_encrypt.h"
 #include "util/crypto_util/cipher.h"
 
 namespace cobalt {
@@ -47,47 +48,75 @@
   // |scheme| specifies which encryption scheme should be used.
   // The use of EncryptedMessage::NONE is disallowed.
   //
-  // |public_key_pem| must be appropriate to |scheme|. If |scheme| is
-  // EncryptedMessage::HYBRID_ECDH_V1 then |public_key_pem| must be a PEM
-  // encoding of a public key appropriate for that scheme.
+  // |public_key| must be appropriate to |scheme|. If |scheme| is
+  // EncryptedMessage::NONE then |public_key| is ignored. If |scheme| is
+  // EncryptedMessage::HYBRID_ECDH_V1 then |public_key| must be a PEM
+  // encoding of a public key appropriate for that scheme. If |scheme| is
+  // EncryptedMessage::HYBRID_TINK the |public_key| must be a base64-encoded
+  // serialized tink Keyset protobuf message.
   static statusor::StatusOr<std::unique_ptr<EncryptedMessageMaker>> Make(
-      const std::string& public_key_pem,
-      EncryptedMessage::EncryptionScheme scheme);
+      const std::string& public_key, EncryptedMessage::EncryptionScheme scheme);
 
   // Make an EncryptedMessageMaker. (Do not use in production)
   //
   // |scheme| specifies which encryption scheme should be used. As of this
-  // writing there are two schemes:
-  //   (i) EncryptedMessage::NONE means that messages will not be
-  //   encrypted: they will be sent in plain text. This scheme must
-  //   never be used in production Cobalt.
+  // writing there are three schemes:
+  //   - EncryptedMessage::NONE: See MakeUnencrypted below.
+  //   - EncryptedMessage::HYBRID_ECDH_V1: See MakeHybridEcdh below.
+  //   - EncryptedMessage::HYBRID_TINK: See MakeHybridTink below.
   //
-  //   (ii) EncryptedMessage::HYBRID_ECDH_V1 indicates that version 1 of
-  //   Cobalt's Elliptic-Curve Diffie-Hellman-based hybrid
-  //   public-key/private-key encryption scheme should be used.
-  //
-  // |public_key_pem| must be appropriate to |scheme|. If |scheme| is
-  // EncryptedMessage::NONE then |public_key_pem| is ignored. If |scheme| is
-  // EncryptedMessage::HYBRID_ECDH_V1 then |public_key_pem| must be a PEM
-  // encoding of a public key appropriate for that scheme.
+  // |public_key| must be appropriate to |scheme|. If |scheme| is
+  // EncryptedMessage::NONE then |public_key| is ignored. If |scheme| is
+  // EncryptedMessage::HYBRID_ECDH_V1 then |public_key| must be a PEM
+  // encoding of a public key appropriate for that scheme. If |scheme| is
+  // EncryptedMessage::HYBRID_TINK the |public_key| must be a base64-encoded
+  // serialized tink Keyset protobuf message.
   static statusor::StatusOr<std::unique_ptr<EncryptedMessageMaker>>
-  MakeAllowUnencrypted(const std::string& public_key_pem,
+  MakeAllowUnencrypted(const std::string& public_key,
                        EncryptedMessage::EncryptionScheme scheme);
 
-  // Make an UnencryptedMessageMaker. Equivalent to calling:
-  // MakeAllowUnencrypted("", EncryptedMessage::NONE)
+  // Make an UnencryptedMessageMaker.
+  // Message will be serialized, but not encrypted: they will be sent in plain
+  // text. This scheme must never be used in production Cobalt.
   static std::unique_ptr<EncryptedMessageMaker> MakeUnencrypted();
 
-  // Make a HybridEcdhEncryptedMessageMaker. Equivalent to calling:
-  // Make(public_key_pem, EncryptedMessage::HYBRID_ECDH_V1.
+  // Make a HybridEcdhEncryptedMessageMaker.
+  // Messages will be encrypted using version 1 of Cobalt's Elliptic-Curve
+  // Diffie-Hellman-based hybrid public-key/private-key encryption scheme using
+  // the |public_key_pem| that was passed in..
   static statusor::StatusOr<std::unique_ptr<EncryptedMessageMaker>>
   MakeHybridEcdh(const std::string& public_key_pem);
 
+  // Make a HybridTinkEncryptedMessageMaker.
+  // Messages will be encrypted using Tink's hybrid encryption scheme based on
+  // the keyset passed in. |public_keyset_base64| is a tink::Keyset protobuf
+  // message serialized and base64 encoded.
+  static statusor::StatusOr<std::unique_ptr<EncryptedMessageMaker>>
+  MakeHybridTink(const std::string& public_keyset_base64);
+
   virtual ~EncryptedMessageMaker() = default;
 };
 
+// HybridTinkEncryptedMessageMaker is an implementation of
+// EncryptedMessageMaker. See EncryptedMessage::HYBRID_TINK for details.
+class HybridTinkEncryptedMessageMaker : public EncryptedMessageMaker {
+ public:
+  explicit HybridTinkEncryptedMessageMaker(
+      std::unique_ptr<::crypto::tink::HybridEncrypt> encrypter);
+
+  bool Encrypt(const google::protobuf::MessageLite& message,
+               EncryptedMessage* encrypted_message) const;
+
+  EncryptedMessage::EncryptionScheme scheme() const {
+    return EncryptedMessage::HYBRID_TINK;
+  }
+
+ private:
+  std::unique_ptr<::crypto::tink::HybridEncrypt> encrypter_;
+};
+
 // HybridEcdhEncryptedMessageMaker is an implementation of
-// EncryptedMessageMaker. See EncryptedScheme::HYBRID_ECDH_V1 for details.
+// EncryptedMessageMaker. See EncryptedMessage::HYBRID_ECDH_V1 for details.
 class HybridEcdhEncryptedMessageMaker : public EncryptedMessageMaker {
  public:
   explicit HybridEcdhEncryptedMessageMaker(
diff --git a/util/encrypted_message_util_test.cc b/util/encrypted_message_util_test.cc
index ac003c7..7cadfa7 100644
--- a/util/encrypted_message_util_test.cc
+++ b/util/encrypted_message_util_test.cc
@@ -15,10 +15,19 @@
 #include "util/encrypted_message_util.h"
 
 #include <string>
+#include <utility>
 
 #include "./encrypted_message.pb.h"
 #include "./observation.pb.h"
 #include "third_party/googletest/googletest/include/gtest/gtest.h"
+#include "third_party/tink/cc/cleartext_keyset_handle.h"
+#include "third_party/tink/cc/hybrid_config.h"
+#include "third_party/tink/cc/hybrid_decrypt_factory.h"
+#include "third_party/tink/cc/hybrid_encrypt_factory.h"
+#include "third_party/tink/cc/hybrid_key_templates.h"
+#include "third_party/tink/cc/keyset_handle.h"
+#include "third_party/tink/include/proto/tink.pb.h"
+#include "util/crypto_util/base64.h"
 #include "util/crypto_util/cipher.h"
 
 namespace cobalt {
@@ -156,5 +165,76 @@
   EXPECT_FALSE(real_decrypter.DecryptMessage(encrypted_message, &observation));
 }
 
+// Check some ways that creating a HybridTinkEncryptedMessageMaker could fail.
+TEST(HybridTinkEncryptedMessageMaker, Make) {
+  // Check that an empty key is rejected.
+  auto result = cobalt::util::EncryptedMessageMaker::MakeHybridTink("");
+  EXPECT_EQ(INVALID_ARGUMENT, result.status().error_code());
+
+  // Check that a key with invalid base64 characters is rejected.
+  result = cobalt::util::EncryptedMessageMaker::MakeHybridTink("'''");
+  EXPECT_EQ(INVALID_ARGUMENT, result.status().error_code());
+}
+
+// Check that the HybridTinkEncryptedMessageMaker is correctly created and
+// encrypts the observations it is given.
+TEST(HybridTinkEncryptedMessageMaker, Encrypt) {
+  auto status = ::crypto::tink::HybridConfig::Register();
+  EXPECT_TRUE(status.ok());
+
+  // Generate a handle to a new public-private key set.
+  auto keyset_handle_result = ::crypto::tink::KeysetHandle::GenerateNew(
+      ::crypto::tink::HybridKeyTemplates::EciesP256HkdfHmacSha256Aes128Gcm());
+  EXPECT_TRUE(keyset_handle_result.ok());
+
+  // Get a handle to the public key.
+  auto public_keyset_handle_result =
+      keyset_handle_result.ValueOrDie()->GetPublicKeysetHandle();
+  EXPECT_TRUE(public_keyset_handle_result.ok());
+
+  // Get the keyset protobuf message itself.
+  const google::crypto::tink::Keyset public_keyset =
+      ::crypto::tink::CleartextKeysetHandle::GetKeyset(
+          *(public_keyset_handle_result.ValueOrDie()));
+  EXPECT_EQ(1, public_keyset.key_size());
+
+  // Serialize an encode the public key in the format expected by
+  // HybridTinkEncryptedMessageMaker.
+  std::string serialized_public_keyset;
+  EXPECT_TRUE(public_keyset.SerializeToString(&serialized_public_keyset));
+
+  std::string base64_public_keyset;
+  EXPECT_TRUE(cobalt::crypto::Base64Encode(serialized_public_keyset,
+                                           &base64_public_keyset));
+
+  auto maker =
+      cobalt::util::EncryptedMessageMaker::MakeHybridTink(base64_public_keyset);
+  EXPECT_TRUE(maker.ok());
+
+  std::string expected = "hello";
+  // Make a dummy observation.
+  auto observation = MakeDummyObservation(expected);
+
+  // Encrypt the dummy observation.
+  EncryptedMessage encrypted_message;
+  EXPECT_TRUE(maker.ValueOrDie()->Encrypt(observation, &encrypted_message));
+  EXPECT_EQ(EncryptedMessage::HYBRID_TINK, encrypted_message.scheme());
+
+  // Obtain a decrypter to be able to check the encrypted dummy observation.
+  auto decrypter_result = keyset_handle_result.ValueOrDie()
+                              ->GetPrimitive<::crypto::tink::HybridDecrypt>();
+  EXPECT_TRUE(decrypter_result.ok());
+  auto decrypter = std::move(decrypter_result.ValueOrDie());
+  auto decrypted_result =
+      decrypter->Decrypt(encrypted_message.ciphertext(), "");
+  EXPECT_TRUE(decrypted_result.ok());
+
+  observation.Clear();
+  EXPECT_EQ(0u, observation.parts().count(expected));
+  EXPECT_TRUE(observation.ParseFromString(decrypted_result.ValueOrDie()));
+
+  EXPECT_EQ(1u, observation.parts().count(expected));
+}
+
 }  // namespace util
 }  // namespace cobalt