Add entitlement license support and tests
This commit is contained in:
@@ -195,6 +195,7 @@ cc_library(
|
||||
srcs = [
|
||||
"license_whitebox_create_test.cc",
|
||||
"license_whitebox_decrypt_test.cc",
|
||||
"license_whitebox_entitlement_content_key_test.cc",
|
||||
"license_whitebox_get_secret_string_test.cc",
|
||||
"license_whitebox_key_control_block_test.cc",
|
||||
"license_whitebox_license_key_mode.cc",
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
#include "api/golden_data.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
namespace {
|
||||
|
||||
GoldenData::Content CreateContent(const std::vector<uint8_t>& plaintext,
|
||||
const std::vector<uint8_t>& ciphertext,
|
||||
const AesIv& iv,
|
||||
@@ -36,7 +38,9 @@ GoldenData::Content CreateContent(const std::vector<uint8_t>& plaintext,
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GoldenData::GoldenData() {
|
||||
// Content generated with:
|
||||
// openssl aes-128-cbc -e -in data.txt
|
||||
@@ -84,6 +88,51 @@ GoldenData::GoldenData() {
|
||||
/* software_crypto_key_id= */ {0xFF, 5, 0, 0},
|
||||
/* software_decode_key_id= */ {0xFF, 6, 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() {
|
||||
@@ -91,4 +140,5 @@ KeyId GoldenData::GetFreeId() {
|
||||
// avoid conflicts.
|
||||
return {0xAB, 0, 0, next_id_++};
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
@@ -29,6 +29,20 @@ class GoldenData {
|
||||
ContentKeyData software_decode_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();
|
||||
|
||||
@@ -36,11 +50,14 @@ class GoldenData {
|
||||
|
||||
const Content& CTRContent() const { return ctr_content_; }
|
||||
|
||||
const EntitlementContent& EntitlementContent() const { return entitlement_; }
|
||||
|
||||
KeyId GetFreeId();
|
||||
|
||||
private:
|
||||
Content cbc_content_;
|
||||
Content ctr_content_;
|
||||
struct EntitlementContent entitlement_;
|
||||
|
||||
uint8_t next_id_ = 0;
|
||||
};
|
||||
|
||||
@@ -208,6 +208,50 @@ WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox,
|
||||
const uint8_t* license_request,
|
||||
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
|
||||
// key and to know what operations can be performed with that key.
|
||||
//
|
||||
|
||||
@@ -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
|
||||
@@ -43,6 +43,14 @@ class LicenseWhiteboxProcessLicenseResponseTest
|
||||
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_;
|
||||
License license_;
|
||||
};
|
||||
@@ -91,6 +99,21 @@ TEST_F(LicenseWhiteboxProcessLicenseResponseTest,
|
||||
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
|
||||
// was manipulating the license and we are using protobuf parsing), the
|
||||
// implementation is free to pick either key.
|
||||
|
||||
@@ -14,6 +14,7 @@ using KeyId = std::array<uint8_t, 4>;
|
||||
|
||||
// AES keys can only be 16 bytes.
|
||||
using AesKey = std::array<uint8_t, 16>;
|
||||
using Aes256Key = std::array<uint8_t, 32>;
|
||||
|
||||
// AES IV can only be 16 bytes.
|
||||
using AesIv = std::array<uint8_t, 16>;
|
||||
@@ -38,6 +39,14 @@ struct ContentKeyData {
|
||||
SecurityLevel level;
|
||||
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
|
||||
|
||||
#endif // WHITEBOX_API_TEST_KEY_TYPES_H_
|
||||
|
||||
@@ -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,
|
||||
const TestLicenseBuilder::Settings& settings,
|
||||
const std::string& container_key,
|
||||
@@ -457,6 +475,10 @@ void TestLicenseBuilder::AddContentKey(const ContentKeyData& key) {
|
||||
content_keys_.push_back(key);
|
||||
}
|
||||
|
||||
void TestLicenseBuilder::AddEntitlementKey(const EntitlementKeyData& key) {
|
||||
entitlement_keys_.push_back(key);
|
||||
}
|
||||
|
||||
void TestLicenseBuilder::AddOperatorSessionKey(const KeyId& id) {
|
||||
operator_session_keys_.push_back(id);
|
||||
}
|
||||
@@ -504,10 +526,16 @@ void TestLicenseBuilder::Build(const TestServer& server,
|
||||
response.add_key());
|
||||
}
|
||||
|
||||
// Cannot have both content keys and entitlement keys.
|
||||
CHECK(content_keys_.empty() || entitlement_keys_.empty());
|
||||
|
||||
for (const auto& key : content_keys_) {
|
||||
AddContentKeyToContainer(key, settings_, container_key_,
|
||||
response.add_key());
|
||||
}
|
||||
for (const auto& key : entitlement_keys_) {
|
||||
AddEntitlementKeyToContainer(key, container_key_, response.add_key());
|
||||
}
|
||||
|
||||
for (const auto& key : operator_session_keys_) {
|
||||
AddOperatorSessionKeyToContainer(key, response.add_key());
|
||||
|
||||
@@ -119,6 +119,8 @@ class TestLicenseBuilder {
|
||||
|
||||
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
|
||||
// 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.
|
||||
@@ -145,6 +147,7 @@ class TestLicenseBuilder {
|
||||
Settings settings_;
|
||||
|
||||
std::vector<ContentKeyData> content_keys_;
|
||||
std::vector<EntitlementKeyData> entitlement_keys_;
|
||||
std::vector<SigningKey> signing_keys_;
|
||||
std::vector<KeyId> operator_session_keys_;
|
||||
};
|
||||
|
||||
@@ -12,10 +12,17 @@
|
||||
|
||||
namespace widevine {
|
||||
|
||||
struct ContentKey {
|
||||
enum class KeyType {
|
||||
kInvalid,
|
||||
kContentKey,
|
||||
kEntitlementKey,
|
||||
};
|
||||
|
||||
struct InternalKey {
|
||||
// This is the status will be returned in |WB_License_QueryKeyStatus()|.
|
||||
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
|
||||
// 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
|
||||
|
||||
#endif // WHITEBOX_REFERENCE_IMPL_CONTENT_KEY_H_
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "base/check.h"
|
||||
#include "base/check_op.h"
|
||||
#include "base/logging.h"
|
||||
#include "crypto_utils/aes_cbc_decryptor.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(iv.size(), 16u);
|
||||
CHECK(decrypted);
|
||||
CHECK_GE(decrypted->size(), encrypted.size());
|
||||
|
||||
decrypted->resize(encrypted.size());
|
||||
|
||||
@@ -30,18 +30,18 @@ bool LicenseParser::Decrypt(const std::string& key,
|
||||
reinterpret_cast<uint8_t*>(&decrypted->front()));
|
||||
}
|
||||
|
||||
bool LicenseParser::UnwrapContentKey(
|
||||
const std::string& wrapped_content_key,
|
||||
bool LicenseParser::UnwrapKey(
|
||||
const std::string& wrapped_key,
|
||||
const std::vector<ProviderKey>& provider_keys,
|
||||
size_t provider_key_id,
|
||||
const std::string& key_decryption_key,
|
||||
const std::string& key_decryption_key_iv,
|
||||
std::string* unwrapped_content_key) {
|
||||
std::string* unwrapped_key) {
|
||||
const bool provider_key_id_valid =
|
||||
(provider_key_id >= 1 && provider_key_id <= provider_keys.size());
|
||||
|
||||
// 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) {
|
||||
const auto& mask = provider_keys[provider_key_id - 1].mask;
|
||||
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.
|
||||
std::string unwrapped_key(key);
|
||||
std::string intermediate_key;
|
||||
if (!Decrypt(key_decryption_key, key_decryption_key_iv, key,
|
||||
&unwrapped_key)) {
|
||||
&intermediate_key)) {
|
||||
DVLOG(1) << "Failed to decrypt content key using KEK.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// If |provider_key_id| not used, simply return the key decrypted so far.
|
||||
if (!provider_key_id_valid) {
|
||||
unwrapped_content_key->swap(unwrapped_key);
|
||||
unwrapped_key->swap(intermediate_key);
|
||||
return true;
|
||||
}
|
||||
|
||||
// |provider_key_id| is used, so decrypt the unwrapped key using the
|
||||
// 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 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.";
|
||||
return false;
|
||||
}
|
||||
unwrapped_content_key->swap(final_key);
|
||||
unwrapped_key->swap(final_key);
|
||||
return true;
|
||||
}
|
||||
|
||||
ContentKey LicenseParser::CreateContentKey(
|
||||
WB_KeyStatus LicenseParser::GetKeyStatus(
|
||||
video_widevine::License_KeyContainer_SecurityLevel level,
|
||||
bool is_hw_verified,
|
||||
const std::string& key) {
|
||||
ContentKey content_key;
|
||||
bool is_hw_verified) {
|
||||
// If the device is hardware verified, then we can override the status to
|
||||
// allow masked decrypt and decrypt.
|
||||
if (is_hw_verified) {
|
||||
return WB_KEY_STATUS_CONTENT_KEY_DECRYPT;
|
||||
}
|
||||
|
||||
switch (level) {
|
||||
case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO:
|
||||
content_key.status = WB_KEY_STATUS_CONTENT_KEY_DECRYPT;
|
||||
break;
|
||||
|
||||
return WB_KEY_STATUS_CONTENT_KEY_DECRYPT;
|
||||
case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_DECODE:
|
||||
content_key.status = WB_KEY_STATUS_CONTENT_KEY_MASKED_DECRYPT;
|
||||
break;
|
||||
|
||||
return WB_KEY_STATUS_CONTENT_KEY_MASKED_DECRYPT;
|
||||
default:
|
||||
// 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
|
||||
// hardware verified.
|
||||
content_key.status = WB_KEY_STATUS_CONTENT_KEY_VALID;
|
||||
break;
|
||||
return WB_KEY_STATUS_CONTENT_KEY_VALID;
|
||||
}
|
||||
}
|
||||
|
||||
// If the device is hardware verified, then we can override the status to
|
||||
// allow masked decrypt and decrypt.
|
||||
if (is_hw_verified) {
|
||||
content_key.status = WB_KEY_STATUS_CONTENT_KEY_DECRYPT;
|
||||
}
|
||||
InternalKey LicenseParser::CreateInternalKey(
|
||||
KeyType key_type, video_widevine::License_KeyContainer_SecurityLevel level,
|
||||
bool is_hw_verified, const std::string& key) {
|
||||
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
|
||||
// will only risk exposing it. We only have an entry for it so we can handle
|
||||
// errors correctly.
|
||||
if (content_key.can_decrypt() || content_key.can_masked_decrypt()) {
|
||||
CHECK_EQ(key.size(), content_key.key.size());
|
||||
std::copy(key.begin(), key.end(), content_key.key.begin());
|
||||
if (internal_key.can_decrypt() || internal_key.can_masked_decrypt()) {
|
||||
CHECK_LE(key.size(), internal_key.key.size());
|
||||
std::copy(key.begin(), key.end(), internal_key.key.begin());
|
||||
}
|
||||
|
||||
return content_key;
|
||||
return internal_key;
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
@@ -39,7 +39,9 @@ class LicenseParser {
|
||||
// If there is no renewal key, then `nullptr` should be returned.
|
||||
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:
|
||||
static bool Decrypt(const std::string& key,
|
||||
@@ -47,26 +49,32 @@ class LicenseParser {
|
||||
const std::string& encrypted,
|
||||
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
|
||||
// |unwrapped_content_key| is updated on success, false otherwise.
|
||||
static bool UnwrapContentKey(const std::string& wrapped_content_key,
|
||||
const std::vector<ProviderKey>& provider_keys,
|
||||
size_t provider_key_id,
|
||||
const std::string& key_decryption_key,
|
||||
const std::string& key_decryption_key_iv,
|
||||
std::string* unwrapped_content_key);
|
||||
static bool UnwrapKey(const std::string& wrapped_key,
|
||||
const std::vector<ProviderKey>& provider_keys,
|
||||
size_t provider_key_id,
|
||||
const std::string& key_decryption_key,
|
||||
const std::string& key_decryption_key_iv,
|
||||
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.
|
||||
// |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.
|
||||
// returned in InternalKey if decrypt or masked_decrypt is allowed.
|
||||
// Otherwise |key| is dropped.
|
||||
static ContentKey CreateContentKey(
|
||||
static InternalKey CreateInternalKey(
|
||||
KeyType key_type,
|
||||
video_widevine::License_KeyContainer_SecurityLevel level,
|
||||
bool is_hw_verified,
|
||||
const std::string& key);
|
||||
|
||||
static WB_KeyStatus GetKeyStatus(
|
||||
video_widevine::License_KeyContainer_SecurityLevel level,
|
||||
bool is_hw_verified);
|
||||
|
||||
};
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
@@ -91,6 +91,10 @@ bool CheckAndUpdateSize(size_t min_size, size_t* out_size) {
|
||||
return good;
|
||||
}
|
||||
|
||||
std::string MakeString(const uint8_t* data, size_t size) {
|
||||
return std::string(data, data + size);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// 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::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;
|
||||
};
|
||||
@@ -140,9 +145,9 @@ std::vector<uint8_t> GetSecretStringFor(WB_CipherMode mode) {
|
||||
kCTRSecretStringPattern + sizeof(kCTRSecretStringPattern));
|
||||
}
|
||||
|
||||
const widevine::ContentKey* FindKey(const WB_License_Whitebox* whitebox,
|
||||
const uint8_t* id,
|
||||
size_t id_size) {
|
||||
const widevine::InternalKey* FindKey(const WB_License_Whitebox* whitebox,
|
||||
const uint8_t* id,
|
||||
size_t id_size) {
|
||||
CHECK(whitebox);
|
||||
CHECK(id);
|
||||
CHECK_GT(id_size, 0u);
|
||||
@@ -489,12 +494,74 @@ WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox,
|
||||
}
|
||||
|
||||
whitebox->content_keys = parser->GetContentKeys();
|
||||
whitebox->entitlement_keys = parser->GetEntitlementKeys();
|
||||
|
||||
whitebox->initialized = true;
|
||||
|
||||
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_KeyQueryType type,
|
||||
const uint8_t* key_id,
|
||||
@@ -531,7 +598,7 @@ WB_Result WB_License_QueryKeyStatus(const WB_License_Whitebox* whitebox,
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
const widevine::ContentKey* content_key =
|
||||
const widevine::InternalKey* content_key =
|
||||
FindKey(whitebox, key_id, key_id_size);
|
||||
|
||||
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.
|
||||
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,
|
||||
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
|
||||
// |masked_output_data_size|.
|
||||
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);
|
||||
|
||||
if (result != WB_RESULT_OK) {
|
||||
|
||||
@@ -80,10 +80,8 @@ WB_Result OdkLicenseParser::Parse(const std::string& decryption_key,
|
||||
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) {
|
||||
const OEMCrypto_KeyObject& key = odk_context.license.key_array[i];
|
||||
|
||||
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
|
||||
@@ -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
|
||||
// parse the key, we'll already have an entry for the invalid key.
|
||||
content_keys_[key_id] = ParseContentKey(decryption_key, message, key,
|
||||
provider_keys, provider_key_id);
|
||||
switch (odk_context.license.license_type) {
|
||||
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;
|
||||
@@ -107,11 +118,16 @@ const RenewalKey* OdkLicenseParser::GetRenewalKey() const {
|
||||
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 {
|
||||
return content_keys_;
|
||||
}
|
||||
|
||||
const std::map<std::string, InternalKey>&
|
||||
OdkLicenseParser::GetEntitlementKeys() const {
|
||||
return entitlement_keys_;
|
||||
}
|
||||
|
||||
RenewalKey OdkLicenseParser::ParseSigningKeys(const std::string& decryption_key,
|
||||
const std::string& key,
|
||||
const std::string& iv) const {
|
||||
@@ -150,7 +166,8 @@ RenewalKey OdkLicenseParser::ParseSigningKeys(const std::string& decryption_key,
|
||||
return renewal_key;
|
||||
}
|
||||
|
||||
ContentKey OdkLicenseParser::ParseContentKey(
|
||||
InternalKey OdkLicenseParser::ParseInternalKey(
|
||||
KeyType key_type,
|
||||
const std::string& decryption_key,
|
||||
const std::string& message,
|
||||
const OEMCrypto_KeyObject& key,
|
||||
@@ -163,23 +180,24 @@ ContentKey OdkLicenseParser::ParseContentKey(
|
||||
|
||||
if (iv.size() != 16u) {
|
||||
VLOG(3) << "Invalid content iv size.";
|
||||
return ContentKey();
|
||||
return InternalKey();
|
||||
}
|
||||
|
||||
std::string wrapped_key = ExtractItem(key.key_data, message);
|
||||
|
||||
// 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.
|
||||
if (wrapped_key.size() != 16u) {
|
||||
VLOG(3) << "Invalid content key size (" << wrapped_key.size() << ").";
|
||||
return ContentKey();
|
||||
const size_t key_size = key_type == KeyType::kEntitlementKey ? 32u : 16u;
|
||||
if (wrapped_key.size() != key_size) {
|
||||
VLOG(3) << "Invalid key size (" << wrapped_key.size() << ").";
|
||||
return InternalKey();
|
||||
}
|
||||
|
||||
std::string unwrapped_key;
|
||||
if (!UnwrapContentKey(wrapped_key, provider_keys, provider_key_id,
|
||||
decryption_key, iv, &unwrapped_key)) {
|
||||
VLOG(3) << "Failed to decrypt content key.";
|
||||
return ContentKey();
|
||||
if (!UnwrapKey(wrapped_key, provider_keys, provider_key_id, decryption_key,
|
||||
iv, &unwrapped_key)) {
|
||||
VLOG(3) << "Failed to decrypt key.";
|
||||
return InternalKey();
|
||||
}
|
||||
|
||||
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
|
||||
// this.
|
||||
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
|
||||
|
||||
@@ -20,21 +20,25 @@ class OdkLicenseParser : public LicenseParser {
|
||||
|
||||
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:
|
||||
RenewalKey ParseSigningKeys(const std::string& decryption_key,
|
||||
const std::string& key,
|
||||
const std::string& iv) const;
|
||||
|
||||
ContentKey ParseContentKey(const std::string& decryption_key,
|
||||
const std::string& message,
|
||||
const OEMCrypto_KeyObject& key,
|
||||
const std::vector<ProviderKey>& provider_keys,
|
||||
size_t provider_key_id) const;
|
||||
InternalKey ParseInternalKey(KeyType key_type,
|
||||
const std::string& decryption_key,
|
||||
const std::string& message,
|
||||
const OEMCrypto_KeyObject& key,
|
||||
const std::vector<ProviderKey>& provider_keys,
|
||||
size_t provider_key_id) const;
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -105,10 +105,22 @@ WB_Result ProtobufLicenseParser::Parse(
|
||||
if (key.id().empty()) {
|
||||
VLOG(3) << "Skipping content key : no key id.";
|
||||
} else {
|
||||
content_keys_[key.id()] = ParseContentKey(
|
||||
decryption_key, key, is_verified, provider_keys, provider_key_id);
|
||||
content_keys_[key.id()] = ParseInternalKey(
|
||||
KeyType::kContentKey, decryption_key, key, is_verified,
|
||||
provider_keys, provider_key_id);
|
||||
}
|
||||
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:
|
||||
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];
|
||||
}
|
||||
|
||||
const std::map<std::string, ContentKey>& ProtobufLicenseParser::GetContentKeys()
|
||||
const {
|
||||
const std::map<std::string, InternalKey>&
|
||||
ProtobufLicenseParser::GetContentKeys() const {
|
||||
return content_keys_;
|
||||
}
|
||||
|
||||
const std::map<std::string, InternalKey>&
|
||||
ProtobufLicenseParser::GetEntitlementKeys() const {
|
||||
return entitlement_keys_;
|
||||
}
|
||||
|
||||
RenewalKey ProtobufLicenseParser::ParseSigningKey(
|
||||
const std::string& decryption_key,
|
||||
const video_widevine::License_KeyContainer& key) const {
|
||||
@@ -173,7 +190,8 @@ RenewalKey ProtobufLicenseParser::ParseSigningKey(
|
||||
return renewal_key;
|
||||
}
|
||||
|
||||
ContentKey ProtobufLicenseParser::ParseContentKey(
|
||||
InternalKey ProtobufLicenseParser::ParseInternalKey(
|
||||
KeyType key_type,
|
||||
const std::string& decryption_key,
|
||||
const video_widevine::License_KeyContainer& key,
|
||||
bool is_verified,
|
||||
@@ -181,33 +199,35 @@ ContentKey ProtobufLicenseParser::ParseContentKey(
|
||||
size_t provider_key_id) const {
|
||||
// This should have been verified before calling the parser.
|
||||
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;
|
||||
constexpr size_t kKeySizeWithPadding = 32u;
|
||||
const size_t key_size_without_padding =
|
||||
key_type == KeyType::kEntitlementKey ? 32u : 16u;
|
||||
const size_t key_size_with_padding = key_size_without_padding + 16u;
|
||||
|
||||
if (key.key().size() != kKeySizeWithoutPadding &&
|
||||
key.key().size() != kKeySizeWithPadding) {
|
||||
VLOG(3) << "Invalid content key size (" << key.key().size() << ").";
|
||||
return ContentKey();
|
||||
if (key.key().size() != key_size_without_padding &&
|
||||
key.key().size() != key_size_with_padding) {
|
||||
VLOG(3) << "Invalid key size (" << key.key().size() << ").";
|
||||
return InternalKey();
|
||||
}
|
||||
|
||||
if (key.iv().size() != 16u) {
|
||||
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;
|
||||
if (!UnwrapContentKey(wrapped_key, provider_keys, provider_key_id,
|
||||
decryption_key, key.iv(), &unwrapped_key)) {
|
||||
if (!UnwrapKey(wrapped_key, provider_keys, provider_key_id, decryption_key,
|
||||
key.iv(), &unwrapped_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
|
||||
// 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
|
||||
|
||||
@@ -19,21 +19,25 @@ class ProtobufLicenseParser : public LicenseParser {
|
||||
|
||||
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(
|
||||
const std::string& decryption_key,
|
||||
const video_widevine::License_KeyContainer& key) const;
|
||||
|
||||
ContentKey ParseContentKey(const std::string& decryption_key,
|
||||
const video_widevine::License_KeyContainer& key,
|
||||
bool is_verified,
|
||||
const std::vector<ProviderKey>& provider_keys,
|
||||
size_t provider_key_id) const;
|
||||
InternalKey ParseInternalKey(KeyType key_type,
|
||||
const std::string& decryption_key,
|
||||
const video_widevine::License_KeyContainer& key,
|
||||
bool is_verified,
|
||||
const std::vector<ProviderKey>& provider_keys,
|
||||
size_t provider_key_id) const;
|
||||
|
||||
private:
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user