Refactor decrypt unit tests
Merge from Widevine repo of http://go/wvgerrit/169052 Refactor the decrypt unit tests into a separate file. Bug: 253779846 Merged from https://widevine-internal-review.googlesource.com/167180 Change-Id: I10a4a987b0d597f0c6d2953c0723bea4d790fb9c
This commit is contained in:
committed by
Fred Gylys-Colwell
parent
dbd5bd2a4d
commit
6897bc1a1c
690
libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.cpp
Normal file
690
libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.cpp
Normal file
@@ -0,0 +1,690 @@
|
||||
// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine
|
||||
// License Agreement.
|
||||
//
|
||||
|
||||
#include "oemcrypto_decrypt_test.h"
|
||||
#include "log.h"
|
||||
#include "oemcrypto_basic_test.h"
|
||||
#include "oemcrypto_resource_test.h"
|
||||
#include "oemcrypto_session_tests_helper.h"
|
||||
#include "platform.h"
|
||||
#include "test_sleep.h"
|
||||
|
||||
using ::testing::Combine;
|
||||
using ::testing::Range;
|
||||
using ::testing::Values;
|
||||
|
||||
namespace wvoec {
|
||||
|
||||
// Cannot decrypt without first getting a key handle.
|
||||
TEST_P(OEMCryptoLicenseTest, FailDecryptWithoutGettingAHandle) {
|
||||
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());
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
||||
}
|
||||
|
||||
// Cannot decrypt with an old key handle.
|
||||
TEST_P(OEMCryptoLicenseTest, FailDecryptWithOldKeyHandle) {
|
||||
Session donor_session;
|
||||
LicenseRoundTrip license_messages2(&donor_session);
|
||||
ASSERT_NO_FATAL_FAILURE(donor_session.open());
|
||||
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&donor_session));
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages2.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages2.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages2.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages2.LoadResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(donor_session.TestDecryptCTR());
|
||||
|
||||
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());
|
||||
|
||||
// Inject the donor session's key handle into |session_| and then close the
|
||||
// donor, which should render the handle invalid.
|
||||
session_.key_handle() = donor_session.key_handle();
|
||||
donor_session.close();
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
||||
}
|
||||
|
||||
// SelectKey should fail if we attempt to select a key that has not been loaded.
|
||||
// Also, the error should be NO_CONTENT_KEY.
|
||||
// This test should pass for v15 devices, except that the exact error code was
|
||||
// not specified until v16.
|
||||
TEST_P(OEMCryptoLicenseTest, SelectKeyNotThereAPI16) {
|
||||
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());
|
||||
|
||||
const char* key_id = "no_key";
|
||||
vector<uint8_t> key_handle;
|
||||
OEMCryptoResult sts = GetKeyHandleIntoVector(
|
||||
session_.session_id(), reinterpret_cast<const uint8_t*>(key_id),
|
||||
strlen(key_id), OEMCrypto_CipherMode_CENC, key_handle);
|
||||
if (sts != OEMCrypto_SUCCESS) {
|
||||
EXPECT_EQ(OEMCrypto_ERROR_NO_CONTENT_KEY, sts);
|
||||
} else {
|
||||
// Delayed error code. If select key was a success, then we should
|
||||
// eventually see the error when we decrypt.
|
||||
vector<uint8_t> in_buffer(256);
|
||||
vector<uint8_t> out_buffer(in_buffer.size());
|
||||
OEMCrypto_SampleDescription sample_description;
|
||||
OEMCrypto_SubSampleDescription subsample_description;
|
||||
|
||||
GenerateSimpleSampleDescription(in_buffer, out_buffer, &sample_description,
|
||||
&subsample_description);
|
||||
|
||||
// Generate test data
|
||||
for (size_t i = 0; i < in_buffer.size(); i++) in_buffer[i] = i % 256;
|
||||
|
||||
// Create the pattern description (always 0,0 for CTR)
|
||||
OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0};
|
||||
|
||||
// Try to decrypt the data
|
||||
sts = OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(),
|
||||
&sample_description, 1, &pattern);
|
||||
EXPECT_EQ(sts, OEMCrypto_ERROR_NO_CONTENT_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
// 'cens' mode is no longer supported in v16
|
||||
TEST_P(OEMCryptoLicenseTest, RejectCensAPI16) {
|
||||
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());
|
||||
|
||||
vector<uint8_t> key_handle;
|
||||
OEMCryptoResult sts;
|
||||
sts = GetKeyHandleIntoVector(session_.session_id(),
|
||||
session_.license().keys[0].key_id,
|
||||
session_.license().keys[0].key_id_length,
|
||||
OEMCrypto_CipherMode_CENC, key_handle);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
|
||||
vector<uint8_t> in_buffer(256);
|
||||
vector<uint8_t> out_buffer(in_buffer.size());
|
||||
OEMCrypto_SampleDescription sample_description;
|
||||
OEMCrypto_SubSampleDescription subsample_description;
|
||||
|
||||
GenerateSimpleSampleDescription(in_buffer, out_buffer, &sample_description,
|
||||
&subsample_description);
|
||||
|
||||
// Create a non-zero pattern to indicate this is 'cens'
|
||||
OEMCrypto_CENCEncryptPatternDesc pattern = {1, 9};
|
||||
|
||||
// Try to decrypt the data
|
||||
sts = OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(),
|
||||
&sample_description, 1, &pattern);
|
||||
EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts);
|
||||
}
|
||||
|
||||
// 'cbc1' mode is no longer supported in v16
|
||||
TEST_P(OEMCryptoLicenseTest, RejectCbc1API16) {
|
||||
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());
|
||||
|
||||
vector<uint8_t> key_handle;
|
||||
OEMCryptoResult sts;
|
||||
sts = GetKeyHandleIntoVector(session_.session_id(),
|
||||
session_.license().keys[0].key_id,
|
||||
session_.license().keys[0].key_id_length,
|
||||
OEMCrypto_CipherMode_CBCS, key_handle);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
|
||||
vector<uint8_t> in_buffer(256);
|
||||
vector<uint8_t> out_buffer(in_buffer.size());
|
||||
OEMCrypto_SampleDescription sample_description;
|
||||
OEMCrypto_SubSampleDescription subsample_description;
|
||||
|
||||
GenerateSimpleSampleDescription(in_buffer, out_buffer, &sample_description,
|
||||
&subsample_description);
|
||||
|
||||
// Create a zero pattern to indicate this is 'cbc1'
|
||||
OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0};
|
||||
|
||||
// Try to decrypt the data
|
||||
sts = OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(),
|
||||
&sample_description, 1, &pattern);
|
||||
EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts);
|
||||
}
|
||||
|
||||
TEST_P(OEMCryptoLicenseTest, RejectCbcsWithBlockOffset) {
|
||||
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());
|
||||
|
||||
vector<uint8_t> key_handle;
|
||||
OEMCryptoResult sts;
|
||||
sts = GetKeyHandleIntoVector(session_.session_id(),
|
||||
session_.license().keys[0].key_id,
|
||||
session_.license().keys[0].key_id_length,
|
||||
OEMCrypto_CipherMode_CBCS, key_handle);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
|
||||
vector<uint8_t> in_buffer(256);
|
||||
vector<uint8_t> out_buffer(in_buffer.size());
|
||||
OEMCrypto_SampleDescription sample_description;
|
||||
OEMCrypto_SubSampleDescription subsample_description;
|
||||
|
||||
GenerateSimpleSampleDescription(in_buffer, out_buffer, &sample_description,
|
||||
&subsample_description);
|
||||
subsample_description.block_offset = 5; // Any value 1-15 will do.
|
||||
|
||||
// Create a non-zero pattern to indicate this is 'cbcs'.
|
||||
OEMCrypto_CENCEncryptPatternDesc pattern = {1, 9};
|
||||
|
||||
// Try to decrypt the data
|
||||
sts = OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(),
|
||||
&sample_description, 1, &pattern);
|
||||
EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts);
|
||||
}
|
||||
|
||||
TEST_P(OEMCryptoLicenseTest, RejectOversizedBlockOffset) {
|
||||
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());
|
||||
|
||||
vector<uint8_t> key_handle;
|
||||
OEMCryptoResult sts;
|
||||
sts = GetKeyHandleIntoVector(session_.session_id(),
|
||||
session_.license().keys[0].key_id,
|
||||
session_.license().keys[0].key_id_length,
|
||||
OEMCrypto_CipherMode_CENC, key_handle);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
|
||||
vector<uint8_t> in_buffer(256);
|
||||
vector<uint8_t> out_buffer(in_buffer.size());
|
||||
OEMCrypto_SampleDescription sample_description;
|
||||
OEMCrypto_SubSampleDescription subsample_description;
|
||||
|
||||
GenerateSimpleSampleDescription(in_buffer, out_buffer, &sample_description,
|
||||
&subsample_description);
|
||||
subsample_description.block_offset = 0xFF; // Anything 16+
|
||||
|
||||
// Create a zero pattern to indicate this is 'cenc'.
|
||||
OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0};
|
||||
|
||||
// Try to decrypt the data
|
||||
sts = OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(),
|
||||
&sample_description, 1, &pattern);
|
||||
EXPECT_NE(OEMCrypto_SUCCESS, sts);
|
||||
|
||||
// Try again with the minimum invalid value
|
||||
subsample_description.block_offset = 16;
|
||||
sts = OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(),
|
||||
&sample_description, 1, &pattern);
|
||||
EXPECT_NE(OEMCrypto_SUCCESS, sts);
|
||||
}
|
||||
|
||||
TEST_P(OEMCryptoSessionTestDecryptWithHDCP, DecryptAPI09) {
|
||||
// Test parameterized by HDCP version.
|
||||
DecryptWithHDCP(static_cast<OEMCrypto_HDCP_Capability>(GetParam()));
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(TestHDCP, OEMCryptoSessionTestDecryptWithHDCP,
|
||||
Range(1, 6));
|
||||
|
||||
// If the license does not allow a hash, then we should not compute one.
|
||||
TEST_P(OEMCryptoLicenseTest, HashForbiddenAPI15) {
|
||||
uint32_t hash_type = OEMCrypto_SupportsDecryptHash();
|
||||
// If hash is not supported, or is vendor defined, don't try to test it.
|
||||
if (hash_type != OEMCrypto_CRC_Clear_Buffer) return;
|
||||
|
||||
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());
|
||||
|
||||
uint32_t frame_number = 1;
|
||||
uint32_t hash = 42;
|
||||
// It is OK to set the hash before loading the keys
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS,
|
||||
OEMCrypto_SetDecryptHash(session_.session_id(), frame_number,
|
||||
reinterpret_cast<const uint8_t*>(&hash),
|
||||
sizeof(hash)));
|
||||
// It is OK to select the key and decrypt.
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR());
|
||||
// But the error code should be bad.
|
||||
ASSERT_EQ(OEMCrypto_ERROR_UNKNOWN_FAILURE,
|
||||
OEMCrypto_GetHashErrorCode(session_.session_id(), &frame_number));
|
||||
}
|
||||
|
||||
// This test verifies OEMCrypto_SetDecryptHash for out of range frame number.
|
||||
TEST_P(OEMCryptoLicenseTest, DecryptHashForOutOfRangeFrameNumber) {
|
||||
uint32_t frame_number = kHugeRandomNumber;
|
||||
uint32_t hash = 42;
|
||||
ASSERT_NO_FATAL_FAILURE(OEMCrypto_SetDecryptHash(
|
||||
session_.session_id(), frame_number,
|
||||
reinterpret_cast<const uint8_t*>(&hash), sizeof(hash)));
|
||||
}
|
||||
|
||||
//
|
||||
// Decrypt Tests -- these test Decrypt CTR mode only.
|
||||
//
|
||||
TEST_P(OEMCryptoLicenseTest, Decrypt) {
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
license_messages_.core_response()
|
||||
.timer_limits.total_playback_duration_seconds = kDuration;
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR());
|
||||
}
|
||||
|
||||
// Verify that a zero duration means infinite license duration.
|
||||
TEST_P(OEMCryptoLicenseTest, DecryptZeroDuration) {
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
license_messages_.core_response()
|
||||
.timer_limits.total_playback_duration_seconds = 0;
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR());
|
||||
}
|
||||
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, SingleLargeSubsample) {
|
||||
// This subsample size is larger than a few encrypt/skip patterns. Most
|
||||
// test cases use a pattern length of 160, so we'll run through at least two
|
||||
// full patterns if we have more than 320 -- round up to 400.
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{0, 400},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// When the pattern length is 10 blocks, there is a discrepancy between the
|
||||
// HLS and the CENC standards for samples of size 160*N+16, for N = 1, 2, 3...
|
||||
// We require the CENC standard for OEMCrypto, and let a layer above us break
|
||||
// samples into pieces if they wish to use the HLS standard.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, PatternPlusOneBlock) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{0, 160 + 16},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// Test that a single block can be decrypted.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, OneBlock) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{0, 16},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// This tests the ability to decrypt multiple subsamples with no offset.
|
||||
// There is no offset within the block, used by CTR mode.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, NoOffset) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{25, 160},
|
||||
{50, 256},
|
||||
{25, 160},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// This tests an offset into the block for the second encrypted subsample.
|
||||
// This should only work for CTR mode, for CBC mode an error is expected in
|
||||
// the decrypt step.
|
||||
// If this test fails for CTR mode, then it is probably handling the
|
||||
// block_offset incorrectly.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, EvenOffset) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{25, 8},
|
||||
{25, 32},
|
||||
{25, 50},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
// CTR Mode is self-inverse -- i.e. We can pick the encrypted data and
|
||||
// compute the unencrypted data. By picking the encrypted data to be all 0,
|
||||
// it is easier to re-encrypt the data and debug problems. Similarly, we
|
||||
// pick an iv = 0.
|
||||
memset(initial_iv_, 0, KEY_IV_SIZE);
|
||||
TestSample& sample = samples_[0]; // There is only one sample in this test
|
||||
sample.truth_buffer.assign(sample.description.buffers.input_data_length, 0);
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
if (decrypt_inplace_) {
|
||||
const size_t total_size = sample.description.buffers.input_data_length;
|
||||
// In case of decrypt_inplace_, encrypted_buffer contains padded bytes
|
||||
// which is used for buffer overrun validation. Do not copy the padded
|
||||
// bytes to truth_buffer.
|
||||
sample.truth_buffer.assign(sample.encrypted_buffer.begin(),
|
||||
sample.encrypted_buffer.begin() + total_size);
|
||||
} else {
|
||||
sample.truth_buffer =
|
||||
sample.encrypted_buffer; // truth_buffer_ = encrypted zero buffer.
|
||||
}
|
||||
// Run EncryptData to re-encrypt this buffer. For CTR mode, we should get
|
||||
// back to zeros.
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// If the EvenOffset test passes, but this one doesn't, then DecryptCENC might
|
||||
// be using the wrong definition of block offset. Adding the block offset to
|
||||
// the block boundary should give you the beginning of the encrypted data.
|
||||
// This should only work for CTR mode, for CBC mode, the block offset must be
|
||||
// 0, so an error is expected in the decrypt step.
|
||||
// Another way to view the block offset is with the formula:
|
||||
// block_boundary + block_offset = beginning of subsample.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, OddOffset) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{10, 50},
|
||||
{10, 75},
|
||||
{10, 75},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// This tests that the algorithm used to increment the counter for
|
||||
// AES-CTR mode is correct. There are two possible implementations:
|
||||
// 1) increment the counter as if it were a 128 bit number,
|
||||
// 2) increment the low 64 bits as a 64 bit number and leave the high bits
|
||||
// alone.
|
||||
// For CENC, the algorithm we should use is the second one. OpenSSL defaults to
|
||||
// the first. If this test is not passing, you should look at the way you
|
||||
// increment the counter. Look at the example code in ctr128_inc64 above.
|
||||
// If you start with an IV of 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, after you
|
||||
// increment twice, you should get 0xFFFFFFFFFFFFFFFF0000000000000000.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptWithNearWrap) {
|
||||
memcpy(initial_iv_,
|
||||
wvutil::a2b_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE").data(),
|
||||
KEY_IV_SIZE);
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{0, 256},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// This tests the case where an encrypted sample is not an even number of
|
||||
// blocks. For CTR mode, the partial block is encrypted. For CBC mode the
|
||||
// partial block should be a copy of the clear data.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, PartialBlock) {
|
||||
// Note: for more complete test coverage, we want a sample size that is in
|
||||
// the encrypted range for some tests, e.g. (3,7), and in the skip range for
|
||||
// other tests, e.g. (7, 3). 3*16 < 50 and 7*16 > 50.
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{0, 50},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// Based on the resource rating, OEMCrypto should be able to handle the maximum
|
||||
// amount of data that can be passed to it. This is the lesser of:
|
||||
//
|
||||
// 1) The maximum total sample size
|
||||
// 2) The maximum number of subsamples multiplied by the maximum subsample size
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSampleAPI16) {
|
||||
const size_t max_sample_size = GetResourceValue(kMaxSampleSize);
|
||||
const size_t max_subsample_size = GetResourceValue(kMaxSubsampleSize);
|
||||
const size_t max_num_subsamples = GetResourceValue(kMaxNumberSubsamples);
|
||||
|
||||
// The +1 on this line ensures that, even in cases where max_sample_size is
|
||||
// not evenly divisible by max_num_subsamples and thus the division gets
|
||||
// truncated, (max_num_subsamples * subsample_size) will be greater than
|
||||
// max_sample_size.
|
||||
const size_t subsample_size =
|
||||
std::min(max_sample_size / max_num_subsamples + 1, max_subsample_size);
|
||||
size_t bytes_remaining = max_sample_size;
|
||||
std::vector<SubsampleSize> subsample_sizes;
|
||||
while (bytes_remaining > 0 && subsample_sizes.size() < max_num_subsamples) {
|
||||
const size_t this_subsample_size =
|
||||
std::min(subsample_size, bytes_remaining);
|
||||
const size_t clear_size = this_subsample_size / 2;
|
||||
const size_t encrypted_size = this_subsample_size - clear_size;
|
||||
|
||||
subsample_sizes.push_back({clear_size, encrypted_size});
|
||||
bytes_remaining -= this_subsample_size;
|
||||
}
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes(subsample_sizes));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests,
|
||||
OEMCryptoMemoryCheckDecryptCENCStatusForHugeNumberOfSubSamples) {
|
||||
size_t number_of_subsamples = 10000;
|
||||
std::vector<SubsampleSize> subsample_sizes;
|
||||
while (number_of_subsamples-- > 0) {
|
||||
subsample_sizes.push_back({100, 100});
|
||||
}
|
||||
SetSubsampleSizes(subsample_sizes);
|
||||
LoadLicense();
|
||||
MakeBuffers();
|
||||
EncryptData();
|
||||
// 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);
|
||||
}
|
||||
OEMCryptoResult result =
|
||||
OEMCrypto_DecryptCENC(key_handle_.data(), key_handle_.size(),
|
||||
sample_descriptions.data(), 1, &pattern_);
|
||||
LOGD("Large number of subsamples test has return code %d", result);
|
||||
}
|
||||
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests,
|
||||
OEMCryptoMemoryCheckDecryptCENCStatusForHugeSubSample) {
|
||||
std::vector<SubsampleSize> subsample_sizes;
|
||||
subsample_sizes.push_back({100000, 100000});
|
||||
SetSubsampleSizes(subsample_sizes);
|
||||
LoadLicense();
|
||||
MakeBuffers();
|
||||
EncryptData();
|
||||
// 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);
|
||||
}
|
||||
OEMCryptoResult result =
|
||||
OEMCrypto_DecryptCENC(key_handle_.data(), key_handle_.size(),
|
||||
sample_descriptions.data(), 1, &pattern_);
|
||||
LOGD("Large subsample test has return code %d", result);
|
||||
}
|
||||
|
||||
// Based on the resource rating, OEMCrypto should be able to handle the maximum
|
||||
// subsample size.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSubsample) {
|
||||
const size_t max = GetResourceValue(kMaxSubsampleSize);
|
||||
const size_t half_max = max / 2;
|
||||
// This test assumes that the maximum sample size is always more than three
|
||||
// times the maximum subsample size.
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{max, 0},
|
||||
{0, max},
|
||||
{half_max, max - half_max},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// There are probably no frames this small, but we should handle them anyway.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptSmallBuffer) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{5, 5},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// Test the case where there is only a clear subsample and no encrypted
|
||||
// subsample.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptUnencrypted) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{256, 0},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// This tests the ability to decrypt multiple samples at once.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, MultipleSamples) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetSampleSizes({
|
||||
{
|
||||
{52, 160},
|
||||
{25, 256},
|
||||
{25, 320},
|
||||
},
|
||||
{
|
||||
{300, 64},
|
||||
{50, 160},
|
||||
{2, 160},
|
||||
{24, 160},
|
||||
{128, 256},
|
||||
},
|
||||
{
|
||||
{70, 320},
|
||||
{160, 160},
|
||||
},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// This tests that calling OEMCrypto_Idle and OEMCrypto_Wake once or multiple
|
||||
// times doesn't break anything.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, IdleAndWake) {
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
OEMCrypto_Idle(OEMCrypto_IdleState::OEMCrypto_CpuSuspend, 0));
|
||||
ASSERT_NO_FATAL_FAILURE(OEMCrypto_Wake());
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
OEMCrypto_Idle(OEMCrypto_IdleState::OEMCrypto_CpuSuspend, 0));
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
OEMCrypto_Idle(OEMCrypto_IdleState::OEMCrypto_CpuSuspend, 0));
|
||||
ASSERT_NO_FATAL_FAILURE(OEMCrypto_Wake());
|
||||
ASSERT_NO_FATAL_FAILURE(OEMCrypto_Wake());
|
||||
}
|
||||
|
||||
// This tests that after an idle and a wake, decryption can continue in an
|
||||
// open session.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, ContinueDecryptionAfterIdleAndWake) {
|
||||
// This subsample size is larger than a few encrypt/skip patterns. Most
|
||||
// test cases use a pattern length of 160, so we'll run through at least two
|
||||
// full patterns if we have more than 320 -- round up to 400.
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{0, 400},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
FreeSecureBuffers();
|
||||
// Set state to idle then wake again and try to reencrypt/decrypt
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
OEMCrypto_Idle(OEMCrypto_IdleState::OEMCrypto_CpuSuspend, 0));
|
||||
ASSERT_NO_FATAL_FAILURE(OEMCrypto_Wake());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// Used to construct a specific pattern.
|
||||
constexpr OEMCrypto_CENCEncryptPatternDesc MakePattern(size_t encrypt,
|
||||
size_t skip) {
|
||||
return {encrypt, skip};
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
CTRTests, OEMCryptoSessionTestsDecryptTests,
|
||||
Combine(Values(MakePattern(0, 0)), Values(OEMCrypto_CipherMode_CENC),
|
||||
::testing::ValuesIn(global_features.GetOutputTypes())));
|
||||
|
||||
// Decrypt in place for CBC tests was only required in v13.
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
CBCTestsAPI14, OEMCryptoSessionTestsDecryptTests,
|
||||
Combine(
|
||||
Values(MakePattern(3, 7), MakePattern(9, 1),
|
||||
// HLS edge cases. We should follow the CENC spec, not HLS spec.
|
||||
MakePattern(1, 9), MakePattern(1, 0),
|
||||
// AV1 patterns not already covered above.
|
||||
MakePattern(5, 5), MakePattern(10, 0)),
|
||||
Values(OEMCrypto_CipherMode_CBCS),
|
||||
::testing::ValuesIn(global_features.GetOutputTypes())));
|
||||
|
||||
// A request to decrypt data to a clear buffer when the key control block
|
||||
// requires a secure data path.
|
||||
TEST_P(OEMCryptoLicenseTest, DecryptSecureToClear) {
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
license_messages_.set_control(wvoec::kControlObserveDataPath |
|
||||
wvoec::kControlDataPathSecure);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
||||
}
|
||||
|
||||
// Test that key duration is honored.
|
||||
TEST_P(OEMCryptoLicenseTest, KeyDuration) {
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
license_messages_.core_response()
|
||||
.timer_limits.total_playback_duration_seconds = kDuration;
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(true, OEMCrypto_SUCCESS));
|
||||
wvutil::TestSleep::Sleep(kShortSleep); // Should still be valid key.
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(false, OEMCrypto_SUCCESS));
|
||||
wvutil::TestSleep::Sleep(kLongSleep); // Should be expired key.
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED));
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestGetKeyHandleExpired(0));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoLicenseTest,
|
||||
Range<uint32_t>(kCurrentAPI - 2, kCurrentAPI + 1));
|
||||
|
||||
} // namespace wvoec
|
||||
430
libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.h
Normal file
430
libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.h
Normal file
@@ -0,0 +1,430 @@
|
||||
// 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 "oec_session_util.h"
|
||||
#include "oemcrypto_basic_test.h"
|
||||
#include "oemcrypto_license_test.h"
|
||||
#include "oemcrypto_resource_test.h"
|
||||
#include "oemcrypto_session_tests_helper.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(¤t, &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();
|
||||
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({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_
|
||||
@@ -10,11 +10,14 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "clock.h"
|
||||
#include "log.h"
|
||||
#include "oec_session_util.h"
|
||||
#include "oemcrypto_basic_test.h"
|
||||
#include "oemcrypto_corpus_generator_helper.h"
|
||||
#include "oemcrypto_resource_test.h"
|
||||
#include "oemcrypto_session_tests_helper.h"
|
||||
#include "wvcrc32.h"
|
||||
|
||||
using ::testing::WithParamInterface;
|
||||
|
||||
@@ -237,6 +240,127 @@ class OEMCryptoLicenseTest : public OEMCryptoLicenseTestAPI16,
|
||||
}
|
||||
};
|
||||
|
||||
// Test usage table functionality.
|
||||
class LicenseWithUsageEntry {
|
||||
public:
|
||||
LicenseWithUsageEntry(const std::string& pst = "my_pst")
|
||||
: session_(),
|
||||
license_messages_(&session_),
|
||||
generic_crypto_(false),
|
||||
time_license_received_(0),
|
||||
time_first_decrypt_(0),
|
||||
time_last_decrypt_(0),
|
||||
active_(true) {
|
||||
license_messages_.set_pst(pst);
|
||||
}
|
||||
|
||||
void MakeAndLoadOnline(OEMCryptoSessionTests* test) {
|
||||
MakeAndLoad(test,
|
||||
wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired);
|
||||
}
|
||||
|
||||
// If status in not a nullptr, then creating a new entry is allowed to fail,
|
||||
// and its error code is stored in status.
|
||||
void MakeOfflineAndClose(OEMCryptoSessionTests* test,
|
||||
OEMCryptoResult* status = nullptr) {
|
||||
MakeAndLoad(test, wvoec::kControlNonceOrEntry, status);
|
||||
if (status != nullptr && *status != OEMCrypto_SUCCESS) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.close());
|
||||
return;
|
||||
}
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.UpdateUsageEntry(&(test->encrypted_usage_header_)));
|
||||
ASSERT_NO_FATAL_FAILURE(GenerateVerifyReport(kUnused));
|
||||
ASSERT_NO_FATAL_FAILURE(session_.close());
|
||||
}
|
||||
|
||||
// If status in not a nullptr, then creating a new entry is allowed to fail,
|
||||
// and its error code is stored in status.
|
||||
void MakeAndLoad(SessionUtil* util, uint32_t control,
|
||||
OEMCryptoResult* status = nullptr) {
|
||||
license_messages_.set_control(control);
|
||||
ASSERT_NO_FATAL_FAILURE(session_.open());
|
||||
ASSERT_NO_FATAL_FAILURE(util->InstallTestDrmKey(&session_));
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
if (generic_crypto_) {
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
license_messages_.CreateResponseWithGenericCryptoKeys());
|
||||
} else {
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
||||
}
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry(status));
|
||||
if (status != nullptr && *status != OEMCrypto_SUCCESS) return;
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
time_license_received_ = wvutil::Clock().GetCurrentTime();
|
||||
}
|
||||
|
||||
void OpenAndReload(SessionUtil* util) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.open());
|
||||
ASSERT_NO_FATAL_FAILURE(session_.ReloadUsageEntry());
|
||||
ASSERT_NO_FATAL_FAILURE(util->InstallTestDrmKey(&session_));
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateDerivedKeysFromSessionKey());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
}
|
||||
|
||||
// Test decrypt, and update the decrypt times for the pst report.
|
||||
void TestDecryptCTR(bool select_key_first = true,
|
||||
OEMCryptoResult expected_result = OEMCrypto_SUCCESS) {
|
||||
session_.TestDecryptCTR(select_key_first, expected_result);
|
||||
time_last_decrypt_ = wvutil::Clock().GetCurrentTime();
|
||||
if (time_first_decrypt_ == 0) time_first_decrypt_ = time_last_decrypt_;
|
||||
}
|
||||
|
||||
void DeactivateUsageEntry() {
|
||||
active_ = false;
|
||||
if (ShouldGenerateCorpus()) {
|
||||
const std::string file_name =
|
||||
GetFileName("oemcrypto_deactivate_usage_entry_fuzz_seed_corpus");
|
||||
AppendToFile(file_name, pst().c_str(), pst().length());
|
||||
}
|
||||
ASSERT_EQ(
|
||||
OEMCrypto_SUCCESS,
|
||||
OEMCrypto_DeactivateUsageEntry(
|
||||
session_.session_id(),
|
||||
reinterpret_cast<const uint8_t*>(pst().c_str()), pst().length()));
|
||||
}
|
||||
|
||||
void GenerateVerifyReport(OEMCrypto_Usage_Entry_Status status) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateReport(pst()));
|
||||
Test_PST_Report expected(pst(), status);
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.VerifyReport(expected, time_license_received_,
|
||||
time_first_decrypt_, time_last_decrypt_));
|
||||
// The PST report was signed above. Below we verify that the entire message
|
||||
// that is sent to the server will be signed by the right mac keys.
|
||||
RenewalRoundTrip renewal_messages(&license_messages_);
|
||||
renewal_messages.set_is_release(!active_);
|
||||
ASSERT_NO_FATAL_FAILURE(renewal_messages.SignAndVerifyRequest());
|
||||
}
|
||||
|
||||
void ReloadUsageEntry() {
|
||||
session_.ReloadUsageEntry();
|
||||
session_.set_mac_keys(license_messages_.response_data().mac_keys);
|
||||
}
|
||||
|
||||
const std::string& pst() const { return license_messages_.pst(); }
|
||||
void set_pst(const std::string& pst) { license_messages_.set_pst(pst); }
|
||||
LicenseRoundTrip& license_messages() { return license_messages_; }
|
||||
Session& session() { return session_; }
|
||||
void set_generic_crypto(bool generic_crypto) {
|
||||
generic_crypto_ = generic_crypto;
|
||||
}
|
||||
|
||||
private:
|
||||
Session session_;
|
||||
LicenseRoundTrip license_messages_;
|
||||
bool generic_crypto_;
|
||||
int64_t time_license_received_;
|
||||
int64_t time_first_decrypt_;
|
||||
int64_t time_last_decrypt_;
|
||||
bool active_;
|
||||
};
|
||||
|
||||
} // namespace wvoec
|
||||
|
||||
#endif // CDM_OEMCRYPTO_LICENSE_TEST_
|
||||
@@ -5505,127 +5505,6 @@ INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoGenericCryptoKeyIdLengthTest,
|
||||
/// @addtogroup usage_table
|
||||
/// @{
|
||||
|
||||
// Test usage table functionality.
|
||||
class LicenseWithUsageEntry {
|
||||
public:
|
||||
LicenseWithUsageEntry(const std::string& pst = "my_pst")
|
||||
: session_(),
|
||||
license_messages_(&session_),
|
||||
generic_crypto_(false),
|
||||
time_license_received_(0),
|
||||
time_first_decrypt_(0),
|
||||
time_last_decrypt_(0),
|
||||
active_(true) {
|
||||
license_messages_.set_pst(pst);
|
||||
}
|
||||
|
||||
void MakeAndLoadOnline(OEMCryptoSessionTests* test) {
|
||||
MakeAndLoad(test,
|
||||
wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired);
|
||||
}
|
||||
|
||||
// If status in not a nullptr, then creating a new entry is allowed to fail,
|
||||
// and its error code is stored in status.
|
||||
void MakeOfflineAndClose(OEMCryptoSessionTests* test,
|
||||
OEMCryptoResult* status = nullptr) {
|
||||
MakeAndLoad(test, wvoec::kControlNonceOrEntry, status);
|
||||
if (status != nullptr && *status != OEMCrypto_SUCCESS) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.close());
|
||||
return;
|
||||
}
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.UpdateUsageEntry(&(test->encrypted_usage_header_)));
|
||||
ASSERT_NO_FATAL_FAILURE(GenerateVerifyReport(kUnused));
|
||||
ASSERT_NO_FATAL_FAILURE(session_.close());
|
||||
}
|
||||
|
||||
// If status in not a nullptr, then creating a new entry is allowed to fail,
|
||||
// and its error code is stored in status.
|
||||
void MakeAndLoad(SessionUtil* util, uint32_t control,
|
||||
OEMCryptoResult* status = nullptr) {
|
||||
license_messages_.set_control(control);
|
||||
ASSERT_NO_FATAL_FAILURE(session_.open());
|
||||
ASSERT_NO_FATAL_FAILURE(util->InstallTestDrmKey(&session_));
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
if (generic_crypto_) {
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
license_messages_.CreateResponseWithGenericCryptoKeys());
|
||||
} else {
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
||||
}
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry(status));
|
||||
if (status != nullptr && *status != OEMCrypto_SUCCESS) return;
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
time_license_received_ = wvutil::Clock().GetCurrentTime();
|
||||
}
|
||||
|
||||
void OpenAndReload(SessionUtil* util) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.open());
|
||||
ASSERT_NO_FATAL_FAILURE(session_.ReloadUsageEntry());
|
||||
ASSERT_NO_FATAL_FAILURE(util->InstallTestDrmKey(&session_));
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateDerivedKeysFromSessionKey());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
}
|
||||
|
||||
// Test decrypt, and update the decrypt times for the pst report.
|
||||
void TestDecryptCTR(bool select_key_first = true,
|
||||
OEMCryptoResult expected_result = OEMCrypto_SUCCESS) {
|
||||
session_.TestDecryptCTR(select_key_first, expected_result);
|
||||
time_last_decrypt_ = wvutil::Clock().GetCurrentTime();
|
||||
if (time_first_decrypt_ == 0) time_first_decrypt_ = time_last_decrypt_;
|
||||
}
|
||||
|
||||
void DeactivateUsageEntry() {
|
||||
active_ = false;
|
||||
if (ShouldGenerateCorpus()) {
|
||||
const std::string file_name =
|
||||
GetFileName("oemcrypto_deactivate_usage_entry_fuzz_seed_corpus");
|
||||
AppendToFile(file_name, pst().c_str(), pst().length());
|
||||
}
|
||||
ASSERT_EQ(
|
||||
OEMCrypto_SUCCESS,
|
||||
OEMCrypto_DeactivateUsageEntry(
|
||||
session_.session_id(),
|
||||
reinterpret_cast<const uint8_t*>(pst().c_str()), pst().length()));
|
||||
}
|
||||
|
||||
void GenerateVerifyReport(OEMCrypto_Usage_Entry_Status status) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateReport(pst()));
|
||||
Test_PST_Report expected(pst(), status);
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.VerifyReport(expected, time_license_received_,
|
||||
time_first_decrypt_, time_last_decrypt_));
|
||||
// The PST report was signed above. Below we verify that the entire message
|
||||
// that is sent to the server will be signed by the right mac keys.
|
||||
RenewalRoundTrip renewal_messages(&license_messages_);
|
||||
renewal_messages.set_is_release(!active_);
|
||||
ASSERT_NO_FATAL_FAILURE(renewal_messages.SignAndVerifyRequest());
|
||||
}
|
||||
|
||||
void ReloadUsageEntry() {
|
||||
session_.ReloadUsageEntry();
|
||||
session_.set_mac_keys(license_messages_.response_data().mac_keys);
|
||||
}
|
||||
|
||||
const std::string& pst() const { return license_messages_.pst(); }
|
||||
void set_pst(const std::string& pst) { license_messages_.set_pst(pst); }
|
||||
LicenseRoundTrip& license_messages() { return license_messages_; }
|
||||
Session& session() { return session_; }
|
||||
void set_generic_crypto(bool generic_crypto) {
|
||||
generic_crypto_ = generic_crypto;
|
||||
}
|
||||
|
||||
private:
|
||||
Session session_;
|
||||
LicenseRoundTrip license_messages_;
|
||||
bool generic_crypto_;
|
||||
int64_t time_license_received_;
|
||||
int64_t time_first_decrypt_;
|
||||
int64_t time_last_decrypt_;
|
||||
bool active_;
|
||||
};
|
||||
|
||||
class OEMCryptoUsageTableTest : public OEMCryptoGenericCryptoTest {
|
||||
public:
|
||||
void SetUp() override { OEMCryptoGenericCryptoTest::SetUp(); }
|
||||
|
||||
Reference in New Issue
Block a user