Merge changes from topics "presubmit-am-0d92e9728c2d40da892bd450843310cb", "presubmit-am-11f8881adcb544ca8682231293b0f1c7", "presubmit-am-30bc14671b7b4b309e57b8600f46b32b", "presubmit-am-35012549d66140dd9d446b5eedf6e274", "presubmit-am-374672395de04b7b8f697a54e16be928", "presubmit-am-48d77602d3694ced89dd6e82a89fa646", "presubmit-am-4f8d5681247e4064a298d1e5263c41be", "presubmit-am-89930436636343d5a779bc06ccc307dc", "presubmit-am-904492a27e4449e78cf21dd9f4ab8ff0", "presubmit-am-90646715a3284730bf356bb6f4634729", "presubmit-am-a1ae313a0fde4696b7fb8c4390d3a94c", "presubmit-am-ae051fae1d06485ca7f12bcf265e8328", "presubmit-am-b4e6ace5be72409aab8e328c6f2a0288", "presubmit-am-dd16b680e0454031b2213179b22df7d7", "presubmit-am-e249264532da4839841f4cab3675fa61", "presubmit-am-e3a2f43ba2f84f429536270e16d0d251", "presubmit-am-e5f2e7a319d04b89950c63471d7f2458", "presubmit-am-ea47ff378925466c8c92e2ed9b58c461", "presubmit-am-f582c497c3274c7e84606cf3da4b09df" into tm-dev

* changes:
  Change the signature format requirement of OEMCrypto_GenerateCertificateKeyPair
  Fix EnsureProvisioned for double provisioning
  Update fuzz tests to match output desriptor struct
  Use default url to inform app of prov40 stages
  Fix key_control_iv in OEMCrypto tests
  Fix jenkins/opk_optee after v17 merge
  Remove old test license holder
  Generic crypto tests: use license holder
  Reboot tests: verify offline license is valid after reboot
  Policy integration tests: use license holder
  Integration tests: add license holder
  Reboot test: Initialize fake clock
  Reboot test: save large files
  Test max number of DRM private keys
  Merge oemcrypto-v17 to master
  Update cipher mode elsewhere
  Fix 1 ClangTidyBuild finding:
  Add out of bounds testing for LoadKeys()
  Separate invalid session test for ReuseUsageEntry
This commit is contained in:
Rahul Frias
2022-03-17 16:39:36 +00:00
committed by Android (Google) Code Review
30 changed files with 2418 additions and 1351 deletions

View File

@@ -82,7 +82,8 @@ class CertificateProvisioning {
const std::string& origin, const std::string& spoid,
CdmProvisioningRequest* request, std::string* default_url);
CdmResponseType GetProvisioning40RequestInternal(
wvutil::FileSystem* file_system, CdmProvisioningRequest* request);
wvutil::FileSystem* file_system, CdmProvisioningRequest* request,
std::string* default_url);
CdmResponseType FillEncryptedClientId(
const std::string& client_token,
video_widevine::ProvisioningRequest& provisioning_request);

View File

