Code Drop Two (Update One)
This is the second code drop for the white-box api reference implementation and tests. This corrects the errors in the license white-box reference implementation and implements the remaining test cases. It should be noted that there is one test case missing, the test case for handling ChromeOS's unique policy settings. In order to make the tests easier to create and read, a license builder class was created and golden content and keys were wrapped in their own classes. How key errors are communicated was changed in the API. WB_RESULT_NO_SUCH_KEY and WB_RESULT_WRONG_KEY_TYPE were merged into WB_RESULT_KEY_UNAVAILABLE.
This commit is contained in:
39
api/BUILD
39
api/BUILD
@@ -1,12 +1,13 @@
|
||||
# Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
package(default_visibility = ["//visibility:private"])
|
||||
|
||||
cc_library(
|
||||
name = "result",
|
||||
hdrs = [
|
||||
"result.h",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
@@ -14,6 +15,7 @@ cc_library(
|
||||
hdrs = [
|
||||
"aead_whitebox.h",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":result",
|
||||
],
|
||||
@@ -24,6 +26,7 @@ cc_library(
|
||||
hdrs = [
|
||||
"license_whitebox.h",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":result",
|
||||
],
|
||||
@@ -35,13 +38,28 @@ cc_library(
|
||||
hdrs = [
|
||||
"test_data.h",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "license_test_helper",
|
||||
name = "golden_data",
|
||||
testonly = True,
|
||||
srcs = ["license_test_helper.cc"],
|
||||
hdrs = ["license_test_helper.h"],
|
||||
srcs = [
|
||||
"golden_data.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"golden_data.h",
|
||||
],
|
||||
deps = [
|
||||
"//chromium_deps/cdm/protos:license_protocol_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "license_builder",
|
||||
testonly = True,
|
||||
srcs = ["license_builder.cc"],
|
||||
hdrs = ["license_builder.h"],
|
||||
deps = [
|
||||
"//chromium_deps/base",
|
||||
"//chromium_deps/cdm/keys:dev_certs",
|
||||
@@ -57,9 +75,11 @@ cc_library(
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"aead_whitebox_create_test.cc",
|
||||
"aead_whitebox_cross_instance_test.cc",
|
||||
"aead_whitebox_decrypt_test.cc",
|
||||
"aead_whitebox_encrypt_test.cc",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":aead_whitebox",
|
||||
":test_data",
|
||||
@@ -74,11 +94,20 @@ cc_library(
|
||||
"license_whitebox_create_test.cc",
|
||||
"license_whitebox_decrypt_test.cc",
|
||||
"license_whitebox_get_secret_string_test.cc",
|
||||
"license_whitebox_masked_decrypt_test.cc",
|
||||
"license_whitebox_process_license_response_test.cc",
|
||||
"license_whitebox_sign_license_request_test.cc",
|
||||
"license_whitebox_sign_renewal_request_test.cc",
|
||||
"license_whitebox_test_base.cc",
|
||||
"license_whitebox_verify_renewal_response_test.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"license_whitebox_test_base.h",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":license_test_helper",
|
||||
":golden_data",
|
||||
":license_builder",
|
||||
":license_whitebox",
|
||||
":test_data",
|
||||
"//chromium_deps/cdm/keys:api",
|
||||
|
||||
125
api/aead_whitebox_cross_instance_test.cc
Normal file
125
api/aead_whitebox_cross_instance_test.cc
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "api/aead_whitebox.h"
|
||||
#include "api/test_data.h"
|
||||
#include "testing/include/gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
// These tests focus on functionality that should and should not exist between
|
||||
// different Aead White-box instances. They assume that all other functionality
|
||||
// of the white-box has already been verified.
|
||||
class AeadWhiteboxCrossInstanceTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override { init_data_ = GetValidAeadInitData(); }
|
||||
|
||||
void TearDown() override {
|
||||
// Even if the pointer is null, as per the API, it should be safe to call
|
||||
// WB_Aead_Delete().
|
||||
WB_Aead_Delete(whitebox_a_);
|
||||
WB_Aead_Delete(whitebox_b_);
|
||||
}
|
||||
|
||||
WB_Aead_Whitebox* whitebox_a_ = nullptr;
|
||||
WB_Aead_Whitebox* whitebox_b_ = nullptr;
|
||||
|
||||
// Save a copy of the init data so that it is easier to create instances. It
|
||||
// would be nice if this could be const, but we need to set it in SetUp().
|
||||
std::vector<uint8_t> init_data_;
|
||||
|
||||
// We need two different contexts so that we can have two unique instances.
|
||||
// Each context was generated using a random number generator. There is no
|
||||
// meaning to the bytes, the only condition is that they be different from
|
||||
// each other.
|
||||
const std::vector<uint8_t> context_a_ = {
|
||||
0xa0, 0xaf, 0x75, 0xbf, 0x6b, 0xff, 0x2f, 0x76,
|
||||
0x90, 0x47, 0xc4, 0x21, 0xb5, 0xf2, 0xb9, 0x62,
|
||||
};
|
||||
|
||||
const std::vector<uint8_t> context_b_ = {
|
||||
0xfe, 0x6d, 0x01, 0x45, 0xbd, 0xa9, 0xcc, 0xd4,
|
||||
0x74, 0xaf, 0xca, 0xf8, 0xf9, 0x79, 0x2d, 0xf4,
|
||||
};
|
||||
|
||||
// Both instances will always use the same plaintext. Since we are going to
|
||||
// verify decryption, we need the plaintext to be recognizable after it is
|
||||
// encrypted and then decrypted. To make it easier to read, we also avoided
|
||||
// random data.
|
||||
const std::vector<uint8_t> common_plaintext_ = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
};
|
||||
|
||||
// We don't know how big the ciphertext will be, so we don't know how much
|
||||
// memory to allocate for our buffers. Since our plaintext is small, 256
|
||||
// should be large enough.
|
||||
const size_t safe_buffer_size_ = 256;
|
||||
};
|
||||
|
||||
// Create two instances, each with unique contexts. Since they have different
|
||||
// contexts, they should not be able to decrypt each other's data.
|
||||
TEST_F(AeadWhiteboxCrossInstanceTest,
|
||||
DataVerificationErrorForDifferentContext) {
|
||||
ASSERT_EQ(WB_Aead_Create(init_data_.data(), init_data_.size(),
|
||||
context_a_.data(), context_a_.size(), &whitebox_a_),
|
||||
WB_RESULT_OK);
|
||||
ASSERT_EQ(WB_Aead_Create(init_data_.data(), init_data_.size(),
|
||||
context_b_.data(), context_b_.size(), &whitebox_b_),
|
||||
WB_RESULT_OK);
|
||||
|
||||
size_t ciphertext_size = safe_buffer_size_;
|
||||
std::vector<uint8_t> ciphertext(ciphertext_size);
|
||||
|
||||
size_t plaintext_size = safe_buffer_size_;
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
|
||||
// The flow looks like:
|
||||
// |common plaintext| --> [WB A] --> ciphertext --> [WB B] --> plaintext
|
||||
//
|
||||
// But it should fail after white-box tries to decrypt it, so |plaintext|
|
||||
// should never get written to.
|
||||
ASSERT_EQ(WB_Aead_Encrypt(whitebox_a_, common_plaintext_.data(),
|
||||
common_plaintext_.size(), ciphertext.data(),
|
||||
&ciphertext_size),
|
||||
WB_RESULT_OK);
|
||||
ciphertext.resize(ciphertext_size);
|
||||
|
||||
ASSERT_EQ(WB_Aead_Decrypt(whitebox_b_, ciphertext.data(), ciphertext.size(),
|
||||
plaintext.data(), &plaintext_size),
|
||||
WB_RESULT_DATA_VERIFICATION_ERROR);
|
||||
plaintext.resize(plaintext_size);
|
||||
}
|
||||
|
||||
// Create two instances, each using the same contexts. Since they have the same
|
||||
// context, they should be able to decrypt each other's data.
|
||||
TEST_F(AeadWhiteboxCrossInstanceTest, DataVerificationErrorForCommonContext) {
|
||||
ASSERT_EQ(WB_Aead_Create(init_data_.data(), init_data_.size(),
|
||||
context_a_.data(), context_a_.size(), &whitebox_a_),
|
||||
WB_RESULT_OK);
|
||||
ASSERT_EQ(WB_Aead_Create(init_data_.data(), init_data_.size(),
|
||||
context_a_.data(), context_a_.size(), &whitebox_b_),
|
||||
WB_RESULT_OK);
|
||||
|
||||
size_t ciphertext_size = safe_buffer_size_;
|
||||
std::vector<uint8_t> ciphertext(ciphertext_size);
|
||||
|
||||
size_t plaintext_size = safe_buffer_size_;
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
|
||||
// The flow looks like:
|
||||
// |common plaintext| --> [WB A] --> ciphertext --> [WB B] --> plaintext
|
||||
ASSERT_EQ(WB_Aead_Encrypt(whitebox_a_, common_plaintext_.data(),
|
||||
common_plaintext_.size(), ciphertext.data(),
|
||||
&ciphertext_size),
|
||||
WB_RESULT_OK);
|
||||
ciphertext.resize(ciphertext_size);
|
||||
|
||||
ASSERT_EQ(WB_Aead_Decrypt(whitebox_b_, ciphertext.data(), ciphertext.size(),
|
||||
plaintext.data(), &plaintext_size),
|
||||
WB_RESULT_OK);
|
||||
plaintext.resize(plaintext_size);
|
||||
|
||||
ASSERT_EQ(plaintext, common_plaintext_);
|
||||
}
|
||||
} // namespace
|
||||
92
api/golden_data.cc
Normal file
92
api/golden_data.cc
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
#include "api/golden_data.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
GoldenData::GoldenData() {
|
||||
// Content generated with:
|
||||
// openssl aes-128-cbc -e -in data.txt
|
||||
// -K EBDD62F16814D27B68EF122AFCE4AE3C
|
||||
// -iv 30313233343536373839303132333435 | xxd -i
|
||||
// Extra padding was stripped off.
|
||||
cbc_content_ = {
|
||||
/* plaintext */ {'t', 'h', 'i', 's', ' ', 'i', 's', ' ', 't', 'h', 'e',
|
||||
' ', 'p', 'l', 'a', 'i', 'n', 't', 'e', 'x', 't', ' ',
|
||||
':', ' ', '3', '2', ' ', 'b', 'y', 't', 'e', 's'},
|
||||
/* ciphertext */
|
||||
{0x5e, 0x60, 0x0d, 0x3c, 0x29, 0xb9, 0x49, 0x4c, 0x65, 0x67, 0x7e,
|
||||
0x87, 0x82, 0x9d, 0x47, 0x58, 0xb9, 0x86, 0xd0, 0x39, 0x6a, 0x67,
|
||||
0x2c, 0x53, 0xe9, 0xbc, 0x99, 0x5b, 0x23, 0x34, 0x9f, 0xf8},
|
||||
/* key */
|
||||
{0xEB, 0xDD, 0x62, 0xF1, 0x68, 0x14, 0xD2, 0x7B, 0x68, 0xEF, 0x12, 0x2A,
|
||||
0xFC, 0xE4, 0xAE, 0x3C},
|
||||
/* iv */
|
||||
{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31,
|
||||
0x32, 0x33, 0x34, 0x35},
|
||||
};
|
||||
|
||||
cbc_crypto_key_ = {
|
||||
video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO,
|
||||
{0xFF, 0, 0, 0},
|
||||
&cbc_content_,
|
||||
};
|
||||
|
||||
cbc_decode_key_ = {
|
||||
video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_DECODE,
|
||||
{0xFF, 1, 0, 0},
|
||||
&cbc_content_,
|
||||
};
|
||||
|
||||
cbc_hardware_key_ = {
|
||||
video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_CRYPTO,
|
||||
{0xFF, 2, 0, 0},
|
||||
&cbc_content_,
|
||||
};
|
||||
|
||||
// Content generated with:
|
||||
// openssl aes-128-ctr -e -in data.txt
|
||||
// -K dd3c6cd4ea73b99d55f2e0357e1f560f
|
||||
// -iv d50c08b31fc09e9e748431ca972334e6 | xxd -i
|
||||
ctr_content_ = {
|
||||
/* 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', '.'},
|
||||
/* ciphertext */
|
||||
{0x5d, 0x83, 0xdd, 0xb9, 0xed, 0x18, 0x2f, 0x10, 0xbf, 0x6f, 0x4d,
|
||||
0xb0, 0xb3, 0xeb, 0x0d, 0x20, 0xd7, 0x7e, 0x9a, 0x3a, 0xc4, 0x41,
|
||||
0xcf, 0x0a, 0xb3, 0xae, 0x02, 0x01, 0x0a, 0xf2, 0x72, 0x72},
|
||||
/* key */
|
||||
{0xdd, 0x3c, 0x6c, 0xd4, 0xea, 0x73, 0xb9, 0x9d, 0x55, 0xf2, 0xe0, 0x35,
|
||||
0x7e, 0x1f, 0x56, 0x0f},
|
||||
/* iv */
|
||||
{0xd5, 0x0c, 0x08, 0xb3, 0x1f, 0xc0, 0x9e, 0x9e, 0x74, 0x84, 0x31, 0xca,
|
||||
0x97, 0x23, 0x34, 0xe6},
|
||||
};
|
||||
|
||||
ctr_crypto_key_ = {
|
||||
video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO,
|
||||
{0xFF, 3, 0, 0},
|
||||
&ctr_content_,
|
||||
};
|
||||
|
||||
ctr_decode_key_ = {
|
||||
video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_DECODE,
|
||||
{0xFF, 4, 0, 0},
|
||||
&ctr_content_,
|
||||
};
|
||||
|
||||
ctr_hardware_key_ = {
|
||||
video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_CRYPTO,
|
||||
{0xFF, 5, 0, 0},
|
||||
&ctr_content_,
|
||||
};
|
||||
}
|
||||
|
||||
void GoldenData::MakeKeyIdDifferent(std::vector<uint8_t>* key_id) const {
|
||||
// All our internal key ids start with 0xFF, so pushing something that is not
|
||||
// 0xFF to the front will ensure that they don't collide.
|
||||
key_id->insert(key_id->begin(), 0xAB);
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
61
api/golden_data.h
Normal file
61
api/golden_data.h
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
#ifndef WHITEBOX_API_GOLDEN_DATA_H_
|
||||
#define WHITEBOX_API_GOLDEN_DATA_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "cdm/protos/license_protocol.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
class GoldenData {
|
||||
public:
|
||||
struct Content {
|
||||
std::vector<uint8_t> plaintext;
|
||||
std::vector<uint8_t> ciphertext;
|
||||
std::vector<uint8_t> key;
|
||||
std::vector<uint8_t> iv;
|
||||
};
|
||||
|
||||
struct Key {
|
||||
video_widevine::License_KeyContainer_SecurityLevel level;
|
||||
std::vector<uint8_t> id;
|
||||
const Content* content;
|
||||
};
|
||||
|
||||
GoldenData();
|
||||
|
||||
const Content& CBCContent() const { return cbc_content_; }
|
||||
const Key& CBCCryptoKey() const { return cbc_crypto_key_; }
|
||||
const Key& CBCDecodeKey() const { return cbc_decode_key_; }
|
||||
const Key& CBCHardwareKey() const { return cbc_hardware_key_; }
|
||||
|
||||
const Content& CTRContent() const { return ctr_content_; }
|
||||
const Key& CTRCryptoKey() const { return ctr_crypto_key_; }
|
||||
const Key& CTRDecodeKey() const { return ctr_decode_key_; }
|
||||
const Key& CTRHardwareKey() const { return ctr_hardware_key_; }
|
||||
|
||||
// When a test needs to define a key id that does not conflict with any key
|
||||
// ids defined in the golden data, it should use this to update their key id
|
||||
// by prepending a single byte to ensure it won't collide with any of the
|
||||
// internal key ids.
|
||||
void MakeKeyIdDifferent(std::vector<uint8_t>* key_id) const;
|
||||
|
||||
private:
|
||||
Content cbc_content_;
|
||||
Key cbc_crypto_key_;
|
||||
Key cbc_decode_key_;
|
||||
Key cbc_hardware_key_;
|
||||
|
||||
Content ctr_content_;
|
||||
Key ctr_crypto_key_;
|
||||
Key ctr_decode_key_;
|
||||
Key ctr_hardware_key_;
|
||||
};
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
#endif // WHITEBOX_API_GOLDEN_DATA_H_
|
||||
233
api/license_builder.cc
Normal file
233
api/license_builder.cc
Normal file
@@ -0,0 +1,233 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
#include "api/license_builder.h"
|
||||
|
||||
#include <ctime>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "cdm/keys/certs.h"
|
||||
#include "crypto_utils/aes_cbc_encryptor.h"
|
||||
#include "crypto_utils/crypto_util.h"
|
||||
#include "crypto_utils/sha_util.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
namespace {
|
||||
|
||||
void InitializeRequest(video_widevine::LicenseRequest* request) {
|
||||
request->set_request_time(std::time(nullptr)); // Use time=now.
|
||||
|
||||
auto* client_id = request->mutable_client_id();
|
||||
auto* capabilities = client_id->mutable_client_capabilities();
|
||||
capabilities->set_video_resolution_constraints(true);
|
||||
capabilities->set_client_token(true);
|
||||
capabilities->set_session_token(false);
|
||||
capabilities->set_max_hdcp_version(
|
||||
video_widevine::ClientIdentification::ClientCapabilities::HDCP_V1);
|
||||
|
||||
auto* client_info = client_id->add_client_info();
|
||||
client_info->set_name("architecture_name");
|
||||
client_info->set_value("x86-64");
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name("company_name");
|
||||
client_info->set_value("Google");
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name("model_name");
|
||||
client_info->set_value("ChromeCDM");
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name("platform_name");
|
||||
client_info->set_value("Windows");
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name("widevine_cdm_version");
|
||||
client_info->set_value("4.10.1686.29");
|
||||
|
||||
client_id->set_type(
|
||||
video_widevine::ClientIdentification_TokenType_DRM_DEVICE_CERTIFICATE);
|
||||
client_id->set_token(wvcdm::kRsaDrmCertificate,
|
||||
wvcdm::kRsaDrmCertificateSize);
|
||||
|
||||
auto* content_id = request->mutable_content_id();
|
||||
auto* webm_key_id = content_id->mutable_webm_key_id();
|
||||
webm_key_id->set_license_type(video_widevine::STREAMING);
|
||||
webm_key_id->set_request_id("REQUEST_ID");
|
||||
webm_key_id->set_header("01234567890123456");
|
||||
|
||||
request->set_protocol_version(video_widevine::VERSION_2_1);
|
||||
request->set_type(video_widevine::LicenseRequest::NEW);
|
||||
}
|
||||
|
||||
void InitializeResponse(const video_widevine::LicenseRequest& request,
|
||||
video_widevine::License* response) {
|
||||
auto* id = response->mutable_id();
|
||||
id->set_request_id("REQUEST_ID");
|
||||
id->set_session_id("SESSION_ID");
|
||||
id->set_type(video_widevine::STREAMING);
|
||||
id->set_version(0);
|
||||
|
||||
auto* policy = response->mutable_policy();
|
||||
policy->set_can_play(true);
|
||||
policy->set_can_persist(false);
|
||||
policy->set_can_renew(true);
|
||||
policy->set_license_duration_seconds(600);
|
||||
policy->set_renewal_delay_seconds(30);
|
||||
policy->set_renewal_retry_interval_seconds(10);
|
||||
policy->set_renew_with_usage(false);
|
||||
|
||||
response->set_license_start_time(request.request_time());
|
||||
response->set_remote_attestation_verified(false);
|
||||
response->set_platform_verification_status(
|
||||
video_widevine::PlatformVerificationStatus::PLATFORM_UNVERIFIED);
|
||||
}
|
||||
|
||||
std::string EncryptKey(const std::string& key,
|
||||
const std::string& iv,
|
||||
const std::vector<uint8_t>& plaintext) {
|
||||
AesCbcEncryptor encryptor;
|
||||
encryptor.SetKey(reinterpret_cast<const uint8_t*>(key.data()), key.size());
|
||||
|
||||
std::vector<uint8_t> ciphertext(plaintext.size());
|
||||
CHECK(encryptor.Encrypt(reinterpret_cast<const uint8_t*>(iv.data()),
|
||||
iv.size(), plaintext.data(), plaintext.size(),
|
||||
ciphertext.data()));
|
||||
|
||||
return std::string(ciphertext.begin(), ciphertext.end());
|
||||
}
|
||||
|
||||
std::string DeriveIV(const std::vector<uint8_t>& context) {
|
||||
const std::string context_str(context.begin(), context.end());
|
||||
return crypto_util::DeriveIv(context_str);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
std::vector<uint8_t> LicenseBuilder::NoPadding() {
|
||||
return {};
|
||||
}
|
||||
|
||||
// static
|
||||
std::vector<uint8_t> LicenseBuilder::PKSC8Padding() {
|
||||
return {
|
||||
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
|
||||
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
|
||||
};
|
||||
}
|
||||
|
||||
// static
|
||||
std::vector<uint8_t> LicenseBuilder::DefaultSigningKey() {
|
||||
return {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
||||
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
|
||||
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
|
||||
};
|
||||
}
|
||||
|
||||
LicenseBuilder::LicenseBuilder() {
|
||||
DCHECK_EQ(session_key_.size(), 16u);
|
||||
|
||||
// Initialize the request and the response with the static fields that will
|
||||
// be common across all licences.
|
||||
InitializeRequest(&request_);
|
||||
InitializeResponse(request_, &response_);
|
||||
|
||||
serialized_request_ = request_.SerializeAsString();
|
||||
container_key_ = crypto_util::DeriveKey(
|
||||
session_key_, crypto_util::kWrappingKeyLabel, serialized_request_,
|
||||
crypto_util::kWrappingKeySizeBits);
|
||||
}
|
||||
|
||||
void LicenseBuilder::AddSigningKey(const std::vector<uint8_t>& key,
|
||||
const std::vector<uint8_t>& padding) {
|
||||
DCHECK_EQ(key.size(), 64u); // 512 bits
|
||||
|
||||
auto* container = response_.add_key();
|
||||
container->set_type(video_widevine::License_KeyContainer_KeyType_SIGNING);
|
||||
|
||||
// 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);
|
||||
container->set_iv(key_iv);
|
||||
|
||||
std::vector<uint8_t> final_key = key;
|
||||
final_key.insert(final_key.end(), padding.begin(), padding.end());
|
||||
container->set_key(EncryptKey(container_key_, key_iv, final_key));
|
||||
}
|
||||
|
||||
void LicenseBuilder::AddStubbedContentKey() {
|
||||
auto* container = response_.add_key();
|
||||
|
||||
container->set_type(video_widevine::License_KeyContainer_KeyType_CONTENT);
|
||||
container->set_id("stubbed-content-key");
|
||||
container->set_level(
|
||||
video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO);
|
||||
container->set_iv("0000000000000000");
|
||||
|
||||
// We don't bother encrypting the key, it should never be used and there is no
|
||||
// way to verify it.
|
||||
container->set_key("0000000000000000");
|
||||
}
|
||||
|
||||
void LicenseBuilder::AddContentKey(
|
||||
video_widevine::License_KeyContainer_SecurityLevel level,
|
||||
const std::vector<uint8_t>& key_id,
|
||||
const std::vector<uint8_t>& key,
|
||||
const std::vector<uint8_t>& padding) {
|
||||
DCHECK_GT(key_id.size(), 0u);
|
||||
DCHECK_EQ(key.size(), 16u);
|
||||
|
||||
auto* container = response_.add_key();
|
||||
|
||||
container->set_type(video_widevine::License_KeyContainer_KeyType_CONTENT);
|
||||
container->set_id(key_id.data(), key_id.size());
|
||||
container->set_level(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
|
||||
// deterministic.
|
||||
const auto key_iv = DeriveIV(key);
|
||||
container->set_iv(key_iv);
|
||||
|
||||
std::vector<uint8_t> final_key = key;
|
||||
final_key.insert(final_key.end(), padding.begin(), padding.end());
|
||||
container->set_key(EncryptKey(container_key_, key_iv, final_key));
|
||||
}
|
||||
|
||||
void LicenseBuilder::AddOperatorSessionKey(const std::vector<uint8_t>& key_id) {
|
||||
DCHECK_GT(key_id.size(), 0u);
|
||||
|
||||
// We only set the type and id because the key should not actually be used.
|
||||
auto* container = response_.add_key();
|
||||
container->set_type(
|
||||
video_widevine::License_KeyContainer_KeyType_OPERATOR_SESSION);
|
||||
container->set_id(key_id.data(), key_id.size());
|
||||
}
|
||||
|
||||
void LicenseBuilder::Build(const RsaPublicKey& public_key,
|
||||
License* license) const {
|
||||
DCHECK(license);
|
||||
|
||||
const std::string message_str = response_.SerializeAsString();
|
||||
|
||||
std::string signing_key = crypto_util::DeriveKey(
|
||||
session_key_, crypto_util::kSigningKeyLabel, serialized_request_,
|
||||
crypto_util::kSigningKeySizeBits * 2);
|
||||
signing_key.resize(crypto_util::kSigningKeySizeBytes);
|
||||
|
||||
const std::string signature_str =
|
||||
crypto_util::CreateSignatureHmacSha256(signing_key, message_str);
|
||||
|
||||
std::string session_key_str;
|
||||
CHECK(public_key.Encrypt(session_key_, &session_key_str));
|
||||
|
||||
license->request = std::vector<uint8_t>(serialized_request_.begin(),
|
||||
serialized_request_.end());
|
||||
license->message =
|
||||
std::vector<uint8_t>(message_str.begin(), message_str.end());
|
||||
license->signature =
|
||||
std::vector<uint8_t>(signature_str.begin(), signature_str.end());
|
||||
license->session_key =
|
||||
std::vector<uint8_t>(session_key_str.begin(), session_key_str.end());
|
||||
}
|
||||
} // namespace widevine
|
||||
70
api/license_builder.h
Normal file
70
api/license_builder.h
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
#ifndef WHITEBOX_API_LICENSE_BUILDER_H_
|
||||
#define WHITEBOX_API_LICENSE_BUILDER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cdm/protos/license_protocol.pb.h"
|
||||
#include "crypto_utils/rsa_key.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
struct License {
|
||||
std::vector<uint8_t> request;
|
||||
std::vector<uint8_t> message;
|
||||
std::vector<uint8_t> signature;
|
||||
|
||||
// |session_key_| encrypted using the public key. The white-box expects the
|
||||
// session key to be encrypted, so we use the name "session_key_" (even if it
|
||||
// is encrypted), we omit the term "encrypted" to match the naming in the API.
|
||||
std::vector<uint8_t> session_key;
|
||||
};
|
||||
|
||||
class LicenseBuilder {
|
||||
public:
|
||||
// Returns padding data the can be used as |padding| when calling
|
||||
// AddSigningKey() or AddContentKey().
|
||||
static std::vector<uint8_t> NoPadding();
|
||||
static std::vector<uint8_t> PKSC8Padding();
|
||||
|
||||
// Returns a default signing key that can be used with AddSigningKey().
|
||||
static std::vector<uint8_t> DefaultSigningKey();
|
||||
|
||||
LicenseBuilder();
|
||||
|
||||
void AddSigningKey(const std::vector<uint8_t>& key,
|
||||
const std::vector<uint8_t>& padding = NoPadding());
|
||||
|
||||
// Add a content key so that there is some key in the license. This should not
|
||||
// be used with AddContentKey().
|
||||
void AddStubbedContentKey();
|
||||
|
||||
void AddContentKey(video_widevine::License_KeyContainer_SecurityLevel level,
|
||||
const std::vector<uint8_t>& key_id,
|
||||
const std::vector<uint8_t>& key,
|
||||
const std::vector<uint8_t>& padding = NoPadding());
|
||||
|
||||
// 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.
|
||||
void AddOperatorSessionKey(const std::vector<uint8_t>& key_id);
|
||||
|
||||
// Gets the serialized license request and response (in components) that would
|
||||
// have been used in the license exchange.
|
||||
void Build(const RsaPublicKey& public_key, License* license) const;
|
||||
|
||||
private:
|
||||
const std::string session_key_ = "0123456789ABCDEF";
|
||||
|
||||
video_widevine::LicenseRequest request_;
|
||||
video_widevine::License response_;
|
||||
std::string serialized_request_;
|
||||
std::string container_key_;
|
||||
};
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
#endif // WHITEBOX_API_LICENSE_BUILDER_H_
|
||||
@@ -80,8 +80,12 @@ WB_Result WB_License_SignLicenseRequest(const WB_License_Whitebox* whitebox,
|
||||
uint8_t* signature,
|
||||
size_t* signature_size);
|
||||
|
||||
// Verifies a license response using HMAC and the server signing key, extract
|
||||
// the content keys and signing keys, and load them into the white-box.
|
||||
// Verifies a license response using HMAC and the server signing key.
|
||||
//
|
||||
// Extracts and loads content and signing keys for use. Any content keys that
|
||||
// exceed the security levels permitted by the instance, will be thrown away,
|
||||
// but the key ids are retained (see WB_RESULT_INSUFFICIENT_SECURITY_LEVEL). All
|
||||
// non-content keys and non-signing keys will be thrown away.
|
||||
//
|
||||
// This function can only be called once per white-box instance. To parse a new
|
||||
// license response, a new white-box instance must be used.
|
||||
@@ -110,16 +114,17 @@ WB_Result WB_License_SignLicenseRequest(const WB_License_Whitebox* whitebox,
|
||||
// WB_RESULT_OK if the response was verified and the keys were loaded into
|
||||
// |whitebox|.
|
||||
//
|
||||
// WB_RESULT_INVALID_SIGNATURE if |message|'s signature does not match
|
||||
// |signature|.
|
||||
//
|
||||
// WB_RESULT_INVALID_PARAMETER if |whitebox| was null, if |message| was null,
|
||||
// if |message_size| was zero, if |message| did not conform to the expected
|
||||
// format, if |signature| was null, if |signature_size| was incorrect, if
|
||||
// |session_key| was null, if |session_key_size| was incorrect, if
|
||||
// |session_key| could not be unwrapped correctly, if |license_request| was
|
||||
// null, if |license_request_size| was zero, or if |license_request| was
|
||||
// malformed.
|
||||
// null, or if |license_request_size| was zero.
|
||||
//
|
||||
// WB_RESULT_INVALID_SIGNATURE if |message|'s signature does not match
|
||||
// |signature|.
|
||||
//
|
||||
// WB_RESULT_INVALID_STATE if a license has already been loaded.
|
||||
WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox,
|
||||
const uint8_t* message,
|
||||
size_t message_size,
|
||||
@@ -186,7 +191,7 @@ WB_Result WB_License_SignRenewalRequest(const WB_License_Whitebox* whitebox,
|
||||
// WB_RESULT_INVALID_SIGNATURE if |message|'s signature did not match
|
||||
// |signature|.
|
||||
//
|
||||
// WB_RESULT_INVALID_STATE if |whitebox| had no keys.
|
||||
// WB_RESULT_INVALID_STATE if |whitebox| had not loaded a license.
|
||||
WB_Result WB_License_VerifyRenewalResponse(const WB_License_Whitebox* whitebox,
|
||||
const uint8_t* message,
|
||||
size_t message_size,
|
||||
@@ -225,19 +230,16 @@ WB_Result WB_License_VerifyRenewalResponse(const WB_License_Whitebox* whitebox,
|
||||
// if |key_id| was null, if |key_id_size| was zero, if |secret_string| was
|
||||
// null, or if |secret_string_size| was null.
|
||||
//
|
||||
// WB_RESULT_NO_SUCH_KEY if |key_id| did not match any keys in |whitebox|.
|
||||
// WB_RESULT_INSUFFICIENT_SECURITY_LEVEL if |key_id| referred to a key from
|
||||
// the license, but the |whitebox| was not allowed to use it.
|
||||
//
|
||||
// WB_RESULT_WRONG_KEY_TYPE if |key_id| referred to a key in |whitebox| but the
|
||||
// key was not a content key.
|
||||
//
|
||||
// WB_RESULT_INSUFFICIENT_SECURITY_LEVEL if |key_id| refers to a key in
|
||||
// |whitebox| but the key's security level was neither SW_SECURE_CRYPTO nor
|
||||
// SW_SECURE_DECODE.
|
||||
// WB_RESULT_KEY_UNAVAILABLE if |key_id| did not match a content key from the
|
||||
// loaded license.
|
||||
//
|
||||
// WB_RESULT_BUFFER_TOO_SMALL if |secret_string_size| (as input) was less than
|
||||
// the required size.
|
||||
//
|
||||
// WB_RESULT_INVALID_STATE if |whitebox| had no keys.
|
||||
// WB_RESULT_INVALID_STATE if |whitebox| had not loaded a license.
|
||||
WB_Result WB_License_GetSecretString(const WB_License_Whitebox* whitebox,
|
||||
WB_CipherMode mode,
|
||||
const uint8_t* key_id,
|
||||
@@ -283,18 +285,16 @@ WB_Result WB_License_GetSecretString(const WB_License_Whitebox* whitebox,
|
||||
// if |input_data_size| was invalid, if |iv| was null, if |iv_size| was
|
||||
// invalid, if |output_data| was null, or if |output_data_size| was null.
|
||||
//
|
||||
// WB_RESULT_NO_SUCH_KEY if |key_id| matches no key in |whitebox|.
|
||||
// WB_RESULT_INSUFFICIENT_SECURITY_LEVEL if |key_id| referred to a key from
|
||||
// the license, but the |whitebox| was not allowed to use it.
|
||||
//
|
||||
// WB_RESULT_WRONG_KEY_TYPE if |key_id| referred to a key in |whitebox| but
|
||||
// the key was not a content key.
|
||||
//
|
||||
// WB_RESULT_INSUFFICIENT_SECURITY_LEVEL if |key_id| referred to a key in
|
||||
// |whitebox| but the key's security level was not SW_SECURE_CRYPTO.
|
||||
//
|
||||
// WB_RESULT_INVALID_STATE if |whitebox| had no keys.
|
||||
// WB_RESULT_KEY_UNAVAILABLE if |key_id| did not match a content key from the
|
||||
// loaded license.
|
||||
//
|
||||
// WB_RESULT_BUFFER_TOO_SMALL if |output_data_size| (as input) was less than
|
||||
// the required size.
|
||||
//
|
||||
// WB_RESULT_INVALID_STATE if |whitebox| had not loaded a license.
|
||||
WB_Result WB_License_Decrypt(const WB_License_Whitebox* whitebox,
|
||||
WB_CipherMode mode,
|
||||
const uint8_t* key_id,
|
||||
@@ -346,21 +346,19 @@ WB_Result WB_License_Decrypt(const WB_License_Whitebox* whitebox,
|
||||
// WB_RESULT_INVALID_PARAMETER if |whitebox| was null, if |mode| was invalid,
|
||||
// if |key_id| was null, if |key_id_size| was zero, if |input_data| was null,
|
||||
// if |input_data_size| was invalid, if |iv| was null, if |iv_size| was
|
||||
// invalid, if |output_data| was null, or if |output_data_size| was null.
|
||||
// invalid, if |masked_output_data| was null, or if |masked_output_data_size|
|
||||
// was null.
|
||||
//
|
||||
// WB_RESULT_NO_SUCH_KEY if |key_id| matches no key in |whitebox|.
|
||||
// WB_RESULT_INSUFFICIENT_SECURITY_LEVEL if |key_id| referred to a key from
|
||||
// the license, but the |whitebox| was not allowed to use it.
|
||||
//
|
||||
// WB_RESULT_WRONG_KEY_TYPE if |key_id| referred to a key in |whitebox| but the
|
||||
// key was not a content key.
|
||||
//
|
||||
// WB_RESULT_INSUFFICIENT_SECURITY_LEVEL if |key_id| referred to a key in
|
||||
// |whitebox| but the key's security level was neither SW_SECURE_CRYPTO nor
|
||||
// SW_SECURE_DECODE.
|
||||
//
|
||||
// WB_RESULT_INVALID_STATE if |whitebox| had no keys.
|
||||
// WB_RESULT_KEY_UNAVAILABLE if |key_id| did not match a content key from the
|
||||
// loaded license.
|
||||
//
|
||||
// WB_RESULT_BUFFER_TOO_SMALL if |masked_output_data_size| (as input) was less
|
||||
// than the required size.
|
||||
//
|
||||
// WB_RESULT_INVALID_STATE if |whitebox| had not loaded a license.
|
||||
WB_Result WB_License_MaskedDecrypt(const WB_License_Whitebox* whitebox,
|
||||
WB_CipherMode mode,
|
||||
const uint8_t* key_id,
|
||||
|
||||
@@ -6,315 +6,470 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "api/golden_data.h"
|
||||
#include "api/license_builder.h"
|
||||
#include "api/license_whitebox_test_base.h"
|
||||
#include "api/test_data.h"
|
||||
#include "crypto_utils/rsa_key.h"
|
||||
#include "testing/include/gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
namespace widevine {
|
||||
|
||||
// All decrypt tests require a valid (initialized) white-box. However, not all
|
||||
// tests need to load a license, so that is left out of SetUp().
|
||||
class LicenseWhiteboxDecryptTest : public ::testing::Test {
|
||||
class LicenseWhiteboxDecryptTest : public LicenseWhiteboxTestBase {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// All of our tests here require a valid whitebox.
|
||||
std::vector<uint8_t> init_data = GetLicenseInitData();
|
||||
ASSERT_GT(init_data.size(), 0u);
|
||||
ASSERT_EQ(WB_License_Create(init_data.data(), init_data.size(), &whitebox_),
|
||||
LicenseWhiteboxTestBase::SetUp();
|
||||
|
||||
// Because we are using the same buffer for both CTR and CBC, need to make
|
||||
// sure that it is large enough for either one.
|
||||
plaintext_size_ = std::max(golden_data_.CBCContent().ciphertext.size(),
|
||||
golden_data_.CTRContent().ciphertext.size());
|
||||
plaintext_.resize(plaintext_size_);
|
||||
|
||||
golden_data_.MakeKeyIdDifferent(&non_content_key_id_);
|
||||
golden_data_.MakeKeyIdDifferent(&missing_key_id_);
|
||||
}
|
||||
|
||||
void LoadLicense(const std::vector<uint8_t>& padding) {
|
||||
LicenseBuilder builder;
|
||||
|
||||
builder.AddContentKey(golden_data_.CBCCryptoKey().level,
|
||||
golden_data_.CBCCryptoKey().id,
|
||||
golden_data_.CBCCryptoKey().content->key);
|
||||
|
||||
builder.AddContentKey(golden_data_.CTRCryptoKey().level,
|
||||
golden_data_.CTRCryptoKey().id,
|
||||
golden_data_.CTRCryptoKey().content->key);
|
||||
|
||||
builder.AddContentKey(golden_data_.CBCDecodeKey().level,
|
||||
golden_data_.CBCDecodeKey().id,
|
||||
golden_data_.CBCDecodeKey().content->key);
|
||||
|
||||
builder.AddContentKey(golden_data_.CBCHardwareKey().level,
|
||||
golden_data_.CBCHardwareKey().id,
|
||||
golden_data_.CBCHardwareKey().content->key);
|
||||
|
||||
builder.AddOperatorSessionKey(non_content_key_id_);
|
||||
|
||||
License license;
|
||||
builder.Build(*public_key_, &license);
|
||||
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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);
|
||||
ASSERT_TRUE(whitebox_);
|
||||
}
|
||||
|
||||
void TearDown() override { WB_License_Delete(whitebox_); }
|
||||
// We need two special keys for this test, one that will be used for a
|
||||
// non-content key and one that will never be in the license.
|
||||
std::vector<uint8_t> non_content_key_id_ = {0, 0, 0};
|
||||
std::vector<uint8_t> missing_key_id_ = {1, 0, 0};
|
||||
|
||||
void LoadLicense() {
|
||||
// TODO: Load the license here. It would be nice if we could do it in
|
||||
// SetUp(), but since we need to support the WB_RESULT_INVALID_STATE test
|
||||
// case, we need a way to not load a license.
|
||||
}
|
||||
|
||||
WB_License_Whitebox* whitebox_ = nullptr;
|
||||
|
||||
// TODO(vaage): Replace key ids with the key ids in the license. Since we
|
||||
// don't have a license right now, any id will do.
|
||||
const std::vector<uint8_t> crypto_key_id_ = {0xAA, 0xBB, 0xAA, 0xBB};
|
||||
const std::vector<uint8_t> decode_key_id_ = {0xAA, 0xBB, 0xAA, 0xBB};
|
||||
const std::vector<uint8_t> renewal_key_id_ = {0xAA, 0xBB, 0xAA, 0xBB};
|
||||
|
||||
// TODO(vaage): Replace with golden ciphertext that will match the golden
|
||||
// plaintext.
|
||||
const std::vector<uint8_t> ciphertext_ = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
const std::vector<uint8_t> iv_ = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// TODO(vaage): Replace with golden plaintext that will match the golden
|
||||
// ciphertext.
|
||||
const std::vector<uint8_t> plaintext_ = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
// This is the buffer used to store the output of each decrypt call.
|
||||
size_t plaintext_size_;
|
||||
std::vector<uint8_t> plaintext_;
|
||||
};
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, Success) {
|
||||
// TODO(vaage): This test will fail for now because it will attempt to look-up
|
||||
// the key before attempting to decrypt anything. Since |plaintext_| and
|
||||
// |ciphertext_| are not even set correctly, this wouldn't pass even if it had
|
||||
// a license.
|
||||
GTEST_SKIP();
|
||||
TEST_F(LicenseWhiteboxDecryptTest, CryptoKeyWithCbcDataInCbcMode) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
LoadLicense();
|
||||
ASSERT_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_.resize(plaintext_size_);
|
||||
ASSERT_EQ(plaintext_, golden_data_.CBCCryptoKey().content->plaintext);
|
||||
}
|
||||
|
||||
size_t plaintext_size = ciphertext_.size();
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
TEST_F(LicenseWhiteboxDecryptTest, CryptoKeyWithCtrDataInCtrMode) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(WB_License_Decrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(),
|
||||
crypto_key_id_.size(), ciphertext_.data(), ciphertext_.size(),
|
||||
iv_.data(), iv_.size(), plaintext.data(), &plaintext_size),
|
||||
WB_RESULT_OK);
|
||||
ASSERT_EQ(
|
||||
WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CTR,
|
||||
golden_data_.CTRCryptoKey().id.data(),
|
||||
golden_data_.CTRCryptoKey().id.size(),
|
||||
golden_data_.CTRCryptoKey().content->ciphertext.data(),
|
||||
golden_data_.CTRCryptoKey().content->ciphertext.size(),
|
||||
golden_data_.CTRCryptoKey().content->iv.data(),
|
||||
golden_data_.CTRCryptoKey().content->iv.size(),
|
||||
plaintext_.data(), &plaintext_size_),
|
||||
WB_RESULT_OK);
|
||||
plaintext_.resize(plaintext_size_);
|
||||
ASSERT_EQ(plaintext_, golden_data_.CTRCryptoKey().content->plaintext);
|
||||
}
|
||||
|
||||
// Since we are not using any padding, the ciphertext and plaintext should be
|
||||
// the same size. However, in the case that something went wrong, resize the
|
||||
// plaintext so that ASSERT_EQ will operate on the correct size.
|
||||
plaintext.resize(plaintext_size);
|
||||
// We try to decrypt CBC encrypted data in CTR mode. All operations should be
|
||||
// successful, but the resulting plaintext should not match.
|
||||
TEST_F(LicenseWhiteboxDecryptTest, CryptoKeyWithCbcDataInCtrMode) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(plaintext, plaintext_);
|
||||
ASSERT_EQ(
|
||||
WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CTR,
|
||||
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_.resize(plaintext_size_);
|
||||
ASSERT_NE(plaintext_, golden_data_.CBCCryptoKey().content->plaintext);
|
||||
}
|
||||
|
||||
// We try to decrypt CTR encrypted data in CBC mode. All operations should be
|
||||
// successful, but the resulting plaintext should not match.
|
||||
TEST_F(LicenseWhiteboxDecryptTest, CryptoKeyWithCtrDataInCbcMode) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CTRCryptoKey().id.data(),
|
||||
golden_data_.CTRCryptoKey().id.size(),
|
||||
golden_data_.CTRCryptoKey().content->ciphertext.data(),
|
||||
golden_data_.CTRCryptoKey().content->ciphertext.size(),
|
||||
golden_data_.CTRCryptoKey().content->iv.data(),
|
||||
golden_data_.CTRCryptoKey().content->iv.size(),
|
||||
plaintext_.data(), &plaintext_size_),
|
||||
WB_RESULT_OK);
|
||||
plaintext_.resize(plaintext_size_);
|
||||
ASSERT_NE(plaintext_, golden_data_.CTRCryptoKey().content->plaintext);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, CryptoKeyWithCbcDataAndPKCS8Padding) {
|
||||
LoadLicense(LicenseBuilder::PKSC8Padding());
|
||||
|
||||
ASSERT_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_.resize(plaintext_size_);
|
||||
ASSERT_EQ(plaintext_, golden_data_.CBCCryptoKey().content->plaintext);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, CryptoKeyWithCtrDataAndPKCS8Padding) {
|
||||
LoadLicense(LicenseBuilder::PKSC8Padding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CTR,
|
||||
golden_data_.CTRCryptoKey().id.data(),
|
||||
golden_data_.CTRCryptoKey().id.size(),
|
||||
golden_data_.CTRCryptoKey().content->ciphertext.data(),
|
||||
golden_data_.CTRCryptoKey().content->ciphertext.size(),
|
||||
golden_data_.CTRCryptoKey().content->iv.data(),
|
||||
golden_data_.CTRCryptoKey().content->iv.size(),
|
||||
plaintext_.data(), &plaintext_size_),
|
||||
WB_RESULT_OK);
|
||||
plaintext_.resize(plaintext_size_);
|
||||
ASSERT_EQ(plaintext_, golden_data_.CTRCryptoKey().content->plaintext);
|
||||
}
|
||||
|
||||
// Try decrypting two different sets of content to make sure that two
|
||||
// different keys can be used at the same time.
|
||||
TEST_F(LicenseWhiteboxDecryptTest, SuccessWithMultipleKeys) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_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_.resize(plaintext_size_);
|
||||
ASSERT_EQ(plaintext_, golden_data_.CBCCryptoKey().content->plaintext);
|
||||
|
||||
// Reset our output buffer.
|
||||
plaintext_.clear();
|
||||
plaintext_size_ = golden_data_.CTRDecodeKey().content->plaintext.size();
|
||||
plaintext_.resize(plaintext_size_);
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CTR,
|
||||
golden_data_.CTRCryptoKey().id.data(),
|
||||
golden_data_.CTRCryptoKey().id.size(),
|
||||
golden_data_.CTRCryptoKey().content->ciphertext.data(),
|
||||
golden_data_.CTRCryptoKey().content->ciphertext.size(),
|
||||
golden_data_.CTRCryptoKey().content->iv.data(),
|
||||
golden_data_.CTRCryptoKey().content->iv.size(),
|
||||
plaintext_.data(), &plaintext_size_),
|
||||
WB_RESULT_OK);
|
||||
|
||||
plaintext_.resize(plaintext_size_);
|
||||
ASSERT_EQ(plaintext_, golden_data_.CTRCryptoKey().content->plaintext);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForNullWhitebox) {
|
||||
LoadLicense();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
size_t plaintext_size = ciphertext_.size();
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
|
||||
ASSERT_EQ(WB_License_Decrypt(
|
||||
nullptr, WB_CIPHER_MODE_CBC, crypto_key_id_.data(),
|
||||
crypto_key_id_.size(), ciphertext_.data(), ciphertext_.size(),
|
||||
iv_.data(), iv_.size(), plaintext.data(), &plaintext_size),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
ASSERT_EQ(
|
||||
WB_License_Decrypt(nullptr, 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_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForInvalidCipherMode) {
|
||||
// TODO(vaage): This test will fail for now because it will attempt to
|
||||
// look-up the key before attempting to decrypt anything.
|
||||
GTEST_SKIP();
|
||||
|
||||
LoadLicense();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
// In order to trick the compiler into letting us pass an invalid enum value
|
||||
// to WB__License_Decrypt(), we need to cast it. If we don't do this, the
|
||||
// compiler tries to save us.
|
||||
const WB_CipherMode invalid_mode = static_cast<WB_CipherMode>(0xFF);
|
||||
|
||||
size_t plaintext_size = ciphertext_.size();
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
|
||||
ASSERT_EQ(WB_License_Decrypt(whitebox_, invalid_mode, crypto_key_id_.data(),
|
||||
crypto_key_id_.size(), ciphertext_.data(),
|
||||
ciphertext_.size(), iv_.data(), iv_.size(),
|
||||
plaintext.data(), &plaintext_size),
|
||||
ASSERT_EQ(WB_License_Decrypt(
|
||||
whitebox_, invalid_mode, 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_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForNullKeyId) {
|
||||
LoadLicense();
|
||||
|
||||
size_t plaintext_size = ciphertext_.size();
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
|
||||
ASSERT_EQ(WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, nullptr,
|
||||
crypto_key_id_.size(), ciphertext_.data(),
|
||||
ciphertext_.size(), iv_.data(), iv_.size(),
|
||||
plaintext.data(), &plaintext_size),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForNullZeroKeyIdSize) {
|
||||
LoadLicense();
|
||||
|
||||
size_t plaintext_size = ciphertext_.size();
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(),
|
||||
0, ciphertext_.data(), ciphertext_.size(), iv_.data(),
|
||||
iv_.size(), plaintext.data(), &plaintext_size),
|
||||
WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, nullptr,
|
||||
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_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForZeroKeyIdSize) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CBCCryptoKey().id.data(), 0,
|
||||
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_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForNullInputData) {
|
||||
// TODO(vaage): This test will fail for now because it will attempt to
|
||||
// look-up the key before attempting to decrypt anything.
|
||||
GTEST_SKIP();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
LoadLicense();
|
||||
|
||||
size_t plaintext_size = ciphertext_.size();
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
|
||||
ASSERT_EQ(WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
crypto_key_id_.data(), crypto_key_id_.size(),
|
||||
nullptr, ciphertext_.size(), iv_.data(),
|
||||
iv_.size(), plaintext.data(), &plaintext_size),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
ASSERT_EQ(
|
||||
WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CBCCryptoKey().id.data(),
|
||||
golden_data_.CBCCryptoKey().id.size(), nullptr,
|
||||
golden_data_.CBCCryptoKey().content->ciphertext.size(),
|
||||
golden_data_.CBCCryptoKey().content->iv.data(),
|
||||
golden_data_.CBCCryptoKey().content->iv.size(),
|
||||
plaintext_.data(), &plaintext_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
// AES CBC requires that the input be block aligned (multiple of 16). CTR does
|
||||
// not care. Size zero input is not considered invalid input.
|
||||
TEST_F(LicenseWhiteboxDecryptTest,
|
||||
InvalidParameterForCBCAndInvalidInputDataSize) {
|
||||
// TODO(vaage): This test will fail for now because it will attempt to
|
||||
// look-up the key before attempting to decrypt anything.
|
||||
GTEST_SKIP();
|
||||
// not care.
|
||||
TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForInvalidCBCInputDataSize) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
LoadLicense();
|
||||
ASSERT_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(),
|
||||
14, golden_data_.CBCCryptoKey().content->iv.data(),
|
||||
golden_data_.CBCCryptoKey().content->iv.size(),
|
||||
plaintext_.data(), &plaintext_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
size_t plaintext_size = ciphertext_.size();
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
// The white-box (using any cipher mode) should reject input with size zero.
|
||||
TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForZeroInputDataSize) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
crypto_key_id_.data(), crypto_key_id_.size(),
|
||||
ciphertext_.data(), 14, iv_.data(), iv_.size(),
|
||||
plaintext.data(), &plaintext_size),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
ASSERT_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(),
|
||||
0, golden_data_.CBCCryptoKey().content->iv.data(),
|
||||
golden_data_.CBCCryptoKey().content->iv.size(),
|
||||
plaintext_.data(), &plaintext_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForNullIV) {
|
||||
// TODO(vaage): This test will fail for now because it will attempt to
|
||||
// look-up the key before attempting to decrypt anything.
|
||||
GTEST_SKIP();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
LoadLicense();
|
||||
|
||||
size_t plaintext_size = ciphertext_.size();
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
|
||||
ASSERT_EQ(WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
crypto_key_id_.data(), crypto_key_id_.size(),
|
||||
ciphertext_.data(), ciphertext_.size(), nullptr,
|
||||
iv_.size(), plaintext.data(), &plaintext_size),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
ASSERT_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(), nullptr,
|
||||
golden_data_.CBCCryptoKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
// IV size should be 16. Any number other than 16 should fail.
|
||||
TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForInvalidIVSize) {
|
||||
// TODO(vaage): This test will fail for now because it will attempt to
|
||||
// look-up the key before attempting to decrypt anything.
|
||||
GTEST_SKIP();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
LoadLicense();
|
||||
|
||||
size_t plaintext_size = ciphertext_.size();
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
|
||||
ASSERT_EQ(WB_License_Decrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(),
|
||||
crypto_key_id_.size(), ciphertext_.data(), ciphertext_.size(),
|
||||
iv_.data(), 9, plaintext.data(), &plaintext_size),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
ASSERT_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(), 9,
|
||||
plaintext_.data(), &plaintext_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForNullOutput) {
|
||||
// TODO(vaage): This test will fail for now because it will attempt to
|
||||
// look-up the key before attempting to decrypt anything.
|
||||
GTEST_SKIP();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
LoadLicense();
|
||||
|
||||
size_t plaintext_size = ciphertext_.size();
|
||||
|
||||
ASSERT_EQ(WB_License_Decrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(),
|
||||
crypto_key_id_.size(), ciphertext_.data(), ciphertext_.size(),
|
||||
iv_.data(), iv_.size(), nullptr, &plaintext_size),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
ASSERT_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(),
|
||||
nullptr, &plaintext_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForNullOutputSize) {
|
||||
// TODO(vaage): This test will fail for now because it will attempt to
|
||||
// look-up the key before attempting to decrypt anything.
|
||||
GTEST_SKIP();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
LoadLicense();
|
||||
|
||||
size_t plaintext_size = ciphertext_.size();
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
|
||||
ASSERT_EQ(WB_License_Decrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(),
|
||||
crypto_key_id_.size(), ciphertext_.data(), ciphertext_.size(),
|
||||
iv_.data(), iv_.size(), plaintext.data(), nullptr),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
ASSERT_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(), nullptr),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, NoSuchKey) {
|
||||
// TODO(vaage): This test will fail for now because it will attempt to
|
||||
// look-up the key and see that there are no keys, which will result in
|
||||
// WB_RESULT_INVALID_STATE.
|
||||
GTEST_SKIP();
|
||||
// For this test, "missing key id" specifically means a key id that was never
|
||||
// in the license to start with. This is different than "non content key"
|
||||
// and "dropped content key", as those keys were in the license but ignored.
|
||||
TEST_F(LicenseWhiteboxDecryptTest, KeyUnavailableForMissingKeyId) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
LoadLicense();
|
||||
|
||||
// For our fake key id, it is uncommon for key ids to be short. So using a one
|
||||
// byte key id should safe as they are allowed, just not likely to appear in
|
||||
// any test license.
|
||||
std::vector<uint8_t> not_a_key_id = {0xFF};
|
||||
|
||||
size_t plaintext_size = ciphertext_.size();
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
|
||||
ASSERT_EQ(WB_License_Decrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, not_a_key_id.data(),
|
||||
not_a_key_id.size(), ciphertext_.data(), ciphertext_.size(),
|
||||
iv_.data(), iv_.size(), plaintext.data(), &plaintext_size),
|
||||
WB_RESULT_NO_SUCH_KEY);
|
||||
ASSERT_EQ(
|
||||
WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, missing_key_id_.data(),
|
||||
missing_key_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_KEY_UNAVAILABLE);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, WrongKeyType) {
|
||||
// TODO(vaage): This test will fail for now because it will attempt to
|
||||
// look-up the key and see that there are no keys, which will result in
|
||||
// WB_RESULT_INVALID_STATE.
|
||||
GTEST_SKIP();
|
||||
TEST_F(LicenseWhiteboxDecryptTest, KeyUnavailableForNonContentKey) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
LoadLicense();
|
||||
|
||||
size_t plaintext_size = ciphertext_.size();
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
|
||||
ASSERT_EQ(WB_License_Decrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, renewal_key_id_.data(),
|
||||
renewal_key_id_.size(), ciphertext_.data(), ciphertext_.size(),
|
||||
iv_.data(), iv_.size(), plaintext.data(), &plaintext_size),
|
||||
WB_RESULT_WRONG_KEY_TYPE);
|
||||
ASSERT_EQ(
|
||||
WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
non_content_key_id_.data(), non_content_key_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_KEY_UNAVAILABLE);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, InsufficientSecurityLevel) {
|
||||
// TODO(vaage): This test will fail for now because it will attempt to
|
||||
// look-up the key and see that there are no keys, which will result in
|
||||
// WB_RESULT_INVALID_STATE.
|
||||
GTEST_SKIP();
|
||||
// Under normal circumstances, a hardware key should be dropped. The exception
|
||||
// to this rule is on ChromeOS with a special license.
|
||||
TEST_F(LicenseWhiteboxDecryptTest,
|
||||
InsufficientSecurityLevelForHardwareContentKey) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
LoadLicense();
|
||||
ASSERT_EQ(WB_License_Decrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CBCHardwareKey().id.data(),
|
||||
golden_data_.CBCHardwareKey().id.size(),
|
||||
golden_data_.CBCHardwareKey().content->ciphertext.data(),
|
||||
golden_data_.CBCHardwareKey().content->ciphertext.size(),
|
||||
golden_data_.CBCHardwareKey().content->iv.data(),
|
||||
golden_data_.CBCHardwareKey().content->iv.size(),
|
||||
plaintext_.data(), &plaintext_size_),
|
||||
WB_RESULT_INSUFFICIENT_SECURITY_LEVEL);
|
||||
}
|
||||
|
||||
size_t plaintext_size = ciphertext_.size();
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
TEST_F(LicenseWhiteboxDecryptTest, InsufficientSecurityLevelForDecodeKey) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
// Use the software decode key as they are limited to
|
||||
// WB_License_MaskedDecrypt().
|
||||
ASSERT_EQ(WB_License_Decrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, decode_key_id_.data(),
|
||||
decode_key_id_.size(), ciphertext_.data(), ciphertext_.size(),
|
||||
iv_.data(), iv_.size(), plaintext.data(), &plaintext_size),
|
||||
WB_RESULT_INSUFFICIENT_SECURITY_LEVEL);
|
||||
// WB_License_Decrypt().
|
||||
ASSERT_EQ(
|
||||
WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(),
|
||||
plaintext_.data(), &plaintext_size_),
|
||||
WB_RESULT_INSUFFICIENT_SECURITY_LEVEL);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, BufferTooSmall) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
// Our ciphertext will be large enough that we should not need to worry about
|
||||
// using a constant here.
|
||||
plaintext_size_ = 8;
|
||||
|
||||
ASSERT_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_BUFFER_TOO_SMALL);
|
||||
|
||||
// We don't use padding so the reported plaintext size should be the same as
|
||||
// the cipher text size.
|
||||
ASSERT_EQ(plaintext_size_,
|
||||
golden_data_.CBCCryptoKey().content->ciphertext.size());
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, InvalidState) {
|
||||
@@ -322,34 +477,16 @@ TEST_F(LicenseWhiteboxDecryptTest, InvalidState) {
|
||||
// WB_RESULT_INVALID_STATE is that no key can be found and keys are provided
|
||||
// via a license.
|
||||
|
||||
size_t plaintext_size = ciphertext_.size();
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
|
||||
ASSERT_EQ(WB_License_Decrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(),
|
||||
crypto_key_id_.size(), ciphertext_.data(), ciphertext_.size(),
|
||||
iv_.data(), iv_.size(), plaintext.data(), &plaintext_size),
|
||||
WB_RESULT_INVALID_STATE);
|
||||
ASSERT_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_INVALID_STATE);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxDecryptTest, BufferTooSmall) {
|
||||
// TODO(vaage): This test will fail for now because it will attempt to
|
||||
// look-up the key before attempting to decrypt anything.
|
||||
GTEST_SKIP();
|
||||
|
||||
// Our ciphertext will be large enough that we should not need to worry about
|
||||
// using a constant here.
|
||||
size_t plaintext_size = 8;
|
||||
std::vector<uint8_t> plaintext(plaintext_size);
|
||||
|
||||
ASSERT_EQ(WB_License_Decrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(),
|
||||
crypto_key_id_.size(), ciphertext_.data(), ciphertext_.size(),
|
||||
iv_.data(), iv_.size(), plaintext.data(), &plaintext_size),
|
||||
WB_RESULT_BUFFER_TOO_SMALL);
|
||||
|
||||
// We don't use padding so the reported plaintext size should be the same as
|
||||
// the cipher text size.
|
||||
ASSERT_EQ(plaintext_size, ciphertext_.size());
|
||||
}
|
||||
} // namespace
|
||||
} // namespace widevine
|
||||
|
||||
@@ -5,148 +5,221 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "api/golden_data.h"
|
||||
#include "api/license_builder.h"
|
||||
#include "api/license_whitebox_test_base.h"
|
||||
#include "api/test_data.h"
|
||||
#include "crypto_utils/rsa_key.h"
|
||||
#include "testing/include/gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
namespace widevine {
|
||||
|
||||
class LicenseWhiteboxGetSecretStringTest : public ::testing::Test {
|
||||
class LicenseWhiteboxGetSecretStringTest : public LicenseWhiteboxTestBase {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
LicenseWhiteboxTestBase::SetUp();
|
||||
|
||||
// The secret string size is implementation specific, so this number just
|
||||
// needs to be reasonably large to accommodate different implementations.
|
||||
secret_string_size_ = 256;
|
||||
secret_string_.resize(secret_string_size_);
|
||||
|
||||
const std::vector<uint8_t> init_data = GetLicenseInitData();
|
||||
|
||||
ASSERT_EQ(WB_License_Create(init_data.data(), init_data.size(), &whitebox_),
|
||||
WB_RESULT_OK);
|
||||
ASSERT_TRUE(whitebox_);
|
||||
golden_data_.MakeKeyIdDifferent(&non_content_key_id_);
|
||||
golden_data_.MakeKeyIdDifferent(&missing_key_id_);
|
||||
}
|
||||
|
||||
void TearDown() override { WB_License_Delete(whitebox_); }
|
||||
|
||||
void LoadLicense() {
|
||||
// TODO: We will need to load a license for most tests as they will require
|
||||
// a key id look-up and we get keys from licenses.
|
||||
LicenseBuilder builder;
|
||||
|
||||
builder.AddContentKey(golden_data_.CBCCryptoKey().level,
|
||||
golden_data_.CBCCryptoKey().id,
|
||||
golden_data_.CBCCryptoKey().content->key);
|
||||
|
||||
builder.AddContentKey(golden_data_.CBCDecodeKey().level,
|
||||
golden_data_.CBCDecodeKey().id,
|
||||
golden_data_.CBCDecodeKey().content->key);
|
||||
|
||||
builder.AddContentKey(golden_data_.CBCHardwareKey().level,
|
||||
golden_data_.CBCHardwareKey().id,
|
||||
golden_data_.CBCHardwareKey().content->key);
|
||||
|
||||
builder.AddOperatorSessionKey(non_content_key_id_);
|
||||
|
||||
License license;
|
||||
builder.Build(*public_key_, &license);
|
||||
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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);
|
||||
}
|
||||
|
||||
// TODO: These needs to be replaced with the actual keys in the loaded
|
||||
// license.
|
||||
const std::vector<uint8_t> crypto_key_id_ = {1, 2, 3};
|
||||
const std::vector<uint8_t> hardware_key_id_ = {2, 3, 4};
|
||||
const std::vector<uint8_t> bad_key_id_ = {3, 4, 5};
|
||||
const std::vector<uint8_t> non_content_key_id_ = {4, 5, 6};
|
||||
// We need two special keys for this test, one that will be used for a
|
||||
// non-content key and one that will never be in the license.
|
||||
std::vector<uint8_t> non_content_key_id_ = {0, 0, 0};
|
||||
std::vector<uint8_t> missing_key_id_ = {1, 0, 0};
|
||||
|
||||
size_t secret_string_size_;
|
||||
std::vector<uint8_t> secret_string_;
|
||||
|
||||
WB_License_Whitebox* whitebox_;
|
||||
};
|
||||
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest, Success) {
|
||||
// TODO: This test needs to be skipped right now because we can't load a
|
||||
// license and we need a license to have key ids.
|
||||
GTEST_SKIP();
|
||||
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest, SuccessForCBCWithCryptoKey) {
|
||||
LoadLicense();
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
crypto_key_id_.data(), crypto_key_id_.size(),
|
||||
golden_data_.CBCCryptoKey().id.data(),
|
||||
golden_data_.CBCCryptoKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_OK);
|
||||
secret_string_.resize(secret_string_size_);
|
||||
ASSERT_GT(secret_string_.size(), 0);
|
||||
ASSERT_GT(secret_string_size_, 0);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest, SuccessForCTRWithCryptoKey) {
|
||||
LoadLicense();
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR,
|
||||
golden_data_.CBCCryptoKey().id.data(),
|
||||
golden_data_.CBCCryptoKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_OK);
|
||||
ASSERT_GT(secret_string_size_, 0);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest, SuccessForCBCWithDecodeKey) {
|
||||
LoadLicense();
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_OK);
|
||||
ASSERT_GT(secret_string_size_, 0);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest, SuccessForCTRWithDecodeKey) {
|
||||
LoadLicense();
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR,
|
||||
golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_OK);
|
||||
ASSERT_GT(secret_string_size_, 0);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest, InvalidParameterForNullWhitebox) {
|
||||
LoadLicense();
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(nullptr, WB_CIPHER_MODE_CBC,
|
||||
crypto_key_id_.data(), crypto_key_id_.size(),
|
||||
golden_data_.CBCCryptoKey().id.data(),
|
||||
golden_data_.CBCCryptoKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest,
|
||||
InvalidParameterForInvalidCipherMode) {
|
||||
LoadLicense();
|
||||
|
||||
// In order to trick the compiler into letting us pass an invalid enum value
|
||||
// to WB__License_Decrypt(), we need to cast it. If we don't do this, the
|
||||
// compiler tries to save us.
|
||||
const WB_CipherMode invalid_mode = static_cast<WB_CipherMode>(0xFF);
|
||||
|
||||
ASSERT_EQ(WB_License_GetSecretString(
|
||||
whitebox_, invalid_mode, golden_data_.CBCCryptoKey().id.data(),
|
||||
golden_data_.CBCCryptoKey().id.size(), secret_string_.data(),
|
||||
&secret_string_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest, InvalidParameterForNullKeyId) {
|
||||
LoadLicense();
|
||||
ASSERT_EQ(WB_License_GetSecretString(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, nullptr, crypto_key_id_.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, nullptr,
|
||||
golden_data_.CBCCryptoKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest, InvalidParameterForZeroKeyIdSize) {
|
||||
LoadLicense();
|
||||
ASSERT_EQ(WB_License_GetSecretString(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(), 0,
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CBCCryptoKey().id.data(), 0,
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest,
|
||||
InvalidParameterForNullSecretString) {
|
||||
LoadLicense();
|
||||
ASSERT_EQ(WB_License_GetSecretString(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(),
|
||||
crypto_key_id_.size(), nullptr, &secret_string_size_),
|
||||
|
||||
ASSERT_EQ(WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CBCCryptoKey().id.data(),
|
||||
golden_data_.CBCCryptoKey().id.size(),
|
||||
nullptr, &secret_string_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest,
|
||||
InvalidParameterForNullSecretStringSize) {
|
||||
LoadLicense();
|
||||
ASSERT_EQ(WB_License_GetSecretString(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(),
|
||||
crypto_key_id_.size(), secret_string_.data(), nullptr),
|
||||
|
||||
ASSERT_EQ(WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CBCCryptoKey().id.data(),
|
||||
golden_data_.CBCCryptoKey().id.size(),
|
||||
secret_string_.data(), nullptr),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest, NoSuchKey) {
|
||||
// TODO: This test needs to be skipped right now because we can't load a
|
||||
// license and we need a license to have key ids.
|
||||
GTEST_SKIP();
|
||||
|
||||
// For this test, "missing key id" specifically means a key id that was never
|
||||
// in the license to start with. This is different than "non content key"
|
||||
// and "dropped content key", as those keys were in the license but ignored.
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest, KeyUnavailableForMissingKeyId) {
|
||||
LoadLicense();
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
bad_key_id_.data(), bad_key_id_.size(),
|
||||
missing_key_id_.data(), missing_key_id_.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_NO_SUCH_KEY);
|
||||
WB_RESULT_KEY_UNAVAILABLE);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest, WrongKeyType) {
|
||||
// TODO: This test needs to be skipped right now because we can't load a
|
||||
// license and we need a license to have key ids.
|
||||
GTEST_SKIP();
|
||||
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest, KeyUnavailableForNonContentKey) {
|
||||
LoadLicense();
|
||||
|
||||
ASSERT_EQ(WB_License_GetSecretString(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, non_content_key_id_.data(),
|
||||
non_content_key_id_.size(), secret_string_.data(),
|
||||
&secret_string_size_),
|
||||
WB_RESULT_NO_SUCH_KEY);
|
||||
WB_RESULT_KEY_UNAVAILABLE);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest, InsufficientSecurityLevel) {
|
||||
// TODO: This test needs to be skipped right now because we can't load a
|
||||
// license and we need a license to have key ids.
|
||||
GTEST_SKIP();
|
||||
|
||||
// Under normal circumstances, a hardware key should be dropped. The exception
|
||||
// to this rule is on ChromeOS with a special license.
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest,
|
||||
InsufficientSecurityLevelForHardwareContentKey) {
|
||||
LoadLicense();
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, hardware_key_id_.data(),
|
||||
hardware_key_id_.size(), secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_NO_SUCH_KEY);
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CBCHardwareKey().id.data(),
|
||||
golden_data_.CBCHardwareKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_INSUFFICIENT_SECURITY_LEVEL);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxGetSecretStringTest, BufferTooSmall) {
|
||||
// TODO: This test needs to be skipped right now because we can't load a
|
||||
// license and we need a license to have key ids.
|
||||
GTEST_SKIP();
|
||||
|
||||
LoadLicense();
|
||||
|
||||
// Since the secret string is implementation specific, we don't want to make
|
||||
@@ -156,7 +229,8 @@ TEST_F(LicenseWhiteboxGetSecretStringTest, BufferTooSmall) {
|
||||
secret_string_size_ = 1;
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
crypto_key_id_.data(), crypto_key_id_.size(),
|
||||
golden_data_.CBCCryptoKey().id.data(),
|
||||
golden_data_.CBCCryptoKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_BUFFER_TOO_SMALL);
|
||||
|
||||
@@ -172,9 +246,10 @@ TEST_F(LicenseWhiteboxGetSecretStringTest, InvalidState) {
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
crypto_key_id_.data(), crypto_key_id_.size(),
|
||||
golden_data_.CBCCryptoKey().id.data(),
|
||||
golden_data_.CBCCryptoKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_INVALID_STATE);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace widevine
|
||||
|
||||
766
api/license_whitebox_masked_decrypt_test.cc
Normal file
766
api/license_whitebox_masked_decrypt_test.cc
Normal file
@@ -0,0 +1,766 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
#include "api/license_whitebox.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "api/golden_data.h"
|
||||
#include "api/license_builder.h"
|
||||
#include "api/license_whitebox_test_base.h"
|
||||
#include "api/test_data.h"
|
||||
#include "base/logging.h"
|
||||
#include "crypto_utils/crypto_util.h"
|
||||
#include "crypto_utils/rsa_key.h"
|
||||
#include "testing/include/gtest/gtest.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
class LicenseWhiteboxMaskedDecryptTest : public LicenseWhiteboxTestBase {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
LicenseWhiteboxTestBase::SetUp();
|
||||
|
||||
// Because we are going to use the same buffer for both tests, make sure it
|
||||
// will be large enough for either.
|
||||
plaintext_size_ = std::max(golden_data_.CBCContent().ciphertext.size(),
|
||||
golden_data_.CTRContent().ciphertext.size());
|
||||
plaintext_.resize(plaintext_size_);
|
||||
|
||||
// We have no idea how big the secret string will be, but it should be safe
|
||||
// to assume it won't be larger than the plaintext.
|
||||
secret_string_size_ = plaintext_size_;
|
||||
secret_string_.resize(secret_string_size_);
|
||||
|
||||
golden_data_.MakeKeyIdDifferent(&non_content_key_id_);
|
||||
golden_data_.MakeKeyIdDifferent(&missing_key_id_);
|
||||
}
|
||||
|
||||
void LoadLicense(const std::vector<uint8_t>& padding) {
|
||||
LicenseBuilder builder;
|
||||
|
||||
builder.AddContentKey(golden_data_.CBCCryptoKey().level,
|
||||
golden_data_.CBCCryptoKey().id,
|
||||
golden_data_.CBCCryptoKey().content->key);
|
||||
|
||||
builder.AddContentKey(golden_data_.CTRCryptoKey().level,
|
||||
golden_data_.CTRCryptoKey().id,
|
||||
golden_data_.CTRCryptoKey().content->key);
|
||||
|
||||
builder.AddContentKey(golden_data_.CBCDecodeKey().level,
|
||||
golden_data_.CBCDecodeKey().id,
|
||||
golden_data_.CBCDecodeKey().content->key);
|
||||
|
||||
builder.AddContentKey(golden_data_.CTRDecodeKey().level,
|
||||
golden_data_.CTRDecodeKey().id,
|
||||
golden_data_.CTRDecodeKey().content->key);
|
||||
|
||||
builder.AddContentKey(golden_data_.CBCHardwareKey().level,
|
||||
golden_data_.CBCHardwareKey().id,
|
||||
golden_data_.CBCHardwareKey().content->key);
|
||||
|
||||
builder.AddOperatorSessionKey(non_content_key_id_);
|
||||
|
||||
License license;
|
||||
builder.Build(*public_key_, &license);
|
||||
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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);
|
||||
}
|
||||
|
||||
// We need two special keys for this test, one that will be used for a
|
||||
// non-content key and one that will never be in the license.
|
||||
std::vector<uint8_t> non_content_key_id_ = {0, 0, 0};
|
||||
std::vector<uint8_t> missing_key_id_ = {1, 0, 0};
|
||||
|
||||
size_t secret_string_size_;
|
||||
std::vector<uint8_t> secret_string_;
|
||||
|
||||
size_t plaintext_size_;
|
||||
std::vector<uint8_t> plaintext_;
|
||||
};
|
||||
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, DecodeKeyWithCbcDataInCbcMode) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_OK);
|
||||
plaintext_.resize(plaintext_size_);
|
||||
|
||||
// Returned data is masked, so it should be the correct size but not
|
||||
// match the original text.
|
||||
ASSERT_EQ(plaintext_.size(),
|
||||
golden_data_.CBCDecodeKey().content->plaintext.size());
|
||||
ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext);
|
||||
|
||||
// Now unmask the data.
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_OK);
|
||||
secret_string_.resize(secret_string_size_);
|
||||
WB_License_Unmask(secret_string_.data(), secret_string_.size(),
|
||||
plaintext_.data(), plaintext_.size());
|
||||
ASSERT_EQ(plaintext_, golden_data_.CBCDecodeKey().content->plaintext);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, DecodeKeyWithCtrDataInCtrMode) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CTR, golden_data_.CTRDecodeKey().id.data(),
|
||||
golden_data_.CTRDecodeKey().id.size(),
|
||||
golden_data_.CTRDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CTRDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CTRDecodeKey().content->iv.data(),
|
||||
golden_data_.CTRDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_OK);
|
||||
plaintext_.resize(plaintext_size_);
|
||||
|
||||
// Returned data is masked, so it should be the correct size but not
|
||||
// match the original text.
|
||||
ASSERT_EQ(plaintext_.size(),
|
||||
golden_data_.CTRDecodeKey().content->plaintext.size());
|
||||
ASSERT_NE(plaintext_, golden_data_.CTRDecodeKey().content->plaintext);
|
||||
|
||||
// Now unmask the data.
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR,
|
||||
golden_data_.CTRDecodeKey().id.data(),
|
||||
golden_data_.CTRDecodeKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_OK);
|
||||
secret_string_.resize(secret_string_size_);
|
||||
WB_License_Unmask(secret_string_.data(), secret_string_.size(),
|
||||
plaintext_.data(), plaintext_.size());
|
||||
ASSERT_EQ(plaintext_, golden_data_.CTRDecodeKey().content->plaintext);
|
||||
}
|
||||
|
||||
// We try to decrypt CBC encrypted data in CTR mode. All operations should be
|
||||
// successful, but the resulting plaintext should not match.
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, DecodeKeyWithCbcDataInCtrMode) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CTR, golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_OK);
|
||||
plaintext_.resize(plaintext_size_);
|
||||
|
||||
// Whatever is returned must not be the original text.
|
||||
ASSERT_EQ(plaintext_.size(),
|
||||
golden_data_.CBCDecodeKey().content->plaintext.size());
|
||||
ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext);
|
||||
|
||||
// Now unmask the data. Still should not match.
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR,
|
||||
golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_OK);
|
||||
secret_string_.resize(secret_string_size_);
|
||||
WB_License_Unmask(secret_string_.data(), secret_string_.size(),
|
||||
plaintext_.data(), plaintext_.size());
|
||||
ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext);
|
||||
}
|
||||
|
||||
// We try to decrypt CTR encrypted data in CBC mode. All operations should be
|
||||
// successful, but the resulting plaintext should not match.
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, DecodeKeyWithCtrDataInCbcMode) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CTRDecodeKey().id.data(),
|
||||
golden_data_.CTRDecodeKey().id.size(),
|
||||
golden_data_.CTRDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CTRDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CTRDecodeKey().content->iv.data(),
|
||||
golden_data_.CTRDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_OK);
|
||||
plaintext_.resize(plaintext_size_);
|
||||
|
||||
// Whatever is returned must not be the original text.
|
||||
ASSERT_EQ(plaintext_.size(),
|
||||
golden_data_.CTRDecodeKey().content->plaintext.size());
|
||||
ASSERT_NE(plaintext_, golden_data_.CTRDecodeKey().content->plaintext);
|
||||
|
||||
// Now unmask the data. Still should not match.
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CTRDecodeKey().id.data(),
|
||||
golden_data_.CTRDecodeKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_OK);
|
||||
secret_string_.resize(secret_string_size_);
|
||||
WB_License_Unmask(secret_string_.data(), secret_string_.size(),
|
||||
plaintext_.data(), plaintext_.size());
|
||||
ASSERT_NE(plaintext_, golden_data_.CTRDecodeKey().content->plaintext);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCbcDataInCbcMode) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCCryptoKey().id.data(),
|
||||
golden_data_.CBCCryptoKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_OK);
|
||||
plaintext_.resize(plaintext_size_);
|
||||
|
||||
// Returned data is masked, so it should be the correct size but not
|
||||
// match the original text.
|
||||
ASSERT_EQ(plaintext_.size(),
|
||||
golden_data_.CBCDecodeKey().content->plaintext.size());
|
||||
ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext);
|
||||
|
||||
// Now unmask the data.
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CBCCryptoKey().id.data(),
|
||||
golden_data_.CBCCryptoKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_OK);
|
||||
secret_string_.resize(secret_string_size_);
|
||||
WB_License_Unmask(secret_string_.data(), secret_string_.size(),
|
||||
plaintext_.data(), plaintext_.size());
|
||||
ASSERT_EQ(plaintext_, golden_data_.CBCDecodeKey().content->plaintext);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCtrDataInCtrMode) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CTR, golden_data_.CTRCryptoKey().id.data(),
|
||||
golden_data_.CTRCryptoKey().id.size(),
|
||||
golden_data_.CTRDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CTRDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CTRDecodeKey().content->iv.data(),
|
||||
golden_data_.CTRDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_OK);
|
||||
plaintext_.resize(plaintext_size_);
|
||||
|
||||
// Returned data is masked, so it should be the correct size but not
|
||||
// match the original text.
|
||||
ASSERT_EQ(plaintext_.size(),
|
||||
golden_data_.CTRDecodeKey().content->plaintext.size());
|
||||
ASSERT_NE(plaintext_, golden_data_.CTRDecodeKey().content->plaintext);
|
||||
|
||||
// Now unmask the data.
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR,
|
||||
golden_data_.CTRCryptoKey().id.data(),
|
||||
golden_data_.CTRCryptoKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_OK);
|
||||
secret_string_.resize(secret_string_size_);
|
||||
WB_License_Unmask(secret_string_.data(), secret_string_.size(),
|
||||
plaintext_.data(), plaintext_.size());
|
||||
ASSERT_EQ(plaintext_, golden_data_.CTRDecodeKey().content->plaintext);
|
||||
}
|
||||
|
||||
// We try to decrypt CBC encrypted data in CTR mode. All operations should be
|
||||
// successful, but the resulting plaintext should not match.
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCbcDataInCtrMode) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CTR, golden_data_.CBCCryptoKey().id.data(),
|
||||
golden_data_.CBCCryptoKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_OK);
|
||||
plaintext_.resize(plaintext_size_);
|
||||
|
||||
// Whatever is returned must not be the original text.
|
||||
ASSERT_EQ(plaintext_.size(),
|
||||
golden_data_.CBCDecodeKey().content->plaintext.size());
|
||||
ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext);
|
||||
|
||||
// Now unmask the data. Still should not match.
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR,
|
||||
golden_data_.CBCCryptoKey().id.data(),
|
||||
golden_data_.CBCCryptoKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_OK);
|
||||
secret_string_.resize(secret_string_size_);
|
||||
WB_License_Unmask(secret_string_.data(), secret_string_.size(),
|
||||
plaintext_.data(), plaintext_.size());
|
||||
ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext);
|
||||
}
|
||||
|
||||
// We try to decrypt CTR encrypted data in CBC mode. All operations should be
|
||||
// successful, but the resulting plaintext should not match.
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCtrDataInCbcMode) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CTRCryptoKey().id.data(),
|
||||
golden_data_.CTRCryptoKey().id.size(),
|
||||
golden_data_.CTRCryptoKey().content->ciphertext.data(),
|
||||
golden_data_.CTRCryptoKey().content->ciphertext.size(),
|
||||
golden_data_.CTRCryptoKey().content->iv.data(),
|
||||
golden_data_.CTRCryptoKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_OK);
|
||||
plaintext_.resize(plaintext_size_);
|
||||
|
||||
// Whatever is returned must not be the original text.
|
||||
ASSERT_EQ(plaintext_.size(),
|
||||
golden_data_.CTRCryptoKey().content->plaintext.size());
|
||||
ASSERT_NE(plaintext_, golden_data_.CTRCryptoKey().content->plaintext);
|
||||
|
||||
// Now unmask the data. Still should not match.
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CTRCryptoKey().id.data(),
|
||||
golden_data_.CTRCryptoKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_OK);
|
||||
secret_string_.resize(secret_string_size_);
|
||||
WB_License_Unmask(secret_string_.data(), secret_string_.size(),
|
||||
plaintext_.data(), plaintext_.size());
|
||||
ASSERT_NE(plaintext_, golden_data_.CTRCryptoKey().content->plaintext);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCbcDataAndPKCS8Padding) {
|
||||
LoadLicense(LicenseBuilder::PKSC8Padding());
|
||||
|
||||
ASSERT_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);
|
||||
plaintext_.resize(plaintext_size_);
|
||||
|
||||
// Returned data is masked, so it should be the correct size but not
|
||||
// match the original text.
|
||||
ASSERT_EQ(plaintext_.size(),
|
||||
golden_data_.CBCCryptoKey().content->plaintext.size());
|
||||
ASSERT_NE(plaintext_, golden_data_.CBCCryptoKey().content->plaintext);
|
||||
|
||||
// Now unmask the data.
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CBCCryptoKey().id.data(),
|
||||
golden_data_.CBCCryptoKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_OK);
|
||||
secret_string_.resize(secret_string_size_);
|
||||
WB_License_Unmask(secret_string_.data(), secret_string_.size(),
|
||||
plaintext_.data(), plaintext_.size());
|
||||
ASSERT_EQ(plaintext_, golden_data_.CBCCryptoKey().content->plaintext);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCtrDataAndPKCS8Padding) {
|
||||
LoadLicense(LicenseBuilder::PKSC8Padding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CTR, golden_data_.CTRCryptoKey().id.data(),
|
||||
golden_data_.CTRCryptoKey().id.size(),
|
||||
golden_data_.CTRCryptoKey().content->ciphertext.data(),
|
||||
golden_data_.CTRCryptoKey().content->ciphertext.size(),
|
||||
golden_data_.CTRCryptoKey().content->iv.data(),
|
||||
golden_data_.CTRCryptoKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_OK);
|
||||
plaintext_.resize(plaintext_size_);
|
||||
|
||||
// Returned data is masked, so it should be the correct size but not
|
||||
// match the original text.
|
||||
ASSERT_EQ(plaintext_.size(),
|
||||
golden_data_.CTRCryptoKey().content->plaintext.size());
|
||||
ASSERT_NE(plaintext_, golden_data_.CTRCryptoKey().content->plaintext);
|
||||
|
||||
// Now unmask the data.
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR,
|
||||
golden_data_.CTRCryptoKey().id.data(),
|
||||
golden_data_.CTRCryptoKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_OK);
|
||||
secret_string_.resize(secret_string_size_);
|
||||
WB_License_Unmask(secret_string_.data(), secret_string_.size(),
|
||||
plaintext_.data(), plaintext_.size());
|
||||
ASSERT_EQ(plaintext_, golden_data_.CTRCryptoKey().content->plaintext);
|
||||
}
|
||||
|
||||
// Try decrypting two different sets of content to make sure that two
|
||||
// different keys can be used at the same time.
|
||||
//
|
||||
// Since we have two CBC keys, try using the decode key and then the crypto
|
||||
// key.
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, SuccessWithMultipleKeys) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_OK);
|
||||
plaintext_.resize(plaintext_size_);
|
||||
|
||||
// Returned data is masked, so it should be the correct size but not
|
||||
// match the original text.
|
||||
ASSERT_EQ(plaintext_.size(),
|
||||
golden_data_.CBCDecodeKey().content->plaintext.size());
|
||||
ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext);
|
||||
|
||||
// Now unmask the data.
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_OK);
|
||||
secret_string_.resize(secret_string_size_);
|
||||
WB_License_Unmask(secret_string_.data(), secret_string_.size(),
|
||||
plaintext_.data(), plaintext_.size());
|
||||
ASSERT_EQ(plaintext_, golden_data_.CBCDecodeKey().content->plaintext);
|
||||
|
||||
// Reset our output buffer.
|
||||
plaintext_.clear();
|
||||
plaintext_size_ = golden_data_.CTRDecodeKey().content->plaintext.size();
|
||||
plaintext_.resize(plaintext_size_);
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CTR, golden_data_.CTRDecodeKey().id.data(),
|
||||
golden_data_.CTRDecodeKey().id.size(),
|
||||
golden_data_.CTRDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CTRDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CTRDecodeKey().content->iv.data(),
|
||||
golden_data_.CTRDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_OK);
|
||||
plaintext_.resize(plaintext_size_);
|
||||
|
||||
// Returned data is masked, so it should be the correct size but not
|
||||
// match the original text.
|
||||
ASSERT_EQ(plaintext_.size(),
|
||||
golden_data_.CTRDecodeKey().content->plaintext.size());
|
||||
ASSERT_NE(plaintext_, golden_data_.CTRDecodeKey().content->plaintext);
|
||||
|
||||
// Now unmask the data.
|
||||
secret_string_.clear();
|
||||
secret_string_size_ = plaintext_.size();
|
||||
secret_string_.resize(secret_string_size_);
|
||||
ASSERT_EQ(
|
||||
WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR,
|
||||
golden_data_.CTRDecodeKey().id.data(),
|
||||
golden_data_.CTRDecodeKey().id.size(),
|
||||
secret_string_.data(), &secret_string_size_),
|
||||
WB_RESULT_OK);
|
||||
secret_string_.resize(secret_string_size_);
|
||||
WB_License_Unmask(secret_string_.data(), secret_string_.size(),
|
||||
plaintext_.data(), plaintext_.size());
|
||||
ASSERT_EQ(plaintext_, golden_data_.CTRDecodeKey().content->plaintext);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullWhitebox) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
nullptr, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForInvalidCipherMode) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
// In order to trick the compiler into letting us pass an invalid enum value
|
||||
// to WB__License_MaskedDecrypt(), we need to cast it. If we don't do this,
|
||||
// the compiler tries to save us.
|
||||
const WB_CipherMode invalid_mode = static_cast<WB_CipherMode>(0xFF);
|
||||
|
||||
ASSERT_EQ(WB_License_MaskedDecrypt(
|
||||
whitebox_, invalid_mode, golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(),
|
||||
plaintext_.data(), &plaintext_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullKeyId) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, nullptr,
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(),
|
||||
plaintext_.data(), &plaintext_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullZeroKeyIdSize) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(),
|
||||
0, golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullInputData) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(), nullptr,
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
// AES CBC requires that the input be block aligned (multiple of 16). CTR does
|
||||
// not care.
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest,
|
||||
InvalidParameterForInvalidCBCInputDataSize) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(), 14,
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
// The white-box (using any cipher mode) should reject input with size zero.
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForZeroInputDataSize) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(), 0,
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullIV) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(), nullptr,
|
||||
golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
// IV size should be 16. Any number other than 16 should fail.
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForInvalidIVSize) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(), 9, plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullOutput) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(), nullptr,
|
||||
&plaintext_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullOutputSize) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
nullptr),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
// For this test, "missing key id" specifically means a key id that was never
|
||||
// in the license to start with. This is different than "non content key"
|
||||
// and "dropped content key", as those keys were in the license but ignored.
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, KeyUnavailableForMissingKeyId) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, missing_key_id_.data(),
|
||||
missing_key_id_.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(),
|
||||
plaintext_.data(), &plaintext_size_),
|
||||
WB_RESULT_KEY_UNAVAILABLE);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, KeyUnavailableForNonContentKey) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, non_content_key_id_.data(),
|
||||
non_content_key_id_.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(),
|
||||
plaintext_.data(), &plaintext_size_),
|
||||
WB_RESULT_KEY_UNAVAILABLE);
|
||||
}
|
||||
|
||||
// Under normal circumstances, a hardware key should be dropped. The exception
|
||||
// to this rule is on ChromeOS with a special license.
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest,
|
||||
InsufficientSecurityLevelForHardwareContentKey) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC,
|
||||
golden_data_.CBCHardwareKey().id.data(),
|
||||
golden_data_.CBCHardwareKey().id.size(),
|
||||
golden_data_.CBCHardwareKey().content->ciphertext.data(),
|
||||
golden_data_.CBCHardwareKey().content->ciphertext.size(),
|
||||
golden_data_.CBCHardwareKey().content->iv.data(),
|
||||
golden_data_.CBCHardwareKey().content->iv.size(),
|
||||
plaintext_.data(), &plaintext_size_),
|
||||
WB_RESULT_INSUFFICIENT_SECURITY_LEVEL);
|
||||
}
|
||||
|
||||
// Unlike the other tests, we do not call LoadLicense() as the criteria for
|
||||
// WB_RESULT_INVALID_STATE is that no key can be found and keys are provided
|
||||
// via a license.
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidState) {
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_INVALID_STATE);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxMaskedDecryptTest, BufferTooSmall) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
// Our ciphertext will be large enough that we should not need to worry about
|
||||
// using a constant here.
|
||||
plaintext_size_ = 8;
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_MaskedDecrypt(
|
||||
whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(),
|
||||
golden_data_.CBCDecodeKey().id.size(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.data(),
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size(),
|
||||
golden_data_.CBCDecodeKey().content->iv.data(),
|
||||
golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(),
|
||||
&plaintext_size_),
|
||||
WB_RESULT_BUFFER_TOO_SMALL);
|
||||
|
||||
// We don't use padding so the reported plaintext size should be the same as
|
||||
// the cipher text size.
|
||||
ASSERT_EQ(plaintext_size_,
|
||||
golden_data_.CBCDecodeKey().content->ciphertext.size());
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
284
api/license_whitebox_process_license_response_test.cc
Normal file
284
api/license_whitebox_process_license_response_test.cc
Normal file
@@ -0,0 +1,284 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
#include "api/license_whitebox.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "api/golden_data.h"
|
||||
#include "api/license_builder.h"
|
||||
#include "api/license_whitebox_test_base.h"
|
||||
#include "api/test_data.h"
|
||||
#include "crypto_utils/crypto_util.h"
|
||||
#include "crypto_utils/rsa_key.h"
|
||||
#include "testing/include/gtest/gtest.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
class LicenseWhiteboxProcessLicenseResponseTest
|
||||
: public LicenseWhiteboxTestBase {
|
||||
protected:
|
||||
void UseLicenseWithoutSigningKey() {
|
||||
LicenseBuilder builder;
|
||||
builder.AddStubbedContentKey();
|
||||
builder.Build(*public_key_, &license_);
|
||||
}
|
||||
|
||||
void UseLicenseWithSigningKey(const std::vector<uint8_t>& padding) {
|
||||
LicenseBuilder builder;
|
||||
builder.AddSigningKey(LicenseBuilder::DefaultSigningKey(), padding);
|
||||
builder.AddStubbedContentKey();
|
||||
builder.Build(*public_key_, &license_);
|
||||
}
|
||||
|
||||
License license_;
|
||||
};
|
||||
|
||||
TEST_F(LicenseWhiteboxProcessLicenseResponseTest, SuccessWithoutSigningKey) {
|
||||
UseLicenseWithoutSigningKey();
|
||||
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxProcessLicenseResponseTest,
|
||||
SuccessWithSigningKeyNoPadding) {
|
||||
UseLicenseWithSigningKey(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxProcessLicenseResponseTest,
|
||||
SuccessWithSigningKeyPKSC8Padding) {
|
||||
UseLicenseWithSigningKey(LicenseBuilder::PKSC8Padding());
|
||||
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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);
|
||||
}
|
||||
|
||||
class LicenseWhiteboxProcessLicenseResponseErrorTest
|
||||
: public LicenseWhiteboxProcessLicenseResponseTest {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
LicenseWhiteboxProcessLicenseResponseTest::SetUp();
|
||||
|
||||
// For these tests, we don't care what license we use, it just needs to be
|
||||
// a valid license.
|
||||
UseLicenseWithoutSigningKey();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest,
|
||||
InvalidSignatureForModifedMessage) {
|
||||
Modify(&license_.message);
|
||||
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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_INVALID_SIGNATURE);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest,
|
||||
InvalidSignatureForModifedSignature) {
|
||||
Modify(&license_.signature);
|
||||
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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_INVALID_SIGNATURE);
|
||||
}
|
||||
|
||||
// The license request is used to derive the signing key. If the request was
|
||||
// modified, then the wrong signing key should be generated.
|
||||
TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest,
|
||||
InvalidSignatureForModifedLicenseRequest) {
|
||||
Modify(&license_.request);
|
||||
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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_INVALID_SIGNATURE);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest,
|
||||
InvalidParameterForNullWhitebox) {
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
nullptr, 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_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest,
|
||||
InvalidParameterForNullMessage) {
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, nullptr, 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_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest,
|
||||
InvalidParameterForZeroMessageSize) {
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, license_.message.data(), 0,
|
||||
license_.signature.data(), license_.signature.size(),
|
||||
license_.session_key.data(), license_.session_key.size(),
|
||||
license_.request.data(), license_.request.size()),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest,
|
||||
InvalidParameterForNullSignature) {
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, license_.message.data(), license_.message.size(),
|
||||
nullptr, license_.signature.size(), license_.session_key.data(),
|
||||
license_.session_key.size(), license_.request.data(),
|
||||
license_.request.size()),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest,
|
||||
InvalidParameterForInvalidSignatureSize) {
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, license_.message.data(), license_.message.size(),
|
||||
license_.signature.data(), 5, license_.session_key.data(),
|
||||
license_.session_key.size(), license_.request.data(),
|
||||
license_.request.size()),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest,
|
||||
InvalidParameterForNullSessionKey) {
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, license_.message.data(), license_.message.size(),
|
||||
license_.signature.data(), license_.signature.size(), nullptr,
|
||||
license_.session_key.size(), license_.request.data(),
|
||||
license_.request.size()),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest,
|
||||
InvalidParameterForInvalidSessionKeySize) {
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, license_.message.data(), license_.message.size(),
|
||||
license_.signature.data(), license_.signature.size(),
|
||||
license_.session_key.data(), 5, license_.request.data(),
|
||||
license_.request.size()),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
// If the session key is modified, unwrapping it will fail. Therefore, we will
|
||||
// know that the parameter is invalid (compared to a modified license request).
|
||||
TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest,
|
||||
InvalidParameterForModifedSessionKey) {
|
||||
Modify(&license_.session_key);
|
||||
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest,
|
||||
InvalidParameterForNullLicenseRequest) {
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, license_.message.data(), license_.message.size(),
|
||||
license_.signature.data(), license_.signature.size(),
|
||||
license_.session_key.data(), license_.session_key.size(),
|
||||
nullptr, license_.request.size()),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest,
|
||||
InvalidParameterForZeroLienseRequestSize) {
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, license_.message.data(), license_.message.size(),
|
||||
license_.signature.data(), license_.signature.size(),
|
||||
license_.session_key.data(), license_.session_key.size(),
|
||||
license_.request.data(), 0),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
class LicenseWhiteboxMultiLicenseTest
|
||||
: public LicenseWhiteboxProcessLicenseResponseTest {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
LicenseWhiteboxProcessLicenseResponseTest::SetUp();
|
||||
|
||||
// For these tests, we don't care what license we use, it just needs to be
|
||||
// a valid license.
|
||||
UseLicenseWithoutSigningKey();
|
||||
}
|
||||
};
|
||||
|
||||
// A whitebox can only process a license once. If it has loaded a license
|
||||
// (successfully) it should reject later calls with WB_RESULT_INVALID_STATE.
|
||||
TEST_F(LicenseWhiteboxMultiLicenseTest, InvalidState) {
|
||||
// Load the first license. This one is expected to succeed as the whitebox has
|
||||
// not loaded a license yet.
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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);
|
||||
|
||||
// Attempt to load the same license again. This should fail as it already has
|
||||
// a license (even though it is the same license).
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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_INVALID_STATE);
|
||||
}
|
||||
|
||||
// Even though a whitebox can only load a license once, if it fails to load a
|
||||
// license, it should still be able to try again.
|
||||
TEST_F(LicenseWhiteboxMultiLicenseTest, SuccessAfterFailure) {
|
||||
// Force this one to fail my changing the request, this will cause an error
|
||||
// in key derivation which is a later step of license parsing.
|
||||
std::vector<uint8_t> bad_request = license_.request;
|
||||
Modify(&bad_request);
|
||||
ASSERT_NE(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, license_.message.data(), license_.message.size(),
|
||||
license_.signature.data(), license_.signature.size(),
|
||||
license_.session_key.data(), license_.session_key.size(),
|
||||
bad_request.data(), bad_request.size()),
|
||||
WB_RESULT_OK);
|
||||
|
||||
// Attempt to load the license again, but use the correct (unmodified)
|
||||
// request.
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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);
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
128
api/license_whitebox_sign_license_request_test.cc
Normal file
128
api/license_whitebox_sign_license_request_test.cc
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
#include "api/license_whitebox.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "api/license_builder.h"
|
||||
#include "api/license_whitebox_test_base.h"
|
||||
#include "api/test_data.h"
|
||||
#include "crypto_utils/rsa_key.h"
|
||||
#include "testing/include/gtest/gtest.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
class LicenseWhiteboxSignLicenseRequestTest : public LicenseWhiteboxTestBase {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
LicenseWhiteboxTestBase::SetUp();
|
||||
|
||||
LicenseBuilder builder;
|
||||
builder.Build(*public_key_, &license_);
|
||||
|
||||
// We must make the default size large to hold the signature returned by
|
||||
// WB_License_SignLicenseRequest().
|
||||
signature_size_ = 256;
|
||||
signature_.resize(signature_size_);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> invalid_license_request_ = {
|
||||
0x1e, 0x70, 0xbd, 0xeb, 0x24, 0xf2, 0x9d, 0x05, 0xc5, 0xb5,
|
||||
0xf4, 0xca, 0xe6, 0x1d, 0x01, 0x97, 0x29, 0xf4, 0xe0, 0x7c,
|
||||
0xfd, 0xcc, 0x97, 0x8d, 0xc2, 0xbb, 0x2d, 0x9b, 0x6b, 0x45,
|
||||
0x06, 0xbd, 0x2c, 0x66, 0x10, 0x42, 0x73, 0x8d, 0x88, 0x9b,
|
||||
0x18, 0xcc, 0xcb, 0x7e, 0x43, 0x23, 0x06, 0xe9, 0x8f, 0x8f,
|
||||
};
|
||||
|
||||
License license_;
|
||||
|
||||
// These will be the output from each test case.
|
||||
size_t signature_size_;
|
||||
std::vector<uint8_t> signature_;
|
||||
};
|
||||
|
||||
TEST_F(LicenseWhiteboxSignLicenseRequestTest, SuccessForInvalidLicenseRequest) {
|
||||
ASSERT_EQ(
|
||||
WB_License_SignLicenseRequest(whitebox_, invalid_license_request_.data(),
|
||||
invalid_license_request_.size(),
|
||||
signature_.data(), &signature_size_),
|
||||
WB_RESULT_OK);
|
||||
|
||||
signature_.resize(signature_size_);
|
||||
|
||||
ASSERT_TRUE(public_key_->VerifySignature(
|
||||
std::string(invalid_license_request_.begin(),
|
||||
invalid_license_request_.end()),
|
||||
std::string(signature_.begin(), signature_.end())));
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxSignLicenseRequestTest, SuccessForValidLicenseRequest) {
|
||||
ASSERT_EQ(WB_License_SignLicenseRequest(whitebox_, license_.request.data(),
|
||||
license_.request.size(),
|
||||
signature_.data(), &signature_size_),
|
||||
WB_RESULT_OK);
|
||||
|
||||
signature_.resize(signature_size_);
|
||||
|
||||
ASSERT_TRUE(public_key_->VerifySignature(
|
||||
std::string(license_.request.begin(), license_.request.end()),
|
||||
std::string(signature_.begin(), signature_.end())));
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxSignLicenseRequestTest, InvalidParameterForNullWhitebox) {
|
||||
ASSERT_EQ(WB_License_SignLicenseRequest(nullptr, license_.request.data(),
|
||||
license_.request.size(),
|
||||
signature_.data(), &signature_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxSignLicenseRequestTest,
|
||||
InvalidParameterForNullLicenseRequest) {
|
||||
ASSERT_EQ(
|
||||
WB_License_SignLicenseRequest(whitebox_, nullptr, license_.request.size(),
|
||||
signature_.data(), &signature_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxSignLicenseRequestTest,
|
||||
InvalidParameterForZeroLicenseRequestSize) {
|
||||
ASSERT_EQ(WB_License_SignLicenseRequest(whitebox_, license_.request.data(), 0,
|
||||
signature_.data(), &signature_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxSignLicenseRequestTest,
|
||||
InvalidParameterForNullSignature) {
|
||||
ASSERT_EQ(WB_License_SignLicenseRequest(whitebox_, license_.request.data(),
|
||||
license_.request.size(), nullptr,
|
||||
&signature_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxSignLicenseRequestTest,
|
||||
InvalidParameterForNullSignatureSize) {
|
||||
ASSERT_EQ(WB_License_SignLicenseRequest(whitebox_, license_.request.data(),
|
||||
license_.request.size(),
|
||||
signature_.data(), nullptr),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxSignLicenseRequestTest, BufferTooSmall) {
|
||||
// We could test zero, but zero is just a subset of "smaller" and we want to
|
||||
// make sure that they zero is not the only check. So use something larger
|
||||
// than zero.
|
||||
signature_size_ = 1;
|
||||
|
||||
ASSERT_EQ(WB_License_SignLicenseRequest(whitebox_, license_.request.data(),
|
||||
license_.request.size(),
|
||||
signature_.data(), &signature_size_),
|
||||
WB_RESULT_BUFFER_TOO_SMALL);
|
||||
|
||||
// When WB_RESULT_BUFFER_TOO_SMALL is returned, the required buffer size
|
||||
// should be returned via |signature_size|. Since we don't know what it is, we
|
||||
// must rely on it being larger than the original "too small" size.
|
||||
ASSERT_GT(signature_size_, 1);
|
||||
}
|
||||
} // namespace widevine
|
||||
@@ -6,144 +6,196 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "api/license_builder.h"
|
||||
#include "api/license_whitebox_test_base.h"
|
||||
#include "api/test_data.h"
|
||||
#include "crypto_utils/crypto_util.h"
|
||||
#include "crypto_utils/rsa_key.h"
|
||||
#include "testing/include/gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
namespace widevine {
|
||||
|
||||
const uint8_t kDummyMessage[] = {
|
||||
0x1e, 0x70, 0xbd, 0xeb, 0x24, 0xf2, 0x9d, 0x05, 0xc5, 0xb5,
|
||||
0xf4, 0xca, 0xe6, 0x1d, 0x01, 0x97, 0x29, 0xf4, 0xe0, 0x7c,
|
||||
0xfd, 0xcc, 0x97, 0x8d, 0xc2, 0xbb, 0x2d, 0x9b, 0x6b, 0x45,
|
||||
0x06, 0xbd, 0x2c, 0x66, 0x10, 0x42, 0x73, 0x8d, 0x88, 0x9b,
|
||||
0x18, 0xcc, 0xcb, 0x7e, 0x43, 0x23, 0x06, 0xe9, 0x8f, 0x8f,
|
||||
};
|
||||
const size_t kDummyMessageSize = sizeof(kDummyMessage);
|
||||
|
||||
// Size of a license signature. This must be big enough to hold the signature
|
||||
// returned by WB_License_SignRenewalRequest().
|
||||
constexpr size_t kSignatureSize = 256;
|
||||
|
||||
// These tests assume that WB_License_Create() and
|
||||
// WB_License_ProcessLicenseResponse() work as all tests will require a valid
|
||||
// white-box instance with a valid license already loaded.
|
||||
class LicenseWhiteboxSignRenewalRequestTest : public ::testing::Test {
|
||||
class LicenseWhiteboxSignRenewalRequestTest : public LicenseWhiteboxTestBase {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
const std::vector<uint8_t> init_data_ = GetLicenseInitData();
|
||||
ASSERT_EQ(
|
||||
WB_License_Create(init_data_.data(), init_data_.size(), &whitebox_),
|
||||
WB_RESULT_OK);
|
||||
LicenseWhiteboxTestBase::SetUp();
|
||||
|
||||
// We don't know the actual signature size, so make the buffer large enough
|
||||
// that it should accomidate any change in signature size.
|
||||
signature_size_ = 256;
|
||||
signature_.resize(signature_size_);
|
||||
}
|
||||
|
||||
void TearDown() override { WB_License_Delete(whitebox_); }
|
||||
void LoadLicense(const std::vector<uint8_t>& padding) {
|
||||
LicenseBuilder builder;
|
||||
builder.AddSigningKey(signing_key_, padding);
|
||||
// Add a throw away key. We just need a key in the license since a license
|
||||
// should always have a content key.
|
||||
builder.AddStubbedContentKey();
|
||||
|
||||
void LoadLicense() {
|
||||
// TODO: Load the license here. It would be nice if we could do it in
|
||||
// SetUp(), but since we need to support the WB_RESULT_INVALID_STATE test
|
||||
// case, we need a way to not load a license.
|
||||
License license;
|
||||
builder.Build(*public_key_, &license);
|
||||
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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);
|
||||
}
|
||||
|
||||
WB_License_Whitebox* whitebox_ = nullptr;
|
||||
// Get the expected signature for |message|. By returning the message, this
|
||||
// will allow us to assert in the test, giving us better output in the case of
|
||||
// a failure.
|
||||
std::vector<uint8_t> GetSignature(const std::vector<uint8_t>& message) {
|
||||
// The client key is the second half of the signing key.
|
||||
std::string key_str(signing_key_.begin(), signing_key_.end());
|
||||
key_str.erase(0, crypto_util::kSigningKeySizeBytes);
|
||||
|
||||
const std::string signature = crypto_util::CreateSignatureHmacSha256(
|
||||
key_str, std::string(message.begin(), message.end()));
|
||||
|
||||
return std::vector<uint8_t>(signature.begin(), signature.end());
|
||||
}
|
||||
|
||||
const std::string session_key_ = "0123456789ABCDEF";
|
||||
|
||||
const std::vector<uint8_t> signing_key_ = LicenseBuilder::DefaultSigningKey();
|
||||
|
||||
size_t signature_size_;
|
||||
std::vector<uint8_t> signature_;
|
||||
|
||||
const std::vector<uint8_t> garbage_request_ = {
|
||||
0x1e, 0x70, 0xbd, 0xeb, 0x24, 0xf2, 0x9d, 0x05, 0xc5, 0xb5,
|
||||
0xf4, 0xca, 0xe6, 0x1d, 0x01, 0x97, 0x29, 0xf4, 0xe0, 0x7c,
|
||||
0xfd, 0xcc, 0x97, 0x8d, 0xc2, 0xbb, 0x2d, 0x9b, 0x6b, 0x45,
|
||||
0x06, 0xbd, 0x2c, 0x66, 0x10, 0x42, 0x73, 0x8d, 0x88, 0x9b,
|
||||
0x18, 0xcc, 0xcb, 0x7e, 0x43, 0x23, 0x06, 0xe9, 0x8f, 0x8f,
|
||||
};
|
||||
};
|
||||
|
||||
// TODO: Implement the success test case (ideally with a real renewal request).
|
||||
TEST_F(LicenseWhiteboxSignRenewalRequestTest, SuccessWithInvalidRequest) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, garbage_request_.data(),
|
||||
garbage_request_.size(),
|
||||
signature_.data(), &signature_size_),
|
||||
WB_RESULT_OK);
|
||||
|
||||
signature_.resize(signature_size_);
|
||||
ASSERT_EQ(signature_, GetSignature(garbage_request_));
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxSignRenewalRequestTest,
|
||||
SuccessWithSigningKeyPKSC8Padding) {
|
||||
LoadLicense(LicenseBuilder::PKSC8Padding());
|
||||
|
||||
ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, garbage_request_.data(),
|
||||
garbage_request_.size(),
|
||||
signature_.data(), &signature_size_),
|
||||
WB_RESULT_OK);
|
||||
|
||||
signature_.resize(signature_size_);
|
||||
ASSERT_EQ(signature_, GetSignature(garbage_request_));
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxSignRenewalRequestTest, InvalidParameterForNullWhitebox) {
|
||||
LoadLicense();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
size_t signature_size = kSignatureSize;
|
||||
std::vector<uint8_t> signature(signature_size);
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_SignRenewalRequest(nullptr, kDummyMessage, kDummyMessageSize,
|
||||
signature.data(), &signature_size),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
ASSERT_EQ(WB_License_SignRenewalRequest(nullptr, garbage_request_.data(),
|
||||
garbage_request_.size(),
|
||||
signature_.data(), &signature_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxSignRenewalRequestTest, InvalidParameterForNullMessage) {
|
||||
LoadLicense();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
size_t signature_size = kSignatureSize;
|
||||
std::vector<uint8_t> signature(signature_size);
|
||||
|
||||
ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, nullptr, kDummyMessageSize,
|
||||
signature.data(), &signature_size),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
ASSERT_EQ(
|
||||
WB_License_SignRenewalRequest(whitebox_, nullptr, garbage_request_.size(),
|
||||
signature_.data(), &signature_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxSignRenewalRequestTest,
|
||||
InvalidParameterForZeroMessageSize) {
|
||||
LoadLicense();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
size_t signature_size = kSignatureSize;
|
||||
std::vector<uint8_t> signature(signature_size);
|
||||
|
||||
ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, kDummyMessage, 0,
|
||||
signature.data(), &signature_size),
|
||||
ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, garbage_request_.data(), 0,
|
||||
signature_.data(), &signature_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxSignRenewalRequestTest,
|
||||
InvalidParameterForNullSignature) {
|
||||
LoadLicense();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
size_t signature_size = kSignatureSize;
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_SignRenewalRequest(whitebox_, kDummyMessage, kDummyMessageSize,
|
||||
nullptr, &signature_size),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, garbage_request_.data(),
|
||||
garbage_request_.size(), nullptr,
|
||||
&signature_size_),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxSignRenewalRequestTest,
|
||||
InvalidParameterForNullSignatureSize) {
|
||||
LoadLicense();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
size_t signature_size = kSignatureSize;
|
||||
std::vector<uint8_t> signature(signature_size);
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_SignRenewalRequest(whitebox_, kDummyMessage, kDummyMessageSize,
|
||||
signature.data(), nullptr),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, garbage_request_.data(),
|
||||
garbage_request_.size(),
|
||||
signature_.data(), nullptr),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxSignRenewalRequestTest, BufferTooSmall) {
|
||||
// TODO: This test must be skipped as the "too small" check takes place after
|
||||
// we check for a key.
|
||||
GTEST_SKIP();
|
||||
|
||||
LoadLicense();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
// We need the signature to be too small. While it would be possible to use
|
||||
// zero, using a non-zero value ensures that we are not combining "empty" and
|
||||
// "too small".
|
||||
size_t signature_size = 1;
|
||||
std::vector<uint8_t> signature(signature_size);
|
||||
signature_size_ = 1;
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_SignRenewalRequest(whitebox_, kDummyMessage, kDummyMessageSize,
|
||||
signature.data(), &signature_size),
|
||||
WB_RESULT_BUFFER_TOO_SMALL);
|
||||
ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, garbage_request_.data(),
|
||||
garbage_request_.size(),
|
||||
signature_.data(), &signature_size_),
|
||||
WB_RESULT_BUFFER_TOO_SMALL);
|
||||
|
||||
// Since the API does not limit the signature size, we can't specify the
|
||||
// actual expected size, however, it should at least be greater than our "too
|
||||
// small" size.
|
||||
ASSERT_GT(signature_size, 1);
|
||||
ASSERT_GT(signature_size_, 1);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxSignRenewalRequestTest, InvalidState) {
|
||||
TEST_F(LicenseWhiteboxSignRenewalRequestTest, InvalidStateForNoLicense) {
|
||||
// Unlike the other tests, we do not call LoadLicense() because we need to
|
||||
// have no license loaded in order to have no renewal key, which is the
|
||||
// criteria WB_RESULT_INVALID_STATE.
|
||||
|
||||
size_t signature_size = kSignatureSize;
|
||||
std::vector<uint8_t> signature(signature_size);
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_SignRenewalRequest(whitebox_, kDummyMessage, kDummyMessageSize,
|
||||
signature.data(), &signature_size),
|
||||
WB_RESULT_INVALID_STATE);
|
||||
ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, garbage_request_.data(),
|
||||
garbage_request_.size(),
|
||||
signature_.data(), &signature_size_),
|
||||
WB_RESULT_INVALID_STATE);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(LicenseWhiteboxSignRenewalRequestTest, InvalidStateForNoSigningKey) {
|
||||
// Make a license with no signing key but has a content key. Every license
|
||||
// must have a content key.
|
||||
LicenseBuilder builder;
|
||||
builder.AddStubbedContentKey();
|
||||
|
||||
License license;
|
||||
builder.Build(*public_key_, &license);
|
||||
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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);
|
||||
|
||||
ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, garbage_request_.data(),
|
||||
garbage_request_.size(),
|
||||
signature_.data(), &signature_size_),
|
||||
WB_RESULT_INVALID_STATE);
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
36
api/license_whitebox_test_base.cc
Normal file
36
api/license_whitebox_test_base.cc
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
#include "api/license_whitebox_test_base.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "api/test_data.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
void LicenseWhiteboxTestBase::SetUp() {
|
||||
const std::vector<uint8_t> init_data = GetLicenseInitData();
|
||||
ASSERT_EQ(WB_License_Create(init_data.data(), init_data.size(), &whitebox_),
|
||||
WB_RESULT_OK);
|
||||
|
||||
const auto public_key_data = GetMatchingLicensePublicKey();
|
||||
public_key_.reset(RsaPublicKey::Create(
|
||||
std::string(public_key_data.begin(), public_key_data.end())));
|
||||
|
||||
ASSERT_TRUE(public_key_);
|
||||
}
|
||||
|
||||
void LicenseWhiteboxTestBase::TearDown() {
|
||||
WB_License_Delete(whitebox_);
|
||||
}
|
||||
|
||||
void LicenseWhiteboxTestBase::Modify(std::vector<uint8_t>* data) const {
|
||||
ASSERT_TRUE(data);
|
||||
ASSERT_GT(data->size(), 0);
|
||||
|
||||
// Bitwise-not the first byte so that we are guaranteed to have at least one
|
||||
// byte different from the original data.
|
||||
data->data()[0] = ~data->data()[0];
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
34
api/license_whitebox_test_base.h
Normal file
34
api/license_whitebox_test_base.h
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
#ifndef WHITEBOX_API_LICENSE_WHITEBOX_TEST_BASE_H_
|
||||
#define WHITEBOX_API_LICENSE_WHITEBOX_TEST_BASE_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/golden_data.h"
|
||||
#include "api/license_whitebox.h"
|
||||
#include "crypto_utils/rsa_key.h"
|
||||
#include "testing/include/gtest/gtest.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
class LicenseWhiteboxTestBase : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override;
|
||||
|
||||
void TearDown() override;
|
||||
|
||||
// Modify a buffer so that it won't be the exact same as it was before. This
|
||||
// to make it easier to invalidate signatures.
|
||||
void Modify(std::vector<uint8_t>* data) const;
|
||||
|
||||
std::unique_ptr<RsaPublicKey> public_key_;
|
||||
|
||||
GoldenData golden_data_;
|
||||
|
||||
WB_License_Whitebox* whitebox_;
|
||||
};
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
#endif // WHITEBOX_API_LICENSE_WHITEBOX_TEST_BASE_H_
|
||||
@@ -6,127 +6,186 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "api/license_builder.h"
|
||||
#include "api/license_whitebox_test_base.h"
|
||||
#include "api/test_data.h"
|
||||
#include "crypto_utils/crypto_util.h"
|
||||
#include "crypto_utils/rsa_key.h"
|
||||
#include "testing/include/gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
namespace widevine {
|
||||
|
||||
// TODO: This needs to be the real response.
|
||||
const uint8_t kResponse[] = {
|
||||
0x1e, 0x70, 0xbd, 0xeb, 0x24, 0xf2, 0x9d, 0x05, 0xc5, 0xb5,
|
||||
0xf4, 0xca, 0xe6, 0x1d, 0x01, 0x97, 0x29, 0xf4, 0xe0, 0x7c,
|
||||
0xfd, 0xcc, 0x97, 0x8d, 0xc2, 0xbb, 0x2d, 0x9b, 0x6b, 0x45,
|
||||
0x06, 0xbd, 0x2c, 0x66, 0x10, 0x42, 0x73, 0x8d, 0x88, 0x9b,
|
||||
0x18, 0xcc, 0xcb, 0x7e, 0x43, 0x23, 0x06, 0xe9, 0x8f, 0x8f,
|
||||
};
|
||||
const size_t kResponseSize = sizeof(kResponse);
|
||||
|
||||
// TODO: This needs to be the actual signature.
|
||||
const uint8_t kSignature[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
const uint8_t kSignatureSize = sizeof(kSignature);
|
||||
|
||||
// These tests assume that WB_License_Create() and
|
||||
// WB_License_ProcessLicenseResponse() work as all tests will require a valid
|
||||
// white-box instance with a valid license already loaded.
|
||||
class LicenseWhiteboxVerifyRenewalResponseTest : public ::testing::Test {
|
||||
class LicenseWhiteboxVerifyRenewalResponseTest
|
||||
: public LicenseWhiteboxTestBase {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
const std::vector<uint8_t> init_data_ = GetLicenseInitData();
|
||||
ASSERT_EQ(
|
||||
WB_License_Create(init_data_.data(), init_data_.size(), &whitebox_),
|
||||
WB_RESULT_OK);
|
||||
LicenseWhiteboxTestBase::SetUp();
|
||||
garbage_renewal_signature_ = Sign(garbage_renewal_message_);
|
||||
}
|
||||
|
||||
void TearDown() override { WB_License_Delete(whitebox_); }
|
||||
void LoadLicense(const std::vector<uint8_t>& padding) {
|
||||
const auto signing_key = LicenseBuilder::DefaultSigningKey();
|
||||
|
||||
void LoadLicense() {
|
||||
// TODO: Load the license here. It would be nice if we could do it in
|
||||
// SetUp(), but since we need to support the WB_RESULT_INVALID_STATE test
|
||||
// case, we need a way to not load a license.
|
||||
// We need a license so that we can always have a valid signature for our
|
||||
// message(s), but don't load the license as some test will need no
|
||||
// license loaded.
|
||||
LicenseBuilder builder;
|
||||
builder.AddSigningKey(signing_key, padding);
|
||||
builder.AddStubbedContentKey();
|
||||
|
||||
License license;
|
||||
builder.Build(*public_key_, &license);
|
||||
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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);
|
||||
}
|
||||
|
||||
WB_License_Whitebox* whitebox_ = nullptr;
|
||||
std::vector<uint8_t> Sign(const std::vector<uint8_t>& message) {
|
||||
const auto key = LicenseBuilder::DefaultSigningKey();
|
||||
|
||||
// The server signing key is the first half of the signing key.
|
||||
std::string server_key = std::string(
|
||||
key.begin(), key.begin() + crypto_util::kSigningKeySizeBytes);
|
||||
|
||||
const auto signature = crypto_util::CreateSignatureHmacSha256(
|
||||
server_key, std::string(message.begin(), message.end()));
|
||||
|
||||
return std::vector<uint8_t>(signature.begin(), signature.end());
|
||||
}
|
||||
|
||||
// Allow this to be mutable so that a test can corrupt it. This data is random
|
||||
// and has no meaning.
|
||||
std::vector<uint8_t> garbage_renewal_message_ = {
|
||||
0xf1, 0x5c, 0xf1, 0x92, 0x73, 0x0c, 0xf9, 0x5d, 0x2b, 0x1e, 0x3f, 0x51,
|
||||
0xb2, 0x75, 0xa1, 0xb3, 0xd3, 0xa8, 0x16, 0x83, 0x08, 0xf1, 0xe2, 0x47,
|
||||
0x7b, 0x80, 0x37, 0xed, 0xf8, 0x8b, 0x1d, 0x79, 0x7f, 0xb0, 0xa1, 0xde,
|
||||
0xcd, 0xba, 0xd4, 0x8f, 0xb7, 0x3c, 0x1a, 0x3f, 0x3e, 0x3a, 0xb4, 0xea,
|
||||
0xd8, 0xd7, 0xa4, 0x65, 0xa1, 0x40, 0x87, 0xf6, 0xaa, 0xf4, 0xb1, 0x24,
|
||||
0x17, 0xed, 0xf4, 0xca, 0x18, 0x51, 0x4a, 0x54, 0x3c, 0x73, 0xca, 0x45,
|
||||
0x3e, 0xef, 0x39, 0x49, 0x65, 0xdd, 0x62, 0x11, 0x99, 0x13, 0x40, 0x67,
|
||||
0x7f, 0xfb, 0x07, 0x09, 0x1e, 0xfe, 0x0e, 0xdc, 0xda, 0x0a, 0x85, 0x91,
|
||||
0x15, 0x40, 0xa8, 0x7a, 0x0e, 0x76, 0xf6, 0xbe, 0x94, 0x2c, 0x70, 0xe9,
|
||||
0x07, 0xea, 0xf8, 0x7a, 0xc3, 0x48, 0xe1, 0xcf, 0xf4, 0x7b, 0xd6, 0x27,
|
||||
0xd7, 0x30, 0x6f, 0x18, 0xb3, 0x2d, 0x6a, 0x23,
|
||||
};
|
||||
|
||||
// Allow this to be mutable so that we can initialize it in SetUp() but also
|
||||
// so a test can corrupt it.
|
||||
std::vector<uint8_t> garbage_renewal_signature_;
|
||||
};
|
||||
|
||||
// TODO: Implement the success test case.
|
||||
// TODO: Implement a test that uses a real serialized response. Once we have a
|
||||
// real serialized response, we should update all the tests - except the
|
||||
// SuccessForGarbageMessage - to use the real serialized response.
|
||||
|
||||
TEST_F(LicenseWhiteboxVerifyRenewalResponseTest, SuccessForGarbageMessage) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_,
|
||||
garbage_renewal_message_.data(),
|
||||
garbage_renewal_message_.size(),
|
||||
garbage_renewal_signature_.data(),
|
||||
garbage_renewal_signature_.size()),
|
||||
WB_RESULT_OK);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxVerifyRenewalResponseTest,
|
||||
SuccessWithSigningKeyPKSC8Padding) {
|
||||
LoadLicense(LicenseBuilder::PKSC8Padding());
|
||||
|
||||
ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_,
|
||||
garbage_renewal_message_.data(),
|
||||
garbage_renewal_message_.size(),
|
||||
garbage_renewal_signature_.data(),
|
||||
garbage_renewal_signature_.size()),
|
||||
WB_RESULT_OK);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxVerifyRenewalResponseTest,
|
||||
InvalidParameterForNullWhitebox) {
|
||||
LoadLicense();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(WB_License_VerifyRenewalResponse(nullptr, kResponse, kResponseSize,
|
||||
kSignature, kSignatureSize),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
ASSERT_EQ(
|
||||
WB_License_VerifyRenewalResponse(nullptr, garbage_renewal_message_.data(),
|
||||
garbage_renewal_message_.size(),
|
||||
garbage_renewal_signature_.data(),
|
||||
garbage_renewal_signature_.size()),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxVerifyRenewalResponseTest,
|
||||
InvalidParameterForNullMessage) {
|
||||
LoadLicense();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_, nullptr, kResponseSize,
|
||||
kSignature, kSignatureSize),
|
||||
ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_, nullptr,
|
||||
garbage_renewal_message_.size(),
|
||||
garbage_renewal_signature_.data(),
|
||||
garbage_renewal_signature_.size()),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxVerifyRenewalResponseTest,
|
||||
InvalidParameterForZeroMessageSize) {
|
||||
LoadLicense();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_, kResponse, 0,
|
||||
kSignature, kSignatureSize),
|
||||
ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_,
|
||||
garbage_renewal_message_.data(), 0,
|
||||
garbage_renewal_signature_.data(),
|
||||
garbage_renewal_signature_.size()),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxVerifyRenewalResponseTest,
|
||||
InvalidParameterForNullSignature) {
|
||||
LoadLicense();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
ASSERT_EQ(WB_License_VerifyRenewalResponse(
|
||||
whitebox_, kResponse, kResponseSize, nullptr, kSignatureSize),
|
||||
whitebox_, garbage_renewal_message_.data(),
|
||||
garbage_renewal_message_.size(), nullptr,
|
||||
garbage_renewal_signature_.size()),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxVerifyRenewalResponseTest,
|
||||
InvalidParameterForInvalidSignatureSize) {
|
||||
// TODO: This test needs to be skipped for now as the check for signature size
|
||||
// is done after we compute the signature. This requires a valid license to
|
||||
// have been loaded.
|
||||
GTEST_SKIP();
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
// We need an invalid signature size, the easiest way to do this is to take
|
||||
// the expected signature size and trim a bit off the end.
|
||||
const size_t invalid_signature_size = kSignatureSize - 1;
|
||||
|
||||
LoadLicense();
|
||||
|
||||
ASSERT_EQ(
|
||||
WB_License_VerifyRenewalResponse(whitebox_, kResponse, kResponseSize,
|
||||
kSignature, invalid_signature_size),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
ASSERT_EQ(WB_License_VerifyRenewalResponse(
|
||||
whitebox_, garbage_renewal_message_.data(),
|
||||
garbage_renewal_message_.size(),
|
||||
garbage_renewal_signature_.data(), 14),
|
||||
WB_RESULT_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
TEST_F(LicenseWhiteboxVerifyRenewalResponseTest,
|
||||
InvalidSignatureForInvalidSignature) {
|
||||
// TODO: This test needs to be skipped for now as comparing signatures
|
||||
// requires a key provided by a valid license.
|
||||
GTEST_SKIP();
|
||||
InvalidSignatureForModifiedMessage) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
// In order to create an invalid signature, copy the valid signature and flip
|
||||
// one byte. This will ensure that our invalid signature is always different
|
||||
// from the valid signature.
|
||||
std::vector<uint8_t> invalid_signature(kSignature,
|
||||
kSignature + kSignatureSize);
|
||||
invalid_signature[0] = ~invalid_signature[0];
|
||||
Modify(&garbage_renewal_message_);
|
||||
|
||||
LoadLicense();
|
||||
ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_,
|
||||
garbage_renewal_message_.data(),
|
||||
garbage_renewal_message_.size(),
|
||||
garbage_renewal_signature_.data(),
|
||||
garbage_renewal_signature_.size()),
|
||||
WB_RESULT_INVALID_SIGNATURE);
|
||||
}
|
||||
|
||||
ASSERT_EQ(WB_License_VerifyRenewalResponse(
|
||||
whitebox_, kResponse, kResponseSize, invalid_signature.data(),
|
||||
invalid_signature.size()),
|
||||
TEST_F(LicenseWhiteboxVerifyRenewalResponseTest,
|
||||
InvalidSignatureForModifiedSignature) {
|
||||
LoadLicense(LicenseBuilder::NoPadding());
|
||||
|
||||
Modify(&garbage_renewal_signature_);
|
||||
|
||||
ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_,
|
||||
garbage_renewal_message_.data(),
|
||||
garbage_renewal_message_.size(),
|
||||
garbage_renewal_signature_.data(),
|
||||
garbage_renewal_signature_.size()),
|
||||
WB_RESULT_INVALID_SIGNATURE);
|
||||
}
|
||||
|
||||
@@ -134,9 +193,36 @@ TEST_F(LicenseWhiteboxVerifyRenewalResponseTest, InvalidStateForNoLicense) {
|
||||
// Unlike the other tests, we do not call LoadLicense() as the criteria for
|
||||
// WB_RESULT_INVALID_STATE is that no key can be found and keys are provided
|
||||
// via a license.
|
||||
ASSERT_EQ(
|
||||
WB_License_VerifyRenewalResponse(whitebox_, kResponse, kResponseSize,
|
||||
kSignature, kSignatureSize),
|
||||
WB_RESULT_INVALID_STATE);
|
||||
ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_,
|
||||
garbage_renewal_message_.data(),
|
||||
garbage_renewal_message_.size(),
|
||||
garbage_renewal_signature_.data(),
|
||||
garbage_renewal_signature_.size()),
|
||||
WB_RESULT_INVALID_STATE);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(LicenseWhiteboxVerifyRenewalResponseTest, InvalidStateForNoSigningKey) {
|
||||
// Create a license with no signing key and one content key (every license
|
||||
// must have a content key).
|
||||
widevine::LicenseBuilder builder;
|
||||
builder.AddStubbedContentKey();
|
||||
|
||||
License license;
|
||||
builder.Build(*public_key_, &license);
|
||||
|
||||
ASSERT_EQ(WB_License_ProcessLicenseResponse(
|
||||
whitebox_, 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);
|
||||
|
||||
ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_,
|
||||
garbage_renewal_message_.data(),
|
||||
garbage_renewal_message_.size(),
|
||||
garbage_renewal_signature_.data(),
|
||||
garbage_renewal_signature_.size()),
|
||||
WB_RESULT_INVALID_STATE);
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
15
api/result.h
15
api/result.h
@@ -29,23 +29,20 @@ typedef enum {
|
||||
// The message and signature did not pass signature verification.
|
||||
WB_RESULT_INVALID_SIGNATURE = 5,
|
||||
|
||||
// A key was requested that had not been loaded into the white-box.
|
||||
WB_RESULT_NO_SUCH_KEY = 6,
|
||||
|
||||
// A key was requested that was found in the white-box, but is being used
|
||||
// improperly.
|
||||
WB_RESULT_WRONG_KEY_TYPE = 7,
|
||||
// The request key is not available for the operations. For example, no key
|
||||
// exists for the given key id.
|
||||
WB_RESULT_KEY_UNAVAILABLE = 6,
|
||||
|
||||
// The requested key's security level is not sufficient for the operation.
|
||||
WB_RESULT_INSUFFICIENT_SECURITY_LEVEL = 8,
|
||||
WB_RESULT_INSUFFICIENT_SECURITY_LEVEL = 7,
|
||||
|
||||
// The input data failed to be verified. This may happen if the data was
|
||||
// corrupted or tampered.
|
||||
WB_RESULT_DATA_VERIFICATION_ERROR = 9,
|
||||
WB_RESULT_DATA_VERIFICATION_ERROR = 8,
|
||||
|
||||
// The padding at the end of the decrypted data does not match the
|
||||
// specification.
|
||||
WB_RESULT_INVALID_PADDING = 10,
|
||||
WB_RESULT_INVALID_PADDING = 9,
|
||||
} WB_Result;
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -26,15 +26,17 @@
|
||||
#include "third_party/boringssl/src/include/openssl/sha.h"
|
||||
|
||||
namespace {
|
||||
using KeyContainer = video_widevine::License_KeyContainer;
|
||||
using RsaPrivateKey = widevine::RsaPrivateKey;
|
||||
using AesCbcDecryptor = widevine::AesCbcDecryptor;
|
||||
using AesCtrDecryptor = widevine::AesCtrEncryptor;
|
||||
using SecurityLevel = video_widevine::License_KeyContainer_SecurityLevel;
|
||||
using KeyContainer = video_widevine::License_KeyContainer;
|
||||
using RsaPrivateKey = widevine::RsaPrivateKey;
|
||||
|
||||
struct ContentKey {
|
||||
// Minimum security level that this key requires in order to be used.
|
||||
SecurityLevel level;
|
||||
// When we store a key, we create our own little policy for the key saying
|
||||
// what functions may use it. This allows us to "blacklist" a key by setting
|
||||
// all "allow_*" to false.
|
||||
bool allow_decrypt;
|
||||
bool allow_masked_decrypt;
|
||||
|
||||
// Key used to decrypt content.
|
||||
std::vector<uint8_t> key;
|
||||
@@ -50,10 +52,6 @@ struct WB_License_Whitebox {
|
||||
std::string server_signing_key;
|
||||
std::string client_signing_key;
|
||||
|
||||
// We use two data structures to track keys. We track all the key ids seen
|
||||
// when loading the license and the content keys. This allows us to tell the
|
||||
// difference between a key missing and a key being misused.
|
||||
std::set<std::string> all_key_ids;
|
||||
std::map<std::string, ContentKey> content_keys;
|
||||
};
|
||||
|
||||
@@ -88,36 +86,15 @@ void ApplyPattern(const uint8_t* input_buffer,
|
||||
}
|
||||
}
|
||||
|
||||
WB_Result FindKey(const WB_License_Whitebox* whitebox,
|
||||
const uint8_t* id,
|
||||
size_t id_size,
|
||||
ContentKey* key) {
|
||||
const ContentKey* FindKey(const WB_License_Whitebox* whitebox,
|
||||
const uint8_t* id,
|
||||
size_t id_size) {
|
||||
DCHECK(whitebox);
|
||||
DCHECK(id);
|
||||
DCHECK_GT(id_size, 0u);
|
||||
DCHECK(key);
|
||||
|
||||
const std::string key_id = AsString(id, id_size);
|
||||
|
||||
if (whitebox->all_key_ids.empty()) {
|
||||
DVLOG(1) << "Invalid state: no keys have been loaded.";
|
||||
return WB_RESULT_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (whitebox->all_key_ids.find(key_id) == whitebox->all_key_ids.end()) {
|
||||
DVLOG(1) << "No such key: " << base::HexEncode(id, id_size) << ".";
|
||||
return WB_RESULT_NO_SUCH_KEY;
|
||||
}
|
||||
|
||||
const auto found = whitebox->content_keys.find(key_id);
|
||||
|
||||
if (found == whitebox->content_keys.end()) {
|
||||
DVLOG(1) << "Wrong key type: " << base::HexEncode(id, id_size) << ".";
|
||||
return WB_RESULT_WRONG_KEY_TYPE;
|
||||
}
|
||||
|
||||
*key = found->second;
|
||||
return WB_RESULT_OK;
|
||||
return found == whitebox->content_keys.end() ? nullptr : &found->second;
|
||||
}
|
||||
|
||||
WB_Result DecryptBuffer(WB_CipherMode mode,
|
||||
@@ -246,6 +223,7 @@ WB_Result WB_License_SignLicenseRequest(const WB_License_Whitebox* whitebox,
|
||||
return WB_RESULT_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
*signature_size = result.size();
|
||||
return WB_RESULT_OK;
|
||||
}
|
||||
|
||||
@@ -258,16 +236,33 @@ WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox,
|
||||
size_t session_key_size,
|
||||
const uint8_t* license_request,
|
||||
size_t license_request_size) {
|
||||
const size_t kSigningKeySizeBytes =
|
||||
widevine::crypto_util::kSigningKeySizeBytes;
|
||||
|
||||
if (!whitebox || !message || !signature || !session_key || !license_request) {
|
||||
DVLOG(1) << "Invalid parameter: null pointer.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (message_size == 0 || license_request_size == 0) {
|
||||
// If we have already loaded a license, ProcessLicenseResponse() may not be
|
||||
// called again. The white-box needs to be destroyed and a new one needs
|
||||
// to be created.
|
||||
if (whitebox->content_keys.size() > 0) {
|
||||
DVLOG(1) << "Invalid state: already loaded a license.";
|
||||
return WB_RESULT_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (message_size == 0 || session_key_size == 0 || license_request_size == 0) {
|
||||
DVLOG(1) << "Invalid parameter: array size 0.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// Because we use SHA256, the hash will be 32 bytes (256 bits).
|
||||
if (signature_size != 32) {
|
||||
DVLOG(1) << "Invalid parameter: invalid signature size.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
std::string decrypted_session_key;
|
||||
if (!whitebox->key->Decrypt(AsString(session_key, session_key_size),
|
||||
&decrypted_session_key)) {
|
||||
@@ -280,15 +275,15 @@ WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox,
|
||||
widevine::AsStringView(license_request, license_request_size),
|
||||
widevine::crypto_util::kSigningKeySizeBits * 2);
|
||||
|
||||
if (signing_key_material.size() <
|
||||
widevine::crypto_util::kSigningKeySizeBytes * 2) {
|
||||
if (signing_key_material.size() < kSigningKeySizeBytes * 2) {
|
||||
DVLOG(1) << "Invalid parameter: invalid session key size.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
const std::string server_signing_key =
|
||||
signing_key_material.substr(0, kSigningKeySizeBytes);
|
||||
if (!widevine::crypto_util::VerifySignatureHmacSha256(
|
||||
signing_key_material,
|
||||
widevine::AsStringView(signature, signature_size),
|
||||
server_signing_key, widevine::AsStringView(signature, signature_size),
|
||||
widevine::AsStringView(message, message_size))) {
|
||||
DVLOG(1) << "Failed to verify signed message.";
|
||||
return WB_RESULT_INVALID_SIGNATURE;
|
||||
@@ -314,8 +309,18 @@ WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox,
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
CHECK(!license.key().empty());
|
||||
std::string server_renewal_key;
|
||||
std::string client_renewal_key;
|
||||
std::map<std::string, ContentKey> content_keys;
|
||||
|
||||
for (const auto& key : license.key()) {
|
||||
// If this is not a key we're interested in, skip it as soon as possible.
|
||||
// Don't even bother unwrapping it.
|
||||
if (key.type() != KeyContainer::SIGNING &&
|
||||
key.type() != KeyContainer::CONTENT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string wrapped_key = key.key();
|
||||
std::vector<uint8_t> unwrapped_key(wrapped_key.size());
|
||||
|
||||
@@ -329,47 +334,64 @@ WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox,
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
whitebox->all_key_ids.insert(key.id());
|
||||
|
||||
switch (key.type()) {
|
||||
case KeyContainer::SIGNING: {
|
||||
const size_t kSigningKeySizeBytes =
|
||||
widevine::crypto_util::kSigningKeySizeBytes;
|
||||
if (unwrapped_key.size() < kSigningKeySizeBytes * 2) {
|
||||
DVLOG(1) << "Invalid parameter: Invalid signing key.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
const std::string signing_key =
|
||||
AsString(unwrapped_key.data(), unwrapped_key.size());
|
||||
whitebox->server_signing_key =
|
||||
signing_key.substr(0, kSigningKeySizeBytes);
|
||||
whitebox->client_signing_key =
|
||||
signing_key.substr(kSigningKeySizeBytes, kSigningKeySizeBytes);
|
||||
break;
|
||||
if (key.type() == KeyContainer::SIGNING) {
|
||||
if (unwrapped_key.size() < kSigningKeySizeBytes * 2) {
|
||||
DVLOG(1) << "Invalid parameter: Invalid signing key.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
case KeyContainer::CONTENT: {
|
||||
constexpr size_t kContentKeySizeBytes = 16;
|
||||
if (unwrapped_key.size() < kContentKeySizeBytes) {
|
||||
DVLOG(1) << "Invalid parameter: Invalid content key.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
const std::string signing_key =
|
||||
AsString(unwrapped_key.data(), unwrapped_key.size());
|
||||
server_renewal_key = signing_key.substr(0, kSigningKeySizeBytes);
|
||||
client_renewal_key =
|
||||
signing_key.substr(kSigningKeySizeBytes, kSigningKeySizeBytes);
|
||||
} else if (key.type() == KeyContainer::CONTENT) {
|
||||
constexpr size_t kContentKeySizeBytes = 16;
|
||||
|
||||
unwrapped_key.resize(kContentKeySizeBytes);
|
||||
whitebox->content_keys[key.id()] = {key.level(),
|
||||
std::move(unwrapped_key)};
|
||||
break;
|
||||
if (unwrapped_key.size() < kContentKeySizeBytes) {
|
||||
DVLOG(1) << "Invalid parameter: Invalid content key.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
default:
|
||||
// We purposefully ignore the other cases. In the case that any other
|
||||
// key appeared in the license, the key id would have been tracked in
|
||||
// |all_key_ids|.
|
||||
break;
|
||||
unwrapped_key.resize(kContentKeySizeBytes);
|
||||
|
||||
ContentKey content_key;
|
||||
|
||||
switch (key.level()) {
|
||||
case video_widevine::
|
||||
License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO:
|
||||
content_key.allow_decrypt = true;
|
||||
content_key.allow_masked_decrypt = true;
|
||||
content_key.key = std::move(unwrapped_key);
|
||||
break;
|
||||
case video_widevine::
|
||||
License_KeyContainer_SecurityLevel_SW_SECURE_DECODE:
|
||||
content_key.allow_decrypt = false;
|
||||
content_key.allow_masked_decrypt = true;
|
||||
content_key.key = std::move(unwrapped_key);
|
||||
break;
|
||||
default:
|
||||
content_key.allow_decrypt = false;
|
||||
content_key.allow_masked_decrypt = false;
|
||||
// Don't set key. We don't want to save this key as we should never
|
||||
// be using it. We only have an entry so that we can handle errors
|
||||
// correctly.
|
||||
break;
|
||||
}
|
||||
|
||||
content_keys[key.id()] = content_key;
|
||||
} else {
|
||||
// We should have already skipped over this key.
|
||||
CHECK(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the loaded state over to the white-box instance now that we know we
|
||||
// have a valid state.
|
||||
whitebox->server_signing_key.swap(server_renewal_key);
|
||||
whitebox->client_signing_key.swap(client_renewal_key);
|
||||
whitebox->content_keys.swap(content_keys);
|
||||
|
||||
return WB_RESULT_OK;
|
||||
}
|
||||
|
||||
@@ -388,10 +410,13 @@ WB_Result WB_License_SignRenewalRequest(const WB_License_Whitebox* whitebox,
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// License request must have been processed at least once before calling this
|
||||
// function.
|
||||
if (whitebox->content_keys.empty()) {
|
||||
DVLOG(1) << "Invalid state: missing license.";
|
||||
return WB_RESULT_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (whitebox->client_signing_key.empty()) {
|
||||
DVLOG(1) << __func__ << " called before license received.";
|
||||
DVLOG(1) << "Invalid state: license does not support renewals.";
|
||||
return WB_RESULT_INVALID_STATE;
|
||||
}
|
||||
|
||||
@@ -408,6 +433,7 @@ WB_Result WB_License_SignRenewalRequest(const WB_License_Whitebox* whitebox,
|
||||
return WB_RESULT_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
*signature_size = computed_signature.size();
|
||||
return WB_RESULT_OK;
|
||||
}
|
||||
|
||||
@@ -426,10 +452,13 @@ WB_Result WB_License_VerifyRenewalResponse(const WB_License_Whitebox* whitebox,
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// License request must have been processed at least once before calling this
|
||||
// function.
|
||||
if (whitebox->content_keys.empty()) {
|
||||
DVLOG(1) << "Invalid state: missing license.";
|
||||
return WB_RESULT_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (whitebox->server_signing_key.empty()) {
|
||||
DVLOG(1) << __func__ << " called before license received.";
|
||||
DVLOG(1) << "Invalid state: license does not support renewals.";
|
||||
return WB_RESULT_INVALID_STATE;
|
||||
}
|
||||
|
||||
@@ -438,15 +467,14 @@ WB_Result WB_License_VerifyRenewalResponse(const WB_License_Whitebox* whitebox,
|
||||
whitebox->server_signing_key,
|
||||
widevine::AsStringView(message, message_size));
|
||||
|
||||
if (signature_size < computed_signature.size()) {
|
||||
DVLOG(1) << "Invalid parameters: invalid signature "
|
||||
"size.";
|
||||
if (signature_size != computed_signature.size()) {
|
||||
DVLOG(1) << "Invalid parameters: invalid signature size.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (computed_signature != widevine::AsStringView(signature, signature_size)) {
|
||||
DVLOG(1) << "Data verification error: signatures do not match.";
|
||||
return WB_RESULT_DATA_VERIFICATION_ERROR;
|
||||
return WB_RESULT_INVALID_SIGNATURE;
|
||||
}
|
||||
|
||||
return WB_RESULT_OK;
|
||||
@@ -463,6 +491,11 @@ WB_Result WB_License_GetSecretString(const WB_License_Whitebox* whitebox,
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (whitebox->content_keys.empty()) {
|
||||
DVLOG(1) << "Invalid state: missing license.";
|
||||
return WB_RESULT_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (mode != WB_CIPHER_MODE_CTR && mode != WB_CIPHER_MODE_CBC) {
|
||||
DVLOG(1) << "Invalid parameter: invalid cipher mode.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
@@ -475,11 +508,17 @@ WB_Result WB_License_GetSecretString(const WB_License_Whitebox* whitebox,
|
||||
|
||||
// The secret string can differ between keys, so we need to make sure that
|
||||
// the key id is actually a content key.
|
||||
ContentKey content_key;
|
||||
const WB_Result key_lookup_result =
|
||||
FindKey(whitebox, key_id, key_id_size, &content_key);
|
||||
if (key_lookup_result != WB_RESULT_OK) {
|
||||
return key_lookup_result;
|
||||
const ContentKey* content_key = FindKey(whitebox, key_id, key_id_size);
|
||||
|
||||
if (content_key == nullptr) {
|
||||
DVLOG(1) << "Key unavailable: could not find key.";
|
||||
return WB_RESULT_KEY_UNAVAILABLE;
|
||||
}
|
||||
|
||||
if (!content_key->allow_masked_decrypt) {
|
||||
DVLOG(1) << "Insufficient security level: key policy does not allow use "
|
||||
"with MaskedDecrypt().";
|
||||
return WB_RESULT_INSUFFICIENT_SECURITY_LEVEL;
|
||||
}
|
||||
|
||||
if (!widevine::MemCopy(kSecretStringPattern, kSecretStringPatternSize,
|
||||
@@ -489,6 +528,7 @@ WB_Result WB_License_GetSecretString(const WB_License_Whitebox* whitebox,
|
||||
return WB_RESULT_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
*secret_string_size = kSecretStringPatternSize;
|
||||
return WB_RESULT_OK;
|
||||
}
|
||||
|
||||
@@ -507,31 +547,31 @@ WB_Result WB_License_Decrypt(const WB_License_Whitebox* whitebox,
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (whitebox->content_keys.empty()) {
|
||||
DVLOG(1) << "Invalid state: missing license.";
|
||||
return WB_RESULT_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (key_id_size == 0) {
|
||||
DVLOG(1) << "Invalid parameter: array size 0.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
ContentKey content_key;
|
||||
const WB_Result key_lookup_result =
|
||||
FindKey(whitebox, key_id, key_id_size, &content_key);
|
||||
if (key_lookup_result != WB_RESULT_OK) {
|
||||
return key_lookup_result;
|
||||
const ContentKey* content_key = FindKey(whitebox, key_id, key_id_size);
|
||||
|
||||
if (content_key == nullptr) {
|
||||
DVLOG(1) << "Key unavailable: could not find key.";
|
||||
return WB_RESULT_KEY_UNAVAILABLE;
|
||||
}
|
||||
|
||||
// Check if decryption is allowed by the policy.
|
||||
switch (content_key.level) {
|
||||
case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO:
|
||||
break;
|
||||
case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_DECODE:
|
||||
case video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_CRYPTO:
|
||||
case video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_DECODE:
|
||||
case video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_ALL:
|
||||
return WB_RESULT_INSUFFICIENT_SECURITY_LEVEL;
|
||||
if (!content_key->allow_decrypt) {
|
||||
DVLOG(1) << "Insufficient security level: key policy does not allow use "
|
||||
"with Decrypt().";
|
||||
return WB_RESULT_INSUFFICIENT_SECURITY_LEVEL;
|
||||
}
|
||||
|
||||
// DecryptBuffer() will validate the remaining decryption parameters.
|
||||
return DecryptBuffer(mode, content_key.key, input_data, input_data_size, iv,
|
||||
return DecryptBuffer(mode, content_key->key, input_data, input_data_size, iv,
|
||||
iv_size, output_data, output_data_size);
|
||||
}
|
||||
|
||||
@@ -545,32 +585,32 @@ WB_Result WB_License_MaskedDecrypt(const WB_License_Whitebox* whitebox,
|
||||
size_t iv_size,
|
||||
uint8_t* masked_output_data,
|
||||
size_t* masked_output_data_size) {
|
||||
if (!whitebox || !key_id) {
|
||||
if (!whitebox || !key_id || !masked_output_data || !masked_output_data_size) {
|
||||
DVLOG(1) << "Invalid parameter: null pointer.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (whitebox->content_keys.empty()) {
|
||||
DVLOG(1) << "Invalid state: missing license.";
|
||||
return WB_RESULT_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (key_id_size == 0) {
|
||||
DVLOG(1) << "Invalid parameter: array size 0.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
ContentKey content_key;
|
||||
const WB_Result key_lookup_result =
|
||||
FindKey(whitebox, key_id, key_id_size, &content_key);
|
||||
if (key_lookup_result != WB_RESULT_OK) {
|
||||
return key_lookup_result;
|
||||
const ContentKey* content_key = FindKey(whitebox, key_id, key_id_size);
|
||||
|
||||
if (content_key == nullptr) {
|
||||
DVLOG(1) << "Key unavailable: could not find key.";
|
||||
return WB_RESULT_KEY_UNAVAILABLE;
|
||||
}
|
||||
|
||||
// Check if decryption is allowed by the policy.
|
||||
switch (content_key.level) {
|
||||
case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO:
|
||||
case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_DECODE:
|
||||
break;
|
||||
case video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_CRYPTO:
|
||||
case video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_DECODE:
|
||||
case video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_ALL:
|
||||
return WB_RESULT_INSUFFICIENT_SECURITY_LEVEL;
|
||||
if (!content_key->allow_masked_decrypt) {
|
||||
DVLOG(1) << "Insufficient security level: key policy does not allow use "
|
||||
"with MaskedDecrypt().";
|
||||
return WB_RESULT_INSUFFICIENT_SECURITY_LEVEL;
|
||||
}
|
||||
|
||||
// DecryptBuffer() will validate all the parameters, so just make sure it is
|
||||
@@ -584,7 +624,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, input_data, input_data_size, iv,
|
||||
DecryptBuffer(mode, content_key->key, input_data, input_data_size, iv,
|
||||
iv_size, output.data(), masked_output_data_size);
|
||||
|
||||
if (result != WB_RESULT_OK) {
|
||||
|
||||
Reference in New Issue
Block a user