Group license support

This commit is contained in:
Lu Chen
2021-03-05 16:09:59 -08:00
parent 00785b2ccd
commit 5c81c1aa9e
30 changed files with 1236 additions and 585 deletions

View File

@@ -12,19 +12,11 @@
#include "cas_status.h"
#include "cas_util.h"
#include "crypto_key.h"
#include "device_files.pb.h"
#include "license_protocol.pb.h"
#include "mock_crypto_session.h"
#include "string_conversions.h"
// Prototype for ExtractEntitlementKeys. This prototype is added here to allow
// this method to be unit tested without being added to CasLicense header.
namespace wvcas {
std::vector<wvcas::CryptoKey> ExtractEntitlementKeys(
const video_widevine::License& license);
} // namespace wvcas
using ::testing::_;
using ::testing::AllOf;
using ::testing::DoAll;
@@ -211,41 +203,6 @@ std::string CreateLicenseFileData() {
return hashed_file.SerializeAsString();
}
TEST(CasLicenseUtilityTest, ExtractEntitlementKeys) {
video_widevine::License license;
auto* key = license.add_key();
key->set_type(video_widevine::License::KeyContainer::ENTITLEMENT);
key->set_id(kKeyIDVideo);
key->set_iv(kKeyVideoIV);
key->mutable_key_control()->set_key_control_block(kKeyControlVideo);
key->mutable_key_control()->set_iv(kKeyControlIVVideo);
key->set_track_label(kTrackTypeVideo);
key = license.add_key();
key->set_type(video_widevine::License::KeyContainer::ENTITLEMENT);
key->set_id(kKeyIDAudio);
key->set_iv(kKeyAudioIV);
key->mutable_key_control()->set_key_control_block(kKeyControlAudio);
key->mutable_key_control()->set_iv(kKeyControlIVAudio);
key->set_track_label(kTrackTypeAudio);
std::vector<wvcas::CryptoKey> keys = wvcas::ExtractEntitlementKeys(license);
ASSERT_EQ(2, keys.size());
EXPECT_EQ(kKeyIDVideo, keys[0].key_id());
EXPECT_EQ(kKeyVideoIV, keys[0].key_data_iv());
EXPECT_EQ(kKeyControlVideo, keys[0].key_control());
EXPECT_EQ(kKeyControlIVVideo, keys[0].key_control_iv());
EXPECT_EQ(kTrackTypeVideo, keys[0].track_label());
EXPECT_EQ(kKeyIDAudio, keys[1].key_id());
EXPECT_EQ(kKeyAudioIV, keys[1].key_data_iv());
EXPECT_EQ(kKeyControlAudio, keys[1].key_control());
EXPECT_EQ(kKeyControlIVAudio, keys[1].key_control_iv());
EXPECT_EQ(kTrackTypeAudio, keys[1].track_label());
}
TEST_F(CasLicenseTest, GenerateDeviceProvisioningRequest) {
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
TestCasLicense cas_license;
@@ -409,24 +366,31 @@ TEST_F(CasLicenseTest, HandleEntitlementResponse) {
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
EXPECT_CALL(*cas_license.policy_engine_, CanPersist())
.WillOnce(Return(false));
status = cas_license.HandleEntitlementResponse(entitlement_response, nullptr);
status = cas_license.HandleEntitlementResponse(entitlement_response,
/*content_id_filter=*/nullptr,
/*device_file=*/nullptr);
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
// Not a group license.
EXPECT_TRUE(cas_license.GetGroupId().empty());
EXPECT_TRUE(cas_license.GetContentIdList().empty());
EXPECT_FALSE(cas_license.IsGroupLicense());
EXPECT_FALSE(cas_license.IsMultiContentLicense());
// Valid with device file.
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
EXPECT_CALL(*cas_license.policy_engine_, CanPersist())
.WillOnce(Return(false));
std::string device_file;
status =
cas_license.HandleEntitlementResponse(entitlement_response, &device_file);
status = cas_license.HandleEntitlementResponse(
entitlement_response, /*content_id_filter=*/nullptr, &device_file);
EXPECT_TRUE(device_file.empty());
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
// Valid with device file and can_persist = true.
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
EXPECT_CALL(*cas_license.policy_engine_, CanPersist()).WillOnce(Return(true));
status =
cas_license.HandleEntitlementResponse(entitlement_response, &device_file);
status = cas_license.HandleEntitlementResponse(
entitlement_response, /*content_id_filter=*/nullptr, &device_file);
EXPECT_FALSE(device_file.empty());
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
@@ -632,6 +596,79 @@ TEST_F(CasLicenseTest, RestoreLicense) {
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
EXPECT_CALL(*cas_license.policy_engine_, UpdateLicense(_));
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
cas_license.HandleStoredLicense(wrapped_rsa_key_, license_file_data)
cas_license
.HandleStoredLicense(wrapped_rsa_key_, license_file_data,
/*content_id_filter=*/nullptr)
.status_code());
}
TEST_F(CasLicenseTest, HandleMultiContentEntitlementResponse) {
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
TestCasLicense cas_license;
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
cas_license.initialize(strict_mock_, nullptr).status_code());
EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _))
.WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError));
EXPECT_CALL(*strict_mock_, APIVersion(_))
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
EXPECT_CALL(*strict_mock_, GenerateNonce(_))
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
EXPECT_CALL(*strict_mock_,
PrepareAndSignLicenseRequest(_, NotNull(), NotNull()))
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
Return(wvcas::CasStatusCode::kNoError)));
EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _));
std::string serialized_entitlement_request;
wvcas::CasStatus status = cas_license.GenerateEntitlementRequest(
kInitializationData, device_certificate_, wrapped_rsa_key_,
wvcas::LicenseType::kStreaming, &serialized_entitlement_request);
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
// Create multi content entitlement response.
video_widevine::LicenseCategorySpec license_category_spec;
license_category_spec.set_license_category(
video_widevine::LicenseCategorySpec::MULTI_CONTENT_LICENSE);
license_category_spec.set_group_id("group_id");
video_widevine::License license;
*license.mutable_license_category_spec() = license_category_spec;
video_widevine::License::KeyContainer::KeyCategorySpec key_category_spec;
key_category_spec.set_content_id("content_id_1");
auto* key = license.add_key();
key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT);
*key->mutable_key_category_spec() = key_category_spec;
key = license.add_key();
key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT);
*key->mutable_key_category_spec() = key_category_spec;
key = license.add_key();
key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT);
key_category_spec.set_content_id("content_id_2");
*key->mutable_key_category_spec() = key_category_spec;
video_widevine::SignedMessage signed_message;
license.SerializeToString(signed_message.mutable_msg());
signed_message.set_type(video_widevine::SignedMessage::CAS_LICENSE);
signed_message.set_signature(kExpectedSignature);
signed_message.set_session_key(kExpectedSignature);
signed_message.set_oemcrypto_core_message(kCoreMessage);
std::string entitlement_response = signed_message.SerializeAsString();
EXPECT_CALL(*strict_mock_, DeriveKeysFromSessionKey(_, _, _, _, _, _))
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
EXPECT_CALL(*strict_mock_, LoadLicense(_, _, _))
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
EXPECT_CALL(*cas_license.policy_engine_, CanPersist()).WillOnce(Return(true));
status = cas_license.HandleEntitlementResponse(entitlement_response,
/*content_id_filter=*/nullptr,
/*device_file=*/nullptr);
EXPECT_EQ(status.status_code(), wvcas::CasStatusCode::kNoError);
// Not a group license.
EXPECT_EQ(cas_license.GetGroupId(), "group_id");
EXPECT_THAT(cas_license.GetContentIdList(),
testing::ElementsAre("content_id_1", "content_id_2"));
EXPECT_FALSE(cas_license.IsGroupLicense());
EXPECT_TRUE(cas_license.IsMultiContentLicense());
}

