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