Commit 53a80b56 authored by Maksim Moskvitin's avatar Maksim Moskvitin Committed by Commit Bot

Add SecureBox implementation

SecureBox provides a set of simple interfaces for performing encryptions
and decryptions, by using a public key owned by the recipient and/or a
secret shared by the sender and the recipient.

Bug: 1101813
Change-Id: I9ced4c91a8d31b0b395096ad6045b5075df899f5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2279971
Commit-Queue: Maksim Moskvitin <mmoskvitin@google.com>
Reviewed-by: default avatarDavid Benjamin <davidben@chromium.org>
Reviewed-by: default avatarMikel Astiz <mastiz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#791049}
parent 80d56521
......@@ -520,6 +520,7 @@ source_set("unit_tests") {
"nigori/nigori_unittest.cc",
"protocol/proto_enum_conversions_unittest.cc",
"protocol/proto_value_conversions_unittest.cc",
"trusted_vault/securebox_unittest.cc",
"trusted_vault/standalone_trusted_vault_client_unittest.cc",
]
......
......@@ -4,6 +4,8 @@
static_library("trusted_vault") {
sources = [
"securebox.cc",
"securebox.h",
"standalone_trusted_vault_backend.cc",
"standalone_trusted_vault_backend.h",
"standalone_trusted_vault_client.cc",
......@@ -17,5 +19,7 @@ static_library("trusted_vault") {
"//components/os_crypt",
"//components/signin/public/identity_manager",
"//components/sync/protocol",
"//crypto",
"//third_party/boringssl",
]
}
......@@ -3,4 +3,6 @@ include_rules = [
"+components/signin/public",
"+components/sync/driver",
"+components/sync/protocol",
"+crypto",
"+third_party/boringssl",
]
\ No newline at end of file
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync/trusted_vault/securebox.h"
#include <algorithm>
#include <cstdint>
#include <memory>
#include <utility>
#include <vector>
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_piece.h"
#include "crypto/hkdf.h"
#include "crypto/openssl_util.h"
#include "crypto/random.h"
#include "third_party/boringssl/src/include/openssl/aead.h"
#include "third_party/boringssl/src/include/openssl/bn.h"
#include "third_party/boringssl/src/include/openssl/ec.h"
#include "third_party/boringssl/src/include/openssl/ecdh.h"
#include "third_party/boringssl/src/include/openssl/nid.h"
namespace syncer {
namespace {
const size_t kP256FieldBytes = 32;
const size_t kAES128KeyLength = 16;
const size_t kNonceLength = 12;
const size_t kTagLength = 16;
const size_t kECPrivateKeyLength = 32;
const size_t kECPointLength = 65;
const size_t kVersionLength = 2;
const uint8_t kSecureBoxVersion[] = {0x02, 0};
const uint8_t kHkdfSalt[] = {'S', 'E', 'C', 'U', 'R', 'E',
'B', 'O', 'X', 0x02, 0};
const char kHkdfInfoWithPublicKey[] = "P256 HKDF-SHA-256 AES-128-GCM";
// Returns bytes representation of |str| (without trailing \0).
base::span<const uint8_t> StringToBytes(base::StringPiece str) {
return base::as_bytes(base::make_span(str));
}
// Concatenates spans in |bytes_spans|.
std::vector<uint8_t> ConcatBytes(
const std::vector<base::span<const uint8_t>>& bytes_spans) {
size_t total_size = 0;
for (const auto& span : bytes_spans) {
total_size += span.size();
}
std::vector<uint8_t> result(total_size);
auto output_it = result.begin();
for (const auto& span : bytes_spans) {
output_it = std::copy(span.begin(), span.end(), output_it);
}
return result;
}
// Creates public EC_KEY from |public_key_bytes|. |public_key_bytes| must be
// a X9.62 formatted NIST P-256 point.
bssl::UniquePtr<EC_KEY> ECPublicKeyFromBytes(
base::span<const uint8_t> public_key_bytes,
const crypto::OpenSSLErrStackTracer& err_tracer) {
bssl::UniquePtr<EC_KEY> ec_key(
EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
DCHECK(ec_key);
bssl::UniquePtr<EC_POINT> point(
EC_POINT_new(EC_KEY_get0_group(ec_key.get())));
DCHECK(point);
if (!EC_POINT_oct2point(EC_KEY_get0_group(ec_key.get()), point.get(),
public_key_bytes.data(), kECPointLength,
/*ctx=*/nullptr) ||
!EC_KEY_set_public_key(ec_key.get(), point.get()) ||
!EC_KEY_check_key(ec_key.get())) {
// |public_key_bytes| doesn't represent a valid NIST P-256 point.
return nullptr;
}
return ec_key;
}
// Writes |key| point into |output| using X9.62 format.
std::vector<uint8_t> ECPublicKeyToBytes(
const EC_KEY* key,
const crypto::OpenSSLErrStackTracer& err_tracer) {
std::vector<uint8_t> result(kECPointLength);
int export_length = EC_POINT_point2oct(
EC_KEY_get0_group(key), EC_KEY_get0_public_key(key),
POINT_CONVERSION_UNCOMPRESSED, result.data(), kECPointLength, nullptr);
DCHECK_EQ(export_length, static_cast<int>(kECPointLength));
return result;
}
bssl::UniquePtr<EC_KEY> GenerateECKey(
const crypto::OpenSSLErrStackTracer& err_tracer) {
bssl::UniquePtr<EC_KEY> ec_key(
EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
DCHECK(ec_key);
int generate_key_result = EC_KEY_generate_key(ec_key.get());
DCHECK(generate_key_result);
return ec_key;
}
// Computes a 16-byte shared AES-GCM secret. If |private_key| is not nullptr,
// first computes the EC-DH secret. Appends the |shared_secret|, and computes
// HKDF of that. |public_key| must be not nullpt if |private_key| isn't nullptr.
// |shared_secret| may be empty.
std::vector<uint8_t> SecureBoxComputeSecret(
const EC_KEY& private_key,
const EC_POINT& public_key,
base::span<const uint8_t> shared_secret,
const crypto::OpenSSLErrStackTracer& err_tracer) {
std::vector<uint8_t> dh_secret(kP256FieldBytes);
int dh_secret_length = ECDH_compute_key(dh_secret.data(), kP256FieldBytes,
&public_key, &private_key,
/*kdf=*/nullptr);
CHECK_EQ(dh_secret_length, static_cast<int>(kP256FieldBytes));
std::vector<uint8_t> key_material = ConcatBytes({dh_secret, shared_secret});
return crypto::HkdfSha256(key_material, kHkdfSalt,
StringToBytes(kHkdfInfoWithPublicKey),
kAES128KeyLength);
}
// This function implements AES-GCM, using AES-128, a 96-bit nonce, and 128-bit
// tag.
std::vector<uint8_t> SecureBoxAesGcmEncrypt(
base::span<const uint8_t> secret_key,
base::span<const uint8_t> nonce,
base::span<const uint8_t> plaintext,
base::span<const uint8_t> associated_data,
const crypto::OpenSSLErrStackTracer& err_tracer) {
DCHECK_EQ(secret_key.size(), kAES128KeyLength);
DCHECK_EQ(nonce.size(), kNonceLength);
const size_t max_output_length =
EVP_AEAD_max_overhead(EVP_aead_aes_128_gcm()) + plaintext.size();
bssl::ScopedEVP_AEAD_CTX ctx;
size_t output_length;
std::vector<uint8_t> result(max_output_length);
int init_result =
EVP_AEAD_CTX_init(ctx.get(), EVP_aead_aes_128_gcm(), secret_key.data(),
secret_key.size(), kTagLength, nullptr);
DCHECK(init_result);
int seal_result = EVP_AEAD_CTX_seal(
ctx.get(), result.data(), &output_length, max_output_length, nonce.data(),
nonce.size(), plaintext.data(), plaintext.size(), associated_data.data(),
associated_data.size());
CHECK(seal_result);
DCHECK_LE(output_length, max_output_length);
result.resize(output_length);
return result;
}
// Decrypts using AES-GCM.
base::Optional<std::vector<uint8_t>> SecureBoxAesGcmDecrypt(
base::span<const uint8_t> secret_key,
base::span<const uint8_t> nonce,
base::span<const uint8_t> ciphertext,
base::span<const uint8_t> associated_data,
const crypto::OpenSSLErrStackTracer& err_tracer) {
const size_t max_output_length = ciphertext.size();
bssl::ScopedEVP_AEAD_CTX ctx;
size_t output_length;
std::vector<uint8_t> result(max_output_length);
int init_result =
EVP_AEAD_CTX_init(ctx.get(), EVP_aead_aes_128_gcm(), secret_key.data(),
secret_key.size(), kTagLength, /*impl=*/nullptr);
DCHECK(init_result);
if (!EVP_AEAD_CTX_open(ctx.get(), result.data(), &output_length,
max_output_length, nonce.data(), nonce.size(),
ciphertext.data(), ciphertext.size(),
associated_data.data(), associated_data.size())) {
// |ciphertext| can't be decrypted with given parameters.
return base::nullopt;
}
DCHECK_LE(output_length, max_output_length);
result.resize(output_length);
return result;
}
// Creates NIST P-256 EC_KEY given NIST P-256 point multiplier in padded
// big-endian format. Returns nullptr if P-256 key can't be derived using
// |key_bytes| or its format is incorrect.
bssl::UniquePtr<EC_KEY> ImportECPrivateKey(
base::span<uint8_t> key_bytes,
const crypto::OpenSSLErrStackTracer& err_tracer) {
if (key_bytes.size() != kECPrivateKeyLength) {
return nullptr;
}
bssl::UniquePtr<EC_KEY> private_ec_key(
EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
DCHECK(private_ec_key);
bssl::UniquePtr<BIGNUM> private_key(
BN_bin2bn(key_bytes.data(), kECPrivateKeyLength, /*ret=*/nullptr));
if (!private_key ||
!EC_KEY_set_private_key(private_ec_key.get(), private_key.get())) {
return nullptr;
}
const EC_GROUP* group = EC_KEY_get0_group(private_ec_key.get());
bssl::UniquePtr<EC_POINT> point(EC_POINT_new(group));
if (!EC_POINT_mul(EC_KEY_get0_group(private_ec_key.get()), point.get(),
private_key.get(), /*q=*/nullptr, /*m=*/nullptr,
/*ctx=*/nullptr) ||
!EC_KEY_set_public_key(private_ec_key.get(), point.get()) ||
!EC_KEY_check_key(private_ec_key.get())) {
return nullptr;
}
return private_ec_key;
}
} // namespace
// static
std::unique_ptr<SecureBoxPublicKey> SecureBoxPublicKey::CreateByImport(
base::span<const uint8_t> key_bytes) {
const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
bssl::UniquePtr<EC_KEY> ec_key = ECPublicKeyFromBytes(key_bytes, err_tracer);
if (!ec_key) {
return nullptr;
}
return base::WrapUnique(
new SecureBoxPublicKey(std::move(ec_key), err_tracer));
}
// static
std::unique_ptr<SecureBoxPublicKey> SecureBoxPublicKey::CreateInternal(
bssl::UniquePtr<EC_KEY> key,
const crypto::OpenSSLErrStackTracer& err_tracer) {
return base::WrapUnique(new SecureBoxPublicKey(std::move(key), err_tracer));
}
SecureBoxPublicKey::SecureBoxPublicKey(
bssl::UniquePtr<EC_KEY> key,
const crypto::OpenSSLErrStackTracer& err_tracer)
: key_(std::move(key)) {
DCHECK(EC_KEY_check_key(key_.get()));
DCHECK_EQ(EC_GROUP_get_curve_name(EC_KEY_get0_group(key_.get())),
NID_X9_62_prime256v1);
}
SecureBoxPublicKey::~SecureBoxPublicKey() = default;
std::vector<uint8_t> SecureBoxPublicKey::ExportToBytes() const {
const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
return ECPublicKeyToBytes(key_.get(), err_tracer);
}
std::vector<uint8_t> SecureBoxPublicKey::Encrypt(
base::span<const uint8_t> shared_secret,
base::span<const uint8_t> header,
base::span<const uint8_t> payload) const {
const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
bssl::UniquePtr<EC_KEY> our_key_pair = GenerateECKey(err_tracer);
std::vector<uint8_t> secret_key =
SecureBoxComputeSecret(*our_key_pair, *EC_KEY_get0_public_key(key_.get()),
shared_secret, err_tracer);
std::vector<uint8_t> nonce(kNonceLength);
crypto::RandBytes(nonce.data(), kNonceLength);
std::vector<uint8_t> ciphertext =
SecureBoxAesGcmEncrypt(secret_key, nonce, payload, header, err_tracer);
std::vector<uint8_t> encoded_our_public_key =
ECPublicKeyToBytes(our_key_pair.get(), err_tracer);
return ConcatBytes(
{kSecureBoxVersion, encoded_our_public_key, nonce, ciphertext});
}
// static
std::unique_ptr<SecureBoxPrivateKey> SecureBoxPrivateKey::CreateByImport(
base::span<const uint8_t> key_bytes) {
const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
if (key_bytes.size() != kECPrivateKeyLength) {
return nullptr;
}
bssl::UniquePtr<EC_KEY> private_ec_key(
EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
DCHECK(private_ec_key);
bssl::UniquePtr<BIGNUM> private_key(
BN_bin2bn(key_bytes.data(), kECPrivateKeyLength, /*ret=*/nullptr));
if (!private_key ||
!EC_KEY_set_private_key(private_ec_key.get(), private_key.get())) {
return nullptr;
}
const EC_GROUP* group = EC_KEY_get0_group(private_ec_key.get());
bssl::UniquePtr<EC_POINT> point(EC_POINT_new(group));
if (!EC_POINT_mul(EC_KEY_get0_group(private_ec_key.get()), point.get(),
private_key.get(), /*q=*/nullptr, /*m=*/nullptr,
/*ctx=*/nullptr) ||
!EC_KEY_set_public_key(private_ec_key.get(), point.get()) ||
!EC_KEY_check_key(private_ec_key.get())) {
return nullptr;
}
return base::WrapUnique(
new SecureBoxPrivateKey(std::move(private_ec_key), err_tracer));
}
// static
std::unique_ptr<SecureBoxPrivateKey> SecureBoxPrivateKey::CreateInternal(
bssl::UniquePtr<EC_KEY> key,
const crypto::OpenSSLErrStackTracer& err_tracer) {
return base::WrapUnique(new SecureBoxPrivateKey(std::move(key), err_tracer));
}
SecureBoxPrivateKey::SecureBoxPrivateKey(
bssl::UniquePtr<EC_KEY> key,
const crypto::OpenSSLErrStackTracer& error_tracer)
: key_(std::move(key)) {
DCHECK(EC_KEY_get0_private_key(key_.get()));
DCHECK(EC_KEY_check_key(key_.get()));
DCHECK_EQ(EC_GROUP_get_curve_name(EC_KEY_get0_group(key_.get())),
NID_X9_62_prime256v1);
}
SecureBoxPrivateKey::~SecureBoxPrivateKey() = default;
std::vector<uint8_t> SecureBoxPrivateKey::ExportToBytes() const {
const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
std::vector<uint8_t> result(kECPrivateKeyLength);
int bn2bin_result =
BN_bn2bin_padded(result.data(), kECPrivateKeyLength,
/*in=*/EC_KEY_get0_private_key(key_.get()));
DCHECK(bn2bin_result);
return result;
}
base::Optional<std::vector<uint8_t>> SecureBoxPrivateKey::Decrypt(
base::span<const uint8_t> shared_secret,
base::span<const uint8_t> header,
base::span<const uint8_t> encrypted_payload) const {
const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
size_t min_payload_size = kVersionLength + kECPointLength + kNonceLength;
if (encrypted_payload.size() < min_payload_size ||
encrypted_payload[0] != kSecureBoxVersion[0] ||
encrypted_payload[1] != kSecureBoxVersion[1]) {
return base::nullopt;
}
size_t offset = kVersionLength;
bssl::UniquePtr<EC_KEY> their_ec_public_key = ECPublicKeyFromBytes(
encrypted_payload.subspan(offset, kECPointLength), err_tracer);
if (!their_ec_public_key) {
return base::nullopt;
}
offset += kECPointLength;
std::vector<uint8_t> secret_key = SecureBoxComputeSecret(
*key_, *EC_KEY_get0_public_key(their_ec_public_key.get()), shared_secret,
err_tracer);
base::span<const uint8_t> nonce =
encrypted_payload.subspan(offset, kNonceLength);
offset += kNonceLength;
base::span<const uint8_t> ciphertext = encrypted_payload.subspan(offset);
return SecureBoxAesGcmDecrypt(secret_key, nonce, ciphertext, header,
err_tracer);
}
// static
std::unique_ptr<SecureBoxKeyPair> SecureBoxKeyPair::GenerateRandom() {
const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
return base::WrapUnique(
new SecureBoxKeyPair(GenerateECKey(err_tracer), err_tracer));
}
// static
std::unique_ptr<SecureBoxKeyPair> SecureBoxKeyPair::CreateByPrivateKeyImport(
base::span<uint8_t> private_key_bytes) {
const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
bssl::UniquePtr<EC_KEY> private_key =
ImportECPrivateKey(private_key_bytes, err_tracer);
if (!private_key) {
return nullptr;
}
return base::WrapUnique(
new SecureBoxKeyPair(std::move(private_key), err_tracer));
}
SecureBoxKeyPair::SecureBoxKeyPair(
bssl::UniquePtr<EC_KEY> private_ec_key,
const crypto::OpenSSLErrStackTracer& err_tracer) {
DCHECK(private_ec_key);
bssl::UniquePtr<EC_KEY> public_ec_key(
EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
EC_KEY_set_public_key(public_ec_key.get(),
EC_KEY_get0_public_key(private_ec_key.get()));
private_key_ = SecureBoxPrivateKey::CreateInternal(std::move(private_ec_key),
err_tracer);
DCHECK(private_key_);
public_key_ =
SecureBoxPublicKey::CreateInternal(std::move(public_ec_key), err_tracer);
DCHECK(public_key_);
}
SecureBoxKeyPair::~SecureBoxKeyPair() = default;
} // namespace syncer
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_SYNC_TRUSTED_VAULT_SECUREBOX_H_
#define COMPONENTS_SYNC_TRUSTED_VAULT_SECUREBOX_H_
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include "base/containers/span.h"
#include "base/optional.h"
#include "third_party/boringssl/src/include/openssl/base.h"
namespace crypto {
class OpenSSLErrStackTracer;
} // namespace crypto
namespace syncer {
class SecureBoxPublicKey {
public:
// Creates public key given a X9.62 formatted NIST P-256 point as |key_bytes|
// (e.g. by the output of SecureBoxPublicKey::ExportToBytes()). Returns
// nullptr if point format isn't correct or it doesn't represent a valid P-256
// point.
static std::unique_ptr<SecureBoxPublicKey> CreateByImport(
base::span<const uint8_t> key_bytes);
// |key| must be a valid NIST P-256 key with filled public key. This method
// shouldn't be used outside internal SecureBox implementation.
static std::unique_ptr<SecureBoxPublicKey> CreateInternal(
bssl::UniquePtr<EC_KEY> key,
const crypto::OpenSSLErrStackTracer& err_tracer);
SecureBoxPublicKey(const SecureBoxPublicKey& other) = delete;
SecureBoxPublicKey& operator=(const SecureBoxPublicKey& other) = delete;
~SecureBoxPublicKey();
// Returns a X9.62 formatted NIST P-256 point.
std::vector<uint8_t> ExportToBytes() const;
// Encrypts |payload| according to SecureBox v2 spec (go/securebox2):
// 1. Key material is P-256 ECDH key derived from |key_| and randomly
// generated P-256 key pair, concatenated with |shared_secret|.
// 2. Encryption key is derived from key material using HKDF-SHA256.
// 3. |payload| is encrypted using AES-128-GCM, using random 96-bit nonce and
// given |header|.
// |shared_secret|, |header| and |payload| may be empty.
std::vector<uint8_t> Encrypt(base::span<const uint8_t> shared_secret,
base::span<const uint8_t> header,
base::span<const uint8_t> payload) const;
private:
// |key| must be a valid NIST P-256 key with filled public key.
SecureBoxPublicKey(bssl::UniquePtr<EC_KEY> key,
const crypto::OpenSSLErrStackTracer& err_tracer);
bssl::UniquePtr<EC_KEY> key_;
};
class SecureBoxPrivateKey {
public:
// Creates private key given NIST P-256 scalar in padded big-endian format
// (e.g. by the output of SecureBoxPrivateKey::ExportToBytes()). Returns
// nullptr if P-256 key can't be decoded from |key_bytes| or its format is
// incorrect.
static std::unique_ptr<SecureBoxPrivateKey> CreateByImport(
base::span<const uint8_t> key_bytes);
// |key| must be a valid NIST P-256 key with filled private and public key.
// This method shouldn't be used outside internal SecureBox implementation.
static std::unique_ptr<SecureBoxPrivateKey> CreateInternal(
bssl::UniquePtr<EC_KEY> key,
const crypto::OpenSSLErrStackTracer& err_tracer);
SecureBoxPrivateKey(const SecureBoxPrivateKey& other) = delete;
SecureBoxPrivateKey& operator=(const SecureBoxPrivateKey& other) = delete;
~SecureBoxPrivateKey();
// Returns NIST P-256 scalar in padded big-endian format.
std::vector<uint8_t> ExportToBytes() const;
// Decrypts |encrypted_payload| according to SecureBox v2 spec (see
// SecureBoxPublicKey::Encrypt()). Returns nullopt if payload was encrypted
// with different parameters or |encrypted_payload| isn't a valid SecureBox
// encrypted data.
base::Optional<std::vector<uint8_t>> Decrypt(
base::span<const uint8_t> shared_secret,
base::span<const uint8_t> header,
base::span<const uint8_t> encrypted_payload) const;
private:
// |key| must be a valid NIST P-256 key with filled private and public key.
explicit SecureBoxPrivateKey(bssl::UniquePtr<EC_KEY> key,
const crypto::OpenSSLErrStackTracer& err_tracer);
bssl::UniquePtr<EC_KEY> key_;
};
class SecureBoxKeyPair {
public:
// Generates new random key pair. Never returns nullptr.
static std::unique_ptr<SecureBoxKeyPair> GenerateRandom();
// Creates key pair given NIST P-256 scalar in padded big-endian format
// (e.g. by the output of SecureBoxPrivateKey::ExportToBytes()). Returns
// nullptr if P-256 key can't be decoded from |private_key_bytes| or its
// format is incorrect.
static std::unique_ptr<SecureBoxKeyPair> CreateByPrivateKeyImport(
base::span<uint8_t> private_key_bytes);
SecureBoxKeyPair(const SecureBoxKeyPair& other) = delete;
SecureBoxKeyPair& operator=(const SecureBoxKeyPair& other) = delete;
~SecureBoxKeyPair();
const SecureBoxPrivateKey& private_key() const { return *private_key_; }
const SecureBoxPublicKey& public_key() const { return *public_key_; }
private:
SecureBoxKeyPair(bssl::UniquePtr<EC_KEY> private_ec_key,
const crypto::OpenSSLErrStackTracer& err_tracer);
std::unique_ptr<SecureBoxPrivateKey> private_key_;
std::unique_ptr<SecureBoxPublicKey> public_key_;
};
} // namespace syncer
#endif // COMPONENTS_SYNC_TRUSTED_VAULT_SECUREBOX_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync/trusted_vault/securebox.h"
#include <cstdint>
#include <map>
#include <memory>
#include <vector>
#include "base/optional.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
namespace {
using testing::Eq;
using testing::IsEmpty;
using testing::Ne;
using testing::NotNull;
using testing::SizeIs;
std::vector<uint8_t> StringToBytes(base::StringPiece str) {
const uint8_t* raw_data = reinterpret_cast<const uint8_t*>(str.data());
return std::vector<uint8_t>(raw_data, raw_data + str.length());
}
class SecureBoxTest : public testing::Test {
public:
const size_t kPublicKeyLengthInBytes = 65;
const size_t kPrivateKeyLengthInBytes = 32;
const std::vector<uint8_t> kTestSharedSecret =
StringToBytes("TEST_SHARED_SECRET");
const std::vector<uint8_t> kTestHeader = StringToBytes("TEST_HEADER");
const std::vector<uint8_t> kTestPayload = StringToBytes("TEST_PAYLOAD");
};
TEST_F(SecureBoxTest, ShouldExportAndImportPublicKey) {
std::unique_ptr<SecureBoxKeyPair> key_pair =
SecureBoxKeyPair::GenerateRandom();
ASSERT_THAT(key_pair, NotNull());
std::vector<uint8_t> exported_public_key =
key_pair->public_key().ExportToBytes();
EXPECT_THAT(exported_public_key, SizeIs(kPublicKeyLengthInBytes));
std::unique_ptr<SecureBoxPublicKey> imported_public_key =
SecureBoxPublicKey::CreateByImport(exported_public_key);
EXPECT_THAT(imported_public_key, NotNull());
EXPECT_THAT(imported_public_key->ExportToBytes(), Eq(exported_public_key));
}
TEST_F(SecureBoxTest, ShouldExportAndImportPrivateKey) {
std::unique_ptr<SecureBoxKeyPair> key_pair =
SecureBoxKeyPair::GenerateRandom();
ASSERT_THAT(key_pair, NotNull());
std::vector<uint8_t> exported_private_key =
key_pair->private_key().ExportToBytes();
EXPECT_THAT(exported_private_key, SizeIs(kPrivateKeyLengthInBytes));
std::unique_ptr<SecureBoxPrivateKey> imported_private_key =
SecureBoxPrivateKey::CreateByImport(exported_private_key);
ASSERT_THAT(imported_private_key, NotNull());
EXPECT_THAT(imported_private_key->ExportToBytes(), Eq(exported_private_key));
}
TEST_F(SecureBoxTest, ShouldExportPrivateKeyAndImportKeyPair) {
std::unique_ptr<SecureBoxKeyPair> key_pair =
SecureBoxKeyPair::GenerateRandom();
ASSERT_THAT(key_pair, NotNull());
std::vector<uint8_t> exported_private_key =
key_pair->private_key().ExportToBytes();
std::unique_ptr<SecureBoxKeyPair> imported_key_pair =
SecureBoxKeyPair::CreateByPrivateKeyImport(exported_private_key);
ASSERT_THAT(imported_key_pair, NotNull());
EXPECT_THAT(imported_key_pair->private_key().ExportToBytes(),
Eq(exported_private_key));
EXPECT_THAT(imported_key_pair->public_key().ExportToBytes(),
Eq(key_pair->public_key().ExportToBytes()));
}
TEST_F(SecureBoxTest, ShouldGenerateDifferentKeys) {
std::unique_ptr<SecureBoxKeyPair> key_pair_a =
SecureBoxKeyPair::GenerateRandom();
std::unique_ptr<SecureBoxKeyPair> key_pair_b =
SecureBoxKeyPair::GenerateRandom();
ASSERT_THAT(key_pair_a, NotNull());
ASSERT_THAT(key_pair_b, NotNull());
EXPECT_THAT(key_pair_a->public_key().ExportToBytes(),
Ne(key_pair_b->public_key().ExportToBytes()));
EXPECT_THAT(key_pair_a->private_key().ExportToBytes(),
Ne(key_pair_b->private_key().ExportToBytes()));
}
TEST_F(SecureBoxTest, ShouldEncryptThenDecrypt) {
std::unique_ptr<SecureBoxKeyPair> key_pair =
SecureBoxKeyPair::GenerateRandom();
ASSERT_THAT(key_pair, NotNull());
std::vector<uint8_t> encrypted = key_pair->public_key().Encrypt(
kTestSharedSecret, kTestHeader, kTestPayload);
base::Optional<std::vector<uint8_t>> decrypted =
key_pair->private_key().Decrypt(kTestSharedSecret, kTestHeader,
encrypted);
ASSERT_THAT(decrypted, Ne(base::nullopt));
EXPECT_THAT(*decrypted, Eq(kTestPayload));
}
TEST_F(SecureBoxTest, ShouldEncryptThenDecryptWithEmptySharedSecret) {
std::unique_ptr<SecureBoxKeyPair> key_pair =
SecureBoxKeyPair::GenerateRandom();
ASSERT_THAT(key_pair, NotNull());
std::vector<uint8_t> encrypted = key_pair->public_key().Encrypt(
/*shared_secret=*/base::span<uint8_t>(), kTestHeader, kTestPayload);
base::Optional<std::vector<uint8_t>> decrypted =
key_pair->private_key().Decrypt(/*shared_secret=*/base::span<uint8_t>(),
kTestHeader, encrypted);
ASSERT_THAT(decrypted, Ne(base::nullopt));
EXPECT_THAT(*decrypted, Eq(kTestPayload));
}
TEST_F(SecureBoxTest, ShouldEncryptThenDecryptWithEmptyHeader) {
std::unique_ptr<SecureBoxKeyPair> key_pair =
SecureBoxKeyPair::GenerateRandom();
ASSERT_THAT(key_pair, NotNull());
std::vector<uint8_t> encrypted = key_pair->public_key().Encrypt(
kTestSharedSecret, /*header=*/base::span<uint8_t>(), kTestPayload);
base::Optional<std::vector<uint8_t>> decrypted =
key_pair->private_key().Decrypt(
kTestSharedSecret, /*header=*/base::span<uint8_t>(), encrypted);
ASSERT_THAT(decrypted, Ne(base::nullopt));
EXPECT_THAT(*decrypted, Eq(kTestPayload));
}
TEST_F(SecureBoxTest, ShouldEncryptThenDecryptWithEmptyPayload) {
std::unique_ptr<SecureBoxKeyPair> key_pair =
SecureBoxKeyPair::GenerateRandom();
ASSERT_THAT(key_pair, NotNull());
std::vector<uint8_t> encrypted = key_pair->public_key().Encrypt(
kTestSharedSecret, kTestHeader, /*payload=*/base::span<uint8_t>());
base::Optional<std::vector<uint8_t>> decrypted =
key_pair->private_key().Decrypt(kTestSharedSecret, kTestHeader,
encrypted);
ASSERT_THAT(decrypted, Ne(base::nullopt));
EXPECT_THAT(*decrypted, IsEmpty());
}
TEST_F(SecureBoxTest, ShouldDecryptTestVectors) {
struct TestVector {
std::string private_key;
std::string shared_secret;
std::string header;
std::string payload;
std::string encrypted_payload;
};
const std::vector<TestVector> kTestVectors = {
{/*private_key=*/
"49e052293c29b5a50b0013eec9d030ac2ad70a42fe093be084264647cb04e16f",
/*shared_secret=*/"efbaea750bb3e8190e8bb0b6df9f1382",
/*header=*/"6430055109c9c1853ab0d11e",
/*payload=*/
"9f2648ea719ee452bd4f4e9e4b19df75de6c028c8bd3a385",
/*encrypted_payload=*/
"020004c4c803b44a189adac641bb04ed0073d352de5e2cfdba935a88e33c5f39f26d4d"
"8c7e87e4dd9322491ac401f92d3336560d181629017bd58e4884ea25e44423ec75a889"
"b43e4ea48f46864fc863430459dc241d7acef4042255eed8c4fbf9a71cde4ef4d650d5"
"72f8a22f67a73751f4a1b4dad0fc"},
{/*private_key=*/
"2818d19cc43b873d94e50d9e4cd07dac8814c0597c6b11866350f1e17aeb87c3",
/*shared_secret=*/"6689c9b65a6723c419b818c03340b3ce",
/*header=*/"278a6d38639793e2dcdae738",
/*payload=*/
"732fd9df1c334a0404db3ca15cc8bb7dd03fa0a6b42f329f",
/*encrypted_payload=*/
"020004fe4928f722ec664567779d83b1254786931a2e119019f33b4e0413c0e08b0845"
"eb892ce5a9bf54154081fae12808ce40824743df8c70aeb681d5f132d75aecf00eb89a"
"4827bf50fb7e11ca57f5f7d9364dbf5d552a513ac705e2159d3bb801e414c16f14a837"
"400e31cc85181c2dccd14f558af1"},
{/*private_key=*/
"90149c92a432b9a62d4ebee16e2d358aa7253c3160126f01bb16e3d70523643a",
/*shared_secret=*/"6f7171c87b33c444f9f0b0c0956b4977",
/*header=*/"6094dd78335861acaf599a29",
/*payload=*/
"011360559ef4615e42bfb67de144acf4a10c750a92af6cef",
/*encrypted_payload=*/
"02000413765df10f60bc52a5be14e1d7a3c08ab907704574d30993db6d960344e366d3"
"42f0e06416ac0baa15a8c7e6adaffece55c4df14cc0f6d8769e3a6dc64a85df4ed5959"
"f76b51b123bc8f2572bec12c46138b5362b967850cc6b297fffc20fa639adfc2aebc37"
"f96aea37d9f46c42970d44ebe245"},
{/*private_key=*/
"332f062284ca639d4d89047b4518b57e081ad211ce60d2855c162e55adf702b4",
/*shared_secret=*/"3f84552779a8e37a8cfed47cde41a14f",
/*header=*/"8b21ec8a81b2e79221af61e3",
/*payload=*/
"92fd1f0f297b0e60cf1e61d59c7b820f90c027ef74f57a91",
/*encrypted_payload=*/
"020004c8d7a52c441f298e9366ccb10a0197db798f56ef1b94c026783b95bd209f48ea"
"07f5f783ea7c9617ddd6c1651b7f983f0404fb6a0d59f57035416e7d079479b7197662"
"21930955107978660153165b30aea9e6bf9cae23e9fa9156c27e44da6bc254d636fd0b"
"3b5e1ec279c7d9d2ed5e6644d638"}};
for (const TestVector& test_vector : kTestVectors) {
SCOPED_TRACE("Failure with private key: " + test_vector.private_key);
std::vector<uint8_t> private_key_bytes;
ASSERT_TRUE(
base::HexStringToBytes(test_vector.private_key, &private_key_bytes));
std::unique_ptr<SecureBoxPrivateKey> private_key =
SecureBoxPrivateKey::CreateByImport(private_key_bytes);
ASSERT_THAT(private_key, NotNull());
std::vector<uint8_t> shared_secret;
ASSERT_TRUE(
base::HexStringToBytes(test_vector.shared_secret, &shared_secret));
std::vector<uint8_t> header;
ASSERT_TRUE(base::HexStringToBytes(test_vector.header, &header));
std::vector<uint8_t> encrypted_payload;
ASSERT_TRUE(base::HexStringToBytes(test_vector.encrypted_payload,
&encrypted_payload));
base::Optional<std::vector<uint8_t>> decrypted_payload =
private_key->Decrypt(shared_secret, header, encrypted_payload);
ASSERT_THAT(decrypted_payload, Ne(base::nullopt));
std::vector<uint8_t> expected_payload;
ASSERT_TRUE(base::HexStringToBytes(test_vector.payload, &expected_payload));
EXPECT_THAT(*decrypted_payload, Eq(expected_payload));
}
}
} // namespace
} // namespace syncer
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment