The OEMCrypto utils have been copied over from the CDM repo. Tests have been excluded for this CL. Files represent a snapshot taken from http://go/wvgerrit/148270 and http://go/wvgerrit/148372. Bug: 205902021 Change-Id: I1a58952cd1436a48974367c5436bf7296163e6f1
932 lines
29 KiB
C++
932 lines
29 KiB
C++
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
|
// source code may only be used and distributed under the Widevine License
|
|
// Agreement.
|
|
//
|
|
// Reference implementation utilities of OEMCrypto APIs
|
|
//
|
|
#include "oemcrypto_ecc_key.h"
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
#include <mutex>
|
|
|
|
#include <openssl/bn.h>
|
|
#include <openssl/crypto.h>
|
|
#include <openssl/ec.h>
|
|
#include <openssl/ecdsa.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/x509.h>
|
|
|
|
#include "log.h"
|
|
#include "scoped_object.h"
|
|
|
|
namespace wvoec {
|
|
namespace util {
|
|
namespace {
|
|
// Estimated max size (in bytes) of a serialized ECC key (public or
|
|
// private). These values are based on rough calculations for
|
|
// secp521r1 (largest of the supported curves) and should be slightly
|
|
// larger needed.
|
|
constexpr size_t kPrivateKeySize = 250;
|
|
constexpr size_t kPublicKeySize = 164;
|
|
|
|
// 256 bit key, intended to be used with CMAC-AES-256.
|
|
constexpr size_t kEccSessionKeySize = 32;
|
|
|
|
using ScopedBigNum = ScopedObject<BIGNUM, BN_free>;
|
|
using ScopedBigNumCtx = ScopedObject<BN_CTX, BN_CTX_free>;
|
|
using ScopedBio = ScopedObject<BIO, BIO_vfree>;
|
|
using ScopedEcKey = ScopedObject<EC_KEY, EC_KEY_free>;
|
|
using ScopedEvpMdCtx = ScopedObject<EVP_MD_CTX, EVP_MD_CTX_free>;
|
|
using ScopedEvpPkey = ScopedObject<EVP_PKEY, EVP_PKEY_free>;
|
|
using ScopedPrivateKeyInfo =
|
|
ScopedObject<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>;
|
|
using ScopedSigPoint = ScopedObject<ECDSA_SIG, ECDSA_SIG_free>;
|
|
|
|
const EC_GROUP* GetEcGroup(EccCurve curve) {
|
|
// Creating a named EC_GROUP is an expensive operation, and they
|
|
// are always used in a manner which does not transfer ownership.
|
|
// Maintaining a process-wide set of supported EC groups reduces
|
|
// the overhead of group operations.
|
|
static std::mutex group_mutex;
|
|
static EC_GROUP* group_256 = nullptr;
|
|
static EC_GROUP* group_384 = nullptr;
|
|
static EC_GROUP* group_521 = nullptr;
|
|
std::lock_guard<std::mutex> group_lock(group_mutex);
|
|
switch (curve) {
|
|
case kEccSecp256r1: {
|
|
if (group_256 == nullptr) {
|
|
LOGD("Creating secp256r1 group");
|
|
// The curve secp256r1 was originally named prime256v1
|
|
// in the X9.62 specification.
|
|
group_256 = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
|
|
assert(group_256 != nullptr);
|
|
}
|
|
return group_256;
|
|
}
|
|
case kEccSecp384r1: {
|
|
if (group_384 == nullptr) {
|
|
LOGD("Creating secp384r1 group");
|
|
group_384 = EC_GROUP_new_by_curve_name(NID_secp384r1);
|
|
assert(group_384 != nullptr);
|
|
}
|
|
return group_384;
|
|
}
|
|
case kEccSecp521r1: {
|
|
if (group_521 == nullptr) {
|
|
LOGD("Creating secp521r1 group");
|
|
group_521 = EC_GROUP_new_by_curve_name(NID_secp521r1);
|
|
assert(group_521 != nullptr);
|
|
}
|
|
return group_521;
|
|
}
|
|
default:
|
|
LOGE("Cannot get EC group for unknown curve: curve = %d",
|
|
static_cast<int>(curve));
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// Determines which of the supported ECC curves the provided |key|
|
|
// belongs to.
|
|
//
|
|
// This is intended to be used on keys that have been deserialized
|
|
// from an ASN.1 structure which may have contained a key which is
|
|
// supported by OpenSSL/BoringSSL but not necessarily by OEMCrypto.
|
|
//
|
|
// If the key group is unknown to OEMCrypto or if an error occurs,
|
|
// kEccCurveUnknown is returned.
|
|
EccCurve GetCurveFromKeyGroup(const EC_KEY* key) {
|
|
ScopedBigNumCtx ctx(BN_CTX_new());
|
|
if (!ctx) {
|
|
LOGE("Failed to allocate BN ctx");
|
|
return kEccCurveUnknown;
|
|
}
|
|
const EC_GROUP* group = EC_KEY_get0_group(key);
|
|
if (group == nullptr) {
|
|
LOGE("Provided key does not have a group");
|
|
return kEccCurveUnknown;
|
|
}
|
|
int rc = EC_GROUP_cmp(group, GetEcGroup(kEccSecp256r1), ctx.get());
|
|
if (rc == 0) {
|
|
return kEccSecp256r1;
|
|
}
|
|
if (rc == -1) {
|
|
LOGE("Error occurred while checking against secp256r1");
|
|
return kEccCurveUnknown;
|
|
}
|
|
|
|
rc = EC_GROUP_cmp(group, GetEcGroup(kEccSecp384r1), ctx.get());
|
|
if (rc == 0) {
|
|
return kEccSecp384r1;
|
|
}
|
|
if (rc == -1) {
|
|
LOGE("Error occurred while checking against secp384r1");
|
|
return kEccCurveUnknown;
|
|
}
|
|
|
|
rc = EC_GROUP_cmp(group, GetEcGroup(kEccSecp521r1), ctx.get());
|
|
if (rc == 0) {
|
|
return kEccSecp521r1;
|
|
}
|
|
if (rc == -1) {
|
|
LOGE("Error occurred while checking against secp521r1");
|
|
return kEccCurveUnknown;
|
|
}
|
|
|
|
LOGW("Unsupported curve group");
|
|
return kEccCurveUnknown;
|
|
}
|
|
|
|
// Compares the public EC points of both keys to see if they are the
|
|
// equal.
|
|
// Both |public_key| and |private_key| must be of the same group.
|
|
bool IsMatchingKeyPair(const EC_KEY* public_key, const EC_KEY* private_key) {
|
|
ScopedBigNumCtx ctx(BN_CTX_new());
|
|
if (!ctx) {
|
|
LOGE("Failed to allocate BN ctx");
|
|
return false;
|
|
}
|
|
// Returns: 1 if not equal, 0 if equal, -1 if error.
|
|
const int res = EC_POINT_cmp(EC_KEY_get0_group(public_key),
|
|
EC_KEY_get0_public_key(public_key),
|
|
EC_KEY_get0_public_key(private_key), ctx.get());
|
|
if (res == -1) {
|
|
LOGE("Error occurred comparing keys");
|
|
}
|
|
return res == 0;
|
|
}
|
|
|
|
// Performs a SHA2 digest on the provided |message| and outputs the
|
|
// computed hash to |digest|.
|
|
// The digest algorithm used depends on which curve is used.
|
|
// - secp256r1 -> SHA-256
|
|
// - secp384r1 -> SHA-384
|
|
// - secp521r1 -> SHA-512
|
|
// This function assumes that all parameters are valid.
|
|
// Returns true on success, false otherwise.
|
|
bool DigestMessage(EccCurve curve, const uint8_t* message, size_t message_size,
|
|
std::vector<uint8_t>* digest) {
|
|
const EVP_MD* md_engine = nullptr;
|
|
switch (curve) {
|
|
case kEccSecp256r1: {
|
|
md_engine = EVP_sha256();
|
|
break;
|
|
}
|
|
case kEccSecp384r1: {
|
|
md_engine = EVP_sha384();
|
|
break;
|
|
}
|
|
case kEccSecp521r1: {
|
|
md_engine = EVP_sha512();
|
|
break;
|
|
}
|
|
case kEccCurveUnknown:
|
|
// This case is to suppress compiler warnings. It will never
|
|
// occur.
|
|
break;
|
|
}
|
|
if (md_engine == nullptr) {
|
|
LOGE("Failed to get MD engine: curve = %d", static_cast<int>(curve));
|
|
return false;
|
|
}
|
|
|
|
ScopedEvpMdCtx md_ctx(EVP_MD_CTX_new());
|
|
if (!md_ctx) {
|
|
LOGE("Failed to create MD CTX");
|
|
return false;
|
|
}
|
|
if (!EVP_DigestInit_ex(md_ctx.get(), md_engine, nullptr)) {
|
|
LOGE("Failed to init MD CTX");
|
|
return false;
|
|
}
|
|
if (message_size > 0 &&
|
|
!EVP_DigestUpdate(md_ctx.get(), message, message_size)) {
|
|
LOGE("Failed to update");
|
|
return false;
|
|
}
|
|
digest->resize(EVP_MD_CTX_size(md_ctx.get()), 0);
|
|
const int res = EVP_DigestFinal_ex(md_ctx.get(), digest->data(), nullptr);
|
|
if (!res) {
|
|
LOGE("Failed to finalize");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// This KDF function is defined by OEMCrypto ECC specification.
|
|
// Function signature is based on the |kdf| parameter of
|
|
// ECDH_compute_key(). This function assumes that all pointer
|
|
// parameters are not null.
|
|
void* WidevineEccKdf(const void* secret, size_t secret_length, void* key,
|
|
size_t* key_size) {
|
|
if (*key_size < kEccSessionKeySize) {
|
|
LOGE("Output buffer is too small: required = %zu, size = %zu",
|
|
kEccSessionKeySize, *key_size);
|
|
return nullptr;
|
|
}
|
|
std::vector<uint8_t> digest;
|
|
if (!DigestMessage(kEccSecp256r1 /* SHA-256 */,
|
|
reinterpret_cast<const uint8_t*>(secret), secret_length,
|
|
&digest)) {
|
|
LOGE("Cannot derive key: Failed to hash secret");
|
|
return nullptr;
|
|
}
|
|
if (digest.size() != kEccSessionKeySize) {
|
|
LOGE("Unexpected hash size: actual = %zu, expected = %zu", digest.size(),
|
|
kEccSessionKeySize);
|
|
return nullptr;
|
|
}
|
|
*key_size = kEccSessionKeySize;
|
|
memcpy(key, digest.data(), *key_size);
|
|
return key;
|
|
}
|
|
|
|
void OpensslFreeU8(uint8_t* ptr) { OPENSSL_free(ptr); }
|
|
|
|
// Internal ECC public key serialization.
|
|
OEMCryptoResult SerializeEccPublicKey(const EC_KEY* key, uint8_t* buffer,
|
|
size_t* buffer_size) {
|
|
if (buffer_size == nullptr) {
|
|
LOGE("Output buffer size is null");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (buffer == nullptr && *buffer_size > 0) {
|
|
LOGE("Output buffer is null");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
uint8_t* der_key_raw = nullptr;
|
|
const int der_res = i2d_EC_PUBKEY(
|
|
const_cast<EC_KEY*>(key) /* Does not get modified */, &der_key_raw);
|
|
if (der_res < 0) {
|
|
LOGE("Public key serialization failed");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
ScopedObject<uint8_t, OpensslFreeU8> der_key(der_key_raw);
|
|
der_key_raw = nullptr;
|
|
if (!der_key) {
|
|
LOGE("Encoded key is unexpectedly null");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (der_res == 0) {
|
|
LOGE("Unexpected DER encoded size");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
const size_t required_size = static_cast<size_t>(der_res);
|
|
if (buffer == nullptr || *buffer_size < required_size) {
|
|
*buffer_size = required_size;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
memcpy(buffer, der_key.get(), required_size);
|
|
*buffer_size = required_size;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
std::vector<uint8_t> SerializeEccPublicKey(const EC_KEY* key) {
|
|
size_t key_size = kPublicKeySize;
|
|
std::vector<uint8_t> key_data(key_size, 0);
|
|
const OEMCryptoResult res =
|
|
SerializeEccPublicKey(key, key_data.data(), &key_size);
|
|
if (res != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to serialize public key: result = %d", static_cast<int>(res));
|
|
key_data.clear();
|
|
} else {
|
|
key_data.resize(key_size);
|
|
}
|
|
return key_data;
|
|
}
|
|
|
|
bool ParseEccPrivateKeyInfo(const uint8_t* buffer, size_t length,
|
|
ScopedEcKey* key, EccCurve* curve) {
|
|
if (length == 0) {
|
|
LOGE("Public key is too small: length = %zu", length);
|
|
return false;
|
|
}
|
|
ScopedBio bio(BIO_new_mem_buf(buffer, static_cast<int>(length)));
|
|
if (!bio) {
|
|
LOGE("Failed to allocate BIO buffer");
|
|
return false;
|
|
}
|
|
// Step 1: Deserializes PKCS8 PrivateKeyInfo containing an ECC key.
|
|
ScopedPrivateKeyInfo priv_info(
|
|
d2i_PKCS8_PRIV_KEY_INFO_bio(bio.get(), nullptr));
|
|
if (!priv_info) {
|
|
LOGE("Failed to parse private key");
|
|
return false;
|
|
}
|
|
// Step 2: Convert to EC_KEY.
|
|
ScopedEvpPkey pkey(EVP_PKCS82PKEY(priv_info.get()));
|
|
if (!pkey) {
|
|
LOGE("Failed to convert PKCS8 to EVP");
|
|
return false;
|
|
}
|
|
const int key_type = EVP_PKEY_base_id(pkey.get());
|
|
if (key_type != EVP_PKEY_EC) {
|
|
LOGE("Decoded private key is not ECC");
|
|
return false;
|
|
}
|
|
key->reset(EVP_PKEY_get1_EC_KEY(pkey.get()));
|
|
if (!*key) {
|
|
LOGE("Failed to get ECC key");
|
|
return false;
|
|
}
|
|
// Step 3: Verify key parameters and curve family.
|
|
const int check = EC_KEY_check_key(key->get());
|
|
if (check == 0) {
|
|
LOGE("ECC key parameters are invalid");
|
|
return false;
|
|
} else if (check == -1) {
|
|
LOGE("Failed to check ECC key");
|
|
return false;
|
|
}
|
|
*curve = GetCurveFromKeyGroup(key->get());
|
|
if (*curve == kEccCurveUnknown) {
|
|
LOGE("Failed to determine key group");
|
|
return false;
|
|
}
|
|
// Required flags for IETF compliance.
|
|
EC_KEY_set_asn1_flag(key->get(), OPENSSL_EC_NAMED_CURVE);
|
|
EC_KEY_set_conv_form(key->get(), POINT_CONVERSION_UNCOMPRESSED);
|
|
return true;
|
|
}
|
|
} // namespace
|
|
|
|
std::string EccCurveToString(EccCurve curve) {
|
|
switch (curve) {
|
|
case kEccSecp256r1:
|
|
return "secp256r1";
|
|
case kEccSecp384r1:
|
|
return "secp384r1";
|
|
case kEccSecp521r1:
|
|
return "secp521r1";
|
|
case kEccCurveUnknown:
|
|
return "Unknown";
|
|
}
|
|
return "Unknown(" + std::to_string(static_cast<int>(curve)) + ")";
|
|
}
|
|
|
|
// static
|
|
std::unique_ptr<EccPublicKey> EccPublicKey::New(
|
|
const EccPrivateKey& private_key) {
|
|
std::unique_ptr<EccPublicKey> key(new EccPublicKey());
|
|
if (!key->InitFromPrivateKey(private_key)) {
|
|
LOGE("Failed to initialize public key from private key");
|
|
key.reset();
|
|
}
|
|
return key;
|
|
}
|
|
|
|
// static
|
|
std::unique_ptr<EccPublicKey> EccPublicKey::Load(const uint8_t* buffer,
|
|
size_t length) {
|
|
if (buffer == nullptr) {
|
|
LOGE("Provided public key buffer is null");
|
|
return nullptr;
|
|
}
|
|
if (length == 0) {
|
|
LOGE("Provided public key buffer is zero length");
|
|
return nullptr;
|
|
}
|
|
std::unique_ptr<EccPublicKey> key(new EccPublicKey());
|
|
if (!key->InitFromSubjectPublicKeyInfo(buffer, length)) {
|
|
LOGE("Failed to initialize public key from SubjectPublicKeyInfo");
|
|
key.reset();
|
|
}
|
|
return key;
|
|
}
|
|
|
|
// static
|
|
std::unique_ptr<EccPublicKey> EccPublicKey::Load(const std::string& buffer) {
|
|
if (buffer.empty()) {
|
|
LOGE("Provided public key buffer is empty");
|
|
return std::unique_ptr<EccPublicKey>();
|
|
}
|
|
return Load(reinterpret_cast<const uint8_t*>(buffer.data()), buffer.size());
|
|
}
|
|
|
|
// static
|
|
std::unique_ptr<EccPublicKey> EccPublicKey::Load(
|
|
const std::vector<uint8_t>& buffer) {
|
|
if (buffer.empty()) {
|
|
LOGE("Provided public key buffer is empty");
|
|
return std::unique_ptr<EccPublicKey>();
|
|
}
|
|
return Load(buffer.data(), buffer.size());
|
|
}
|
|
|
|
// static
|
|
std::unique_ptr<EccPublicKey> EccPublicKey::LoadPrivateKeyInfo(
|
|
const uint8_t* buffer, size_t length) {
|
|
if (buffer == nullptr) {
|
|
LOGE("Provided public key buffer is null");
|
|
return nullptr;
|
|
}
|
|
if (length == 0) {
|
|
LOGE("Provided public key buffer is zero length");
|
|
return nullptr;
|
|
}
|
|
std::unique_ptr<EccPublicKey> key(new EccPublicKey());
|
|
if (!key->InitFromPrivateKeyInfo(buffer, length)) {
|
|
LOGE("Failed to initialize public key from PrivateKeyInfo");
|
|
key.reset();
|
|
}
|
|
return key;
|
|
}
|
|
|
|
// static
|
|
std::unique_ptr<EccPublicKey> EccPublicKey::LoadPrivateKeyInfo(
|
|
const std::string& buffer) {
|
|
if (buffer.empty()) {
|
|
LOGE("Provided public key buffer is empty");
|
|
return std::unique_ptr<EccPublicKey>();
|
|
}
|
|
return LoadPrivateKeyInfo(reinterpret_cast<const uint8_t*>(buffer.data()),
|
|
buffer.size());
|
|
}
|
|
|
|
// static
|
|
std::unique_ptr<EccPublicKey> EccPublicKey::LoadPrivateKeyInfo(
|
|
const std::vector<uint8_t>& buffer) {
|
|
if (buffer.empty()) {
|
|
LOGE("Provided public key buffer is empty");
|
|
return std::unique_ptr<EccPublicKey>();
|
|
}
|
|
return LoadPrivateKeyInfo(buffer.data(), buffer.size());
|
|
}
|
|
|
|
bool EccPublicKey::IsMatchingPrivateKey(
|
|
const EccPrivateKey& private_key) const {
|
|
if (private_key.curve() != curve_) {
|
|
return false;
|
|
}
|
|
return IsMatchingKeyPair(GetEcKey(), private_key.GetEcKey());
|
|
}
|
|
|
|
OEMCryptoResult EccPublicKey::Serialize(uint8_t* buffer,
|
|
size_t* buffer_size) const {
|
|
return SerializeEccPublicKey(key_, buffer, buffer_size);
|
|
}
|
|
|
|
std::vector<uint8_t> EccPublicKey::Serialize() const {
|
|
return SerializeEccPublicKey(key_);
|
|
}
|
|
|
|
OEMCryptoResult EccPublicKey::VerifySignature(const uint8_t* message,
|
|
size_t message_length,
|
|
const uint8_t* signature,
|
|
size_t signature_length) const {
|
|
if (signature == nullptr || signature_length == 0) {
|
|
LOGE("Signature is missing");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (message == nullptr && message_length > 0) {
|
|
LOGE("Bad message data");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
// Step 1: Parse signature.
|
|
const uint8_t* tp = signature;
|
|
ScopedSigPoint sig_point(d2i_ECDSA_SIG(nullptr, &tp, signature_length));
|
|
if (!sig_point) {
|
|
LOGE("Failed to parse signature");
|
|
// Most likely an invalid signature than an OpenSSL error.
|
|
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
|
}
|
|
// Step 2: Hash message
|
|
std::vector<uint8_t> digest;
|
|
if (!DigestMessage(curve_, message, message_length, &digest)) {
|
|
LOGE("Failed to digest message");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
// Step 3: Verify signature
|
|
const int res = ECDSA_do_verify(
|
|
digest.data(), static_cast<int>(digest.size()), sig_point.get(), key_);
|
|
if (res == -1) {
|
|
LOGE("Error occurred checking signature");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (res == 0) {
|
|
LOGD("Signature did not match");
|
|
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult EccPublicKey::VerifySignature(
|
|
const std::string& message, const std::string& signature) const {
|
|
if (signature.empty()) {
|
|
LOGE("Signature should not be empty");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
return VerifySignature(
|
|
reinterpret_cast<const uint8_t*>(message.data()), message.size(),
|
|
reinterpret_cast<const uint8_t*>(signature.data()), signature.size());
|
|
}
|
|
|
|
OEMCryptoResult EccPublicKey::VerifySignature(
|
|
const std::vector<uint8_t>& message,
|
|
const std::vector<uint8_t>& signature) const {
|
|
if (signature.empty()) {
|
|
LOGE("Signature should not be empty");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
return VerifySignature(message.data(), message.size(), signature.data(),
|
|
signature.size());
|
|
}
|
|
|
|
EccPublicKey::~EccPublicKey() {
|
|
if (key_ != nullptr) {
|
|
EC_KEY_free(key_);
|
|
key_ = nullptr;
|
|
}
|
|
curve_ = kEccCurveUnknown;
|
|
}
|
|
|
|
bool EccPublicKey::InitFromSubjectPublicKeyInfo(const uint8_t* buffer,
|
|
size_t length) {
|
|
// Deserialize SubjectPublicKeyInfo
|
|
const uint8_t* tp = buffer;
|
|
ScopedEcKey key(d2i_EC_PUBKEY(nullptr, &tp, length));
|
|
if (!key) {
|
|
LOGE("Failed to parse ECC key");
|
|
return false;
|
|
}
|
|
// Verify key parameters and curve family.
|
|
const int check = EC_KEY_check_key(key.get());
|
|
if (check == 0) {
|
|
LOGE("ECC key parameters are invalid");
|
|
return false;
|
|
} else if (check == -1) {
|
|
LOGE("Failed to check ECC key");
|
|
return false;
|
|
}
|
|
curve_ = GetCurveFromKeyGroup(key.get());
|
|
if (curve_ == kEccCurveUnknown) {
|
|
LOGE("Failed to determine key group");
|
|
return false;
|
|
}
|
|
// Required flags for IETF compliance.
|
|
EC_KEY_set_asn1_flag(key.get(), OPENSSL_EC_NAMED_CURVE);
|
|
EC_KEY_set_conv_form(key.get(), POINT_CONVERSION_UNCOMPRESSED);
|
|
key_ = key.release();
|
|
return true;
|
|
}
|
|
|
|
bool EccPublicKey::InitFromPrivateKeyInfo(const uint8_t* buffer,
|
|
size_t length) {
|
|
ScopedEcKey private_key;
|
|
if (!ParseEccPrivateKeyInfo(buffer, length, &private_key, &curve_)) {
|
|
return false;
|
|
}
|
|
// TODO(sigquit): Strip private information.
|
|
key_ = private_key.release();
|
|
return true;
|
|
}
|
|
|
|
bool EccPublicKey::InitFromPrivateKey(const EccPrivateKey& private_key) {
|
|
ScopedEcKey key(EC_KEY_new());
|
|
if (!key) {
|
|
LOGE("Failed to allocate key");
|
|
return false;
|
|
}
|
|
if (!EC_KEY_set_group(key.get(), EC_KEY_get0_group(private_key.GetEcKey()))) {
|
|
LOGE("Failed to set group");
|
|
return false;
|
|
}
|
|
if (!EC_KEY_set_public_key(key.get(),
|
|
EC_KEY_get0_public_key(private_key.GetEcKey()))) {
|
|
LOGE("Failed to set public point");
|
|
return false;
|
|
}
|
|
curve_ = private_key.curve();
|
|
// Required flags for IETF compliance.
|
|
EC_KEY_set_asn1_flag(key.get(), OPENSSL_EC_NAMED_CURVE);
|
|
EC_KEY_set_conv_form(key.get(), POINT_CONVERSION_UNCOMPRESSED);
|
|
key_ = key.release();
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
std::unique_ptr<EccPrivateKey> EccPrivateKey::New(EccCurve curve) {
|
|
std::unique_ptr<EccPrivateKey> key(new EccPrivateKey());
|
|
if (!key->InitFromCurve(curve)) {
|
|
LOGE("Failed to initialize private key from curve");
|
|
key.reset();
|
|
}
|
|
return key;
|
|
}
|
|
|
|
// static
|
|
std::unique_ptr<EccPrivateKey> EccPrivateKey::Load(const uint8_t* buffer,
|
|
size_t length) {
|
|
if (buffer == nullptr) {
|
|
LOGE("Provided private key buffer is null");
|
|
return nullptr;
|
|
}
|
|
if (length == 0) {
|
|
LOGE("Provided private key buffer is zero length");
|
|
return nullptr;
|
|
}
|
|
std::unique_ptr<EccPrivateKey> key(new EccPrivateKey());
|
|
if (!key->InitFromPrivateKeyInfo(buffer, length)) {
|
|
LOGE("Failed to initialize private key from PrivateKeyInfo");
|
|
key.reset();
|
|
}
|
|
return key;
|
|
}
|
|
|
|
// static
|
|
std::unique_ptr<EccPrivateKey> EccPrivateKey::Load(const std::string& buffer) {
|
|
if (buffer.empty()) {
|
|
LOGE("Provided private key buffer is empty");
|
|
return std::unique_ptr<EccPrivateKey>();
|
|
}
|
|
return Load(reinterpret_cast<const uint8_t*>(buffer.data()), buffer.size());
|
|
}
|
|
|
|
// static
|
|
std::unique_ptr<EccPrivateKey> EccPrivateKey::Load(
|
|
const std::vector<uint8_t>& buffer) {
|
|
if (buffer.empty()) {
|
|
LOGE("Provided private key buffer is empty");
|
|
return std::unique_ptr<EccPrivateKey>();
|
|
}
|
|
return Load(buffer.data(), buffer.size());
|
|
}
|
|
|
|
std::unique_ptr<EccPublicKey> EccPrivateKey::MakePublicKey() const {
|
|
return EccPublicKey::New(*this);
|
|
}
|
|
|
|
bool EccPrivateKey::IsMatchingPublicKey(const EccPublicKey& public_key) const {
|
|
if (public_key.curve() != curve_) {
|
|
return false;
|
|
}
|
|
return IsMatchingKeyPair(public_key.GetEcKey(), GetEcKey());
|
|
}
|
|
|
|
OEMCryptoResult EccPrivateKey::Serialize(uint8_t* buffer,
|
|
size_t* buffer_size) const {
|
|
if (buffer_size == nullptr) {
|
|
LOGE("Output buffer size is null");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (buffer == nullptr && *buffer_size > 0) {
|
|
LOGE("Output buffer is null");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
// Step 1: Convert EC_KEY key to EVP.
|
|
ScopedEvpPkey pkey(EVP_PKEY_new());
|
|
if (!pkey) {
|
|
LOGE("Failed to allocate EVP");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (!EVP_PKEY_set1_EC_KEY(pkey.get(), key_)) {
|
|
LOGE("Failed to set EVP ECC key");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
// Step 2: Convert ECC EVP to PKCS8 format.
|
|
ScopedPrivateKeyInfo priv_info(EVP_PKEY2PKCS8(pkey.get()));
|
|
if (!priv_info) {
|
|
LOGE("Failed to convert ECC key to PKCS8 info");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
// Step 3: Serialize PKCS8 to DER encoding.
|
|
ScopedBio bio(BIO_new(BIO_s_mem()));
|
|
if (!bio) {
|
|
LOGE("Failed to allocate IO buffer for ECC key");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (!i2d_PKCS8_PRIV_KEY_INFO_bio(bio.get(), priv_info.get())) {
|
|
LOGE("Failed to serialize ECC key");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
// Step 4: Determine key size and copy.
|
|
char* key_ptr = nullptr;
|
|
const long key_size = BIO_get_mem_data(bio.get(), &key_ptr);
|
|
if (key_size < 0) {
|
|
LOGE("Failed to get ECC key size");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (key_ptr == nullptr) {
|
|
LOGE("Encoded key is unexpectedly null");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
const size_t required_size = static_cast<size_t>(key_size);
|
|
if (*buffer_size < required_size) {
|
|
*buffer_size = required_size;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
*buffer_size = required_size;
|
|
memcpy(buffer, key_ptr, required_size);
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
std::vector<uint8_t> EccPrivateKey::Serialize() const {
|
|
size_t key_size = kPrivateKeySize;
|
|
std::vector<uint8_t> key_data(key_size, 0);
|
|
const OEMCryptoResult res = Serialize(key_data.data(), &key_size);
|
|
if (res != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to serialize private key: result = %d", static_cast<int>(res));
|
|
key_data.clear();
|
|
} else {
|
|
key_data.resize(key_size);
|
|
}
|
|
return key_data;
|
|
}
|
|
|
|
OEMCryptoResult EccPrivateKey::SerializeAsPublicKey(uint8_t* buffer,
|
|
size_t* buffer_size) const {
|
|
return SerializeEccPublicKey(key_, buffer, buffer_size);
|
|
}
|
|
|
|
std::vector<uint8_t> EccPrivateKey::SerializeAsPublicKey() const {
|
|
return SerializeEccPublicKey(key_);
|
|
}
|
|
|
|
OEMCryptoResult EccPrivateKey::GenerateSignature(
|
|
const uint8_t* message, size_t message_length, uint8_t* signature,
|
|
size_t* signature_length) const {
|
|
if (signature_length == nullptr) {
|
|
LOGE("Output signature size is null");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (signature == nullptr && *signature_length > 0) {
|
|
LOGE("Output signature is null");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (message == nullptr && message_length > 0) {
|
|
LOGE("Invalid message data");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
const size_t expected_signature_length = ECDSA_size(key_);
|
|
if (*signature_length < expected_signature_length) {
|
|
*signature_length = expected_signature_length;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
|
|
// Step 1: Hash message.
|
|
std::vector<uint8_t> digest;
|
|
if (!DigestMessage(curve_, message, message_length, &digest)) {
|
|
LOGE("Failed to digest message");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
// Step 2: Generate signature point.
|
|
ScopedSigPoint sig_point(
|
|
ECDSA_do_sign(digest.data(), static_cast<int>(digest.size()), key_));
|
|
if (!sig_point) {
|
|
LOGE("Failed to perform ECDSA");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
// Step 3: Serialize
|
|
std::vector<uint8_t> temp(expected_signature_length);
|
|
uint8_t* sig_ptr = temp.data();
|
|
const int res = i2d_ECDSA_SIG(sig_point.get(), &sig_ptr);
|
|
if (res <= 0) {
|
|
LOGE("Failed to serialize signature");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
const size_t required_size = static_cast<size_t>(res);
|
|
if (signature == nullptr || *signature_length < required_size) {
|
|
*signature_length = required_size;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
memcpy(signature, temp.data(), required_size);
|
|
*signature_length = required_size;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
std::vector<uint8_t> EccPrivateKey::GenerateSignature(
|
|
const std::string& message) const {
|
|
size_t signature_size = SignatureSize();
|
|
std::vector<uint8_t> signature(signature_size, 0);
|
|
const OEMCryptoResult res =
|
|
GenerateSignature(reinterpret_cast<const uint8_t*>(message.data()),
|
|
message.size(), signature.data(), &signature_size);
|
|
if (res != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to generate signature: result = %d", static_cast<int>(res));
|
|
signature.clear();
|
|
} else {
|
|
signature.resize(signature_size);
|
|
}
|
|
return signature;
|
|
}
|
|
|
|
std::vector<uint8_t> EccPrivateKey::GenerateSignature(
|
|
const std::vector<uint8_t>& message) const {
|
|
size_t signature_size = SignatureSize();
|
|
std::vector<uint8_t> signature(signature_size, 0);
|
|
const OEMCryptoResult res = GenerateSignature(
|
|
message.data(), message.size(), signature.data(), &signature_size);
|
|
if (res != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to generate signature: result = %d", static_cast<int>(res));
|
|
signature.clear();
|
|
} else {
|
|
signature.resize(signature_size);
|
|
}
|
|
return signature;
|
|
}
|
|
|
|
size_t EccPrivateKey::SignatureSize() const { return ECDSA_size(key_); }
|
|
|
|
OEMCryptoResult EccPrivateKey::DeriveSessionKey(
|
|
const EccPublicKey& public_key, uint8_t* session_key,
|
|
size_t* session_key_size) const {
|
|
if (public_key.curve() != curve_) {
|
|
LOGE("Incompatible ECC keys: public = %s, private = %s",
|
|
EccCurveToString(public_key.curve()).c_str(),
|
|
EccCurveToString(curve_).c_str());
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (session_key_size == nullptr) {
|
|
LOGE("Output session key size buffer is null");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (session_key == nullptr && *session_key_size > 0) {
|
|
LOGE("Output session key buffer is null");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (*session_key_size < kEccSessionKeySize) {
|
|
*session_key_size = kEccSessionKeySize;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
const int res = ECDH_compute_key(
|
|
session_key, kEccSessionKeySize,
|
|
EC_KEY_get0_public_key(public_key.GetEcKey()), key_, WidevineEccKdf);
|
|
if (res < 0) {
|
|
LOGE("ECDH error occurred");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (static_cast<size_t>(res) != kEccSessionKeySize) {
|
|
LOGE("Unexpected key size: size = %d", res);
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
*session_key_size = kEccSessionKeySize;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
std::vector<uint8_t> EccPrivateKey::DeriveSessionKey(
|
|
const EccPublicKey& public_key) const {
|
|
size_t session_key_size = kEccSessionKeySize;
|
|
std::vector<uint8_t> session_key(session_key_size, 0);
|
|
const OEMCryptoResult res =
|
|
DeriveSessionKey(public_key, session_key.data(), &session_key_size);
|
|
if (res != OEMCrypto_SUCCESS) {
|
|
LOGE("Failed to derive session key: result = %d", static_cast<int>(res));
|
|
session_key.clear();
|
|
} else {
|
|
session_key.resize(session_key_size);
|
|
}
|
|
return session_key;
|
|
}
|
|
|
|
size_t EccPrivateKey::SessionKeyLength() const { return kEccSessionKeySize; }
|
|
|
|
EccPrivateKey::~EccPrivateKey() {
|
|
if (key_ != nullptr) {
|
|
EC_KEY_free(key_);
|
|
key_ = nullptr;
|
|
}
|
|
curve_ = kEccCurveUnknown;
|
|
}
|
|
|
|
bool EccPrivateKey::InitFromPrivateKeyInfo(const uint8_t* buffer,
|
|
size_t length) {
|
|
ScopedEcKey key;
|
|
if (!ParseEccPrivateKeyInfo(buffer, length, &key, &curve_)) return false;
|
|
key_ = key.release();
|
|
return true;
|
|
}
|
|
|
|
bool EccPrivateKey::InitFromCurve(EccCurve curve) {
|
|
const EC_GROUP* group = GetEcGroup(curve);
|
|
if (group == nullptr) {
|
|
LOGE("Failed to get ECC group");
|
|
return false;
|
|
}
|
|
ScopedEcKey key(EC_KEY_new());
|
|
if (!key) {
|
|
LOGE("Failed to allocate key");
|
|
return false;
|
|
}
|
|
if (!EC_KEY_set_group(key.get(), group)) {
|
|
LOGE("Failed to set group");
|
|
return false;
|
|
}
|
|
// Generate random key.
|
|
if (!EC_KEY_generate_key(key.get())) {
|
|
LOGE("Failed to generate random key");
|
|
return false;
|
|
}
|
|
curve_ = curve;
|
|
// Required flags for IETF compliance.
|
|
EC_KEY_set_asn1_flag(key.get(), OPENSSL_EC_NAMED_CURVE);
|
|
EC_KEY_set_conv_form(key.get(), POINT_CONVERSION_UNCOMPRESSED);
|
|
key_ = key.release();
|
|
return true;
|
|
}
|
|
} // namespace util
|
|
} // namespace wvoec
|