Add entitlement license support and tests

This commit is contained in:
Jacob Trimble
2022-01-27 14:23:17 -08:00
parent e1454e88ce
commit 9cd251fa23
17 changed files with 480 additions and 104 deletions

View File

@@ -195,6 +195,7 @@ cc_library(
srcs = [ srcs = [
"license_whitebox_create_test.cc", "license_whitebox_create_test.cc",
"license_whitebox_decrypt_test.cc", "license_whitebox_decrypt_test.cc",
"license_whitebox_entitlement_content_key_test.cc",
"license_whitebox_get_secret_string_test.cc", "license_whitebox_get_secret_string_test.cc",
"license_whitebox_key_control_block_test.cc", "license_whitebox_key_control_block_test.cc",
"license_whitebox_license_key_mode.cc", "license_whitebox_license_key_mode.cc",

View File

@@ -3,7 +3,9 @@
#include "api/golden_data.h" #include "api/golden_data.h"
namespace widevine { namespace widevine {
namespace { namespace {
GoldenData::Content CreateContent(const std::vector<uint8_t>& plaintext, GoldenData::Content CreateContent(const std::vector<uint8_t>& plaintext,
const std::vector<uint8_t>& ciphertext, const std::vector<uint8_t>& ciphertext,
const AesIv& iv, const AesIv& iv,
@@ -36,7 +38,9 @@ GoldenData::Content CreateContent(const std::vector<uint8_t>& plaintext,
return content; return content;
} }
} // namespace } // namespace
GoldenData::GoldenData() { GoldenData::GoldenData() {
// Content generated with: // Content generated with:
// openssl aes-128-cbc -e -in data.txt // openssl aes-128-cbc -e -in data.txt
@@ -84,6 +88,51 @@ GoldenData::GoldenData() {
/* software_crypto_key_id= */ {0xFF, 5, 0, 0}, /* software_crypto_key_id= */ {0xFF, 5, 0, 0},
/* software_decode_key_id= */ {0xFF, 6, 0, 0}, /* software_decode_key_id= */ {0xFF, 6, 0, 0},
/* hardware_key_id= */ {0xFF, 7, 0, 0}); /* hardware_key_id= */ {0xFF, 7, 0, 0});
// Key data generated with:
// echo 5ca79528bb76e14bf29c5bc853795a9b |
// xxd -r -ps |
// openssl aes-256-cbc -e
// -K 8e682447b6b46696eb874d1e38467784d849f3ed208f87cc83dbb4e3f357cea3
// -iv 6fc04cd8423d5f660ca045769a200048 |
// xxd -i
entitlement_.entitlement_key.id = GetFreeId();
entitlement_.entitlement_key.key = {
0x8e, 0x68, 0x24, 0x47, 0xb6, 0xb4, 0x66, 0x96,
0xeb, 0x87, 0x4d, 0x1e, 0x38, 0x46, 0x77, 0x84,
0xd8, 0x49, 0xf3, 0xed, 0x20, 0x8f, 0x87, 0xcc,
0x83, 0xdb, 0xb4, 0xe3, 0xf3, 0x57, 0xce, 0xa3,
};
entitlement_.key_data_iv = {
0x6f, 0xc0, 0x4c, 0xd8, 0x42, 0x3d, 0x5f, 0x66,
0x0c, 0xa0, 0x45, 0x76, 0x9a, 0x20, 0x00, 0x48,
};
entitlement_.key_data = {
0xf6, 0xed, 0x76, 0xe4, 0x54, 0x4f, 0x92, 0xd1,
0xb2, 0xf5, 0x68, 0xa0, 0x5e, 0x9c, 0x76, 0x00,
0xca, 0xc5, 0x08, 0xc5, 0x1b, 0xe3, 0x4a, 0xe6,
0xbe, 0x42, 0x40, 0x95, 0x18, 0x72, 0x5e, 0xce,
};
// Content generated with:
// openssl aes-128-ctr -e -in data.txt
// -K 5ca79528bb76e14bf29c5bc853795a9b
// -iv 02565ac40a2d159ff79aa97c97cb3b68 | xxd -i
entitlement_.iv = {
0x02, 0x56, 0x5a, 0xc4, 0x0a, 0x2d, 0x15, 0x9f,
0xf7, 0x9a, 0xa9, 0x7c, 0x97, 0xcb, 0x3b, 0x68,
};
entitlement_.plaintext = {
'T', 'h', 'i', 'r', 't', 'y', '-', 't',
'w', 'o', ' ', 'b', 'y', 't', 'e', 's',
' ', 'o', 'f', ' ', 'r', 'a', 'n', 'd',
'o', 'm', ' ', 'd', 'a', 't', 'a', '.',
};
entitlement_.ciphertext = {
0xaf, 0xcb, 0xd3, 0x42, 0x14, 0x46, 0x3c, 0x3a,
0x38, 0xe8, 0xfd, 0xed, 0x7f, 0x5b, 0x25, 0x78,
0xc9, 0xfc, 0x7b, 0x7d, 0x00, 0xf8, 0x75, 0xef,
0xde, 0xd2, 0xd9, 0xdd, 0x8c, 0xcd, 0x20, 0x49
};
} }
KeyId GoldenData::GetFreeId() { KeyId GoldenData::GetFreeId() {
@@ -91,4 +140,5 @@ KeyId GoldenData::GetFreeId() {
// avoid conflicts. // avoid conflicts.
return {0xAB, 0, 0, next_id_++}; return {0xAB, 0, 0, next_id_++};
} }
} // namespace widevine } // namespace widevine

View File

@@ -29,6 +29,20 @@ class GoldenData {
ContentKeyData software_decode_key; ContentKeyData software_decode_key;
ContentKeyData hardware_key; ContentKeyData hardware_key;
}; };
struct EntitlementContent {
std::vector<uint8_t> ciphertext;
std::vector<uint8_t> plaintext;
// IV used to encrypt the plaintext.
AesIv iv;
// The content key encrypted with the entitlement key.
std::vector<uint8_t> key_data;
// IV used to encrypt the key_data.
AesIv key_data_iv;
EntitlementKeyData entitlement_key;
};
GoldenData(); GoldenData();
@@ -36,11 +50,14 @@ class GoldenData {
const Content& CTRContent() const { return ctr_content_; } const Content& CTRContent() const { return ctr_content_; }
const EntitlementContent& EntitlementContent() const { return entitlement_; }
KeyId GetFreeId(); KeyId GetFreeId();
private: private:
Content cbc_content_; Content cbc_content_;
Content ctr_content_; Content ctr_content_;
struct EntitlementContent entitlement_;
uint8_t next_id_ = 0; uint8_t next_id_ = 0;
}; };

View File

@@ -208,6 +208,50 @@ WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox,
const uint8_t* license_request, const uint8_t* license_request,
size_t license_request_size); size_t license_request_size);
// Loads a content key using a previously loaded entitlement key. The key data
// is a normal 128-bit AES content key. The key data is encrypted with an
// entitlement key that was loaded from the license using 256-bit AES-CBC.
//
// Args:
// whitebox (in) : An initialized white-box instance.
//
// key_data (in) : The content key data.
//
// key_data_size (in) : The number of bytes in |key_data|.
//
// iv (in) : The initialization vector for the encrypted key data.
//
// iv_size (in) : The number of bytes in |iv|.
//
// entitlement_key_id (in) : The ID of the entitlement key the key data is
// encrypted with.
//
// entitlement_key_id_size (in) : The number of bytes in |entitlement_key_id|.
//
// content_key_id (in) : The ID of the new content key.
//
// content_key_id_size (in) : The number of bytes in |content_key_id|.
//
// Returns:
// WB_RESULT_OK if the key was loaded successfully.
//
// WB_INVALID_PARAMETER if any of the pointers are null or if there is another
// content key loaded with the same key ID.
//
// WB_RESULT_KEY_UNAVAILABLE if the requested entitlement key was not in the
// license.
//
// WB_RESULT_INVALID_STATE if |whitebox| had not loaded a license.
WB_Result WB_License_LoadEntitledContentKey(WB_License_Whitebox* whitebox,
const uint8_t* entitlement_key_id,
size_t entitlement_key_id_size,
const uint8_t* content_key_id,
size_t content_key_id_size,
const uint8_t* iv,
size_t iv_size,
const uint8_t* key_data,
size_t key_data_size);
// Queries the white-box to know whether or not the white-box loaded a specific // Queries the white-box to know whether or not the white-box loaded a specific
// key and to know what operations can be performed with that key. // key and to know what operations can be performed with that key.
// //

View File

@@ -0,0 +1,77 @@
// Copyright 2021 Google LLC. All Rights Reserved.
#include "api/license_whitebox.h"
#include <string>
#include <vector>
#include "api/golden_data.h"
#include "api/license_whitebox_test_base.h"
#include "api/test_license_builder.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace widevine {
class LicenseWhiteboxEntitlementContentKeyTest
: public LicenseWhiteboxTestBase {
protected:
void SetUp() {
LicenseWhiteboxTestBase::SetUp();
server_ = TestServer::CreateDualKey();
TestLicenseBuilder builder;
builder.AddSigningKey(TestLicenseBuilder::DefaultSigningKey());
builder.AddEntitlementKey(
golden_data_.EntitlementContent().entitlement_key);
builder.Build(*server_, &license_);
}
std::unique_ptr<TestServer> server_;
License license_;
};
TEST_F(LicenseWhiteboxEntitlementContentKeyTest, Decrypt) {
ASSERT_EQ(WB_License_ProcessLicenseResponse(
whitebox_, WB_LICENSE_KEY_MODE_DUAL_KEY,
license_.core_message.data(), license_.core_message.size(),
license_.message.data(), license_.message.size(),
license_.signature.data(), license_.signature.size(),
license_.session_key.data(), license_.session_key.size(),
kNoProviderKeyId, license_.request.data(),
license_.request.size()),
WB_RESULT_OK);
const KeyId key_id = golden_data_.GetFreeId();
auto& content = golden_data_.EntitlementContent();
ASSERT_EQ(WB_License_LoadEntitledContentKey(
whitebox_,
content.entitlement_key.id.data(),
content.entitlement_key.id.size(),
key_id.data(),
key_id.size(),
content.key_data_iv.data(),
content.key_data_iv.size(),
content.key_data.data(),
content.key_data.size()),
WB_RESULT_OK);
std::vector<uint8_t> decrypted(content.plaintext.size());
size_t decrypted_size = decrypted.size();
ASSERT_EQ(WB_License_Decrypt(
whitebox_,
WB_CIPHER_MODE_CTR,
key_id.data(),
key_id.size(),
content.ciphertext.data(),
content.ciphertext.size(),
content.iv.data(),
content.iv.size(),
&decrypted[0],
&decrypted_size),
WB_RESULT_OK);
decrypted.resize(decrypted_size);
EXPECT_EQ(decrypted, content.plaintext);
}
} // namespace widevine

View File

@@ -43,6 +43,14 @@ class LicenseWhiteboxProcessLicenseResponseTest
builder.Build(*server_, &license_); builder.Build(*server_, &license_);
} }
void UseLicenseWithEntitlementKey() {
TestLicenseBuilder builder;
builder.AddSigningKey(TestLicenseBuilder::DefaultSigningKey());
builder.AddEntitlementKey(
golden_data_.EntitlementContent().entitlement_key);
builder.Build(*server_, &license_);
}
std::unique_ptr<TestServer> server_; std::unique_ptr<TestServer> server_;
License license_; License license_;
}; };
@@ -91,6 +99,21 @@ TEST_F(LicenseWhiteboxProcessLicenseResponseTest,
WB_RESULT_OK); WB_RESULT_OK);
} }
TEST_F(LicenseWhiteboxProcessLicenseResponseTest,
SuccessWithEntitlementKey) {
UseLicenseWithEntitlementKey();
ASSERT_EQ(WB_License_ProcessLicenseResponse(
whitebox_, WB_LICENSE_KEY_MODE_DUAL_KEY,
license_.core_message.data(), license_.core_message.size(),
license_.message.data(), license_.message.size(),
license_.signature.data(), license_.signature.size(),
license_.session_key.data(), license_.session_key.size(),
kNoProviderKeyId, license_.request.data(),
license_.request.size()),
WB_RESULT_OK);
}
// If there were multiple signing keys (this can only happen if a license server // If there were multiple signing keys (this can only happen if a license server
// was manipulating the license and we are using protobuf parsing), the // was manipulating the license and we are using protobuf parsing), the
// implementation is free to pick either key. // implementation is free to pick either key.

