Files
android/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.h
Ian Benz 35cf9c2f99 Fix OEMCrypto test issues identified by Coverity
Change-Id: Ic9f4982bf022292d10a0a88f10648a46077ec0cf
2024-02-01 13:40:51 -08:00

431 lines
18 KiB
C++

// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine
// License Agreement.
//
// Test data for OEMCrypto unit tests.
//
#ifndef CDM_OEMCRYPTO_DECRYPT_TEST_
#define CDM_OEMCRYPTO_DECRYPT_TEST_
#include <gtest/gtest.h>
#include "OEMCryptoCENC.h"
#include "log.h"
#include "oec_decrypt_fallback_chain.h"
#include "oemcrypto_basic_test.h"
#include "oemcrypto_license_test.h"
namespace wvoec {
// Used to test the different HDCP versions. This test is parameterized by the
// required HDCP version in the key control block.
class OEMCryptoSessionTestDecryptWithHDCP : public OEMCryptoSessionTests,
public WithParamInterface<int> {
protected:
void DecryptWithHDCP(OEMCrypto_HDCP_Capability version) {
OEMCryptoResult sts;
OEMCrypto_HDCP_Capability current, maximum;
sts = OEMCrypto_GetHDCPCapability(&current, &maximum);
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
Session s;
ASSERT_NO_FATAL_FAILURE(s.open());
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s));
LicenseRoundTrip license_messages(&s);
license_messages.set_control((version << wvoec::kControlHDCPVersionShift) |
wvoec::kControlObserveHDCP |
wvoec::kControlHDCPRequired);
ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse());
ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse());
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse());
if (((version <= HDCP_V2_3 || current >= HDCP_V1_0) && version > current) ||
(current == HDCP_V1 && version >= HDCP_V1_0)) {
if (global_features.api_version >= 16) {
// Can provide either OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION or
// OEMCrypto_ERROR_INSUFFICIENT_HDCP. TestDecryptCTR allows either to be
// reported if OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION is expected.
ASSERT_NO_FATAL_FAILURE(
s.TestDecryptCTR(true, OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION))
<< "Failed when current HDCP = " << HDCPCapabilityAsString(current)
<< ", maximum HDCP = " << HDCPCapabilityAsString(maximum)
<< ", license HDCP = " << HDCPCapabilityAsString(version);
} else {
ASSERT_NO_FATAL_FAILURE(
s.TestDecryptCTR(true, OEMCrypto_ERROR_INSUFFICIENT_HDCP))
<< "Failed when current HDCP = " << HDCPCapabilityAsString(current)
<< ", maximum HDCP = " << HDCPCapabilityAsString(maximum)
<< ", license HDCP = " << HDCPCapabilityAsString(version);
}
} else {
ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(true, OEMCrypto_SUCCESS))
<< "Failed when current HDCP = " << HDCPCapabilityAsString(current)
<< ", maximum HDCP = " << HDCPCapabilityAsString(maximum)
<< ", license HDCP = " << HDCPCapabilityAsString(version);
}
}
};
struct SubsampleSize {
size_t clear_size;
size_t encrypted_size;
SubsampleSize(size_t clear, size_t encrypted)
: clear_size(clear), encrypted_size(encrypted) {}
};
// Struct for holding the data for one test sample in the decrypt tests.
struct TestSample {
// Encrypted data -- this is input to OEMCrypto, and output from EncryptData.
std::vector<uint8_t> encrypted_buffer;
std::vector<uint8_t> clear_buffer; // OEMCrypto store clear output here.
std::vector<uint8_t> truth_buffer; // Truth data for clear text.
OEMCrypto_SampleDescription description;
std::vector<OEMCrypto_SubSampleDescription> subsamples;
int secure_buffer_fid;
};
// A class of tests that test decryption for a variety of patterns and modes.
// This test is parameterized by three parameters:
// 1. The pattern used for pattern decryption.
// 2. The cipher mode for decryption: either CTR or CBC.
// 3. A boolean that determines if decrypt in place should be done. When the
// output buffer is clear, it should be possible for the input and output
// buffers to be the same.
class OEMCryptoSessionTestsDecryptTests
: public OEMCryptoLicenseTestAPI16,
public WithParamInterface<tuple<OEMCrypto_CENCEncryptPatternDesc,
OEMCryptoCipherMode, OutputType>> {
protected:
void SetUp() override {
OEMCryptoLicenseTestAPI16::SetUp();
if (wvoec::global_features.derive_key_method ==
wvoec::DeviceFeatures::NO_METHOD) {
GTEST_SKIP() << "Test for devices that can derive session keys only.";
}
pattern_ = ::testing::get<0>(GetParam());
cipher_mode_ = ::testing::get<1>(GetParam());
decrypt_inplace_ = ::testing::get<2>(GetParam()).decrypt_inplace;
output_buffer_type_ = ::testing::get<2>(GetParam()).type;
verify_crc_ = global_features.supports_crc;
// Pick a random key.
EXPECT_EQ(GetRandBytes(key_, sizeof(key_)), 1);
// Pick a random starting iv. Some tests override this before using it.
EXPECT_EQ(GetRandBytes(initial_iv_, sizeof(initial_iv_)), 1);
}
void TearDown() override {
FreeSecureBuffers();
OEMCryptoLicenseTestAPI16::TearDown();
}
void SetSubsampleSizes(std::vector<SubsampleSize> subsample_sizes) {
// This is just sugar for having one sample with the given subsamples in it.
SetSampleSizes({std::move(subsample_sizes)});
}
void SetSampleSizes(std::vector<std::vector<SubsampleSize>> sample_sizes) {
ASSERT_GT(sample_sizes.size(), 0u);
samples_.clear();
samples_.reserve(sample_sizes.size());
// Convert all the size arrays to TestSample structs
for (const std::vector<SubsampleSize>& subsample_sizes : sample_sizes) {
// This could be one line if we had C++17
samples_.emplace_back();
TestSample& sample = samples_.back();
ASSERT_GT(subsample_sizes.size(), 0u);
sample.subsamples.reserve(subsample_sizes.size());
// Convert all the sizes to subsample descriptions and tally the total
// sample size
size_t sample_size = 0;
size_t current_block_offset = 0;
for (const SubsampleSize& size : subsample_sizes) {
sample.subsamples.push_back(OEMCrypto_SubSampleDescription{
size.clear_size, size.encrypted_size,
0, // Subsample Flags, to be filled in after the loop
current_block_offset});
// Update the rolling variables
sample_size += size.clear_size + size.encrypted_size;
if (cipher_mode_ == OEMCrypto_CipherMode_CENC) {
current_block_offset =
(current_block_offset + size.encrypted_size) % AES_BLOCK_SIZE;
}
}
// Set the subsample flags now that all the subsamples are processed
sample.subsamples.front().subsample_flags |= OEMCrypto_FirstSubsample;
sample.subsamples.back().subsample_flags |= OEMCrypto_LastSubsample;
// Set related information on the sample description
sample.description.subsamples = sample.subsamples.data();
sample.description.subsamples_length = sample.subsamples.size();
sample.description.buffers.input_data_length = sample_size;
}
}
// Set up the input buffer and either a clear or secure output buffer for each
// test sample. This should be called after SetSubsampleSizes().
void MakeBuffers() {
for (TestSample& sample : samples_) {
const size_t total_size = sample.description.buffers.input_data_length;
ASSERT_GT(total_size, 0u);
sample.encrypted_buffer.clear();
sample.truth_buffer.clear();
sample.clear_buffer.clear();
sample.encrypted_buffer.resize(total_size);
sample.truth_buffer.resize(total_size);
for (size_t i = 0; i < total_size; i++) sample.truth_buffer[i] = i % 256;
OEMCrypto_DestBufferDesc& output_descriptor =
sample.description.buffers.output_descriptor;
output_descriptor.type = output_buffer_type_;
switch (output_descriptor.type) {
case OEMCrypto_BufferType_Clear:
if (decrypt_inplace_) {
// Add some padding to verify there is no overrun.
sample.encrypted_buffer.resize(total_size + kBufferOverrunPadding,
0xaa);
output_descriptor.buffer.clear.clear_buffer =
sample.encrypted_buffer.data();
} else {
// Add some padding to verify there is no overrun.
sample.clear_buffer.resize(total_size + kBufferOverrunPadding,
0xaa);
output_descriptor.buffer.clear.clear_buffer =
sample.clear_buffer.data();
}
output_descriptor.buffer.clear.clear_buffer_length = total_size;
break;
case OEMCrypto_BufferType_Secure:
output_descriptor.buffer.secure.secure_buffer_length = total_size;
ASSERT_EQ(OEMCrypto_AllocateSecureBuffer(
session_.session_id(), total_size, &output_descriptor,
&sample.secure_buffer_fid),
OEMCrypto_SUCCESS);
ASSERT_NE(output_descriptor.buffer.secure.secure_buffer, nullptr);
// It is OK if OEMCrypto changes the maximum size, but there must
// still be enough room for our data.
ASSERT_GE(output_descriptor.buffer.secure.secure_buffer_length,
total_size);
output_descriptor.buffer.secure.offset = 0;
break;
case OEMCrypto_BufferType_Direct:
output_descriptor.buffer.direct.is_video = false;
break;
} // switch (output_descriptor.type)
} // sample loop
}
void FreeSecureBuffers() {
for (TestSample& sample : samples_) {
OEMCrypto_DestBufferDesc& output_descriptor =
sample.description.buffers.output_descriptor;
if (output_descriptor.type == OEMCrypto_BufferType_Secure) {
ASSERT_EQ(OEMCrypto_FreeSecureBuffer(session_.session_id(),
&output_descriptor,
sample.secure_buffer_fid),
OEMCrypto_SUCCESS);
}
}
}
void EncryptData() {
AES_KEY aes_key;
AES_set_encrypt_key(key_, AES_BLOCK_SIZE * 8, &aes_key);
for (TestSample& sample : samples_) {
uint8_t iv[KEY_IV_SIZE]; // Current IV
memcpy(iv, initial_iv_, KEY_IV_SIZE);
memcpy(sample.description.iv, initial_iv_, KEY_IV_SIZE);
size_t buffer_index = 0; // byte index into in and out.
size_t block_offset = 0; // byte index into current block.
for (const OEMCrypto_SubSampleDescription& subsample :
sample.subsamples) {
// Copy clear content.
if (subsample.num_bytes_clear > 0) {
memcpy(&sample.encrypted_buffer[buffer_index],
&sample.truth_buffer[buffer_index], subsample.num_bytes_clear);
buffer_index += subsample.num_bytes_clear;
}
// The IV resets at the start of each subsample in the 'cbcs' schema.
if (cipher_mode_ == OEMCrypto_CipherMode_CBCS) {
memcpy(iv, initial_iv_, KEY_IV_SIZE);
}
size_t pattern_offset = 0;
const size_t subsample_end =
buffer_index + subsample.num_bytes_encrypted;
while (buffer_index < subsample_end) {
const size_t size =
min(subsample_end - buffer_index, AES_BLOCK_SIZE - block_offset);
const size_t pattern_length = pattern_.encrypt + pattern_.skip;
const bool skip_block =
(pattern_offset >= pattern_.encrypt) && (pattern_length > 0);
if (pattern_length > 0) {
pattern_offset = (pattern_offset + 1) % pattern_length;
}
// CBC mode should just copy a partial block at the end. If there
// is a partial block at the beginning, an error is returned, so we
// can put whatever we want in the output buffer.
if (skip_block || ((cipher_mode_ == OEMCrypto_CipherMode_CBCS) &&
(size < AES_BLOCK_SIZE))) {
memcpy(&sample.encrypted_buffer[buffer_index],
&sample.truth_buffer[buffer_index], size);
block_offset = 0; // Next block should be complete.
} else {
if (cipher_mode_ == OEMCrypto_CipherMode_CENC) {
uint8_t aes_output[AES_BLOCK_SIZE];
AES_encrypt(iv, aes_output, &aes_key);
for (size_t n = 0; n < size; n++) {
sample.encrypted_buffer[buffer_index + n] =
aes_output[n + block_offset] ^
sample.truth_buffer[buffer_index + n];
}
if (size + block_offset < AES_BLOCK_SIZE) {
// Partial block. Don't increment iv. Compute next block
// offset.
block_offset = block_offset + size;
} else {
EXPECT_EQ(block_offset + size,
static_cast<size_t>(AES_BLOCK_SIZE));
// Full block. Increment iv, and set offset to 0 for next
// block.
ctr128_inc64(1, iv);
block_offset = 0;
}
} else {
uint8_t aes_input[AES_BLOCK_SIZE];
for (size_t n = 0; n < size; n++) {
aes_input[n] = sample.truth_buffer[buffer_index + n] ^ iv[n];
}
AES_encrypt(aes_input, &sample.encrypted_buffer[buffer_index],
&aes_key);
memcpy(iv, &sample.encrypted_buffer[buffer_index],
AES_BLOCK_SIZE);
// CBC mode should always start on block boundary.
block_offset = 0;
}
}
buffer_index += size;
} // encryption loop
} // per-subsample loop
} // per-sample loop
}
void LoadLicense() {
uint32_t control = wvoec::kControlNonceEnabled;
if (verify_crc_) control |= kControlAllowHashVerification;
if (output_buffer_type_ == OEMCrypto_BufferType_Secure)
control |= kControlObserveDataPath | kControlDataPathSecure;
license_messages_.set_control(control);
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
license_messages_.core_response()
.timer_limits.initial_renewal_duration_seconds = kDuration;
memcpy(license_messages_.response_data().keys[0].key_data, key_,
sizeof(key_));
license_messages_.response_data().keys[0].cipher_mode = cipher_mode_;
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
ASSERT_EQ(GetKeyHandleIntoVector(session_.session_id(),
session_.license().keys[0].key_id,
session_.license().keys[0].key_id_length,
cipher_mode_, key_handle_),
OEMCrypto_SUCCESS);
}
void TestDecryptCENC() { ASSERT_EQ(DecryptCENC(), OEMCrypto_SUCCESS); }
void ValidateDecryptedData() {
for (TestSample& sample : samples_) {
if (sample.description.buffers.output_descriptor.type ==
OEMCrypto_BufferType_Clear) {
const size_t total_size = sample.description.buffers.input_data_length;
// To verify there is no buffer overrun after decrypting, look at the
// padded bytes just after the data buffer that was written. It
// should not have changed from the original 0xaa that we set in
// MakeBuffer function.
if (decrypt_inplace_) {
EXPECT_EQ(std::count(sample.encrypted_buffer.begin() + total_size,
sample.encrypted_buffer.end(), 0xaa),
static_cast<int32_t>(kBufferOverrunPadding))
<< "Buffer overrun.";
sample.encrypted_buffer.resize(total_size); // Remove padding.
// We expect encrypted buffer to have been changed by OEMCrypto.
EXPECT_EQ(sample.encrypted_buffer, sample.truth_buffer);
} else {
EXPECT_EQ(std::count(sample.clear_buffer.begin() + total_size,
sample.clear_buffer.end(), 0xaa),
static_cast<int32_t>(kBufferOverrunPadding))
<< "Buffer overrun.";
sample.clear_buffer.resize(total_size); // Remove padding.
EXPECT_EQ(sample.clear_buffer, sample.truth_buffer);
}
}
}
if (verify_crc_) {
uint32_t frame;
ASSERT_EQ(OEMCrypto_GetHashErrorCode(session_.session_id(), &frame),
OEMCrypto_SUCCESS);
}
}
OEMCryptoResult DecryptCENC() {
// OEMCrypto only supports providing a decrypt hash for one sample.
if (samples_.size() > 1) verify_crc_ = false;
// If supported, check the decrypt hashes.
if (verify_crc_) {
const TestSample& sample = samples_[0];
uint32_t hash =
util::wvcrc32(sample.truth_buffer.data(), sample.truth_buffer.size());
OEMCrypto_SetDecryptHash(session_.session_id(), 1,
reinterpret_cast<const uint8_t*>(&hash),
sizeof(hash));
}
// Build an array of just the sample descriptions.
std::vector<OEMCrypto_SampleDescription> sample_descriptions;
sample_descriptions.reserve(samples_.size());
for (TestSample& sample : samples_) {
// This must be deferred until this point in case the test modifies the
// buffer before testing decrypt.
sample.description.buffers.input_data = sample.encrypted_buffer.data();
// Append to the description array.
sample_descriptions.push_back(sample.description);
}
// Perform decryption using the test data that was previously set up.
OEMCryptoResult result = DecryptFallbackChain::Decrypt(
key_handle_.data(), key_handle_.size(), sample_descriptions.data(),
sample_descriptions.size(), cipher_mode_, &pattern_);
if (result != OEMCrypto_SUCCESS) return result;
ValidateDecryptedData();
return result;
}
// Parameters of test case
OEMCrypto_CENCEncryptPatternDesc pattern_;
OEMCryptoCipherMode cipher_mode_;
bool decrypt_inplace_; // If true, input and output buffers are the same.
OEMCryptoBufferType output_buffer_type_;
bool verify_crc_;
uint8_t key_[AES_BLOCK_SIZE]; // Encryption Key.
uint8_t initial_iv_[KEY_IV_SIZE]; // Starting IV for every sample.
std::vector<TestSample> samples_;
std::vector<uint8_t> key_handle_;
};
} // namespace wvoec
#endif // CDM_OEMCRYPTO_DECRYPT_TEST_