From 8a3cbb14c0411a8162637744339c2ba200d1e33c Mon Sep 17 00:00:00 2001 From: Aaron Vaage Date: Mon, 8 Feb 2021 17:14:52 -0800 Subject: [PATCH] Add "Missing Security Level" Test It is possible for the key security level to be omitted from the key container. When this happens, SW_SECURE_CRYPTO should be used as the key's security level (as per the protobuf definition). This only matters when reading the security level from the key container since the security level must appear in the key control block. This change adds a test that will purposely omit the key security level from the key container. --- whitebox/api/BUILD | 1 + whitebox/api/aead_test_data.h | 4 - whitebox/api/golden_data.cc | 12 +- whitebox/api/golden_data.h | 2 +- whitebox/api/license_whitebox_benchmark.cc | 11 +- .../license_whitebox_security_level_test.cc | 100 +++++++++++++++++ whitebox/api/test_key_types.h | 12 ++ whitebox/api/test_license_builder.cc | 105 ++++++++++++++---- whitebox/api/test_license_builder.h | 15 ++- 9 files changed, 216 insertions(+), 46 deletions(-) create mode 100644 whitebox/api/license_whitebox_security_level_test.cc 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