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.
This commit is contained in:
Aaron Vaage
2021-02-08 17:14:52 -08:00
parent d0b37c53dd
commit 8a3cbb14c0
9 changed files with 216 additions and 46 deletions

View File

@@ -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",

View File

@@ -14,10 +14,6 @@ std::vector<uint8_t> GetValidAeadInitData();
// Returns init data that the aead white-box will reject.
std::vector<uint8_t> GetInvalidAeadInitData();
// Returns the matching public key for the data returned by
// GetLicenseInitData(). The format is a DER encoded PKCS#1 RSAPublicKey.
std::vector<uint8_t> GetMatchingLicensePublicKey();
} // namespace widevine
#endif // WHITEBOX_API_AEAD_TEST_DATA_H_

View File

@@ -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_,
};

View File

@@ -22,7 +22,7 @@ class GoldenData {
};
struct Key {
video_widevine::License_KeyContainer_SecurityLevel level;
SecurityLevel level;
KeyId id;
const Content* content;
};

View File

@@ -25,21 +25,20 @@ void LicenseWhiteboxBenchmark::SetUp() {
iv_ = data_source_.Get<kBlockSize>();
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;

View File

@@ -0,0 +1,100 @@
// Copyright 2020 Google LLC. All Rights Reserved.
#include "api/license_whitebox.h"
#include <memory>
#include <string>
#include <vector>
#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<KeyControlBlock, SecurityLevel>> {
protected:
void SetUp() override {
LicenseWhiteboxTestBase::SetUp();
std::tie(key_control_block_, security_level_) = GetParam();
}
KeyControlBlock key_control_block_;
SecurityLevel security_level_;
std::vector<uint8_t> 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

View File

@@ -18,6 +18,18 @@ using AesKey = std::array<uint8_t, 16>;
// AES IV can only be 16 bytes.
using AesIv = std::array<uint8_t, 16>;
// 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_

View File

@@ -4,6 +4,7 @@
#include <array>
#include <ctime>
#include <map>
#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;

View File

@@ -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