Files
whitebox/whitebox/reference/impl/protobuf_license_parser.cc
Aaron Vaage f936dd2983 Add "License Key Mode" To API
In order to support both single-key and dual-key RSA implementations
where single-key will use key 0 for both sign and encryption and where
dual-key will use key 0 for sign and key 1 for encryption.

Additional changes in this code drop:

 - Added VMP / RA override enabled tests
 - Added VMP / RA override disabled tests

This brings the partner repo in sync with the internal repo at
commit 71760b6da1ec546c65b56e2f86b39b73b53f6734.
2021-04-05 12:05:15 -07:00

208 lines
7.0 KiB
C++

// Copyright 2021 Google LLC. All Rights Reserved.
#include "reference/impl/protobuf_license_parser.h"
#include "base/check_op.h"
#include "base/logging.h"
#include "crypto_utils/crypto_util.h"
namespace widevine {
namespace {
// WV_ENABLE_HW_VERIFICATION = 0: prevent VMP/RA from overriding the key
// security level.
//
// WV_ENABLE_HW_VERIFICATION = 1: allow VMP/RA to override the key security
// level.
#if WV_ENABLE_HW_VERIFICATION == 0
constexpr bool kEnableHwVerification = false;
#elif WV_ENABLE_HW_VERIFICATION == 1
constexpr bool kEnableHwVerification = true;
#else
#error Undefined or unknown value for WV_ENABLE_HW_VERIFICATION
#endif // WV_ENABLE_HW_VERIFICATION
// We use "remote_attestation_verified" and "platform_verification_status" to
// determine whether the platform is hardware verified.
//
// Each variable can be in one of three states. Each variable has a missing
// value, a true value, and a false value.
//
// |----------------------------------------------------------|
// | | RA N/A | RA VERIFIED | RA NOT VERIFIED |
// |----------------------------------------------------------|
// | VMP N/A | 0 | 1 | 0 |
// | VMP HW_VERIFIED | 1 | 1 | 0 |
// | VMP OTHER | 0 | 0 | 0 |
// |----------------------------------------------------------|
bool IsPlatformHardwareVerified(const video_widevine::License& license) {
int ra;
if (!license.has_remote_attestation_verified()) {
ra = 0;
} else if (license.remote_attestation_verified()) {
ra = 1;
} else {
ra = 2;
}
int vmp;
if (!license.has_platform_verification_status()) {
vmp = 0;
} else if (license.platform_verification_status() ==
video_widevine::PLATFORM_HARDWARE_VERIFIED) {
vmp = 1;
} else {
vmp = 2;
}
// Use int to match the table we have in the comment above.
const int table[3][3] = {
{0, 1, 0},
{1, 1, 0},
{0, 0, 0},
};
return table[vmp][ra] == 1;
}
} // namespace
WB_Result ProtobufLicenseParser::Parse(const std::string& decryption_key,
const ODKContext& odk_context,
const std::string& message) {
video_widevine::License license;
if (!license.ParseFromString(message)) {
VLOG(3) << "Invalid parameter: Invalid license.";
return WB_RESULT_INVALID_PARAMETER;
}
// When the platform is hardware verified, all keys are unlocked and are
// available to be used with either decrypt function. Use this flag to
// overwrite the calculated values for the internal policies to enable
// this behaviour.
const bool is_verified =
kEnableHwVerification && IsPlatformHardwareVerified(license);
for (const auto& key : license.key()) {
// The default `type()` value will be SIGNING when not present in the
// protobuf.
switch (key.type()) {
case video_widevine::License_KeyContainer::SIGNING:
// The ODK will not write the key to the core message if there is no key
// data. This is odd, since it does not check any other fields. To be
// consistent, we have the protobuf implementation do the same.
if (key.key().empty()) {
VLOG(3) << "Skipping signing key : no key data.";
} else {
renewal_keys_.push_back(ParseSigningKey(decryption_key, key));
}
break;
case video_widevine::License_KeyContainer::CONTENT:
if (key.id().empty()) {
VLOG(3) << "Skipping content key : no key id.";
} else {
content_keys_[key.id()] =
ParseContentKey(decryption_key, key, is_verified);
}
break;
default:
VLOG(3) << "Skipping key of type " << key.type() << ".";
}
}
// This should not happen, but if it happens, the implementation is free to
// use any of them.
if (renewal_keys_.size() > 1) {
VLOG(3) << "Multiple renewal keys found. Extra keys will be ignored.";
}
return WB_RESULT_OK;
}
const widevine::RenewalKey* ProtobufLicenseParser::GetRenewalKey() const {
return renewal_keys_.empty() ? nullptr : &renewal_keys_[0];
}
const std::map<std::string, ContentKey>& ProtobufLicenseParser::GetContentKeys()
const {
return content_keys_;
}
RenewalKey ProtobufLicenseParser::ParseSigningKey(
const std::string& decryption_key,
const video_widevine::License_KeyContainer& key) const {
CHECK_EQ(decryption_key.size(), 16u);
CHECK_EQ(key.type(), video_widevine::License_KeyContainer::SIGNING);
const size_t kSizeWithoutPadding = 2 * crypto_util::kSigningKeySizeBytes;
const size_t kSizeWithPadding = 2 * crypto_util::kSigningKeySizeBytes + 16u;
if (key.key().size() != kSizeWithoutPadding &&
key.key().size() != kSizeWithPadding) {
VLOG(3) << "Invalid renewal key size (" << key.key().size() << ").";
return RenewalKey();
}
if (key.iv().size() != 16u) {
VLOG(3) << "Invalid iv size.";
return RenewalKey();
}
const std::string wrapped_key = key.key().substr(0, kSizeWithoutPadding);
std::string unwrapped_key(wrapped_key);
if (!Decrypt(decryption_key, key.iv(), wrapped_key, &unwrapped_key)) {
// The input has to be a specific length, so if it is not, it means that
// something is wrong with the license.
VLOG(3) << "Failed to decrypt renewal key.";
return RenewalKey();
}
widevine::RenewalKey renewal_key;
renewal_key.status = WB_KEY_STATUS_SIGNING_KEY_VALID;
std::copy(unwrapped_key.begin(),
unwrapped_key.begin() + crypto_util::kSigningKeySizeBytes,
renewal_key.server.begin());
std::copy(unwrapped_key.begin() + crypto_util::kSigningKeySizeBytes,
unwrapped_key.end(), renewal_key.client.begin());
return renewal_key;
}
ContentKey ProtobufLicenseParser::ParseContentKey(
const std::string& decryption_key,
const video_widevine::License_KeyContainer& key,
bool is_verified) const {
// This should have been verified before calling the parser.
CHECK_EQ(decryption_key.size(), 16u) << "Incorrect decryption key size.";
CHECK_EQ(key.type(), video_widevine::License_KeyContainer::CONTENT);
constexpr size_t kKeySizeWithoutPadding = 16u;
constexpr size_t kKeySizeWithPadding = 32u;
if (key.key().size() != kKeySizeWithoutPadding &&
key.key().size() != kKeySizeWithPadding) {
VLOG(3) << "Invalid content key size (" << key.key().size() << ").";
return ContentKey();
}
if (key.iv().size() != 16u) {
VLOG(3) << "Invalid iv size.";
return ContentKey();
}
std::string wrapped_key = key.key().substr(0, kKeySizeWithoutPadding);
std::string unwrapped_key(wrapped_key);
if (!Decrypt(decryption_key, key.iv(), wrapped_key, &unwrapped_key)) {
VLOG(3) << "Failed to decrypt content key.";
return ContentKey();
}
// The default value for `level()` will be SW_SECURE_CRYPTO. This means that
// if the level value was not set, it will be treated as SW_SECURE_CRYPTO.
return CreateContentKey(key.level(), is_verified, unwrapped_key);
}
} // namespace widevine