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.
930 lines
34 KiB
C++
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;
|
|
}
|
|
}
|