Files
whitebox/whitebox/reference/impl/license_whitebox_impl.cc
2022-01-27 14:23:17 -08:00

936 lines
33 KiB
C++

// Copyright 2020 Google LLC. All Rights Reserved.
#include "api/license_whitebox.h"
#include <array>
#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/logging.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 "license_protocol.pb.h"
#include "oemcrypto/odk/include/odk.h"
#include "oemcrypto/odk/include/odk_message.h"
#include "oemcrypto/odk/include/odk_structs.h"
#include "oemcrypto/odk/src/odk_serialize.h"
#include "oemcrypto/odk/src/serialization_base.h"
#include "reference/impl/content_key.h"
#include "reference/impl/license_parser.h"
#include "reference/impl/memory_util.h"
#include "reference/impl/odk.h"
#include "reference/impl/odk_license_parser.h"
#include "reference/impl/private_keys.h"
#include "reference/impl/protobuf_license_parser.h"
#include "reference/impl/renewal_key.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;
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));
}
// 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];
}
bool CheckAndUpdateSize(size_t min_size, size_t* out_size) {
const bool good = *out_size >= min_size;
if (!good) {
DVLOG(1) << "Buffer too small: needs " << min_size << ", but was "
<< *out_size << ".";
}
*out_size = min_size;
return good;
}
std::string MakeString(const uint8_t* data, size_t size) {
return std::string(data, data + size);
}
} // namespace
// The white-box type can't be in the namespace as it is declared in the header.
struct WB_License_Whitebox {
// A basic flag to track whether or not we have loaded a license. We do this
// to avoid relying on signing keys and content keys to know if we loaded a
// license.
bool initialized = false;
// CDM key, used for signing license requests.
std::unique_ptr<RsaPrivateKey> signing_key;
// CDM key, used for decrypting the license response.
std::unique_ptr<RsaPrivateKey> encryption_key;
std::unique_ptr<widevine::RenewalKey> renewal_key;
std::map<std::string, widevine::InternalKey> content_keys;
std::map<std::string, widevine::InternalKey> entitlement_keys;
std::vector<widevine::LicenseParser::ProviderKey> provider_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.
//
// Use a different pattern for CBC than for CTR to ensure that mixing them will
// fail. An implementation can use a different string per key and per cipher
// mode.
const uint8_t kCBCSecretStringPattern[] = {
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
};
const uint8_t kCTRSecretStringPattern[] = {
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
};
std::vector<uint8_t> GetSecretStringFor(WB_CipherMode mode) {
return mode == WB_CIPHER_MODE_CBC
? std::vector<uint8_t>(
kCBCSecretStringPattern,
kCBCSecretStringPattern + sizeof(kCBCSecretStringPattern))
: std::vector<uint8_t>(
kCTRSecretStringPattern,
kCTRSecretStringPattern + sizeof(kCTRSecretStringPattern));
}
const widevine::InternalKey* FindKey(const WB_License_Whitebox* whitebox,
const uint8_t* id,
size_t id_size) {
CHECK(whitebox);
CHECK(id);
CHECK_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 uint8_t* key,
const size_t key_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 (!input_data || !iv || !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;
}
// There is no padding, so the output will be the same length as the input.
if (!CheckAndUpdateSize(input_data_size, output_data_size)) {
return WB_RESULT_BUFFER_TOO_SMALL;
}
if (!output_data) {
DVLOG(1) << "Invalid parameter: null pointer.";
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;
}
// If we passed a key to this function, it should be the correct size. If it
// wasn't, we should not have saved the content key.
CHECK(key) << "Missing key data";
CHECK_EQ(key_size, 16u) << "Incorrect key size. Should be 16, but was "
<< key_size;
// 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, key_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, key_size));
// Encrypt and Decrypt for CTR use the same interface.
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;
}
std::unique_ptr<RsaPrivateKey> LoadPrivateKey(
const std::vector<uint8_t>& key_data) {
std::string key_string(key_data.begin(), key_data.end());
return std::unique_ptr<RsaPrivateKey>(RsaPrivateKey::Create(key_string));
}
std::vector<widevine::LicenseParser::ProviderKey> CreateProviderKeys(
const uint8_t* whitebox_init_data,
size_t whitebox_init_data_size) {
std::vector<widevine::LicenseParser::ProviderKey> result;
for (size_t i = 0; i < whitebox_init_data_size / 32; ++i) {
widevine::LicenseParser::ProviderKey provider_key;
provider_key.mask.assign(whitebox_init_data, whitebox_init_data + 16);
provider_key.key.assign(whitebox_init_data + 16, whitebox_init_data + 32);
result.emplace_back(provider_key);
whitebox_init_data += 32;
}
return result;
}
} // namespace
WB_Result WB_License_Create(const uint8_t* whitebox_init_data,
size_t whitebox_init_data_size,
WB_License_Whitebox** whitebox) {
if (whitebox == nullptr) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
// whitebox_init_data is optional.
if (whitebox_init_data_size != 0 && whitebox_init_data == nullptr) {
DVLOG(1) << "Invalid parameter: Missing whitebox_init_data.";
return WB_RESULT_INVALID_PARAMETER;
}
// However, if specified, it must be a multiple of 32 bytes (as each Provider
// Key is a 16-byte mask followed by a 16-byte key).
if (whitebox_init_data_size % 32 != 0) {
DVLOG(1) << "Invalid parameter: whitebox_init_data_size must be a multiple "
"of 32.";
return WB_RESULT_INVALID_PARAMETER;
}
auto signing_key = LoadPrivateKey(widevine::GetSigningPrivateKey());
CHECK(signing_key) << "Failed to load (internal) signing private key.";
auto encryption_key = LoadPrivateKey(widevine::GetEncryptionPrivateKey());
CHECK(encryption_key) << "Failed to load (internal) encryption private key.";
// Should always be non-null on modern compilers
// (https://isocpp.org/wiki/faq/freestore-mgmt).
*whitebox = new WB_License_Whitebox();
(*whitebox)->signing_key.swap(signing_key);
(*whitebox)->encryption_key.swap(encryption_key);
(*whitebox)->provider_keys =
CreateProviderKeys(whitebox_init_data, whitebox_init_data_size);
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 || !signature_size) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
const size_t real_sign_size = whitebox->signing_key->KeySize();
if (!CheckAndUpdateSize(real_sign_size, signature_size)) {
return WB_RESULT_BUFFER_TOO_SMALL;
}
// Only check after checking for short buffer so callers can query the size
// without a buffer.
if (license_request_size == 0) {
DVLOG(1) << "Invalid parameter: array size 0.";
return WB_RESULT_INVALID_PARAMETER;
}
if (!license_request || !signature) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
std::string result;
// Regardless of single-key or dual-key, we always use the signing key when
// signing the license request.
CHECK(whitebox->signing_key->GenerateSignature(
std::string(license_request, license_request + license_request_size),
&result));
CHECK(widevine::MemCopy(result.data(), result.size(), signature,
*signature_size));
return WB_RESULT_OK;
}
WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox,
WB_LicenseKeyMode license_key_mode,
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,
size_t provider_key_id,
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 || 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;
}
const RsaPrivateKey* private_key = nullptr;
switch (license_key_mode) {
case WB_LICENSE_KEY_MODE_SINGLE_KEY:
// When in single-key mode, we assume that the signing key is our only
// key.
private_key = whitebox->signing_key.get();
break;
case WB_LICENSE_KEY_MODE_DUAL_KEY:
private_key = whitebox->encryption_key.get();
break;
default:
CHECK(false) << "Unknown license key mode " << license_key_mode;
break;
}
CHECK(private_key);
// Defer checking the session key size until we know which private key we are
// going to use. This will allow us to verify the session key size using the
// private key (rather than using a fixed number).
if (session_key_size != private_key->KeySize()) {
DVLOG(1) << "Invalid parameter: session key size (" << session_key_size
<< " != " << private_key->KeySize() << ").";
return WB_RESULT_INVALID_PARAMETER;
}
// From talking with Intertrust, they suggested allowing an invalid session
// key to propogate through the system, leading to the message verification
// failing. While we could do that, in the reference, we opt to just return
// the signature error now.
std::string decrypted_session_key;
if (!private_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_SIGNATURE;
}
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;
}
ODKContext odk_context;
if (core_message_size > 0) {
auto result =
GetODKContext(combined_message_str, core_message_size, &odk_context);
if (result != WB_RESULT_OK) {
return result;
}
}
std::unique_ptr<widevine::LicenseParser> parser;
// 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 (odk_context.is_valid && !HasEncryptedKeyControlBlock(odk_context) &&
IsOdkVersionSupported(odk_context.major_version,
odk_context.minor_version)) {
parser.reset(new widevine::OdkLicenseParser);
} else {
parser.reset(new widevine::ProtobufLicenseParser);
}
WB_Result result = parser->Parse(decryption_key, odk_context, message_str,
whitebox->provider_keys, provider_key_id);
if (result != WB_RESULT_OK) {
return result;
}
// Copy the loaded state over to the white-box instance now that we know we
// have a valid state.
auto* renewal_key = parser->GetRenewalKey();
if (renewal_key) {
whitebox->renewal_key.reset(new widevine::RenewalKey(*renewal_key));
}
whitebox->content_keys = parser->GetContentKeys();
whitebox->entitlement_keys = parser->GetEntitlementKeys();
whitebox->initialized = true;
return WB_RESULT_OK;
}
WB_Result WB_License_LoadEntitledContentKey(WB_License_Whitebox* whitebox,
const uint8_t* entitlement_key_id,
size_t entitlement_key_id_size,
const uint8_t* content_key_id,
size_t content_key_id_size,
const uint8_t* iv,
size_t iv_size,
const uint8_t* key_data,
size_t key_data_size) {
if (!whitebox || !key_data || !iv || !entitlement_key_id || !content_key_id) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
if (key_data_size < 16) {
DVLOG(1) << "Invalid parameter: key data too small.";
return WB_RESULT_INVALID_PARAMETER;
}
if (!whitebox->initialized) {
DVLOG(1) << "Invalid state: no license loaded.";
return WB_RESULT_INVALID_STATE;
}
auto it = whitebox->entitlement_keys.find(
MakeString(entitlement_key_id, entitlement_key_id_size));
if (it == whitebox->entitlement_keys.end()) {
DVLOG(1) << "Entitlement key not found.";
return WB_RESULT_KEY_UNAVAILABLE;
}
if (!it->second.is_valid() ||
it->second.type != widevine::KeyType::kEntitlementKey) {
DVLOG(1) << "Entitlement key not valid.";
return WB_RESULT_KEY_UNAVAILABLE;
}
auto new_key_id = MakeString(content_key_id, content_key_id_size);
if (whitebox->content_keys.count(new_key_id) > 0) {
DVLOG(1) << "Content key already exists.";
return WB_RESULT_INVALID_PARAMETER;
}
AesCbcDecryptor decryptor;
if (!decryptor.SetKey(it->second.key.data(), it->second.key.size())) {
DVLOG(1) << "Error setting AES key.";
return WB_RESULT_INVALID_STATE;
}
std::vector<uint8_t> clear_data(key_data_size);
if (!decryptor.Decrypt(iv, iv_size, key_data, key_data_size,
clear_data.data())) {
DVLOG(1) << "Error decrypting content key.";
return WB_RESULT_INVALID_PARAMETER;
}
widevine::InternalKey new_key;
new_key.status = it->second.status;
std::copy(clear_data.begin(), clear_data.begin() + 16, new_key.key.begin());
whitebox->content_keys.emplace(new_key_id, new_key);
return WB_RESULT_OK;
}
WB_Result WB_License_QueryKeyStatus(const WB_License_Whitebox* whitebox,
WB_KeyQueryType type,
const uint8_t* key_id,
size_t key_id_size,
WB_KeyStatus* key_status) {
if (!whitebox || !key_status) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
if (!whitebox->initialized) {
DVLOG(1) << "Invalid state: no license loaded.";
return WB_RESULT_INVALID_STATE;
}
switch (type) {
case WB_KEY_QUERY_TYPE_SIGNING_KEY: {
if (whitebox->renewal_key == nullptr) {
return WB_RESULT_KEY_UNAVAILABLE;
}
*key_status = whitebox->renewal_key->status;
return WB_RESULT_OK;
}
case WB_KEY_QUERY_TYPE_CONTENT_KEY: {
if (key_id == nullptr) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
if (key_id_size == 0) {
DVLOG(1) << "Invalid parameter: array size 0.";
return WB_RESULT_INVALID_PARAMETER;
}
const widevine::InternalKey* content_key =
FindKey(whitebox, key_id, key_id_size);
if (content_key == nullptr) {
return WB_RESULT_KEY_UNAVAILABLE;
}
*key_status = content_key->status;
return WB_RESULT_OK;
}
}
return WB_RESULT_INVALID_PARAMETER; // Unknown query type.
}
static WB_Result WB_License_SignCommon(const WB_License_Whitebox* whitebox,
const uint8_t* message,
size_t message_size,
uint8_t* signature,
size_t* signature_size,
bool sha256) {
if (!whitebox || !message || !signature_size) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
if (!CheckAndUpdateSize(sha256 ? SHA256_DIGEST_LENGTH : SHA_DIGEST_LENGTH,
signature_size)) {
return WB_RESULT_BUFFER_TOO_SMALL;
}
if (!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->initialized) {
DVLOG(1) << "Invalid state: no license loaded.";
return WB_RESULT_INVALID_STATE;
}
if (whitebox->renewal_key == nullptr) {
DVLOG(1) << "Key Unavailable: no signing key in license.";
return WB_RESULT_KEY_UNAVAILABLE;
}
if (whitebox->renewal_key->status != WB_KEY_STATUS_SIGNING_KEY_VALID) {
DVLOG(1) << "Key Unavailable: invalid signing key in license.";
return WB_RESULT_KEY_UNAVAILABLE;
}
auto func = sha256 ? &widevine::crypto_util::CreateSignatureHmacSha256
: &widevine::crypto_util::CreateSignatureHmacSha1;
const std::string computed_signature =
func(std::string(whitebox->renewal_key->client.begin(),
whitebox->renewal_key->client.end()),
std::string(message, message + message_size));
CHECK(widevine::MemCopy(computed_signature.data(), computed_signature.size(),
signature, *signature_size));
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) {
return WB_License_SignCommon(whitebox, message, message_size, signature,
signature_size, /* sha256= */ true);
}
WB_Result WB_License_SignPstReport(const WB_License_Whitebox* whitebox,
const uint8_t* message,
size_t message_size,
uint8_t* signature,
size_t* signature_size) {
return WB_License_SignCommon(whitebox, message, message_size, signature,
signature_size, /* sha256= */ false);
}
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->initialized) {
DVLOG(1) << "Invalid state: no license loaded.";
return WB_RESULT_INVALID_STATE;
}
if (whitebox->renewal_key == nullptr) {
DVLOG(1) << "Key Unavailable: no signing key in license.";
return WB_RESULT_KEY_UNAVAILABLE;
}
if (whitebox->renewal_key->status != WB_KEY_STATUS_SIGNING_KEY_VALID) {
DVLOG(1) << "Key Unavailable: invalid signing key in license.";
return WB_RESULT_KEY_UNAVAILABLE;
}
const std::string computed_signature =
widevine::crypto_util::CreateSignatureHmacSha256(
std::string(whitebox->renewal_key->server.begin(),
whitebox->renewal_key->server.end()),
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_size) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
if (!whitebox->initialized) {
DVLOG(1) << "Invalid state: no license loaded.";
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;
}
const auto secret_pattern = GetSecretStringFor(mode);
if (!CheckAndUpdateSize(secret_pattern.size(), secret_string_size)) {
return WB_RESULT_BUFFER_TOO_SMALL;
}
if (!secret_string) {
DVLOG(1) << "Invalid parameter: null pointer.";
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 auto* 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->is_valid()) {
DVLOG(1) << "Key unavailable: invalid key.";
return WB_RESULT_KEY_UNAVAILABLE;
}
if (!content_key->can_masked_decrypt()) {
DVLOG(1) << "Insufficient security level: key policy does not allow use "
"with MaskedDecrypt().";
return WB_RESULT_INSUFFICIENT_SECURITY_LEVEL;
}
CHECK(widevine::MemCopy(secret_pattern.data(), secret_pattern.size(),
secret_string, *secret_string_size));
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->initialized) {
DVLOG(1) << "Invalid state: no license loaded.";
return WB_RESULT_INVALID_STATE;
}
if (key_id_size == 0) {
DVLOG(1) << "Invalid parameter: array size 0.";
return WB_RESULT_INVALID_PARAMETER;
}
const auto* 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->is_valid()) {
DVLOG(1) << "Key unavailable: invalid key.";
return WB_RESULT_KEY_UNAVAILABLE;
}
if (!content_key->can_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.data(), /* key_size= */ 16u,
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->initialized) {
DVLOG(1) << "Invalid state: no license loaded.";
return WB_RESULT_INVALID_STATE;
}
if (key_id_size == 0) {
DVLOG(1) << "Invalid parameter: array size 0.";
return WB_RESULT_INVALID_PARAMETER;
}
const auto* 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->is_valid()) {
DVLOG(1) << "Key unavailable: invalid key.";
return WB_RESULT_KEY_UNAVAILABLE;
}
if (!content_key->can_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.data(), /* key_size= */ 16u, 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 auto mask = GetSecretStringFor(mode);
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.
CHECK(masked_data);
CHECK(secret_string);
CHECK(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;
}
}