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.
523 lines
19 KiB
C++
523 lines
19 KiB
C++
// Copyright 2020 Google LLC. All Rights Reserved.
|
|
|
|
#include "api/test_license_builder.h"
|
|
|
|
#include <array>
|
|
#include <ctime>
|
|
#include <map>
|
|
|
|
#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 {
|
|
|
|
using KeyControlBlock = std::array<uint8_t, 16>;
|
|
|
|
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);
|
|
}
|
|
|
|
template <typename Key, typename IV, typename Plaintext>
|
|
std::string Encrypt(const Key& key, const IV& iv, const Plaintext& plaintext) {
|
|
std::vector<uint8_t> key_data(key.begin(), key.end());
|
|
std::vector<uint8_t> iv_data(iv.begin(), iv.end());
|
|
std::vector<uint8_t> plaintext_data(plaintext.begin(), plaintext.end());
|
|
|
|
AesCbcEncryptor encryptor;
|
|
encryptor.SetKey(key_data.data(), key_data.size());
|
|
|
|
std::vector<uint8_t> ciphertext(plaintext.size());
|
|
CHECK(encryptor.Encrypt(iv_data.data(), iv_data.size(), plaintext_data.data(),
|
|
plaintext_data.size(), ciphertext.data()));
|
|
|
|
return std::string(ciphertext.begin(), ciphertext.end());
|
|
}
|
|
|
|
template <typename In>
|
|
std::string DeriveIV(const In& context) {
|
|
const std::string context_str(context.begin(), context.end());
|
|
return crypto_util::DeriveIv(context_str);
|
|
}
|
|
|
|
video_widevine::License_KeyContainer_SecurityLevel SecurityLevelToProto(
|
|
SecurityLevel level) {
|
|
switch (level) {
|
|
case SecurityLevel::kSoftwareSecureCrypto:
|
|
return video_widevine::
|
|
License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO;
|
|
case SecurityLevel::kSoftwareSecureDecode:
|
|
return video_widevine::
|
|
License_KeyContainer_SecurityLevel_SW_SECURE_DECODE;
|
|
case SecurityLevel::kHardwareSecureCrypto:
|
|
return video_widevine::
|
|
License_KeyContainer_SecurityLevel_HW_SECURE_CRYPTO;
|
|
case SecurityLevel::kHardwareSecureDecode:
|
|
return video_widevine::
|
|
License_KeyContainer_SecurityLevel_HW_SECURE_DECODE;
|
|
case SecurityLevel::kHardwareSecureAll:
|
|
return video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_ALL;
|
|
|
|
case SecurityLevel::kUndefined:
|
|
// If the security level is undefined (in the key container) it would
|
|
// default to software secure crypto.
|
|
return video_widevine::
|
|
License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO;
|
|
}
|
|
|
|
CHECK(false) << "Unsupported security level";
|
|
}
|
|
|
|
KeyControlBlock CreateKeyControlBlock(
|
|
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
|
|
KeyControlBlock 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;
|
|
}
|
|
|
|
return key_control_block;
|
|
}
|
|
|
|
std::string GenerateCoreMessage(const std::string& serialized_request,
|
|
const std::string& serialized_license_response,
|
|
uint16_t api_major_version,
|
|
uint16_t api_minor_version,
|
|
bool use_padding) {
|
|
DCHECK_EQ(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, use_padding, &oemcrypto_core_message));
|
|
return oemcrypto_core_message;
|
|
}
|
|
|
|
std::vector<uint8_t> GetPadding(TestLicenseBuilder::Padding padding) {
|
|
if (padding == TestLicenseBuilder::Padding::kPKSC8) {
|
|
return {
|
|
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
|
|
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
|
|
};
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void AddContentKeyToContainer(const TestLicenseBuilder::ContentKey& key_data,
|
|
const TestLicenseBuilder::Settings& settings,
|
|
const std::string& container_key,
|
|
video_widevine::License_KeyContainer* container) {
|
|
if (settings.include_content_key_type) {
|
|
container->set_type(video_widevine::License_KeyContainer_KeyType_CONTENT);
|
|
}
|
|
|
|
if (settings.include_content_key_id) {
|
|
container->set_id(key_data.id.data(), key_data.id.size());
|
|
}
|
|
|
|
// If the security level is undefined it means that no security level should
|
|
// appear in the key container. When reading the key container, it will be
|
|
// softare secure crypto as that is what the protobuf defaults to.
|
|
if (key_data.level != SecurityLevel::kUndefined) {
|
|
container->set_level(SecurityLevelToProto(key_data.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_data.key);
|
|
|
|
if (settings.include_content_key_iv) {
|
|
container->set_iv(key_iv);
|
|
container->mutable_iv()->resize(settings.content_key_iv_size);
|
|
}
|
|
|
|
if (settings.include_content_key_key) {
|
|
std::vector<uint8_t> key(key_data.key.begin(), key_data.key.end());
|
|
|
|
auto padding = GetPadding(settings.padding);
|
|
key.insert(key.end(), padding.begin(), padding.end());
|
|
|
|
auto encrypted_key = Encrypt(container_key, key_iv, key);
|
|
|
|
container->set_key(encrypted_key);
|
|
|
|
if (settings.content_key_key_size_override) {
|
|
container->mutable_key()->resize(settings.content_key_key_size);
|
|
}
|
|
}
|
|
|
|
// There are three different ways we can add a key control block. It is
|
|
// possible to have no key control block (it is an optional field). This is
|
|
// the only option if the security level for the key is missing (the security
|
|
// level is non-optional in the key control block).
|
|
//
|
|
// The key control block can either be in the clear or encrypted. The latest
|
|
// version of the license protocol has it in the clear.
|
|
switch (settings.key_control_block) {
|
|
case TestLicenseBuilder::KeyControlBlock::kNone:
|
|
break;
|
|
|
|
case TestLicenseBuilder::KeyControlBlock::kClear: {
|
|
auto* key_control = container->mutable_key_control();
|
|
const auto key_control_block = CreateKeyControlBlock(
|
|
SecurityLevelToProto(key_data.level), key_control);
|
|
|
|
key_control->set_key_control_block(key_control_block.data(),
|
|
key_control_block.size());
|
|
|
|
break;
|
|
}
|
|
|
|
case TestLicenseBuilder::KeyControlBlock::kEncrypted: {
|
|
// It is only when the key control block is encrypted will the IV be set.
|
|
// The key control block is encrypted with the content key. This will no
|
|
// longer be the case in OEMCrypto 17.
|
|
auto* key_control = container->mutable_key_control();
|
|
const auto key_control_block = CreateKeyControlBlock(
|
|
SecurityLevelToProto(key_data.level), key_control);
|
|
|
|
const auto key_control_block_iv = DeriveIV(key_control_block);
|
|
const auto encrypted_key_control_block =
|
|
Encrypt(key_data.key, key_control_block_iv, key_control_block);
|
|
|
|
key_control->set_iv(key_control_block_iv);
|
|
key_control->set_key_control_block(encrypted_key_control_block.data(),
|
|
encrypted_key_control_block.size());
|
|
break;
|
|
}
|
|
|
|
default:
|
|
CHECK(false) << "Unknown key control block settings";
|
|
}
|
|
}
|
|
|
|
void AddSigningKeyToContainer(const TestLicenseBuilder::SigningKey& key_data,
|
|
const TestLicenseBuilder::Settings& settings,
|
|
const std::string& container_key,
|
|
video_widevine::License_KeyContainer* container) {
|
|
if (settings.include_signing_key_type) {
|
|
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_data);
|
|
|
|
if (settings.include_signing_key_iv) {
|
|
container->set_iv(key_iv);
|
|
container->mutable_iv()->resize(settings.signing_key_iv_size);
|
|
}
|
|
|
|
if (settings.include_signing_key_key) {
|
|
std::vector<uint8_t> key(key_data.begin(), key_data.end());
|
|
|
|
auto padding = GetPadding(settings.padding);
|
|
key.insert(key.end(), padding.begin(), padding.end());
|
|
|
|
auto encrypted_key = Encrypt(container_key, key_iv, key);
|
|
|
|
container->set_key(encrypted_key);
|
|
|
|
if (settings.signing_key_key_size_override) {
|
|
container->mutable_key()->resize(settings.signing_key_key_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AddOperatorSessionKeyToContainer(
|
|
const KeyId& key_id,
|
|
video_widevine::License_KeyContainer* container) {
|
|
container->set_type(
|
|
video_widevine::License_KeyContainer_KeyType_OPERATOR_SESSION);
|
|
container->set_id(key_id.data(), key_id.size());
|
|
}
|
|
|
|
uint16_t GetOdkMinorVersion(TestLicenseBuilder::OdkVersion odk_version) {
|
|
switch (odk_version) {
|
|
case TestLicenseBuilder::OdkVersion::k16_3:
|
|
return 3;
|
|
case TestLicenseBuilder::OdkVersion::k16_5:
|
|
return 5;
|
|
case TestLicenseBuilder::OdkVersion::kNone:
|
|
DCHECK(false);
|
|
return 0;
|
|
default:
|
|
CHECK(false) << "Unknown ODK Version";
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// static
|
|
TestLicenseBuilder::SigningKey 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(
|
|
std::string(session_key_.begin(), session_key_.end()),
|
|
crypto_util::kWrappingKeyLabel, serialized_request_,
|
|
crypto_util::kWrappingKeySizeBits);
|
|
}
|
|
|
|
void TestLicenseBuilder::AddSigningKey(const SigningKey& key) {
|
|
signing_keys_.push_back(key);
|
|
}
|
|
|
|
void TestLicenseBuilder::AddStubbedContentKey() {
|
|
content_keys_.emplace_back();
|
|
auto& key_data = content_keys_.back();
|
|
key_data.level = SecurityLevel::kSoftwareSecureCrypto;
|
|
key_data.id = {0, 0, 0, 0};
|
|
key_data.key = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
}
|
|
|
|
void TestLicenseBuilder::AddContentKey(SecurityLevel level,
|
|
const KeyId& id,
|
|
const AesKey& key) {
|
|
content_keys_.emplace_back();
|
|
auto& key_data = content_keys_.back();
|
|
key_data.level = level;
|
|
key_data.id = id;
|
|
key_data.key = key;
|
|
}
|
|
|
|
void TestLicenseBuilder::AddOperatorSessionKey(const KeyId& id) {
|
|
operator_session_keys_.push_back(id);
|
|
}
|
|
|
|
void TestLicenseBuilder::Build(const TestServer& server,
|
|
License* license) const {
|
|
DCHECK(license);
|
|
|
|
// Make a copy to allow us to maintain the const-correctness of this
|
|
// function.
|
|
video_widevine::License response = response_;
|
|
|
|
switch (settings_.remote_attestation) {
|
|
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;
|
|
}
|
|
|
|
switch (settings_.verification_status) {
|
|
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;
|
|
}
|
|
|
|
for (const auto& key : signing_keys_) {
|
|
AddSigningKeyToContainer(key, settings_, container_key_,
|
|
response.add_key());
|
|
}
|
|
|
|
for (const auto& key : content_keys_) {
|
|
AddContentKeyToContainer(key, settings_, container_key_,
|
|
response.add_key());
|
|
}
|
|
|
|
for (const auto& key : operator_session_keys_) {
|
|
AddOperatorSessionKeyToContainer(key, response.add_key());
|
|
}
|
|
|
|
const std::string message_str = response.SerializeAsString();
|
|
std::string signing_key = crypto_util::DeriveKey(
|
|
std::string(session_key_.begin(), session_key_.end()),
|
|
crypto_util::kSigningKeyLabel, serialized_request_,
|
|
crypto_util::kSigningKeySizeBits * 2);
|
|
signing_key.resize(crypto_util::kSigningKeySizeBytes);
|
|
|
|
std::vector<uint8_t> encrypted_session_key = server.Encrypt(
|
|
std::vector<uint8_t>(session_key_.begin(), session_key_.end()));
|
|
CHECK_GT(encrypted_session_key.size(), 0u);
|
|
|
|
std::string oemcrypto_core_message;
|
|
if (settings_.odk_version != OdkVersion::kNone) {
|
|
uint16_t api_major_version = 16;
|
|
uint16_t api_minor_version = GetOdkMinorVersion(settings_.odk_version);
|
|
oemcrypto_core_message = GenerateCoreMessage(
|
|
serialized_request_, message_str, api_major_version, api_minor_version,
|
|
settings_.padding != Padding::kNone);
|
|
}
|
|
|
|
// If |odk_version| is kNone, |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(encrypted_session_key.begin(),
|
|
encrypted_session_key.end());
|
|
}
|
|
} // namespace widevine
|