Initial Code Drop

This is the initial code drop of the reference implementation and
test cases for the Widevine Whitebox API.

In this drop, the full reference implementation for the AEAD
white-box is provided and all test cases verifying the top-level
behave have are enabled. Since the implementations can vary so much
the testing is mostly left to verifying the return codes for specific
parameter conditions.

A full reference implementation for the license white-box is provided,
however not all tests are implemented or enabled. A number of tests
have been disabled as they required a loaded license and test licenses
are still being worked on.

The two license white-box API functions that are the further from
competition are ProcessLicenseResponse() and MaskedDecryt().
ProcessLicenseResponse() is still being worked on and MaskedDecrypt()
is waiting on Decrypt() to be fully functional.

Most tests focus on verifying return code for specific parameter
conditions, but as test licenses are created, tests looking to test
the internal behaviour of license management will be added to
ProcessLicenseResponse(), Decrypt(), and MaskedDecrypt().
This commit is contained in:
Aaron Vaage
2020-05-18 19:45:53 -07:00
commit 77f7ef98c0
91 changed files with 9597 additions and 0 deletions

View File

@@ -0,0 +1,355 @@
// Copyright 2020 Google LLC. All Rights Reserved.
#include "api/license_whitebox.h"
#include <memory>
#include <string>
#include <vector>
#include "api/test_data.h"
#include "testing/include/gtest/gtest.h"
namespace {
// 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 {
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_),
WB_RESULT_OK);
ASSERT_TRUE(whitebox_);
}
void TearDown() override { WB_License_Delete(whitebox_); }
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,
};
};
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();
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(), &plaintext_size),
WB_RESULT_OK);
// 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);
ASSERT_EQ(plaintext, plaintext_);
}
TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForNullWhitebox) {
LoadLicense();
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);
}
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();
// 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),
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);
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_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();
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);
}
// 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();
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(), 14, iv_.data(), 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();
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);
}
// 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();
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);
}
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();
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);
}
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();
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);
}
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();
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);
}
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();
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);
}
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();
LoadLicense();
size_t plaintext_size = ciphertext_.size();
std::vector<uint8_t> plaintext(plaintext_size);
// 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);
}
TEST_F(LicenseWhiteboxDecryptTest, InvalidState) {
// 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.
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);
}
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