Files
whitebox/api/test_license_builder.cc
Aaron Vaage 789377fed2 ODK and Shared Libraries
In this code drop we introduce the ODK dependency. The reference
implementation has been updated to make use of the ODK and the related
tests have been included.

In addition, we have included an example of how a shared libraries can
be created. This will allow make it easier to test and verify different
implementations of the API.

Most other changes introduce by this code drop were made to clean-up the
reference implementation and limit dependencies.
2020-07-23 16:18:41 -07:00

374 lines
14 KiB
C++

// Copyright 2020 Google LLC. All Rights Reserved.
#include "api/test_license_builder.h"
#include <ctime>
#include "base/logging.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