Widevine ECM v3 is redesigned mainly based on protobuf, and supports new features including carrying fingerprinting and service blocking information. Existing clients must upgrade the Widevine CAS plugin to use the new ECM v3.
364 lines
12 KiB
C++
364 lines
12 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
// Copyright 2019 Google LLC.
|
|
//
|
|
// This software is licensed under the terms defined in the Widevine Master
|
|
// License Agreement. For a copy of this agreement, please contact
|
|
// widevine-licensing@google.com.
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Description:
|
|
// Definition of elliptic curve public and private key classes.
|
|
|
|
#include "common/ec_key.h"
|
|
|
|
#include <vector>
|
|
|
|
#include "glog/logging.h"
|
|
#include "absl/memory/memory.h"
|
|
#include "openssl/base.h"
|
|
#include "openssl/bn.h"
|
|
#include "openssl/ec.h"
|
|
#include "openssl/ecdh.h"
|
|
#include "openssl/ecdsa.h"
|
|
#include "openssl/err.h"
|
|
#include "openssl/evp.h"
|
|
#include "openssl/sha.h"
|
|
#include "common/aes_cbc_util.h"
|
|
#include "common/ec_util.h"
|
|
#include "common/hash_algorithm.h"
|
|
#include "common/openssl_util.h"
|
|
#include "common/sha_util.h"
|
|
|
|
namespace widevine {
|
|
|
|
namespace {
|
|
|
|
void* SHA256KDF(const void* in, size_t inlen, void* out, size_t* outlen) {
|
|
std::string shared_session_key(static_cast<const char*>(in), inlen);
|
|
std::string derived_shared_session_key = Sha256_Hash(shared_session_key);
|
|
if (*outlen != SHA256_DIGEST_LENGTH) {
|
|
return nullptr;
|
|
}
|
|
memcpy(out, derived_shared_session_key.data(), *outlen);
|
|
return out;
|
|
}
|
|
|
|
ECPrivateKey::EllipticCurve ECKeyToCurve(const EC_KEY* key) {
|
|
if (key == nullptr) {
|
|
return ECPrivateKey::UNDEFINED_CURVE;
|
|
}
|
|
return ec_util::NidToCurve(EC_GROUP_get_curve_name(EC_KEY_get0_group(key)));
|
|
}
|
|
|
|
std::string OpenSSLErrorString(uint32_t error) {
|
|
char buf[ERR_ERROR_STRING_BUF_LEN];
|
|
ERR_error_string_n(error, buf, sizeof(buf));
|
|
return buf;
|
|
}
|
|
|
|
std::string GetMessageDigest(const std::string& message,
|
|
widevine::HashAlgorithm hash_algorithm) {
|
|
switch (hash_algorithm) {
|
|
case widevine::HashAlgorithm::kUnspecified:
|
|
case widevine::HashAlgorithm::kSha256:
|
|
return widevine::Sha256_Hash(message);
|
|
case widevine::HashAlgorithm::kSha1:
|
|
LOG(ERROR) << "Unexpected hash algorithm: "
|
|
<< static_cast<int>(hash_algorithm);
|
|
return "";
|
|
}
|
|
LOG(FATAL) << "Unexpected hash algorithm: "
|
|
<< static_cast<int>(hash_algorithm);
|
|
return "";
|
|
}
|
|
|
|
} // namespace
|
|
|
|
ECPrivateKey::ECPrivateKey(EC_KEY* ec_key) : key_(ec_key) {
|
|
CHECK(key() != nullptr);
|
|
CHECK(EC_KEY_get0_private_key(key()) != nullptr);
|
|
CHECK_NE(ECKeyToCurve(key()), ECPrivateKey::UNDEFINED_CURVE);
|
|
CHECK_EQ(EC_KEY_check_key(key()), 1);
|
|
}
|
|
|
|
ECPrivateKey::ECPrivateKey(ScopedECKEY ec_key) : key_(std::move(ec_key)) {}
|
|
|
|
ECPrivateKey::ECPrivateKey(const ECPrivateKey& ec_key)
|
|
: ECPrivateKey(EC_KEY_dup(ec_key.key())) {}
|
|
|
|
std::unique_ptr<ECPrivateKey> ECPrivateKey::Create(
|
|
const std::string& serialized_key) {
|
|
EC_KEY* key;
|
|
if (!ec_util::DeserializeECPrivateKey(serialized_key, &key)) {
|
|
return nullptr;
|
|
}
|
|
ScopedECKEY scoped_ec_key(key);
|
|
if (EC_KEY_check_key(scoped_ec_key.get()) != 1) {
|
|
LOG(ERROR) << "Invalid private EC key: "
|
|
<< OpenSSLErrorString(ERR_get_error());
|
|
return nullptr;
|
|
}
|
|
if (ECKeyToCurve(scoped_ec_key.get()) == ECPrivateKey::UNDEFINED_CURVE) {
|
|
LOG(ERROR) << "Key has unsupported curve";
|
|
return nullptr;
|
|
}
|
|
return absl::make_unique<ECPrivateKey>(scoped_ec_key.release());
|
|
}
|
|
|
|
std::unique_ptr<ECPublicKey> ECPrivateKey::PublicKey() const {
|
|
if (key_ == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return absl::make_unique<ECPublicKey>(ScopedECKEY(EC_KEY_dup(key_.get())));
|
|
}
|
|
|
|
bool ECPrivateKey::DeriveSharedSessionKey(
|
|
const ECPublicKey& public_key,
|
|
std::string* derived_shared_session_key) const {
|
|
if (derived_shared_session_key == nullptr) {
|
|
LOG(ERROR) << "|derived_shared_session_key| cannot be nullptr";
|
|
return false;
|
|
}
|
|
const EC_GROUP* group1 = EC_KEY_get0_group(key());
|
|
const EC_GROUP* group2 = EC_KEY_get0_group(public_key.key());
|
|
if (EC_GROUP_cmp(group1, group2, nullptr) != 0) {
|
|
LOG(ERROR) << "EC_GROUPs do not match";
|
|
return false;
|
|
}
|
|
const EC_POINT* public_key_point = EC_KEY_get0_public_key(public_key.key());
|
|
derived_shared_session_key->resize(SHA256_DIGEST_LENGTH, 0);
|
|
int result = ECDH_compute_key(
|
|
reinterpret_cast<void*>(
|
|
const_cast<char*>(derived_shared_session_key->data())),
|
|
derived_shared_session_key->size(), public_key_point, key(), SHA256KDF);
|
|
if (result == -1) {
|
|
LOG(ERROR) << "Could not write shared session key: "
|
|
<< OpenSSLErrorString(ERR_get_error());
|
|
return false;
|
|
}
|
|
if (result != SHA256_DIGEST_LENGTH) {
|
|
LOG(ERROR) << "Wrote " << result
|
|
<< " bytes to derived shared session key instead of "
|
|
<< SHA256_DIGEST_LENGTH << " bytes";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ECPrivateKey::GenerateSignature(const std::string& message,
|
|
HashAlgorithm hash_algorithm,
|
|
std::string* signature) const {
|
|
if (message.empty()) {
|
|
LOG(ERROR) << "|message| cannot be empty";
|
|
return false;
|
|
}
|
|
if (signature == nullptr) {
|
|
LOG(ERROR) << "|signature| cannot be nullptr";
|
|
return false;
|
|
}
|
|
|
|
// Hash the message using corresponding hash algorithm.
|
|
std::string message_digest = GetMessageDigest(message, hash_algorithm);
|
|
if (message_digest.empty()) {
|
|
LOG(ERROR) << "Empty message digest";
|
|
return false;
|
|
}
|
|
|
|
size_t max_signature_size = ECDSA_size(key());
|
|
if (max_signature_size == 0) {
|
|
LOG(ERROR) << "key_ does not have a group set";
|
|
return false;
|
|
}
|
|
signature->resize(max_signature_size);
|
|
unsigned int bytes_written = 0;
|
|
int result = ECDSA_sign(
|
|
0 /* unused type */,
|
|
reinterpret_cast<const uint8_t*>(message_digest.data()),
|
|
message_digest.size(),
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
|
&bytes_written, key());
|
|
if (result != 1) {
|
|
LOG(ERROR) << "Could not calculate signature: "
|
|
<< OpenSSLErrorString(ERR_get_error());
|
|
return false;
|
|
}
|
|
signature->resize(bytes_written);
|
|
return true;
|
|
}
|
|
|
|
bool ECPrivateKey::MatchesPrivateKey(const ECPrivateKey& private_key) const {
|
|
return BN_cmp(EC_KEY_get0_private_key(key()),
|
|
EC_KEY_get0_private_key(private_key.key())) == 0;
|
|
}
|
|
|
|
bool ECPrivateKey::MatchesPublicKey(const ECPublicKey& public_key) const {
|
|
return EC_POINT_cmp(EC_KEY_get0_group(key()), EC_KEY_get0_public_key(key()),
|
|
EC_KEY_get0_public_key(public_key.key()), nullptr) == 0;
|
|
}
|
|
|
|
ECPrivateKey::EllipticCurve ECPrivateKey::Curve() const {
|
|
return ECKeyToCurve(key());
|
|
}
|
|
|
|
bool ECPrivateKey::SerializedKey(std::string* serialized_key) const {
|
|
CHECK(serialized_key);
|
|
return ec_util::SerializeECPrivateKey(key_.get(), serialized_key);
|
|
}
|
|
|
|
bool ECPrivateKey::GetRawPrivateKey(std::string* raw_private_key) const {
|
|
if (raw_private_key == nullptr) {
|
|
LOG(WARNING) << "|raw_private_key| cannot be nullptr.";
|
|
return false;
|
|
}
|
|
const EC_GROUP* group = EC_KEY_get0_group(key());
|
|
if (group == nullptr) {
|
|
LOG(WARNING) << "Failed to load EC key group.";
|
|
return false;
|
|
}
|
|
int key_size_bits = EC_GROUP_get_degree(group);
|
|
if (key_size_bits == 0) {
|
|
LOG(WARNING) << "Key size is 0.";
|
|
return false;
|
|
}
|
|
const BIGNUM* key_bn = EC_KEY_get0_private_key(key());
|
|
int key_size_bytes = (key_size_bits + 7) / 8;
|
|
std::vector<uint8_t> tmp_raw_private_key_data(key_size_bytes);
|
|
int num_bytes = BN_bn2bin(key_bn, tmp_raw_private_key_data.data());
|
|
if (num_bytes != key_size_bytes) {
|
|
LOG(WARNING) << "Failed to convert EC private key BIGNUM to binary data.";
|
|
return false;
|
|
}
|
|
raw_private_key->assign(tmp_raw_private_key_data.begin(),
|
|
tmp_raw_private_key_data.end());
|
|
return true;
|
|
}
|
|
|
|
ECPublicKey::ECPublicKey(EC_KEY* ec_key) : key_(ec_key) {
|
|
CHECK(key() != nullptr);
|
|
CHECK(EC_KEY_get0_private_key(key()) == nullptr);
|
|
CHECK_NE(ECKeyToCurve(key()), ECPrivateKey::UNDEFINED_CURVE);
|
|
CHECK_EQ(EC_KEY_check_key(key()), 1);
|
|
}
|
|
|
|
ECPublicKey::ECPublicKey(ScopedECKEY ec_key) : key_(std::move(ec_key)) {}
|
|
|
|
ECPublicKey::ECPublicKey(const ECPublicKey& ec_key)
|
|
: ECPublicKey(EC_KEY_dup(ec_key.key())) {}
|
|
|
|
std::unique_ptr<ECPublicKey> ECPublicKey::Create(
|
|
const std::string& serialized_key) {
|
|
EC_KEY* key;
|
|
if (!ec_util::DeserializeECPublicKey(serialized_key, &key)) {
|
|
return nullptr;
|
|
}
|
|
ScopedECKEY scoped_ec_key(key);
|
|
if (EC_KEY_check_key(scoped_ec_key.get()) != 1) {
|
|
LOG(ERROR) << "Invalid public EC key: "
|
|
<< OpenSSLErrorString(ERR_get_error());
|
|
return nullptr;
|
|
}
|
|
if (ECKeyToCurve(scoped_ec_key.get()) == ECPrivateKey::UNDEFINED_CURVE) {
|
|
LOG(ERROR) << "Key has unsupported curve";
|
|
return nullptr;
|
|
}
|
|
return absl::make_unique<ECPublicKey>(scoped_ec_key.release());
|
|
}
|
|
|
|
std::unique_ptr<ECPublicKey> ECPublicKey::CreateFromKeyPoint(
|
|
ECPrivateKey::EllipticCurve curve, const std::string& key_point) {
|
|
EC_KEY* key;
|
|
if (!ec_util::GetPublicKeyFromKeyPoint(curve, key_point, &key)) {
|
|
return nullptr;
|
|
}
|
|
|
|
ScopedECKEY scoped_ec_key(key);
|
|
if (EC_KEY_check_key(scoped_ec_key.get()) != 1) {
|
|
LOG(ERROR) << "Invalid public EC key: "
|
|
<< OpenSSLErrorString(ERR_get_error());
|
|
return nullptr;
|
|
}
|
|
if (ECKeyToCurve(scoped_ec_key.get()) == ECPrivateKey::UNDEFINED_CURVE) {
|
|
LOG(ERROR) << "Key has unsupported curve";
|
|
return nullptr;
|
|
}
|
|
return absl::make_unique<ECPublicKey>(scoped_ec_key.release());
|
|
}
|
|
|
|
bool ECPublicKey::VerifySignature(const std::string& message,
|
|
HashAlgorithm hash_algorithm,
|
|
const std::string& signature) const {
|
|
if (message.empty()) {
|
|
LOG(ERROR) << "|message| cannot be empty";
|
|
return false;
|
|
}
|
|
if (signature.empty()) {
|
|
LOG(ERROR) << "|signature| cannot be empty";
|
|
return false;
|
|
}
|
|
|
|
// Hash the message using corresponding hash algorithm.
|
|
std::string message_digest = GetMessageDigest(message, hash_algorithm);
|
|
if (message_digest.empty()) {
|
|
LOG(ERROR) << "Empty message digest";
|
|
return false;
|
|
}
|
|
|
|
int result = ECDSA_verify(
|
|
0 /* unused type */,
|
|
reinterpret_cast<const uint8_t*>(message_digest.data()),
|
|
message_digest.size(),
|
|
reinterpret_cast<uint8_t*>(const_cast<char*>(signature.data())),
|
|
signature.size(), key());
|
|
if (result != 1) {
|
|
LOG(ERROR) << "Could not verify signature: "
|
|
<< OpenSSLErrorString(ERR_get_error());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ECPublicKey::MatchesPrivateKey(const ECPrivateKey& private_key) const {
|
|
return private_key.MatchesPublicKey(*this);
|
|
}
|
|
|
|
bool ECPublicKey::MatchesPublicKey(const ECPublicKey& public_key) const {
|
|
return EC_POINT_cmp(EC_KEY_get0_group(key()), EC_KEY_get0_public_key(key()),
|
|
EC_KEY_get0_public_key(public_key.key()), nullptr) == 0;
|
|
}
|
|
|
|
ECPrivateKey::EllipticCurve ECPublicKey::Curve() const {
|
|
return ECKeyToCurve(key());
|
|
}
|
|
|
|
bool ECPublicKey::SerializedKey(std::string* serialized_key) const {
|
|
CHECK(serialized_key);
|
|
return ec_util::SerializeECPublicKey(key_.get(), serialized_key);
|
|
}
|
|
|
|
bool ECPublicKey::GetPointEncodedKey(std::string* encoded_key) const {
|
|
CHECK(encoded_key);
|
|
return ec_util::GetPublicKeyPoint(key_.get(), encoded_key);
|
|
}
|
|
|
|
bool ECPublicKey::GetRawPublicKey(std::string* raw_public_key) const {
|
|
if (raw_public_key == nullptr) {
|
|
LOG(WARNING) << "|raw_public_key| cannot be nullptr.";
|
|
return false;
|
|
}
|
|
std::string tmp_raw_public_key;
|
|
if (!ec_util::GetPublicKeyPoint(key(), &tmp_raw_public_key) ||
|
|
tmp_raw_public_key.empty()) {
|
|
LOG(WARNING) << "Failed to encoded EC_KEY";
|
|
}
|
|
CHECK_EQ(tmp_raw_public_key[0], POINT_CONVERSION_UNCOMPRESSED);
|
|
// Public key is X9.62 uncompressed format. Expected structure is <tag><X><Y>,
|
|
// where tag = 0x04 indicating uncompressed, and X and Y lengths are equal but
|
|
// dependent on ECC curve. Tag is removed because Intel Sigma 2.1.0 library
|
|
// requires only X and Y bytes.
|
|
raw_public_key->assign(tmp_raw_public_key.begin() + 1,
|
|
tmp_raw_public_key.end());
|
|
return true;
|
|
}
|
|
|
|
} // namespace widevine
|