Files
whitebox/api/test_license_builder.cc
Aaron Vaage 69ea909ff5 Multiple Renewal Keys and Logging
In this code update we add a test to ensure that the White-box API
implementation handle seeing multiple renewal keys correctly. Since
there should be no more than one renewal key in a license response, upon
seeing a second renewal key, the implementation should return a
WB_RESULT_INVALID_PARAMETER code.

Due to changes in how Chrome manages CHECKS and DCHECKS, this code has
been updated to use the new headers.
2020-08-21 17:18:28 -07:00

375 lines
14 KiB
C++

// Copyright 2020 Google LLC. All Rights Reserved.
#include "api/test_license_builder.h"
#include <ctime>
#include "base/check.h"
#include "base/check_op.h"
#include "cdm/keys/certs.h"
#include "crypto_utils/aes_cbc_encryptor.h"
#include "crypto_utils/crypto_util.h"
#include "crypto_utils/sha_util.h"
#include "oemcrypto/odk/include/core_message_deserialize.h"
#include "oemcrypto/odk/include/core_message_serialize_proto.h"
#include "oemcrypto/odk/include/odk.h"
#include "oemcrypto/odk/include/odk_structs.h"
namespace widevine {
namespace {
void InitializeRequest(video_widevine::LicenseRequest* request) {
request->set_request_time(std::time(nullptr)); // Use time=now.
auto* client_id = request->mutable_client_id();
auto* capabilities = client_id->mutable_client_capabilities();
capabilities->set_video_resolution_constraints(true);
capabilities->set_client_token(true);
capabilities->set_session_token(false);
capabilities->set_max_hdcp_version(
video_widevine::ClientIdentification::ClientCapabilities::HDCP_V1);
auto* client_info = client_id->add_client_info();
client_info->set_name("architecture_name");
client_info->set_value("x86-64");
client_info = client_id->add_client_info();
client_info->set_name("company_name");
client_info->set_value("Google");
client_info = client_id->add_client_info();
client_info->set_name("model_name");
client_info->set_value("ChromeCDM");
client_info = client_id->add_client_info();
client_info->set_name("platform_name");
client_info->set_value("Windows");
client_info = client_id->add_client_info();
client_info->set_name("widevine_cdm_version");
client_info->set_value("4.10.1686.29");
client_id->set_type(
video_widevine::ClientIdentification_TokenType_DRM_DEVICE_CERTIFICATE);
client_id->set_token(wvcdm::kRsaDrmCertificate,
wvcdm::kRsaDrmCertificateSize);
auto* content_id = request->mutable_content_id();
auto* webm_key_id = content_id->mutable_webm_key_id();
webm_key_id->set_license_type(video_widevine::STREAMING);
webm_key_id->set_request_id("REQUEST_ID");
webm_key_id->set_header("01234567890123456");
request->set_protocol_version(video_widevine::VERSION_2_1);
request->set_type(video_widevine::LicenseRequest::NEW);
}
void InitializeResponse(const video_widevine::LicenseRequest& request,
video_widevine::License* response) {
auto* id = response->mutable_id();
id->set_request_id("REQUEST_ID");
id->set_session_id("SESSION_ID");
id->set_type(video_widevine::STREAMING);
id->set_version(0);
auto* policy = response->mutable_policy();
policy->set_can_play(true);
policy->set_can_persist(false);
policy->set_can_renew(true);
policy->set_license_duration_seconds(600);
policy->set_renewal_delay_seconds(30);
policy->set_renewal_retry_interval_seconds(10);
policy->set_renew_with_usage(false);
response->set_license_start_time(request.request_time());
response->set_remote_attestation_verified(false);
response->set_platform_verification_status(
video_widevine::PlatformVerificationStatus::PLATFORM_UNVERIFIED);
}
std::string EncryptKey(const std::string& key,
const std::string& iv,
const std::vector<uint8_t>& plaintext) {
AesCbcEncryptor encryptor;
encryptor.SetKey(reinterpret_cast<const uint8_t*>(key.data()), key.size());
std::vector<uint8_t> ciphertext(plaintext.size());
CHECK(encryptor.Encrypt(reinterpret_cast<const uint8_t*>(iv.data()),
iv.size(), plaintext.data(), plaintext.size(),
ciphertext.data()));
return std::string(ciphertext.begin(), ciphertext.end());
}
std::string DeriveIV(const std::vector<uint8_t>& context) {
const std::string context_str(context.begin(), context.end());
return crypto_util::DeriveIv(context_str);
}
void UpdateKeyControlBlock(
video_widevine::License_KeyContainer_SecurityLevel level,
video_widevine::License_KeyContainer_KeyControl* key_control) {
// The key control block is an 128 bit structure containing the following
// fields. The fields are defined to be in big-endian byte order.
//
// Bytes 0..3: Verification.
// Constant bytes “kctl”, “kc09”, “kc10”, “kc11”, ... “kc15”.
// Bytes 4..7: Obsolete.
// Bytes 8..11: Nonce.
// Bytes 12..15: Control Bits
// Bits 27..26: Security_Level (Only for L3 white-box implementations)
// 0 = SW_SECURE_CRYPTO
// 1 = SW_SECURE_DECODE
// 2 = HW_SECURE_CRYPTO
// 3 = HW_SECURE_DECODE or HW_SECURE_ALL
std::vector<uint8_t> key_control_block = {
'k', 'c', 't', 'l', 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
switch (level) {
case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO:
key_control_block[12] = 0x00 << 2;
break;
case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_DECODE:
key_control_block[12] = 0x01 << 2;
break;
case video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_CRYPTO:
key_control_block[12] = 0x02 << 2;
break;
default:
key_control_block[12] = 0x03 << 2;
break;
}
// Key Control Block is no longer encrypted, so no need to set IV.
key_control->set_key_control_block(key_control_block.data(),
key_control_block.size());
}
std::string GenerateCoreMessage(
const std::string& serialized_request,
const std::string& serialized_license_response) {
constexpr uint16_t api_major_version = 16;
constexpr uint16_t api_minor_version = 5;
static_assert(api_major_version == ODK_MAJOR_VERSION,
"Verify ODK library is compatible.");
constexpr uint32_t session_id = 0xcafebabe;
constexpr uint32_t nonce = 0xdeadbeef;
ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce,
session_id};
// Start by making a call to determine how big the core_message for the
// request needs to be.
size_t core_message_length = 0;
auto odk_result = ODK_PrepareCoreLicenseRequest(
reinterpret_cast<uint8_t*>(const_cast<char*>(serialized_request.data())),
serialized_request.size(), &core_message_length, &nonce_values);
CHECK_EQ(odk_result, OEMCrypto_ERROR_SHORT_BUFFER);
// Now that we know the size, create |combined_request_message| with room
// for the core message and append |serialized_request_|, as the combined
// buffer is needed by ODK_PrepareCoreLicenseRequest().
std::string combined_request_message;
combined_request_message.resize(core_message_length);
combined_request_message.append(serialized_request);
odk_result = ODK_PrepareCoreLicenseRequest(
reinterpret_cast<uint8_t*>(
const_cast<char*>(combined_request_message.data())),
combined_request_message.size(), &core_message_length, &nonce_values);
CHECK_EQ(odk_result, OEMCrypto_SUCCESS);
// As the core_message is the first part of |combined_request_message|,
// extract it.
const std::string request_core_message =
combined_request_message.substr(0, core_message_length);
std::string core_message_hash = widevine::Sha256_Hash(request_core_message);
oemcrypto_core_message::ODK_LicenseRequest core_request{};
CHECK(oemcrypto_core_message::deserialize::CoreLicenseRequestFromMessage(
request_core_message, &core_request));
std::string oemcrypto_core_message;
CHECK(oemcrypto_core_message::serialize::CreateCoreLicenseResponseFromProto(
serialized_license_response, core_request, core_message_hash,
/* nonce_required= */ true, &oemcrypto_core_message));
return oemcrypto_core_message;
}
} // namespace
// static
std::vector<uint8_t> TestLicenseBuilder::NoPadding() {
return {};
}
// static
std::vector<uint8_t> TestLicenseBuilder::PKSC8Padding() {
return {
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
};
}
// static
std::vector<uint8_t> TestLicenseBuilder::DefaultSigningKey() {
return {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
};
}
TestLicenseBuilder::TestLicenseBuilder() {
DCHECK_EQ(session_key_.size(), 16u);
// Initialize the request and the response with the static fields that will
// be common across all licences.
InitializeRequest(&request_);
InitializeResponse(request_, &response_);
serialized_request_ = request_.SerializeAsString();
container_key_ = crypto_util::DeriveKey(
session_key_, crypto_util::kWrappingKeyLabel, serialized_request_,
crypto_util::kWrappingKeySizeBits);
}
void TestLicenseBuilder::AddSigningKey(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& padding) {
DCHECK_EQ(key.size(), 64u); // 512 bits
auto* container = response_.add_key();
container->set_type(video_widevine::License_KeyContainer_KeyType_SIGNING);
// To avoid having to define a key iv for each key, derive a key iv from the
// key. This will allows us to have a different IVs between keys but keep it
// deterministic.
const auto key_iv = DeriveIV(key);
container->set_iv(key_iv);
std::vector<uint8_t> final_key = key;
final_key.insert(final_key.end(), padding.begin(), padding.end());
container->set_key(EncryptKey(container_key_, key_iv, final_key));
}
void TestLicenseBuilder::AddStubbedContentKey() {
const video_widevine::License_KeyContainer_SecurityLevel kLevel =
video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO;
auto* container = response_.add_key();
container->set_type(video_widevine::License_KeyContainer_KeyType_CONTENT);
container->set_id("stubbed-content-key");
container->set_level(kLevel);
container->set_iv("0000000000000000");
// We don't bother encrypting the key, it should never be used and there is no
// way to verify it. Note that the ODK automatically strips padding, so
// this key needs to include 16 bytes of padding.
container->set_key("00000000000000000000000000000000");
UpdateKeyControlBlock(kLevel, container->mutable_key_control());
}
void TestLicenseBuilder::AddContentKey(
video_widevine::License_KeyContainer_SecurityLevel level,
const std::vector<uint8_t>& key_id,
const std::vector<uint8_t>& key,
const std::vector<uint8_t>& padding) {
DCHECK_GT(key_id.size(), 0u);
DCHECK_EQ(key.size(), 16u);
auto* container = response_.add_key();
container->set_type(video_widevine::License_KeyContainer_KeyType_CONTENT);
container->set_id(key_id.data(), key_id.size());
container->set_level(level);
// To avoid having to define a key iv for each key, derive a key iv from the
// key. This will allows us to have a different IVs between keys but keep it
// deterministic.
const auto key_iv = DeriveIV(key);
container->set_iv(key_iv);
std::vector<uint8_t> final_key = key;
final_key.insert(final_key.end(), padding.begin(), padding.end());
container->set_key(EncryptKey(container_key_, key_iv, final_key));
UpdateKeyControlBlock(level, container->mutable_key_control());
}
void TestLicenseBuilder::AddOperatorSessionKey(
const std::vector<uint8_t>& key_id) {
DCHECK_GT(key_id.size(), 0u);
// We only set the type and id because the key should not actually be used.
auto* container = response_.add_key();
container->set_type(
video_widevine::License_KeyContainer_KeyType_OPERATOR_SESSION);
container->set_id(key_id.data(), key_id.size());
}
void TestLicenseBuilder::SetRemoteAttestation(RemoteAttestation setting) {
switch (setting) {
case RemoteAttestation::kUnavailable:
response_.clear_remote_attestation_verified();
break;
case RemoteAttestation::kVerified:
response_.set_remote_attestation_verified(true);
break;
case RemoteAttestation::kUnverified:
response_.set_remote_attestation_verified(false);
break;
}
}
void TestLicenseBuilder::SetVerificationStatus(VerificationStatus setting) {
switch (setting) {
case VerificationStatus::kUnavailable:
response_.clear_platform_verification_status();
break;
case VerificationStatus::kHardwareVerified:
response_.set_platform_verification_status(
video_widevine::PLATFORM_HARDWARE_VERIFIED);
break;
case VerificationStatus::kOther:
response_.set_platform_verification_status(
video_widevine::PLATFORM_UNVERIFIED);
break;
}
}
void TestLicenseBuilder::SetUseODK(bool setting) {
use_odk_ = setting;
}
void TestLicenseBuilder::Build(const RsaPublicKey& public_key,
License* license) const {
DCHECK(license);
const std::string message_str = response_.SerializeAsString();
std::string signing_key = crypto_util::DeriveKey(
session_key_, crypto_util::kSigningKeyLabel, serialized_request_,
crypto_util::kSigningKeySizeBits * 2);
signing_key.resize(crypto_util::kSigningKeySizeBytes);
std::string session_key_str;
CHECK(public_key.Encrypt(session_key_, &session_key_str));
const std::string oemcrypto_core_message =
use_odk_ ? GenerateCoreMessage(serialized_request_, message_str)
: std::string();
// If |use_odk_| is false, |oemcrypto_core_message| will be empty.
const std::string signature_str = crypto_util::CreateSignatureHmacSha256(
signing_key, oemcrypto_core_message + message_str);
license->request.assign(serialized_request_.begin(),
serialized_request_.end());
if (!oemcrypto_core_message.empty()) {
license->core_message.assign(oemcrypto_core_message.begin(),
oemcrypto_core_message.end());
}
license->message.assign(message_str.begin(), message_str.end());
license->signature.assign(signature_str.begin(), signature_str.end());
license->session_key.assign(session_key_str.begin(), session_key_str.end());
}
} // namespace widevine