Files
android/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.h
Vicky Min 18369730b9 Refactor OEMCrypto_SetDecryptHash
The current implementation of OEMCrypto_SetDecryptHash gives developers
flexibility to use different types of hashes. However, all the
implementations we have seen thus far use crc32. Because of this, crc32
should be sufficient and we can refactor OEMCrypto_SetDecryptHash to
only use the crc32 hash.

Bug: 287706586
Change-Id: I4aaa253b2656dfd9c984f77dfb08fe160b23b47c
2024-02-22 13:52:26 -08:00

429 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 crc32 =
util::wvcrc32(sample.truth_buffer.data(), sample.truth_buffer.size());
OEMCrypto_SetDecryptHash(session_.session_id(), 1, crc32);
}
// 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_