diff --git a/whitebox/api/BUILD b/whitebox/api/BUILD index f82c66c..d032571 100644 --- a/whitebox/api/BUILD +++ b/whitebox/api/BUILD @@ -138,6 +138,7 @@ cc_library( "license_whitebox_masked_decrypt_test.cc", "license_whitebox_process_license_response_core_message_test.cc", "license_whitebox_process_license_response_test.cc", + "license_whitebox_security_level_test.cc", "license_whitebox_sign_license_request_test.cc", "license_whitebox_sign_renewal_request_test.cc", "license_whitebox_test_base.cc", diff --git a/whitebox/api/aead_test_data.h b/whitebox/api/aead_test_data.h index a168edc..29afa3a 100644 --- a/whitebox/api/aead_test_data.h +++ b/whitebox/api/aead_test_data.h @@ -14,10 +14,6 @@ std::vector GetValidAeadInitData(); // Returns init data that the aead white-box will reject. std::vector GetInvalidAeadInitData(); -// Returns the matching public key for the data returned by -// GetLicenseInitData(). The format is a DER encoded PKCS#1 RSAPublicKey. -std::vector GetMatchingLicensePublicKey(); - } // namespace widevine #endif // WHITEBOX_API_AEAD_TEST_DATA_H_ diff --git a/whitebox/api/golden_data.cc b/whitebox/api/golden_data.cc index 3fddf30..f836779 100644 --- a/whitebox/api/golden_data.cc +++ b/whitebox/api/golden_data.cc @@ -27,19 +27,19 @@ GoldenData::GoldenData() { }; cbc_crypto_key_ = { - video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO, + SecurityLevel::kSoftwareSecureCrypto, {0xFF, 0, 0, 0}, &cbc_content_, }; cbc_decode_key_ = { - video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_DECODE, + SecurityLevel::kSoftwareSecureDecode, {0xFF, 1, 0, 0}, &cbc_content_, }; cbc_hardware_key_ = { - video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_CRYPTO, + SecurityLevel::kHardwareSecureCrypto, {0xFF, 2, 0, 0}, &cbc_content_, }; @@ -65,19 +65,19 @@ GoldenData::GoldenData() { }; ctr_crypto_key_ = { - video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO, + SecurityLevel::kSoftwareSecureCrypto, {0xFF, 3, 0, 0}, &ctr_content_, }; ctr_decode_key_ = { - video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_DECODE, + SecurityLevel::kSoftwareSecureDecode, {0xFF, 4, 0, 0}, &ctr_content_, }; ctr_hardware_key_ = { - video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_CRYPTO, + SecurityLevel::kHardwareSecureCrypto, {0xFF, 5, 0, 0}, &ctr_content_, }; diff --git a/whitebox/api/golden_data.h b/whitebox/api/golden_data.h index 9e94dbb..1131e84 100644 --- a/whitebox/api/golden_data.h +++ b/whitebox/api/golden_data.h @@ -22,7 +22,7 @@ class GoldenData { }; struct Key { - video_widevine::License_KeyContainer_SecurityLevel level; + SecurityLevel level; KeyId id; const Content* content; }; diff --git a/whitebox/api/license_whitebox_benchmark.cc b/whitebox/api/license_whitebox_benchmark.cc index c72595a..f632250 100644 --- a/whitebox/api/license_whitebox_benchmark.cc +++ b/whitebox/api/license_whitebox_benchmark.cc @@ -25,21 +25,20 @@ void LicenseWhiteboxBenchmark::SetUp() { iv_ = data_source_.Get(); const auto public_key_data = GetLicensePublicKey(); - public_key_.reset(widevine::RsaPublicKey::Create( + public_key_.reset(RsaPublicKey::Create( std::string(public_key_data.begin(), public_key_data.end()))); ASSERT_TRUE(public_key_); } License LicenseWhiteboxBenchmark::CreateLicense() const { - widevine::TestLicenseBuilder license_builder; + TestLicenseBuilder license_builder; license_builder.AddSigningKey(TestLicenseBuilder::DefaultSigningKey()); // Use secure crypto as it will work with both Decrypt() and // MaskedDecrypt(). - license_builder.AddContentKey( - video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO, - key_id_, key_); + license_builder.AddContentKey(SecurityLevel::kSoftwareSecureCrypto, key_id_, + key_); - widevine::License license; + License license; license_builder.Build(*public_key_, &license); return license; diff --git a/whitebox/api/license_whitebox_security_level_test.cc b/whitebox/api/license_whitebox_security_level_test.cc new file mode 100644 index 0000000..d9ce4b9 --- /dev/null +++ b/whitebox/api/license_whitebox_security_level_test.cc @@ -0,0 +1,100 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#include "api/license_whitebox.h" + +#include +#include +#include + +#include "api/golden_data.h" +#include "api/license_whitebox_test_base.h" +#include "api/test_license_builder.h" +#include "crypto_utils/rsa_key.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace widevine { + +using KeyControlBlock = TestLicenseBuilder::KeyControlBlock; + +class LicenseWhiteboxSecurityLevelTest + : public LicenseWhiteboxTestBase, + public testing::WithParamInterface< + std::tuple> { + protected: + void SetUp() override { + LicenseWhiteboxTestBase::SetUp(); + + std::tie(key_control_block_, security_level_) = GetParam(); + } + + KeyControlBlock key_control_block_; + SecurityLevel security_level_; + + std::vector plaintext_; + size_t plaintext_size_; +}; + +TEST_P(LicenseWhiteboxSecurityLevelTest, CanLoadAndUseKey) { + TestLicenseBuilder builder; + builder.GetSettings().key_control_block = key_control_block_; + builder.AddContentKey(security_level_, golden_data_.CBCCryptoKey().id, + golden_data_.CBCCryptoKey().content->key); + + License license; + builder.Build(*public_key_, &license); + + ASSERT_EQ( + WB_License_ProcessLicenseResponse( + whitebox_, 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(), + license.request.data(), license.request.size()), + WB_RESULT_OK); + + plaintext_size_ = golden_data_.CBCCryptoKey().content->ciphertext.size(); + plaintext_.resize(plaintext_size_); + + // Make sure that the key was loaded successfully even if the security level + // was missing. When missing, SOFTWARE CRYPTO should be used. + // + // Use EXPECT instead of ASSERT so that we can see the result of both decrypt + // calls before failing the test. + + EXPECT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_OK); + + plaintext_size_ = golden_data_.CBCCryptoKey().content->ciphertext.size(); + plaintext_.resize(plaintext_size_); + + EXPECT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_OK); +} + +INSTANTIATE_TEST_SUITE_P( + AllCombinations, + LicenseWhiteboxSecurityLevelTest, + ::testing::Combine( + ::testing::Values(KeyControlBlock::kNone, + KeyControlBlock::kClear, + KeyControlBlock::kEncrypted), + ::testing::Values(SecurityLevel::kUndefined, + SecurityLevel::kSoftwareSecureCrypto))); + +} // namespace widevine diff --git a/whitebox/api/test_key_types.h b/whitebox/api/test_key_types.h index 8ec1144..9518a78 100644 --- a/whitebox/api/test_key_types.h +++ b/whitebox/api/test_key_types.h @@ -18,6 +18,18 @@ using AesKey = std::array; // AES IV can only be 16 bytes. using AesIv = std::array; +// This enum mirrors the enum used in the protobuf, but allows us to add +// value for "undefined" which signals that we don't want the security +// level to appear in the license. +enum class SecurityLevel { + kSoftwareSecureCrypto, + kSoftwareSecureDecode, + kHardwareSecureCrypto, + kHardwareSecureDecode, + kHardwareSecureAll, + kUndefined, +}; + } // 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 96b1d84..c8d5af9 100644 --- a/whitebox/api/test_license_builder.cc +++ b/whitebox/api/test_license_builder.cc @@ -4,6 +4,7 @@ #include #include +#include #include "base/check.h" #include "base/check_op.h" @@ -109,6 +110,34 @@ std::string DeriveIV(const In& context) { return crypto_util::DeriveIv(context_str); } +video_widevine::License_KeyContainer_SecurityLevel SecurityLevelToProto( + SecurityLevel level) { + switch (level) { + case SecurityLevel::kSoftwareSecureCrypto: + return video_widevine:: + License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO; + case SecurityLevel::kSoftwareSecureDecode: + return video_widevine:: + License_KeyContainer_SecurityLevel_SW_SECURE_DECODE; + case SecurityLevel::kHardwareSecureCrypto: + return video_widevine:: + License_KeyContainer_SecurityLevel_HW_SECURE_CRYPTO; + case SecurityLevel::kHardwareSecureDecode: + return video_widevine:: + License_KeyContainer_SecurityLevel_HW_SECURE_DECODE; + case SecurityLevel::kHardwareSecureAll: + return video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_ALL; + + case SecurityLevel::kUndefined: + // If the security level is undefined (in the key container) it would + // default to software secure crypto. + return video_widevine:: + License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO; + } + + CHECK(false) << "Unsupported security level"; +} + KeyControlBlock CreateKeyControlBlock( video_widevine::License_KeyContainer_SecurityLevel level, video_widevine::License_KeyContainer_KeyControl* key_control) { @@ -213,7 +242,13 @@ void AddContentKeyToContainer(const TestLicenseBuilder::ContentKey& key_data, video_widevine::License_KeyContainer* container) { container->set_type(video_widevine::License_KeyContainer_KeyType_CONTENT); container->set_id(key_data.id.data(), key_data.id.size()); - container->set_level(key_data.level); + + // If the security level is undefined it means that no security level should + // appear in the key container. When reading the key container, it will be + // softare secure crypto as that is what the protobuf defaults to. + if (key_data.level != SecurityLevel::kUndefined) { + container->set_level(SecurityLevelToProto(key_data.level)); + } // 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 @@ -228,26 +263,50 @@ void AddContentKeyToContainer(const TestLicenseBuilder::ContentKey& key_data, container->set_key(Encrypt(container_key, key_iv, key)); - const auto key_control_block = - CreateKeyControlBlock(key_data.level, container->mutable_key_control()); + // There are three different ways we can add a key control block. It is + // possible to have no key control block (it is an optional field). This is + // the only option if the security level for the key is missing (the security + // level is non-optional in the key control block). + // + // The key control block can either be in the clear or encrypted. The latest + // version of the license protocol has it in the clear. + switch (settings.key_control_block) { + case TestLicenseBuilder::KeyControlBlock::kNone: + break; - auto* key_control = container->mutable_key_control(); + case TestLicenseBuilder::KeyControlBlock::kClear: { + const auto key_control_block = + CreateKeyControlBlock(SecurityLevelToProto(key_data.level), + container->mutable_key_control()); + auto* key_control = container->mutable_key_control(); + key_control->set_key_control_block(key_control_block.data(), + key_control_block.size()); - // It is only when the key control block is encrypted will the IV be set. - // The key control block is encrypted with the content key. This will no - // longer be the case in OEMCrypto 17. - if (settings.key_control_block == - TestLicenseBuilder::KeyControlBlock::kEncrypted) { - const auto key_control_block_iv = DeriveIV(key_control_block); - const auto encrypted_key_control_block = - Encrypt(key_data.key, key_control_block_iv, key_control_block); + break; + } - key_control->set_iv(key_control_block_iv); - key_control->set_key_control_block(encrypted_key_control_block.data(), - encrypted_key_control_block.size()); - } else { - key_control->set_key_control_block(key_control_block.data(), - key_control_block.size()); + case TestLicenseBuilder::KeyControlBlock::kEncrypted: { + // It is only when the key control block is encrypted will the IV be set. + // The key control block is encrypted with the content key. This will no + // longer be the case in OEMCrypto 17. + const auto key_control_block = + CreateKeyControlBlock(SecurityLevelToProto(key_data.level), + container->mutable_key_control()); + auto* key_control = container->mutable_key_control(); + + const auto key_control_block_iv = DeriveIV(key_control_block); + const auto encrypted_key_control_block = + Encrypt(key_data.key, key_control_block_iv, key_control_block); + + key_control->set_iv(key_control_block_iv); + key_control->set_key_control_block(encrypted_key_control_block.data(), + encrypted_key_control_block.size()); + + break; + } + + default: + CHECK(false) << "Unknown key control block settings"; } } @@ -325,16 +384,14 @@ void TestLicenseBuilder::AddSigningKey(const SigningKey& key) { void TestLicenseBuilder::AddStubbedContentKey() { content_keys_.emplace_back(); auto& key_data = content_keys_.back(); - key_data.level = - video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO; + key_data.level = SecurityLevel::kSoftwareSecureCrypto; key_data.id = {0, 0, 0, 0}; key_data.key = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; } -void TestLicenseBuilder::AddContentKey( - video_widevine::License_KeyContainer_SecurityLevel level, - const KeyId& id, - const AesKey& key) { +void TestLicenseBuilder::AddContentKey(SecurityLevel level, + const KeyId& id, + const AesKey& key) { content_keys_.emplace_back(); auto& key_data = content_keys_.back(); key_data.level = level; diff --git a/whitebox/api/test_license_builder.h b/whitebox/api/test_license_builder.h index f47ce3a..fb3fea4 100644 --- a/whitebox/api/test_license_builder.h +++ b/whitebox/api/test_license_builder.h @@ -33,8 +33,14 @@ class TestLicenseBuilder { AesKey key; - video_widevine::License_KeyContainer_SecurityLevel level = - video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO; + // Default to the security level being undefined in the key container. When + // undefined, the key should be treated as if it is "software secure + // crypto". + // + // However, when we use the key control block, the security level in the key + // container does not matter as the key control block will always have the + // security level. + SecurityLevel level = SecurityLevel::kUndefined; }; // Signing keys must be 512 bits (64 bytes). @@ -65,6 +71,7 @@ class TestLicenseBuilder { }; enum class KeyControlBlock { + kNone, kClear, kEncrypted, }; @@ -88,9 +95,7 @@ class TestLicenseBuilder { // be used with AddContentKey(). void AddStubbedContentKey(); - void AddContentKey(video_widevine::License_KeyContainer_SecurityLevel level, - const KeyId& id, - const AesKey& key); + void AddContentKey(SecurityLevel level, const KeyId& id, const AesKey& 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