blob: e330b68c8b0f0f9867feb5527bdede0f30539fd6 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/trusted_vault/securebox.h"
#include <algorithm>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/ranges/algorithm.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 trusted_vault {
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";
const char kHkdfInfoWithoutPublicKey[] = "SHARED 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 base::span<const uint8_t>& span : bytes_spans) {
total_size += span.size();
}
std::vector<uint8_t> result(total_size);
auto output_it = result.begin();
for (const base::span<const uint8_t>& span : bytes_spans) {
output_it = base::ranges::copy(span, 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 null, first
// computes the EC-DH secret. Appends the |shared_secret|, and computes HKDF of
// that. |public_key| and |private_key| might be null, but if either of them is
// not null, other must be not null as well. |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) {
DCHECK_EQ(!!private_key, !!public_key);
std::vector<uint8_t> dh_secret;
std::string hkdf_info;
if (private_key) {
hkdf_info = kHkdfInfoWithPublicKey;
dh_secret.resize(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));
} else {
hkdf_info = kHkdfInfoWithoutPublicKey;
}
std::vector<uint8_t> key_material = ConcatBytes({dh_secret, shared_secret});
return crypto::HkdfSha256(key_material, kHkdfSalt, StringToBytes(hkdf_info),
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.
absl::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 absl::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<const 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;
}
// |our_key_pair| and |their_public_key| might be null, but if either of them is
// not null, other must be not null as well. |shared_secret|, |header| and
// |payload| may be empty.
std::vector<uint8_t> SecureBoxEncryptImpl(
const EC_KEY* our_key_pair,
const EC_POINT* their_public_key,
base::span<const uint8_t> shared_secret,
base::span<const uint8_t> header,
base::span<const uint8_t> payload,
const crypto::OpenSSLErrStackTracer& err_tracer) {
DCHECK_EQ(!!our_key_pair, !!their_public_key);
std::vector<uint8_t> secret = SecureBoxComputeSecret(
our_key_pair, their_public_key, shared_secret, err_tracer);
std::vector<uint8_t> nonce(kNonceLength);
crypto::RandBytes(nonce.data(), kNonceLength);
std::vector<uint8_t> ciphertext =
SecureBoxAesGcmEncrypt(secret, nonce, payload, header, err_tracer);
std::vector<uint8_t> encoded_our_public_key;
if (our_key_pair) {
encoded_our_public_key = ECPublicKeyToBytes(our_key_pair, err_tracer);
}
return ConcatBytes(
{kSecureBoxVersion, encoded_our_public_key, nonce, ciphertext});
}
// |our_private_key| may be null. |shared_secret|, |header| and |payload| may be
// empty. Returns nullopt if decryption failed.
absl::optional<std::vector<uint8_t>> SecureBoxDecryptImpl(
const EC_KEY* our_private_key,
base::span<const uint8_t> shared_secret,
base::span<const uint8_t> header,
base::span<const uint8_t> encrypted_payload) {
const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
size_t min_payload_size = kVersionLength + kNonceLength;
if (our_private_key) {
min_payload_size += kECPointLength;
}
if (encrypted_payload.size() < min_payload_size ||
encrypted_payload[0] != kSecureBoxVersion[0] ||
encrypted_payload[1] != kSecureBoxVersion[1]) {
return absl::nullopt;
}
size_t offset = kVersionLength;
bssl::UniquePtr<EC_KEY> their_ec_public_key;
const EC_POINT* their_ec_public_key_point = nullptr;
if (our_private_key) {
their_ec_public_key = ECPublicKeyFromBytes(
encrypted_payload.subspan(offset, kECPointLength), err_tracer);
if (!their_ec_public_key) {
return absl::nullopt;
}
their_ec_public_key_point =
EC_KEY_get0_public_key(their_ec_public_key.get());
offset += kECPointLength;
}
std::vector<uint8_t> secret_key = SecureBoxComputeSecret(
our_private_key, their_ec_public_key_point, 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);
}
} // namespace
std::vector<uint8_t> SecureBoxSymmetricEncrypt(
base::span<const uint8_t> shared_secret,
base::span<const uint8_t> header,
base::span<const uint8_t> payload) {
const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
return SecureBoxEncryptImpl(/*our_key_pair=*/nullptr,
/*their_public_key=*/nullptr, shared_secret,
header, payload, err_tracer);
}
absl::optional<std::vector<uint8_t>> SecureBoxSymmetricDecrypt(
base::span<const uint8_t> shared_secret,
base::span<const uint8_t> header,
base::span<const uint8_t> encrypted_payload) {
return SecureBoxDecryptImpl(/*our_private_key=*/nullptr, shared_secret,
header, encrypted_payload);
}
// 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);
return SecureBoxEncryptImpl(our_key_pair.get(),
EC_KEY_get0_public_key(key_.get()), shared_secret,
header, payload, err_tracer);
}
// static
std::unique_ptr<SecureBoxPrivateKey> SecureBoxPrivateKey::CreateByImport(
base::span<const uint8_t> key_bytes) {
const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
bssl::UniquePtr<EC_KEY> private_ec_key =
ImportECPrivateKey(key_bytes, err_tracer);
if (!private_ec_key) {
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;
}
absl::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 {
return SecureBoxDecryptImpl(key_.get(), shared_secret, header,
encrypted_payload);
}
// 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<const 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 trusted_vault