View File

@@ -49,12 +49,6 @@ static const std::string kOddWrappedKeyIv("odd_wrapped_content_key_iv");
static const std::string kEvenContentIv("even_content_iv");
static const std::string kOddContentIv("odd_content_iv");
// Defined in cas_license.cpp.
namespace wvcas {
extern std::vector<CryptoKey> ExtractKeyControlKeys(
const video_widevine::License& license);
} // namespace wvcas
// TODO(jfore): Add validation of arg->buffer based on type. Type is assumed to
// be clear.
MATCHER_P2(IsValidOutputBuffer, type, dest, "") {
@@ -271,12 +265,6 @@ class MockedOEMCrypto : public wvcas::OEMCryptoInterface {
const uint8_t* content_key_id,
size_t content_key_id_length,
OEMCryptoCipherMode cipher_mode));
MOCK_METHOD7(OEMCrypto_RefreshKeys,
OEMCryptoResult(OEMCrypto_SESSION session,
const uint8_t* message, size_t message_length,
const uint8_t* signature,
size_t signature_length, size_t num_keys,
const OEMCrypto_KeyRefreshObject* key_array));
MOCK_METHOD2(OEMCrypto_GetDeviceID,
OEMCryptoResult(uint8_t* deviceID, size_t* idLength));
MOCK_METHOD2(OEMCrypto_CreateEntitledKeySession,
@@ -937,56 +925,35 @@ TEST_F(CryptoSessionTest, SelectKeys) {
}
}
TEST_F(CryptoSessionTest, RefreshKeys) {
TestCryptoSession<StrictMock<MockedOEMCrypto> > crypto_session(
strict_oemcrypto_interface_);
EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Initialize());
EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Terminate());
EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_OpenSession(_))
TEST_F(CryptoSessionTest, LoadRenewal) {
TestCryptoSession<NiceMock<MockedOEMCrypto> > crypto_session(
nice_oemcrypto_interface_);
EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_))
.WillOnce(
DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS)));
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
crypto_session.initialize().status_code());
video_widevine::License license;
// Empty key array - no keys.
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
crypto_session
.RefreshKeys(license.SerializeAsString(), "signature",
std::vector<wvcas::CryptoKey>())
.status_code());
auto* key_1 = license.add_key();
key_1->set_type(video_widevine::License_KeyContainer::KEY_CONTROL);
auto* key_2 = license.add_key();
key_2->set_type(video_widevine::License_KeyContainer::KEY_CONTROL);
auto* key_3 = license.add_key();
key_3->set_type(video_widevine::License_KeyContainer::KEY_CONTROL);
std::vector<wvcas::CryptoKey> key_array =
wvcas::ExtractKeyControlKeys(license);
EXPECT_CALL(strict_oemcrypto_interface_,
OEMCrypto_RefreshKeys(_, _, _, _, _, 3, _))
const std::string signed_message("signed_message");
const std::string core_message("core_message");
const std::string signature("signature");
EXPECT_CALL(
nice_oemcrypto_interface_,
OEMCrypto_LoadRenewal(kOemcSessionId, NotNull(),
signed_message.size() + core_message.size(),
core_message.size(), NotNull(), signature.size()))
.WillOnce(Return(OEMCrypto_ERROR_SIGNATURE_FAILURE))
.WillOnce(Return(OEMCrypto_SUCCESS));
// OEMCrypto error.
ASSERT_EQ(
wvcas::CasStatusCode::kCryptoSessionError,
crypto_session
.RefreshKeys(license.SerializeAsString(), "signature", key_array)
.status_code());
ASSERT_EQ(wvcas::CasStatusCode::kCryptoSessionError,
crypto_session.LoadRenewal(signed_message, core_message, signature)
.status_code());
// Valid.
ASSERT_EQ(
wvcas::CasStatusCode::kNoError,
crypto_session
.RefreshKeys(license.SerializeAsString(), "signature", key_array)
.status_code());
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
crypto_session.LoadRenewal(signed_message, core_message, signature)
.status_code());
}
TEST_F(CryptoSessionTest, ReadUniqueId) {

View File

@@ -55,7 +55,7 @@ class EcmParserV2Test : public testing::Test {
void BuildEcm(bool with_rotation, bool content_iv_flag);
std::vector<uint8_t> ecm_data_;
std::unique_ptr<const wvcas::EcmParserV2> parser_;
std::unique_ptr<wvcas::EcmParserV2> parser_;
};
size_t EcmParserV2Test::ContentKeyIVSize(bool content_iv_flag) {

View File

@@ -14,6 +14,7 @@
namespace wvcas {
namespace {
using video_widevine::EcmGroupKeyData;
using video_widevine::EcmMetaData;
using video_widevine::EcmPayload;
using video_widevine::SignedEcmPayload;
@@ -84,7 +85,7 @@ TEST(EcmParserV3Test, CreateWithEvenKeySuccess) {
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_EQ(parser->version(), kEcmVersion);
@@ -121,7 +122,7 @@ TEST(EcmParserV3Test, CreateWithEvenOddKeysSuccess) {
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_TRUE(parser->rotation_enabled());
@@ -155,7 +156,7 @@ TEST(EcmParserV3Test, CreateWithOmittedOddKeyFieldsSuccess) {
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_TRUE(parser->rotation_enabled());
@@ -187,7 +188,7 @@ TEST(EcmParserV3Test, AgeRestrictionSuccess) {
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_EQ(parser->age_restriction(), expected_age_restriction);
@@ -206,7 +207,7 @@ TEST_P(EcmParserV3AgeRestrictionTest, ExpectedAgeRestriction) {
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_EQ(parser->age_restriction(), expected_age_restriction);
@@ -229,7 +230,7 @@ TEST_P(EcmParserV3CipherModeTest, ExpectedCipherMode) {
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_EQ(parser->crypto_mode(), expected);
@@ -252,7 +253,7 @@ TEST(EcmParserV3Test, FingerprintingSuccess) {
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_TRUE(parser->has_fingerprinting());
@@ -267,7 +268,7 @@ TEST(EcmParserV3Test, ServiceBlockingSuccess) {
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_TRUE(parser->has_service_blocking());
@@ -281,11 +282,126 @@ TEST(EcmParserV3Test, SignatureSuccess) {
signed_ecm_payload.set_signature(expected_signature);
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_EQ(parser->signature(), expected_signature);
}
TEST(EcmParserV3Test, SetGroupIdSuccess) {
const std::string group_id = "group_id";
const std::string group_id2 = "another_group";
EcmPayload ecm_payload;
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
group_key_data->set_group_id(group_id);
group_key_data = ecm_payload.add_group_key_data();
group_key_data->set_group_id(group_id2);
SignedEcmPayload signed_ecm_payload;
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_TRUE(parser->set_group_id(group_id));
EXPECT_TRUE(parser->set_group_id(group_id2));
}
TEST(EcmParserV3Test, SetUnknownGroupIdFail) {
EcmPayload ecm_payload;
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
group_key_data->set_group_id("group_id");
SignedEcmPayload signed_ecm_payload;
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_FALSE(parser->set_group_id("unknown"));
}
TEST(EcmParserV3Test, ParserWithGroupIdSuccess) {
const std::string group_id = "group_id";
EcmPayload ecm_payload;
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
group_key_data->set_group_id(group_id);
group_key_data->mutable_even_key_data()->set_entitlement_key_id(
kEntitlementId);
group_key_data->mutable_even_key_data()->set_wrapped_key_data(
kWrappedContentKey);
group_key_data->mutable_even_key_data()->set_content_iv(kContentIv);
group_key_data->mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv);
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId2);
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(
kWrappedContentKey2);
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv2);
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv2);
SignedEcmPayload signed_ecm_payload;
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
// If group Id is not set, the normal keys will be returned.
std::vector<uint8_t> result =
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId2);
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey2);
result = parser->content_iv(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv2);
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv2);
// Now set the group id.
EXPECT_TRUE(parser->set_group_id(group_id));
result = parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
result = parser->content_iv(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv);
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv);
}
TEST(EcmParserV3Test, ParserGroupKeysWithOmittedFieldsSuccess) {
const std::string group_id = "group_id";
EcmPayload ecm_payload;
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
group_key_data->set_group_id(group_id);
group_key_data->mutable_even_key_data()->set_entitlement_key_id(
kEntitlementId);
group_key_data->mutable_even_key_data()->set_wrapped_key_data(
kWrappedContentKey);
// Content IV and wrapped key iv is omitted in |group_key_data|/
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId2);
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(
kWrappedContentKey2);
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv2);
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv2);
SignedEcmPayload signed_ecm_payload;
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_TRUE(parser->set_group_id(group_id));
std::vector<uint8_t> result =
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
// Content IV and wrapped key iv are from normal non-group key.
result = parser->content_iv(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv2);
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv2);
}
} // namespace
} // namespace wvcas

