1533 lines
49 KiB
C++
1533 lines
49 KiB
C++
// Copyright 2020 Google LLC. All Rights Reserved.
|
|
|
|
#include "api/license_whitebox.h"
|
|
|
|
#include <array>
|
|
#include <atomic>
|
|
#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_cbc_encryptor.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"
|
|
|
|
#ifdef KEY_TRAP
|
|
#include "api/plain_aes.h"
|
|
#include "api/trap_keys.h"
|
|
#endif
|
|
|
|
namespace {
|
|
using AesCbcDecryptor = widevine::AesCbcDecryptor;
|
|
using AesCbcEncryptor = widevine::AesCbcEncryptor;
|
|
using AesCtrDecryptor = widevine::AesCtrEncryptor;
|
|
using KeyContainer = video_widevine::License_KeyContainer;
|
|
using RsaPrivateKey = widevine::RsaPrivateKey;
|
|
|
|
static std::atomic<bool> sign_license_request_initialized(false);
|
|
static std::atomic<bool> process_license_response_initialized(false);
|
|
|
|
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;
|
|
}
|
|
|
|
#ifndef ALWAYS_DECRYPT_TO_CLEAR
|
|
// 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];
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
|
|
#if defined(HAS_GENERIC_CRYPTO) || defined(HAS_ENTITLEMENT)
|
|
std::string MakeString(const uint8_t* data, size_t size) {
|
|
return std::string(data, data + size);
|
|
}
|
|
#endif
|
|
|
|
struct WB_License_ExportedData_Key {
|
|
std::array<uint8_t, 16> key_id;
|
|
uint8_t key_id_size;
|
|
widevine::InternalKey key;
|
|
};
|
|
struct WB_License_ExportedData {
|
|
uint8_t version;
|
|
bool has_renewal;
|
|
std::array<uint8_t, 32> renewal_client;
|
|
size_t num_content_keys;
|
|
size_t num_entitlement_keys;
|
|
size_t num_generic_keys;
|
|
WB_License_ExportedData_Key keys[0];
|
|
};
|
|
|
|
constexpr const uint8_t kExportedVersion = 1;
|
|
|
|
} // namespace
|
|
|
|
// The white-box type can't be in the namespace as it is declared in the header.
|
|
struct WB_CONCAT_VERSION(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::map<std::string, widevine::InternalKey> generic_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.
|
|
#ifndef ALWAYS_DECRYPT_TO_CLEAR
|
|
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));
|
|
}
|
|
#endif
|
|
|
|
const widevine::InternalKey* FindKey(
|
|
const WB_CONCAT_VERSION(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) {
|
|
#ifdef KEY_TRAP
|
|
std::array<uint8_t, 11 * 16> expanded_key;
|
|
ExpandKey(key, key_size, expanded_key.data());
|
|
for (int i = 0; i < 11; i++) {
|
|
CompareToTrapKeyAndModify(
|
|
/*key_to_compare=*/expanded_key.data() + 16 * i,
|
|
/*trap_key=*/trap_key_post_key_scheduling_multi_expanded.data() +
|
|
16 * i,
|
|
/*modified_trap_key=*/
|
|
trap_key_post_key_scheduling_multi_cbc_modified_expanded.data() +
|
|
16 * i,
|
|
/*key_size=*/16);
|
|
}
|
|
|
|
// ...
|
|
|
|
for (int i = 0; i < 11; i++) {
|
|
CompareToTrapKeyAndModify(
|
|
/*key_to_compare=*/expanded_key.data() + 16 * i,
|
|
/*trap_key=*/trap_key_post_key_scheduling_single_expanded.data() +
|
|
16 * i,
|
|
/*modified_trap_key=*/
|
|
trap_key_post_key_scheduling_single_modified_expanded.data() + 16 * i,
|
|
/*key_size=*/16);
|
|
}
|
|
|
|
PlainAesCbcDecrypt(input_data, input_data_size, iv, expanded_key.data(),
|
|
key_size, output_data, /*is_key_expanded=*/true);
|
|
#else
|
|
AesCbcDecryptor decryptor;
|
|
CHECK(decryptor.SetKey(key, key_size));
|
|
|
|
CHECK(decryptor.Decrypt(iv, iv_size, input_data, input_data_size,
|
|
output_data));
|
|
#endif
|
|
} else if (mode == WB_CIPHER_MODE_CTR) {
|
|
#ifdef KEY_TRAP
|
|
std::array<uint8_t, 11 * 16> expanded_key;
|
|
ExpandKey(key, key_size, expanded_key.data());
|
|
|
|
for (int i = 0; i < 11; i++) {
|
|
CompareToTrapKeyAndModify(
|
|
/*key_to_compare=*/expanded_key.data() + 16 * i,
|
|
/*trap_key=*/trap_key_post_key_scheduling_multi_expanded.data() +
|
|
16 * i,
|
|
/*modified_trap_key=*/
|
|
trap_key_post_key_scheduling_multi_ctr_modified_expanded.data() +
|
|
16 * i,
|
|
/*key_size=*/16);
|
|
}
|
|
|
|
// ...
|
|
|
|
for (int i = 0; i < 11; i++) {
|
|
CompareToTrapKeyAndModify(
|
|
/*key_to_compare=*/expanded_key.data() + 16 * i,
|
|
/*trap_key=*/trap_key_post_key_scheduling_single_expanded.data() +
|
|
16 * i,
|
|
/*modified_trap_key=*/
|
|
trap_key_post_key_scheduling_single_modified_expanded.data() + 16 * i,
|
|
/*key_size=*/16);
|
|
}
|
|
|
|
PlainAesCtrDecrypt(input_data, input_data_size, iv, expanded_key.data(),
|
|
key_size, output_data, /*is_key_expanded=*/true);
|
|
#else
|
|
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));
|
|
#endif
|
|
} 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;
|
|
#ifdef HAS_PROVIDER_KEYS
|
|
for (size_t i = 0; i < whitebox_init_data_size / 32; ++i) {
|
|
widevine::LicenseParser::ProviderKey provider_key;
|
|
memcpy(provider_key.mask.data(), whitebox_init_data, 16);
|
|
memcpy(provider_key.key.data(), whitebox_init_data + 16, 16);
|
|
result.emplace_back(provider_key);
|
|
whitebox_init_data += 32;
|
|
}
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
WB_Result WB_CONCAT_VERSION(WB_License_Create)(
|
|
const uint8_t* whitebox_init_data,
|
|
size_t whitebox_init_data_size,
|
|
WB_CONCAT_VERSION(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_CONCAT_VERSION(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;
|
|
}
|
|
|
|
WB_Result WB_CONCAT_VERSION(WB_License_Import)(
|
|
const uint8_t* buffer,
|
|
size_t buffer_size,
|
|
WB_CONCAT_VERSION(WB_License_Whitebox)** whitebox) {
|
|
#ifdef HAS_IMPORT_EXPORT
|
|
if (whitebox == nullptr) {
|
|
DVLOG(1) << "Invalid parameter: null pointer.";
|
|
return WB_RESULT_INVALID_PARAMETER;
|
|
}
|
|
if (buffer_size == 0 || buffer == nullptr) {
|
|
DVLOG(1) << "Invalid parameter: Missing whitebox_init_data.";
|
|
return WB_RESULT_INVALID_PARAMETER;
|
|
}
|
|
if (buffer_size < sizeof(WB_License_ExportedData)) {
|
|
DVLOG(1) << "Invalid parameter: Buffer too small.";
|
|
return WB_RESULT_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
auto* info = reinterpret_cast<const WB_License_ExportedData*>(buffer);
|
|
if (info->version != kExportedVersion) {
|
|
DVLOG(1) << "Unsupported version of exported data.";
|
|
return WB_RESULT_INVALID_PARAMETER;
|
|
}
|
|
const size_t total_keys = info->num_content_keys +
|
|
info->num_entitlement_keys + info->num_generic_keys;
|
|
if (buffer_size < sizeof(WB_License_ExportedData) +
|
|
(sizeof(WB_License_ExportedData_Key) * total_keys)) {
|
|
DVLOG(1) << "Invalid parameter: Buffer too small.";
|
|
return WB_RESULT_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
*whitebox = new WB_CONCAT_VERSION(WB_License_Whitebox)();
|
|
(*whitebox)->initialized = true;
|
|
|
|
if (info->has_renewal) {
|
|
std::unique_ptr<widevine::RenewalKey> renewal_key{new widevine::RenewalKey};
|
|
renewal_key->status = WB_KEY_STATUS_SIGNING_KEY_VALID;
|
|
renewal_key->client = info->renewal_client;
|
|
(*whitebox)->renewal_key = std::move(renewal_key);
|
|
}
|
|
|
|
for (size_t i = 0; i < total_keys; i++) {
|
|
auto& key = info->keys[i];
|
|
CHECK(key.key_id_size <= sizeof(key.key_id));
|
|
std::string key_id{key.key_id.begin(), key.key_id.data() + key.key_id_size};
|
|
if (i < info->num_content_keys) {
|
|
(*whitebox)->content_keys.emplace(key_id, key.key);
|
|
} else if (i - info->num_content_keys < info->num_entitlement_keys) {
|
|
(*whitebox)->entitlement_keys.emplace(key_id, key.key);
|
|
} else {
|
|
(*whitebox)->generic_keys.emplace(key_id, key.key);
|
|
}
|
|
}
|
|
|
|
return WB_RESULT_OK;
|
|
#else
|
|
return WB_RESULT_NOT_IMPLEMENTED;
|
|
#endif
|
|
}
|
|
|
|
void WB_CONCAT_VERSION(WB_License_Delete)(
|
|
WB_CONCAT_VERSION(WB_License_Whitebox) * whitebox) {
|
|
// Safe to delete nullptr (https://isocpp.org/wiki/faq/freestore-mgmt).
|
|
delete whitebox;
|
|
}
|
|
|
|
WB_Result WB_CONCAT_VERSION(WB_License_ExportKeys)(
|
|
const WB_CONCAT_VERSION(WB_License_Whitebox)* whitebox,
|
|
uint8_t* buffer,
|
|
size_t* buffer_size) {
|
|
#ifdef HAS_IMPORT_EXPORT
|
|
if (!whitebox || !buffer_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;
|
|
}
|
|
|
|
const size_t required_size =
|
|
sizeof(WB_License_ExportedData) +
|
|
sizeof(WB_License_ExportedData_Key) *
|
|
(whitebox->content_keys.size() + whitebox->entitlement_keys.size() +
|
|
whitebox->generic_keys.size());
|
|
if (!CheckAndUpdateSize(required_size, buffer_size) || !buffer) {
|
|
return WB_RESULT_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
auto* info = reinterpret_cast<WB_License_ExportedData*>(buffer);
|
|
info->has_renewal =
|
|
whitebox->renewal_key &&
|
|
whitebox->renewal_key->status == WB_KEY_STATUS_SIGNING_KEY_VALID;
|
|
if (info->has_renewal) {
|
|
info->renewal_client = whitebox->renewal_key->client;
|
|
}
|
|
info->version = kExportedVersion;
|
|
info->num_content_keys = whitebox->content_keys.size();
|
|
info->num_entitlement_keys = whitebox->entitlement_keys.size();
|
|
info->num_generic_keys = whitebox->generic_keys.size();
|
|
size_t i = 0;
|
|
for (const auto& dict : {whitebox->content_keys, whitebox->entitlement_keys,
|
|
whitebox->generic_keys}) {
|
|
for (const auto& pair : dict) {
|
|
auto& key = info->keys[i];
|
|
CHECK(pair.first.size() <= sizeof(key.key_id));
|
|
|
|
memcpy(key.key_id.data(), pair.first.data(), pair.first.size());
|
|
key.key_id_size = pair.first.size();
|
|
key.key = pair.second;
|
|
i++;
|
|
}
|
|
}
|
|
return WB_RESULT_OK;
|
|
#else
|
|
return WB_RESULT_NOT_IMPLEMENTED;
|
|
#endif
|
|
}
|
|
|
|
WB_Result WB_CONCAT_VERSION(WB_License_SignLicenseRequest_Init)() {
|
|
sign_license_request_initialized = true;
|
|
return WB_RESULT_OK;
|
|
}
|
|
|
|
WB_Result WB_CONCAT_VERSION(WB_License_SignLicenseRequest)(
|
|
const WB_CONCAT_VERSION(WB_License_Whitebox) * whitebox,
|
|
const uint8_t* license_request,
|
|
size_t license_request_size,
|
|
uint8_t* signature,
|
|
size_t* signature_size) {
|
|
if (!sign_license_request_initialized) {
|
|
DVLOG(1)
|
|
<< "Must successfully call WB_License_SignLicenseRequest_Init first.";
|
|
return WB_RESULT_INVALID_STATE;
|
|
}
|
|
|
|
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_CONCAT_VERSION(WB_License_ProcessLicenseResponse_Init)() {
|
|
if (!sign_license_request_initialized) {
|
|
return WB_RESULT_INVALID_STATE;
|
|
}
|
|
|
|
process_license_response_initialized = true;
|
|
return WB_RESULT_OK;
|
|
}
|
|
|
|
WB_Result WB_CONCAT_VERSION(WB_License_ProcessLicenseResponse)(
|
|
WB_CONCAT_VERSION(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) {
|
|
if (!process_license_response_initialized) {
|
|
DVLOG(1) << "Must successfully call WB_License_ProcessLicenseResponse_Init "
|
|
"first.";
|
|
return WB_RESULT_INVALID_STATE;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#ifdef ENABLE_LICENSE_PROTOCOL_2_2
|
|
const size_t kSha512Size = 64;
|
|
if (license_request_size != kSha512Size) {
|
|
DVLOG(1) << "Invalid parameter: invalid request size.";
|
|
return WB_RESULT_INVALID_PARAMETER;
|
|
}
|
|
#endif
|
|
|
|
#ifndef HAS_PROVIDER_KEYS
|
|
if (provider_key_id != 0) {
|
|
return WB_RESULT_NOT_IMPLEMENTED;
|
|
}
|
|
#endif
|
|
|
|
// 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) {
|
|
DVLOG(1) << "Failed to get ODK context.";
|
|
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) {
|
|
DVLOG(1) << "Failed to parse license.";
|
|
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();
|
|
#ifdef HAS_ENTITLEMENT
|
|
whitebox->entitlement_keys = parser->GetEntitlementKeys();
|
|
#endif
|
|
#ifdef HAS_GENERIC_CRYPTO
|
|
whitebox->generic_keys = parser->GetGenericKeys();
|
|
#endif
|
|
|
|
whitebox->initialized = true;
|
|
|
|
return WB_RESULT_OK;
|
|
}
|
|
|
|
WB_Result WB_CONCAT_VERSION(WB_License_LoadEntitledContentKey)(
|
|
WB_CONCAT_VERSION(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) {
|
|
#ifndef HAS_ENTITLEMENT
|
|
return WB_RESULT_NOT_IMPLEMENTED;
|
|
#else
|
|
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 (entitlement_key_id_size > 16 || content_key_id_size > 16) {
|
|
DVLOG(1) << "Invalid parameter: key id too big.";
|
|
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;
|
|
#endif
|
|
}
|
|
|
|
WB_Result WB_CONCAT_VERSION(WB_License_RemoveEntitledContentKey)(
|
|
WB_CONCAT_VERSION(WB_License_Whitebox) * whitebox,
|
|
const uint8_t* content_key_id,
|
|
size_t content_key_id_size) {
|
|
#ifndef HAS_ENTITLEMENT
|
|
return WB_RESULT_NOT_IMPLEMENTED;
|
|
#else
|
|
if (!whitebox || !content_key_id) {
|
|
DVLOG(1) << "Invalid parameter: null pointer.";
|
|
return WB_RESULT_INVALID_PARAMETER;
|
|
}
|
|
if (content_key_id_size > 16) {
|
|
DVLOG(1) << "Invalid parameter: key id too big.";
|
|
return WB_RESULT_INVALID_PARAMETER;
|
|
}
|
|
if (!whitebox->initialized) {
|
|
DVLOG(1) << "Invalid state: no license loaded.";
|
|
return WB_RESULT_INVALID_STATE;
|
|
}
|
|
if (whitebox->entitlement_keys.empty()) {
|
|
DVLOG(1) << "Invalid state: can only remove when using entitlement keys.";
|
|
return WB_RESULT_INVALID_STATE;
|
|
}
|
|
|
|
if (whitebox->content_keys.erase(
|
|
MakeString(content_key_id, content_key_id_size)) == 0) {
|
|
DVLOG(1) << "Content key not found.";
|
|
return WB_RESULT_KEY_UNAVAILABLE;
|
|
}
|
|
return WB_RESULT_OK;
|
|
#endif
|
|
}
|
|
|
|
WB_Result WB_CONCAT_VERSION(WB_License_QueryKeyStatus)(
|
|
const WB_CONCAT_VERSION(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 (key_id_size > 16) {
|
|
DVLOG(1) << "Invalid parameter: key id too big.";
|
|
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_GENERIC_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;
|
|
}
|
|
|
|
auto it = whitebox->generic_keys.find({key_id, key_id + key_id_size});
|
|
|
|
if (it == whitebox->generic_keys.end()) {
|
|
return WB_RESULT_KEY_UNAVAILABLE;
|
|
}
|
|
|
|
*key_status = it->second.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_CONCAT_VERSION(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_CONCAT_VERSION(WB_License_SignRenewalRequest)(
|
|
const WB_CONCAT_VERSION(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_CONCAT_VERSION(WB_License_SignPstReport)(
|
|
const WB_CONCAT_VERSION(WB_License_Whitebox) * whitebox,
|
|
const uint8_t* message,
|
|
size_t message_size,
|
|
uint8_t* signature,
|
|
size_t* signature_size) {
|
|
#ifndef HAS_SIGN_PST_REPORT
|
|
return WB_RESULT_NOT_IMPLEMENTED;
|
|
#else
|
|
return WB_License_SignCommon(whitebox, message, message_size, signature,
|
|
signature_size, /* sha256= */ false);
|
|
#endif
|
|
}
|
|
|
|
WB_Result WB_CONCAT_VERSION(WB_License_VerifyRenewalResponse)(
|
|
const WB_CONCAT_VERSION(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_CONCAT_VERSION(WB_License_GetSecretString)(
|
|
const WB_CONCAT_VERSION(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) {
|
|
#ifdef ALWAYS_DECRYPT_TO_CLEAR
|
|
return WB_RESULT_NOT_IMPLEMENTED;
|
|
#else
|
|
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;
|
|
}
|
|
if (key_id_size > 16) {
|
|
DVLOG(1) << "Invalid parameter: key id too big.";
|
|
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_PERMISSIONS;
|
|
}
|
|
|
|
CHECK(widevine::MemCopy(secret_pattern.data(), secret_pattern.size(),
|
|
secret_string, *secret_string_size));
|
|
return WB_RESULT_OK;
|
|
#endif
|
|
}
|
|
|
|
WB_Result WB_CONCAT_VERSION(WB_License_GenericEncrypt)(
|
|
const WB_CONCAT_VERSION(WB_License_Whitebox) * whitebox,
|
|
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) {
|
|
#ifndef HAS_GENERIC_CRYPTO
|
|
return WB_RESULT_NOT_IMPLEMENTED;
|
|
#else
|
|
if (!whitebox || !key_id || !output_data_size) {
|
|
DVLOG(1) << "Invalid parameter: null pointer.";
|
|
return WB_RESULT_INVALID_PARAMETER;
|
|
}
|
|
if (!CheckAndUpdateSize(input_data_size, output_data_size)) {
|
|
return WB_RESULT_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
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;
|
|
}
|
|
if (key_id_size > 16) {
|
|
DVLOG(1) << "Invalid parameter: key id too big.";
|
|
return WB_RESULT_INVALID_PARAMETER;
|
|
}
|
|
|
|
auto it = whitebox->generic_keys.find({key_id, key_id + key_id_size});
|
|
if (it == whitebox->generic_keys.end()) {
|
|
DVLOG(1) << "Generic crypto key not found.";
|
|
return WB_RESULT_KEY_UNAVAILABLE;
|
|
}
|
|
if (!it->second.is_valid()) {
|
|
DVLOG(1) << "Generic crypto key not valid.";
|
|
return WB_RESULT_KEY_UNAVAILABLE;
|
|
}
|
|
if (!(it->second.kcb_flags & WB_KCB_FLAGS_ALLOW_ENCRYPT)) {
|
|
return WB_RESULT_INSUFFICIENT_PERMISSIONS;
|
|
}
|
|
|
|
AesCbcEncryptor encryptor;
|
|
if (!encryptor.SetKey(it->second.key.data(), 16u)) {
|
|
DVLOG(1) << "Error setting AES key.";
|
|
return WB_RESULT_INVALID_STATE;
|
|
}
|
|
if (!encryptor.Encrypt(iv, iv_size, input_data, input_data_size,
|
|
output_data)) {
|
|
DVLOG(1) << "Error encrypting data.";
|
|
return WB_RESULT_INVALID_PARAMETER;
|
|
}
|
|
return WB_RESULT_OK;
|
|
#endif
|
|
}
|
|
|
|
WB_Result WB_CONCAT_VERSION(WB_License_GenericDecrypt)(
|
|
const WB_CONCAT_VERSION(WB_License_Whitebox) * whitebox,
|
|
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) {
|
|
#ifndef HAS_GENERIC_CRYPTO
|
|
return WB_RESULT_NOT_IMPLEMENTED;
|
|
#else
|
|
if (!whitebox || !key_id || !output_data_size) {
|
|
DVLOG(1) << "Invalid parameter: null pointer.";
|
|
return WB_RESULT_INVALID_PARAMETER;
|
|
}
|
|
if (!CheckAndUpdateSize(input_data_size, output_data_size)) {
|
|
return WB_RESULT_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
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;
|
|
}
|
|
if (key_id_size > 16) {
|
|
DVLOG(1) << "Invalid parameter: key id too big.";
|
|
return WB_RESULT_INVALID_PARAMETER;
|
|
}
|
|
|
|
auto it = whitebox->generic_keys.find({key_id, key_id + key_id_size});
|
|
if (it == whitebox->generic_keys.end()) {
|
|
DVLOG(1) << "Generic crypto key not found.";
|
|
return WB_RESULT_KEY_UNAVAILABLE;
|
|
}
|
|
if (!it->second.is_valid()) {
|
|
DVLOG(1) << "Generic crypto key not valid.";
|
|
return WB_RESULT_KEY_UNAVAILABLE;
|
|
}
|
|
if (!(it->second.kcb_flags & WB_KCB_FLAGS_ALLOW_DECRYPT)) {
|
|
return WB_RESULT_INSUFFICIENT_PERMISSIONS;
|
|
}
|
|
|
|
AesCbcDecryptor decryptor;
|
|
if (!decryptor.SetKey(it->second.key.data(), 16u)) {
|
|
DVLOG(1) << "Error setting AES key.";
|
|
return WB_RESULT_INVALID_STATE;
|
|
}
|
|
if (!decryptor.Decrypt(iv, iv_size, input_data, input_data_size,
|
|
output_data)) {
|
|
DVLOG(1) << "Error decrypting data.";
|
|
return WB_RESULT_INVALID_PARAMETER;
|
|
}
|
|
return WB_RESULT_OK;
|
|
#endif
|
|
}
|
|
|
|
WB_Result WB_CONCAT_VERSION(WB_License_GenericSign)(
|
|
const WB_CONCAT_VERSION(WB_License_Whitebox) * whitebox,
|
|
const uint8_t* key_id,
|
|
size_t key_id_size,
|
|
const uint8_t* message,
|
|
size_t message_size,
|
|
uint8_t* output_data,
|
|
size_t* output_data_size) {
|
|
#ifndef HAS_GENERIC_CRYPTO
|
|
return WB_RESULT_NOT_IMPLEMENTED;
|
|
#else
|
|
if (!whitebox || !key_id || !message || !output_data_size) {
|
|
DVLOG(1) << "Invalid parameter: null pointer.";
|
|
return WB_RESULT_INVALID_PARAMETER;
|
|
}
|
|
if (!CheckAndUpdateSize(widevine::crypto_util::kSha256HmacSizeBytes,
|
|
output_data_size)) {
|
|
return WB_RESULT_BUFFER_TOO_SMALL;
|
|
}
|
|
if (!output_data) {
|
|
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;
|
|
}
|
|
if (key_id_size > 16) {
|
|
DVLOG(1) << "Invalid parameter: key id too big.";
|
|
return WB_RESULT_INVALID_PARAMETER;
|
|
}
|
|
|
|
auto it = whitebox->generic_keys.find({key_id, key_id + key_id_size});
|
|
if (it == whitebox->generic_keys.end()) {
|
|
DVLOG(1) << "Generic crypto key not found.";
|
|
return WB_RESULT_KEY_UNAVAILABLE;
|
|
}
|
|
if (!it->second.is_valid()) {
|
|
DVLOG(1) << "Generic crypto key not valid.";
|
|
return WB_RESULT_KEY_UNAVAILABLE;
|
|
}
|
|
if (!(it->second.kcb_flags & WB_KCB_FLAGS_ALLOW_SIGN)) {
|
|
return WB_RESULT_INSUFFICIENT_PERMISSIONS;
|
|
}
|
|
|
|
std::string result = widevine::crypto_util::CreateSignatureHmacSha256(
|
|
MakeString(it->second.key.data(), it->second.key.size()),
|
|
MakeString(message, message_size));
|
|
memcpy(output_data, result.data(), result.size());
|
|
return WB_RESULT_OK;
|
|
#endif
|
|
}
|
|
|
|
WB_Result WB_CONCAT_VERSION(WB_License_GenericVerify)(
|
|
const WB_CONCAT_VERSION(WB_License_Whitebox) * whitebox,
|
|
const uint8_t* key_id,
|
|
size_t key_id_size,
|
|
const uint8_t* message,
|
|
size_t message_size,
|
|
const uint8_t* signature,
|
|
size_t signature_size) {
|
|
#ifndef HAS_GENERIC_CRYPTO
|
|
return WB_RESULT_NOT_IMPLEMENTED;
|
|
#else
|
|
if (!whitebox || !key_id || !message || !signature) {
|
|
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;
|
|
}
|
|
if (key_id_size > 16) {
|
|
DVLOG(1) << "Invalid parameter: key id too big.";
|
|
return WB_RESULT_INVALID_PARAMETER;
|
|
}
|
|
|
|
auto it = whitebox->generic_keys.find({key_id, key_id + key_id_size});
|
|
if (it == whitebox->generic_keys.end()) {
|
|
DVLOG(1) << "Generic crypto key not found.";
|
|
return WB_RESULT_KEY_UNAVAILABLE;
|
|
}
|
|
if (!it->second.is_valid()) {
|
|
DVLOG(1) << "Generic crypto key not valid.";
|
|
return WB_RESULT_KEY_UNAVAILABLE;
|
|
}
|
|
if (!(it->second.kcb_flags & WB_KCB_FLAGS_ALLOW_VERIFY)) {
|
|
return WB_RESULT_INSUFFICIENT_PERMISSIONS;
|
|
}
|
|
|
|
if (!widevine::crypto_util::VerifySignatureHmacSha256(
|
|
MakeString(it->second.key.data(), it->second.key.size()),
|
|
MakeString(signature, signature_size),
|
|
MakeString(message, message_size))) {
|
|
return WB_RESULT_INVALID_SIGNATURE;
|
|
}
|
|
return WB_RESULT_OK;
|
|
#endif
|
|
}
|
|
|
|
WB_Result WB_CONCAT_VERSION(WB_License_Decrypt)(
|
|
const WB_CONCAT_VERSION(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;
|
|
}
|
|
if (key_id_size > 16) {
|
|
DVLOG(1) << "Invalid parameter: key id too big.";
|
|
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_PERMISSIONS;
|
|
}
|
|
|
|
// 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_CONCAT_VERSION(WB_License_MaskedDecrypt)(
|
|
const WB_CONCAT_VERSION(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) {
|
|
#ifdef ALWAYS_DECRYPT_TO_CLEAR
|
|
return WB_RESULT_NOT_IMPLEMENTED;
|
|
#else
|
|
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;
|
|
}
|
|
if (key_id_size > 16) {
|
|
DVLOG(1) << "Invalid parameter: key id too big.";
|
|
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_PERMISSIONS;
|
|
}
|
|
|
|
// 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.at(i % mask.size()));
|
|
}
|
|
|
|
return WB_RESULT_OK;
|
|
#endif
|
|
}
|
|
|
|
void WB_CONCAT_VERSION(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;
|
|
}
|
|
}
|