From 9cd251fa235023158b76cbfc3b9725bde5b88b8b Mon Sep 17 00:00:00 2001 From: Jacob Trimble Date: Thu, 27 Jan 2022 14:23:17 -0800 Subject: [PATCH] Add entitlement license support and tests --- whitebox/api/BUILD | 1 + whitebox/api/golden_data.cc | 50 ++++++++++++ whitebox/api/golden_data.h | 17 ++++ whitebox/api/license_whitebox.h | 44 ++++++++++ ...e_whitebox_entitlement_content_key_test.cc | 77 ++++++++++++++++++ ..._whitebox_process_license_response_test.cc | 23 ++++++ whitebox/api/test_key_types.h | 9 +++ whitebox/api/test_license_builder.cc | 28 +++++++ whitebox/api/test_license_builder.h | 3 + whitebox/reference/impl/content_key.h | 16 ++-- whitebox/reference/impl/license_parser.cc | 64 +++++++-------- whitebox/reference/impl/license_parser.h | 30 ++++--- .../reference/impl/license_whitebox_impl.cc | 81 +++++++++++++++++-- whitebox/reference/impl/odk_license_parser.cc | 49 +++++++---- whitebox/reference/impl/odk_license_parser.h | 18 +++-- .../reference/impl/protobuf_license_parser.cc | 56 ++++++++----- .../reference/impl/protobuf_license_parser.h | 18 +++-- 17 files changed, 480 insertions(+), 104 deletions(-) create mode 100644 whitebox/api/license_whitebox_entitlement_content_key_test.cc diff --git a/whitebox/api/BUILD b/whitebox/api/BUILD index d961fbc..acb6639 100644 --- a/whitebox/api/BUILD +++ b/whitebox/api/BUILD @@ -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", diff --git a/whitebox/api/golden_data.cc b/whitebox/api/golden_data.cc index b31de89..278111c 100644 --- a/whitebox/api/golden_data.cc +++ b/whitebox/api/golden_data.cc @@ -3,7 +3,9 @@ #include "api/golden_data.h" namespace widevine { + namespace { + GoldenData::Content CreateContent(const std::vector& plaintext, const std::vector& ciphertext, const AesIv& iv, @@ -36,7 +38,9 @@ GoldenData::Content CreateContent(const std::vector& 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 diff --git a/whitebox/api/golden_data.h b/whitebox/api/golden_data.h index 789d902..aa955b1 100644 --- a/whitebox/api/golden_data.h +++ b/whitebox/api/golden_data.h @@ -29,6 +29,20 @@ class GoldenData { ContentKeyData software_decode_key; ContentKeyData hardware_key; }; + struct EntitlementContent { + std::vector ciphertext; + std::vector plaintext; + + // IV used to encrypt the plaintext. + AesIv iv; + + // The content key encrypted with the entitlement key. + std::vector 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; }; diff --git a/whitebox/api/license_whitebox.h b/whitebox/api/license_whitebox.h index f737e08..e157399 100644 --- a/whitebox/api/license_whitebox.h +++ b/whitebox/api/license_whitebox.h @@ -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. // diff --git a/whitebox/api/license_whitebox_entitlement_content_key_test.cc b/whitebox/api/license_whitebox_entitlement_content_key_test.cc new file mode 100644 index 0000000..c9b4af9 --- /dev/null +++ b/whitebox/api/license_whitebox_entitlement_content_key_test.cc @@ -0,0 +1,77 @@ +// Copyright 2021 Google LLC. All Rights Reserved. + +#include "api/license_whitebox.h" + +#include +#include + +#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 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 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 diff --git a/whitebox/api/license_whitebox_process_license_response_test.cc b/whitebox/api/license_whitebox_process_license_response_test.cc index 3cb33a7..968463a 100644 --- a/whitebox/api/license_whitebox_process_license_response_test.cc +++ b/whitebox/api/license_whitebox_process_license_response_test.cc @@ -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 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. diff --git a/whitebox/api/test_key_types.h b/whitebox/api/test_key_types.h index 624becc..32adb9b 100644 --- a/whitebox/api/test_key_types.h +++ b/whitebox/api/test_key_types.h @@ -14,6 +14,7 @@ using KeyId = std::array; // AES keys can only be 16 bytes. using AesKey = std::array; +using Aes256Key = std::array; // AES IV can only be 16 bytes. using AesIv = std::array; @@ -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_ diff --git a/whitebox/api/test_license_builder.cc b/whitebox/api/test_license_builder.cc index 755ddbe..60bcfb5 100644 --- a/whitebox/api/test_license_builder.cc +++ b/whitebox/api/test_license_builder.cc @@ -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 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()); diff --git a/whitebox/api/test_license_builder.h b/whitebox/api/test_license_builder.h index cb03083..975530d 100644 --- a/whitebox/api/test_license_builder.h +++ b/whitebox/api/test_license_builder.h @@ -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 content_keys_; + std::vector entitlement_keys_; std::vector signing_keys_; std::vector operator_session_keys_; }; diff --git a/whitebox/reference/impl/content_key.h b/whitebox/reference/impl/content_key.h index 5284c03..042c315 100644 --- a/whitebox/reference/impl/content_key.h +++ b/whitebox/reference/impl/content_key.h @@ -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 key; + KeyType type = KeyType::kInvalid; + std::array 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_ diff --git a/whitebox/reference/impl/license_parser.cc b/whitebox/reference/impl/license_parser.cc index e62f51d..efaab80 100644 --- a/whitebox/reference/impl/license_parser.cc +++ b/whitebox/reference/impl/license_parser.cc @@ -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(&decrypted->front())); } -bool LicenseParser::UnwrapContentKey( - const std::string& wrapped_content_key, +bool LicenseParser::UnwrapKey( + const std::string& wrapped_key, const std::vector& 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 diff --git a/whitebox/reference/impl/license_parser.h b/whitebox/reference/impl/license_parser.h index cb11ec9..356565e 100644 --- a/whitebox/reference/impl/license_parser.h +++ b/whitebox/reference/impl/license_parser.h @@ -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& GetContentKeys() const = 0; + virtual const std::map& GetContentKeys() const = 0; + virtual const std::map& + 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& 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& 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 diff --git a/whitebox/reference/impl/license_whitebox_impl.cc b/whitebox/reference/impl/license_whitebox_impl.cc index b5e6360..2bc5cb2 100644 --- a/whitebox/reference/impl/license_whitebox_impl.cc +++ b/whitebox/reference/impl/license_whitebox_impl.cc @@ -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 renewal_key; - std::map content_keys; + std::map content_keys; + std::map entitlement_keys; std::vector provider_keys; }; @@ -140,9 +145,9 @@ std::vector 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 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) { diff --git a/whitebox/reference/impl/odk_license_parser.cc b/whitebox/reference/impl/odk_license_parser.cc index 0d0e97a..665f52c 100644 --- a/whitebox/reference/impl/odk_license_parser.cc +++ b/whitebox/reference/impl/odk_license_parser.cc @@ -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& OdkLicenseParser::GetContentKeys() +const std::map& OdkLicenseParser::GetContentKeys() const { return content_keys_; } +const std::map& +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 diff --git a/whitebox/reference/impl/odk_license_parser.h b/whitebox/reference/impl/odk_license_parser.h index 52881f2..c77ee85 100644 --- a/whitebox/reference/impl/odk_license_parser.h +++ b/whitebox/reference/impl/odk_license_parser.h @@ -20,21 +20,25 @@ class OdkLicenseParser : public LicenseParser { const widevine::RenewalKey* GetRenewalKey() const override; - const std::map& GetContentKeys() const override; + const std::map& GetContentKeys() const override; + const std::map& 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& 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& provider_keys, + size_t provider_key_id) const; + std::vector renewal_keys_; - std::map content_keys_; + std::map content_keys_; + std::map entitlement_keys_; }; } // namespace widevine diff --git a/whitebox/reference/impl/protobuf_license_parser.cc b/whitebox/reference/impl/protobuf_license_parser.cc index c5a33e0..5352c56 100644 --- a/whitebox/reference/impl/protobuf_license_parser.cc +++ b/whitebox/reference/impl/protobuf_license_parser.cc @@ -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& ProtobufLicenseParser::GetContentKeys() - const { +const std::map& +ProtobufLicenseParser::GetContentKeys() const { return content_keys_; } +const std::map& +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 diff --git a/whitebox/reference/impl/protobuf_license_parser.h b/whitebox/reference/impl/protobuf_license_parser.h index a47cf47..8fb59d5 100644 --- a/whitebox/reference/impl/protobuf_license_parser.h +++ b/whitebox/reference/impl/protobuf_license_parser.h @@ -19,21 +19,25 @@ class ProtobufLicenseParser : public LicenseParser { const widevine::RenewalKey* GetRenewalKey() const override; - const std::map& GetContentKeys() const override; + const std::map& GetContentKeys() const override; + const std::map& + 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& 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& provider_keys, + size_t provider_key_id) const; private: std::vector renewal_keys_; - std::map content_keys_; + std::map content_keys_; + std::map entitlement_keys_; }; } // namespace widevine