Add entitlement license support and tests
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -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_);
|
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.
|
||||||
|
|||||||
@@ -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_
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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_
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user