@@ -26,6 +26,11 @@ const std::string kProvisioningServerUrl =
"https://www.googleapis.com/"
"certificateprovisioning/v1/devicecertificates/create"
"?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE";
// In case of provisioning 4, the default url is used as a way to inform app of
// the current provisioning stage. In the first stage, this suffix is appended
// to kProvisioningServerUrl; in the second stage, there is no change to
// kProvisioningServerUrl.
const std::string kProv40FirstStageServerUrlSuffix = "&preProvisioning=true";
// NOTE: Provider ID = widevine.com
const std::string kCpProductionServiceCertificate = wvutil::a2bs_hex(
@@ -207,7 +212,7 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal(
if (crypto_session_->GetPreProvisionTokenType() ==
kClientTokenBootCertChain) {
return GetProvisioning40RequestInternal(file_system, request);
return GetProvisioning40RequestInternal(file_system, request, default_url);
}
// Prepare device provisioning request.
@@ -298,7 +303,8 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal(
}
CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
wvutil::FileSystem* file_system, CdmProvisioningRequest* request) {
wvutil::FileSystem* file_system, CdmProvisioningRequest* request,
std::string* default_url) {
if (!crypto_session_->IsOpen()) {
LOGE("Crypto session is not open");
return PROVISIONING_4_CRYPTO_SESSION_NOT_OPEN;
@@ -333,6 +339,15 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
}
}
if (stored_oem_cert.empty()) {
// This is the first stage provisioning.
default_url->assign(kProvisioningServerUrl +
kProv40FirstStageServerUrlSuffix);
} else {
// This is the second stage provisioning.
default_url->assign(kProvisioningServerUrl);
}
// If this is the first stage, |stored_oem_cert| remains empty. In this case,
// the client identification token will be retrieved from OEMCrypto, which is
// the BCC in this case.

View File

@@ -18,6 +18,7 @@
#include "device_files.h"
#include "file_store.h"
#include "initialization_data.h"
#include "license_holder.h"
#include "license_request.h"
#include "log.h"
#include "properties.h"
@@ -387,14 +388,11 @@ TEST_F(WvCdmEngineTest, DISABLED_NormalDecryptionWebm) {
TEST_F(WvCdmEngineTest, LoadKey) {
EnsureProvisioned();
TestLicenseHolder holder(&cdm_engine_);
holder.OpenSession(config_.key_system());
holder.GenerateKeyRequest(binary_key_id(), ISO_BMFF_VIDEO_MIME_TYPE);
holder.CreateDefaultLicense();
std::vector<uint8_t> key_data(CONTENT_KEY_SIZE, '1');
wvoec::KeyControlBlock block = {};
holder.AddKey("key_one", key_data, block);
holder.SignAndLoadLicense();
LicenseHolder holder("CDM_Streaming", &cdm_engine_, config_);
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
ASSERT_NO_FATAL_FAILURE(holder.FetchLicense());
ASSERT_NO_FATAL_FAILURE(holder.LoadLicense());
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
}
// This test generates a renewal and then requests the renewal using the server
@@ -402,6 +400,7 @@ TEST_F(WvCdmEngineTest, LoadKey) {
// skip this test when you want to set the license and renewal server on the
// command line.
TEST_F(WvCdmEngineTest, LicenseRenewalSpecifiedServer) {
EnsureProvisioned();
GenerateKeyRequest(binary_key_id(), kCencMimeType);
VerifyNewKeyResponse(config_.license_server(), config_.client_auth());
@@ -423,6 +422,7 @@ TEST_F(WvCdmEngineTest, LicenseRenewalSpecifiedServer) {
// This test generates a renewal and then requests it from the server specified
// by the current test configuration.
TEST_F(WvCdmEngineTest, LicenseRenewal) {
EnsureProvisioned();
GenerateKeyRequest(binary_key_id(), kCencMimeType);
VerifyNewKeyResponse(config_.license_server(), config_.client_auth());

View File

@@ -18,7 +18,7 @@
#include "clock.h"
#include "config_test_env.h"
#include "initialization_data.h"
#include "license_request.h"
#include "license_holder.h"
#include "log.h"
#include "metrics_collections.h"
#include "odk_structs.h"
@@ -36,8 +36,6 @@ namespace wvcdm {
namespace {
constexpr int kHttpOk = 200;
const std::string kCencMimeType = "cenc";
// How many seconds to fudge the start or stop playback times. We fudge these
// times because there might be a little round off when sleeping, and because
// the time that the license was signed or loaded might be off a little bit due
@@ -86,27 +84,6 @@ const RenewalPolicy kHeartbeatRenewal = {"CDM_Heartbeat_renewal", 10, 30};
// Key ID in all duration tests.
const KeyId kKeyId = "Duration_Key====";
class SimpleEventListener : public wvcdm::WvCdmEventListener {
public:
SimpleEventListener() { renewal_needed_ = false; }
// We will want to know when a renewal is needed.
void OnSessionRenewalNeeded(const CdmSessionId& /*session*/) override {
renewal_needed_ = true;
}
void OnSessionKeysChange(const CdmSessionId&, const CdmKeyStatusMap&,
bool) override {}
void OnExpirationUpdate(const CdmSessionId&,
int64_t expiry_time UNUSED) override {}
bool renewal_needed() { return renewal_needed_; }
void set_renewal_needed(bool renewal_needed) {
renewal_needed_ = renewal_needed;
}
private:
bool renewal_needed_;
};
} // namespace
// All duration tests are parameterized by can_persist = true or false.
@@ -114,7 +91,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
public ::testing::WithParamInterface<bool> {
public:
CdmDurationTest(const std::string& content_id)
: content_id_(content_id),
: license_holder_(content_id, &cdm_engine_, config_),
first_load_occurred_(false),
allow_lenience_(false) {
// These are reasonable initial values for most tests. This is an unlimited
@@ -150,24 +127,22 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
void SetUp() override {
WvCdmTestBase::SetUp();
EnsureProvisioned();
ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id_));
can_persist_ = GetParam();
if (can_persist_) {
license_holder_.set_can_persist(GetParam());
ASSERT_NO_FATAL_FAILURE(license_holder_.OpenSession());
if (license_holder_.can_persist()) {
// Each content/policy in a test class below should match two policies in
// UAT. One policy id matches the string exactly, and one has
// _can_persist appended.
content_id_ = content_id_ + "_can_persist";
license_type_ = kLicenseTypeOffline;
} else {
license_type_ = kLicenseTypeStreaming;
license_holder_.set_content_id(license_holder_.content_id() +
"_can_persist");
}
// All times in the license are relative to the rental clock.
start_of_rental_clock_ = wvutil::Clock().GetCurrentTime();
FetchLicense();
license_holder_.FetchLicense();
}
void TearDown() override {
cdm_engine_.CloseSession(session_id_);
license_holder_.CloseSession();
// Log the time used in this test suite. When this comment was written,
// these tests took over three hours. If we want to improve that, we need to
// track these times.
@@ -223,99 +198,20 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
return now - start_of_rental_clock_;
}
void OpenSession(CdmSessionId* session_id) {
CdmResponseType status = cdm_engine_.OpenSession(
config_.key_system(), nullptr, &event_listener_, session_id);
ASSERT_EQ(NO_ERROR, status);
ASSERT_TRUE(cdm_engine_.IsOpenSession(*session_id));
}
void CloseSession(const CdmSessionId& session_id) {
CdmResponseType status = cdm_engine_.CloseSession(session_id);
ASSERT_EQ(NO_ERROR, status);
ASSERT_FALSE(cdm_engine_.IsOpenSession(session_id));
}
void FetchLicense() {
video_widevine::WidevinePsshData pssh;
pssh.set_content_id(content_id_);
const std::string init_data_string = MakePSSH(pssh);
const InitializationData init_data(kCencMimeType, init_data_string);
init_data.DumpToLogs();
CdmKeyRequest key_request;
ASSERT_NO_FATAL_FAILURE(GenerateKeyRequest(init_data, &key_request));
ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request));
}
void GenerateKeyRequest(const InitializationData& init_data,
CdmKeyRequest* key_request) {
CdmAppParameterMap empty_app_parameters;
CdmKeySetId empty_key_set_id;
CdmResponseType result = cdm_engine_.GenerateKeyRequest(
session_id_, empty_key_set_id, init_data, license_type_,
empty_app_parameters, key_request);
ASSERT_EQ(KEY_MESSAGE, result);
ASSERT_EQ(kKeyRequestTypeInitial, key_request->type);
}
// Append the content/policy id for the test to the URL.
std::string MakeUrl(const std::string& server_url,
const std::string& policy_id) {
// For these tests, we want to specify the policy, but the UAT server only
// allows us to set the content id as the video_id. So each policy is
// matched to a single license with the same name. The local license
// server, on the other hand, wants to see the policy id in the url. So we
// have to guess which format to use based on the name of the server.
const std::string path = server_url + config_.client_auth();
std::string video_query;
if (path.find("proxy.uat") != std::string::npos) {
// This is uat or uat-nightly. Set the video_id.
video_query = "video_id=" + policy_id;
} else {
// This is probably a local license server. Set the policy.
video_query = "policy=" + policy_id;
}
// If there is already a parameter, then we don't need to add another
// question mark.
return path + ((path.find("?") == std::string::npos) ? "?" : "&") +
video_query;
}
void GetKeyResponse(const CdmKeyRequest& key_request) {
// The content id matches the policy id used on UAT.
const std::string url = MakeUrl(config_.license_server(), content_id_);
UrlRequest url_request(url);
ASSERT_TRUE(url_request.is_connected());
std::string http_response;
url_request.PostRequest(key_request.message);
ASSERT_TRUE(url_request.GetResponse(&http_response));
int status_code = url_request.GetStatusCode(http_response);
ASSERT_EQ(kHttpOk, status_code) << "Error with url = " << url;
LicenseRequest license_request;
license_request.GetDrmMessage(http_response, key_response_);
}
// This ensures that the licenses are loaded into the sessions.
void LoadLicense() {
if (!first_load_occurred_) {
CdmLicenseType license_type;
ASSERT_EQ(KEY_ADDED, cdm_engine_.AddKey(session_id_, key_response_,
&license_type, &key_set_id_));
ASSERT_EQ(license_type_, license_type);
license_holder_.LoadLicense();
first_load_occurred_ = true;
} else if (can_persist_) {
// For the persistent license, we use restore key.
ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id_));
CdmResponseType status = cdm_engine_.RestoreKey(session_id_, key_set_id_);
ASSERT_EQ(KEY_ADDED, status);
} else if (license_holder_.can_persist()) {
ASSERT_NO_FATAL_FAILURE(license_holder_.OpenSession());
license_holder_.ReloadLicense();
}
}
void UnloadLicense() {
if (can_persist_) {
CloseSession(session_id_);
if (license_holder_.can_persist()) {
license_holder_.CloseSession();
}
}
@@ -380,12 +276,8 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
void AllowLenience() { allow_lenience_ = true; }
void Decrypt() {
constexpr size_t buffer_size = 500;
const std::vector<uint8_t> input(buffer_size, 0);
std::vector<uint8_t> output(buffer_size, 0);
const std::vector<uint8_t> iv(KEY_IV_SIZE, 0);
const uint64_t now = CurrentRentalTime();
EXPECT_EQ(NO_ERROR, Decrypt(session_id_, kKeyId, input, iv, &output))
EXPECT_EQ(NO_ERROR, license_holder_.Decrypt(kKeyId))
<< "Failed to decrypt when rental clock = " << now
<< ", and playback clock = "
<< ((now < start_of_playback_) ? 0 : (now - start_of_playback_));
@@ -403,17 +295,12 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
const bool low_end_device =
(wvoec::global_features.api_version < wvoec::kCoreMessagesAPI ||
!wvoec::global_features.usage_table);
if (allow_lenience_ && low_end_device && can_persist_) {
if (allow_lenience_ && low_end_device && license_holder_.can_persist()) {
allow_success = true;
}
allow_lenience_ = false; // Only allow lenience once per test.
constexpr size_t buffer_size = 500;
const std::vector<uint8_t> input(buffer_size, 0);
std::vector<uint8_t> output(buffer_size, 0);
const std::vector<uint8_t> iv(KEY_IV_SIZE, 0);
const uint64_t now = CurrentRentalTime();
CdmResponseType status = Decrypt(session_id_, kKeyId, input, iv, &output);
CdmResponseType status = license_holder_.Decrypt(kKeyId);
// We always allow failure. that's what we usually expect.
if (status == NEED_KEY) return;
// No other error code is allowed: either NO_ERROR or NEED_KEY.
@@ -428,26 +315,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
<< (now < start_of_playback_ ? 0 : now - start_of_playback_);
}
CdmResponseType Decrypt(const CdmSessionId& session_id, const KeyId& key_id,
const std::vector<uint8_t>& input,
const std::vector<uint8_t>& iv,
std::vector<uint8_t>* output) {
CdmDecryptionParametersV16 params(key_id);
params.is_secure = false;
CdmDecryptionSample sample(input.data(), output->data(), 0, input.size(),
iv);
CdmDecryptionSubsample subsample(0, input.size());
sample.subsamples.push_back(subsample);
params.samples.push_back(sample);
return cdm_engine_.DecryptV16(session_id, params);
}
CdmSessionId session_id_;
std::string content_id_;
CdmLicenseType license_type_;
CdmKeySetId key_set_id_;
std::string key_response_;
LicenseHolder license_holder_;
// Time license requests generated. All test times are relative to this value.
uint64_t start_of_rental_clock_;
// The start of playback. This is set to the planned start at the beginning of
@@ -457,9 +325,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
// not have to reload the streaming license, and we should use RestoreKey
// instead of AddKey for the offline license.
bool first_load_occurred_;
bool can_persist_;
ODK_TimerLimits timer_limits_;
SimpleEventListener event_listener_;
// If this is set, then the next time we expect a playback to be terminated,
// we will allow lenient failure.
bool allow_lenience_;
@@ -751,7 +617,8 @@ TEST_P(CdmUseCase_SevenHardTwoSoft, Case4) {
LoadAndAllowPlayback(start_of_playback_,
start_of_playback_ + 4 * kPlayDuration);
UnloadLicense();
if (!can_persist_) return; // streaming license cannot restart.
// streaming license cannot restart.
if (!license_holder_.can_persist()) return;
AllowLenience();
ForbidPlayback(EndOfPlaybackWindow() + kFudge);
}
@@ -947,7 +814,8 @@ TEST_P(CdmUseCase_SevenSoftTwoSoft, Case4) {
LoadAndAllowPlayback(start_of_playback_,
start_of_playback_ + 4 * kPlayDuration);
UnloadLicense();
if (!can_persist_) return; // streaming license cannot restart.
// streaming license cannot restart.
if (!license_holder_.can_persist()) return;
AllowLenience();
ForbidPlayback(EndOfPlaybackWindow() + kFudge);
}
@@ -989,7 +857,7 @@ TEST_P(CdmUseCase_SevenSoftTwoSoft, Case7) {
EndOfPlaybackWindow() + kPlayDuration);
UnloadLicense();
// But forbid restart after playback window.
if (!can_persist_) return; // streaming license cannot restart.
if (!license_holder_.can_persist()) return;
ForbidPlayback(EndOfPlaybackWindow() + 2 * kPlayDuration);
}
@@ -1028,7 +896,7 @@ class RenewalTest : public CdmDurationTest {
// Sleep until a renewal request event is generated. If one does not occur
// before cutoff, an error happens.
void SleepUntilRenewalNeeded(uint64_t cutoff) {
while (!event_listener_.renewal_needed()) {
while (!license_holder_.event_listener().renewal_needed()) {
uint64_t now = CurrentRentalTime();
ASSERT_LT(now, cutoff);
SleepUntil(now + 1);
@@ -1042,34 +910,18 @@ class RenewalTest : public CdmDurationTest {
}
void RequestRenewal(const RenewalPolicy& renewal_policy) {
event_listener_.set_renewal_needed(false);
CdmKeyRequest request;
const CdmResponseType result =
cdm_engine_.GenerateRenewalRequest(session_id_, &request);
ASSERT_EQ(KEY_MESSAGE, result);
const std::string url =
MakeUrl(config_.renewal_server(), renewal_policy.policy_id);
renewal_in_flight_.reset(new UrlRequest(url));
ASSERT_TRUE(renewal_in_flight_->is_connected());
renewal_in_flight_->PostRequest(request.message);
license_holder_.GenerateAndPostRenewalRequest(renewal_policy.policy_id);
}
void LoadRenewal(uint64_t time_of_load, const RenewalPolicy& renewal_policy) {
ASSERT_NE(renewal_in_flight_, nullptr);
std::string http_response;
// Most of the network latency will probably show up in the next few
// commands. I think the tests have enough slop to account for reasonable
// latency with the current value of kRoundTripTime. But we'll know I made a
// mistake if we see errors about "Test Clock skew..." in the SleepUntil
// call below.
ASSERT_TRUE(renewal_in_flight_->GetResponse(&http_response));
int status_code = renewal_in_flight_->GetStatusCode(http_response);
ASSERT_EQ(kHttpOk, status_code);
license_holder_.FetchRenewal();
SleepUntil(time_of_load);
LicenseRequest license_request;
license_request.GetDrmMessage(http_response, renewal_message_);
EXPECT_EQ(KEY_ADDED, cdm_engine_.RenewKey(session_id_, renewal_message_));
license_holder_.LoadRenewal();
ComputeCutoff(time_of_load, renewal_policy);
}
@@ -1260,7 +1112,7 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case4) {
RenewAndContinue(start, load_time, stop, kShortRenewal);
}
UnloadLicense();
if (can_persist_) {
if (license_holder_.can_persist()) {
// Reload license after timer expired.
const uint64_t reload_time = current_cutoff_ + 20;
ReloadAndAllowPlayback(
@@ -1464,7 +1316,7 @@ TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case4) {
RenewAndContinue(start, load_time, stop, kShortRenewal);
}
UnloadLicense();
if (can_persist_) {
if (license_holder_.can_persist()) {
// Reload license after timer expired.
const uint64_t reload_time = current_cutoff_ + 20;
ReloadAndAllowPlayback(

View File

@@ -13,7 +13,7 @@
#include "cdm_engine.h"
#include "config_test_env.h"
#include "license_request.h"
#include "license_holder.h"
#include "log.h"
#include "oec_session_util.h"
#include "oemcrypto_session_tests_helper.h"
@@ -27,31 +27,35 @@
#include "wv_cdm_constants.h"
#include "wv_cdm_types.h"
using wvutil::a2b_hex;
namespace wvcdm {
class WvGenericCryptoTest : public WvCdmTestBaseWithEngine {
public:
WvGenericCryptoTest() : holder_(&cdm_engine_) {}
WvGenericCryptoTest() : holder_("CDM_GenericCrypto", &cdm_engine_, config_) {}
void SetUp() override {
WvCdmTestBase::SetUp();
EnsureProvisioned();
holder_.OpenSession(config_.key_system());
holder_.GenerateKeyRequest(binary_key_id(), ISO_BMFF_VIDEO_MIME_TYPE);
holder_.CreateDefaultLicense();
ASSERT_NO_FATAL_FAILURE(holder_.OpenSession());
ASSERT_NO_FATAL_FAILURE(holder_.FetchLicense());
ency_id_ = "ency";
StripeBuffer(&ency_key_, CONTENT_KEY_SIZE, 'e');
AddOneKey(ency_id_, ency_key_, wvoec::kControlAllowEncrypt);
dency_id_ = "dency";
StripeBuffer(&dency_key_, CONTENT_KEY_SIZE, 'd');
AddOneKey(dency_id_, dency_key_, wvoec::kControlAllowDecrypt);
siggy_id_ = "siggy";
StripeBuffer(&siggy_key_, MAC_KEY_SIZE, 's');
AddOneKey(siggy_id_, siggy_key_, wvoec::kControlAllowSign);
vou_id_ = "vou";
StripeBuffer(&vou_key_, MAC_KEY_SIZE, 'v');
AddOneKey(vou_id_, vou_key_, wvoec::kControlAllowVerify);
ency_id_ = "encrypt-key-----";
ency_key_ = a2b_hex("0102030405060708090a0b0c0d0e0f10");
dency_id_ = "decrypt-key-----";
dency_key_ = a2b_hex("AA02030405060708090a0b0c0d0e0f10");
siggy_id_ = "sign-key--------";
siggy_key_ = a2b_hex(
"BB02030405060708090a0b0c0d0e0f100102030405060708090a0b0c0d0e0f10");
vou_id_ = "verify-key------";
vou_key_ = a2b_hex(
"CC02030405060708090a0b0c0d0e0f100102030405060708090a0b0c0d0e0f10");
both_id_ = "enc-and-dec-key-";
both_key_ = a2b_hex("DD02030405060708090a0b0c0d0e0f10");
sign_and_verify_id_ = "sign-and-verify-";
sign_and_verify_key_ = a2b_hex(
"EE02030405060708090a0b0c0d0e0f100102030405060708090a0b0c0d0e0f10");
StripeBuffer(&in_vector_, CONTENT_KEY_SIZE * 15, '1');
in_buffer_ = std::string(in_vector_.begin(), in_vector_.end());
@@ -62,26 +66,22 @@ class WvGenericCryptoTest : public WvCdmTestBaseWithEngine {
void TearDown() override { holder_.CloseSession(); }
// Create a single key, and add it to the license.
void AddOneKey(const KeyId& key_id, const std::vector<uint8_t>& key_data,
uint32_t key_control_block) {
wvoec::KeyControlBlock block = {};
block.control_bits = htonl(key_control_block);
holder_.AddKey(key_id, key_data, block);
}
protected:
TestLicenseHolder holder_;
LicenseHolder holder_;
KeyId ency_id_;
KeyId dency_id_;
KeyId siggy_id_;
KeyId vou_id_;
KeyId both_id_;
KeyId sign_and_verify_id_;
std::vector<uint8_t> ency_key_;
std::vector<uint8_t> dency_key_;
std::vector<uint8_t> siggy_key_;
std::vector<uint8_t> vou_key_;
std::vector<uint8_t> both_key_;
std::vector<uint8_t> sign_and_verify_key_;
std::vector<uint8_t> in_vector_;
std::vector<uint8_t> iv_vector_;
@@ -89,210 +89,155 @@ class WvGenericCryptoTest : public WvCdmTestBaseWithEngine {
std::string iv_;
};
TEST_F(WvGenericCryptoTest, LoadSpecialKeys) { holder_.SignAndLoadLicense(); }
TEST_F(WvGenericCryptoTest, LoadSpecialKeys) {
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
}
TEST_F(WvGenericCryptoTest, GenericEncryptGood) {
CdmResponseType cdm_sts;
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
std::string encrypted = Aes128CbcEncrypt(ency_key_, in_vector_, iv_vector_);
std::string out_buffer;
holder_.SignAndLoadLicense();
cdm_sts = cdm_engine_.GenericEncrypt(
holder_.session_id(), in_buffer_, ency_id_, iv_,
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer);
EXPECT_EQ(NO_ERROR, cdm_sts);
EXPECT_EQ(NO_ERROR, cdm_engine_.GenericEncrypt(
holder_.session_id(), in_buffer_, ency_id_, iv_,
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer));
EXPECT_EQ(encrypted, out_buffer);
}
TEST_F(WvGenericCryptoTest, GenericEncryptNoKey) {
CdmResponseType cdm_sts;
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
std::string encrypted = Aes128CbcEncrypt(ency_key_, in_vector_, iv_vector_);
std::string out_buffer;
KeyId key_id("no_key");
holder_.SignAndLoadLicense();
cdm_sts = cdm_engine_.GenericEncrypt(
holder_.session_id(), in_buffer_, key_id, iv_,
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer);
EXPECT_NE(NO_ERROR, cdm_sts);
EXPECT_NE(NO_ERROR, cdm_engine_.GenericEncrypt(
holder_.session_id(), in_buffer_, key_id, iv_,
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer));
EXPECT_NE(encrypted, out_buffer);
}
TEST_F(WvGenericCryptoTest, GenericEncryptKeyNotAllowed) {
CdmResponseType cdm_sts;
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
// Trying to use Decrypt key to encrypt, which is not allowed.
KeyId key_id = dency_id_;
std::string encrypted = Aes128CbcEncrypt(dency_key_, in_vector_, iv_vector_);
std::string out_buffer;
holder_.SignAndLoadLicense();
cdm_sts = cdm_engine_.GenericEncrypt(
holder_.session_id(), in_buffer_, key_id, iv_,
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer);
EXPECT_EQ(UNKNOWN_ERROR, cdm_sts);
EXPECT_EQ(UNKNOWN_ERROR,
cdm_engine_.GenericEncrypt(
holder_.session_id(), in_buffer_, key_id, iv_,
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer));
EXPECT_NE(encrypted, out_buffer);
}
TEST_F(WvGenericCryptoTest, GenericDecryptGood) {
CdmResponseType cdm_sts;
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
std::string decrypted = Aes128CbcDecrypt(dency_key_, in_vector_, iv_vector_);
std::string out_buffer;
holder_.SignAndLoadLicense();
cdm_sts = cdm_engine_.GenericDecrypt(
holder_.session_id(), in_buffer_, dency_id_, iv_,
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer);
EXPECT_EQ(NO_ERROR, cdm_sts);
EXPECT_EQ(NO_ERROR, cdm_engine_.GenericDecrypt(
holder_.session_id(), in_buffer_, dency_id_, iv_,
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer));
EXPECT_EQ(decrypted, out_buffer);
}
TEST_F(WvGenericCryptoTest, GenericDecryptNoKey) {
CdmResponseType cdm_sts;
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
std::string decrypted = Aes128CbcDecrypt(dency_key_, in_vector_, iv_vector_);
std::string out_buffer;
KeyId key_id = "no_key";
holder_.SignAndLoadLicense();
cdm_sts = cdm_engine_.GenericDecrypt(
holder_.session_id(), in_buffer_, key_id, iv_,
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer);
EXPECT_NE(NO_ERROR, cdm_sts);
EXPECT_NE(NO_ERROR, cdm_engine_.GenericDecrypt(
holder_.session_id(), in_buffer_, key_id, iv_,
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer));
EXPECT_NE(decrypted, out_buffer);
}
TEST_F(WvGenericCryptoTest, GenericDecryptKeyNotAllowed) {
CdmResponseType cdm_sts;
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
// Trying to use Encrypt key to decrypt, which is not allowed.
KeyId key_id = ency_id_;
std::string decrypted = Aes128CbcDecrypt(ency_key_, in_vector_, iv_vector_);
std::string out_buffer;
holder_.SignAndLoadLicense();
cdm_sts = cdm_engine_.GenericDecrypt(
holder_.session_id(), in_buffer_, key_id, iv_,
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer);
EXPECT_EQ(UNKNOWN_ERROR, cdm_sts);
EXPECT_EQ(UNKNOWN_ERROR,
cdm_engine_.GenericDecrypt(
holder_.session_id(), in_buffer_, key_id, iv_,
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer));
EXPECT_NE(decrypted, out_buffer);
}
TEST_F(WvGenericCryptoTest, GenericSignGood) {
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
std::string out_buffer;
std::string signature = SignHMAC(in_buffer_, siggy_key_);
EXPECT_EQ(NO_ERROR, cdm_engine_.GenericSign(
holder_.session_id(), in_buffer_, siggy_id_,
wvcdm::kSigningAlgorithmHmacSha256, &out_buffer));
EXPECT_EQ(signature, out_buffer);
}
TEST_F(WvGenericCryptoTest, GenericSignKeyNotAllowed) {
CdmResponseType cdm_sts;
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
// Wrong key
std::string key_id = vou_id_;
std::string out_buffer;
std::string signature = SignHMAC(in_buffer_, siggy_key_);
holder_.SignAndLoadLicense();
cdm_sts =
EXPECT_EQ(
UNKNOWN_ERROR,
cdm_engine_.GenericSign(holder_.session_id(), in_buffer_, key_id,
wvcdm::kSigningAlgorithmHmacSha256, &out_buffer);
EXPECT_EQ(UNKNOWN_ERROR, cdm_sts);
wvcdm::kSigningAlgorithmHmacSha256, &out_buffer));
EXPECT_NE(signature, out_buffer);
}
TEST_F(WvGenericCryptoTest, GenericSignGood) {
CdmResponseType cdm_sts;
std::string out_buffer;
std::string signature = SignHMAC(in_buffer_, siggy_key_);
holder_.SignAndLoadLicense();
cdm_sts =
cdm_engine_.GenericSign(holder_.session_id(), in_buffer_, siggy_id_,
wvcdm::kSigningAlgorithmHmacSha256, &out_buffer);
EXPECT_EQ(NO_ERROR, cdm_sts);
EXPECT_EQ(signature, out_buffer);
TEST_F(WvGenericCryptoTest, GenericVerifyGood) {
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
std::string signature = SignHMAC(in_buffer_, vou_key_);
EXPECT_EQ(NO_ERROR, cdm_engine_.GenericVerify(
holder_.session_id(), in_buffer_, vou_id_,
wvcdm::kSigningAlgorithmHmacSha256, signature));
}
TEST_F(WvGenericCryptoTest, GenericVerifyKeyNotAllowed) {
CdmResponseType cdm_sts;
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
// Wrong key
std::string key_id = siggy_id_;
std::string signature = SignHMAC(in_buffer_, siggy_key_);
holder_.SignAndLoadLicense();
cdm_sts =
cdm_engine_.GenericVerify(holder_.session_id(), in_buffer_, key_id,
wvcdm::kSigningAlgorithmHmacSha256, signature);
EXPECT_EQ(UNKNOWN_ERROR, cdm_sts);
EXPECT_EQ(UNKNOWN_ERROR, cdm_engine_.GenericVerify(
holder_.session_id(), in_buffer_, key_id,
wvcdm::kSigningAlgorithmHmacSha256, signature));
}
TEST_F(WvGenericCryptoTest, GenericVerifyBadSignautre) {
CdmResponseType cdm_sts;
TEST_F(WvGenericCryptoTest, GenericVerifyBadSignature) {
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
std::string signature(MAC_KEY_SIZE, 's');
holder_.SignAndLoadLicense();
cdm_sts =
cdm_engine_.GenericVerify(holder_.session_id(), in_buffer_, vou_id_,
wvcdm::kSigningAlgorithmHmacSha256, signature);
// OEMCrypto error is OEMCrypto_ERROR_SIGNATURE_FAILURE
EXPECT_EQ(UNKNOWN_ERROR, cdm_sts);
}
TEST_F(WvGenericCryptoTest, GenericVerifyGood) {
CdmResponseType cdm_sts;
std::string signature = SignHMAC(in_buffer_, vou_key_);
holder_.SignAndLoadLicense();
cdm_sts =
cdm_engine_.GenericVerify(holder_.session_id(), in_buffer_, vou_id_,
wvcdm::kSigningAlgorithmHmacSha256, signature);
EXPECT_EQ(NO_ERROR, cdm_sts);
EXPECT_EQ(UNKNOWN_ERROR, cdm_engine_.GenericVerify(
holder_.session_id(), in_buffer_, vou_id_,
wvcdm::kSigningAlgorithmHmacSha256, signature));
}
TEST_F(WvGenericCryptoTest, GenericEncryptDecrypt) {
CdmResponseType cdm_sts;
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
std::string encrypted = Aes128CbcEncrypt(both_key_, in_vector_, iv_vector_);
std::string out_buffer;
std::string clear_buffer;
EXPECT_EQ(NO_ERROR, cdm_engine_.GenericEncrypt(
holder_.session_id(), in_buffer_, both_id_, iv_,
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer));
EXPECT_EQ(encrypted, out_buffer);
KeyId key_id = "enc and dec";
std::vector<uint8_t> key_data;
StripeBuffer(&key_data, CONTENT_KEY_SIZE, '3');
AddOneKey(key_id, key_data,
wvoec::kControlAllowEncrypt | wvoec::kControlAllowDecrypt);
holder_.SignAndLoadLicense();
cdm_sts = cdm_engine_.GenericEncrypt(
holder_.session_id(), in_buffer_, key_id, iv_,
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer);
EXPECT_EQ(NO_ERROR, cdm_sts);
cdm_sts = cdm_engine_.GenericDecrypt(
holder_.session_id(), out_buffer, key_id, iv_,
wvcdm::kEncryptionAlgorithmAesCbc128, &clear_buffer);
EXPECT_EQ(NO_ERROR, cdm_sts);
EXPECT_EQ(in_buffer_, clear_buffer);
std::string decrypted = Aes128CbcDecrypt(dency_key_, in_vector_, iv_vector_);
EXPECT_EQ(NO_ERROR, cdm_engine_.GenericDecrypt(
holder_.session_id(), encrypted, both_id_, iv_,
wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer));
EXPECT_EQ(in_buffer_, out_buffer);
}
TEST_F(WvGenericCryptoTest, GenericSignVerify) {
CdmResponseType cdm_sts;
std::string signature_buffer;
ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense());
KeyId key_id = "sign and ver";
std::vector<uint8_t> key_data;
StripeBuffer(&key_data, MAC_KEY_SIZE, '4');
AddOneKey(key_id, key_data,
wvoec::kControlAllowSign | wvoec::kControlAllowVerify);
std::string signature = SignHMAC(in_buffer_, key_data);
holder_.SignAndLoadLicense();
cdm_sts = cdm_engine_.GenericSign(holder_.session_id(), in_buffer_, key_id,
wvcdm::kSigningAlgorithmHmacSha256,
&signature_buffer);
EXPECT_EQ(NO_ERROR, cdm_sts);
EXPECT_EQ(MAC_KEY_SIZE, signature_buffer.size());
cdm_sts = cdm_engine_.GenericVerify(holder_.session_id(), in_buffer_, key_id,
wvcdm::kSigningAlgorithmHmacSha256,
signature_buffer);
EXPECT_EQ(NO_ERROR, cdm_sts);
EXPECT_EQ(signature, signature_buffer);
std::string out_buffer;
std::string signature = SignHMAC(in_buffer_, sign_and_verify_key_);
EXPECT_EQ(NO_ERROR, cdm_engine_.GenericSign(
holder_.session_id(), in_buffer_, sign_and_verify_id_,
wvcdm::kSigningAlgorithmHmacSha256, &out_buffer));
EXPECT_EQ(signature, out_buffer);
EXPECT_EQ(NO_ERROR, cdm_engine_.GenericVerify(
holder_.session_id(), in_buffer_, sign_and_verify_id_,
wvcdm::kSigningAlgorithmHmacSha256, signature));
}
} // namespace wvcdm

View File

@@ -0,0 +1,227 @@
// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include "license_holder.h"
#include "license_request.h"
#include "oec_device_features.h"
#include "test_base.h"
namespace wvcdm {
namespace {
constexpr int kHttpOk = 200;
const std::string kCencMimeType = "cenc";
} // namespace
LicenseHolder::~LicenseHolder() {}
void LicenseHolder::OpenSession() {
CdmResponseType status = cdm_engine_->OpenSession(
config_.key_system(), nullptr, &event_listener_, &session_id_);
ASSERT_EQ(NO_ERROR, status);
ASSERT_TRUE(cdm_engine_->IsOpenSession(session_id_));
}
void LicenseHolder::CloseSession() {
CdmResponseType status = cdm_engine_->CloseSession(session_id_);
ASSERT_EQ(NO_ERROR, status);
ASSERT_FALSE(cdm_engine_->IsOpenSession(session_id_));
}
void LicenseHolder::FetchLicense() {
video_widevine::WidevinePsshData pssh;
pssh.set_content_id(content_id_);
const std::string init_data_string = MakePSSH(pssh);
const InitializationData init_data(kCencMimeType, init_data_string);
init_data.DumpToLogs();
CdmKeyRequest key_request;
ASSERT_NO_FATAL_FAILURE(GenerateKeyRequest(init_data, &key_request))
<< "Failed for " << content_id();
ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request))
<< "Failed for " << content_id();
}
void LicenseHolder::LoadLicense() {
CdmLicenseType license_type;
ASSERT_EQ(KEY_ADDED, cdm_engine_->AddKey(session_id_, key_response_,
&license_type, &key_set_id_))
<< "Failed to load license for " << content_id();
if (can_persist_) {
ASSERT_EQ(license_type, kLicenseTypeOffline)
<< "Failed for " << content_id();
} else {
ASSERT_EQ(license_type, kLicenseTypeStreaming)
<< "Failed for " << content_id();
}
}
void LicenseHolder::ReloadLicense() {
CdmResponseType status = cdm_engine_->RestoreKey(session_id_, key_set_id_);
ASSERT_EQ(KEY_ADDED, status)
<< "Failed to reload license for " << content_id();
}
void LicenseHolder::GenerateAndPostRenewalRequest(
const std::string& policy_id) {
event_listener_.set_renewal_needed(false);
CdmKeyRequest request;
const CdmResponseType result =
cdm_engine_->GenerateRenewalRequest(session_id_, &request);
ASSERT_EQ(KEY_MESSAGE, result) << "Failed for " << content_id();
const std::string url = MakeUrl(config_.renewal_server(), policy_id);
renewal_in_flight_.reset(new UrlRequest(url));
ASSERT_TRUE(renewal_in_flight_->is_connected())
<< "Failed for " << content_id();
renewal_in_flight_->PostRequest(request.message);
}
void LicenseHolder::FetchRenewal() {
ASSERT_NE(renewal_in_flight_, nullptr) << "Failed for " << content_id();
ASSERT_TRUE(renewal_in_flight_->GetResponse(&renewal_response_))
<< "Failed for " << content_id();
int status_code = renewal_in_flight_->GetStatusCode(renewal_response_);
ASSERT_EQ(kHttpOk, status_code) << "Failed for " << content_id();
}
void LicenseHolder::LoadRenewal() {
LicenseRequest license_request;
license_request.GetDrmMessage(renewal_response_, renewal_message_);
EXPECT_EQ(KEY_ADDED, cdm_engine_->RenewKey(session_id_, renewal_message_))
<< "Failed for " << content_id();
}
void LicenseHolder::RemoveLicense() {
EXPECT_EQ(NO_ERROR, cdm_engine_->RemoveLicense(session_id_))
<< "Failed for " << content_id();
}
CdmResponseType LicenseHolder::Decrypt(const std::string& key_id) {
constexpr size_t buffer_size = 500;
const std::vector<uint8_t> input(buffer_size, 0);
std::vector<uint8_t> output(buffer_size, 0);
const std::vector<uint8_t> iv(KEY_IV_SIZE, 0);
CdmDecryptionParametersV16 params(key_id);
params.is_secure = false;
CdmDecryptionSample sample(input.data(), output.data(), 0, input.size(), iv);
CdmDecryptionSubsample subsample(0, input.size());
sample.subsamples.push_back(subsample);
params.samples.push_back(sample);
return cdm_engine_->DecryptV16(session_id_, params);
}
void LicenseHolder::DecryptSecure(const KeyId& key_id) {
ASSERT_TRUE(wvoec::global_features.test_secure_buffers);
constexpr size_t buffer_size = 500;
const std::vector<uint8_t> input(buffer_size, 0);
const std::vector<uint8_t> iv(KEY_IV_SIZE, 0);
// To create a secure buffer, we need to know the OEMCrypto session id.
CdmQueryMap query_map;
cdm_engine_->QueryOemCryptoSessionId(session_id_, &query_map);
const std::string oec_session_id_string =
query_map[QUERY_KEY_OEMCRYPTO_SESSION_ID];
uint32_t oec_session_id = std::stoi(oec_session_id_string);
int secure_buffer_fid;
OEMCrypto_DestBufferDesc output_descriptor;
output_descriptor.type = OEMCrypto_BufferType_Secure;
output_descriptor.buffer.secure.secure_buffer_length = buffer_size;
ASSERT_EQ(
OEMCrypto_AllocateSecureBuffer(oec_session_id, buffer_size,
&output_descriptor, &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, buffer_size);
output_descriptor.buffer.secure.offset = 0;
// Now create a sample array for the CDM layer.
CdmDecryptionParametersV16 params(key_id);
params.is_secure = true;
CdmDecryptionSample sample(input.data(),
output_descriptor.buffer.secure.secure_buffer, 0,
input.size(), iv);
CdmDecryptionSubsample subsample(0, input.size());
sample.subsamples.push_back(subsample);
params.samples.push_back(sample);
CdmResponseType status = cdm_engine_->DecryptV16(session_id_, params);
// Free the secure buffer before we check the return status.
EXPECT_EQ(OEMCrypto_FreeSecureBuffer(oec_session_id, &output_descriptor,
secure_buffer_fid),
OEMCrypto_SUCCESS);
ASSERT_EQ(status, NO_ERROR);
}
void LicenseHolder::FailDecrypt(const KeyId& key_id,
CdmResponseType expected_status) {
CdmResponseType status = Decrypt(key_id);
// If the server knows we cannot handle the key, it would not have given us
// the key. In that case, the status should indicate no key.
if (status != NEED_KEY) {
// Otherwise, we should have gotten the expected error.
ASSERT_EQ(expected_status, status);
}
}
void LicenseHolder::GenerateKeyRequest(const InitializationData& init_data,
CdmKeyRequest* key_request) {
CdmAppParameterMap empty_app_parameters;
CdmKeySetId empty_key_set_id;
CdmLicenseType license_type =
can_persist_ ? kLicenseTypeOffline : kLicenseTypeStreaming;
CdmResponseType result = cdm_engine_->GenerateKeyRequest(
session_id_, empty_key_set_id, init_data, license_type,
empty_app_parameters, key_request);
ASSERT_EQ(KEY_MESSAGE, result);
ASSERT_EQ(kKeyRequestTypeInitial, key_request->type);
}
std::string LicenseHolder::MakeUrl(const std::string& server_url,
const std::string& policy_id) {
// For tests, we want to specify the policy, but the UAT server only allows us
// to set the content id as the video_id. So each policy is matched to a
// single license with the same name. The local license server, on the other
// hand, wants to see the policy id in the url. So we have to guess which
// format to use based on the name of the server.
const std::string path = server_url + config_.client_auth();
std::string video_query;
if (!policy_id.empty()) {
if (path.find("proxy.uat") != std::string::npos) {
// This is uat or uat-nightly. Set the video_id.
video_query = "video_id=" + policy_id;
} else {
// This is probably a local license server. Set the policy.
video_query = "policy=" + policy_id;
}
// If there is already a parameter, then we don't need to add another
// question mark.
return path + ((path.find("?") == std::string::npos) ? "?" : "&") +
video_query;
} else {
return path;
}
}
void LicenseHolder::GetKeyResponse(const CdmKeyRequest& key_request) {
// The content id matches the policy id used on UAT.
const std::string url = MakeUrl(config_.license_server(), "");
UrlRequest url_request(url);
ASSERT_TRUE(url_request.is_connected());
std::string http_response;
url_request.PostRequest(key_request.message);
ASSERT_TRUE(url_request.GetResponse(&http_response));
int status_code = url_request.GetStatusCode(http_response);
ASSERT_EQ(kHttpOk, status_code) << "Error with url = " << url << "\n"
<< "content_id = " << content_id() << "\n"
<< "response = " << http_response;
LicenseRequest license_request;
license_request.GetDrmMessage(http_response, key_response_);
}
} // namespace wvcdm

View File

@@ -0,0 +1,133 @@
// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#ifndef WVCDM_CORE_TEST_LICENSE_HOLDER_H_
#define WVCDM_CORE_TEST_LICENSE_HOLDER_H_
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include "cdm_engine.h"
#include "config_test_env.h"
#include "url_request.h"
#include "wv_attributes.h"
#include "wv_cdm_event_listener.h"
#include "log.h"
namespace wvcdm {
// An event listener to tell when a renewal is needed.
class SimpleEventListener : public wvcdm::WvCdmEventListener {
public:
SimpleEventListener() { renewal_needed_ = false; }
// We will want to know when a renewal is needed.
void OnSessionRenewalNeeded(const CdmSessionId& /*session*/) override {
renewal_needed_ = true;
}
void OnSessionKeysChange(const CdmSessionId&, const CdmKeyStatusMap&,
bool) override {}
void OnExpirationUpdate(const CdmSessionId&,
int64_t expiry_time UNUSED) override {}
bool renewal_needed() { return renewal_needed_; }
void set_renewal_needed(bool renewal_needed) {
renewal_needed_ = renewal_needed;
}
private:
bool renewal_needed_ = false;
};
class LicenseHolder {
public:
// Set up a new license holder with the specified content id.
LicenseHolder(const std::string& content_id, CdmEngine* cdm_engine,
const ConfigTestEnv& config)
: content_id_(content_id), cdm_engine_(cdm_engine), config_(config) {}
~LicenseHolder();
// Open a session for the license. Must be called before FetchLicense or
// ReloadLicense.
void OpenSession();
// Close the session and release resources.
void CloseSession();
// Generate a license request, send it to the license server, and wait for the
// response. The response is *not* loaded into the cdm engine.
void FetchLicense();
// Load the license response into the CDM engine. A call to FetchLicense()
// must be made first.
void LoadLicense();
// Reload the license. Call OpenSession() before calling
// ReloadLicense(). Also, the key_set_id must have been set previously. The
// key_set_id is set by calling LoadLicense(), or by calling set_key_set_id().
void ReloadLicense();
// Generate the renewal request, and send it to the server.
void GenerateAndPostRenewalRequest(const std::string& policy_id);
// Fetch the renewal response. This can add a few seconds of latency.
void FetchRenewal();
// Load the renewal response that was fetched in FetchRenewal().
void LoadRenewal();
// Releases the license and frees up entry in usage table.
void RemoveLicense();
// Try to decrypt some random data. This does not verify that the data is
// decrypted correctly. Returns the result of the decrypt operation.
CdmResponseType Decrypt(const std::string& key_id);
// Try to decrypt some random data to a secure buffer. If the test harness
// does not allow creating a secure buffer, then this function fails
// immediately. Otherwise, a secure buffer is created and used for a
// decryption operation.
void DecryptSecure(const KeyId& key_id);
// Try to decrypt some random data, but expect failure. The failure may
// be either the expected_status, or NEED_KEY. We allow NEED_KEY in case
// the server recognized that we cannot support the given key.
void FailDecrypt(const KeyId& key_id, CdmResponseType expected_status);
const std::string& content_id() const { return content_id_; }
void set_content_id(const std::string& content_id) {
content_id_ = content_id;
}
// The session id. This is only valid after a call to OpenSession.
const std::string& session_id() { return session_id_; }
// Returns true if the license is offline.
bool can_persist() const { return can_persist_; }
// Sets whether the license is offline or not.
void set_can_persist(bool can_persist) { can_persist_ = can_persist; }
uint64_t start_of_rental_clock() const { return start_of_rental_clock_; }
const std::string& key_set_id() const { return key_set_id_; }
void set_key_set_id(const std::string& key_set_id) {
key_set_id_ = key_set_id;
}
SimpleEventListener& event_listener() { return event_listener_; }
private:
std::string content_id_;
CdmSessionId session_id_;
CdmKeySetId key_set_id_;
std::string key_response_;
bool can_persist_ = false;
uint64_t start_of_rental_clock_ = 0u;
CdmEngine* cdm_engine_ = nullptr;
const ConfigTestEnv& config_;
SimpleEventListener event_listener_;
std::unique_ptr<UrlRequest> renewal_in_flight_;
std::string renewal_message_;
std::string renewal_response_;
// Generate the license request.
void GenerateKeyRequest(const InitializationData& init_data,
CdmKeyRequest* key_request);
// Generate a URL for the specified policy. The license request should be sent
// to this url.
std::string MakeUrl(const std::string& server_url,
const std::string& policy_id);
// Fetch the key response from the server.
void GetKeyResponse(const CdmKeyRequest& key_request);
};
} // namespace wvcdm
#endif // WVCDM_CORE_TEST_LICENSE_HOLDER_H_

View File

@@ -13,27 +13,15 @@
#include <gtest/gtest.h>
#include "cdm_engine.h"
#include "clock.h"
#include "config_test_env.h"
#include "initialization_data.h"
#include "license_request.h"
#include "license_holder.h"
#include "log.h"
#include "metrics_collections.h"
#include "oec_device_features.h"
#include "test_base.h"
#include "test_printers.h"
#include "test_sleep.h"
#include "url_request.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_types.h"
namespace wvcdm {
namespace {
constexpr int kHttpOk = 200;
const std::string kCencMimeType = "cenc";
} // namespace
// Core Policy Integration Test
class CorePIGTest : public WvCdmTestBaseWithEngine {
protected:
@@ -41,210 +29,44 @@ class CorePIGTest : public WvCdmTestBaseWithEngine {
WvCdmTestBase::SetUp();
EnsureProvisioned();
}
void OpenSession(CdmSessionId* session_id) {
CdmResponseType status = cdm_engine_.OpenSession(
config_.key_system(), nullptr, nullptr, session_id);
ASSERT_EQ(NO_ERROR, status);
ASSERT_TRUE(cdm_engine_.IsOpenSession(*session_id));
}
void CloseSession(const CdmSessionId& session_id) {
CdmResponseType status = cdm_engine_.CloseSession(session_id);
ASSERT_EQ(NO_ERROR, status);
ASSERT_FALSE(cdm_engine_.IsOpenSession(session_id));
}
// Create a license request for the given content_id and requesting the
// specified license_type.
void GenerateKeyRequest(const CdmSessionId& session_id,
const std::string& content_id,
CdmKeyRequest* key_request,
CdmLicenseType license_type) {
video_widevine::WidevinePsshData pssh;
pssh.set_content_id(content_id);
const std::string init_data_string = MakePSSH(pssh);
const InitializationData init_data(kCencMimeType, init_data_string);
init_data.DumpToLogs();
CdmAppParameterMap empty_app_parameters;
CdmKeySetId empty_key_set_id;
CdmResponseType result = cdm_engine_.GenerateKeyRequest(
session_id, empty_key_set_id, init_data, license_type,
empty_app_parameters, key_request);
ASSERT_EQ(KEY_MESSAGE, result);
ASSERT_EQ(kKeyRequestTypeInitial, key_request->type);
}
// Send the request to the server and get the response.
void GetKeyResponse(const CdmKeyRequest& key_request,
std::string* key_response) {
const std::string url = config_.license_server() + config_.client_auth();
UrlRequest url_request(url);
ASSERT_TRUE(url_request.is_connected());
std::string http_response;
url_request.PostRequest(key_request.message);
ASSERT_TRUE(url_request.GetResponse(&http_response));
int status_code = url_request.GetStatusCode(http_response);
ASSERT_EQ(kHttpOk, status_code);
LicenseRequest license_request;
license_request.GetDrmMessage(http_response, *key_response);
}
// Load the license response into the specified session. Verify it has the
// correct license type (either streaming or offline).
void AddKey(const CdmSessionId& session_id, const std::string& key_response,
CdmLicenseType expected_license_type, CdmKeySetId* key_set_id) {
CdmLicenseType license_type;
CdmResponseType status =
cdm_engine_.AddKey(session_id, key_response, &license_type, key_set_id);
ASSERT_EQ(KEY_ADDED, status);
ASSERT_EQ(expected_license_type, license_type);
}
// Reload the license response into the specified session.
void RestoreKey(const CdmSessionId& session_id,
const CdmKeySetId& key_set_id) {
CdmResponseType status = cdm_engine_.RestoreKey(session_id, key_set_id);
ASSERT_EQ(KEY_ADDED, status);
}
// Use the key to decrypt.
void Decrypt(const CdmSessionId& session_id, const KeyId& key_id) {
constexpr size_t buffer_size = 500;
const std::vector<uint8_t> input(buffer_size, 0);
std::vector<uint8_t> output(buffer_size, 0);
const std::vector<uint8_t> iv(KEY_IV_SIZE, 0);
ASSERT_EQ(NO_ERROR, Decrypt(session_id, key_id, input, iv, &output));
}
// Try to use the key to decrypt, but expect the key has expired.
void FailDecrypt(const CdmSessionId& session_id, const KeyId& key_id,
CdmResponseType expected_status) {
constexpr size_t buffer_size = 500;
const std::vector<uint8_t> input(buffer_size, 0);
std::vector<uint8_t> output(buffer_size, 0);
const std::vector<uint8_t> iv(KEY_IV_SIZE, 0);
CdmResponseType status = Decrypt(session_id, key_id, input, iv, &output);
// If the server knows we cannot handle the key, it would not have given us
// the key. In that case, the status should indicate no key.
if (status != NEED_KEY) {
// Otherwise, we should have gotten the expected error.
ASSERT_EQ(expected_status, status);
}
}
// Decrypt or fail to decrypt, with the expected status.
CdmResponseType Decrypt(const CdmSessionId& session_id, const KeyId& key_id,
const std::vector<uint8_t>& input,
const std::vector<uint8_t>& iv,
std::vector<uint8_t>* output) {
CdmDecryptionParametersV16 params(key_id);
params.is_secure = false;
CdmDecryptionSample sample(input.data(), output->data(), 0, input.size(),
iv);
CdmDecryptionSubsample subsample(0, input.size());
sample.subsamples.push_back(subsample);
params.samples.push_back(sample);
return cdm_engine_.DecryptV16(session_id, params);
}
// Use the key to decrypt to a secure buffer.
void DecryptSecure(const CdmSessionId& session_id, const KeyId& key_id) {
ASSERT_TRUE(wvoec::global_features.test_secure_buffers);
constexpr size_t buffer_size = 500;
const std::vector<uint8_t> input(buffer_size, 0);
const std::vector<uint8_t> iv(KEY_IV_SIZE, 0);
// To create a secure buffer, we need to know the OEMCrypto session id.
CdmQueryMap query_map;
cdm_engine_.QueryOemCryptoSessionId(session_id, &query_map);
const std::string oec_session_id_string =
query_map[QUERY_KEY_OEMCRYPTO_SESSION_ID];
uint32_t oec_session_id = std::stoi(oec_session_id_string);
int secure_buffer_fid;
OEMCrypto_DestBufferDesc output_descriptor;
output_descriptor.type = OEMCrypto_BufferType_Secure;
output_descriptor.buffer.secure.secure_buffer_length = buffer_size;
ASSERT_EQ(
OEMCrypto_AllocateSecureBuffer(oec_session_id, buffer_size,
&output_descriptor, &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,
buffer_size);
output_descriptor.buffer.secure.offset = 0;
// Now create a sample array for the CDM layer.
CdmDecryptionParametersV16 params(key_id);
params.is_secure = true;
CdmDecryptionSample sample(input.data(),
output_descriptor.buffer.secure.secure_buffer, 0,
input.size(), iv);
CdmDecryptionSubsample subsample(0, input.size());
sample.subsamples.push_back(subsample);
params.samples.push_back(sample);
CdmResponseType status = cdm_engine_.DecryptV16(session_id, params);
// Free the secure buffer before we check the return status.
OEMCrypto_FreeSecureBuffer(oec_session_id, &output_descriptor,
secure_buffer_fid);
ASSERT_EQ(status, NO_ERROR);
}
};
// An offline license with nonce not required.
TEST_F(CorePIGTest, OfflineNoNonce) {
const std::string content_id = "2015_tears";
LicenseHolder holder("CDM_OfflineNoNonce", &cdm_engine_, config_);
holder.set_can_persist(true);
const KeyId key_id = "0000000000000000";
const CdmLicenseType license_type = kLicenseTypeOffline;
CdmSessionId session_id;
ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id));
CdmKeyRequest key_request;
ASSERT_NO_FATAL_FAILURE(
GenerateKeyRequest(session_id, content_id, &key_request, license_type));
std::string key_response;
ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request, &key_response));
CdmKeySetId key_set_id;
ASSERT_NO_FATAL_FAILURE(
AddKey(session_id, key_response, license_type, &key_set_id));
ASSERT_NO_FATAL_FAILURE(Decrypt(session_id, key_id));
ASSERT_NO_FATAL_FAILURE(CloseSession(session_id));
ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id));
ASSERT_NO_FATAL_FAILURE(RestoreKey(session_id, key_set_id));
ASSERT_NO_FATAL_FAILURE(Decrypt(session_id, key_id));
ASSERT_NO_FATAL_FAILURE(CloseSession(session_id));
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
ASSERT_NO_FATAL_FAILURE(holder.FetchLicense());
ASSERT_NO_FATAL_FAILURE(holder.LoadLicense());
EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id));
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
// Should be able to close the previous session, open a new session,
// and reload the license.
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
ASSERT_NO_FATAL_FAILURE(holder.ReloadLicense());
EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id));
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
}
// An offline license with nonce and provider session token.
TEST_F(CorePIGTest, OfflineWithPST) {
const std::string content_id = "offline_clip2";
const KeyId key_id =
"\x32\x60\xF3\x9E\x12\xCC\xF6\x53\x52\x99\x90\x16\x8A\x35\x83\xFF";
const CdmLicenseType license_type = kLicenseTypeOffline;
CdmSessionId session_id;
ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id));
CdmKeyRequest key_request;
ASSERT_NO_FATAL_FAILURE(
GenerateKeyRequest(session_id, content_id, &key_request, license_type));
std::string key_response;
ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request, &key_response));
CdmKeySetId key_set_id;
ASSERT_NO_FATAL_FAILURE(
AddKey(session_id, key_response, license_type, &key_set_id));
ASSERT_NO_FATAL_FAILURE(Decrypt(session_id, key_id));
ASSERT_NO_FATAL_FAILURE(CloseSession(session_id));
ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id));
ASSERT_NO_FATAL_FAILURE(RestoreKey(session_id, key_set_id));
ASSERT_NO_FATAL_FAILURE(Decrypt(session_id, key_id));
ASSERT_NO_FATAL_FAILURE(CloseSession(session_id));
LicenseHolder holder("CDM_OfflineWithPST", &cdm_engine_, config_);
holder.set_can_persist(true);
const KeyId key_id = "0000000000000000";
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
ASSERT_NO_FATAL_FAILURE(holder.FetchLicense());
ASSERT_NO_FATAL_FAILURE(holder.LoadLicense());
EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id));
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
// Should be able to close the previous session, open a new session,
// and reload the license.
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
ASSERT_NO_FATAL_FAILURE(holder.ReloadLicense());
EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id));
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
}
// This test verifies that the system can download and install license with a
@@ -252,31 +74,39 @@ TEST_F(CorePIGTest, OfflineWithPST) {
// a non-secure buffer using this key, but that we can decrypt to a secure
// buffer, if the test harness supports secure buffers.
TEST_F(CorePIGTest, OfflineHWSecureRequired) {
const std::string content_id = "GTS_HW_SECURE_ALL";
const KeyId key_id = "0000000000000002";
LicenseHolder holder("CDM_OfflineHWSecureRequired", &cdm_engine_, config_);
holder.set_can_persist(true);
const KeyId sw_key_id = "0000000000000000";
const KeyId hw_key_id = "0000000000000001";
const CdmLicenseType license_type = kLicenseTypeOffline;
CdmSessionId session_id;
ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id));
CdmKeyRequest key_request;
ASSERT_NO_FATAL_FAILURE(
GenerateKeyRequest(session_id, content_id, &key_request, license_type));
std::string key_response;
ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request, &key_response));
CdmKeySetId key_set_id;
ASSERT_NO_FATAL_FAILURE(
AddKey(session_id, key_response, license_type, &key_set_id));
// First we try to decrypt to a non-secure buffer and verify that it fails.
// TODO(b/164517875): This error code should be something actionable.
ASSERT_NO_FATAL_FAILURE(FailDecrypt(session_id, key_id, DECRYPT_ERROR));
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
ASSERT_NO_FATAL_FAILURE(holder.FetchLicense());
ASSERT_NO_FATAL_FAILURE(holder.LoadLicense());
EXPECT_EQ(NO_ERROR, holder.Decrypt(sw_key_id));
ASSERT_NO_FATAL_FAILURE(holder.FailDecrypt(hw_key_id, DECRYPT_ERROR));
// Next, if possible, we try to decrypt to a secure buffer, and verify
// success.
if (wvoec::global_features.test_secure_buffers) {
ASSERT_NO_FATAL_FAILURE(DecryptSecure(session_id, key_id));
ASSERT_NO_FATAL_FAILURE(holder.DecryptSecure(hw_key_id));
} else {
LOGI("Test harness cannot create secure buffers. test skipped.");
}
ASSERT_NO_FATAL_FAILURE(CloseSession(session_id));
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
// Should be able to close the previous session, open a new session,
// and reload the license.
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
ASSERT_NO_FATAL_FAILURE(holder.ReloadLicense());
EXPECT_EQ(NO_ERROR, holder.Decrypt(sw_key_id));
ASSERT_NO_FATAL_FAILURE(holder.FailDecrypt(hw_key_id, DECRYPT_ERROR));
// Next, if possible, we try to decrypt to a secure buffer, and verify
// success.
if (wvoec::global_features.test_secure_buffers) {
ASSERT_NO_FATAL_FAILURE(holder.DecryptSecure(hw_key_id));
} else {
LOGI("Test harness cannot create secure buffers. test skipped.");
}
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
}
} // namespace wvcdm