View File

@@ -55,6 +55,11 @@ TEST(IntegrationTests, TestCasPluginEventPassing) {
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasPluginEventPassing"));
}
TEST(IntegrationTests, TestSessionFailWithoutProvisioning) {
EXPECT_EQ(kIntegrationTestPassed,
RunNamedTest("TestSessionFailWithoutProvisioning"));
}
TEST(IntegrationTests, TestUniqueIdQuery) {
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestUniqueIdQuery"));
}

View File

@@ -20,6 +20,7 @@
using ::testing::_;
using ::testing::DoAll;
using ::testing::NiceMock;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::StrictMock;
@@ -54,20 +55,27 @@ class MockLicense : public wvcas::CasLicense {
const std::string& wrapped_rsa_key,
wvcas::LicenseType license_type,
std::string* signed_license_request));
MOCK_METHOD2(HandleStoredLicense,
MOCK_METHOD3(HandleStoredLicense,
wvcas::CasStatus(const std::string& wrapped_rsa_key,
const std::string& license_file));
const std::string& license_file,
const std::string* content_id_filter));
MOCK_METHOD2(GenerateEntitlementRenewalRequest,
wvcas::CasStatus(const std::string& device_certificate,
std::string* signed_renewal_request));
MOCK_METHOD2(HandleEntitlementRenewalResponse,
wvcas::CasStatus(const std::string& renewal_response,
std::string* device_file));
MOCK_METHOD2(HandleEntitlementResponse,
MOCK_METHOD3(HandleEntitlementResponse,
wvcas::CasStatus(const std::string& entitlement_response,
const std::string* content_id_filter,
std::string* device_file));
MOCK_METHOD0(BeginDecryption, void());
MOCK_METHOD0(UpdateLicenseForLicenseRemove, void());
MOCK_METHOD(std::string, GetGroupId, (), (const, override));
MOCK_METHOD(std::vector<std::string>, GetContentIdList, (),
(const, override));
MOCK_METHOD(bool, IsMultiContentLicense, (), (const, override));
MOCK_METHOD(bool, IsGroupLicense, (), (const, override));
};
typedef StrictMock<MockLicense> StrictMockLicense;
@@ -136,8 +144,10 @@ class MockWidevineSession : public wvcas::WidevineCasSession {
public:
MockWidevineSession() {}
~MockWidevineSession() override {}
MOCK_METHOD2(processEcm, wvcas::CasStatus(const wvcas::CasEcm& ecm,
uint8_t parental_control_age));
MOCK_METHOD(wvcas::CasStatus, processEcm,
(const wvcas::CasEcm& ecm, uint8_t parental_control_age,
const std::string& license_group_id),
(override));
MOCK_METHOD2(HandleProcessEcm,
wvcas::CasStatus(const wvcas::WvCasSessionId& sessionId,
const wvcas::CasEcm& ecm));
@@ -238,6 +248,7 @@ TEST_F(WidevineCasTest, generateEntitlementRequest) {
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
cas_api.initialize(nullptr).status_code());
EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(false));
// Invalid parameter.
std::string request, init_data, license_id;
@@ -267,7 +278,7 @@ TEST_F(WidevineCasTest, generateEntitlementRequest) {
.WillRepeatedly(Return(mock_file));
EXPECT_CALL(*mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize));
EXPECT_CALL(*cas_api.license_, HandleStoredLicense(_, _))
EXPECT_CALL(*cas_api.license_, HandleStoredLicense)
.WillRepeatedly(Return(wvcas::CasStatus(
wvcas::CasStatusCode::kCasLicenseError, "forced failure")));
EXPECT_EQ(wvcas::CasStatusCode::kCasLicenseError,
@@ -281,7 +292,7 @@ TEST_F(WidevineCasTest, generateEntitlementRequest) {
EXPECT_CALL(*mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize));
// For expired license file, remove it successfully
// and return CasLicenseError.
EXPECT_CALL(*cas_api.license_, HandleStoredLicense(_, _))
EXPECT_CALL(*cas_api.license_, HandleStoredLicense)
.WillRepeatedly(Return(wvcas::CasStatus::OkStatus()));
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(true));
EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillRepeatedly(Return(true));
@@ -442,6 +453,7 @@ TEST_P(WidevineCasTest, ECMProcessing) {
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false));
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
cas_api.initialize(nullptr).status_code());
EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(false));
wvcas::WvCasSessionId video_sid;
wvcas::WvCasSessionId audio_sid;
@@ -487,10 +499,10 @@ TEST_P(WidevineCasTest, ECMProcessing) {
int expected_begin_decryption_calls = expected_process_ecm_calls * 2;
EXPECT_CALL(*reinterpret_cast<MockWidevineSession*>(video_session.get()),
processEcm(video_ecm, 0))
processEcm(video_ecm, 0, ""))
.Times(expected_process_ecm_calls);
EXPECT_CALL(*reinterpret_cast<MockWidevineSession*>(audio_session.get()),
processEcm(audio_ecm, 0))
processEcm(audio_ecm, 0, ""))
.Times(expected_process_ecm_calls);
EXPECT_CALL(*cas_api.license_, BeginDecryption())
.Times(expected_begin_decryption_calls);
@@ -507,7 +519,7 @@ TEST_P(WidevineCasTest, ECMProcessing) {
.WillOnce(Return(file_handle));
EXPECT_CALL(*file_handle, Read(_, _)).WillOnce(Return(file_data.size()));
EXPECT_CALL(*cas_api.license_, HandleStoredLicense(_, _));
EXPECT_CALL(*cas_api.license_, HandleStoredLicense);
EXPECT_EQ(
wvcas::CasStatusCode::kNoError,
cas_api.generateEntitlementRequest("init_data", &request, license_id)
@@ -520,12 +532,26 @@ TEST_P(WidevineCasTest, ECMProcessing) {
license_id);
} else {
// Empty response.
std::string init_data;
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, _))
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
// Initialize CaMediaId.
EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillOnce(Return(false));
EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest(_, _, _, _, _))
.WillRepeatedly(Return(wvcas::CasStatus::OkStatus()));
std::string request, init_data, license_id;
EXPECT_EQ(
wvcas::CasStatusCode::kNoError,
cas_api.handleEntitlementResponse("response", init_data).status_code());
cas_api.generateEntitlementRequest(init_data, &request, license_id)
.status_code());
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse)
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
std::string multi_content_license_info;
std::string group_license_info;
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
cas_api
.handleEntitlementResponse("response", init_data,
multi_content_license_info,
group_license_info)
.status_code());
}
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
@@ -589,17 +615,217 @@ TEST_F(WidevineCasTest, RemoveLicense) {
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
cas_api.RemoveLicense(mocked_file_name).status_code());
}
// Happy case: remove the in used license file
std::string hash;
std::string kBasePathPrefix = "/data/vendor/mediacas/IDM/widevine/";
hash.resize(SHA256_DIGEST_LENGTH);
const auto* input =
reinterpret_cast<const unsigned char*>(mocked_file_name.data());
auto* output = reinterpret_cast<unsigned char*>(&hash[0]);
SHA256(input, mocked_file_name.size(), output);
std::string full_filename = GenerateTestLicenseFileName(mocked_file_name);
TEST_F(WidevineCasTest, RemoveLicenseInUse) {
TestWidevineCas cas_api;
EXPECT_CALL(*cas_api.crypto_session_, initialize())
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
EXPECT_EQ(cas_api.initialize(nullptr).status_code(),
wvcas::CasStatusCode::kNoError);
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false));
// Initialize media_id_.
EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest)
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
std::string request, init_data, license_id;
EXPECT_EQ(cas_api.generateEntitlementRequest(init_data, &request, license_id)
.status_code(),
wvcas::CasStatusCode::kNoError);
// Install the license
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, _, NotNull()))
.WillOnce(DoAll(SetArgPointee<2>("device_file"),
Return(wvcas::CasStatus::OkStatus())));
EXPECT_CALL(*cas_api.license_, IsMultiContentLicense)
.WillRepeatedly(Return(false));
EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(false));
EXPECT_CALL(*cas_api.license_, GetGroupId).WillRepeatedly(Return(""));
std::string multi_content_license_info;
std::string group_license_info;
EXPECT_EQ(cas_api
.handleEntitlementResponse("response", license_id,
multi_content_license_info,
group_license_info)
.status_code(),
wvcas::CasStatusCode::kNoError);
EXPECT_FALSE(license_id.empty());
MockFile mock_file;
EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(true));
EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillRepeatedly(Return(true));
EXPECT_CALL(*cas_api.license_, UpdateLicenseForLicenseRemove()).Times(1);
EXPECT_EQ(cas_api.RemoveLicense(license_id + ".lic").status_code(),
wvcas::CasStatusCode::kNoError);
}
TEST_F(WidevineCasTest, handleMultiContentEntitlementResponse) {
TestWidevineCas cas_api;
EXPECT_CALL(*cas_api.crypto_session_, initialize())
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
EXPECT_EQ(cas_api.initialize(nullptr).status_code(),
wvcas::CasStatusCode::kNoError);
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false));
// Initialize media_id_.
EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest)
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
std::string request, init_data, license_id;
EXPECT_EQ(cas_api.generateEntitlementRequest(init_data, &request, license_id)
.status_code(),
wvcas::CasStatusCode::kNoError);
std::string license_group_id = "license_group_id";
std::vector<std::string> content_list = {"content1", "content2"};
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, _, NotNull()))
.WillOnce(DoAll(SetArgPointee<2>("device_file"),
Return(wvcas::CasStatus::OkStatus())));
EXPECT_CALL(*cas_api.license_, IsMultiContentLicense)
.WillRepeatedly(Return(true));
EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(false));
EXPECT_CALL(*cas_api.license_, GetGroupId)
.WillRepeatedly(Return(license_group_id));
EXPECT_CALL(*cas_api.license_, GetContentIdList)
.WillRepeatedly(Return(content_list));
std::string multi_content_license_info;
std::string group_license_info;
EXPECT_EQ(cas_api
.handleEntitlementResponse("response", license_id,
multi_content_license_info,
group_license_info)
.status_code(),
wvcas::CasStatusCode::kNoError);
std::string expected_license_id =
GenerateTestLicenseFileName(license_group_id);
expected_license_id = expected_license_id.substr(
0, expected_license_id.size() - strlen(kLicenseFileNameSuffix));
EXPECT_EQ(license_id, expected_license_id);
std::string expected_info;
expected_info.push_back(0);
expected_info.push_back(0);
expected_info.push_back(license_id.size());
expected_info.append(license_id);
expected_info.push_back(1);
expected_info.push_back(0);
expected_info.push_back(content_list[0].size());
expected_info.append(content_list[0]);
expected_info.push_back(1);
expected_info.push_back(0);
expected_info.push_back(content_list[1].size());
expected_info.append(content_list[1]);
EXPECT_EQ(multi_content_license_info, expected_info);
EXPECT_TRUE(group_license_info.empty());
}
TEST_F(WidevineCasTest, handleGroupEntitlementResponse) {
TestWidevineCas cas_api;
EXPECT_CALL(*cas_api.crypto_session_, initialize())
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
EXPECT_EQ(cas_api.initialize(nullptr).status_code(),
wvcas::CasStatusCode::kNoError);
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false));
// Initialize media_id_.
EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest)
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
std::string request, init_data, license_id;
EXPECT_EQ(cas_api.generateEntitlementRequest(init_data, &request, license_id)
.status_code(),
wvcas::CasStatusCode::kNoError);
std::string license_group_id = "license_group_id";
std::vector<std::string> content_list = {};
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, _, NotNull()))
.WillOnce(DoAll(SetArgPointee<2>("device_file"),
Return(wvcas::CasStatus::OkStatus())));
EXPECT_CALL(*cas_api.license_, IsMultiContentLicense)
.WillRepeatedly(Return(false));
EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(true));
EXPECT_CALL(*cas_api.license_, GetGroupId)
.WillRepeatedly(Return(license_group_id));
EXPECT_CALL(*cas_api.license_, GetContentIdList)
.WillRepeatedly(Return(content_list));
std::string multi_content_license_info;
std::string group_license_info;
EXPECT_EQ(cas_api
.handleEntitlementResponse("response", license_id,
multi_content_license_info,
group_license_info)
.status_code(),
wvcas::CasStatusCode::kNoError);
std::string expected_license_id =
GenerateTestLicenseFileName(license_group_id);
expected_license_id = expected_license_id.substr(
0, expected_license_id.size() - strlen(kLicenseFileNameSuffix));
EXPECT_EQ(license_id, expected_license_id);
std::string expected_info;
expected_info.push_back(0);
expected_info.push_back(0);
expected_info.push_back(license_id.size());
expected_info.append(license_id);
expected_info.push_back(1);
expected_info.push_back(0);
expected_info.push_back(license_group_id.size());
expected_info.append(license_group_id);
EXPECT_EQ(group_license_info, expected_info);
EXPECT_TRUE(multi_content_license_info.empty());
}
TEST_F(WidevineCasTest, ECMProcessingWithGroupId) {
TestWidevineCas cas_api;
EXPECT_CALL(*cas_api.crypto_session_, initialize())
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
EXPECT_EQ(cas_api.initialize(nullptr).status_code(),
wvcas::CasStatusCode::kNoError);
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false));
// Initialize media_id_.
EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest)
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
std::string request, init_data, license_id;
EXPECT_EQ(cas_api.generateEntitlementRequest(init_data, &request, license_id)
.status_code(),
wvcas::CasStatusCode::kNoError);
// Install license
const std::string license_group_id = "license_group_id";
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse)
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
EXPECT_CALL(*cas_api.license_, IsMultiContentLicense)
.WillRepeatedly(Return(false));
EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(true));
EXPECT_CALL(*cas_api.license_, GetGroupId)
.WillRepeatedly(Return(license_group_id));
std::string multi_content_license_info;
std::string group_license_info;
EXPECT_EQ(cas_api
.handleEntitlementResponse("response", license_id,
multi_content_license_info,
group_license_info)
.status_code(),
wvcas::CasStatusCode::kNoError);
// Init a session
wvcas::WvCasSessionId sid;
EXPECT_CALL(*(cas_api.crypto_session_), CreateEntitledKeySession(_))
.WillOnce(
DoAll(SetArgPointee<0>(10), Return(wvcas::CasStatus::OkStatus())));
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
cas_api.RemoveLicense(full_filename).status_code());
cas_api.openSession(&sid).status_code());
wvcas::CasSessionPtr session =
wvcas::WidevineCasSessionMap::instance().GetSession(sid);
ASSERT_TRUE(session != nullptr);
const wvcas::CasEcm ecm = {1, 2, 3};
// It is expected that process ecm with group_id
EXPECT_CALL(*reinterpret_cast<MockWidevineSession*>(session.get()),
processEcm(ecm, 0, license_group_id))
.Times(1);
EXPECT_CALL(*cas_api.license_, BeginDecryption());
EXPECT_EQ(cas_api.processEcm(sid, ecm).status_code(),
wvcas::CasStatusCode::kNoError);
EXPECT_CALL(*(cas_api.crypto_session_), RemoveEntitledKeySession(sid));
}

