In this code drop we introduce the ODK dependency. The reference implementation has been updated to make use of the ODK and the related tests have been included. In addition, we have included an example of how a shared libraries can be created. This will allow make it easier to test and verify different implementations of the API. Most other changes introduce by this code drop were made to clean-up the reference implementation and limit dependencies.
920 lines
34 KiB
C++
920 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/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::vector<uint8_t>* 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(), decrypted->data());
|
|
}
|
|
|
|
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::vector<uint8_t>& 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 = key;
|
|
}
|
|
|
|
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::string server_renewal_key;
|
|
std::string client_renewal_key;
|
|
std::map<std::string, ContentKey> content_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::vector<uint8_t> unwrapped_signing_key(signing_key_encrypted.size());
|
|
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;
|
|
}
|
|
|
|
const std::string signing_key(unwrapped_signing_key.begin(),
|
|
unwrapped_signing_key.end());
|
|
server_renewal_key = signing_key.substr(0, kSigningKeySizeBytes);
|
|
client_renewal_key =
|
|
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::vector<uint8_t> unwrapped_key(wrapped_key.size());
|
|
|
|
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::vector<uint8_t> unwrapped_key(wrapped_key.size());
|
|
|
|
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;
|
|
}
|
|
|
|
const std::string signing_key(unwrapped_key.begin(),
|
|
unwrapped_key.end());
|
|
server_renewal_key = signing_key.substr(0, kSigningKeySizeBytes);
|
|
client_renewal_key =
|
|
signing_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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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_key);
|
|
whitebox->client_signing_key.swap(client_renewal_key);
|
|
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;
|
|
}
|
|
}
|