// 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 #include #include #include #include #include #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 StringToBytes(base::StringPiece str) { return base::as_bytes(base::make_span(str)); } // Concatenates spans in |bytes_spans|. std::vector ConcatBytes( const std::vector>& bytes_spans) { size_t total_size = 0; for (const base::span& span : bytes_spans) { total_size += span.size(); } std::vector result(total_size); auto output_it = result.begin(); for (const base::span& span : bytes_spans) { output_it = base::ranges::copy(span, output_it); } return result; } // Creates public EC_KEY from |public_key_bytes|. Returns nullptr if // |public_key_bytes| does not represent a X9.62 formatted NIST P-256 point. bssl::UniquePtr ECPublicKeyFromBytes( base::span public_key_bytes, const crypto::OpenSSLErrStackTracer& err_tracer) { if (public_key_bytes.size() != kECPointLength) { // |public_key_bytes| doesn't represent a valid NIST P-256 point. return nullptr; } bssl::UniquePtr ec_key( EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); DCHECK(ec_key); bssl::UniquePtr 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 ECPublicKeyToBytes( const EC_KEY* key, const crypto::OpenSSLErrStackTracer& err_tracer) { std::vector 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(kECPointLength)); return result; } bssl::UniquePtr GenerateECKey( const crypto::OpenSSLErrStackTracer& err_tracer) { bssl::UniquePtr 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 SecureBoxComputeSecret( const EC_KEY* private_key, const EC_POINT* public_key, base::span shared_secret, const crypto::OpenSSLErrStackTracer& err_tracer) { DCHECK_EQ(!!private_key, !!public_key); std::vector 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(kP256FieldBytes)); } else { hkdf_info = kHkdfInfoWithoutPublicKey; } std::vector 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 SecureBoxAesGcmEncrypt( base::span secret_key, base::span nonce, base::span plaintext, base::span 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 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> SecureBoxAesGcmDecrypt( base::span secret_key, base::span nonce, base::span ciphertext, base::span 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 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 ImportECPrivateKey( base::span key_bytes, const crypto::OpenSSLErrStackTracer& err_tracer) { if (key_bytes.size() != kECPrivateKeyLength) { return nullptr; } bssl::UniquePtr private_ec_key( EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); DCHECK(private_ec_key); bssl::UniquePtr 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 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 SecureBoxEncryptImpl( const EC_KEY* our_key_pair, const EC_POINT* their_public_key, base::span shared_secret, base::span header, base::span payload, const crypto::OpenSSLErrStackTracer& err_tracer) { DCHECK_EQ(!!our_key_pair, !!their_public_key); std::vector secret = SecureBoxComputeSecret( our_key_pair, their_public_key, shared_secret, err_tracer); std::vector nonce(kNonceLength); crypto::RandBytes(nonce.data(), kNonceLength); std::vector ciphertext = SecureBoxAesGcmEncrypt(secret, nonce, payload, header, err_tracer); std::vector 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> SecureBoxDecryptImpl( const EC_KEY* our_private_key, base::span shared_secret, base::span header, base::span 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 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 secret_key = SecureBoxComputeSecret( our_private_key, their_ec_public_key_point, shared_secret, err_tracer); base::span nonce = encrypted_payload.subspan(offset, kNonceLength); offset += kNonceLength; base::span ciphertext = encrypted_payload.subspan(offset); return SecureBoxAesGcmDecrypt(secret_key, nonce, ciphertext, header, err_tracer); } } // namespace std::vector SecureBoxSymmetricEncrypt( base::span shared_secret, base::span header, base::span 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> SecureBoxSymmetricDecrypt( base::span shared_secret, base::span header, base::span encrypted_payload) { return SecureBoxDecryptImpl(/*our_private_key=*/nullptr, shared_secret, header, encrypted_payload); } // static std::unique_ptr SecureBoxPublicKey::CreateByImport( base::span key_bytes) { const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); bssl::UniquePtr 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::CreateInternal( bssl::UniquePtr key, const crypto::OpenSSLErrStackTracer& err_tracer) { return base::WrapUnique(new SecureBoxPublicKey(std::move(key), err_tracer)); } SecureBoxPublicKey::SecureBoxPublicKey( bssl::UniquePtr 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 SecureBoxPublicKey::ExportToBytes() const { const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); return ECPublicKeyToBytes(key_.get(), err_tracer); } std::vector SecureBoxPublicKey::Encrypt( base::span shared_secret, base::span header, base::span payload) const { const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); bssl::UniquePtr 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::CreateByImport( base::span key_bytes) { const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); bssl::UniquePtr 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::CreateInternal( bssl::UniquePtr key, const crypto::OpenSSLErrStackTracer& err_tracer) { return base::WrapUnique(new SecureBoxPrivateKey(std::move(key), err_tracer)); } SecureBoxPrivateKey::SecureBoxPrivateKey( bssl::UniquePtr 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 SecureBoxPrivateKey::ExportToBytes() const { const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); std::vector 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> SecureBoxPrivateKey::Decrypt( base::span shared_secret, base::span header, base::span encrypted_payload) const { return SecureBoxDecryptImpl(key_.get(), shared_secret, header, encrypted_payload); } // static std::unique_ptr SecureBoxKeyPair::GenerateRandom() { const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); return base::WrapUnique( new SecureBoxKeyPair(GenerateECKey(err_tracer), err_tracer)); } // static std::unique_ptr SecureBoxKeyPair::CreateByPrivateKeyImport( base::span private_key_bytes) { const crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); bssl::UniquePtr 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 private_ec_key, const crypto::OpenSSLErrStackTracer& err_tracer) { DCHECK(private_ec_key); bssl::UniquePtr 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