862 lines
35 KiB
C++
862 lines
35 KiB
C++
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
|
// source code may only be used and distributed under the Widevine
|
|
// License Agreement.
|
|
//
|
|
|
|
/**
|
|
* @mainpage OEMCrypto Unit Tests
|
|
*
|
|
* The OEMCrypto unit tests are designed to verify that an implementation of
|
|
* OEMCrypto is correctly supporting the OEMCrypto API.
|
|
*
|
|
* @defgroup basic Basic Functionality Tests
|
|
* Basic functionality tests.
|
|
*
|
|
* @defgroup provision Provisioning Tests
|
|
* Test for provisioning and certificate key processing. These tests cover
|
|
* Provsioning 2.0, 3.0 and 4.0. Tests for the wrong provisioning scheme should
|
|
* be skipped.
|
|
*
|
|
* @defgroup license License Request Tests
|
|
* Test for requesting and loading licenses.
|
|
*
|
|
* @defgroup renewal License Renewal Tests
|
|
* Tests for renewing licenses.
|
|
*
|
|
* @defgroup decrypt Decrypt Tests
|
|
* Tests for decrypting content.
|
|
*
|
|
* @defgroup usage_table Usage Table Tests
|
|
* Tests that use the usage table.
|
|
*
|
|
* @defgroup entitle Entitlement License tests
|
|
* Tests for entitlement licenses.
|
|
*
|
|
* @defgroup cas Conditional Access System Tests
|
|
* Tests for OEMCrypto implementations that support MediaCAS.
|
|
*
|
|
* @defgroup cast Cast Test
|
|
* Tests for OEMCrypto implementations that support being a Cast receiver.
|
|
*
|
|
* @defgroup android Android Tests
|
|
* Tests that enforce requirements that are specific to Android.
|
|
*
|
|
* @defgroup generic Generic Crypto Tests
|
|
* Tests for the Generic Crypto functionality.
|
|
*
|
|
* @defgroup security Security Tests
|
|
* Buffer overflow tests, off-by-one tests, and other security tests.
|
|
*
|
|
* The way the huge buffer tests work is to create a large buffer and then call
|
|
* the API. The test then loops and doubles the buffer until the API returns an
|
|
* error. An error is considered a passing test. We expect OEMCrypto to fail
|
|
* gracefully on a huge buffer rather than crashing.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <gtest/gtest.h>
|
|
#include <openssl/aes.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/hmac.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/rsa.h>
|
|
#include <openssl/sha.h>
|
|
#include <openssl/x509.h>
|
|
#include <stdint.h>
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <functional>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "OEMCryptoCENC.h"
|
|
#include "clock.h"
|
|
#include "log.h"
|
|
#include "oec_decrypt_fallback_chain.h"
|
|
#include "oec_device_features.h"
|
|
#include "oec_extra_test_keys.h"
|
|
#include "oec_session_util.h"
|
|
#include "oec_test_data.h"
|
|
#include "oemcrypto_basic_test.h"
|
|
#include "oemcrypto_corpus_generator_helper.h"
|
|
#include "oemcrypto_fuzz_structs.h"
|
|
#include "oemcrypto_license_test.h"
|
|
#include "oemcrypto_provisioning_test.h"
|
|
#include "oemcrypto_resource_test.h"
|
|
#include "oemcrypto_session_tests_helper.h"
|
|
#include "oemcrypto_types.h"
|
|
#include "oemcrypto_usage_table_test.h"
|
|
#include "platform.h"
|
|
#include "string_conversions.h"
|
|
#include "test_sleep.h"
|
|
#include "wvcrc32.h"
|
|
|
|
using ::testing::Range;
|
|
using ::testing::tuple;
|
|
using namespace std;
|
|
|
|
namespace std { // GTest wants PrintTo to be in the std namespace.
|
|
void PrintTo(const tuple<OEMCrypto_CENCEncryptPatternDesc, OEMCryptoCipherMode,
|
|
wvoec::OutputType>& param,
|
|
ostream* os) {
|
|
OEMCrypto_CENCEncryptPatternDesc pattern = ::testing::get<0>(param);
|
|
OEMCryptoCipherMode mode = ::testing::get<1>(param);
|
|
wvoec::OutputType output = ::testing::get<2>(param);
|
|
bool decrypt_inplace = output.decrypt_inplace;
|
|
OEMCryptoBufferType type = output.type;
|
|
*os << ((mode == OEMCrypto_CipherMode_CENC) ? "CENC mode" : "CBCS mode")
|
|
<< ", pattern=(encrypt:" << pattern.encrypt << ", skip:" << pattern.skip
|
|
<< ")";
|
|
switch (type) {
|
|
case OEMCrypto_BufferType_Clear:
|
|
*os << ", BufferType = Clear";
|
|
break;
|
|
case OEMCrypto_BufferType_Secure:
|
|
*os << ", BufferType = Secure";
|
|
break;
|
|
case OEMCrypto_BufferType_Direct:
|
|
*os << ", BufferType = Direct";
|
|
break;
|
|
default:
|
|
*os << ", type = <bad type " << type << ">";
|
|
break;
|
|
}
|
|
if (decrypt_inplace) *os << " (in place)";
|
|
}
|
|
} // namespace std
|
|
|
|
namespace wvoec {
|
|
|
|
/// @addtogroup entitle
|
|
/// @{
|
|
|
|
class OEMCryptoEntitlementLicenseTest : public OEMCryptoLicenseTest {
|
|
protected:
|
|
void LoadEntitlementLicense() {
|
|
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
|
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());
|
|
}
|
|
};
|
|
|
|
/** This verifies that entitlement keys and entitled content keys can be loaded.
|
|
*/
|
|
TEST_P(OEMCryptoEntitlementLicenseTest, LoadEntitlementKeysAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
LoadEntitlementLicense();
|
|
uint32_t key_session_id = 0;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id));
|
|
EntitledMessage entitled_message_1(&license_messages_);
|
|
entitled_message_1.FillKeyArray();
|
|
entitled_message_1.SetEntitledKeySession(key_session_id);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(true));
|
|
EntitledMessage entitled_message_2(&license_messages_);
|
|
entitled_message_2.FillKeyArray();
|
|
entitled_message_2.SetEntitledKeySession(key_session_id);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadKeys(true));
|
|
}
|
|
|
|
/**
|
|
* This verifies that entitled content keys cannot be loaded if we have not yet
|
|
* loaded the entitlement keys.
|
|
*/
|
|
TEST_P(OEMCryptoEntitlementLicenseTest,
|
|
LoadEntitlementKeysNoEntitlementKeysAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
|
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
|
uint32_t key_session_id = 0;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id));
|
|
|
|
EntitledMessage entitled_message_1(&license_messages_);
|
|
entitled_message_1.FillKeyArray();
|
|
entitled_message_1.SetEntitledKeySession(key_session_id);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(false));
|
|
}
|
|
|
|
/**
|
|
* This verifies that entitled content keys cannot be loaded if we have loaded
|
|
* the wrong entitlement keys.
|
|
*/
|
|
TEST_P(OEMCryptoEntitlementLicenseTest,
|
|
LoadEntitlementKeysWrongEntitlementKeysAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
LoadEntitlementLicense();
|
|
uint32_t key_session_id = 0;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id));
|
|
|
|
EntitledMessage entitled_message_1(&license_messages_);
|
|
entitled_message_1.FillKeyArray();
|
|
const std::string key_id = "no_key";
|
|
entitled_message_1.SetEntitlementKeyId(0, key_id);
|
|
entitled_message_1.SetEntitledKeySession(key_session_id);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(false));
|
|
}
|
|
|
|
/**
|
|
* This verifies that entitled content keys cannot be loaded if we specify an
|
|
* entitled key session that has not been created.
|
|
*/
|
|
TEST_P(OEMCryptoEntitlementLicenseTest,
|
|
LoadEntitlementKeysWrongEntitledKeySessionAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
LoadEntitlementLicense();
|
|
uint32_t key_session_id = 0;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id));
|
|
|
|
EntitledMessage entitled_message_1(&license_messages_);
|
|
entitled_message_1.FillKeyArray();
|
|
const uint32_t wrong_key_session_id = key_session_id == 0 ? 1 : 0;
|
|
entitled_message_1.SetEntitledKeySession(wrong_key_session_id);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(false));
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoEntitlementLicenseTest,
|
|
Range<uint32_t>(kCoreMessagesAPI, kCurrentAPI + 1));
|
|
|
|
TEST_P(OEMCryptoLicenseTest, GetKeyHandleEntitledKeyAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
|
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 key_session_id;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id));
|
|
EntitledMessage entitled_message_1(&license_messages_);
|
|
entitled_message_1.FillKeyArray();
|
|
entitled_message_1.SetEntitledKeySession(key_session_id);
|
|
const char* content_key_id = "content_key_id";
|
|
entitled_message_1.SetContentKeyId(0, content_key_id);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(true));
|
|
|
|
vector<uint8_t> key_handle;
|
|
ASSERT_EQ(
|
|
OEMCrypto_SUCCESS,
|
|
GetKeyHandleIntoVector(
|
|
key_session_id, reinterpret_cast<const uint8_t*>(content_key_id),
|
|
strlen(content_key_id), OEMCrypto_CipherMode_CENC, key_handle));
|
|
}
|
|
|
|
// SelectEntitledKey should fail if we attempt to select a key that has not been
|
|
// loaded. Also, the error should be NO_CONTENT_KEY.
|
|
TEST_P(OEMCryptoLicenseTest, SelectKeyEntitledKeyNotThereAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
|
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 key_session_id;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id));
|
|
EntitledMessage entitled_message_1(&license_messages_);
|
|
entitled_message_1.FillKeyArray();
|
|
entitled_message_1.SetEntitledKeySession(key_session_id);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(true));
|
|
|
|
const char* content_key_id = "no_key";
|
|
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
|
|
OEMCrypto_ERROR_INVALID_CONTEXT, key_session_id,
|
|
reinterpret_cast<const uint8_t*>(content_key_id),
|
|
strlen(content_key_id)));
|
|
}
|
|
|
|
// This verifies that entitled key sessions can be created and removed.
|
|
TEST_P(OEMCryptoLicenseTest, EntitledKeySessionsAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
if (!global_features.supports_cas) {
|
|
GTEST_SKIP() << "OEMCrypto does not support CAS";
|
|
}
|
|
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
|
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 key_session_id_1;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id_1));
|
|
ASSERT_NE(key_session_id_1, 0u); // 0 is a reserved id number.
|
|
|
|
uint32_t key_session_id_2;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id_2));
|
|
ASSERT_NE(key_session_id_2, 0u); // 0 is a reserved id number.
|
|
// Entitled key sessions should have unique ids.
|
|
ASSERT_NE(key_session_id_1, key_session_id_2);
|
|
|
|
ASSERT_EQ(OEMCrypto_SUCCESS,
|
|
OEMCrypto_RemoveEntitledKeySession(key_session_id_1));
|
|
ASSERT_EQ(OEMCrypto_SUCCESS,
|
|
OEMCrypto_RemoveEntitledKeySession(key_session_id_2));
|
|
}
|
|
|
|
TEST_P(OEMCryptoLicenseTest,
|
|
EntitledKeySessionsCloseWithOEMCryptoSessionAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
|
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 key_session_id_1;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id_1));
|
|
// Close the OEMCrypto session.
|
|
session_.close();
|
|
// All entitled key sessions associated with the OEMCrypto session should
|
|
// already be destroyed.
|
|
ASSERT_EQ(OEMCrypto_SUCCESS,
|
|
OEMCrypto_RemoveEntitledKeySession(key_session_id_1));
|
|
// Open a new session just for OEMCryptoLicenseTest TearDown.
|
|
session_.open();
|
|
}
|
|
|
|
// This verifies that within an entitled key session, each entitlement key can
|
|
// corresponds to only one content key at most.
|
|
TEST_P(OEMCryptoLicenseTest,
|
|
EntitledKeySessionOneContentKeyPerEntitlementAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
|
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 key_session_id;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id));
|
|
// Construct and load content keys to entitled key session.
|
|
EntitledMessage entitled_message_1(&license_messages_);
|
|
entitled_message_1.FillKeyArray();
|
|
entitled_message_1.SetEntitledKeySession(key_session_id);
|
|
const char* content_key_id_1 = "content_key_id_1";
|
|
entitled_message_1.SetContentKeyId(0, content_key_id_1);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(true));
|
|
// We can select content key 1 in entitled key session.
|
|
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
|
|
OEMCrypto_SUCCESS, key_session_id,
|
|
reinterpret_cast<const uint8_t*>(content_key_id_1),
|
|
strlen(content_key_id_1)));
|
|
|
|
// Load content key with new content id.
|
|
const char* content_key_id_2 = "content_key_id_2";
|
|
entitled_message_1.SetContentKeyId(0, content_key_id_2);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(true));
|
|
// We can select content key 2 in entitled key session.
|
|
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
|
|
OEMCrypto_SUCCESS, key_session_id,
|
|
reinterpret_cast<const uint8_t*>(content_key_id_2),
|
|
strlen(content_key_id_2)));
|
|
|
|
// Content key one is no longer in the entitled key session as they use the
|
|
// same entitlement key.
|
|
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
|
|
OEMCrypto_ERROR_NO_CONTENT_KEY, key_session_id,
|
|
reinterpret_cast<const uint8_t*>(content_key_id_1),
|
|
strlen(content_key_id_1)));
|
|
}
|
|
|
|
// Decrypt should fail if the license is entitlement license, and the key handle
|
|
// is requested from the oemcrypto session (should use entitled key session id
|
|
// instead).
|
|
TEST_P(OEMCryptoLicenseTest,
|
|
RejectOecSessionDecryptWithEntitlementLicenseAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
|
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 key_session_id;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id));
|
|
|
|
// Skip the rest of this test on platforms that do not support separate
|
|
// entitlement and entitled sessions.
|
|
if (global_features.supports_cas || session_.session_id() != key_session_id) {
|
|
// Construct and load content keys to entitled key session.
|
|
EntitledMessage entitled_message(&license_messages_);
|
|
entitled_message.FillKeyArray();
|
|
entitled_message.SetEntitledKeySession(key_session_id);
|
|
constexpr char kContentKeyId[] = "content_key_id";
|
|
const size_t content_key_id_length = strlen(kContentKeyId);
|
|
entitled_message.SetContentKeyId(0, kContentKeyId);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message.LoadKeys(true));
|
|
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
|
|
OEMCrypto_SUCCESS, key_session_id,
|
|
reinterpret_cast<const uint8_t*>(kContentKeyId),
|
|
content_key_id_length));
|
|
|
|
// Try to get a key handle with the oemcrypto session id.
|
|
vector<uint8_t> key_handle;
|
|
EXPECT_NE(GetKeyHandleIntoVector(
|
|
session_.session_id(),
|
|
reinterpret_cast<const uint8_t*>(kContentKeyId),
|
|
content_key_id_length, OEMCrypto_CipherMode_CENC, key_handle),
|
|
OEMCrypto_SUCCESS);
|
|
}
|
|
}
|
|
|
|
// This verifies that an entitled key session can be reassociated to an
|
|
// OEMCrypto session.
|
|
TEST_P(OEMCryptoEntitlementLicenseTest, ReassociateEntitledKeySessionAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
|
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());
|
|
|
|
// Setup an entitled key session in the first OEMCrypto session.
|
|
uint32_t key_session_id;
|
|
OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id);
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
|
EntitledMessage entitled_message(&license_messages_);
|
|
entitled_message.FillKeyArray();
|
|
entitled_message.SetEntitledKeySession(key_session_id);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message.LoadKeys(true));
|
|
|
|
// Setup another session.
|
|
Session session2;
|
|
ASSERT_NO_FATAL_FAILURE(session2.open());
|
|
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&session2));
|
|
// session2 does not have entitlement keys. Re-associating the entitled key
|
|
// session to session2 should fail
|
|
OEMCryptoResult status = OEMCrypto_ReassociateEntitledKeySession(
|
|
key_session_id, session2.session_id());
|
|
if (status == OEMCrypto_ERROR_NOT_IMPLEMENTED &&
|
|
!global_features.supports_cas) {
|
|
GTEST_SKIP() << "Skipping test because "
|
|
"OEMCrypto_ReassociateEntitledKeySession not implemented.";
|
|
}
|
|
EXPECT_NE(OEMCrypto_SUCCESS, status);
|
|
|
|
// session2 loads the correct entitlement keys.
|
|
LicenseRoundTrip license_messages2(&session2);
|
|
license_messages2.set_license_type(OEMCrypto_EntitlementLicense);
|
|
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());
|
|
// Re-associating to session2 should succeed.
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_ReassociateEntitledKeySession(
|
|
key_session_id, session2.session_id()));
|
|
|
|
// Now reassociate the entitled key session back to the first OEMCrypto
|
|
// session.
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_ReassociateEntitledKeySession(
|
|
key_session_id, session_.session_id()));
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message.LoadKeys(true));
|
|
|
|
// session3 has unmatched key policies
|
|
Session session3;
|
|
ASSERT_NO_FATAL_FAILURE(session3.open());
|
|
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&session3));
|
|
LicenseRoundTrip license_messages3(&session3);
|
|
license_messages3.set_license_type(OEMCrypto_EntitlementLicense);
|
|
license_messages3.set_control(license_messages_.control() + 1);
|
|
ASSERT_NO_FATAL_FAILURE(license_messages3.SignAndVerifyRequest());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages3.CreateDefaultResponse());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages3.EncryptAndSignResponse());
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages3.LoadResponse());
|
|
// Re-associating to session3 should fail.
|
|
EXPECT_NE(OEMCrypto_SUCCESS, OEMCrypto_ReassociateEntitledKeySession(
|
|
key_session_id, session3.session_id()));
|
|
}
|
|
|
|
/// @}
|
|
|
|
/// @addtogroup cas
|
|
/// @{
|
|
|
|
TEST_P(OEMCryptoEntitlementLicenseTest, CasOnlyLoadCasKeysAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
if (!global_features.supports_cas) {
|
|
GTEST_SKIP() << "OEMCrypto does not support CAS";
|
|
}
|
|
LoadEntitlementLicense();
|
|
uint32_t key_session_id = 0;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id));
|
|
|
|
EntitledMessage entitled_message_1(&license_messages_);
|
|
entitled_message_1.FillKeyArray();
|
|
entitled_message_1.SetEntitledKeySession(key_session_id);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
|
/*load_even=*/true, /*load_odd=*/true, OEMCrypto_SUCCESS));
|
|
EntitledMessage entitled_message_2(&license_messages_);
|
|
entitled_message_2.FillKeyArray();
|
|
entitled_message_2.SetEntitledKeySession(key_session_id);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadCasKeys(
|
|
/*load_even=*/true, /*load_odd=*/false, OEMCrypto_SUCCESS));
|
|
EntitledMessage entitled_message_3(&license_messages_);
|
|
entitled_message_3.FillKeyArray();
|
|
entitled_message_3.SetEntitledKeySession(key_session_id);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_3.LoadCasKeys(
|
|
/*load_even=*/false, /*load_odd=*/true, OEMCrypto_SUCCESS));
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_3.LoadCasKeys(
|
|
/*load_even=*/false, /*load_odd=*/false, OEMCrypto_SUCCESS));
|
|
}
|
|
|
|
/**
|
|
* This verifies that entitled content keys cannot be loaded if we have loaded
|
|
* the wrong entitlement keys.
|
|
*/
|
|
TEST_P(OEMCryptoEntitlementLicenseTest,
|
|
CasOnlyLoadCasKeysNoEntitlementKeysAPI17) {
|
|
if (!global_features.supports_cas) {
|
|
GTEST_SKIP() << "OEMCrypto does not support CAS";
|
|
}
|
|
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
|
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
|
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
|
uint32_t key_session_id = 0;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id));
|
|
|
|
EntitledMessage entitled_message_1(&license_messages_);
|
|
entitled_message_1.FillKeyArray();
|
|
entitled_message_1.SetEntitledKeySession(key_session_id);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
|
/*load_even=*/true, /*load_odd=*/true, OEMCrypto_ERROR_INVALID_CONTEXT));
|
|
}
|
|
|
|
TEST_P(OEMCryptoEntitlementLicenseTest,
|
|
CasOnlyLoadCasKeysWrongEntitlementKeysAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
if (!global_features.supports_cas) {
|
|
GTEST_SKIP() << "OEMCrypto does not support CAS";
|
|
}
|
|
LoadEntitlementLicense();
|
|
uint32_t key_session_id = 0;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id));
|
|
|
|
EntitledMessage entitled_message_1(&license_messages_);
|
|
entitled_message_1.FillKeyArray();
|
|
const std::string key_id = "no_key";
|
|
entitled_message_1.SetEntitlementKeyId(0, key_id);
|
|
entitled_message_1.SetEntitledKeySession(key_session_id);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
|
/*load_even=*/true, /*load_odd=*/true, OEMCrypto_KEY_NOT_ENTITLED));
|
|
}
|
|
|
|
TEST_P(OEMCryptoEntitlementLicenseTest,
|
|
CasOnlyLoadCasKeysWrongEntitledKeySessionAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
if (!global_features.supports_cas) {
|
|
GTEST_SKIP() << "OEMCrypto does not support CAS";
|
|
}
|
|
LoadEntitlementLicense();
|
|
uint32_t key_session_id = 0;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id));
|
|
|
|
EntitledMessage entitled_message_1(&license_messages_);
|
|
entitled_message_1.FillKeyArray();
|
|
const uint32_t wrong_key_session_id = key_session_id == 0 ? 1 : 0;
|
|
entitled_message_1.SetEntitledKeySession(wrong_key_session_id);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
|
/*load_even=*/true, /*load_odd=*/true,
|
|
OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION));
|
|
}
|
|
|
|
/**
|
|
* This verifies that entitled content keys cannot be loaded if we specify an
|
|
* entitled key session that is actually an oemcrypto session.
|
|
*/
|
|
TEST_P(OEMCryptoEntitlementLicenseTest,
|
|
LoadEntitlementKeysOemcryptoSessionAPI17) {
|
|
if (!global_features.supports_cas) {
|
|
GTEST_SKIP() << "OEMCrypto does not support CAS";
|
|
}
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
LoadEntitlementLicense();
|
|
uint32_t key_session_id = 0;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id));
|
|
|
|
EntitledMessage entitled_message_1(&license_messages_);
|
|
entitled_message_1.FillKeyArray();
|
|
if (session_.session_id() == key_session_id) {
|
|
GTEST_SKIP()
|
|
<< "Skipping test because entitled and entitlement sessions are both "
|
|
<< key_session_id << ".";
|
|
}
|
|
entitled_message_1.SetEntitledKeySession(session_.session_id());
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(false));
|
|
}
|
|
|
|
TEST_P(OEMCryptoEntitlementLicenseTest,
|
|
CasOnlyLoadCasKeysOemcryptoSessionAPI17) {
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
if (!global_features.supports_cas) {
|
|
GTEST_SKIP() << "OEMCrypto does not support CAS";
|
|
}
|
|
LoadEntitlementLicense();
|
|
uint32_t key_session_id = 0;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id));
|
|
|
|
EntitledMessage entitled_message_1(&license_messages_);
|
|
entitled_message_1.FillKeyArray();
|
|
entitled_message_1.SetEntitledKeySession(session_.session_id());
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
|
/*load_even=*/true, /*load_odd=*/true,
|
|
OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION));
|
|
}
|
|
|
|
/**
|
|
* Select key with entitlement license fails if the key id is entitlement key
|
|
* id.
|
|
*/
|
|
TEST_P(OEMCryptoLicenseTest, SelectKeyEntitlementKeyAPI17) {
|
|
if (!global_features.supports_cas) {
|
|
GTEST_SKIP() << "OEMCrypto does not support CAS";
|
|
}
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
|
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 key_session_id;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id));
|
|
EntitledMessage entitled_message_1(&license_messages_);
|
|
entitled_message_1.FillKeyArray();
|
|
entitled_message_1.SetEntitledKeySession(key_session_id);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(true));
|
|
|
|
if (session_.session_id() == key_session_id) {
|
|
GTEST_SKIP()
|
|
<< "Skipping test because entitled and entitlement sessions are both "
|
|
<< key_session_id << ".";
|
|
}
|
|
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
|
|
OEMCrypto_ERROR_INVALID_CONTEXT, session_.session_id(),
|
|
session_.license().keys[0].key_id,
|
|
session_.license().keys[0].key_id_length));
|
|
}
|
|
|
|
// This verifies that multiple entitled key sessions can be created. They can
|
|
// load and select keys independently.
|
|
TEST_P(OEMCryptoLicenseTest, EntitledKeySessionMultipleKeySessionsAPI17) {
|
|
if (!global_features.supports_cas) {
|
|
GTEST_SKIP() << "OEMCrypto does not support CAS";
|
|
}
|
|
if (wvoec::global_features.api_version < 17) {
|
|
GTEST_SKIP() << "Test for versions 17 and up only.";
|
|
}
|
|
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
|
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 key_session_id_1;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id_1));
|
|
EntitledMessage entitled_message_1(&license_messages_);
|
|
entitled_message_1.FillKeyArray();
|
|
entitled_message_1.SetEntitledKeySession(key_session_id_1);
|
|
const char* content_key_id_1 = "content_key_id_1";
|
|
entitled_message_1.SetContentKeyId(0, content_key_id_1);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(true));
|
|
// We can select content key 1 in entitled key session 1.
|
|
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
|
|
OEMCrypto_SUCCESS, key_session_id_1,
|
|
reinterpret_cast<const uint8_t*>(content_key_id_1),
|
|
strlen(content_key_id_1)));
|
|
|
|
// Create another entitled key session.
|
|
uint32_t key_session_id_2;
|
|
OEMCryptoResult status = OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id_2);
|
|
// For DRM, but not for CAS, we allow there to be only a single entitled
|
|
// session.
|
|
if (!global_features.supports_cas &&
|
|
(key_session_id_2 == key_session_id_1 ||
|
|
status == OEMCrypto_ERROR_TOO_MANY_SESSIONS)) {
|
|
GTEST_SKIP()
|
|
<< "Skipping test because multiple entitled sessions not supported.";
|
|
}
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, status);
|
|
// Entitled key sessions should have unique ids.
|
|
ASSERT_NE(key_session_id_1, key_session_id_2);
|
|
|
|
EntitledMessage entitled_message_2(&license_messages_);
|
|
entitled_message_2.FillKeyArray();
|
|
entitled_message_2.SetEntitledKeySession(key_session_id_2);
|
|
const char* content_key_id_2 = "content_key_id_2";
|
|
entitled_message_2.SetContentKeyId(0, content_key_id_2);
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadKeys(true));
|
|
// We can select content key 2 in entitled key session 2.
|
|
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
|
|
OEMCrypto_SUCCESS, key_session_id_2,
|
|
reinterpret_cast<const uint8_t*>(content_key_id_2),
|
|
strlen(content_key_id_2)));
|
|
|
|
// Content key id 1 is not in entitled key session 2.
|
|
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
|
|
OEMCrypto_ERROR_NO_CONTENT_KEY, key_session_id_2,
|
|
reinterpret_cast<const uint8_t*>(content_key_id_1),
|
|
strlen(content_key_id_1)));
|
|
|
|
// Content key id 2 is not in entitled key session 1.
|
|
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
|
|
OEMCrypto_ERROR_NO_CONTENT_KEY, key_session_id_1,
|
|
reinterpret_cast<const uint8_t*>(content_key_id_2),
|
|
strlen(content_key_id_2)));
|
|
}
|
|
|
|
/// @}
|
|
|
|
/// @addtogroup cas
|
|
/// @{
|
|
|
|
#ifdef CAS_TEST
|
|
|
|
# include "tuner_hal.h"
|
|
|
|
class OEMCryptoCasDemoTest : public OEMCryptoEntitlementLicenseTest {};
|
|
|
|
TEST_P(OEMCryptoCasDemoTest, BasicFlow) {
|
|
// License contains entitlement keys, function reused from
|
|
// OEMCryptoEntitlementLicenseTest
|
|
LoadEntitlementLicense();
|
|
uint32_t key_session_id = 0;
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
|
|
session_.session_id(), &key_session_id));
|
|
|
|
EntitledMessage entitled_message(&license_messages_);
|
|
|
|
// Randomly generate entitled content keys
|
|
entitled_message.FillKeyArray();
|
|
if (session_.session_id() == key_session_id) {
|
|
GTEST_SKIP()
|
|
<< "Skipping test because entitled and entitlement sessions are both "
|
|
<< key_session_id << ".";
|
|
}
|
|
entitled_message.SetEntitledKeySession(key_session_id);
|
|
|
|
// Encrypt and load 0th key (even key) into OEMCrypto
|
|
ASSERT_NO_FATAL_FAILURE(entitled_message.LoadCasKeys(
|
|
/*load_even=*/true, /*load_odd=*/false, OEMCrypto_SUCCESS));
|
|
|
|
//
|
|
// Perform DecryptCTR() but for CAS
|
|
//
|
|
vector<uint8_t> unencrypted_data(256, 0);
|
|
vector<uint8_t> encrypted_data(256, 0);
|
|
vector<uint8_t> output_buffer(256, 0);
|
|
unencrypted_data.resize(encrypted_data.size());
|
|
output_buffer.resize(encrypted_data.size());
|
|
|
|
OEMCrypto_SampleDescription sample_description;
|
|
OEMCrypto_SubSampleDescription subsample_description;
|
|
GenerateSimpleSampleDescription(encrypted_data, output_buffer,
|
|
&sample_description, &subsample_description);
|
|
|
|
// Use 0th entitled content key and IV to encrypt test data
|
|
EncryptCTR(unencrypted_data,
|
|
entitled_message.entitled_key_data()->content_key_data,
|
|
entitled_message.entitled_key_data()->content_iv, &encrypted_data);
|
|
|
|
// Assume 0,0 pattern for CTR example
|
|
OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0};
|
|
|
|
// Demo only -- copy IV into sample description so we can use
|
|
// WTPI_DecryptSample() in the Tuner decrypt impl. A real implementation would
|
|
// use the IV from the entitled content key, but the demo relies on the
|
|
// existing decrypt which uses SampleDescription IV.
|
|
memcpy(sample_description.iv,
|
|
entitled_message.entitled_key_data()->content_iv, 16);
|
|
|
|
// Get key token to send to Tuner for decrypt
|
|
std::vector<uint8_t> key_token;
|
|
size_t key_token_length = key_token.size();
|
|
OEMCryptoResult res = OEMCrypto_GetOEMKeyToken(
|
|
key_session_id, key_token.data(), &key_token_length);
|
|
if (res == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
key_token.resize(key_token_length);
|
|
res = OEMCrypto_GetOEMKeyToken(key_session_id, key_token.data(),
|
|
&key_token_length);
|
|
}
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, res);
|
|
|
|
// Decrypt the data
|
|
ASSERT_EQ(TUNER_HAL_SUCCESS,
|
|
TunerHal_Decrypt(key_token.data(), key_token_length,
|
|
TunerHal_KeyParityType_EvenKey,
|
|
&sample_description, // an array of samples.
|
|
1, // the number of samples.
|
|
&pattern));
|
|
|
|
ASSERT_EQ(unencrypted_data, output_buffer);
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoCasDemoTest,
|
|
Range<uint32_t>(kCoreMessagesAPI, kCurrentAPI + 1));
|
|
|
|
#endif
|
|
|
|
/// @}
|
|
} // namespace wvoec
|