|
|
|
|
@@ -10,14 +10,15 @@
|
|
|
|
|
#include <gmock/gmock.h>
|
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
|
|
|
|
|
#include "OEMCryptoCENC.h"
|
|
|
|
|
#include "cdm.h"
|
|
|
|
|
#include "cdm_test_printers.h"
|
|
|
|
|
#include "config_test_env.h"
|
|
|
|
|
#include "decryption_test_data.h"
|
|
|
|
|
#include "license_protocol.pb.h"
|
|
|
|
|
#include "license_request.h"
|
|
|
|
|
#include "log.h"
|
|
|
|
|
#include "oec_device_features.h"
|
|
|
|
|
#include "OEMCryptoCENC.h"
|
|
|
|
|
#include "platform.h"
|
|
|
|
|
#include "properties_ce.h"
|
|
|
|
|
#include "service_certificate.h"
|
|
|
|
|
@@ -32,6 +33,9 @@ using namespace wvcdm;
|
|
|
|
|
|
|
|
|
|
namespace widevine {
|
|
|
|
|
|
|
|
|
|
using video_widevine::LicenseError;
|
|
|
|
|
using video_widevine::SignedMessage;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
const int kHttpOk = 200;
|
|
|
|
|
@@ -56,7 +60,7 @@ const std::string kCencInitData = a2bs_hex(
|
|
|
|
|
// pssh data:
|
|
|
|
|
"08011a0d7769646576696e655f746573"
|
|
|
|
|
"74220f73747265616d696e675f636c69"
|
|
|
|
|
"7031");
|
|
|
|
|
"7039");
|
|
|
|
|
const std::string kCencPersistentInitData = a2bs_hex(
|
|
|
|
|
"00000040" // blob size
|
|
|
|
|
"70737368" // "pssh"
|
|
|
|
|
@@ -65,11 +69,11 @@ const std::string kCencPersistentInitData = a2bs_hex(
|
|
|
|
|
"00000020" // pssh data size
|
|
|
|
|
// pssh data:
|
|
|
|
|
"08011a0d7769646576696e655f746573"
|
|
|
|
|
"74220d6f66666c696e655f636c697032");
|
|
|
|
|
"74220d6f66666c696e655f636c697036");
|
|
|
|
|
const std::string kInvalidCencInitData = a2bs_hex(
|
|
|
|
|
"0000000c" // blob size
|
|
|
|
|
"61736466" // "asdf" (wrong box type)
|
|
|
|
|
"01020304"); // nonsense
|
|
|
|
|
"0000000c" // blob size
|
|
|
|
|
"61736466" // "asdf" (wrong box type)
|
|
|
|
|
"01020304"); // nonsense
|
|
|
|
|
const std::string kNonWidevineCencInitData = a2bs_hex(
|
|
|
|
|
"00000020" // blob size
|
|
|
|
|
"70737368" // "pssh"
|
|
|
|
|
@@ -203,8 +207,6 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener {
|
|
|
|
|
MOCK_METHOD2(onKeyStatusesChange,
|
|
|
|
|
void(const std::string& session_id, bool has_new_usable_key));
|
|
|
|
|
MOCK_METHOD1(onRemoveComplete, void(const std::string& session_id));
|
|
|
|
|
MOCK_METHOD2(onDeferredComplete,
|
|
|
|
|
void(const std::string& session_id, Cdm::Status error_code));
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
void SetUp() override {
|
|
|
|
|
@@ -326,12 +328,7 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener {
|
|
|
|
|
Cdm::InitDataType init_data_type,
|
|
|
|
|
std::string* session_id,
|
|
|
|
|
std::string* message) {
|
|
|
|
|
Cdm::Status status;
|
|
|
|
|
status = cdm_->setServiceCertificate(Cdm::kLicensingService,
|
|
|
|
|
config_.license_service_certificate());
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
|
|
|
|
|
status = cdm_->createSession(session_type, session_id);
|
|
|
|
|
Cdm::Status status = cdm_->createSession(session_type, session_id);
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
|
|
|
|
|
std::string init_data;
|
|
|
|
|
@@ -400,21 +397,27 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener {
|
|
|
|
|
Mock::VerifyAndClear(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string GetProvisioningResponse(const std::string& message) {
|
|
|
|
|
std::string reply;
|
|
|
|
|
std::string GetProvisioningResponse(const std::string& message,
|
|
|
|
|
size_t max_attempts = 10) {
|
|
|
|
|
std::string uri = config_.provisioning_server();
|
|
|
|
|
|
|
|
|
|
LOGV("GetProvisioningResponse: URI: %s", uri.c_str());
|
|
|
|
|
LOGV("GetProvisioningResponse: message:\n%s\n", b2a_hex(message).c_str());
|
|
|
|
|
|
|
|
|
|
std::string reply;
|
|
|
|
|
uri += "&signedRequest=" + message;
|
|
|
|
|
FetchCertificate(uri, &reply);
|
|
|
|
|
if (HasFatalFailure()) {
|
|
|
|
|
LOGE("GetProvisioningResponse: Failed.");
|
|
|
|
|
return "";
|
|
|
|
|
// TODO(b/139361531): Remove loop once provisioning service is stable.
|
|
|
|
|
for (size_t attempt = 1; attempt <= max_attempts; attempt++) {
|
|
|
|
|
FetchCertificate(uri, &reply);
|
|
|
|
|
if (HasFatalFailure()) {
|
|
|
|
|
LOGE("Failed to get provisioning response: attempt = %zu", attempt);
|
|
|
|
|
reply.clear();
|
|
|
|
|
continue;
|
|
|
|
|
} else {
|
|
|
|
|
LOGV("GetProvisioningResponse: response:\n%s\n", reply.c_str());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGV("GetProvisioningResponse: response:\n%s\n", reply.c_str());
|
|
|
|
|
return reply;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -458,19 +461,14 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener {
|
|
|
|
|
|
|
|
|
|
struct DecryptParam {
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
|
|
DecryptParam(
|
|
|
|
|
const std::string& short_name_param,
|
|
|
|
|
Cdm::InitDataType init_data_type_param,
|
|
|
|
|
const std::vector<uint8_t>& key_id_param,
|
|
|
|
|
const uint8_t* iv_param,
|
|
|
|
|
size_t iv_size_param,
|
|
|
|
|
Cdm::EncryptionScheme scheme_param,
|
|
|
|
|
const Cdm::Pattern& pattern_param,
|
|
|
|
|
const uint8_t* input_param,
|
|
|
|
|
size_t input_size_param,
|
|
|
|
|
const uint8_t* output_param,
|
|
|
|
|
size_t output_size_param)
|
|
|
|
|
DecryptParam(const std::string& short_name_param,
|
|
|
|
|
Cdm::InitDataType init_data_type_param,
|
|
|
|
|
const std::vector<uint8_t>& key_id_param,
|
|
|
|
|
const uint8_t* iv_param, size_t iv_size_param,
|
|
|
|
|
Cdm::EncryptionScheme scheme_param,
|
|
|
|
|
const Cdm::Pattern& pattern_param, const uint8_t* input_param,
|
|
|
|
|
size_t input_size_param, const uint8_t* output_param,
|
|
|
|
|
size_t output_size_param)
|
|
|
|
|
: short_name(short_name_param),
|
|
|
|
|
init_data_type(init_data_type_param),
|
|
|
|
|
key_id(&key_id_param),
|
|
|
|
|
@@ -827,12 +825,7 @@ TEST_F(CdmTest, CreateSession) {
|
|
|
|
|
TEST_F(CdmTest, GenerateRequest) {
|
|
|
|
|
EnsureProvisioned();
|
|
|
|
|
std::string session_id;
|
|
|
|
|
Cdm::Status status;
|
|
|
|
|
status = cdm_->setServiceCertificate(Cdm::kLicensingService,
|
|
|
|
|
config_.license_service_certificate());
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
|
|
|
|
|
status = cdm_->createSession(Cdm::kTemporary, &session_id);
|
|
|
|
|
Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id);
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
|
|
|
|
|
// Generate a license request for CENC.
|
|
|
|
|
@@ -923,11 +916,6 @@ TEST_F(CdmTest, Update) {
|
|
|
|
|
EnsureProvisioned();
|
|
|
|
|
std::string session_id;
|
|
|
|
|
std::string message;
|
|
|
|
|
Cdm::Status status;
|
|
|
|
|
status = cdm_->setServiceCertificate(Cdm::kLicensingService,
|
|
|
|
|
config_.license_service_certificate());
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
|
|
|
|
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest(
|
|
|
|
|
Cdm::kTemporary, Cdm::kCenc, &session_id, &message));
|
|
|
|
|
|
|
|
|
|
@@ -938,7 +926,7 @@ TEST_F(CdmTest, Update) {
|
|
|
|
|
|
|
|
|
|
// Update the session.
|
|
|
|
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id, true));
|
|
|
|
|
status = updateWithRetry(session_id, response);
|
|
|
|
|
Cdm::Status status = updateWithRetry(session_id, response);
|
|
|
|
|
EXPECT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
Mock::VerifyAndClear(this);
|
|
|
|
|
|
|
|
|
|
@@ -950,6 +938,35 @@ TEST_F(CdmTest, Update) {
|
|
|
|
|
status = updateWithRetry(session_id, "");
|
|
|
|
|
EXPECT_EQ(Cdm::kTypeError, status);
|
|
|
|
|
|
|
|
|
|
// Try updating with a rejected device certificate.
|
|
|
|
|
{
|
|
|
|
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id, true)).Times(0);
|
|
|
|
|
|
|
|
|
|
LicenseError license_error;
|
|
|
|
|
std::string error_msg;
|
|
|
|
|
SignedMessage signed_message;
|
|
|
|
|
signed_message.set_type(SignedMessage::ERROR_RESPONSE);
|
|
|
|
|
std::string error_response;
|
|
|
|
|
|
|
|
|
|
// Invalid device certificate
|
|
|
|
|
license_error.set_error_code(LicenseError::INVALID_DRM_DEVICE_CERTIFICATE);
|
|
|
|
|
license_error.SerializeToString(&error_msg);
|
|
|
|
|
signed_message.set_msg(error_msg);
|
|
|
|
|
signed_message.SerializeToString(&error_response);
|
|
|
|
|
status = updateWithRetry(session_id, error_response);
|
|
|
|
|
EXPECT_EQ(Cdm::kNeedsDeviceCertificate, status);
|
|
|
|
|
|
|
|
|
|
// Revoked device certificate
|
|
|
|
|
license_error.set_error_code(LicenseError::REVOKED_DRM_DEVICE_CERTIFICATE);
|
|
|
|
|
license_error.SerializeToString(&error_msg);
|
|
|
|
|
signed_message.set_msg(error_msg);
|
|
|
|
|
signed_message.SerializeToString(&error_response);
|
|
|
|
|
status = updateWithRetry(session_id, error_response);
|
|
|
|
|
EXPECT_EQ(Cdm::kUnexpectedError, status);
|
|
|
|
|
|
|
|
|
|
Mock::VerifyAndClear(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create a new session and try updating before generating a request.
|
|
|
|
|
status = cdm_->createSession(Cdm::kTemporary, &session_id);
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
@@ -987,17 +1004,12 @@ TEST_F(CdmTest, LoadTemporary) {
|
|
|
|
|
EnsureProvisioned();
|
|
|
|
|
std::string session_id;
|
|
|
|
|
std::string response;
|
|
|
|
|
Cdm::Status status;
|
|
|
|
|
status = cdm_->setServiceCertificate(Cdm::kLicensingService,
|
|
|
|
|
config_.license_service_certificate());
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
|
|
|
|
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
|
|
|
|
|
Cdm::kTemporary, Cdm::kCenc, &session_id, &response));
|
|
|
|
|
|
|
|
|
|
// Update the temporary session.
|
|
|
|
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id, true));
|
|
|
|
|
status = updateWithRetry(session_id, response);
|
|
|
|
|
Cdm::Status status = updateWithRetry(session_id, response);
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
Mock::VerifyAndClear(this);
|
|
|
|
|
|
|
|
|
|
@@ -1014,14 +1026,13 @@ TEST_F(CdmTest, LoadPersistent) {
|
|
|
|
|
EnsureProvisioned();
|
|
|
|
|
std::string session_id;
|
|
|
|
|
std::string response;
|
|
|
|
|
Cdm::Status status;
|
|
|
|
|
|
|
|
|
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
|
|
|
|
|
Cdm::kPersistentLicense, Cdm::kCenc, &session_id, &response));
|
|
|
|
|
|
|
|
|
|
// Update the persistent session.
|
|
|
|
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id, true));
|
|
|
|
|
status = updateWithRetry(session_id, response);
|
|
|
|
|
Cdm::Status status = updateWithRetry(session_id, response);
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
Mock::VerifyAndClear(this);
|
|
|
|
|
|
|
|
|
|
@@ -1786,11 +1797,8 @@ TEST_F(CdmTest, RequestPersistentLicenseWithWrongInitData) {
|
|
|
|
|
// Generate a request for a persistent license without using the correct
|
|
|
|
|
// persistent content init data.
|
|
|
|
|
std::string session_id;
|
|
|
|
|
Cdm::Status status;
|
|
|
|
|
status = cdm_->setServiceCertificate(Cdm::kLicensingService,
|
|
|
|
|
config_.license_service_certificate());
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
status = cdm_->createSession(Cdm::kPersistentLicense, &session_id);
|
|
|
|
|
Cdm::Status status =
|
|
|
|
|
cdm_->createSession(Cdm::kPersistentLicense, &session_id);
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
|
|
|
|
|
std::string message;
|
|
|
|
|
@@ -1809,11 +1817,7 @@ TEST_F(CdmTest, DISABLED_RequestTemporaryLicenseWithWrongInitData) {
|
|
|
|
|
EnsureProvisioned();
|
|
|
|
|
// Generate a request for a temporary license using persistent init data.
|
|
|
|
|
std::string session_id;
|
|
|
|
|
Cdm::Status status;
|
|
|
|
|
status = cdm_->setServiceCertificate(Cdm::kLicensingService,
|
|
|
|
|
config_.license_service_certificate());
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
status = cdm_->createSession(Cdm::kTemporary, &session_id);
|
|
|
|
|
Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id);
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
|
|
|
|
|
std::string message;
|
|
|
|
|
@@ -2022,9 +2026,6 @@ TEST_F(CdmTest, HandlesKeyRotationWithOnlyOneLicenseRequest) {
|
|
|
|
|
// The CreateSessionAndX helpers need to be reworked so this function can use
|
|
|
|
|
// them.
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess,
|
|
|
|
|
cdm_->setServiceCertificate(Cdm::kLicensingService,
|
|
|
|
|
config_.license_service_certificate()));
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id));
|
|
|
|
|
|
|
|
|
|
// Generate a license request for the 1st entitlement init data.
|
|
|
|
|
@@ -2049,77 +2050,92 @@ TEST_F(CdmTest, HandlesKeyRotationWithOnlyOneLicenseRequest) {
|
|
|
|
|
Mock::VerifyAndClear(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set up decrypt input and output.
|
|
|
|
|
Cdm::InputBuffer input;
|
|
|
|
|
input.data = kInput;
|
|
|
|
|
input.data_length = kInputSize;
|
|
|
|
|
input.encryption_scheme = Cdm::kAesCtr;
|
|
|
|
|
input.pattern = kPatternNone;
|
|
|
|
|
// Set up subsample
|
|
|
|
|
Cdm::Subsample subsample;
|
|
|
|
|
subsample.protected_bytes = kInputSize;
|
|
|
|
|
|
|
|
|
|
Cdm::OutputBuffer output;
|
|
|
|
|
std::vector<uint8_t> output_buffer(input.data_length);
|
|
|
|
|
output.data = &(output_buffer[0]);
|
|
|
|
|
output.data_length = output_buffer.size();
|
|
|
|
|
// Set up sample
|
|
|
|
|
Cdm::Sample sample;
|
|
|
|
|
sample.input.data = kInput;
|
|
|
|
|
sample.input.data_length = subsample.protected_bytes;
|
|
|
|
|
sample.input.subsamples = &subsample;
|
|
|
|
|
sample.input.subsamples_length = 1;
|
|
|
|
|
std::vector<uint8_t> output_buffer(sample.input.data_length);
|
|
|
|
|
sample.output.data = output_buffer.data();
|
|
|
|
|
sample.output.data_length = output_buffer.size();
|
|
|
|
|
|
|
|
|
|
// Set up batch to decrypt
|
|
|
|
|
Cdm::DecryptionBatch batch;
|
|
|
|
|
batch.samples = &sample;
|
|
|
|
|
batch.samples_length = 1;
|
|
|
|
|
batch.encryption_scheme = Cdm::kAesCtr;
|
|
|
|
|
|
|
|
|
|
// Attempt multiple decrypts with a key from the first license.
|
|
|
|
|
input.key_id = kKeyIdEntitlement1.data();
|
|
|
|
|
input.key_id_length = kKeyIdEntitlement1.size();
|
|
|
|
|
input.iv = kIvEntitlement1;
|
|
|
|
|
input.iv_length = kIvEntitlement1Size;
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, cdm_->decrypt(input, output));
|
|
|
|
|
batch.key_id = kKeyIdEntitlement1.data();
|
|
|
|
|
batch.key_id_length = kKeyIdEntitlement1.size();
|
|
|
|
|
sample.input.iv = kIvEntitlement1;
|
|
|
|
|
sample.input.iv_length = kIvEntitlement1Size;
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, cdm_->decrypt(batch));
|
|
|
|
|
const std::vector<uint8_t> expected_output1(
|
|
|
|
|
kOutputEntitlement1, kOutputEntitlement1 + kOutputEntitlement1Size);
|
|
|
|
|
EXPECT_EQ(expected_output1, output_buffer);
|
|
|
|
|
memset(&(output_buffer[0]), 0, output_buffer.size());
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, cdm_->decrypt(input, output));
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, cdm_->decrypt(batch));
|
|
|
|
|
EXPECT_EQ(expected_output1, output_buffer);
|
|
|
|
|
|
|
|
|
|
// Load the second entitlement license using the first. This should not
|
|
|
|
|
// require any server roundtrip.
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess,
|
|
|
|
|
cdm_->loadEmbeddedKeys(session_id, Cdm::kCenc,
|
|
|
|
|
kCencEntitlementInitData2));
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, cdm_->loadEmbeddedKeys(session_id, Cdm::kCenc,
|
|
|
|
|
kCencEntitlementInitData2));
|
|
|
|
|
|
|
|
|
|
// Attempt multiple decrypts with a key from the second license.
|
|
|
|
|
input.key_id = kKeyIdEntitlement2.data();
|
|
|
|
|
input.key_id_length = kKeyIdEntitlement2.size();
|
|
|
|
|
input.iv = kIvEntitlement2;
|
|
|
|
|
input.iv_length = kIvEntitlement2Size;
|
|
|
|
|
batch.key_id = kKeyIdEntitlement2.data();
|
|
|
|
|
batch.key_id_length = kKeyIdEntitlement2.size();
|
|
|
|
|
sample.input.iv = kIvEntitlement2;
|
|
|
|
|
sample.input.iv_length = kIvEntitlement2Size;
|
|
|
|
|
memset(&(output_buffer[0]), 0, output_buffer.size());
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, cdm_->decrypt(input, output));
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, cdm_->decrypt(batch));
|
|
|
|
|
const std::vector<uint8_t> expected_output2(
|
|
|
|
|
kOutputEntitlement2, kOutputEntitlement2 + kOutputEntitlement2Size);
|
|
|
|
|
EXPECT_EQ(expected_output2, output_buffer);
|
|
|
|
|
memset(&(output_buffer[0]), 0, output_buffer.size());
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, cdm_->decrypt(input, output));
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, cdm_->decrypt(batch));
|
|
|
|
|
EXPECT_EQ(expected_output2, output_buffer);
|
|
|
|
|
|
|
|
|
|
// Attempt multiple decrypts with a key from the first license again.
|
|
|
|
|
input.key_id = kKeyIdEntitlement1.data();
|
|
|
|
|
input.key_id_length = kKeyIdEntitlement1.size();
|
|
|
|
|
input.iv = kIvEntitlement1;
|
|
|
|
|
input.iv_length = kIvEntitlement1Size;
|
|
|
|
|
batch.key_id = kKeyIdEntitlement1.data();
|
|
|
|
|
batch.key_id_length = kKeyIdEntitlement1.size();
|
|
|
|
|
sample.input.iv = kIvEntitlement1;
|
|
|
|
|
sample.input.iv_length = kIvEntitlement1Size;
|
|
|
|
|
memset(&(output_buffer[0]), 0, output_buffer.size());
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, cdm_->decrypt(input, output));
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, cdm_->decrypt(batch));
|
|
|
|
|
EXPECT_EQ(expected_output1, output_buffer);
|
|
|
|
|
memset(&(output_buffer[0]), 0, output_buffer.size());
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, cdm_->decrypt(input, output));
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, cdm_->decrypt(batch));
|
|
|
|
|
EXPECT_EQ(expected_output1, output_buffer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(CdmTest, ClearPlaybackWithoutAKeyId) {
|
|
|
|
|
TEST_F(CdmTest, ClearPlaybackWithoutAKeyIdOrIv) {
|
|
|
|
|
EnsureProvisioned();
|
|
|
|
|
|
|
|
|
|
Cdm::InputBuffer input;
|
|
|
|
|
Cdm::OutputBuffer output;
|
|
|
|
|
// Set up subsample
|
|
|
|
|
Cdm::Subsample subsample;
|
|
|
|
|
subsample.clear_bytes = kInputSize;
|
|
|
|
|
|
|
|
|
|
input.data = kInput;
|
|
|
|
|
input.data_length = kInputSize;
|
|
|
|
|
input.encryption_scheme = Cdm::kClear;
|
|
|
|
|
// Set up sample
|
|
|
|
|
Cdm::Sample sample;
|
|
|
|
|
sample.input.data = kInput;
|
|
|
|
|
sample.input.data_length = subsample.clear_bytes;
|
|
|
|
|
sample.input.subsamples = &subsample;
|
|
|
|
|
sample.input.subsamples_length = 1;
|
|
|
|
|
std::vector<uint8_t> output_buffer(sample.input.data_length);
|
|
|
|
|
sample.output.data = output_buffer.data();
|
|
|
|
|
sample.output.data_length = output_buffer.size();
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> output_buffer(input.data_length);
|
|
|
|
|
output.data = &(output_buffer[0]);
|
|
|
|
|
output.data_length = output_buffer.size();
|
|
|
|
|
// Set up batch to decrypt
|
|
|
|
|
Cdm::DecryptionBatch batch;
|
|
|
|
|
batch.samples = &sample;
|
|
|
|
|
batch.samples_length = 1;
|
|
|
|
|
|
|
|
|
|
// Note that the use of kInput for the expected output is not an error. This
|
|
|
|
|
// is clear playback, so the data should pass through unchanged.
|
|
|
|
|
@@ -2130,70 +2146,111 @@ TEST_F(CdmTest, ClearPlaybackWithoutAKeyId) {
|
|
|
|
|
cdm_->createSession(Cdm::kTemporary, &session_id);
|
|
|
|
|
|
|
|
|
|
// Decrypt without specifying a session or key ID should fail.
|
|
|
|
|
Cdm::Status status = cdm_->decrypt(input, output);
|
|
|
|
|
Cdm::Status status = cdm_->decrypt(batch);
|
|
|
|
|
EXPECT_EQ(Cdm::kNoKey, status);
|
|
|
|
|
|
|
|
|
|
// Decrypt with a known session should succeed.
|
|
|
|
|
status = cdm_->decrypt(session_id, input, output);
|
|
|
|
|
status = cdm_->decrypt(session_id, batch);
|
|
|
|
|
EXPECT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(CdmTest, EncryptedPlaybackWithoutALicense) {
|
|
|
|
|
EnsureProvisioned();
|
|
|
|
|
|
|
|
|
|
Cdm::InputBuffer input;
|
|
|
|
|
Cdm::OutputBuffer output;
|
|
|
|
|
// Set up subsample
|
|
|
|
|
Cdm::Subsample subsample;
|
|
|
|
|
subsample.protected_bytes = kInputSize;
|
|
|
|
|
|
|
|
|
|
input.key_id = kKeyIdCtr.data();
|
|
|
|
|
input.key_id_length = kKeyIdCtr.size();
|
|
|
|
|
input.iv = kIvCenc;
|
|
|
|
|
input.iv_length = kIvCencSize;
|
|
|
|
|
input.data = kInput;
|
|
|
|
|
input.data_length = kInputSize;
|
|
|
|
|
input.encryption_scheme = Cdm::kAesCtr;
|
|
|
|
|
// Set up sample
|
|
|
|
|
Cdm::Sample sample;
|
|
|
|
|
sample.input.iv = kIvCenc;
|
|
|
|
|
sample.input.iv_length = kIvCencSize;
|
|
|
|
|
sample.input.data = kInput;
|
|
|
|
|
sample.input.data_length = subsample.protected_bytes;
|
|
|
|
|
sample.input.subsamples = &subsample;
|
|
|
|
|
sample.input.subsamples_length = 1;
|
|
|
|
|
std::vector<uint8_t> output_buffer(sample.input.data_length);
|
|
|
|
|
sample.output.data = output_buffer.data();
|
|
|
|
|
sample.output.data_length = output_buffer.size();
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> output_buffer(input.data_length);
|
|
|
|
|
output.data = &(output_buffer[0]);
|
|
|
|
|
output.data_length = output_buffer.size();
|
|
|
|
|
// Set up batch to decrypt
|
|
|
|
|
Cdm::DecryptionBatch batch;
|
|
|
|
|
batch.samples = &sample;
|
|
|
|
|
batch.samples_length = 1;
|
|
|
|
|
batch.key_id = kKeyIdCtr.data();
|
|
|
|
|
batch.key_id_length = kKeyIdCtr.size();
|
|
|
|
|
batch.encryption_scheme = Cdm::kAesCtr;
|
|
|
|
|
|
|
|
|
|
// Create a session.
|
|
|
|
|
std::string session_id;
|
|
|
|
|
cdm_->createSession(Cdm::kTemporary, &session_id);
|
|
|
|
|
|
|
|
|
|
// Decrypt without specifying a session should fail.
|
|
|
|
|
Cdm::Status status = cdm_->decrypt(input, output);
|
|
|
|
|
Cdm::Status status = cdm_->decrypt(batch);
|
|
|
|
|
EXPECT_EQ(Cdm::kNoKey, status);
|
|
|
|
|
|
|
|
|
|
// Decrypt with a known session should fail.
|
|
|
|
|
status = cdm_->decrypt(session_id, input, output);
|
|
|
|
|
status = cdm_->decrypt(session_id, batch);
|
|
|
|
|
EXPECT_EQ(Cdm::kNoKey, status);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(CdmTest, GetEmptyMetrics) {
|
|
|
|
|
std::string metrics;
|
|
|
|
|
Cdm::Status status = cdm_->getMetrics(&metrics);
|
|
|
|
|
EXPECT_EQ(status, Cdm::kSuccess);
|
|
|
|
|
EXPECT_NE(metrics.length(), 0u);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(CdmTest, GetMetrics) {
|
|
|
|
|
EnsureProvisioned();
|
|
|
|
|
std::string ignored;
|
|
|
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
|
|
|
CreateSessionAndUpdate(Cdm::kTemporary, Cdm::kCenc, &ignored));
|
|
|
|
|
|
|
|
|
|
std::string metrics;
|
|
|
|
|
Cdm::Status status = cdm_->getMetrics(&metrics);
|
|
|
|
|
EXPECT_EQ(status, Cdm::kSuccess);
|
|
|
|
|
EXPECT_NE(metrics.length(), 0u);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_P(CdmTestWithDecryptParam, DecryptToClearBuffer) {
|
|
|
|
|
EnsureProvisioned();
|
|
|
|
|
DecryptParam param = GetParam();
|
|
|
|
|
|
|
|
|
|
Cdm::InputBuffer input;
|
|
|
|
|
Cdm::OutputBuffer output;
|
|
|
|
|
// Set up subsample
|
|
|
|
|
Cdm::Subsample subsample;
|
|
|
|
|
if (param.scheme == Cdm::kClear) {
|
|
|
|
|
subsample.clear_bytes = param.input_size;
|
|
|
|
|
} else {
|
|
|
|
|
subsample.protected_bytes = param.input_size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
input.key_id = param.key_id->data();
|
|
|
|
|
input.key_id_length = param.key_id->size();
|
|
|
|
|
input.iv = param.iv;
|
|
|
|
|
input.iv_length = param.iv_size;
|
|
|
|
|
input.data = param.input;
|
|
|
|
|
input.data_length = param.input_size;
|
|
|
|
|
input.encryption_scheme = param.scheme;
|
|
|
|
|
input.pattern = *param.pattern;
|
|
|
|
|
// Set up sample
|
|
|
|
|
Cdm::Sample sample;
|
|
|
|
|
sample.input.iv = param.iv;
|
|
|
|
|
sample.input.iv_length = param.iv_size;
|
|
|
|
|
sample.input.data = param.input;
|
|
|
|
|
sample.input.data_length = param.input_size;
|
|
|
|
|
sample.input.subsamples = &subsample;
|
|
|
|
|
sample.input.subsamples_length = 1;
|
|
|
|
|
std::vector<uint8_t> output_buffer(sample.input.data_length);
|
|
|
|
|
sample.output.data = output_buffer.data();
|
|
|
|
|
sample.output.data_length = output_buffer.size();
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> output_buffer(input.data_length);
|
|
|
|
|
output.data = &(output_buffer[0]);
|
|
|
|
|
output.data_length = output_buffer.size();
|
|
|
|
|
// Set up batch to decrypt
|
|
|
|
|
Cdm::DecryptionBatch batch;
|
|
|
|
|
batch.samples = &sample;
|
|
|
|
|
batch.samples_length = 1;
|
|
|
|
|
batch.key_id = param.key_id->data();
|
|
|
|
|
batch.key_id_length = param.key_id->size();
|
|
|
|
|
batch.pattern = *param.pattern;
|
|
|
|
|
batch.encryption_scheme = param.scheme;
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> expected_output(
|
|
|
|
|
param.output, param.output + param.output_size);
|
|
|
|
|
std::vector<uint8_t> expected_output(param.output,
|
|
|
|
|
param.output + param.output_size);
|
|
|
|
|
|
|
|
|
|
// Decrypt without keys loaded should fail.
|
|
|
|
|
Cdm::Status status = cdm_->decrypt(input, output);
|
|
|
|
|
Cdm::Status status = cdm_->decrypt(batch);
|
|
|
|
|
ASSERT_EQ(Cdm::kNoKey, status);
|
|
|
|
|
|
|
|
|
|
// Create a session with the right keys.
|
|
|
|
|
@@ -2202,39 +2259,31 @@ TEST_P(CdmTestWithDecryptParam, DecryptToClearBuffer) {
|
|
|
|
|
Cdm::kTemporary, param.init_data_type, &session_id));
|
|
|
|
|
|
|
|
|
|
// Decrypt should now succeed.
|
|
|
|
|
status = cdm_->decrypt(session_id, input, output);
|
|
|
|
|
status = cdm_->decrypt(session_id, batch);
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
EXPECT_EQ(expected_output, output_buffer);
|
|
|
|
|
|
|
|
|
|
// Decrypt should succeed even without specifying the session ID.
|
|
|
|
|
status = cdm_->decrypt(input, output);
|
|
|
|
|
status = cdm_->decrypt(batch);
|
|
|
|
|
ASSERT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
EXPECT_EQ(expected_output, output_buffer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
INSTANTIATE_TEST_CASE_P(CdmDecryptTest, CdmTestWithDecryptParam, Values(
|
|
|
|
|
DecryptParam("CENC 3.0 cenc Mode",
|
|
|
|
|
Cdm::kCenc, kKeyIdCtr, kIvCenc, kIvCencSize, Cdm::kAesCtr,
|
|
|
|
|
kPatternNone, kInput, kInputSize, kOutputCenc, kOutputCencSize),
|
|
|
|
|
DecryptParam("CENC 3.0 cens Mode",
|
|
|
|
|
Cdm::kCenc, kKeyIdCtr, kIvCens, kIvCensSize, Cdm::kAesCtr,
|
|
|
|
|
kPatternRecommended, kInput, kInputSize, kOutputCens,
|
|
|
|
|
kOutputCensSize),
|
|
|
|
|
DecryptParam("CENC 3.0 cbc1 Mode",
|
|
|
|
|
Cdm::kHls, kKeyIdCbc, kIvCbc1, kIvCbc1Size, Cdm::kAesCbc,
|
|
|
|
|
kPatternNone, kInput, kInputSize, kOutputCbc1, kOutputCbc1Size),
|
|
|
|
|
DecryptParam("CENC 3.0 cbcs Mode",
|
|
|
|
|
Cdm::kHls, kKeyIdCbc, kIvCbcs, kIvCbcsSize, Cdm::kAesCbc,
|
|
|
|
|
kPatternRecommended, kInput, kInputSize, kOutputCbcs,
|
|
|
|
|
kOutputCbcsSize),
|
|
|
|
|
DecryptParam("HLS Audio (CENC 3.0 cbcs Mode Without a Pattern)",
|
|
|
|
|
Cdm::kHls, kKeyIdCbc, kIvCbc1, kIvCbc1Size, Cdm::kAesCbc,
|
|
|
|
|
kPatternHlsAudio, kInput, kInputSize, kOutputCbc1,
|
|
|
|
|
kOutputCbc1Size),
|
|
|
|
|
DecryptParam("Clear Data w/ Known Key ID",
|
|
|
|
|
Cdm::kCenc, kKeyIdCtr, kIvCenc, kIvCencSize, Cdm::kClear,
|
|
|
|
|
kPatternNone, kInput, kInputSize, kInput, kInputSize)
|
|
|
|
|
));
|
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
|
|
|
CdmDecryptTest, CdmTestWithDecryptParam,
|
|
|
|
|
Values(DecryptParam("CENC 3.0 cenc Mode", Cdm::kCenc, kKeyIdCtr, kIvCenc,
|
|
|
|
|
kIvCencSize, Cdm::kAesCtr, kPatternNone, kInput,
|
|
|
|
|
kInputSize, kOutputCenc, kOutputCencSize),
|
|
|
|
|
DecryptParam("CENC 3.0 cbcs Mode", Cdm::kHls, kKeyIdCbc, kIvCbcs,
|
|
|
|
|
kIvCbcsSize, Cdm::kAesCbc, kPatternRecommended, kInput,
|
|
|
|
|
kInputSize, kOutputCbcs, kOutputCbcsSize),
|
|
|
|
|
DecryptParam("HLS Audio (CENC 3.0 cbcs Mode Without a Pattern)",
|
|
|
|
|
Cdm::kHls, kKeyIdCbc, kIvCbc1, kIvCbc1Size,
|
|
|
|
|
Cdm::kAesCbc, kPatternHlsAudio, kInput, kInputSize,
|
|
|
|
|
kOutputCbc1, kOutputCbc1Size),
|
|
|
|
|
DecryptParam("Clear Data w/ Known Key ID", Cdm::kCenc, kKeyIdCtr,
|
|
|
|
|
kIvCenc, kIvCencSize, Cdm::kClear, kPatternNone, kInput,
|
|
|
|
|
kInputSize, kInput, kInputSize)));
|
|
|
|
|
|
|
|
|
|
// TODO (b/119200745):
|
|
|
|
|
// add infrastructure to test secure buffer decrypt for some platforms
|
|
|
|
|
@@ -2276,11 +2325,8 @@ TEST_F(CdmIndividualizationTest, BasicFlow) {
|
|
|
|
|
g_host->remove(kDeviceCertFileName);
|
|
|
|
|
|
|
|
|
|
// Provision the device
|
|
|
|
|
Cdm::Status status = cdm_->setServiceCertificate(
|
|
|
|
|
Cdm::kProvisioningService, config_.provisioning_service_certificate());
|
|
|
|
|
EXPECT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
std::string message;
|
|
|
|
|
status = cdm_->getProvisioningRequest(&message);
|
|
|
|
|
Cdm::Status status = cdm_->getProvisioningRequest(&message);
|
|
|
|
|
EXPECT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
std::string reply = GetProvisioningResponse(message);
|
|
|
|
|
ASSERT_FALSE(reply.empty());
|
|
|
|
|
@@ -2289,9 +2335,6 @@ TEST_F(CdmIndividualizationTest, BasicFlow) {
|
|
|
|
|
|
|
|
|
|
// We should now be able to create a session and generate a request.
|
|
|
|
|
std::string session_id;
|
|
|
|
|
status = cdm_->setServiceCertificate(Cdm::kLicensingService,
|
|
|
|
|
config_.license_service_certificate());
|
|
|
|
|
EXPECT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest(
|
|
|
|
|
Cdm::kTemporary, Cdm::kCenc, &session_id, &message));
|
|
|
|
|
|
|
|
|
|
@@ -2313,11 +2356,8 @@ TEST_F(CdmIndividualizationTest, IsProvisioned) {
|
|
|
|
|
EXPECT_FALSE(cdm_->isProvisioned());
|
|
|
|
|
|
|
|
|
|
// Provision the device
|
|
|
|
|
Cdm::Status status = cdm_->setServiceCertificate(
|
|
|
|
|
Cdm::kProvisioningService, config_.provisioning_service_certificate());
|
|
|
|
|
EXPECT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
std::string message;
|
|
|
|
|
status = cdm_->getProvisioningRequest(&message);
|
|
|
|
|
Cdm::Status status = cdm_->getProvisioningRequest(&message);
|
|
|
|
|
EXPECT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
std::string reply = GetProvisioningResponse(message);
|
|
|
|
|
ASSERT_FALSE(reply.empty());
|
|
|
|
|
@@ -2336,11 +2376,8 @@ TEST_F(CdmIndividualizationTest, RemoveProvisioning) {
|
|
|
|
|
EXPECT_FALSE(cdm_->isProvisioned());
|
|
|
|
|
|
|
|
|
|
// Provision the device
|
|
|
|
|
Cdm::Status status = cdm_->setServiceCertificate(
|
|
|
|
|
Cdm::kProvisioningService, config_.provisioning_service_certificate());
|
|
|
|
|
EXPECT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
std::string message;
|
|
|
|
|
status = cdm_->getProvisioningRequest(&message);
|
|
|
|
|
Cdm::Status status = cdm_->getProvisioningRequest(&message);
|
|
|
|
|
EXPECT_EQ(Cdm::kSuccess, status);
|
|
|
|
|
std::string reply = GetProvisioningResponse(message);
|
|
|
|
|
ASSERT_FALSE(reply.empty());
|
|
|
|
|
@@ -2363,7 +2400,7 @@ TEST_F(CdmIndividualizationTest, NoCreateSessionWithoutProvisioning) {
|
|
|
|
|
|
|
|
|
|
std::string session_id;
|
|
|
|
|
EXPECT_EQ(Cdm::kNeedsDeviceCertificate,
|
|
|
|
|
cdm_->createSession(Cdm::kTemporary, &session_id));
|
|
|
|
|
cdm_->createSession(Cdm::kTemporary, &session_id));
|
|
|
|
|
EXPECT_THAT(session_id, IsEmpty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|