View File

@@ -40,6 +40,7 @@ static const char kOddWrappedKeyIv[] = "odd_wrapped_content_key_iv";
static const char kEvenContentIv[] = "even_content_iv";
static const char kOddContentIv[] = "odd_content_iv";
static const OEMCrypto_SESSION kEntitledKeySessionId = 0x1111;
constexpr char kEmptyGroupId[] = "";
MATCHER(IsValidKeyEvenSlotData, "") {
if (nullptr == arg) {
@@ -125,6 +126,7 @@ class MockEcmParser : public wvcas::EcmParser {
std::vector<uint8_t>(wvcas::KeySlotId id));
MOCK_CONST_METHOD1(wrapped_key_iv, std::vector<uint8_t>(wvcas::KeySlotId id));
MOCK_CONST_METHOD1(content_iv, std::vector<uint8_t>(wvcas::KeySlotId id));
MOCK_METHOD(bool, set_group_id, (const std::string& group_id), (override));
MOCK_CONST_METHOD0(has_fingerprinting, bool());
MOCK_CONST_METHOD0(fingerprinting, video_widevine::Fingerprinting());
MOCK_CONST_METHOD0(has_service_blocking, bool());
@@ -145,7 +147,7 @@ class TestCasSession : public wvcas::WidevineCasSession {
TestCasSession() {}
virtual ~TestCasSession() {}
std::unique_ptr<const wvcas::EcmParser> getEcmParser(
std::unique_ptr<wvcas::EcmParser> getEcmParser(
const wvcas::CasEcm& ecm) const override;
std::vector<uint8_t> entitlement_key_id(wvcas::KeySlotId id) const {
@@ -222,7 +224,7 @@ class TestCasSession : public wvcas::WidevineCasSession {
video_widevine::ServiceBlocking service_blocking_;
};
std::unique_ptr<const wvcas::EcmParser> TestCasSession::getEcmParser(
std::unique_ptr<wvcas::EcmParser> TestCasSession::getEcmParser(
const wvcas::CasEcm& ecm) const {
std::unique_ptr<NiceMock<MockEcmParser>> mock_ecm_parser(
new NiceMock<MockEcmParser>);
@@ -241,6 +243,7 @@ std::unique_ptr<const wvcas::EcmParser> TestCasSession::getEcmParser(
.WillByDefault(Invoke(this, &TestCasSession::wrapped_key_iv));
ON_CALL(*mock_ecm_parser, content_iv(_))
.WillByDefault(Invoke(this, &TestCasSession::content_iv));
ON_CALL(*mock_ecm_parser, set_group_id(_)).WillByDefault(Return(true));
ON_CALL(*mock_ecm_parser, has_fingerprinting())
.WillByDefault(Return(fingerprinting_.has_control()));
ON_CALL(*mock_ecm_parser, fingerprinting())
@@ -249,7 +252,7 @@ std::unique_ptr<const wvcas::EcmParser> TestCasSession::getEcmParser(
.WillByDefault(Return(service_blocking_.device_groups_size() > 0));
ON_CALL(*mock_ecm_parser, service_blocking())
.WillByDefault(Return(service_blocking_));
return std::unique_ptr<const wvcas::EcmParser>(mock_ecm_parser.release());
return std::unique_ptr<wvcas::EcmParser>(mock_ecm_parser.release());
}
TEST_F(CasSessionTest, processEcm) {
@@ -269,7 +272,7 @@ TEST_F(CasSessionTest, processEcm) {
wvcas::CasEcm ecm(184);
EXPECT_CALL(*mock, LoadCasECMKeys(session_id, IsValidKeyEvenSlotData(),
IsValidKeyOddSlotData()));
session.processEcm(ecm, 0);
session.processEcm(ecm, 0, kEmptyGroupId);
EXPECT_CALL(*mock, RemoveEntitledKeySession(session_id));
}
@@ -293,25 +296,25 @@ TEST_F(CasSessionTest, parentalControl) {
// Different Ecm to make sure processEcm() processes this ecm.
std::generate(ecm.begin(), ecm.end(), std::rand);
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
session.processEcm(ecm, 0).status_code());
session.processEcm(ecm, 0, kEmptyGroupId).status_code());
std::generate(ecm.begin(), ecm.end(), std::rand);
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
session.processEcm(ecm, 13).status_code());
session.processEcm(ecm, 13, kEmptyGroupId).status_code());
// Parental control age must >= 10 (if non-zero).
session.set_age_restriction(10);
std::generate(ecm.begin(), ecm.end(), std::rand);
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
session.processEcm(ecm, 0).status_code());
session.processEcm(ecm, 0, kEmptyGroupId).status_code());
std::generate(ecm.begin(), ecm.end(), std::rand);
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
session.processEcm(ecm, 10).status_code());
session.processEcm(ecm, 10, kEmptyGroupId).status_code());
std::generate(ecm.begin(), ecm.end(), std::rand);
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
session.processEcm(ecm, 13).status_code());
session.processEcm(ecm, 13, kEmptyGroupId).status_code());
std::generate(ecm.begin(), ecm.end(), std::rand);
ASSERT_EQ(wvcas::CasStatusCode::kAccessDeniedByParentalControl,
session.processEcm(ecm, 3).status_code());
session.processEcm(ecm, 3, kEmptyGroupId).status_code());
EXPECT_CALL(*mock, RemoveEntitledKeySession(session_id));
}
@@ -354,7 +357,7 @@ TEST_F(CasSessionTest, FingerprintingSuccess) {
OnSessionFingerprintingUpdated(session_id, expected_message))
.Times(1);
session.processEcm(wvcas::CasEcm(184, '0'), 0);
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
}
TEST_F(CasSessionTest, RepeatedFingerprintingNoEventSuccess) {
@@ -368,10 +371,10 @@ TEST_F(CasSessionTest, RepeatedFingerprintingNoEventSuccess) {
session.set_fingerprinting_control("control");
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
session.processEcm(wvcas::CasEcm(184, '0'), 0);
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
// Same fingerprinting will not trigger event.
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(0);
session.processEcm(wvcas::CasEcm(184, '1'), 0);
session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId);
}
TEST_F(CasSessionTest, DifferentFingerprintingTriggerEventSuccess) {
@@ -385,15 +388,15 @@ TEST_F(CasSessionTest, DifferentFingerprintingTriggerEventSuccess) {
session.set_fingerprinting_control("control");
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
session.processEcm(wvcas::CasEcm(184, '0'), 0);
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
// Different fingerprinting will trigger event.
session.set_fingerprinting_control("control2");
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
session.processEcm(wvcas::CasEcm(184, '1'), 0);
session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId);
// Different fingerprinting (including empty) will trigger event.
session.set_fingerprinting_control("");
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
session.processEcm(wvcas::CasEcm(184, '2'), 0);
session.processEcm(wvcas::CasEcm(184, '2'), 0, kEmptyGroupId);
}
TEST_F(CasSessionTest, ServiceBlockingSuccess) {
@@ -412,7 +415,7 @@ TEST_F(CasSessionTest, ServiceBlockingSuccess) {
OnSessionServiceBlockingUpdated(session_id, expected_message))
.Times(1);
session.processEcm(wvcas::CasEcm(184, '0'), 0);
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
}
TEST_F(CasSessionTest, RepeatedServiceBlockingNoEventSuccess) {
@@ -426,9 +429,9 @@ TEST_F(CasSessionTest, RepeatedServiceBlockingNoEventSuccess) {
session.set_service_blocking_groups({"Group1", "g2"});
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
session.processEcm(wvcas::CasEcm(184, '0'), 0);
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(0);
session.processEcm(wvcas::CasEcm(184, '1'), 0);
session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId);
}
TEST_F(CasSessionTest, DifferentServiceBlockingTriggerEventSuccess) {
@@ -442,13 +445,13 @@ TEST_F(CasSessionTest, DifferentServiceBlockingTriggerEventSuccess) {
session.set_service_blocking_groups({"Group1", "g2"});
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
session.processEcm(wvcas::CasEcm(184, '0'), 0);
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
session.set_service_blocking_groups({"Group1"});
session.processEcm(wvcas::CasEcm(184, '1'), 0);
session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId);
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
session.set_service_blocking_groups({});
session.processEcm(wvcas::CasEcm(184, '2'), 0);
session.processEcm(wvcas::CasEcm(184, '2'), 0, kEmptyGroupId);
}
} // namespace