View File

@@ -14,6 +14,7 @@ using KeyId = std::array<uint8_t, 4>;
// AES keys can only be 16 bytes. // AES keys can only be 16 bytes.
using AesKey = std::array<uint8_t, 16>; using AesKey = std::array<uint8_t, 16>;
using Aes256Key = std::array<uint8_t, 32>;
// AES IV can only be 16 bytes. // AES IV can only be 16 bytes.
using AesIv = std::array<uint8_t, 16>; using AesIv = std::array<uint8_t, 16>;
@@ -38,6 +39,14 @@ struct ContentKeyData {
SecurityLevel level; SecurityLevel level;
AesKey key; AesKey key;
}; };
struct EntitlementKeyData {
// The unique key id for this key. Any instance with this id should contain
// the same level and key as this.
KeyId id;
Aes256Key key;
};
} // namespace widevine } // namespace widevine
#endif // WHITEBOX_API_TEST_KEY_TYPES_H_ #endif // WHITEBOX_API_TEST_KEY_TYPES_H_

View File

@@ -364,6 +364,24 @@ void AddContentKeyToContainer(const ContentKeyData& key_data,
} }
} }
void AddEntitlementKeyToContainer(
const EntitlementKeyData& key_data, const std::string& container_key,
video_widevine::License_KeyContainer* container) {
container->set_type(video_widevine::License_KeyContainer_KeyType_ENTITLEMENT);
container->set_id(key_data.id.data(), key_data.id.size());
// To avoid having to define a key iv for each key, derive a key iv from the
// key. This will allows us to have a different IVs between keys but keep it
// deterministic.
const auto key_iv = DeriveIV(key_data.key);
container->set_iv(key_iv);
std::vector<uint8_t> key(key_data.key.begin(), key_data.key.end());
auto encrypted_key = Encrypt(container_key, key_iv, key);
container->set_key(encrypted_key);
}
void AddSigningKeyToContainer(const TestLicenseBuilder::SigningKey& key_data, void AddSigningKeyToContainer(const TestLicenseBuilder::SigningKey& key_data,
const TestLicenseBuilder::Settings& settings, const TestLicenseBuilder::Settings& settings,
const std::string& container_key, const std::string& container_key,
@@ -457,6 +475,10 @@ void TestLicenseBuilder::AddContentKey(const ContentKeyData& key) {
content_keys_.push_back(key); content_keys_.push_back(key);
} }
void TestLicenseBuilder::AddEntitlementKey(const EntitlementKeyData& key) {
entitlement_keys_.push_back(key);
}
void TestLicenseBuilder::AddOperatorSessionKey(const KeyId& id) { void TestLicenseBuilder::AddOperatorSessionKey(const KeyId& id) {
operator_session_keys_.push_back(id); operator_session_keys_.push_back(id);
} }
@@ -504,10 +526,16 @@ void TestLicenseBuilder::Build(const TestServer& server,
response.add_key()); response.add_key());
} }
// Cannot have both content keys and entitlement keys.
CHECK(content_keys_.empty() || entitlement_keys_.empty());
for (const auto& key : content_keys_) { for (const auto& key : content_keys_) {
AddContentKeyToContainer(key, settings_, container_key_, AddContentKeyToContainer(key, settings_, container_key_,
response.add_key()); response.add_key());
} }
for (const auto& key : entitlement_keys_) {
AddEntitlementKeyToContainer(key, container_key_, response.add_key());
}
for (const auto& key : operator_session_keys_) { for (const auto& key : operator_session_keys_) {
AddOperatorSessionKeyToContainer(key, response.add_key()); AddOperatorSessionKeyToContainer(key, response.add_key());

View File

@@ -119,6 +119,8 @@ class TestLicenseBuilder {
void AddContentKey(const ContentKeyData& key); void AddContentKey(const ContentKeyData& key);
void AddEntitlementKey(const EntitlementKeyData& key);
// The key id will matter as we will need to reference it, but the key won't // The key id will matter as we will need to reference it, but the key won't
// matter since we are only using it as a means to verify that a non-content // matter since we are only using it as a means to verify that a non-content
// key can't be used as a content key. // key can't be used as a content key.
@@ -145,6 +147,7 @@ class TestLicenseBuilder {
Settings settings_; Settings settings_;
std::vector<ContentKeyData> content_keys_; std::vector<ContentKeyData> content_keys_;
std::vector<EntitlementKeyData> entitlement_keys_;
std::vector<SigningKey> signing_keys_; std::vector<SigningKey> signing_keys_;
std::vector<KeyId> operator_session_keys_; std::vector<KeyId> operator_session_keys_;
}; };

View File

@@ -12,10 +12,17 @@
namespace widevine { namespace widevine {
struct ContentKey { enum class KeyType {
kInvalid,
kContentKey,
kEntitlementKey,
};
struct InternalKey {
// This is the status will be returned in |WB_License_QueryKeyStatus()|. // This is the status will be returned in |WB_License_QueryKeyStatus()|.
WB_KeyStatus status = WB_KEY_STATUS_INVALID; WB_KeyStatus status = WB_KEY_STATUS_INVALID;
std::array<uint8_t, 16u> key; KeyType type = KeyType::kInvalid;
std::array<uint8_t, 32u> key;
// These are the permission flags that will be used internally to check if // These are the permission flags that will be used internally to check if
// we can use a key. // we can use a key.
@@ -38,11 +45,6 @@ struct ContentKey {
} }
}; };
ContentKey CreateContentKey(
video_widevine::License_KeyContainer_SecurityLevel level,
bool is_hw_verified,
const std::string& key);
} // namespace widevine } // namespace widevine
#endif // WHITEBOX_REFERENCE_IMPL_CONTENT_KEY_H_ #endif // WHITEBOX_REFERENCE_IMPL_CONTENT_KEY_H_

View File

@@ -4,6 +4,7 @@
#include "base/check.h" #include "base/check.h"
#include "base/check_op.h" #include "base/check_op.h"
#include "base/logging.h"
#include "crypto_utils/aes_cbc_decryptor.h" #include "crypto_utils/aes_cbc_decryptor.h"
#include "crypto_utils/crypto_util.h" #include "crypto_utils/crypto_util.h"
@@ -16,7 +17,6 @@ bool LicenseParser::Decrypt(const std::string& key,
CHECK_EQ(key.size(), 16u); CHECK_EQ(key.size(), 16u);
CHECK_EQ(iv.size(), 16u); CHECK_EQ(iv.size(), 16u);
CHECK(decrypted); CHECK(decrypted);
CHECK_GE(decrypted->size(), encrypted.size());
decrypted->resize(encrypted.size()); decrypted->resize(encrypted.size());
@@ -30,18 +30,18 @@ bool LicenseParser::Decrypt(const std::string& key,
reinterpret_cast<uint8_t*>(&decrypted->front())); reinterpret_cast<uint8_t*>(&decrypted->front()));
} }
bool LicenseParser::UnwrapContentKey( bool LicenseParser::UnwrapKey(
const std::string& wrapped_content_key, const std::string& wrapped_key,
const std::vector<ProviderKey>& provider_keys, const std::vector<ProviderKey>& provider_keys,
size_t provider_key_id, size_t provider_key_id,
const std::string& key_decryption_key, const std::string& key_decryption_key,
const std::string& key_decryption_key_iv, const std::string& key_decryption_key_iv,
std::string* unwrapped_content_key) { std::string* unwrapped_key) {
const bool provider_key_id_valid = const bool provider_key_id_valid =
(provider_key_id >= 1 && provider_key_id <= provider_keys.size()); (provider_key_id >= 1 && provider_key_id <= provider_keys.size());
// If |provider_key_id| is used and valid, then start by unmasking it. // If |provider_key_id| is used and valid, then start by unmasking it.
std::string key(wrapped_content_key); std::string key = wrapped_key;
if (provider_key_id_valid) { if (provider_key_id_valid) {
const auto& mask = provider_keys[provider_key_id - 1].mask; const auto& mask = provider_keys[provider_key_id - 1].mask;
for (size_t i = 0; i < key.size(); ++i) for (size_t i = 0; i < key.size(); ++i)
@@ -49,70 +49,70 @@ bool LicenseParser::UnwrapContentKey(
} }
// Now decrypt the key using the Key Encryption Key. // Now decrypt the key using the Key Encryption Key.
std::string unwrapped_key(key); std::string intermediate_key;
if (!Decrypt(key_decryption_key, key_decryption_key_iv, key, if (!Decrypt(key_decryption_key, key_decryption_key_iv, key,
&unwrapped_key)) { &intermediate_key)) {
DVLOG(1) << "Failed to decrypt content key using KEK."; DVLOG(1) << "Failed to decrypt content key using KEK.";
return false; return false;
} }
// If |provider_key_id| not used, simply return the key decrypted so far. // If |provider_key_id| not used, simply return the key decrypted so far.
if (!provider_key_id_valid) { if (!provider_key_id_valid) {
unwrapped_content_key->swap(unwrapped_key); unwrapped_key->swap(intermediate_key);
return true; return true;
} }
// |provider_key_id| is used, so decrypt the unwrapped key using the // |provider_key_id| is used, so decrypt the unwrapped key using the
// appropriate provider key. // appropriate provider key.
std::string final_key(unwrapped_key); std::string final_key;
const auto& provider_key = provider_keys[provider_key_id - 1].key; const auto& provider_key = provider_keys[provider_key_id - 1].key;
const std::string no_iv(16, 0); const std::string no_iv(16, 0);
if (!Decrypt(provider_key, no_iv, unwrapped_key, &final_key)) { if (!Decrypt(provider_key, no_iv, intermediate_key, &final_key)) {
DVLOG(1) << "Failed to decrypt content key using Provider Key."; DVLOG(1) << "Failed to decrypt content key using Provider Key.";
return false; return false;
} }
unwrapped_content_key->swap(final_key); unwrapped_key->swap(final_key);
return true; return true;
} }
ContentKey LicenseParser::CreateContentKey( WB_KeyStatus LicenseParser::GetKeyStatus(
video_widevine::License_KeyContainer_SecurityLevel level, video_widevine::License_KeyContainer_SecurityLevel level,
bool is_hw_verified, bool is_hw_verified) {
const std::string& key) { // If the device is hardware verified, then we can override the status to
ContentKey content_key; // allow masked decrypt and decrypt.
if (is_hw_verified) {
return WB_KEY_STATUS_CONTENT_KEY_DECRYPT;
}
switch (level) { switch (level) {
case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO: case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO:
content_key.status = WB_KEY_STATUS_CONTENT_KEY_DECRYPT; return WB_KEY_STATUS_CONTENT_KEY_DECRYPT;
break;
case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_DECODE: case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_DECODE:
content_key.status = WB_KEY_STATUS_CONTENT_KEY_MASKED_DECRYPT; return WB_KEY_STATUS_CONTENT_KEY_MASKED_DECRYPT;
break;
default: default:
// For example, this could be a hardware key - a valid key but can't be // For example, this could be a hardware key - a valid key but can't be
// used by the CDM. However, this may get override later if the device is // used by the CDM. However, this may get override later if the device is
// hardware verified. // hardware verified.
content_key.status = WB_KEY_STATUS_CONTENT_KEY_VALID; return WB_KEY_STATUS_CONTENT_KEY_VALID;
break;
} }
}
// If the device is hardware verified, then we can override the status to InternalKey LicenseParser::CreateInternalKey(
// allow masked decrypt and decrypt. KeyType key_type, video_widevine::License_KeyContainer_SecurityLevel level,
if (is_hw_verified) { bool is_hw_verified, const std::string& key) {
content_key.status = WB_KEY_STATUS_CONTENT_KEY_DECRYPT; InternalKey internal_key;
} internal_key.type = key_type;
internal_key.status = GetKeyStatus(level, is_hw_verified);
// Unless we are going to use the key, we don't want to save this key as it // 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 // will only risk exposing it. We only have an entry for it so we can handle
// errors correctly. // errors correctly.
if (content_key.can_decrypt() || content_key.can_masked_decrypt()) { if (internal_key.can_decrypt() || internal_key.can_masked_decrypt()) {
CHECK_EQ(key.size(), content_key.key.size()); CHECK_LE(key.size(), internal_key.key.size());
std::copy(key.begin(), key.end(), content_key.key.begin()); std::copy(key.begin(), key.end(), internal_key.key.begin());
} }
return content_key; return internal_key;
} }
} // namespace widevine } // namespace widevine