View File

@@ -10,16 +10,23 @@
#include <sstream>
#include "license_holder.h"
#include "log.h"
#include "test_sleep.h"
using wvutil::a2b_hex;
using wvutil::b2a_hex;
using wvutil::FileSystem;
using wvutil::TestSleep;
using wvutil::unlimited_b2a_hex;
namespace wvcdm {
FileSystem* RebootTest::file_system_;
namespace {
// How much fudge or round off error do we allow in license durations for reboot
// tests.
constexpr int64_t kFudge = 10;
// We will encode a value string by wrapping it in braces, or as hex.
// If the string is not printable, or if it has unmatched braces, then we use
// hex. Otherwise, we surround the whole string with braces.
@@ -33,11 +40,11 @@ std::string EncodeString(const std::string& data) {
// If there are any unprintable characters, except whitespace, or if we
// close a brace before we open it, then just use hex.
if (!printable || braces_count < 0) {
return "0x" + b2a_hex(data) + ",";
return "0x" + unlimited_b2a_hex(data) + ",";
}
}
// If we left any braces open, then use hex.
if (braces_count != 0) return "0x" + b2a_hex(data) + ",";
if (braces_count != 0) return "0x" + unlimited_b2a_hex(data) + ",";
return "{" + data + "},";
}
@@ -51,12 +58,12 @@ std::string EncodeKey(const std::string& data) {
}
// When decoding, we assume that a key starting with "0x" is in hex. So we
// can't have any keys that start with "0x".
if (data.substr(0, 2) == "0x") return "0x" + b2a_hex(data) + ":";
if (data.substr(0, 2) == "0x") return "0x" + unlimited_b2a_hex(data) + ":";
// If the key is just is not printable, or if it has unmatched braces, then
// we use hex. Otherwise, we surround the whole string with braces.
for (size_t i = 0; i < data.length(); i++) {
if (!isprint(data[i]) || (data[i] == ':')) {
return "0x" + b2a_hex(data) + ":";
return "0x" + unlimited_b2a_hex(data) + ":";
}
}
return data + ":";
@@ -227,6 +234,23 @@ void RebootTest::TearDown() {
WvCdmTestBase::TearDown();
}
int64_t RebootTest::LoadTime(const std::string& key) {
int64_t value = 0;
std::istringstream input(persistent_data_[key]);
input >> value;
if (input.fail()) {
LOGE("Could not parse time '%s'", persistent_data_[key].c_str());
}
if (!input.eof()) {
LOGE("Extra text at end of time '%s'", persistent_data_[key].c_str());
}
return value;
}
void RebootTest::SaveTime(const std::string& key, int64_t time) {
persistent_data_[key] = std::to_string(time);
}
/** Test the dump and restore functions above. This does not test CDM
functionality. */
TEST_F(RebootTest, TestDumpUtil) {
@@ -247,6 +271,10 @@ TEST_F(RebootTest, TestDumpUtil) {
// This key looks like it might be hex. It should show up as hex in the
// save file.
map1["0x_bad_key_00"] = "value is ok";
std::string big_string = "start with something {binary";
// Double big_string 8 times, i.e. times 256, so it's bigger than 2k:
for (int i = 0; i < 8; i++) big_string = big_string + big_string;
map1["big_file"] = big_string;
const std::string dump2 = DumpData(map1);
std::map<std::string, std::string> map3;
EXPECT_TRUE(ParseDump(dump2, &map3));
@@ -273,4 +301,429 @@ TEST_F(RebootTest, FilesArePersistent) {
}
}
/** Verify that the clock moves forward over a reboot. */
TEST_F(RebootTest, TimeMovesForward) {
wvutil::TestSleep::Sleep(2);
const int64_t start = wvutil::Clock().GetCurrentTime();
wvutil::TestSleep::Sleep(2);
const int64_t end = wvutil::Clock().GetCurrentTime();
EXPECT_NEAR(end - start, 2.0, 1.0);
const std::string key = "end_time";
if (test_pass() == 0) {
// Save off the end of pass 1.
SaveTime(key, end);
} else {
int64_t previous_end = LoadTime(key);
EXPECT_LT(previous_end, start);
}
}
/** Test offline license durations work correctly after reboot. For time
constraints, this runs four sets of tests in parallel. For each set of tests
we load a list of licenses with a range of rental or playback durations so
that we can be confident that a short wait will find at least one duration
in the range that has expired and at least one duration that has not
expired.
RDa. Load an offline license, reboot the device, reload the license and
verify that the rental duration is enforced from the time the license was
requested.
RDb. Load an offline license, begin playback, reboot the device, reload the
license and verify that the rental duration is enforced from the time the
license was requested.
PDa. Load an offline license, reboot the device, reload the license and
verify that the playback duration is enforced from the time initial playback
started.
PDb. Load an offline license, begin playback, reboot the device, reload the
license and verify that the playback duration is enforced from the time
initial playback started.
Each of these four sets contains licenses with various durations so that the
test can run with a reasonable reboot time. We will assume that a reboot can
take anywhere from 10 seconds to almost an hour. With this in mind, we will
create a license that has a 10 second duration, one with a 20 second
duration, ... and one with an hour duration. All of these licenses will be
loaded before the reboot. Then after the reboot we should be in a situation
like this:
:-----------------------------------------> time axis.
: start of test.
:: licenses loaded.
:: : reboot starts
:: : : reboot finished. test resumes.
:: : :
:[---] 10s - expired :
:[-------] 20s expired :
: ... :
:[---------------------------] this license has not yet expired
:[----------------------------------------------] license not expired.
: ... :
:[---------------------------------------------------] 1 hour. not expired
After the test resumes, we will have at least one license that has not yet
expired. We will then sleep we are near the expiration time of that
license. We can then carefully test that this one license from the set
enforces its duration.
This is complicated by the fact that we are testing four different sets. So
what we really have is something like this:
:-----------------------------------------> time axis.
: start of test.
:: licenses loaded.
:: : reboot starts
:: : : reboot finished. test resumes.
:: : :
:[-----------------------------] first RDa license that has not exipred.
:[--------------------------] first RDb license that has not exipred.
:[---------------------------] first PDa license that has not exipred.
:[-------------------------------] first PDb license that has not exipred.
Since we want to test all four of these licenses, we will compute the
interesting times for each license. These are the times just before, and
just after the expiration time. After sorting these interesting times, we
will test each one in order.
*/
class OfflineLicense {
public:
OfflineLicense(const std::string& test_type, CdmEngine* cdm_engine,
const ConfigTestEnv& config, int64_t duration,
bool play_before_reboot)
: content_id_("CDM_Reboot_" + test_type +
(play_before_reboot ? "b_" : "a_") +
std::to_string(duration)),
duration_(duration),
play_before_reboot_(play_before_reboot),
license_holder_(content_id_, cdm_engine, config) {
license_holder_.set_can_persist(true);
}
virtual ~OfflineLicense() {}
// Fetch and load the license. The session is left open.
void LoadLicense() {
license_holder_.OpenSession();
start_of_rental_clock_ = wvutil::Clock().GetCurrentTime();
license_holder_.FetchLicense();
license_holder_.LoadLicense();
}
// Reload the license. The session is left open.
void ReloadLicense() {
license_holder_.OpenSession();
license_holder_.ReloadLicense();
}
// Action to be taken after loading the license, but before the reboot.
virtual void BeforeReboot() {
if (play_before_reboot_) {
Decrypt();
}
}
// Action to be taken after reloading the license.
virtual void AfterReboot() {}
void CloseSession() { license_holder_.CloseSession(); }
// The time this license should be cutoff. Decrypt should succeed before this
// time and should fail after this time.
virtual int64_t cutoff() = 0;
// Verify that the license may be used to decrypt content.
void Decrypt() {
if (start_of_playback_ == 0) {
start_of_playback_ = wvutil::Clock().GetCurrentTime();
}
const KeyId key_id = "0000000000000000";
EXPECT_EQ(NO_ERROR, license_holder_.Decrypt(key_id))
<< "Failed for " << content_id_
<< ", now = " << wvutil::Clock().GetCurrentTime() << ", rental_clock="
<< (wvutil::Clock().GetCurrentTime() - start_of_rental_clock_)
<< ", playback_clock = "
<< (wvutil::Clock().GetCurrentTime() - start_of_playback_)
<< ", delta to cutoff = "
<< (wvutil::Clock().GetCurrentTime() - cutoff());
}
// Verify that the license has expired, and may not be used to decrypt
// content.
void FailDecrypt() {
const KeyId key_id = "0000000000000000";
EXPECT_EQ(NEED_KEY, license_holder_.Decrypt(key_id))
<< "Decrypt should have failed for " << content_id_
<< ", now = " << wvutil::Clock().GetCurrentTime() << ", rental_clock="
<< (wvutil::Clock().GetCurrentTime() - start_of_rental_clock_)
<< ", playback_clock = "
<< (wvutil::Clock().GetCurrentTime() - start_of_playback_)
<< ", delta to cutoff = "
<< (wvutil::Clock().GetCurrentTime() - cutoff());
}
// Save times and the key set id to persistent data.
void SaveData(RebootTest* reboot_test,
std::map<std::string, std::string>* persistent_data) {
reboot_test->SaveTime("start_of_rental_" + content_id_,
start_of_rental_clock_);
reboot_test->SaveTime("start_of_playback_" + content_id_,
start_of_playback_);
(*persistent_data)["key_set_id_" + content_id_] =
license_holder_.key_set_id();
}
// Load times and the key set id from persistent data.
void LoadData(RebootTest* reboot_test,
std::map<std::string, std::string>* persistent_data) {
start_of_rental_clock_ =
reboot_test->LoadTime("start_of_rental_" + content_id_);
start_of_playback_ =
reboot_test->LoadTime("start_of_playback_" + content_id_);
license_holder_.set_key_set_id(
(*persistent_data)["key_set_id_" + content_id_]);
}
const std::string& content_id() const { return content_id_; }
protected:
const std::string content_id_;
int64_t duration_;
int64_t start_of_rental_clock_ = 0;
int64_t start_of_playback_ = 0;
bool play_before_reboot_;
LicenseHolder license_holder_;
};
// Holds an offline license that has a limit on the rental duration.
class RentalDurationLicense : public OfflineLicense {
public:
RentalDurationLicense(CdmEngine* cdm_engine, const ConfigTestEnv& config,
int64_t duration, bool play_before_reboot)
: OfflineLicense("RD", cdm_engine, config, duration, play_before_reboot) {
}
int64_t cutoff() override { return start_of_rental_clock_ + duration_; }
};
// Holds an offline license that has a limit on the playback duration.
class PlaybackDurationLicense : public OfflineLicense {
public:
PlaybackDurationLicense(CdmEngine* cdm_engine, const ConfigTestEnv& config,
int64_t duration, bool play_before_reboot)
: OfflineLicense("PD", cdm_engine, config, duration, play_before_reboot) {
}
// If we did not start playback before the reboot, we will start playback
// just after reloading the license, post-reboot.
void AfterReboot() override {
if (!play_before_reboot_) {
Decrypt();
}
}
int64_t cutoff() override { return start_of_playback_ + duration_; }
};
// Test that the rental and playback durations are enforced across a reboot.
class OfflineLicenseTest : public RebootTest {
public:
void SetUp() override {
RebootTest::SetUp();
EnsureProvisioned();
// Run each of the following test cases in parallel so that we don't have to
// sleep separately for each one. These durations need to match the polices
// specified on the UAT license server.
test_case_.resize(4);
const std::vector<int64_t> duration_range = {10, 20, 30, 45,
60, 300, 900, 3600};
for (size_t i = 0; i < duration_range.size(); i++) {
test_case_[0].push_back(
std::unique_ptr<OfflineLicense>(new RentalDurationLicense(
&cdm_engine_, config_, duration_range[i], false)));
test_case_[1].push_back(
std::unique_ptr<OfflineLicense>(new RentalDurationLicense(
&cdm_engine_, config_, duration_range[i], true)));
test_case_[2].push_back(
std::unique_ptr<OfflineLicense>(new PlaybackDurationLicense(
&cdm_engine_, config_, duration_range[i], false)));
test_case_[3].push_back(
std::unique_ptr<OfflineLicense>(new PlaybackDurationLicense(
&cdm_engine_, config_, duration_range[i], true)));
}
}
// Load all of the licenses. For the tests that require playback before the
// reboot, we start playback here.
void LoadAllLicenses() {
DeleteAllLicenses();
// For each test case, load an offline license and save the data.
for (size_t n = 0; n < test_case_.size(); n++) {
for (size_t i = 0; i < test_case_[n].size(); i++) {
ASSERT_NO_FATAL_FAILURE(test_case_[n][i]->LoadLicense());
test_case_[n][i]->BeforeReboot(); // For some tests, we decrypt here.
test_case_[n][i]->SaveData(this, &persistent_data_);
test_case_[n][i]->CloseSession();
}
}
}
// Reload all of the licenses. We also go through each license and figure out
// which ones have already expired, or close to expire. We then find the first
// license from each set that will be next to expire, so that we can test
// those more carefully.
void ReloadAllLicense() {
// first_valid is the index of the first license that has not expired and is
// not just about to expire.
first_valid_.resize(test_case_.size());
for (size_t n = 0; n < test_case_.size(); n++) {
for (size_t i = 0; i < test_case_[n].size(); i++) {
OfflineLicense* license = test_case_[n][i].get();
license->LoadData(this, &persistent_data_);
ASSERT_NO_FATAL_FAILURE(license->ReloadLicense());
license->AfterReboot();
// if past cutoff. make sure decrypt fails.
if (license->cutoff() + kFudge < wvutil::Clock().GetCurrentTime()) {
license->FailDecrypt();
}
// If past cutoff, or near cutoff, then we don't want to use it
// as our first valid.
if (license->cutoff() - 2 * kFudge < wvutil::Clock().GetCurrentTime()) {
first_valid_[n] = i + 1;
}
license->CloseSession();
}
// We expect there to be at least one license that has not expired yet.
// If this is not true, then the reboot time was probably longer than
// expected. If it is important to run these tests with a very long reboot
// time, then please ask a Widevine engineer to generate more license
// policies with longer reboot times.
ASSERT_LT(first_valid_[n], test_case_[n].size())
<< "For n=" << n
<< ", content_id = " << test_case_[n][0]->content_id()
<< ", time=" << wvutil::Clock().GetCurrentTime() << "\n"
<< "This is an indication that the reboot took a very long time.\n";
}
}
// Take the first_valid_ array, which tells us which licenses will expire
// soon, and compute the times we are interested in: a little before and a
// little after the cutoff. We want to test that the license is valid just
// before the cutoff and that the license is not valid just after the
// cutoff.
//
// The interesting_times_ is a std::set, so that we may iterate through the
// times in order.
void FindInterestingTimes() {
for (size_t n = 0; n < test_case_.size(); n++) {
OfflineLicense* license = test_case_[n][first_valid_[n]].get();
interesting_times_.insert(license->cutoff() - kFudge);
interesting_times_.insert(license->cutoff() + kFudge);
}
}
// Test at each of the interesting times. These are the times just before and
// after the cutoff of the next license to expire from each list of test
// cases.
void TestInterestingTimes() {
int decrypt_count = 0;
int fail_count = 0;
for (auto time : interesting_times_) {
int64_t now = wvutil::Clock().GetCurrentTime();
int64_t delta = (time - now);
// It is not necessarily an error for the delta to be negative. But it is
// an indication that the we are near an error condition. If the current
// time is a few seconds past the interesting time, then the license
// should still be valid or expired. If delta is very large relative to
// kFudge, then the test might fail. This is still an indication that
// something is wrong: reloading a license and decrypting some content
// should not take multiple seconds.
if (delta < 0) LOGW("Sleep delta would be negative: %ld", delta);
if (delta > 0) TestSleep::Sleep(static_cast<unsigned int>(delta));
// We look at each of the four license that we used to generate the
// interesting times. It's possible that two licenses share the same
// interesting time, so we have to check each of the four against each
// time instead of keeping track of which license generated which time.
for (size_t n = 0; n < test_case_.size(); n++) {
OfflineLicense* license = test_case_[n][first_valid_[n]].get();
ASSERT_NO_FATAL_FAILURE(license->ReloadLicense());
if (time == license->cutoff() - kFudge) {
license->Decrypt();
decrypt_count++;
}
if (time == license->cutoff() + kFudge) {
license->FailDecrypt();
fail_count++;
}
license->CloseSession();
}
}
EXPECT_EQ(decrypt_count, 4) << "Test error. I missed a cutoff";
EXPECT_EQ(fail_count, 4) << "Test error. I missed a cutoff";
}
// After we have tested one license from each list of test cases carefully,
// all the rest of the licenses can be tested. We do not sleep in this
// function, we only test each license that is still valid, or has already
// expired. We ignore licenses that are near the cutoff because we already
// tested those license in the previous function.
void TestAfterInterestingTimes() {
// Make sure that all the rest of the licenses are still valid.
for (size_t n = 0; n < test_case_.size(); n++) {
for (size_t i = first_valid_[n] + 1; i < test_case_[n].size(); i++) {
OfflineLicense* license = test_case_[n][i].get();
ASSERT_NO_FATAL_FAILURE(license->ReloadLicense());
int64_t now = wvutil::Clock().GetCurrentTime();
if (now <= license->cutoff() - kFudge) {
license->Decrypt();
}
if (now >= license->cutoff() + kFudge) {
license->FailDecrypt();
}
license->CloseSession();
}
}
}
void TearDown() override {
// Clean up licenses if any test failed.
if (::testing::Test::HasFailure() || test_pass() == 1) {
DeleteAllLicenses();
}
RebootTest::TearDown();
}
void DeleteAllLicenses() {
std::vector<std::string> key_set_ids;
EXPECT_EQ(NO_ERROR,
cdm_engine_.ListStoredLicenses(kSecurityLevelL1, &key_set_ids));
for (auto key_set : key_set_ids) {
cdm_engine_.RemoveOfflineLicense(key_set, kSecurityLevelL1);
}
// TODO(b/215230202): Is this necessary? It doesn't seem to work.
std::vector<std::string> ksids;
std::vector<std::string> pst;
std::string app_id = "";
EXPECT_EQ(NO_ERROR,
cdm_engine_.ListUsageIds(app_id, kSecurityLevelL1, &ksids, &pst));
for (auto k : ksids) {
EXPECT_EQ(NO_ERROR,
cdm_engine_.DeleteUsageRecord(app_id, kSecurityLevelL1, k));
}
}
std::vector<std::vector<std::unique_ptr<OfflineLicense>>> test_case_;
std::set<int64_t> interesting_times_;
std::vector<size_t> first_valid_;
};
TEST_F(OfflineLicenseTest, VariousTests) {
if (test_pass() == 0) {
LoadAllLicenses();
} else if (test_pass() == 1) {
ReloadAllLicense();
FindInterestingTimes();
TestInterestingTimes();
TestAfterInterestingTimes();
}
}
} // namespace wvcdm

View File

@@ -36,6 +36,12 @@ class RebootTest : public WvCdmTestBaseWithEngine {
static int test_pass() { return default_config_.test_pass(); }
// Load a previously saved time. Returns 0 if the value does not exist or
// cannot be parsed.
int64_t LoadTime(const std::string& key);
// Save a time to persistent storage.
void SaveTime(const std::string& key, int64_t time);
protected:
void SetUp() override;
void TearDown() override;

View File

@@ -122,6 +122,11 @@ void show_menu(const char* prog_name, const std::string& extra_help_text) {
<< " be used with a real OEMCrypto." << std::endl
<< std::endl;
std::cout << " --initial_time=<time>" << std::endl;
std::cout << " Set the initial time on the fake clock. This is ignored"
<< " if fake_sleep was not set." << std::endl
<< std::endl;
std::cout << " --pass=<N>" << std::endl;
std::cout << " Run test pass N. This is used for reboot tests that "
<< "require several passes." << std::endl
@@ -442,7 +447,9 @@ void WvCdmTestBase::EnsureProvisioned() {
Provision();
status = cdm_engine.OpenSession(config_.key_system(), nullptr, nullptr,
&session_id);
if (status == NEED_PROVISIONING) {
continue;
}
ASSERT_EQ(NO_ERROR, status);
status = cdm_engine.GenerateKeyRequest(session_id, key_set_id, init_data,
kLicenseTypeStreaming,
@@ -521,6 +528,14 @@ bool WvCdmTestBase::Initialize(int argc, const char* const argv[],
default_config_.set_test_pass(std::stoi(arg_value));
std::cout << "Running test pass " << default_config_.test_pass()
<< std::endl;
} else if (arg_prefix == "--initial_time") {
if (wvutil::TestSleep::real_sleep()) {
LOGD("Ignoring initial time %s because using a real clock",
arg_value.c_str());
} else {
LOGE("Setting initial fake clock time to %s", arg_value.c_str());
wvutil::TestSleep::SetFakeClock(stol(arg_value));
}
} else if (arg_prefix == "--test_data_path") {
default_config_.set_test_data_path(arg_value);
} else {
@@ -562,266 +577,6 @@ bool WvCdmTestBase::Initialize(int argc, const char* const argv[],
return true;
}
TestLicenseHolder::TestLicenseHolder(CdmEngine* cdm_engine)
: cdm_engine_(cdm_engine),
oemcrypto_api_(0),
session_opened_(false),
// Keys are initialized with simple values, and the correct size:
derived_mac_key_server_(MAC_KEY_SIZE, 'a'),
derived_mac_key_client_(MAC_KEY_SIZE, 'b'),
mac_key_server_(MAC_KEY_SIZE, 'c'),
mac_key_client_(MAC_KEY_SIZE, 'd'),
enc_key_(CONTENT_KEY_SIZE, 'e'),
session_key_(CONTENT_KEY_SIZE, 'f') {}
TestLicenseHolder::~TestLicenseHolder() { CloseSession(); }
void TestLicenseHolder::OpenSession(const std::string& key_system) {
CdmResponseType status =
cdm_engine_->OpenSession(key_system, nullptr, nullptr, &session_id_);
ASSERT_EQ(status, NO_ERROR);
ASSERT_NE("", session_id_) << "Could not open CDM session.";
ASSERT_TRUE(cdm_engine_->IsOpenSession(session_id_));
session_opened_ = true;
}
void TestLicenseHolder::CloseSession() {
if (session_opened_) {
cdm_engine_->CloseSession(session_id_);
session_opened_ = false;
}
}
void TestLicenseHolder::GenerateKeyRequest(
const std::string& key_id, const std::string& init_data_type_string) {
ASSERT_TRUE(session_opened_);
CdmAppParameterMap app_parameters;
CdmKeySetId key_set_id;
InitializationData init_data(init_data_type_string, key_id);
if (wvutil::g_cutoff >= wvutil::CDM_LOG_DEBUG) init_data.DumpToLogs();
CdmKeyRequest key_request;
CdmResponseType result = cdm_engine_->GenerateKeyRequest(
session_id_, key_set_id, init_data, kLicenseTypeStreaming, app_parameters,
&key_request);
EXPECT_EQ(KEY_MESSAGE, result);
signed_license_request_data_ = key_request.message;
EXPECT_EQ(kKeyRequestTypeInitial, key_request.type);
}
void TestLicenseHolder::CreateDefaultLicense() {
video_widevine::SignedMessage signed_message;
EXPECT_TRUE(signed_message.ParseFromString(signed_license_request_data_));
license_request_data_ = signed_message.msg();
video_widevine::LicenseRequest license_request;
EXPECT_TRUE(license_request.ParseFromString(license_request_data_));
video_widevine::ClientIdentification client_id = license_request.client_id();
EXPECT_EQ(
video_widevine::ClientIdentification_TokenType_DRM_DEVICE_CERTIFICATE,
client_id.type());
oemcrypto_api_ =
(client_id.has_client_capabilities() &&
client_id.client_capabilities().has_oem_crypto_api_version())
? client_id.client_capabilities().oem_crypto_api_version()
: 0;
std::string signed_buffer = license_request_data_;
std::string core_message = signed_message.has_oemcrypto_core_message()
? signed_message.oemcrypto_core_message()
: "";
if (oemcrypto_api_ >= 17) {
signed_buffer = signed_message.oemcrypto_core_message() + signed_buffer;
}
// Extract the RSA key from the DRM certificate.
std::string token = client_id.token();
video_widevine::SignedDrmCertificate signed_drm_cert;
EXPECT_TRUE(signed_drm_cert.ParseFromString(token));
video_widevine::DrmCertificate drm_cert;
EXPECT_TRUE(drm_cert.ParseFromString(signed_drm_cert.drm_certificate()));
EXPECT_TRUE(rsa_key_.Init(drm_cert.public_key()));
EXPECT_TRUE(
rsa_key_.VerifySignature(signed_buffer, signed_message.signature()));
DeriveKeysFromSessionKey();
video_widevine::LicenseIdentification* license_id = license()->mutable_id();
license_id->set_request_id("TestCase");
license_id->set_session_id(session_id_);
license_id->set_type(video_widevine::STREAMING);
license_id->set_version(0);
::video_widevine::License_Policy* policy = license()->mutable_policy();
policy->set_can_play(true);
policy->set_can_persist(false);
policy->set_can_renew(false);
policy->set_playback_duration_seconds(0);
policy->set_license_duration_seconds(0);
AddMacKey();
}
void TestLicenseHolder::AddMacKey() {
video_widevine::License_KeyContainer* key_container = license()->add_key();
std::vector<uint8_t> iv(KEY_IV_SIZE, 'v');
std::string iv_s(iv.begin(), iv.end());
key_container->set_iv(iv_s);
key_container->set_type(video_widevine::License_KeyContainer_KeyType_SIGNING);
// Combine server and client mac keys.
std::vector<uint8_t> keys(mac_key_server_);
keys.insert(keys.end(), mac_key_client_.begin(), mac_key_client_.end());
std::string encrypted_keys =
WvCdmTestBase::Aes128CbcEncrypt(enc_key_, keys, iv);
key_container->set_key(encrypted_keys);
}
video_widevine::License_KeyContainer* TestLicenseHolder::AddKey(
const KeyId& key_id, const std::vector<uint8_t>& key_data,
const wvoec::KeyControlBlock& block_in) {
video_widevine::License_KeyContainer* key_container = license()->add_key();
wvoec::KeyControlBlock block = block_in;
if (block.verification[0] == 0) {
block.verification[0] = 'k';
block.verification[1] = 'c';
block.verification[2] = '1';
// This will work until oemcrypto api 20.
block.verification[3] = '0' + wvoec::global_features.api_version - 10;
}
key_container->set_id(key_id);
key_container->set_type(video_widevine::License_KeyContainer_KeyType_CONTENT);
key_container->set_level(
video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO);
std::vector<uint8_t> iv(KEY_IV_SIZE, 'v');
std::string iv_s(iv.begin(), iv.end());
key_container->set_iv(iv_s);
std::string encrypted_key_data =
WvCdmTestBase::Aes128CbcEncrypt(enc_key_, key_data, iv);
// TODO(b/111069024): remove this!
std::string padding(CONTENT_KEY_SIZE, '-');
key_container->set_key(encrypted_key_data + padding);
// TODO(b/192700112): This mock license server should parse the core message
// request, and only encrypt the key control block if the API is greater than
// or equal to 16.5. For now, we can pass tests on the v17 branch by checking
// against v17.0.
if (oemcrypto_api_ >= 17) {
std::string block_s(reinterpret_cast<const char*>(&block),
reinterpret_cast<const char*>(&block) + sizeof(block));
key_container->mutable_key_control()->set_key_control_block(block_s);
} else {
std::vector<uint8_t> block_v(
reinterpret_cast<const uint8_t*>(&block),
reinterpret_cast<const uint8_t*>(&block) + sizeof(block));
std::vector<uint8_t> block_iv(KEY_IV_SIZE, 'w');
std::string block_iv_s(block_iv.begin(), block_iv.end());
std::string encrypted_block =
WvCdmTestBase::Aes128CbcEncrypt(key_data, block_v, block_iv);
key_container->mutable_key_control()->set_iv(block_iv_s);
key_container->mutable_key_control()->set_key_control_block(
encrypted_block);
}
return key_container;
}
void TestLicenseHolder::SignAndLoadLicense() {
#if 0 // Need to turn off protobuf_lite to use this.
LOGV("License = %s\n", license_.DebugString().c_str());
#endif
std::string license_data;
license_.SerializeToString(&license_data);
std::string signature =
WvCdmTestBase::SignHMAC(license_data, derived_mac_key_server_);
std::string session_key_s(session_key_.begin(), session_key_.end());
std::string encrypted_session_key;
EXPECT_TRUE(rsa_key_.Encrypt(session_key_s, &encrypted_session_key));
video_widevine::SignedMessage signed_response;
signed_response.set_msg(license_data);
signed_response.set_type(video_widevine::SignedMessage_MessageType_LICENSE);
signed_response.set_session_key(encrypted_session_key);
signed_response.set_signature(signature);
std::string response_data;
signed_response.SerializeToString(&response_data);
CdmKeySetId key_set_id;
CdmLicenseType license_type; // Required for AddKey. Result value ignored.
EXPECT_EQ(KEY_ADDED, cdm_engine_->AddKey(session_id_, response_data,
&license_type, &key_set_id));
}
void TestLicenseHolder::DeriveKeysFromSessionKey() {
std::string context;
GenerateMacContext(license_request_data_, &context);
std::vector<uint8_t> mac_key_context(context.begin(), context.end());
GenerateEncryptContext(license_request_data_, &context);
std::vector<uint8_t> enc_key_context(context.begin(), context.end());
ASSERT_TRUE(
DeriveKey(session_key_, mac_key_context, 1, &derived_mac_key_server_));
std::vector<uint8_t> mac_key_part2;
ASSERT_TRUE(DeriveKey(session_key_, mac_key_context, 2, &mac_key_part2));
derived_mac_key_server_.insert(derived_mac_key_server_.end(),
mac_key_part2.begin(), mac_key_part2.end());
ASSERT_TRUE(
DeriveKey(session_key_, mac_key_context, 3, &derived_mac_key_client_));
ASSERT_TRUE(DeriveKey(session_key_, mac_key_context, 4, &mac_key_part2));
derived_mac_key_client_.insert(derived_mac_key_client_.end(),
mac_key_part2.begin(), mac_key_part2.end());
std::vector<uint8_t> enc_key;
ASSERT_TRUE(DeriveKey(session_key_, enc_key_context, 1, &enc_key_));
}
bool TestLicenseHolder::DeriveKey(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& context,
int counter, std::vector<uint8_t>* out) {
if (key.empty() || counter > 4 || context.empty() || out == nullptr) {
LOGE("DeriveKey(): bad context");
return false;
}
const EVP_CIPHER* cipher = EVP_aes_128_cbc();
CMAC_CTX* cmac_ctx = CMAC_CTX_new();
if (!cmac_ctx) {
LOGE("DeriveKey(): cmac failure");
return false;
}
if (!CMAC_Init(cmac_ctx, &key[0], key.size(), cipher, nullptr)) {
LOGE("DeriveKey(): CMAC_Init");
CMAC_CTX_free(cmac_ctx);
return false;
}
std::vector<uint8_t> message;
message.push_back(counter);
message.insert(message.end(), context.begin(), context.end());
if (!CMAC_Update(cmac_ctx, &message[0], message.size())) {
LOGE("DeriveKey(): CMAC_Update");
CMAC_CTX_free(cmac_ctx);
return false;
}
size_t reslen;
uint8_t res[128];
if (!CMAC_Final(cmac_ctx, res, &reslen)) {
LOGE("DeriveKey(): CMAC_Final");
CMAC_CTX_free(cmac_ctx);
return false;
}
out->assign(res, res + reslen);
CMAC_CTX_free(cmac_ctx);
return true;
}
std::string MakePSSH(const video_widevine::WidevinePsshData& header) {
std::string data;
header.SerializeToString(&data);

View File

@@ -108,69 +108,6 @@ class TestCryptoSession : public CryptoSession {
CdmResponseType GenerateNonce(uint32_t* nonce) override;
};
// A holder for a license. Users of this class will first open a session with
// OpenSession, then generate a key request with GenerateKeyRequest, and then
// call CreateDefaultLicense to create a bare-bones license with no keys in it.
// The user may then access the license to adjust the policy, or use AddKey to
// add keys to the license. The license is then loaded via SignAndLoadLicense.
class TestLicenseHolder {
public:
// cdm_engine must exist and outlive the TestLicenseHolder.
TestLicenseHolder(CdmEngine* cdm_engine);
~TestLicenseHolder();
// Caller must ensure device already provisioned.
void OpenSession(const std::string& key_system);
void CloseSession();
// Use the cdm_engine to generate a key request in the session. This should
// be called after OpenSession. This saves the signed license request, so
// that the DRM certificate can be extracted in CreateDefaultLicense.
void GenerateKeyRequest(const std::string& key_id,
const std::string& init_data_type_string);
// Create a bare-bones license from the license request. After this, the user
// may access and modify the license using license() below.
void CreateDefaultLicense();
// Sign the license using the DRM certificate's RSA key. Then the license is
// passed to the cdm_engine using AddKey. After this, the license is loaded
// and the keys may be used.
void SignAndLoadLicense();
// The session id. This is only valid after a call to OpenSession.
const std::string& session_id() { return session_id_; }
// The license protobuf. This is only valid after CreateDefaultLicense.
video_widevine::License* license() { return &license_; };
// Add a key with the given key control block and key data.
// If the block's verification is empty, it will be set to a valid value.
// The key data is encrypted correctly.
video_widevine::License_KeyContainer* AddKey(
const KeyId& key_id, const std::vector<uint8_t>& key_data,
const wvoec::KeyControlBlock& block);
private:
// Helper method to generate mac keys and encryption keys for the license.
void DeriveKeysFromSessionKey();
// Derive a single mac key or encryption key using CMAC.
bool DeriveKey(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& context, int counter,
std::vector<uint8_t>* out);
// Add the mac keys to the license.
void AddMacKey();
CdmEngine* cdm_engine_;
uint32_t oemcrypto_api_;
std::string signed_license_request_data_;
std::string license_request_data_;
std::string session_id_;
bool session_opened_;
RsaPublicKey rsa_key_; // From the DRM Certificate.
video_widevine::License license_;
std::vector<uint8_t> derived_mac_key_server_;
std::vector<uint8_t> derived_mac_key_client_;
std::vector<uint8_t> mac_key_server_;
std::vector<uint8_t> mac_key_client_;
std::vector<uint8_t> enc_key_;
std::vector<uint8_t> session_key_;
};
// Given a PSSH data structure, this makes a PSSH string for use in
// generating a license request.
std::string MakePSSH(const video_widevine::WidevinePsshData& header);

View File

@@ -28,6 +28,7 @@ LOCAL_SRC_FILES := \
../core/test/config_test_env.cpp \
../core/test/fake_provisioning_server.cpp \
../core/test/http_socket.cpp \
../core/test/license_holder.cpp \
../core/test/license_request.cpp \
../core/test/test_base.cpp \
../core/test/test_printers.cpp \

View File

@@ -20,10 +20,15 @@ CORE_UTIL_EXPORT std::vector<uint8_t> a2b_hex(const std::string& label,
const std::string& b);
CORE_UTIL_EXPORT std::string a2bs_hex(const std::string& b);
// Binary to ASCII hex conversion.
// Binary to ASCII hex conversion. The default versions limit output to 2k to
// protect us from log spam. The unlimited version has no length limit.
CORE_UTIL_EXPORT std::string b2a_hex(const std::vector<uint8_t>& b);
CORE_UTIL_EXPORT std::string unlimited_b2a_hex(const std::vector<uint8_t>& b);
CORE_UTIL_EXPORT std::string b2a_hex(const std::string& b);
CORE_UTIL_EXPORT std::string unlimited_b2a_hex(const std::string& b);
CORE_UTIL_EXPORT std::string HexEncode(const uint8_t* bytes, size_t size);
CORE_UTIL_EXPORT std::string UnlimitedHexEncode(const uint8_t* bytes,
size_t size);
// Base64 encoding/decoding.
// Converts binary data into the ASCII Base64 character set and vice

View File

@@ -201,20 +201,34 @@ std::string b2a_hex(const std::vector<uint8_t>& byte) {
return HexEncode(byte.data(), byte.size());
}
std::string unlimited_b2a_hex(const std::vector<uint8_t>& byte) {
if (byte.empty()) return "";
return UnlimitedHexEncode(byte.data(), byte.size());
}
std::string b2a_hex(const std::string& byte) {
if (byte.empty()) return "";
return HexEncode(reinterpret_cast<const uint8_t*>(byte.data()),
byte.length());
}
std::string unlimited_b2a_hex(const std::string& byte) {
if (byte.empty()) return "";
return UnlimitedHexEncode(reinterpret_cast<const uint8_t*>(byte.data()),
byte.length());
}
std::string HexEncode(const uint8_t* in_buffer, size_t size) {
static const char kHexChars[] = "0123456789ABCDEF";
if (size == 0) return "";
constexpr unsigned int kMaxSafeSize = 2048;
if (size > kMaxSafeSize) size = kMaxSafeSize;
return UnlimitedHexEncode(in_buffer, size);
}
std::string UnlimitedHexEncode(const uint8_t* in_buffer, size_t size) {
static const char kHexChars[] = "0123456789ABCDEF";
if (size == 0) return "";
// Each input byte creates two output hex characters.
std::string out_buffer(size * 2, '\0');
for (unsigned int i = 0; i < size; ++i) {
char byte = in_buffer[i];
out_buffer[(i << 1)] = kHexChars[(byte >> 4) & 0xf];

View File

@@ -13,7 +13,7 @@ namespace wvutil {
namespace {
// A fake clock that only advances when TestSleep::Sleep is called.
class FakeClock : public wvcdm::TestSleep::CallBack {
class FakeClock : public TestSleep::CallBack {
public:
FakeClock() {
auto now = std::chrono::system_clock().now();
@@ -34,7 +34,7 @@ FakeClock* g_fake_clock = nullptr;
// On devices running a fake OEMCrypto, we can use a fake sleep and fake time.
int64_t Clock::GetCurrentTime() {
wvcdm::TestSleep::SyncFakeClock();
TestSleep::SyncFakeClock();
if (g_fake_clock == nullptr) g_fake_clock = new FakeClock();
return g_fake_clock->now() / 1000;
}

View File

@@ -58,6 +58,21 @@ void TestSleep::SyncFakeClock() {
Sleep(0);
}
void TestSleep::SetFakeClock(int64_t time_seconds) {
if (real_sleep_) {
LOGE("SetFakeClock when using a real clock. Expect other failures.");
}
// Delta could be positive or negative. If the fake clock had been initialized
// by the current time on a real clock, and then the command line
// re-initializes it to 0, then delta is negative.
int64_t delta = time_seconds - Clock().GetCurrentTime();
if (callback_ != nullptr) {
callback_->ElapseTime(delta * 1000);
} else {
LOGE("Setting fake clock with no callback. This won't work.");
}
}
bool TestSleep::RollbackSystemTime(int seconds) {
if (real_sleep_) {
#ifdef _WIN32

View File

@@ -41,6 +41,10 @@ class TestSleep {
// verify this function does not roll back the clock used by OEMCrypto.
static bool RollbackSystemTime(int seconds);
// Set the system clock to the specified time. This is only expected to work
// when real_sleep is false.
static void SetFakeClock(int64_t time_seconds);
// Roll the system clock forward to undo all previous calls to
// RollBackSystemTime. Returns true on success.
static bool ResetRollback() {

View File

@@ -2116,7 +2116,7 @@ OEMCryptoResult OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session,
* 256 bits it will be used for OEMCrypto_Generic_Sign() or
* OEMCrypto_Generic_Verify() as specified in the key control block. If the key
* will be used for OEMCrypto_Generic_Encrypt() or OEMCrypto_Generic_Decrypt()
* then the cipher mode will always be OEMCrypto_CipherMode_CBC. Continue to
* then the cipher mode will always be OEMCrypto_CipherMode_CBCS. Continue to
* use this key for this session until OEMCrypto_SelectKey() is called again,
* or until OEMCrypto_CloseSession() is called.
*
@@ -2292,15 +2292,15 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session,
* 'cbcs'. Starting with v16, OEMCrypto only supports 'cenc' and 'cbcs'. The
* schemes 'cens' and 'cbc1' are not supported.
*
* The decryption mode, either OEMCrypto_CipherMode_CTR or
* OEMCrypto_CipherMode_CBC, was already specified in the call to
* The decryption mode, either OEMCrypto_CipherMode_CENC or
* OEMCrypto_CipherMode_CBCS, was already specified in the call to
* OEMCrypto_SelectKey(). The encryption pattern is specified by the fields in
* the parameter pattern. A description of partial encryption patterns for
* 'cbcs' can be found in the ISO-CENC standard, section 10.4.
*
* 'cenc' SCHEME:
*
* The 'cenc' scheme is OEMCrypto_CipherMode_CTR without an encryption
* The 'cenc' scheme is OEMCrypto_CipherMode_CENC without an encryption
* pattern. All the bytes in the encrypted portion of each subsample are
* encrypted. In the pattern parameter, both the encrypt and skip fields will
* be zero.
@@ -2323,7 +2323,7 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session,
*
* 'cbcs' SCHEME:
*
* The 'cbcs' scheme is OEMCrypto_CipherMode_CBC with an encryption pattern.
* The 'cbcs' scheme is OEMCrypto_CipherMode_CBCS with an encryption pattern.
* Only some of the bytes in the encrypted portion of each subsample are
* encrypted. In the pattern parameter, the encrypt and skip fields will
* usually be non-zero. This mode allows devices to decrypt FMP4 HLS content,
@@ -4811,9 +4811,11 @@ OEMCryptoResult OEMCrypto_GetBootCertificateChain(
* @param[in,out] public_key_size: on input, size of the caller's public_key
* buffer. On output, the number of bytes written into the buffer.
* @param[out] public_key_signature: pointer to the buffer that receives the
* signature of the public key. If an OEM private key is unavailable, it is
* signed by the device private key; otherwise is signed by the OEM private
* key.
* signature of the public key.
* If an OEM private key is unavailable: it is signed by the device private
* key. The signature must be in COSE_SIGN1 format as specified in RFC 8152.
* If an OEM private key is available: it is signed by the OEM private key.
* The signature must be raw signature bytes.
* @param[in,out] public_key_signature_size: on input, size of the caller's
* public_key_signature buffer. On output, the number of bytes written into
* the buffer.

View File

@@ -1 +1 @@
../odk/include/OEMCryptoCENCCommon.h
../../oemcrypto/odk/include/OEMCryptoCENCCommon.h

View File

@@ -30,13 +30,13 @@ struct CoreMessageFeatures {
uint32_t maximum_major_version = 17;
uint32_t maximum_minor_version = 0;
bool operator==(const CoreMessageFeatures &other) const;
bool operator!=(const CoreMessageFeatures &other) const {
bool operator==(const CoreMessageFeatures& other) const;
bool operator!=(const CoreMessageFeatures& other) const {
return !(*this == other);
}
};
std::ostream &operator<<(std::ostream &os, const CoreMessageFeatures &features);
std::ostream& operator<<(std::ostream& os, const CoreMessageFeatures& features);
} // namespace features
} // namespace oemcrypto_core_message

View File

@@ -35,10 +35,10 @@ extern "C" {
*/
#if defined(__GNUC__) || defined(__clang__)
#define ALIGNED __attribute__((aligned))
# define ALIGNED __attribute__((aligned))
#else
#define ALIGNED
#error ODK_Message must be aligned to the maximum useful alignment of the \
# define ALIGNED
# error ODK_Message must be aligned to the maximum useful alignment of the \
machine you are compiling for. Define the ALIGNED macro accordingly.
#endif

View File

@@ -19,7 +19,7 @@ extern "C" {
#define ODK_MINOR_VERSION 0
/* ODK Version string. Date changed automatically on each release. */
#define ODK_RELEASE_DATE "ODK v17.0 2022-01-24"
#define ODK_RELEASE_DATE "ODK v17.0 2022-02-15"
/* The lowest version number for an ODK message. */
#define ODK_FIRST_VERSION 16

View File

@@ -8,7 +8,7 @@ namespace oemcrypto_core_message {
namespace features {
const CoreMessageFeatures CoreMessageFeatures::kDefaultFeatures;
bool CoreMessageFeatures::operator==(const CoreMessageFeatures &other) const {
bool CoreMessageFeatures::operator==(const CoreMessageFeatures& other) const {
return maximum_major_version == other.maximum_major_version &&
maximum_minor_version == other.maximum_minor_version;
}
@@ -31,8 +31,8 @@ CoreMessageFeatures CoreMessageFeatures::DefaultFeatures(
return features;
}
std::ostream &operator<<(std::ostream &os,
const CoreMessageFeatures &features) {
std::ostream& operator<<(std::ostream& os,
const CoreMessageFeatures& features) {
return os << "v" << features.maximum_major_version << "."
<< features.maximum_minor_version;
}

View File

@@ -3,6 +3,8 @@
// License Agreement.
#include "fuzzing/odk_fuzz_helper.h"
#include <string>
#include "odk.h"
namespace oemcrypto_core_message {

View File

@@ -14,7 +14,7 @@ void FreeOutputBuffers(OEMCrypto_SESSION session_id,
int* secure_fd) {
switch (output_descriptor.type) {
case OEMCrypto_BufferType_Clear: {
delete[] output_descriptor.buffer.clear.address;
delete[] output_descriptor.buffer.clear.clear_buffer;
break;
}
case OEMCrypto_BufferType_Secure: {
@@ -32,7 +32,7 @@ bool InitializeOutputBuffers(OEMCrypto_SESSION session_id,
int* secure_fd, size_t input_buffer_size) {
switch (output_descriptor.type) {
case OEMCrypto_BufferType_Clear: {
output_descriptor.buffer.clear.address =
output_descriptor.buffer.clear.clear_buffer =
new OEMCrypto_SharedMemory[input_buffer_size];
return true;
}

View File

@@ -42,7 +42,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
license_api_fuzz.LoadLicense();
OEMCrypto_SelectKey(session->session_id(), session->license().keys[0].key_id,
session->license().keys[0].key_id_length,
OEMCrypto_CipherMode_CTR);
OEMCrypto_CipherMode_CENC);
// Calculate signature for in buffer.
size_t signature_length = 0;
OEMCrypto_Generic_Sign(session->session_id(), in_buffer.data(),

View File

@@ -749,9 +749,15 @@ void LicenseRoundTrip::FillCoreResponseSubstrings() {
core_response_.key_array[i].key_data =
FindSubstring(response_data_.keys[i].key_data,
response_data_.keys[i].key_data_length);
core_response_.key_array[i].key_control_iv =
FindSubstring(response_data_.keys[i].control_iv,
sizeof(response_data_.keys[i].control_iv));
if (core_request().api_major_version < kClearControlBlockAPIMajor ||
(core_request().api_major_version == kClearControlBlockAPIMajor &&
core_request().api_minor_version < kClearControlBlockAPIMinor)) {
core_response_.key_array[i].key_control_iv =
FindSubstring(response_data_.keys[i].control_iv,
sizeof(response_data_.keys[i].control_iv));
} else {
core_response_.key_array[i].key_control_iv = FindSubstring(nullptr, 0);
}
core_response_.key_array[i].key_control =
FindSubstring(&response_data_.keys[i].control,
sizeof(response_data_.keys[i].control));

File diff suppressed because it is too large Load Diff

View File

@@ -172,6 +172,7 @@ const size_t kMaxConcurrentSession[] = { 10, 20, 30, 40};
const size_t kMaxKeysPerSession[] = { 4, 20, 20, 30};
const size_t kMaxTotalKeys[] = { 16, 40, 80, 90};
const size_t kLargeMessageSize[] = { 8*KiB, 8*KiB, 16*KiB, 32*KiB};
const size_t kMaxTotalDRMPrivateKeys[] = { 2, 4, 6, 8};
// Note: Frame rate and simultaneous playback are specified by resource rating,
// but are tested at the system level, so there are no unit tests for frame
// rate. Similarly, number of subsamples for AV1
@@ -265,7 +266,7 @@ TEST_F(OEMCryptoClientTest, FreeUnallocatedSecureBufferNoFailure) {
*/
TEST_F(OEMCryptoClientTest, VersionNumber) {
const std::string log_message =
"OEMCrypto unit tests for API 17.0. Tests last updated 2021-12-03";
"OEMCrypto unit tests for API 17.0. Tests last updated 2022-02-18";
cout << " " << log_message << "\n";
cout << " "
<< "These tests are part of Android T."
@@ -1569,59 +1570,6 @@ class OEMCryptoSessionTests : public OEMCryptoClientTest {
license_messages.EncryptAndSignResponse();
return license_messages.LoadResponse();
}
void TestLoadLicenseForHugeBufferLengths(
const std::function<void(size_t, LicenseRoundTrip*)> f, bool check_status,
bool update_core_message_substring_values) {
auto oemcrypto_function = [&](size_t message_length) {
Session s;
LicenseRoundTrip license_messages(&s);
s.open();
InstallTestRSAKey(&s);
bool verify_keys_loaded = true;
license_messages.SignAndVerifyRequest();
license_messages.CreateDefaultResponse();
if (update_core_message_substring_values) {
// Make the license message big enough so that updated core message
// substring offset and length values from tests are still able to read
// data from license_message buffer rather than reading some garbage
// data.
license_messages.set_message_size(
sizeof(license_messages.response_data()) + message_length);
}
f(message_length, &license_messages);
if (update_core_message_substring_values) {
// We will be updating offset for these tests, which will cause verify
// keys to fail with an assertion. Hence skipping verification.
verify_keys_loaded = false;
}
license_messages.EncryptAndSignResponse();
OEMCryptoResult result =
license_messages.LoadResponse(&s, verify_keys_loaded);
s.close();
return result;
};
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, check_status);
}
void TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
const std::function<void(size_t, LicenseRoundTrip*)> f) {
Session s;
LicenseRoundTrip license_messages(&s);
s.open();
InstallTestRSAKey(&s);
license_messages.SignAndVerifyRequest();
license_messages.CreateDefaultResponse();
size_t message_length = sizeof(license_messages.response_data());
f(message_length, &license_messages);
license_messages.EncryptAndSignResponse();
OEMCryptoResult result = license_messages.LoadResponse();
s.close();
// Verifying error is not due to signature failure which can be caused due
// to test code.
ASSERT_NE(OEMCrypto_ERROR_SIGNATURE_FAILURE, result);
ASSERT_NE(OEMCrypto_SUCCESS, result);
}
};
TEST_F(OEMCryptoSessionTests,
@@ -1695,6 +1643,77 @@ TEST_F(OEMCryptoSessionTests,
!kCheckStatus);
}
class OEMCryptoLicenseOverflowTest : public OEMCryptoSessionTests,
public WithParamInterface<uint32_t> {
public:
OEMCryptoLicenseOverflowTest() : license_api_version_(kCurrentAPI) {}
void SetUp() override {
OEMCryptoSessionTests::SetUp();
license_api_version_ = GetParam();
}
void TearDown() override { OEMCryptoSessionTests::TearDown(); }
void TestLoadLicenseForHugeBufferLengths(
const std::function<void(size_t, LicenseRoundTrip*)> f, bool check_status,
bool update_core_message_substring_values) {
auto oemcrypto_function = [&](size_t message_length) {
Session s;
LicenseRoundTrip license_messages(&s);
license_messages.set_api_version(license_api_version_);
s.open();
InstallTestRSAKey(&s);
bool verify_keys_loaded = true;
license_messages.SignAndVerifyRequest();
license_messages.CreateDefaultResponse();
if (update_core_message_substring_values) {
// Make the license message big enough so that updated core message
// substring offset and length values from tests are still able to read
// data from license_message buffer rather than reading some garbage
// data.
license_messages.set_message_size(
sizeof(license_messages.response_data()) + message_length);
}
f(message_length, &license_messages);
if (update_core_message_substring_values) {
// We will be updating offset for these tests, which will cause verify
// keys to fail with an assertion. Hence skipping verification.
verify_keys_loaded = false;
}
license_messages.EncryptAndSignResponse();
OEMCryptoResult result =
license_messages.LoadResponse(&s, verify_keys_loaded);
s.close();
return result;
};
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, check_status);
}
void TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
const std::function<void(size_t, LicenseRoundTrip*)> f) {
Session s;
LicenseRoundTrip license_messages(&s);
license_messages.set_api_version(license_api_version_);
s.open();
InstallTestRSAKey(&s);
license_messages.SignAndVerifyRequest();
license_messages.CreateDefaultResponse();
size_t message_length = sizeof(license_messages.response_data());
OEMCryptoResult result = license_messages.LoadResponse();
f(message_length, &license_messages);
license_messages.EncryptAndSignResponse();
s.close();
// Verifying error is not due to signature failure which can be caused due
// to test code.
ASSERT_NE(OEMCrypto_ERROR_SIGNATURE_FAILURE, result);
ASSERT_NE(OEMCrypto_SUCCESS, result);
}
protected:
uint32_t license_api_version_;
};
// This class is for testing a single license with the default API version
// of 16. Used for buffer overflow tests.
class OEMCryptoMemoryLicenseTest : public OEMCryptoLicenseTestAPI16 {
@@ -3424,7 +3443,7 @@ TEST_F(OEMCryptoSessionTests,
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, !kCheckStatus);
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyIdLength) {
TestLoadLicenseForHugeBufferLengths(
[](size_t length, LicenseRoundTrip* license_messages) {
@@ -3434,7 +3453,7 @@ TEST_F(OEMCryptoSessionTests,
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyIdOffset) {
TestLoadLicenseForHugeBufferLengths(
[](size_t offset, LicenseRoundTrip* license_messages) {
@@ -3443,7 +3462,7 @@ TEST_F(OEMCryptoSessionTests,
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyIdLength) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3452,7 +3471,7 @@ TEST_F(OEMCryptoSessionTests,
});
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyIdOffset) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3461,7 +3480,7 @@ TEST_F(OEMCryptoSessionTests,
});
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyDataIvLength) {
TestLoadLicenseForHugeBufferLengths(
[](size_t length, LicenseRoundTrip* license_messages) {
@@ -3471,7 +3490,7 @@ TEST_F(OEMCryptoSessionTests,
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyDataIvOffset) {
TestLoadLicenseForHugeBufferLengths(
[](size_t offset, LicenseRoundTrip* license_messages) {
@@ -3481,8 +3500,8 @@ TEST_F(OEMCryptoSessionTests,
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyDataIvLength) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3492,8 +3511,8 @@ TEST_F(
});
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyDataIvOffset) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3503,7 +3522,7 @@ TEST_F(
});
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyDataLength) {
TestLoadLicenseForHugeBufferLengths(
[](size_t length, LicenseRoundTrip* license_messages) {
@@ -3513,7 +3532,7 @@ TEST_F(OEMCryptoSessionTests,
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyDataOffset) {
TestLoadLicenseForHugeBufferLengths(
[](size_t offset, LicenseRoundTrip* license_messages) {
@@ -3522,8 +3541,8 @@ TEST_F(OEMCryptoSessionTests,
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyDataLength) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3533,8 +3552,8 @@ TEST_F(
});
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyDataOffset) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3544,8 +3563,8 @@ TEST_F(
});
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyControlIvLength) {
TestLoadLicenseForHugeBufferLengths(
[](size_t length, LicenseRoundTrip* license_messages) {
@@ -3555,8 +3574,8 @@ TEST_F(
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyControlIvOffset) {
TestLoadLicenseForHugeBufferLengths(
[](size_t offset, LicenseRoundTrip* license_messages) {
@@ -3566,8 +3585,8 @@ TEST_F(
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyControlIvLength) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3578,8 +3597,8 @@ TEST_F(
});
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyControlIvOffset) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3590,7 +3609,7 @@ TEST_F(
});
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyControlLength) {
TestLoadLicenseForHugeBufferLengths(
[](size_t length, LicenseRoundTrip* license_messages) {
@@ -3600,7 +3619,7 @@ TEST_F(OEMCryptoSessionTests,
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyControlOffset) {
TestLoadLicenseForHugeBufferLengths(
[](size_t offset, LicenseRoundTrip* license_messages) {
@@ -3610,8 +3629,8 @@ TEST_F(OEMCryptoSessionTests,
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyControlLength) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3621,8 +3640,8 @@ TEST_F(
});
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyControlOffset) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3632,7 +3651,7 @@ TEST_F(
});
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringEncMacKeyIvLength) {
TestLoadLicenseForHugeBufferLengths(
[](size_t length, LicenseRoundTrip* license_messages) {
@@ -3641,7 +3660,7 @@ TEST_F(OEMCryptoSessionTests,
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringEncMacKeyIvOffset) {
TestLoadLicenseForHugeBufferLengths(
[](size_t offset, LicenseRoundTrip* license_messages) {
@@ -3650,8 +3669,8 @@ TEST_F(OEMCryptoSessionTests,
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringEncMacKeyIvLength) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3662,8 +3681,8 @@ TEST_F(
});
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringEncMacKeyIvOffset) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3674,7 +3693,7 @@ TEST_F(
});
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringEncMacKeyLength) {
TestLoadLicenseForHugeBufferLengths(
[](size_t length, LicenseRoundTrip* license_messages) {
@@ -3683,7 +3702,7 @@ TEST_F(OEMCryptoSessionTests,
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringEncMacKeyOffset) {
TestLoadLicenseForHugeBufferLengths(
[](size_t offset, LicenseRoundTrip* license_messages) {
@@ -3692,8 +3711,8 @@ TEST_F(OEMCryptoSessionTests,
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringEncMacKeyLength) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3702,8 +3721,8 @@ TEST_F(
});
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringEncMacKeyOffset) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3712,7 +3731,7 @@ TEST_F(
});
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringPstLength) {
TestLoadLicenseForHugeBufferLengths(
[](size_t length, LicenseRoundTrip* license_messages) {
@@ -3721,7 +3740,7 @@ TEST_F(OEMCryptoSessionTests,
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringPstOffset) {
TestLoadLicenseForHugeBufferLengths(
[](size_t offset, LicenseRoundTrip* license_messages) {
@@ -3730,7 +3749,7 @@ TEST_F(OEMCryptoSessionTests,
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringPstLength) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3739,7 +3758,7 @@ TEST_F(OEMCryptoSessionTests,
});
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringPstOffset) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3748,8 +3767,8 @@ TEST_F(OEMCryptoSessionTests,
});
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringSrmRestrictionDataLength) {
TestLoadLicenseForHugeBufferLengths(
[](size_t length, LicenseRoundTrip* license_messages) {
@@ -3758,8 +3777,8 @@ TEST_F(
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringSrmRestrictionDataOffset) {
TestLoadLicenseForHugeBufferLengths(
[](size_t offset, LicenseRoundTrip* license_messages) {
@@ -3768,8 +3787,8 @@ TEST_F(
!kCheckStatus, kUpdateCoreMessageSubstringValues);
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringSrmRestrictionDataLength) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3780,8 +3799,8 @@ TEST_F(
});
}
TEST_F(
OEMCryptoSessionTests,
TEST_P(
OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringSrmRestrictionDataOffset) {
TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths(
[](size_t response_message_length, LicenseRoundTrip* license_messages) {
@@ -3792,7 +3811,8 @@ TEST_F(
});
}
TEST_F(OEMCryptoSessionTests, OEMCryptoMemoryLoadLicenseForHugeResponseLength) {
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeResponseLength) {
TestLoadLicenseForHugeBufferLengths(
[](size_t message_size, LicenseRoundTrip* license_messages) {
license_messages->set_message_size(message_size);
@@ -3800,7 +3820,7 @@ TEST_F(OEMCryptoSessionTests, OEMCryptoMemoryLoadLicenseForHugeResponseLength) {
!kCheckStatus, !kUpdateCoreMessageSubstringValues);
}
TEST_F(OEMCryptoSessionTests,
TEST_P(OEMCryptoLicenseOverflowTest,
OEMCryptoMemoryLoadLicenseForHugeCoreMessageLength) {
TestLoadLicenseForHugeBufferLengths(
[](size_t message_size, LicenseRoundTrip* license_messages) {
@@ -3809,6 +3829,9 @@ TEST_F(OEMCryptoSessionTests,
!kCheckStatus, !kUpdateCoreMessageSubstringValues);
}
INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoLicenseOverflowTest,
Range<uint32_t>(kCurrentAPI - 1, kCurrentAPI + 1));
TEST_F(OEMCryptoSessionTests, OEMCryptoMemoryLoadRenewalForHugeResponseLength) {
auto oemcrypto_function = [&](size_t message_size) {
Session s;
@@ -5880,6 +5903,55 @@ TEST_F(OEMCryptoLoadsCertificate, TestMultipleRSAKeys) {
ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR());
}
// This tests the maximum number of DRM private keys that OEMCrypto can load
TEST_F(OEMCryptoLoadsCertificate, TestMaxDRMKeys) {
const size_t max_total_keys = GetResourceValue(kMaxTotalDRMPrivateKeys);
std::vector<std::unique_ptr<Session>> sessions;
std::vector<std::unique_ptr<LicenseRoundTrip>> licenses;
// It should be able to load up to kMaxTotalDRMPrivateKeys keys
for (size_t i = 0; i < max_total_keys; i++) {
sessions.push_back(std::unique_ptr<Session>(new Session()));
licenses.push_back(std::unique_ptr<LicenseRoundTrip>(
new LicenseRoundTrip(sessions[i].get())));
const size_t key_index = i % kTestRSAPKCS8PrivateKeys_2048.size();
encoded_rsa_key_.assign(kTestRSAPKCS8PrivateKeys_2048[key_index].begin(),
kTestRSAPKCS8PrivateKeys_2048[key_index].end());
ASSERT_NO_FATAL_FAILURE(CreateWrappedRSAKey());
ASSERT_NO_FATAL_FAILURE(sessions[i]->open());
ASSERT_NO_FATAL_FAILURE(sessions[i]->PreparePublicKey(
encoded_rsa_key_.data(), encoded_rsa_key_.size()));
ASSERT_NO_FATAL_FAILURE(
sessions[i]->InstallRSASessionTestKey(wrapped_rsa_key_));
}
// Attempts to load one more key than the kMaxTotalDRMPrivateKeys
Session s;
encoded_rsa_key_.assign(kTestRSAPKCS8PrivateKeyInfo3_3072,
kTestRSAPKCS8PrivateKeyInfo3_3072 +
sizeof(kTestRSAPKCS8PrivateKeyInfo3_3072));
Session ps;
ProvisioningRoundTrip provisioning_messages(&ps, encoded_rsa_key_);
provisioning_messages.PrepareSession(keybox_);
ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse());
ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse());
OEMCryptoResult result = provisioning_messages.LoadResponse();
// Key loading is allowed to fail due to resource restriction
if (result != OEMCrypto_SUCCESS) {
ASSERT_TRUE(result == OEMCrypto_ERROR_INSUFFICIENT_RESOURCES ||
result == OEMCrypto_ERROR_TOO_MANY_KEYS);
}
// Verifies that the DRM keys which are already loaded should still function
for (size_t i = 0; i < licenses.size(); i++) {
ASSERT_NO_FATAL_FAILURE(licenses[i]->SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(licenses[i]->CreateDefaultResponse());
ASSERT_NO_FATAL_FAILURE(licenses[i]->EncryptAndSignResponse());
ASSERT_EQ(OEMCrypto_SUCCESS, licenses[i]->LoadResponse());
ASSERT_NO_FATAL_FAILURE(sessions[i]->TestDecryptCTR());
}
}
// Devices that load certificates, should at least support RSA 2048 keys.
TEST_F(OEMCryptoLoadsCertificate, SupportsCertificatesAPI13) {
ASSERT_NE(0u,
@@ -10030,7 +10102,7 @@ TEST_P(OEMCryptoUsageTableTest, PSTLargeBuffer) {
}
// Verify that a usage entry with an invalid session cannot be used.
TEST_P(OEMCryptoUsageTableTest, UsageEntryWithInvalidSessionAPI17) {
TEST_P(OEMCryptoUsageTableTest, UsageEntryWithInvalidSession) {
std::string pst("pst");
LicenseWithUsageEntry entry;
entry.license_messages().set_pst(pst);
@@ -10048,6 +10120,13 @@ TEST_P(OEMCryptoUsageTableTest, UsageEntryWithInvalidSessionAPI17) {
entry.session().close();
ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION,
OEMCrypto_MoveEntry(entry.session().session_id(), 0));
}
// Verify that a usage entry with an invalid session cannot be used.
TEST_P(OEMCryptoUsageTableTest, ReuseUsageEntryWithInvalidSessionAPI17) {
std::string pst("pst");
LicenseWithUsageEntry entry;
entry.license_messages().set_pst(pst);
entry.session().open();
ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry());