Files
whitebox/impl/reference/license_whitebox_impl.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

930 lines
34 KiB
C++

// Copyright 2020 Google LLC. All Rights Reserved.
#include "api/license_whitebox.h"
#include <stdint.h>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/logging.h"
#include "cdm/protos/license_protocol.pb.h"
#include "crypto_utils/aes_cbc_decryptor.h"
#include "crypto_utils/aes_ctr_encryptor.h"
#include "crypto_utils/crypto_util.h"
#include "crypto_utils/rsa_key.h"
#include "impl/reference/memory_util.h"
#include "oemcrypto/odk/include/odk.h"
#include "oemcrypto/odk/include/odk_structs.h"
#include "oemcrypto/odk/src/odk_serialize.h"
#include "oemcrypto/odk/src/serialization_base.h"
#include "third_party/boringssl/src/include/openssl/aes.h"
#include "third_party/boringssl/src/include/openssl/cmac.h"
#include "third_party/boringssl/src/include/openssl/err.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
#include "third_party/boringssl/src/include/openssl/hmac.h"
#include "third_party/boringssl/src/include/openssl/rsa.h"
#include "third_party/boringssl/src/include/openssl/sha.h"
namespace {
using AesCbcDecryptor = widevine::AesCbcDecryptor;
using AesCtrDecryptor = widevine::AesCtrEncryptor;
using KeyContainer = video_widevine::License_KeyContainer;
using RsaPrivateKey = widevine::RsaPrivateKey;
struct ContentKey {
// When we store a key, we create our own little policy for the key saying
// what functions may use it. This allows us to "blacklist" a key by setting
// all "allow_*" to false.
bool allow_decrypt;
bool allow_masked_decrypt;
// Key used to decrypt content.
std::vector<uint8_t> key;
};
// Helper function to decrypt |encrypted| into |decrypted| using |decryptor|
// and |iv|. Done as the protobuf and ODK code use std::string, AesCbcDecryptor
// requires uint8_t* + size_t parameters.
bool Decrypt(AesCbcDecryptor& decryptor,
const std::string& iv,
const std::string& encrypted,
std::string* decrypted) {
DCHECK_GE(decrypted->size(), encrypted.size());
return decryptor.Decrypt(
reinterpret_cast<const uint8_t*>(iv.data()), iv.size(),
reinterpret_cast<const uint8_t*>(encrypted.data()), encrypted.size(),
reinterpret_cast<uint8_t*>(&decrypted->front()));
}
bool IsOdkVersionSupported(uint16_t major_version, uint16_t minor_version) {
// Only ODK v16.5 and later support the fields needed.
constexpr uint16_t first_major_version_supported = 16;
constexpr uint16_t first_minor_version_supported = 5;
return (major_version > first_major_version_supported) ||
((major_version == first_major_version_supported) &&
(minor_version >= first_minor_version_supported));
}
// Helper function to extract the substring |item| from the provided |buffer|.
std::string ExtractItem(const OEMCrypto_Substring& item,
const std::string& buffer) {
return buffer.substr(item.offset, item.length);
}
video_widevine::License_KeyContainer_SecurityLevel ExtractLevel(
const std::string& key_control_block) {
// 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
// Limited checks to verify that this is proper key control block.
// If not valid, assume it's the highest level.
if ((key_control_block.size() != 16u) || (key_control_block[0] != 'k') ||
(key_control_block[1] != 'c')) {
return video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_DECODE;
}
// Extract bits 26..27 from Control Bits.
int security_level = (key_control_block[12] & 0x0C) >> 2;
switch (security_level) {
case 0:
return video_widevine::
License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO;
case 1:
return video_widevine::
License_KeyContainer_SecurityLevel_SW_SECURE_DECODE;
case 2:
return video_widevine::
License_KeyContainer_SecurityLevel_HW_SECURE_CRYPTO;
default:
return video_widevine::
License_KeyContainer_SecurityLevel_HW_SECURE_DECODE;
}
}
// Creates and returns a ContentKey based on the values provided.
// |level| determines whether decrypt or masked_decrypt is allowed.
// |is_hw_verified|, if set, overrides |level| so that both decrypt and
// masked_decrypt is allowed. |Key| is the decryption key, and is only
// returned in ContentKey if decrypt or masked_decrypt is allowed.
// Otherwise |key| is dropped.
ContentKey CreateContentKey(
video_widevine::License_KeyContainer_SecurityLevel level,
bool is_hw_verified,
const std::string& key) {
ContentKey content_key;
switch (level) {
case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO:
content_key.allow_decrypt = true;
content_key.allow_masked_decrypt = true;
break;
case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_DECODE:
content_key.allow_decrypt = false;
content_key.allow_masked_decrypt = true;
break;
default:
content_key.allow_decrypt = false;
content_key.allow_masked_decrypt = false;
break;
}
content_key.allow_decrypt |= is_hw_verified;
content_key.allow_masked_decrypt |= is_hw_verified;
// Unless we are going to use the key, we don't want to save this key as
// it will only risk exposing it. We only have an entry for it so we can
// handle errors correctly.
if (content_key.allow_decrypt || content_key.allow_masked_decrypt) {
content_key.key.assign(key.begin(), key.end());
}
return content_key;
}
// This function uses 16 non-linear bijections that are applied to a byte.
// This is "Example Masking Function 1" from the shared document
// https://docs.google.com/document/d/1xWPwlFHyjT8YzWhY3TyaC02SQvclC_dkEpliGPOUk_o#heading=h.j64j2z3b9v99
uint8_t MaskingFunction1(uint8_t input) {
const size_t x = 97 * input;
const size_t y = x + 96;
return y % 257;
}
// This function performs the reverse of MaskingFunction1().
uint8_t InverseMaskingFunction1(uint8_t input) {
// Rather than compute the byte each time, generate a lookup table for all
// 256 values the first time this is called.
static bool initialized = false;
static uint8_t mapping[256];
if (!initialized) {
initialized = true;
uint8_t byte = 0;
do {
mapping[MaskingFunction1(byte)] = byte;
++byte;
} while (byte != 0);
}
return mapping[input];
}
} // namespace
// The white-box type can't be in the namespace as it is defined in the header.
struct WB_License_Whitebox {
// CDM key, used for license requests.
std::unique_ptr<RsaPrivateKey> key;
// Keys used for license renewal.
std::string server_signing_key;
std::string client_signing_key;
std::map<std::string, ContentKey> content_keys;
};
namespace {
// For simplicity we use a basic pad but we use a different byte for each
// position as we need to support abirtary indexes and we want to make sure that
// a wrong index will actually trigger a failure.
const uint8_t kSecretStringPattern[] = {
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
};
const size_t kSecretStringPatternSize = sizeof(kSecretStringPattern);
const ContentKey* FindKey(const WB_License_Whitebox* whitebox,
const uint8_t* id,
size_t id_size) {
DCHECK(whitebox);
DCHECK(id);
DCHECK_GT(id_size, 0u);
const std::string key_id(id, id + id_size);
const auto found = whitebox->content_keys.find(key_id);
return found == whitebox->content_keys.end() ? nullptr : &found->second;
}
WB_Result DecryptBuffer(WB_CipherMode mode,
const std::vector<uint8_t>& key,
const uint8_t* input_data,
size_t input_data_size,
const uint8_t* iv,
size_t iv_size,
uint8_t* output_data,
size_t* output_data_size) {
if (!input_data || !iv || !output_data || !output_data_size) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
if (input_data_size == 0) {
DVLOG(1) << "Invalid parameter: array size 0.";
return WB_RESULT_INVALID_PARAMETER;
}
// Data must be "block aligned" for CBC. CTR does not have this requirement.
if (mode == WB_CIPHER_MODE_CBC && input_data_size % AES_BLOCK_SIZE != 0) {
DVLOG(1) << "Invalid parameter: bad block size.";
return WB_RESULT_INVALID_PARAMETER;
}
// We only support 16 byte IVs. CENC allows 8 byte, but it is expected that
// the IV will be padded before entering the whitebox.
if (iv_size != 16) {
DVLOG(1) << "Invalid parameter: invalid iv size.";
return WB_RESULT_INVALID_PARAMETER;
}
// There is no padding, so the output will be the same length as the input.
if (*output_data_size < input_data_size) {
DVLOG(1) << "Buffer too small: needs " << input_data_size << " but got "
<< *output_data_size << ".";
*output_data_size = input_data_size;
return WB_RESULT_BUFFER_TOO_SMALL;
}
// By this point, we have verified everything that need to be verified.
// Decryption should just work.
if (mode == WB_CIPHER_MODE_CBC) {
AesCbcDecryptor decryptor;
CHECK(decryptor.SetKey(key.data(), key.size()));
*output_data_size = input_data_size;
CHECK(decryptor.Decrypt(iv, iv_size, input_data, input_data_size,
output_data));
} else if (mode == WB_CIPHER_MODE_CTR) {
AesCtrDecryptor decryptor;
CHECK(decryptor.SetKey(key.data(), key.size()));
// Encrypt and Decrypt for CBC use the same interface.
*output_data_size = input_data_size;
CHECK(decryptor.Encrypt(iv, iv_size, input_data, input_data_size,
output_data));
} else {
DVLOG(1) << "Invalid parameter: invalid cipher mode.";
return WB_RESULT_INVALID_PARAMETER;
}
return WB_RESULT_OK;
}
// 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) {
const bool has_ra = license.has_remote_attestation_verified();
const bool has_vmp = license.has_platform_verification_status();
if (!has_ra && !has_vmp) {
return false;
}
// We trust "missing" more than "false". Now that we know we have some values,
// default to "it's okay" and only override it if we have a new value from the
// license.
bool flags[2] = {true, true};
if (has_ra) {
flags[0] = license.remote_attestation_verified();
}
if (has_vmp) {
flags[1] = license.platform_verification_status() ==
video_widevine::PLATFORM_HARDWARE_VERIFIED;
}
// If we were missing a value, that flag will still be true. But if we see any
// false values, then we know something was found to be invalid.
return flags[0] && flags[1];
}
} // namespace
WB_Result WB_License_Create(const uint8_t* whitebox_init_data,
size_t whitebox_init_data_size,
WB_License_Whitebox** whitebox) {
if (!whitebox_init_data || !whitebox) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
if (whitebox_init_data_size == 0) {
DVLOG(1) << "Invalid parameter: array size 0.";
return WB_RESULT_INVALID_PARAMETER;
}
// |whitebox_init_data| is simply the bytes of a PKCS #8 PrivateKeyInfo block
// of a RSA 2048 bit key.
std::unique_ptr<RsaPrivateKey> key(RsaPrivateKey::Create(std::string(
whitebox_init_data, whitebox_init_data + whitebox_init_data_size)));
if (!key) {
DVLOG(1) << "Invalid parameter: invalid init data.";
return WB_RESULT_INVALID_PARAMETER;
}
// Should always be non-null on modern compilers
// (https://isocpp.org/wiki/faq/freestore-mgmt).
*whitebox = new WB_License_Whitebox();
(*whitebox)->key.swap(key);
return WB_RESULT_OK;
}
void WB_License_Delete(WB_License_Whitebox* whitebox) {
// Safe to delete nullptr (https://isocpp.org/wiki/faq/freestore-mgmt).
delete whitebox;
}
WB_Result WB_License_SignLicenseRequest(const WB_License_Whitebox* whitebox,
const uint8_t* license_request,
size_t license_request_size,
uint8_t* signature,
size_t* signature_size) {
if (!whitebox || !license_request || !signature || !signature_size) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
if (license_request_size == 0) {
DVLOG(1) << "Invalid parameter: array size 0.";
return WB_RESULT_INVALID_PARAMETER;
}
std::string result;
CHECK(whitebox->key->GenerateSignature(
std::string(license_request, license_request + license_request_size),
&result));
if (!widevine::MemCopy(result.data(), result.size(), signature,
*signature_size)) {
DVLOG(1) << "Buffer too small: signature needs " << result.size() << ".";
*signature_size = result.size();
return WB_RESULT_BUFFER_TOO_SMALL;
}
*signature_size = result.size();
return WB_RESULT_OK;
}
WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox,
const uint8_t* core_message,
size_t core_message_size,
const uint8_t* message,
size_t message_size,
const uint8_t* signature,
size_t signature_size,
const uint8_t* session_key,
size_t session_key_size,
const uint8_t* license_request,
size_t license_request_size) {
const size_t kSigningKeySizeBytes =
widevine::crypto_util::kSigningKeySizeBytes;
if (!whitebox || !message || !signature || !session_key || !license_request) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
// |core_message| is optional. If provided, both |core_message| and
// |core_message_size| need to be set.
if (core_message_size > 0 && !core_message) {
DVLOG(1) << "Invalid parameter: core_message null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
// If we have already loaded a license, ProcessLicenseResponse() may not be
// called again. The white-box needs to be destroyed and a new one needs
// to be created.
if (whitebox->content_keys.size() > 0) {
DVLOG(1) << "Invalid state: already loaded a license.";
return WB_RESULT_INVALID_STATE;
}
if (message_size == 0 || session_key_size == 0 || license_request_size == 0) {
DVLOG(1) << "Invalid parameter: array size 0.";
return WB_RESULT_INVALID_PARAMETER;
}
// Because we use SHA256, the hash will be 32 bytes (256 bits).
if (signature_size != 32) {
DVLOG(1) << "Invalid parameter: invalid signature size.";
return WB_RESULT_INVALID_PARAMETER;
}
std::string decrypted_session_key;
if (!whitebox->key->Decrypt(
std::string(session_key, session_key + session_key_size),
&decrypted_session_key)) {
DVLOG(1) << "Invalid parameter: invalid session key.";
return WB_RESULT_INVALID_PARAMETER;
}
std::string signing_key_material = widevine::crypto_util::DeriveKey(
decrypted_session_key, widevine::crypto_util::kSigningKeyLabel,
std::string(license_request, license_request + license_request_size),
widevine::crypto_util::kSigningKeySizeBits * 2);
if (signing_key_material.size() < kSigningKeySizeBytes * 2) {
DVLOG(1) << "Invalid parameter: invalid session key size.";
return WB_RESULT_INVALID_PARAMETER;
}
const std::string server_signing_key =
signing_key_material.substr(0, kSigningKeySizeBytes);
std::string decryption_key = widevine::crypto_util::DeriveKey(
decrypted_session_key, widevine::crypto_util::kWrappingKeyLabel,
std::string(license_request, license_request + license_request_size),
widevine::crypto_util::kWrappingKeySizeBits);
if (decryption_key.empty()) {
DVLOG(1) << "Failed to decrypt decryption key";
return WB_RESULT_INVALID_SIGNATURE;
}
const std::string message_str(message, message + message_size);
std::string core_message_str;
if (core_message_size > 0) {
core_message_str.assign(reinterpret_cast<const char*>(core_message),
core_message_size);
}
const std::string combined_message_str = core_message_str + message_str;
if (!widevine::crypto_util::VerifySignatureHmacSha256(
server_signing_key,
std::string(signature, signature + signature_size),
combined_message_str)) {
DVLOG(1) << "Failed to verify signed message.";
return WB_RESULT_INVALID_SIGNATURE;
}
// If |core_message| is provided, parse it into |parsed_license| and validate
// that it's an appropriate version.
uint16_t odk_major_version = 0;
uint16_t odk_minor_version = 0;
ODK_ParsedLicense parsed_license;
if (core_message_size > 0) {
// Decode |core_message|.
Message* msg = NULL;
AllocateMessage(&msg, message_block);
InitMessage(msg,
reinterpret_cast<uint8_t*>(
const_cast<char*>(combined_message_str.data())),
combined_message_str.size());
// The core message is at the beginning of the buffer, and is the part to be
// parsed.
SetSize(msg, core_message_size);
ODK_LicenseResponse license_response = {{{0}}, &parsed_license, {0}};
Unpack_ODK_LicenseResponse(msg, &license_response);
odk_major_version =
license_response.request.core_message.nonce_values.api_major_version;
odk_minor_version =
license_response.request.core_message.nonce_values.api_minor_version;
if ((GetStatus(msg) != MESSAGE_STATUS_OK) ||
(license_response.request.core_message.message_type !=
ODK_License_Response_Type)) {
DVLOG(1) << "Failed to validate core message.";
return WB_RESULT_INVALID_SIGNATURE;
}
}
AesCbcDecryptor decryptor;
CHECK(
decryptor.SetKey(reinterpret_cast<const uint8_t*>(decryption_key.data()),
decryption_key.size()));
std::map<std::string, ContentKey> content_keys;
std::vector<std::string> server_renewal_keys;
std::vector<std::string> client_renewal_keys;
// Even if |core_message| is provided, only ODK v16.5 and later support the
// fields needed. If an older API is used, ignore it and use the protobuf
// as if |core_message| was not provided.
if (IsOdkVersionSupported(odk_major_version, odk_minor_version)) {
// Start by extracting the signing key.
const std::string signing_key_encrypted =
ExtractItem(parsed_license.enc_mac_keys, message_str);
const std::string signing_key_iv =
ExtractItem(parsed_license.enc_mac_keys_iv, message_str);
if (!signing_key_encrypted.empty() && !signing_key_iv.empty()) {
std::string unwrapped_signing_key(signing_key_encrypted);
if (!Decrypt(decryptor, signing_key_iv, signing_key_encrypted,
&unwrapped_signing_key)) {
DVLOG(1) << "Invalid parameter: Invalid enc_mac_keys.";
return WB_RESULT_INVALID_PARAMETER;
}
if (unwrapped_signing_key.size() < kSigningKeySizeBytes * 2) {
DVLOG(1) << "Invalid parameter: Invalid signing key.";
return WB_RESULT_INVALID_PARAMETER;
}
server_renewal_keys.push_back(
unwrapped_signing_key.substr(0, kSigningKeySizeBytes));
client_renewal_keys.push_back(unwrapped_signing_key.substr(
kSigningKeySizeBytes, kSigningKeySizeBytes));
}
// Now extract all the content keys.
for (size_t i = 0; i < parsed_license.key_array_length; ++i) {
const OEMCrypto_KeyObject& key = parsed_license.key_array[i];
const std::string key_id = ExtractItem(key.key_id, message_str);
const std::string iv = ExtractItem(key.key_data_iv, message_str);
const std::string wrapped_key = ExtractItem(key.key_data, message_str);
std::string unwrapped_key(wrapped_key);
if (!Decrypt(decryptor, iv, wrapped_key, &unwrapped_key)) {
DVLOG(1) << "Invalid parameter: Invalid key.key_data.";
return WB_RESULT_INVALID_PARAMETER;
}
constexpr size_t kContentKeySizeBytes = 16;
if (unwrapped_key.size() < kContentKeySizeBytes) {
DVLOG(1) << "Invalid parameter: Invalid content key.";
return WB_RESULT_INVALID_PARAMETER;
}
unwrapped_key.resize(kContentKeySizeBytes);
// When the platform is hardware verified, all keys are unlocked and are
// available to be used with either decrypt function. The license server
// adjusts the level returned inside the key control block to handle
// this.
const std::string key_control_block =
ExtractItem(key.key_control, message_str);
content_keys[key_id] =
CreateContentKey(ExtractLevel(key_control_block),
/* is_hw_verified */ false, unwrapped_key);
}
} else {
// Core message not provided or an old version, so extract the keys from
// the protobuf.
video_widevine::License license;
if (!license.ParseFromArray(message, message_size)) {
DVLOG(1) << "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 = IsPlatformHardwareVerified(license);
for (const auto& key : license.key()) {
// If this is not a key we're interested in, skip it as soon as possible.
// Don't even bother unwrapping it.
if (key.type() != KeyContainer::SIGNING &&
key.type() != KeyContainer::CONTENT) {
continue;
}
const std::string wrapped_key = key.key();
std::string unwrapped_key(wrapped_key);
if (!Decrypt(decryptor, 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.
DVLOG(1) << "Invalid parameter: Invalid license.";
return WB_RESULT_INVALID_PARAMETER;
}
if (key.type() == KeyContainer::SIGNING) {
if (unwrapped_key.size() < kSigningKeySizeBytes * 2) {
DVLOG(1) << "Invalid parameter: Invalid signing key.";
return WB_RESULT_INVALID_PARAMETER;
}
server_renewal_keys.push_back(
unwrapped_key.substr(0, kSigningKeySizeBytes));
client_renewal_keys.push_back(
unwrapped_key.substr(kSigningKeySizeBytes, kSigningKeySizeBytes));
} else if (key.type() == KeyContainer::CONTENT) {
constexpr size_t kContentKeySizeBytes = 16;
if (unwrapped_key.size() < kContentKeySizeBytes) {
DVLOG(1) << "Invalid parameter: Invalid content key.";
return WB_RESULT_INVALID_PARAMETER;
}
unwrapped_key.resize(kContentKeySizeBytes);
content_keys[key.id()] =
CreateContentKey(key.level(), is_verified, unwrapped_key);
} else {
// We should have already skipped over this key.
CHECK(false);
}
}
}
DCHECK_EQ(server_renewal_keys.size(), client_renewal_keys.size());
if (server_renewal_keys.size() > 1) {
return WB_RESULT_INVALID_PARAMETER;
}
// Add an empty string so that we can always reference a valid string. By
// adding it last, a valid key will get priority over this fallback value.
server_renewal_keys.push_back("");
client_renewal_keys.push_back("");
// Copy the loaded state over to the white-box instance now that we know we
// have a valid state.
whitebox->server_signing_key.swap(server_renewal_keys[0]);
whitebox->client_signing_key.swap(client_renewal_keys[0]);
whitebox->content_keys.swap(content_keys);
return WB_RESULT_OK;
}
WB_Result WB_License_SignRenewalRequest(const WB_License_Whitebox* whitebox,
const uint8_t* message,
size_t message_size,
uint8_t* signature,
size_t* signature_size) {
if (!whitebox || !message || !signature || !signature_size) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
if (message_size == 0) {
DVLOG(1) << "Invalid parameter: array size 0.";
return WB_RESULT_INVALID_PARAMETER;
}
if (whitebox->content_keys.empty()) {
DVLOG(1) << "Invalid state: missing license.";
return WB_RESULT_INVALID_STATE;
}
if (whitebox->client_signing_key.empty()) {
DVLOG(1) << "Invalid state: license does not support renewals.";
return WB_RESULT_INVALID_STATE;
}
const std::string computed_signature =
widevine::crypto_util::CreateSignatureHmacSha256(
whitebox->client_signing_key,
std::string(message, message + message_size));
if (!widevine::MemCopy(computed_signature.data(), computed_signature.size(),
signature, *signature_size)) {
DVLOG(1) << "Buffer too small: signature needs "
<< computed_signature.size() << ".";
*signature_size = computed_signature.size();
return WB_RESULT_BUFFER_TOO_SMALL;
}
*signature_size = computed_signature.size();
return WB_RESULT_OK;
}
WB_Result WB_License_VerifyRenewalResponse(const WB_License_Whitebox* whitebox,
const uint8_t* message,
size_t message_size,
const uint8_t* signature,
size_t signature_size) {
if (!whitebox || !message || !signature) {
DVLOG(1) << "Invalid parameter: null pointer";
return WB_RESULT_INVALID_PARAMETER;
}
if (message_size == 0) {
DVLOG(1) << "Invalid parameter: array size 0";
return WB_RESULT_INVALID_PARAMETER;
}
if (whitebox->content_keys.empty()) {
DVLOG(1) << "Invalid state: missing license.";
return WB_RESULT_INVALID_STATE;
}
if (whitebox->server_signing_key.empty()) {
DVLOG(1) << "Invalid state: license does not support renewals.";
return WB_RESULT_INVALID_STATE;
}
const std::string computed_signature =
widevine::crypto_util::CreateSignatureHmacSha256(
whitebox->server_signing_key,
std::string(message, message + message_size));
if (signature_size != computed_signature.size()) {
DVLOG(1) << "Invalid parameters: invalid signature size.";
return WB_RESULT_INVALID_PARAMETER;
}
if (computed_signature !=
std::string(signature, signature + signature_size)) {
DVLOG(1) << "Data verification error: signatures do not match.";
return WB_RESULT_INVALID_SIGNATURE;
}
return WB_RESULT_OK;
}
WB_Result WB_License_GetSecretString(const WB_License_Whitebox* whitebox,
WB_CipherMode mode,
const uint8_t* key_id,
size_t key_id_size,
uint8_t* secret_string,
size_t* secret_string_size) {
if (!whitebox || !key_id || !secret_string || !secret_string_size) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
if (whitebox->content_keys.empty()) {
DVLOG(1) << "Invalid state: missing license.";
return WB_RESULT_INVALID_STATE;
}
if (mode != WB_CIPHER_MODE_CTR && mode != WB_CIPHER_MODE_CBC) {
DVLOG(1) << "Invalid parameter: invalid cipher mode.";
return WB_RESULT_INVALID_PARAMETER;
}
if (key_id_size == 0) {
DVLOG(1) << "Invalid parameter: array size 0.";
return WB_RESULT_INVALID_PARAMETER;
}
// The secret string can differ between keys, so we need to make sure that
// the key id is actually a content key.
const ContentKey* content_key = FindKey(whitebox, key_id, key_id_size);
if (content_key == nullptr) {
DVLOG(1) << "Key unavailable: could not find key.";
return WB_RESULT_KEY_UNAVAILABLE;
}
if (!content_key->allow_masked_decrypt) {
DVLOG(1) << "Insufficient security level: key policy does not allow use "
"with MaskedDecrypt().";
return WB_RESULT_INSUFFICIENT_SECURITY_LEVEL;
}
if (!widevine::MemCopy(kSecretStringPattern, kSecretStringPatternSize,
secret_string, *secret_string_size)) {
DVLOG(1) << "Buffer too small: needs " << kSecretStringPatternSize << ".";
*secret_string_size = kSecretStringPatternSize;
return WB_RESULT_BUFFER_TOO_SMALL;
}
*secret_string_size = kSecretStringPatternSize;
return WB_RESULT_OK;
}
WB_Result WB_License_Decrypt(const WB_License_Whitebox* whitebox,
WB_CipherMode mode,
const uint8_t* key_id,
size_t key_id_size,
const uint8_t* input_data,
size_t input_data_size,
const uint8_t* iv,
size_t iv_size,
uint8_t* output_data,
size_t* output_data_size) {
if (!whitebox || !key_id) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
if (whitebox->content_keys.empty()) {
DVLOG(1) << "Invalid state: missing license.";
return WB_RESULT_INVALID_STATE;
}
if (key_id_size == 0) {
DVLOG(1) << "Invalid parameter: array size 0.";
return WB_RESULT_INVALID_PARAMETER;
}
const ContentKey* content_key = FindKey(whitebox, key_id, key_id_size);
if (content_key == nullptr) {
DVLOG(1) << "Key unavailable: could not find key.";
return WB_RESULT_KEY_UNAVAILABLE;
}
if (!content_key->allow_decrypt) {
DVLOG(1) << "Insufficient security level: key policy does not allow use "
"with Decrypt().";
return WB_RESULT_INSUFFICIENT_SECURITY_LEVEL;
}
// DecryptBuffer() will validate the remaining decryption parameters.
return DecryptBuffer(mode, content_key->key, input_data, input_data_size, iv,
iv_size, output_data, output_data_size);
}
WB_Result WB_License_MaskedDecrypt(const WB_License_Whitebox* whitebox,
WB_CipherMode mode,
const uint8_t* key_id,
size_t key_id_size,
const uint8_t* input_data,
size_t input_data_size,
const uint8_t* iv,
size_t iv_size,
uint8_t* masked_output_data,
size_t* masked_output_data_size) {
if (!whitebox || !key_id || !masked_output_data || !masked_output_data_size) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
if (whitebox->content_keys.empty()) {
DVLOG(1) << "Invalid state: missing license.";
return WB_RESULT_INVALID_STATE;
}
if (key_id_size == 0) {
DVLOG(1) << "Invalid parameter: array size 0.";
return WB_RESULT_INVALID_PARAMETER;
}
const ContentKey* content_key = FindKey(whitebox, key_id, key_id_size);
if (content_key == nullptr) {
DVLOG(1) << "Key unavailable: could not find key.";
return WB_RESULT_KEY_UNAVAILABLE;
}
if (!content_key->allow_masked_decrypt) {
DVLOG(1) << "Insufficient security level: key policy does not allow use "
"with MaskedDecrypt().";
return WB_RESULT_INSUFFICIENT_SECURITY_LEVEL;
}
// DecryptBuffer() will validate all the parameters, so just make sure it is
// safe to resize this and let the normal validation process handle anything
// wrong with the output size.
std::vector<uint8_t> output;
if (masked_output_data_size) {
output.resize(*masked_output_data_size);
}
// DecryptBuffer() will validate the remaining decryption parameters and set
// |masked_output_data_size|.
const WB_Result result =
DecryptBuffer(mode, content_key->key, input_data, input_data_size, iv,
iv_size, output.data(), masked_output_data_size);
if (result != WB_RESULT_OK) {
return result;
}
// Now apply the masking function to the data. This logic must be mirrored in
// Unmask().
const uint8_t* mask = kSecretStringPattern;
const size_t mask_size = kSecretStringPatternSize;
for (size_t i = 0; i < output.size(); ++i) {
masked_output_data[i] =
InverseMaskingFunction1(output[i] ^ mask[i % mask_size]);
}
return WB_RESULT_OK;
}
void WB_License_Unmask(const uint8_t* masked_data,
size_t offset,
size_t size,
const uint8_t* secret_string,
size_t secret_string_size,
uint8_t* unmasked_data) {
// No return code, so only check if parameters are valid.
DCHECK(masked_data);
DCHECK(secret_string);
DCHECK(unmasked_data);
for (size_t local_i = 0; local_i < size; local_i++) {
const size_t global_i = offset + local_i;
const uint8_t mask = secret_string[global_i % secret_string_size];
unmasked_data[local_i] = MaskingFunction1(masked_data[global_i]) ^ mask;
}
}