blob: 97a203cdd8805afea95bfae4bf75c124c3671f90 [file] [log] [blame] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "libhwsec-foundation/crypto/secure_box.h"
#include <iterator>
#include <optional>
#include <utility>
#include <brillo/secure_blob.h>
#include "libhwsec-foundation/crypto/aes.h"
#include "libhwsec-foundation/crypto/big_num_util.h"
#include "libhwsec-foundation/crypto/ecdh_hkdf.h"
#include "libhwsec-foundation/crypto/elliptic_curve.h"
#include "libhwsec-foundation/crypto/hkdf.h"
namespace hwsec_foundation {
namespace secure_box {
namespace {
constexpr size_t kAES128KeySize = 128 / CHAR_BIT;
constexpr int kPublicKeyCoordinateSize = 256 / CHAR_BIT;
constexpr int kPrivateKeyScalarSize = 256 / CHAR_BIT;
constexpr uint8_t kEcPublicKeyUncompressedFormatPrefix = 4;
constexpr uint8_t kEcPublicKeyUncompressedFormatSize =
1 + 2 * kPublicKeyCoordinateSize;
constexpr uint8_t kSecureBoxVersion[] = {0x02, 0};
constexpr char kHkdfSaltPrefix[] = "SECUREBOX";
constexpr char kHkdfInfoWithPublicKey[] = "P256 HKDF-SHA-256 AES-128-GCM";
constexpr char kHkdfInfoWithoutPublicKey[] = "SHARED HKDF-SHA-256 AES-128-GCM";
crypto::ScopedEC_POINT DecodePublicKey(const EllipticCurve& curve,
BN_CTX* context,
const brillo::Blob& public_key) {
if (public_key.size() != kEcPublicKeyUncompressedFormatSize) {
LOG(ERROR) << "Incorrect public key size.";
return nullptr;
}
if (public_key[0] != kEcPublicKeyUncompressedFormatPrefix) {
LOG(ERROR) << "Incorrect public key prefix.";
return nullptr;
}
crypto::ScopedBIGNUM pub_x_bn = BlobToBigNum(
brillo::Blob(public_key.begin() + 1,
public_key.begin() + 1 + kPublicKeyCoordinateSize));
crypto::ScopedBIGNUM pub_y_bn = BlobToBigNum(brillo::Blob(
public_key.begin() + 1 + kPublicKeyCoordinateSize, public_key.end()));
if (!pub_x_bn || !pub_y_bn) {
LOG(ERROR) << "Failed to transform public key coordinates to BIGNUM.";
return nullptr;
}
crypto::ScopedEC_POINT point = curve.CreatePoint();
if (!point) {
LOG(ERROR) << "Failed to allocate EC point.";
return nullptr;
}
if (!EC_POINT_set_affine_coordinates(curve.GetGroup(), point.get(),
pub_x_bn.get(), pub_y_bn.get(),
context)) {
LOG(ERROR) << "Failed to set affine coordinates.";
return nullptr;
}
if (!curve.IsPointValidAndFinite(*point, context)) {
LOG(ERROR) << "Decoded point is invalid.";
return nullptr;
}
return point;
}
} // namespace
std::optional<brillo::Blob> EncodePublicKey(const EllipticCurve& curve,
BN_CTX* context,
const EC_POINT& public_key_pt) {
crypto::ScopedBIGNUM pub_x_bn = CreateBigNum();
crypto::ScopedBIGNUM pub_y_bn = CreateBigNum();
if (!pub_x_bn || !pub_y_bn) {
LOG(ERROR) << "Failed to allocate BIGNUM structures.";
return std::nullopt;
}
if (!curve.GetAffineCoordinates(public_key_pt, context, pub_x_bn.get(),
pub_y_bn.get())) {
LOG(ERROR) << "Failed to get public key coordinates.";
return std::nullopt;
}
brillo::Blob pub_x, pub_y;
if (!BigNumToBlob(*pub_x_bn, kPublicKeyCoordinateSize, &pub_x) ||
!BigNumToBlob(*pub_y_bn, kPublicKeyCoordinateSize, &pub_y)) {
LOG(ERROR) << "Failed to transform public key coordinates to blobs.";
}
auto public_key = brillo::CombineBlobs(
{brillo::Blob({kEcPublicKeyUncompressedFormatPrefix}), pub_x, pub_y});
DCHECK_EQ(public_key.size(), kEcPublicKeyUncompressedFormatSize);
return public_key;
}
std::optional<KeyPair> DeriveKeyPairFromSeed(const brillo::SecureBlob& seed) {
ScopedBN_CTX context = CreateBigNumContext();
if (!context) {
LOG(ERROR) << "Failed to allocate BIGNUM context.";
return std::nullopt;
}
std::optional<EllipticCurve> curve =
EllipticCurve::Create(EllipticCurve::CurveType::kPrime256, context.get());
if (!curve.has_value()) {
LOG(ERROR) << "Failed to create EllipticCurve.";
}
crypto::ScopedBIGNUM seed_bn = SecureBlobToBigNum(seed);
if (!seed_bn) {
LOG(ERROR) << "Failed to transform seed to BIGNUM.";
return std::nullopt;
}
crypto::ScopedBIGNUM priv_bn =
curve->ModToValidNonZeroScalar(*seed_bn, context.get());
if (!priv_bn) {
LOG(ERROR) << "Failed to transform seed to a valid scalar on curve.";
return std::nullopt;
}
brillo::SecureBlob private_key;
if (!BigNumToSecureBlob(*priv_bn, kPrivateKeyScalarSize, &private_key)) {
LOG(ERROR) << "Failed to transform private key scalar to SecureBlob.";
return std::nullopt;
}
crypto::ScopedEC_POINT public_key_pt =
curve->MultiplyWithGenerator(*priv_bn, context.get());
if (!public_key_pt) {
LOG(ERROR) << "Failed to calculate public key.";
return std::nullopt;
}
std::optional<brillo::Blob> public_key =
EncodePublicKey(*curve, context.get(), *public_key_pt);
if (!public_key.has_value()) {
LOG(ERROR) << "Failed to encode public key.";
return std::nullopt;
}
// SecureBox's encoded private key format is the concatenation of the private
// key and the public key. This is such that when the server side decrypts the
// encrypted encoded private key, it contains the whole key pair.
private_key = brillo::SecureBlob::Combine(
private_key, brillo::SecureBlob(public_key->begin(), public_key->end()));
return KeyPair{
.public_key = std::move(*public_key),
.private_key = std::move(private_key),
};
}
std::optional<brillo::Blob> Encrypt(const brillo::Blob& their_public_key,
const brillo::SecureBlob& shared_secret,
const brillo::Blob& header,
const brillo::SecureBlob& payload) {
if (their_public_key.empty() && shared_secret.empty()) {
LOG(ERROR) << "Either public key or shared secret should be non-empty.";
return std::nullopt;
}
// If |their_public_key| is empty, asymmetric encryption isn't used, so
// |our_key_pair| and |our_public_key| will both be null. In this case,
// |our_public_key| won't be included in the concatenated encryption result
// buffer.
crypto::ScopedEC_KEY our_key_pair;
brillo::SecureBlob dh_secret;
brillo::Blob hkdf_info, our_public_key;
if (their_public_key.empty()) {
hkdf_info = brillo::BlobFromString(kHkdfInfoWithoutPublicKey);
} else {
ScopedBN_CTX context = CreateBigNumContext();
if (!context) {
LOG(ERROR) << "Failed to allocate BIGNUM context.";
return std::nullopt;
}
std::optional<EllipticCurve> curve = EllipticCurve::Create(
EllipticCurve::CurveType::kPrime256, context.get());
if (!curve.has_value()) {
LOG(ERROR) << "Failed to create EllipticCurve.";
return std::nullopt;
}
// Parse their public key.
crypto::ScopedEC_POINT their_public_key_pt =
DecodePublicKey(*curve, context.get(), their_public_key);
if (!their_public_key_pt) {
LOG(ERROR) << "Failed to decode their public key.";
return std::nullopt;
}
// Generate our key pair.
our_key_pair = curve->GenerateKey(context.get());
if (!our_key_pair) {
LOG(ERROR) << "Failed to generate EC key.";
return std::nullopt;
}
const BIGNUM* private_key_bn = EC_KEY_get0_private_key(our_key_pair.get());
const EC_POINT* public_key_pt = EC_KEY_get0_public_key(our_key_pair.get());
if (!private_key_bn || !public_key_pt) {
LOG(ERROR) << "Failed to generate EC key.";
return std::nullopt;
}
// Perform ECDH.
crypto::ScopedEC_POINT shared_secret_point = ComputeEcdhSharedSecretPoint(
*curve, *their_public_key_pt, *private_key_bn);
if (!shared_secret_point) {
LOG(ERROR) << "Failed to compute shared secret point.";
return std::nullopt;
}
if (!ComputeEcdhSharedSecret(*curve, *shared_secret_point, &dh_secret)) {
LOG(ERROR) << "Failed to compute shared secret.";
return std::nullopt;
}
hkdf_info = brillo::BlobFromString(kHkdfInfoWithPublicKey);
std::optional<brillo::Blob> our_public_key_opt =
EncodePublicKey(*curve, context.get(), *public_key_pt);
if (!our_public_key_opt.has_value()) {
LOG(ERROR) << "Failed to encode public key.";
return std::nullopt;
}
our_public_key = std::move(*our_public_key_opt);
}
brillo::SecureBlob keying_material =
brillo::SecureBlob::Combine(dh_secret, shared_secret);
brillo::Blob salt =
brillo::CombineBlobs({brillo::BlobFromString(kHkdfSaltPrefix),
brillo::Blob(std::begin(kSecureBoxVersion),
std::end(kSecureBoxVersion))});
brillo::SecureBlob secret_key;
if (!Hkdf(HkdfHash::kSha256, keying_material, hkdf_info, salt, kAES128KeySize,
&secret_key)) {
LOG(ERROR) << "Failed to perform HKDF.";
return std::nullopt;
}
brillo::Blob nonce, tag, ciphertext;
std::optional<brillo::Blob> ad;
if (!header.empty()) {
ad = header;
}
if (!AesGcmEncrypt(payload, ad, secret_key, &nonce, &tag, &ciphertext)) {
LOG(ERROR) << "Failed to perform AES-GCM.";
return std::nullopt;
}
brillo::Blob encrypted_payload = brillo::CombineBlobs(
{brillo::Blob(std::begin(kSecureBoxVersion), std::end(kSecureBoxVersion)),
our_public_key, nonce, ciphertext, tag});
return encrypted_payload;
}
} // namespace secure_box
} // namespace hwsec_foundation