View File

@@ -0,0 +1,213 @@
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "widevine_media_cas_plugin.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <utils/String8.h>
#include "cas_events.h"
#include "cas_status.h"
#include "media/cas/CasAPI.h"
#include "media/stagefright/MediaErrors.h"
#include "widevine_cas_api.h"
namespace android {
// Minimalist implementation of Android string class to support test.
std::map<const String8*, std::unique_ptr<std::string> > string8s;
String8::String8(const String8& value)
: String8(value.c_str(), value.length()) {}
String8::String8(char const* data, size_t data_length) {
auto result =
string8s.emplace(this, make_unique<std::string>(data, data_length));
mString = result.first->second->data();
}
size_t String8::length() const {
auto it = string8s.find(this);
return it == string8s.end() ? 0 : it->second->size();
}
String8::~String8() { string8s.erase(this); }
} // namespace android
namespace wvcas {
namespace {
using ::testing::_;
using ::testing::DoAll;
using ::testing::Eq;
using ::testing::IsNull;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::SetArgReferee;
class MockWidevineCas : public WidevineCas {
public:
MockWidevineCas() {}
~MockWidevineCas() override {}
MOCK_METHOD(CasStatus, openSession, (WvCasSessionId * sessionId), (override));
MOCK_METHOD(bool, is_provisioned, (), (const, override));
MOCK_METHOD(CasStatus, generateEntitlementRequest,
(const std::string& init_data, std::string* entitlement_request,
std::string& license_id),
(override));
MOCK_METHOD(CasStatus, RecordLicenseId, (const std::string& license_id),
(override));
MOCK_METHOD(CasStatus, handleEntitlementResponse,
(const std::string& response, std::string& license_id,
std::string& multi_content_license_info,
std::string& group_license_info),
(override));
};
// Override WidevineCasPlugin to set WidevineCas and mock callbacks.
class TestWidevineCasPlugin : public WidevineCasPlugin {
public:
TestWidevineCasPlugin() : WidevineCasPlugin() {}
~TestWidevineCasPlugin() override {}
void SetWidevineCasApi(
std::unique_ptr<WidevineCas> widevine_cas_api) override {
WidevineCasPlugin::SetWidevineCasApi(std::move(widevine_cas_api));
}
MOCK_METHOD(void, CallBack,
(void* appData, int32_t event, int32_t arg, uint8_t* data,
size_t size, const CasSessionId* sessionId),
(const, override));
};
TEST(WidevineCasPluginTest, openSessionSuccess) {
TestWidevineCasPlugin plugin;
auto pass_through_cas_api = make_unique<MockWidevineCas>();
MockWidevineCas* cas_api = pass_through_cas_api.get();
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
const int32_t created_session_id = 0x12345678;
const std::vector<uint8_t> expected_android_session_id = {0x78, 0x56, 0x34,
0x12};
EXPECT_CALL(*cas_api, is_provisioned).WillOnce(Return(true));
EXPECT_CALL(*cas_api, openSession(NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(created_session_id),
Return(CasStatus::OkStatus())));
EXPECT_CALL(plugin, CallBack(_, CAS_SESSION_ID, created_session_id, IsNull(),
0, IsNull()));
std::vector<uint8_t> session_id;
EXPECT_EQ(plugin.openSession(&session_id), android::OK);
EXPECT_EQ(session_id, expected_android_session_id);
}
TEST(WidevineCasPluginTest, openSessionWithoutProvisionFail) {
TestWidevineCasPlugin plugin;
auto pass_through_cas_api = make_unique<MockWidevineCas>();
MockWidevineCas* cas_api = pass_through_cas_api.get();
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
EXPECT_CALL(*cas_api, is_provisioned).WillOnce(Return(false));
EXPECT_CALL(*cas_api, openSession(NotNull())).Times((0));
std::vector<uint8_t> session_id;
EXPECT_EQ(plugin.openSession(&session_id),
android::ERROR_CAS_NOT_PROVISIONED);
}
TEST(WidevineCasPluginTest,
provisionWithProvisionStringAlreadyProvisionedSuccess) {
TestWidevineCasPlugin plugin;
auto pass_through_cas_api = make_unique<MockWidevineCas>();
MockWidevineCas* cas_api = pass_through_cas_api.get();
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
const std::string provision_string = "init_data";
EXPECT_CALL(*cas_api, is_provisioned).WillOnce(Return(true));
EXPECT_CALL(plugin, CallBack(_, INDIVIDUALIZATION_COMPLETE, _, _, _, _));
// Provision string is init data; it triggers license request.
EXPECT_CALL(*cas_api,
generateEntitlementRequest(Eq(provision_string), NotNull(), _))
.WillOnce(DoAll(SetArgPointee<1>("signed_license_request"),
Return(CasStatus::OkStatus())));
EXPECT_CALL(plugin, CallBack(_, LICENSE_REQUEST, _, _, _, _));
const android::String8 provision_msg();
EXPECT_EQ(plugin.provision(android::String8(provision_string.c_str(),
provision_string.size())),
android::OK);
}
TEST(WidevineCasPluginTest, HandleAssignLicenseIDSuccess) {
TestWidevineCasPlugin plugin;
auto pass_through_cas_api = make_unique<MockWidevineCas>();
MockWidevineCas* cas_api = pass_through_cas_api.get();
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
const std::string license_id = "license_id";
EXPECT_CALL(*cas_api, RecordLicenseId(license_id))
.WillOnce(Return(CasStatus::OkStatus()));
EXPECT_CALL(plugin, CallBack(_, LICENSE_ID_ASSIGNED, _, _, _, _)).Times(1);
EXPECT_EQ(plugin.sendEvent(ASSIGN_LICENSE_ID, /*arg=*/0,
{license_id.begin(), license_id.end()}),
android::OK);
}
TEST(WidevineCasPluginTest, HandleAssignLicenseIDApiError) {
TestWidevineCasPlugin plugin;
auto pass_through_cas_api = make_unique<MockWidevineCas>();
MockWidevineCas* cas_api = pass_through_cas_api.get();
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
const std::string license_id = "license_id";
EXPECT_CALL(*cas_api, RecordLicenseId)
.WillOnce(Return(CasStatus(CasStatusCode::kInvalidParameter, "invalid")));
EXPECT_CALL(plugin, CallBack(_, CAS_ERROR, _, _, _, _)).Times(1);
EXPECT_NE(plugin.sendEvent(ASSIGN_LICENSE_ID, /*arg=*/0,
{license_id.begin(), license_id.end()}),
android::OK);
}
TEST(WidevineCasPluginTest, HandleEntitlementResponseSuccess) {
TestWidevineCasPlugin plugin;
auto pass_through_cas_api = make_unique<MockWidevineCas>();
MockWidevineCas* cas_api = pass_through_cas_api.get();
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
const std::string license = "license";
const std::string license_id = "id";
const std::string multi_content_license_info = "info";
const std::string group_license_info = "info2";
EXPECT_CALL(*cas_api, handleEntitlementResponse(_, _, _, _))
.WillOnce(DoAll(SetArgReferee<1>(license_id),
SetArgReferee<2>(multi_content_license_info),
SetArgReferee<3>(group_license_info),
Return(CasStatus::OkStatus())));
EXPECT_CALL(plugin,
CallBack(_, LICENSE_CAS_READY, _, _, license_id.size(), _))
.Times(1);
EXPECT_CALL(plugin, CallBack(_, MULTI_CONTENT_LICENSE_INFO, _, _,
multi_content_license_info.size(), _))
.Times(1);
EXPECT_CALL(plugin, CallBack(_, GROUP_LICENSE_INFO, _, _,
group_license_info.size(), _))
.Times(1);
EXPECT_EQ(plugin.sendEvent(LICENSE_RESPONSE, /*arg=*/0,
{license.begin(), license.end()}),
android::OK);
}
TEST(WidevineCasPluginTest, HandleEntitlementResponseEmptyResponseFail) {
TestWidevineCasPlugin plugin;
EXPECT_CALL(plugin, CallBack(_, CAS_ERROR, _, _, _, _)).Times(1);
EXPECT_NE(plugin.sendEvent(LICENSE_RESPONSE, /*arg=*/0, /*eventData=*/{}),
android::OK);
}
} // namespace
} // namespace wvcas