View File

@@ -39,7 +39,9 @@ class LicenseParser {
// If there is no renewal key, then `nullptr` should be returned. // If there is no renewal key, then `nullptr` should be returned.
virtual const widevine::RenewalKey* GetRenewalKey() const = 0; virtual const widevine::RenewalKey* GetRenewalKey() const = 0;
virtual const std::map<std::string, ContentKey>& GetContentKeys() const = 0; virtual const std::map<std::string, InternalKey>& GetContentKeys() const = 0;
virtual const std::map<std::string, InternalKey>&
GetEntitlementKeys() const = 0;
protected: protected:
static bool Decrypt(const std::string& key, static bool Decrypt(const std::string& key,
@@ -47,26 +49,32 @@ class LicenseParser {
const std::string& encrypted, const std::string& encrypted,
std::string* decrypted); std::string* decrypted);
// Unwrap key |wrapped_content_key| using |provider_key_id| and // Unwrap key |wrapped_key| using |provider_key_id| and
// |key_decryption_key|, as necessary. Returns true and // |key_decryption_key|, as necessary. Returns true and
// |unwrapped_content_key| is updated on success, false otherwise. // |unwrapped_content_key| is updated on success, false otherwise.
static bool UnwrapContentKey(const std::string& wrapped_content_key, static bool UnwrapKey(const std::string& wrapped_key,
const std::vector<ProviderKey>& provider_keys, const std::vector<ProviderKey>& provider_keys,
size_t provider_key_id, size_t provider_key_id,
const std::string& key_decryption_key, const std::string& key_decryption_key,
const std::string& key_decryption_key_iv, const std::string& key_decryption_key_iv,
std::string* unwrapped_content_key); std::string* unwrapped_key);
// Creates and returns a ContentKey based on the values provided. // Creates and returns a InternalKey based on the values provided.
// |level| determines whether decrypt or masked_decrypt is allowed. // |level| determines whether decrypt or masked_decrypt is allowed.
// |is_hw_verified|, if set, overrides |level| so that both decrypt and // |is_hw_verified|, if set, overrides |level| so that both decrypt and
// masked_decrypt is allowed. |Key| is the decryption key, and is only // masked_decrypt is allowed. |Key| is the decryption key, and is only
// returned in ContentKey if decrypt or masked_decrypt is allowed. // returned in InternalKey if decrypt or masked_decrypt is allowed.
// Otherwise |key| is dropped. // Otherwise |key| is dropped.
static ContentKey CreateContentKey( static InternalKey CreateInternalKey(
KeyType key_type,
video_widevine::License_KeyContainer_SecurityLevel level, video_widevine::License_KeyContainer_SecurityLevel level,
bool is_hw_verified, bool is_hw_verified,
const std::string& key); const std::string& key);
static WB_KeyStatus GetKeyStatus(
video_widevine::License_KeyContainer_SecurityLevel level,
bool is_hw_verified);
}; };
} // namespace widevine } // namespace widevine

