ODK and Shared Libraries

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.
This commit is contained in:
Aaron Vaage
2020-07-23 16:13:28 -07:00
parent 5d90e8d89b
commit 789377fed2
37 changed files with 1160 additions and 1127 deletions

View File

@@ -34,6 +34,36 @@ cc_library(
"//crypto_utils:aes_ctr_encryptor",
"//crypto_utils:crypto_util",
"//crypto_utils:rsa_key",
"//external:odk",
],
)
# Create a shared library of the whole api. The library will appear in the
# target specific directory under "//bazel-bin".
#
# In order for this to work, the white-box source needs to be in `srcs`. If it
# was included via `deps`, when the library is loaded, the symbols will be
# missing.
cc_binary(
name = "whiteboxapi",
srcs = [
"aead_whitebox_impl.cc",
"license_whitebox_impl.cc",
],
linkshared = True,
deps = [
":memory_util",
"//api:aead_whitebox",
"//api:license_whitebox",
"//api:result",
"//chromium_deps/cdm/keys:dev_certs",
"//chromium_deps/cdm/protos:license_protocol_proto",
"//chromium_deps/third_party/boringssl",
"//crypto_utils:aes_cbc_decryptor",
"//crypto_utils:aes_ctr_encryptor",
"//crypto_utils:crypto_util",
"//crypto_utils:rsa_key",
"//external:odk",
],
)
@@ -75,6 +105,7 @@ cc_test(
deps = [
":license_whitebox",
":test_data",
"//api:license_whitebox_core_message_test",
"//api:license_whitebox_test",
],
)

View File

@@ -15,6 +15,10 @@
#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"
@@ -39,6 +43,145 @@ struct ContentKey {
// 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.
@@ -177,6 +320,7 @@ bool IsPlatformHardwareVerified(const video_widevine::License& license) {
// 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,
@@ -230,7 +374,7 @@ WB_Result WB_License_SignLicenseRequest(const WB_License_Whitebox* whitebox,
}
std::string result;
DCHECK(whitebox->key->GenerateSignature(
CHECK(whitebox->key->GenerateSignature(
std::string(license_request, license_request + license_request_size),
&result));
@@ -246,6 +390,8 @@ WB_Result WB_License_SignLicenseRequest(const WB_License_Whitebox* whitebox,
}
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,
@@ -262,6 +408,13 @@ WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox,
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.
@@ -298,16 +451,8 @@ WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox,
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);
if (!widevine::crypto_util::VerifySignatureHmacSha256(
server_signing_key,
std::string(signature, signature + signature_size),
std::string(message, message + message_size))) {
DVLOG(1) << "Failed to verify signed message.";
return WB_RESULT_INVALID_SIGNATURE;
}
std::string decryption_key = widevine::crypto_util::DeriveKey(
decrypted_session_key, widevine::crypto_util::kWrappingKeyLabel,
@@ -318,61 +463,106 @@ WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox,
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()));
video_widevine::License license;
if (!license.ParseFromArray(message, message_size)) {
DVLOG(1) << "Invalid parameter: Invalid license.";
return WB_RESULT_INVALID_PARAMETER;
}
std::string server_renewal_key;
std::string client_renewal_key;
std::map<std::string, ContentKey> content_keys;
// 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 default values internal our internal policies to enable
// this behaviour.
const bool is_verified = IsPlatformHardwareVerified(license);
// 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;
}
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 (!decryptor.Decrypt(reinterpret_cast<const uint8_t*>(key.iv().data()),
key.iv().size(),
reinterpret_cast<const uint8_t*>(wrapped_key.data()),
wrapped_key.size(), unwrapped_key.data())) {
// 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) {
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_key.begin(), unwrapped_key.end());
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);
} else if (key.type() == KeyContainer::CONTENT) {
constexpr size_t kContentKeySizeBytes = 16;
}
// 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;
@@ -380,39 +570,75 @@ WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox,
unwrapped_key.resize(kContentKeySizeBytes);
ContentKey content_key;
// 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;
}
switch (key.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;
// 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;
}
content_key.allow_decrypt |= is_verified;
content_key.allow_masked_decrypt |= is_verified;
const std::string wrapped_key = key.key();
std::vector<uint8_t> unwrapped_key(wrapped_key.size());
// 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 = std::move(unwrapped_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;
}
content_keys[key.id()] = content_key;
} else {
// We should have already skipped over this key.
CHECK(false);
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);
}
}
}
@@ -662,14 +888,13 @@ WB_Result WB_License_MaskedDecrypt(const WB_License_Whitebox* whitebox,
return result;
}
// Trivial implementation that simply takes the decrypted output and XORs it
// with a fixed pattern. |output|'s size is based on |masked_output_data| so
// we shouldn't need to worry about overflow. This logic must be mirrored in
// 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] = output[i] ^ mask[i % mask_size];
masked_output_data[i] =
InverseMaskingFunction1(output[i] ^ mask[i % mask_size]);
}
return WB_RESULT_OK;
@@ -689,6 +914,6 @@ void WB_License_Unmask(const uint8_t* masked_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] = masked_data[global_i] ^ mask;
unmasked_data[local_i] = MaskingFunction1(masked_data[global_i]) ^ mask;
}
}

View File

@@ -1,11 +0,0 @@
// Copyright 2020 Google LLC. All Rights Reserved.
#include "impl/reference/string_view_util.h"
namespace widevine {
absl::string_view AsStringView(const uint8_t* buffer, size_t buffer_size) {
return absl::string_view(reinterpret_cast<const char*>(buffer), buffer_size);
}
} // namespace widevine

View File

@@ -1,17 +0,0 @@
// Copyright 2020 Google LLC. All Rights Reserved.
#ifndef WHITEBOX_IMPL_REFERENCE_STRING_VIEW_UTIL_H_
#define WHITEBOX_IMPL_REFERENCE_STRING_VIEW_UTIL_H_
#include <stdint.h>
#include "absl/strings/string_view.h"
namespace widevine {
// Create a string view out of a uint8_t array. String view won't create a copy
// copy of the data.
absl::string_view AsStringView(const uint8_t* buffer, size_t buffer_size);
} // namespace widevine
#endif // WHITEBOX_IMPL_REFERENCE_STRING_VIEW_UTIL_H_