697 lines
29 KiB
C++
697 lines
29 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.
|
|
//
|
|
|
|
#include "oemcrypto_decrypt_test.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));
|
|
}
|
|
|
|
// GetKeyHandle 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(std::move(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(std::move(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(std::move(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());
|
|
}
|
|
|
|
TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptZeroSizeSubSample) {
|
|
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
|
{10, 10},
|
|
{0, 0},
|
|
}));
|
|
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
|