View File

@@ -91,6 +91,10 @@ bool CheckAndUpdateSize(size_t min_size, size_t* out_size) {
return good; return good;
} }
std::string MakeString(const uint8_t* data, size_t size) {
return std::string(data, data + size);
}
} // namespace } // namespace
// The white-box type can't be in the namespace as it is declared in the header. // The white-box type can't be in the namespace as it is declared in the header.
@@ -107,7 +111,8 @@ struct WB_License_Whitebox {
std::unique_ptr<widevine::RenewalKey> renewal_key; std::unique_ptr<widevine::RenewalKey> renewal_key;
std::map<std::string, widevine::ContentKey> content_keys; std::map<std::string, widevine::InternalKey> content_keys;
std::map<std::string, widevine::InternalKey> entitlement_keys;
std::vector<widevine::LicenseParser::ProviderKey> provider_keys; std::vector<widevine::LicenseParser::ProviderKey> provider_keys;
}; };
@@ -140,9 +145,9 @@ std::vector<uint8_t> GetSecretStringFor(WB_CipherMode mode) {
kCTRSecretStringPattern + sizeof(kCTRSecretStringPattern)); kCTRSecretStringPattern + sizeof(kCTRSecretStringPattern));
} }
const widevine::ContentKey* FindKey(const WB_License_Whitebox* whitebox, const widevine::InternalKey* FindKey(const WB_License_Whitebox* whitebox,
const uint8_t* id, const uint8_t* id,
size_t id_size) { size_t id_size) {
CHECK(whitebox); CHECK(whitebox);
CHECK(id); CHECK(id);
CHECK_GT(id_size, 0u); CHECK_GT(id_size, 0u);
@@ -489,12 +494,74 @@ WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox,
} }
whitebox->content_keys = parser->GetContentKeys(); whitebox->content_keys = parser->GetContentKeys();
whitebox->entitlement_keys = parser->GetEntitlementKeys();
whitebox->initialized = true; whitebox->initialized = true;
return WB_RESULT_OK; return WB_RESULT_OK;
} }
WB_Result WB_License_LoadEntitledContentKey(WB_License_Whitebox* whitebox,
const uint8_t* entitlement_key_id,
size_t entitlement_key_id_size,
const uint8_t* content_key_id,
size_t content_key_id_size,
const uint8_t* iv,
size_t iv_size,
const uint8_t* key_data,
size_t key_data_size) {
if (!whitebox || !key_data || !iv || !entitlement_key_id || !content_key_id) {
DVLOG(1) << "Invalid parameter: null pointer.";
return WB_RESULT_INVALID_PARAMETER;
}
if (key_data_size < 16) {
DVLOG(1) << "Invalid parameter: key data too small.";
return WB_RESULT_INVALID_PARAMETER;
}
if (!whitebox->initialized) {
DVLOG(1) << "Invalid state: no license loaded.";
return WB_RESULT_INVALID_STATE;
}
auto it = whitebox->entitlement_keys.find(
MakeString(entitlement_key_id, entitlement_key_id_size));
if (it == whitebox->entitlement_keys.end()) {
DVLOG(1) << "Entitlement key not found.";
return WB_RESULT_KEY_UNAVAILABLE;
}
if (!it->second.is_valid() ||
it->second.type != widevine::KeyType::kEntitlementKey) {
DVLOG(1) << "Entitlement key not valid.";
return WB_RESULT_KEY_UNAVAILABLE;
}
auto new_key_id = MakeString(content_key_id, content_key_id_size);
if (whitebox->content_keys.count(new_key_id) > 0) {
DVLOG(1) << "Content key already exists.";
return WB_RESULT_INVALID_PARAMETER;
}
AesCbcDecryptor decryptor;
if (!decryptor.SetKey(it->second.key.data(), it->second.key.size())) {
DVLOG(1) << "Error setting AES key.";
return WB_RESULT_INVALID_STATE;
}
std::vector<uint8_t> clear_data(key_data_size);
if (!decryptor.Decrypt(iv, iv_size, key_data, key_data_size,
clear_data.data())) {
DVLOG(1) << "Error decrypting content key.";
return WB_RESULT_INVALID_PARAMETER;
}
widevine::InternalKey new_key;
new_key.status = it->second.status;
std::copy(clear_data.begin(), clear_data.begin() + 16, new_key.key.begin());
whitebox->content_keys.emplace(new_key_id, new_key);
return WB_RESULT_OK;
}
WB_Result WB_License_QueryKeyStatus(const WB_License_Whitebox* whitebox, WB_Result WB_License_QueryKeyStatus(const WB_License_Whitebox* whitebox,
WB_KeyQueryType type, WB_KeyQueryType type,
const uint8_t* key_id, const uint8_t* key_id,
@@ -531,7 +598,7 @@ WB_Result WB_License_QueryKeyStatus(const WB_License_Whitebox* whitebox,
return WB_RESULT_INVALID_PARAMETER; return WB_RESULT_INVALID_PARAMETER;
} }
const widevine::ContentKey* content_key = const widevine::InternalKey* content_key =
FindKey(whitebox, key_id, key_id_size); FindKey(whitebox, key_id, key_id_size);
if (content_key == nullptr) { if (content_key == nullptr) {
@@ -772,7 +839,7 @@ WB_Result WB_License_Decrypt(const WB_License_Whitebox* whitebox,
} }
// DecryptBuffer() will validate the remaining decryption parameters. // DecryptBuffer() will validate the remaining decryption parameters.
return DecryptBuffer(mode, content_key->key.data(), content_key->key.size(), return DecryptBuffer(mode, content_key->key.data(), /* key_size= */ 16u,
input_data, input_data_size, iv, iv_size, output_data, input_data, input_data_size, iv, iv_size, output_data,
output_data_size); output_data_size);
} }
@@ -831,7 +898,7 @@ WB_Result WB_License_MaskedDecrypt(const WB_License_Whitebox* whitebox,
// DecryptBuffer() will validate the remaining decryption parameters and set // DecryptBuffer() will validate the remaining decryption parameters and set
// |masked_output_data_size|. // |masked_output_data_size|.
const WB_Result result = DecryptBuffer( const WB_Result result = DecryptBuffer(
mode, content_key->key.data(), content_key->key.size(), input_data, mode, content_key->key.data(), /* key_size= */ 16u, input_data,
input_data_size, iv, iv_size, output.data(), masked_output_data_size); input_data_size, iv, iv_size, output.data(), masked_output_data_size);
if (result != WB_RESULT_OK) { if (result != WB_RESULT_OK) {

View File

@@ -80,10 +80,8 @@ WB_Result OdkLicenseParser::Parse(const std::string& decryption_key,
decryption_key, signing_key_encrypted, signing_key_iv)); decryption_key, signing_key_encrypted, signing_key_iv));
} }
// Extract the content keys.
for (size_t i = 0; i < odk_context.license.key_array_length; ++i) { for (size_t i = 0; i < odk_context.license.key_array_length; ++i) {
const OEMCrypto_KeyObject& key = odk_context.license.key_array[i]; const OEMCrypto_KeyObject& key = odk_context.license.key_array[i];
const std::string key_id = ExtractItem(key.key_id, message); const std::string key_id = ExtractItem(key.key_id, message);
// If there is no key id, we can't add an invalid entry since there would // If there is no key id, we can't add an invalid entry since there would
@@ -95,8 +93,21 @@ WB_Result OdkLicenseParser::Parse(const std::string& decryption_key,
// Add the key right away. The key will be invalid, so if we fail to // Add the key right away. The key will be invalid, so if we fail to
// parse the key, we'll already have an entry for the invalid key. // parse the key, we'll already have an entry for the invalid key.
content_keys_[key_id] = ParseContentKey(decryption_key, message, key, switch (odk_context.license.license_type) {
provider_keys, provider_key_id); case OEMCrypto_ContentLicense:
content_keys_[key_id] = ParseInternalKey(
KeyType::kContentKey, decryption_key, message, key, provider_keys,
provider_key_id);
break;
case OEMCrypto_EntitlementLicense:
entitlement_keys_[key_id] = ParseInternalKey(
KeyType::kEntitlementKey, decryption_key, message, key,
provider_keys, provider_key_id);
break;
default:
VLOG(1) << "Unknown license type " << odk_context.license.license_type;
break;
}
} }
return WB_RESULT_OK; return WB_RESULT_OK;
@@ -107,11 +118,16 @@ const RenewalKey* OdkLicenseParser::GetRenewalKey() const {
return renewal_keys_.empty() ? nullptr : &renewal_keys_[0]; return renewal_keys_.empty() ? nullptr : &renewal_keys_[0];
} }
const std::map<std::string, ContentKey>& OdkLicenseParser::GetContentKeys() const std::map<std::string, InternalKey>& OdkLicenseParser::GetContentKeys()
const { const {
return content_keys_; return content_keys_;
} }
const std::map<std::string, InternalKey>&
OdkLicenseParser::GetEntitlementKeys() const {
return entitlement_keys_;
}
RenewalKey OdkLicenseParser::ParseSigningKeys(const std::string& decryption_key, RenewalKey OdkLicenseParser::ParseSigningKeys(const std::string& decryption_key,
const std::string& key, const std::string& key,
const std::string& iv) const { const std::string& iv) const {
@@ -150,7 +166,8 @@ RenewalKey OdkLicenseParser::ParseSigningKeys(const std::string& decryption_key,
return renewal_key; return renewal_key;
} }
ContentKey OdkLicenseParser::ParseContentKey( InternalKey OdkLicenseParser::ParseInternalKey(
KeyType key_type,
const std::string& decryption_key, const std::string& decryption_key,
const std::string& message, const std::string& message,
const OEMCrypto_KeyObject& key, const OEMCrypto_KeyObject& key,
@@ -163,23 +180,24 @@ ContentKey OdkLicenseParser::ParseContentKey(
if (iv.size() != 16u) { if (iv.size() != 16u) {
VLOG(3) << "Invalid content iv size."; VLOG(3) << "Invalid content iv size.";
return ContentKey(); return InternalKey();
} }
std::string wrapped_key = ExtractItem(key.key_data, message); std::string wrapped_key = ExtractItem(key.key_data, message);
// Unlike with protobufs, we don't need to handle padding here. The ODK will // Unlike with protobufs, we don't need to handle padding here. The ODK will
// not include the padding as part of the key's size. // not include the padding as part of the key's size.
if (wrapped_key.size() != 16u) { const size_t key_size = key_type == KeyType::kEntitlementKey ? 32u : 16u;
VLOG(3) << "Invalid content key size (" << wrapped_key.size() << ")."; if (wrapped_key.size() != key_size) {
return ContentKey(); VLOG(3) << "Invalid key size (" << wrapped_key.size() << ").";
return InternalKey();
} }
std::string unwrapped_key; std::string unwrapped_key;
if (!UnwrapContentKey(wrapped_key, provider_keys, provider_key_id, if (!UnwrapKey(wrapped_key, provider_keys, provider_key_id, decryption_key,
decryption_key, iv, &unwrapped_key)) { iv, &unwrapped_key)) {
VLOG(3) << "Failed to decrypt content key."; VLOG(3) << "Failed to decrypt key.";
return ContentKey(); return InternalKey();
} }
const std::string key_control_block = ExtractItem(key.key_control, message); const std::string key_control_block = ExtractItem(key.key_control, message);
@@ -192,7 +210,8 @@ ContentKey OdkLicenseParser::ParseContentKey(
// adjusts the level returned inside the key control block to handle // adjusts the level returned inside the key control block to handle
// this. // this.
const bool is_hw_verified = false; const bool is_hw_verified = false;
return CreateContentKey(security_level, is_hw_verified, unwrapped_key); return CreateInternalKey(
key_type, security_level, is_hw_verified, unwrapped_key);
} }
} // namespace widevine } // namespace widevine

View File

@@ -20,21 +20,25 @@ class OdkLicenseParser : public LicenseParser {
const widevine::RenewalKey* GetRenewalKey() const override; const widevine::RenewalKey* GetRenewalKey() const override;
const std::map<std::string, ContentKey>& GetContentKeys() const override; const std::map<std::string, InternalKey>& GetContentKeys() const override;
const std::map<std::string, InternalKey>& GetEntitlementKeys() const override;
private: private:
RenewalKey ParseSigningKeys(const std::string& decryption_key, RenewalKey ParseSigningKeys(const std::string& decryption_key,
const std::string& key, const std::string& key,
const std::string& iv) const; const std::string& iv) const;
ContentKey ParseContentKey(const std::string& decryption_key, InternalKey ParseInternalKey(KeyType key_type,
const std::string& message, const std::string& decryption_key,
const OEMCrypto_KeyObject& key, const std::string& message,
const std::vector<ProviderKey>& provider_keys, const OEMCrypto_KeyObject& key,
size_t provider_key_id) const; const std::vector<ProviderKey>& provider_keys,
size_t provider_key_id) const;
std::vector<widevine::RenewalKey> renewal_keys_; std::vector<widevine::RenewalKey> renewal_keys_;
std::map<std::string, ContentKey> content_keys_; std::map<std::string, InternalKey> content_keys_;
std::map<std::string, InternalKey> entitlement_keys_;
}; };
} // namespace widevine } // namespace widevine

