blob: 04f1f743247f39b94e02c87458694c9ccf14447d [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 "arc/keymint/context/openssl_utils.h"
#include <base/check_op.h>
#include <base/stl_util.h>
#include <openssl/aes.h>
#include <openssl/rand.h>
#include <optional>
#include <utility>
namespace arc::keymint::context {
namespace {
constexpr size_t kKeySize = 32;
constexpr size_t kAes256GcmPadding = 16;
constexpr uint32_t kAffinePointLength = 32;
constexpr uint32_t kSeedSize = 32;
constexpr uint32_t kP256EcdsaPrivateKeyLength = 32;
// Encrypts a given |input| using AES-GCM-256 with |key|, |auth_data|, and |iv|.
// Returns std::nullopt if there's an error in the OpenSSL operation.
std::optional<brillo::Blob> DoAes256GcmEncrypt(
const brillo::SecureBlob& key,
const brillo::Blob& auth_data,
const brillo::Blob& iv,
const brillo::SecureBlob& input) {
CHECK_EQ(key.size(), kKeySize);
CHECK_EQ(iv.size(), kIvSize);
// Initialize cipher.
crypto::ScopedEVP_CIPHER_CTX ctx(EVP_CIPHER_CTX_new());
if (1 != EVP_EncryptInit_ex(ctx.get(), EVP_aes_256_gcm(),
/* engine */ nullptr,
/* key */ key.data(), /* iv */ iv.data())) {
return std::nullopt;
}
// Update operation with |auth_data|, out pointer must be null.
int auth_update_len = 0;
if (1 != EVP_EncryptUpdate(ctx.get(), /* out */ nullptr, &auth_update_len,
auth_data.data(), auth_data.size())) {
return std::nullopt;
}
// Update operation with |input|.
int update_len = 0;
brillo::Blob output(input.size() + kAes256GcmPadding, 0);
if (1 != EVP_EncryptUpdate(ctx.get(), output.data(), &update_len,
input.data(), input.size())) {
return std::nullopt;
}
// Finish operation, accumulate results in |output|.
int finish_len = 0;
if (1 !=
EVP_EncryptFinal_ex(ctx.get(), output.data() + update_len, &finish_len)) {
return std::nullopt;
}
CHECK_GE(output.size(), update_len + finish_len);
output.resize(update_len + finish_len);
// Retrieve tag.
brillo::Blob tag(kTagSize, 0);
if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, tag.size(),
tag.data())) {
return std::nullopt;
}
// Append tag to |output| and return the encrypted blob.
output.insert(output.end(), tag.begin(), tag.end());
return output;
}
// Decrypts a given |input| using AES-GCM-256 with |key|, |auth_data|, and |iv|.
// Returns std::nullopt if there's an error in the OpenSSL operation.
std::optional<brillo::SecureBlob> DoAes256GcmDecrypt(
const brillo::SecureBlob& key,
const brillo::Blob& auth_data,
const brillo::Blob& iv,
const brillo::Blob& input) {
CHECK_EQ(key.size(), kKeySize);
CHECK_EQ(iv.size(), kIvSize);
// Input must have a tag appended to it.
if (input.size() < kTagSize) {
return std::nullopt;
}
// Initialize cipher.
crypto::ScopedEVP_CIPHER_CTX ctx(EVP_CIPHER_CTX_new());
if (1 != EVP_DecryptInit_ex(ctx.get(), EVP_aes_256_gcm(),
/* engine */ nullptr, key.data(), iv.data())) {
return std::nullopt;
}
// Set expected tag.
brillo::Blob tag(input.end() - kTagSize, input.end());
size_t input_len = input.size() - tag.size();
if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(),
tag.data())) {
return std::nullopt;
}
// Update operation with |auth_data|, out pointer must be null.
int auth_update_len = 0;
if (1 != EVP_DecryptUpdate(ctx.get(), /* out */ nullptr, &auth_update_len,
auth_data.data(), auth_data.size())) {
return std::nullopt;
}
// Update operation with |input|.
int update_len = 0;
brillo::SecureBlob output(input_len + kAes256GcmPadding, 0);
if (1 != EVP_DecryptUpdate(ctx.get(), output.data(), &update_len,
input.data(), input_len)) {
return std::nullopt;
}
// Finish operation, accumulate results in |output|.
int finish_len = 0;
if (1 !=
EVP_CipherFinal_ex(ctx.get(), output.data() + update_len, &finish_len)) {
return std::nullopt;
}
CHECK_GE(output.size(), update_len + finish_len);
output.resize(update_len + finish_len);
// Return decrypted blob;
return output;
}
keymaster_error_t extractEcdsaPEMKey(bssl::UniquePtr<EC_KEY> ec_key,
std::string& private_key_pem) {
// Convert EC_KEY to EVP_PKEY
bssl::UniquePtr<EVP_PKEY> evp_pkey(EVP_PKEY_new());
if (!evp_pkey.get()) {
return ::keymaster::TranslateLastOpenSslError();
}
// This function will take ownership of |ec_key|.
if (EVP_PKEY_assign_EC_KEY(evp_pkey.get(), ec_key.release()) != 1) {
return ::keymaster::TranslateLastOpenSslError();
}
// Convert private key to PEM format
bssl::UniquePtr<BIO> priv_bio(BIO_new(BIO_s_mem()));
if (!priv_bio.get()) {
return ::keymaster::TranslateLastOpenSslError();
}
if (!PEM_write_bio_PrivateKey(priv_bio.get(), evp_pkey.get(), nullptr,
nullptr, 0, nullptr, nullptr)) {
return ::keymaster::TranslateLastOpenSslError();
}
// Extract private key PEM data.
BUF_MEM* buf;
BIO_get_mem_ptr(priv_bio.get(), &buf);
std::string private_key_string(buf->data, buf->length);
private_key_pem = private_key_string;
return KM_ERROR_OK;
}
} // anonymous namespace
std::optional<brillo::Blob> Aes256GcmEncrypt(const brillo::SecureBlob& key,
const brillo::Blob& auth_data,
const brillo::SecureBlob& input) {
// Compute a random IV.
brillo::Blob iv(kIvSize, 0);
if (1 != RAND_bytes(iv.data(), iv.size())) {
return std::nullopt;
}
// Encrypt the input.
std::optional<brillo::Blob> encrypted =
DoAes256GcmEncrypt(key, auth_data, iv, input);
if (!encrypted.has_value()) {
return std::nullopt;
}
// Append the random IV used for encryption to the output.
encrypted->insert(encrypted->end(), iv.begin(), iv.end());
return encrypted;
}
std::optional<brillo::SecureBlob> Aes256GcmDecrypt(
const brillo::SecureBlob& key,
const brillo::Blob& auth_data,
const brillo::Blob& input) {
// Input must have an IV appended to it.
if (input.size() < kIvSize) {
return std::nullopt;
}
// Split the input between the encrypted portion and the IV.
brillo::Blob encrypted(input.begin(), input.end() - kIvSize);
brillo::Blob iv(input.end() - kIvSize, input.end());
// Decrypt the input.
return DoAes256GcmDecrypt(key, auth_data, iv, encrypted);
}
// This function is based upon AOSP Keymaster's |GetEcdsa256KeyFromCert|
// function.
keymaster_error_t GetEcdsa256KeyFromCertBlob(brillo::Blob& certData,
absl::Span<uint8_t> x_coord,
absl::Span<uint8_t> y_coord) {
// Input Validation.
if (certData.empty() || x_coord.size() != kAffinePointLength ||
y_coord.size() != kAffinePointLength) {
return KM_ERROR_INVALID_ARGUMENT;
}
// Create BIO from Vector Data.
bssl::UniquePtr<BIO> certbio(
BIO_new_mem_buf(certData.data(), certData.size()));
if (!certbio.get()) {
// Handle BIO creation error.
return ::keymaster::TranslateLastOpenSslError();
}
// Read Certificate from BIO.
::keymaster::X509_Ptr cert(
PEM_read_bio_X509(certbio.get(), nullptr, nullptr, nullptr));
if (!cert || !cert.get()) {
return ::keymaster::TranslateLastOpenSslError();
}
// Extract Jacobian coordinates from X509 Cert.
::keymaster::EVP_PKEY_Ptr pubKey(X509_get_pubkey(cert.get()));
if (!pubKey.get()) {
return ::keymaster::TranslateLastOpenSslError();
}
EC_KEY* ecKey = EVP_PKEY_get0_EC_KEY(pubKey.get());
if (!ecKey) {
return ::keymaster::TranslateLastOpenSslError();
}
const EC_POINT* jacobian_coords = EC_KEY_get0_public_key(ecKey);
if (!jacobian_coords) {
return ::keymaster::TranslateLastOpenSslError();
}
// Extract Affine coordinates.
bssl::UniquePtr<BIGNUM> x(BN_new());
bssl::UniquePtr<BIGNUM> y(BN_new());
::keymaster::BN_CTX_Ptr ctx(BN_CTX_new());
if (!ctx.get()) {
return ::keymaster::TranslateLastOpenSslError();
}
if (!EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ecKey),
jacobian_coords, x.get(), y.get(),
ctx.get())) {
return ::keymaster::TranslateLastOpenSslError();
}
uint8_t* tmp_x = x_coord.data();
if (BN_bn2binpad(x.get(), tmp_x, kAffinePointLength) != kAffinePointLength) {
return ::keymaster::TranslateLastOpenSslError();
}
uint8_t* tmp_y = y_coord.data();
if (BN_bn2binpad(y.get(), tmp_y, kAffinePointLength) != kAffinePointLength) {
return ::keymaster::TranslateLastOpenSslError();
}
return KM_ERROR_OK;
}
keymaster_error_t GenerateEcdsa256KeyFromSeed(bool test_mode,
absl::Span<uint8_t> seed,
absl::Span<uint8_t> private_key,
std::string& private_key_pem,
absl::Span<uint8_t> x_coord,
absl::Span<uint8_t> y_coord) {
// This function is intended to work only in test mode.
CHECK(test_mode == true);
// Seed Input Validation.
if (seed.size() != kSeedSize) {
LOG(ERROR) << "Invalid seed size found";
return KM_ERROR_INVALID_ARGUMENT;
}
// Size Input Validation.
if (x_coord.size() != kAffinePointLength ||
y_coord.size() != kAffinePointLength ||
private_key.size() != kP256EcdsaPrivateKeyLength) {
return KM_ERROR_INVALID_ARGUMENT;
}
// EC Group Setup.
const EC_GROUP* group = nullptr;
if (!(group = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1))) {
return ::keymaster::TranslateLastOpenSslError();
}
// Key and Context creation.
bssl::UniquePtr<EC_KEY> ec_key(EC_KEY_new());
if (!ec_key.get()) {
return ::keymaster::TranslateLastOpenSslError();
}
bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
if (!ctx.get()) {
return ::keymaster::TranslateLastOpenSslError();
}
// Extract private key from seed.
bssl::UniquePtr<BIGNUM> priv_key(
BN_bin2bn(seed.data(), seed.size(), nullptr));
if (!priv_key.get()) {
return ::keymaster::TranslateLastOpenSslError();
}
// Set Group and Private Key.
if (EC_KEY_set_group(ec_key.get(), group) != 1) {
return ::keymaster::TranslateLastOpenSslError();
}
if (EC_KEY_set_private_key(ec_key.get(), priv_key.get()) != 1) {
return ::keymaster::TranslateLastOpenSslError();
}
// Calculate and Set Public Key.
bssl::UniquePtr<EC_POINT> pub_key_point(
EC_POINT_new(EC_KEY_get0_group(ec_key.get())));
if (!EC_POINT_mul(EC_KEY_get0_group(ec_key.get()), pub_key_point.get(),
priv_key.get(), nullptr, nullptr, nullptr)) {
return ::keymaster::TranslateLastOpenSslError();
}
EC_KEY_set_public_key(ec_key.get(), pub_key_point.get());
// Derive Public Key.
const EC_POINT* jacobian_coords = EC_KEY_get0_public_key(ec_key.get());
if (!jacobian_coords) {
return ::keymaster::TranslateLastOpenSslError();
}
EC_POINT* pub_key_copy = EC_POINT_new(group);
EC_POINT_copy(pub_key_copy, jacobian_coords);
if (EC_POINT_mul(group, pub_key_copy, priv_key.get(), nullptr, nullptr,
ctx.get()) != 1) {
return ::keymaster::TranslateLastOpenSslError();
}
// Extract Affine coordinates.
bssl::UniquePtr<BIGNUM> x(BN_new());
bssl::UniquePtr<BIGNUM> y(BN_new());
if (!ctx.get()) {
return ::keymaster::TranslateLastOpenSslError();
}
if (!EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ec_key.get()),
jacobian_coords, x.get(), y.get(),
ctx.get())) {
return ::keymaster::TranslateLastOpenSslError();
}
uint8_t* tmp_x = x_coord.data();
if (BN_bn2binpad(x.get(), tmp_x, kAffinePointLength) != kAffinePointLength) {
return ::keymaster::TranslateLastOpenSslError();
}
uint8_t* tmp_y = y_coord.data();
if (BN_bn2binpad(y.get(), tmp_y, kAffinePointLength) != kAffinePointLength) {
return ::keymaster::TranslateLastOpenSslError();
}
// Extract Private Key in PEM format.
BN_bn2binpad(priv_key.get(), private_key.data(), private_key.size());
auto error_pem_key = extractEcdsaPEMKey(std::move(ec_key), private_key_pem);
if (error_pem_key != KM_ERROR_OK) {
return error_pem_key;
}
return KM_ERROR_OK;
}
} // namespace arc::keymint::context