View File

@@ -105,10 +105,22 @@ WB_Result ProtobufLicenseParser::Parse(
if (key.id().empty()) { if (key.id().empty()) {
VLOG(3) << "Skipping content key : no key id."; VLOG(3) << "Skipping content key : no key id.";
} else { } else {
content_keys_[key.id()] = ParseContentKey( content_keys_[key.id()] = ParseInternalKey(
decryption_key, key, is_verified, provider_keys, provider_key_id); KeyType::kContentKey, decryption_key, key, is_verified,
provider_keys, provider_key_id);
} }
break; break;
case video_widevine::License_KeyContainer::ENTITLEMENT:
if (key.id().empty()) {
VLOG(3) << "Skipping entitlement key : no key id.";
} else {
entitlement_keys_[key.id()] = ParseInternalKey(
KeyType::kEntitlementKey, decryption_key, key, is_verified,
provider_keys, provider_key_id);
}
break;
default: default:
VLOG(3) << "Skipping key of type " << key.type() << "."; VLOG(3) << "Skipping key of type " << key.type() << ".";
} }
@@ -127,11 +139,16 @@ const widevine::RenewalKey* ProtobufLicenseParser::GetRenewalKey() const {
return renewal_keys_.empty() ? nullptr : &renewal_keys_[0]; return renewal_keys_.empty() ? nullptr : &renewal_keys_[0];
} }
const std::map<std::string, ContentKey>& ProtobufLicenseParser::GetContentKeys() const std::map<std::string, InternalKey>&
const { ProtobufLicenseParser::GetContentKeys() const {
return content_keys_; return content_keys_;
} }
const std::map<std::string, InternalKey>&
ProtobufLicenseParser::GetEntitlementKeys() const {
return entitlement_keys_;
}
RenewalKey ProtobufLicenseParser::ParseSigningKey( RenewalKey ProtobufLicenseParser::ParseSigningKey(
const std::string& decryption_key, const std::string& decryption_key,
const video_widevine::License_KeyContainer& key) const { const video_widevine::License_KeyContainer& key) const {
@@ -173,7 +190,8 @@ RenewalKey ProtobufLicenseParser::ParseSigningKey(
return renewal_key; return renewal_key;
} }
ContentKey ProtobufLicenseParser::ParseContentKey( InternalKey ProtobufLicenseParser::ParseInternalKey(
KeyType key_type,
const std::string& decryption_key, const std::string& decryption_key,
const video_widevine::License_KeyContainer& key, const video_widevine::License_KeyContainer& key,
bool is_verified, bool is_verified,
@@ -181,33 +199,35 @@ ContentKey ProtobufLicenseParser::ParseContentKey(
size_t provider_key_id) const { size_t provider_key_id) const {
// This should have been verified before calling the parser. // This should have been verified before calling the parser.
CHECK_EQ(decryption_key.size(), 16u) << "Incorrect decryption key size."; CHECK_EQ(decryption_key.size(), 16u) << "Incorrect decryption key size.";
CHECK_EQ(key.type(), video_widevine::License_KeyContainer::CONTENT); CHECK(key.type() == video_widevine::License_KeyContainer::CONTENT ||
key.type() == video_widevine::License_KeyContainer::ENTITLEMENT);
constexpr size_t kKeySizeWithoutPadding = 16u; const size_t key_size_without_padding =
constexpr size_t kKeySizeWithPadding = 32u; key_type == KeyType::kEntitlementKey ? 32u : 16u;
const size_t key_size_with_padding = key_size_without_padding + 16u;
if (key.key().size() != kKeySizeWithoutPadding && if (key.key().size() != key_size_without_padding &&
key.key().size() != kKeySizeWithPadding) { key.key().size() != key_size_with_padding) {
VLOG(3) << "Invalid content key size (" << key.key().size() << ")."; VLOG(3) << "Invalid key size (" << key.key().size() << ").";
return ContentKey(); return InternalKey();
} }
if (key.iv().size() != 16u) { if (key.iv().size() != 16u) {
VLOG(3) << "Invalid iv size."; VLOG(3) << "Invalid iv size.";
return ContentKey(); return InternalKey();
} }
std::string wrapped_key = key.key().substr(0, kKeySizeWithoutPadding); std::string wrapped_key = key.key().substr(0, key_size_without_padding);
std::string unwrapped_key; std::string unwrapped_key;
if (!UnwrapContentKey(wrapped_key, provider_keys, provider_key_id, if (!UnwrapKey(wrapped_key, provider_keys, provider_key_id, decryption_key,
decryption_key, key.iv(), &unwrapped_key)) { key.iv(), &unwrapped_key)) {
VLOG(3) << "Failed to decrypt content key."; VLOG(3) << "Failed to decrypt content key.";
return ContentKey(); return InternalKey();
} }
// The default value for `level()` will be SW_SECURE_CRYPTO. This means that // The default value for `level()` will be SW_SECURE_CRYPTO. This means that
// if the level value was not set, it will be treated as SW_SECURE_CRYPTO. // if the level value was not set, it will be treated as SW_SECURE_CRYPTO.
return CreateContentKey(key.level(), is_verified, unwrapped_key); return CreateInternalKey(key_type, key.level(), is_verified, unwrapped_key);
} }
} // namespace widevine } // namespace widevine

View File

@@ -19,21 +19,25 @@ class ProtobufLicenseParser : public LicenseParser {
const widevine::RenewalKey* GetRenewalKey() const override; const widevine::RenewalKey* GetRenewalKey() const override;
const std::map<std::string, ContentKey>& GetContentKeys() const override; const std::map<std::string, InternalKey>& GetContentKeys() const override;
const std::map<std::string, InternalKey>&
GetEntitlementKeys() const override;
RenewalKey ParseSigningKey( RenewalKey ParseSigningKey(
const std::string& decryption_key, const std::string& decryption_key,
const video_widevine::License_KeyContainer& key) const; const video_widevine::License_KeyContainer& key) const;
ContentKey ParseContentKey(const std::string& decryption_key, InternalKey ParseInternalKey(KeyType key_type,
const video_widevine::License_KeyContainer& key, const std::string& decryption_key,
bool is_verified, const video_widevine::License_KeyContainer& key,
const std::vector<ProviderKey>& provider_keys, bool is_verified,
size_t provider_key_id) const; const std::vector<ProviderKey>& provider_keys,
size_t provider_key_id) const;
private: private:
std::vector<widevine::RenewalKey> renewal_keys_; std::vector<widevine::RenewalKey> renewal_keys_;
std::map<std::string, ContentKey> content_keys_; std::map<std::string, InternalKey> content_keys_;
std::map<std::string, InternalKey> entitlement_keys_;
}; };
} // namespace widevine } // namespace widevine