Widevine MediaCas client code that works with Android R
This commit is contained in:
34
tests/Android.bp
Normal file
34
tests/Android.bp
Normal file
@@ -0,0 +1,34 @@
|
||||
cc_binary {
|
||||
name: "wv_cas_tests",
|
||||
proprietary: true,
|
||||
srcs: [
|
||||
"src/wv_cas_test_main.cpp",
|
||||
"src/cas_license_test.cpp",
|
||||
"src/crypto_session_test.cpp",
|
||||
"src/ecm_parser_test.cpp",
|
||||
"src/test_properties.cpp",
|
||||
"src/widevine_cas_session_test.cpp",
|
||||
"src/cas_session_map_test.cpp",
|
||||
"src/license_key_status_test.cpp",
|
||||
],
|
||||
header_libs: [
|
||||
"//vendor/widevine/libwvmediacas/oemcrypto:oemcastroheaders",
|
||||
"media_plugin_headers",
|
||||
],
|
||||
static_libs: [
|
||||
"//vendor/widevine/libwvmediacas/wvutil:libcasutil",
|
||||
"//vendor/widevine/libwvmediacas/plugin:libwvcasplugins",
|
||||
"//vendor/widevine/libwvmediacas/protos:libcas_protos",
|
||||
"libgmock",
|
||||
"libgtest",
|
||||
],
|
||||
shared_libs: [
|
||||
"libcrypto",
|
||||
"libutils",
|
||||
"liblog",
|
||||
"libprotobuf-cpp-lite",
|
||||
],
|
||||
proto: {
|
||||
type: "lite",
|
||||
},
|
||||
}
|
||||
629
tests/src/cas_license_test.cpp
Normal file
629
tests/src/cas_license_test.cpp
Normal file
@@ -0,0 +1,629 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "cas_license.h"
|
||||
#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;
|
||||
using ::testing::Eq;
|
||||
using ::testing::Invoke;
|
||||
using ::testing::IsNull;
|
||||
using ::testing::NotNull;
|
||||
using ::testing::Pointee;
|
||||
using ::testing::Return;
|
||||
using ::testing::ReturnRef;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::StrictMock;
|
||||
|
||||
using wvutil::Base64Decode;
|
||||
using wvutil::Base64SafeDecode;
|
||||
using wvutil::Base64SafeEncodeNoPad;
|
||||
|
||||
using video_widevine_client::sdk::DeviceCertificate;
|
||||
using video_widevine_client::sdk::File;
|
||||
using video_widevine_client::sdk::HashedFile;
|
||||
|
||||
static constexpr char kKeyboxToken[] = "KeyBoxToken";
|
||||
static constexpr char kExpectedRenewalRequest[] = "ExpectedRenewalRequest";
|
||||
static constexpr char kExpectedSignature[] = "ExpectedSignature";
|
||||
static constexpr char kDeviceRsaKey[] = "DeviceRSAKeyDeviceRSAKeyDeviceRSAKey";
|
||||
static constexpr char kDeviceRsaKeyIV[] = "0123456789abcdef";
|
||||
static constexpr char kInitializationData[] = "CasInitializationData";
|
||||
static constexpr char kSessionKey[] = "fedcba9876543210";
|
||||
static constexpr uint32_t kNonce = 0x5555;
|
||||
static constexpr size_t kExpectedKeyboxSizeBytes = 72;
|
||||
static constexpr char kJsonStartSubstr[] = "\"signedResponse\": \"";
|
||||
static constexpr char kJsonEndSubstr[] = "\"";
|
||||
static constexpr char kKeyIDVideo[] = "KeyIdVideo";
|
||||
static constexpr char kKeyIDAudio[] = "KeyIdAudio";
|
||||
static constexpr char kKeyVideoIV[] = "KeyVideoIV";
|
||||
static constexpr char kKeyAudioIV[] = "KeyAudioIV";
|
||||
static constexpr char kKeyVideo[] = "KeyVideo";
|
||||
static constexpr char kKeyAudio[] = "KeyAudio";
|
||||
static constexpr char kKeyControlVideo[] = "KeyControlVideo";
|
||||
static constexpr char kKeyControlAudio[] = "KeyControlAudio";
|
||||
static constexpr char kKeyControlIVVideo[] = "KeyControlIVVideo";
|
||||
static constexpr char kKeyControlIVAudio[] = "KeyControlIVAudio";
|
||||
static constexpr char kTrackTypeVideo[] = "Video";
|
||||
static constexpr char kTrackTypeAudio[] = "Audio";
|
||||
static constexpr char kKeyCompanyName[] = "company_name";
|
||||
static constexpr char kKeyModelName[] = "model_name";
|
||||
static constexpr char kKeyArchitectureName[] = "architecture_name";
|
||||
static constexpr char kKeyDeviceName[] = "device_name";
|
||||
static constexpr char kKeyProductName[] = "product_name";
|
||||
static constexpr char kKeyBuildInfo[] = "build_info";
|
||||
static constexpr char kKeyDeviceId[] = "device_id";
|
||||
static constexpr char kKeyOemCryptoSecurityPatchLevel[] =
|
||||
"oem_crypto_security_patch_level";
|
||||
static constexpr char kRenewalSereverURL[] = "ExpectedRenewalURL";
|
||||
static constexpr char kCoreMessage[] = "CoreMessage";
|
||||
|
||||
typedef StrictMock<MockCryptoSession> StrictMockCryptoSession;
|
||||
|
||||
class MockPolicyEngine : public wvcas::PolicyEngine {
|
||||
public:
|
||||
MockPolicyEngine() {}
|
||||
~MockPolicyEngine() override {}
|
||||
MOCK_METHOD2(initialize,
|
||||
void(std::shared_ptr<wvcas::CryptoSession> crypto_session,
|
||||
wvcas::CasEventListener* event_listener));
|
||||
MOCK_METHOD1(SetLicense, void(const video_widevine::License& license));
|
||||
MOCK_METHOD1(UpdateLicense, void(const video_widevine::License& license));
|
||||
MOCK_CONST_METHOD0(CanRenew, bool());
|
||||
MOCK_CONST_METHOD0(renewal_server_url, const std::string&());
|
||||
MOCK_CONST_METHOD0(IsExpired, bool());
|
||||
MOCK_CONST_METHOD0(CanPersist, bool());
|
||||
MOCK_CONST_METHOD0(always_include_client_id, bool());
|
||||
};
|
||||
typedef StrictMock<MockPolicyEngine> StrictMockPolicyEngine;
|
||||
|
||||
class TestCasLicense : public wvcas::CasLicense {
|
||||
public:
|
||||
explicit TestCasLicense() {}
|
||||
~TestCasLicense() override{};
|
||||
std::unique_ptr<wvcas::PolicyEngine> GetPolicyEngine() override {
|
||||
policy_engine_ = pass_thru_.get();
|
||||
return std::move(pass_thru_);
|
||||
}
|
||||
std::unique_ptr<StrictMockPolicyEngine> pass_thru_ =
|
||||
make_unique<StrictMockPolicyEngine>();
|
||||
StrictMockPolicyEngine* policy_engine_ = pass_thru_.get();
|
||||
};
|
||||
|
||||
class CasLicenseTest : public ::testing::TestWithParam<bool> {
|
||||
public:
|
||||
CasLicenseTest() {}
|
||||
virtual ~CasLicenseTest() {}
|
||||
void SetKeyboxData(uint8_t* keyData, size_t* keyDataLength) {
|
||||
ASSERT_EQ(kExpectedKeyboxSizeBytes, *keyDataLength);
|
||||
memset(keyData, 0, *keyDataLength);
|
||||
memcpy(keyData, kKeyboxToken, 11);
|
||||
}
|
||||
|
||||
std::string CreateProvisioningResponse();
|
||||
|
||||
std::shared_ptr<StrictMockCryptoSession> strict_mock_;
|
||||
std::string wrapped_rsa_key_;
|
||||
std::string device_certificate_;
|
||||
};
|
||||
|
||||
std::string CasLicenseTest::CreateProvisioningResponse() {
|
||||
video_widevine::SignedProvisioningMessage signed_message;
|
||||
signed_message.set_signature(kExpectedSignature);
|
||||
signed_message.set_oemcrypto_core_message(kCoreMessage);
|
||||
|
||||
video_widevine::ProvisioningResponse response;
|
||||
std::string* nonce = response.mutable_nonce();
|
||||
nonce->resize(4);
|
||||
memcpy(&nonce->at(0), &kNonce, 4);
|
||||
|
||||
response.set_device_rsa_key(kDeviceRsaKey);
|
||||
response.set_device_rsa_key_iv(kDeviceRsaKeyIV);
|
||||
response.SerializeToString(signed_message.mutable_message());
|
||||
|
||||
std::vector<uint8_t> b64_message(signed_message.ByteSize());
|
||||
signed_message.SerializeToArray(&b64_message[0], b64_message.size());
|
||||
|
||||
return kJsonStartSubstr + Base64SafeEncodeNoPad(b64_message) + kJsonEndSubstr;
|
||||
}
|
||||
|
||||
bool Hash(const std::string& data, std::string* hash) {
|
||||
if (!hash) return false;
|
||||
hash->resize(SHA256_DIGEST_LENGTH);
|
||||
const unsigned char* input =
|
||||
reinterpret_cast<const unsigned char*>(data.data());
|
||||
unsigned char* output = reinterpret_cast<unsigned char*>(&(*hash)[0]);
|
||||
SHA256(input, data.size(), output);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string CreateEntitlementResponse() {
|
||||
video_widevine::License license;
|
||||
auto* key = license.add_key();
|
||||
key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT);
|
||||
|
||||
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);
|
||||
return signed_message.SerializeAsString();
|
||||
}
|
||||
|
||||
std::string CreateEntitlementRenewalResponse() {
|
||||
video_widevine::License license;
|
||||
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_oemcrypto_core_message(kCoreMessage);
|
||||
return signed_message.SerializeAsString();
|
||||
}
|
||||
|
||||
std::string CreateLicenseFileData() {
|
||||
File file;
|
||||
file.set_type(File::LICENSE);
|
||||
|
||||
video_widevine::SignedMessage license_request;
|
||||
license_request.set_msg("license_request");
|
||||
license_request.set_signature("license_request_signature");
|
||||
license_request.set_type(video_widevine::SignedMessage::CAS_LICENSE_REQUEST);
|
||||
|
||||
video_widevine::SignedMessage renewal_request;
|
||||
renewal_request.set_msg("renewal_request");
|
||||
renewal_request.set_signature("renewal_request_signature");
|
||||
renewal_request.set_type(video_widevine::SignedMessage::CAS_LICENSE_REQUEST);
|
||||
|
||||
video_widevine_client::sdk::License* license = file.mutable_license();
|
||||
license_request.SerializeToString(license->mutable_license_request());
|
||||
license->set_license(CreateEntitlementResponse());
|
||||
renewal_request.SerializeToString(license->mutable_renewal_request());
|
||||
license->set_renewal(CreateEntitlementRenewalResponse());
|
||||
|
||||
HashedFile hashed_file;
|
||||
file.SerializeToString(hashed_file.mutable_file());
|
||||
Hash(hashed_file.file(), hashed_file.mutable_hash());
|
||||
|
||||
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;
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
|
||||
std::string serialized_provisioning_request;
|
||||
std::string expected_signature(kExpectedSignature);
|
||||
|
||||
EXPECT_CALL(*strict_mock_, provisioning_method())
|
||||
.WillRepeatedly(Return(wvcas::Keybox));
|
||||
EXPECT_CALL(*strict_mock_, GetKeyData(NotNull(), NotNull()))
|
||||
.WillOnce(DoAll(Invoke(this, &CasLicenseTest::SetKeyboxData),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_CALL(*strict_mock_, supported_certificates())
|
||||
.WillOnce(Return(wvcas::SupportedCertificates(0x13)));
|
||||
EXPECT_CALL(*strict_mock_, GenerateNonce(NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<0>(kNonce),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_CALL(*strict_mock_, GenerateDerivedKeys(_, _, _, _))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_,
|
||||
PrepareAndSignProvisioningRequest(_, NotNull(), NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_CALL(*strict_mock_, GetDeviceID(NotNull()))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
|
||||
wvcas::CasStatus status = cas_license.GenerateDeviceProvisioningRequest(
|
||||
&serialized_provisioning_request);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
|
||||
// Verify the provisioning request.
|
||||
video_widevine::SignedProvisioningMessage signed_message;
|
||||
auto data = Base64Decode(serialized_provisioning_request);
|
||||
EXPECT_TRUE(signed_message.ParseFromArray(&data[0], data.size()));
|
||||
EXPECT_EQ(kExpectedSignature, signed_message.signature());
|
||||
EXPECT_EQ(video_widevine::SignedProvisioningMessage::PROVISIONING_20,
|
||||
signed_message.protocol_version());
|
||||
|
||||
video_widevine::ProvisioningRequest provisioning_request;
|
||||
EXPECT_TRUE(provisioning_request.ParseFromString(signed_message.message()));
|
||||
|
||||
auto& client_id = provisioning_request.client_id();
|
||||
EXPECT_EQ(video_widevine::ClientIdentification::KEYBOX, client_id.type());
|
||||
std::string token(kKeyboxToken);
|
||||
token.resize(kExpectedKeyboxSizeBytes, 0);
|
||||
EXPECT_EQ(token, client_id.token());
|
||||
|
||||
ASSERT_EQ(sizeof(uint32_t), provisioning_request.nonce().size());
|
||||
EXPECT_EQ(kNonce, *reinterpret_cast<const uint32_t*>(
|
||||
provisioning_request.nonce().data()));
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, HandleProvisioningResponse) {
|
||||
const std::string provisioning_response = CreateProvisioningResponse();
|
||||
|
||||
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_, reset())
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, LoadProvisioning(_, _, _, _));
|
||||
|
||||
wvcas::CasStatus status = cas_license.HandleDeviceProvisioningResponse(
|
||||
provisioning_response, &device_certificate_, &wrapped_rsa_key_, nullptr);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, HandleProvisioningResponseWithDeviceFileOutput) {
|
||||
const std::string provisioning_response = CreateProvisioningResponse();
|
||||
|
||||
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_, reset())
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, LoadProvisioning(_, _, _, _));
|
||||
|
||||
std::string device_cert_file;
|
||||
wvcas::CasStatus status = cas_license.HandleDeviceProvisioningResponse(
|
||||
provisioning_response, &device_certificate_, &wrapped_rsa_key_,
|
||||
&device_cert_file);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
EXPECT_FALSE(device_cert_file.empty());
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, GenerateEntitlementRequest) {
|
||||
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_, 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());
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, HandleEntitlementResponse) {
|
||||
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_, 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());
|
||||
|
||||
video_widevine::SignedMessage signed_message;
|
||||
ASSERT_TRUE(signed_message.ParseFromString(serialized_entitlement_request));
|
||||
|
||||
const std::string entitlement_response = CreateEntitlementResponse();
|
||||
|
||||
EXPECT_CALL(*strict_mock_, DeriveKeysFromSessionKey(_, _, _, _, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, LoadLicense(_, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
|
||||
// Valid.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist())
|
||||
.WillOnce(Return(false));
|
||||
status = cas_license.HandleEntitlementResponse(entitlement_response, nullptr);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
|
||||
// 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);
|
||||
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);
|
||||
EXPECT_FALSE(device_file.empty());
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
|
||||
HashedFile hashed_file;
|
||||
ASSERT_TRUE(hashed_file.ParseFromString(device_file));
|
||||
std::string hash;
|
||||
Hash(hashed_file.file(), &hash);
|
||||
EXPECT_EQ(std::vector<uint8_t>(hash.begin(), hash.end()),
|
||||
std::vector<uint8_t>(hashed_file.hash().begin(),
|
||||
hashed_file.hash().end()));
|
||||
|
||||
File file;
|
||||
ASSERT_TRUE(file.ParseFromString(hashed_file.file()));
|
||||
|
||||
const auto& license_file = file.license();
|
||||
EXPECT_FALSE(license_file.license_request().empty());
|
||||
EXPECT_FALSE(license_file.license().empty());
|
||||
EXPECT_TRUE(license_file.renewal_request().empty());
|
||||
EXPECT_TRUE(license_file.renewal().empty());
|
||||
|
||||
std::string emm_request = *signed_message.mutable_msg();
|
||||
EXPECT_EQ(std::vector<uint8_t>(emm_request.begin(), emm_request.end()),
|
||||
std::vector<uint8_t>(license_file.license_request().begin(),
|
||||
license_file.license_request().end()));
|
||||
EXPECT_EQ(std::vector<uint8_t>(entitlement_response.begin(),
|
||||
entitlement_response.end()),
|
||||
std::vector<uint8_t>(license_file.license().begin(),
|
||||
license_file.license().end()));
|
||||
}
|
||||
|
||||
TEST_P(CasLicenseTest, GenerateRenewalRequest) {
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
TestCasLicense cas_license;
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, always_include_client_id())
|
||||
.WillOnce(Return(GetParam()));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
|
||||
std::string serialized_renewal_request;
|
||||
|
||||
// Policy can_renew is false.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanRenew()).WillOnce(Return(false));
|
||||
EXPECT_NE(wvcas::CasStatusCode::kNoError,
|
||||
cas_license
|
||||
.GenerateEntitlementRenewalRequest(device_certificate_,
|
||||
&serialized_renewal_request)
|
||||
.status_code());
|
||||
|
||||
// Policy can_renew is true.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanRenew()).WillOnce(Return(true));
|
||||
EXPECT_CALL(*strict_mock_,
|
||||
PrepareAndSignRenewalRequest(_, NotNull(), NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license
|
||||
.GenerateEntitlementRenewalRequest(device_certificate_,
|
||||
&serialized_renewal_request)
|
||||
.status_code());
|
||||
video_widevine::SignedMessage signed_message;
|
||||
ASSERT_TRUE(signed_message.ParseFromString(serialized_renewal_request));
|
||||
video_widevine::LicenseRequest license_request;
|
||||
ASSERT_TRUE(license_request.ParseFromString(signed_message.msg()));
|
||||
if (GetParam()) {
|
||||
// Always include client id == true.
|
||||
ASSERT_TRUE(license_request.has_client_id());
|
||||
EXPECT_EQ(device_certificate_, license_request.client_id().token());
|
||||
} else {
|
||||
ASSERT_FALSE(license_request.has_client_id());
|
||||
}
|
||||
}
|
||||
|
||||
// Test renewal request generation with always_include_client_id == true and
|
||||
// false;
|
||||
// Suppress warning "INSTANTIATE_TEST_CASE_P is deprecated".
|
||||
// TODO(b/142954362): Remove the suppression once gtest on pi-tv-dev branch
|
||||
// updates to the latest version.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
INSTANTIATE_TEST_CASE_P(GenerateRenewalRequest, CasLicenseTest,
|
||||
::testing::Bool());
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
TEST_F(CasLicenseTest, HandleRenewalResponse) {
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
TestCasLicense cas_license;
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, always_include_client_id())
|
||||
.WillOnce(Return(false));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
|
||||
std::string serialized_renewal_request;
|
||||
|
||||
// Policy can_renew is false.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanRenew()).WillOnce(Return(false));
|
||||
EXPECT_NE(wvcas::CasStatusCode::kNoError,
|
||||
cas_license
|
||||
.GenerateEntitlementRenewalRequest(device_certificate_,
|
||||
&serialized_renewal_request)
|
||||
.status_code());
|
||||
|
||||
const std::string renewal_response = CreateEntitlementRenewalResponse();
|
||||
|
||||
// Policy can_renew is true. Set expectations to build a renewal request.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanRenew()).WillOnce(Return(true));
|
||||
EXPECT_CALL(*strict_mock_,
|
||||
PrepareAndSignRenewalRequest(_, NotNull(), NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license
|
||||
.GenerateEntitlementRenewalRequest(device_certificate_,
|
||||
&serialized_renewal_request)
|
||||
.status_code());
|
||||
|
||||
video_widevine::SignedMessage signed_message;
|
||||
ASSERT_TRUE(signed_message.ParseFromString(serialized_renewal_request));
|
||||
|
||||
std::string device_file;
|
||||
EXPECT_CALL(*strict_mock_, LoadRenewal(_, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
// Policy can_persist is false. no file information is generated.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, UpdateLicense(_));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist())
|
||||
.WillOnce(Return(false));
|
||||
EXPECT_EQ(
|
||||
wvcas::CasStatusCode::kNoError,
|
||||
cas_license
|
||||
.HandleEntitlementRenewalResponse(renewal_response, &device_file)
|
||||
.status_code());
|
||||
EXPECT_TRUE(device_file.empty());
|
||||
|
||||
// Policy can_persist is true. Validate that device_file is populated with a
|
||||
// valid license file.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist()).WillOnce(Return(true));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, UpdateLicense(_));
|
||||
ASSERT_EQ(
|
||||
wvcas::CasStatusCode::kNoError,
|
||||
cas_license
|
||||
.HandleEntitlementRenewalResponse(renewal_response, &device_file)
|
||||
.status_code());
|
||||
ASSERT_FALSE(device_file.empty());
|
||||
|
||||
HashedFile hashed_file;
|
||||
ASSERT_TRUE(hashed_file.ParseFromString(device_file));
|
||||
std::string hash;
|
||||
Hash(hashed_file.file(), &hash);
|
||||
EXPECT_EQ(std::vector<uint8_t>(hash.begin(), hash.end()),
|
||||
std::vector<uint8_t>(hashed_file.hash().begin(),
|
||||
hashed_file.hash().end()));
|
||||
|
||||
File file;
|
||||
ASSERT_TRUE(file.ParseFromString(hashed_file.file()));
|
||||
|
||||
const auto& license_file = file.license();
|
||||
EXPECT_TRUE(license_file.license_request().empty());
|
||||
EXPECT_TRUE(license_file.license().empty());
|
||||
EXPECT_FALSE(license_file.renewal_request().empty());
|
||||
EXPECT_FALSE(license_file.renewal().empty());
|
||||
|
||||
std::string renewal_request = *signed_message.mutable_msg();
|
||||
EXPECT_EQ(
|
||||
std::vector<uint8_t>(renewal_request.begin(), renewal_request.end()),
|
||||
std::vector<uint8_t>(license_file.renewal_request().begin(),
|
||||
license_file.renewal_request().end()));
|
||||
EXPECT_EQ(
|
||||
std::vector<uint8_t>(renewal_response.begin(), renewal_response.end()),
|
||||
std::vector<uint8_t>(license_file.renewal().begin(),
|
||||
license_file.renewal().end()));
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, IsExpired) {
|
||||
TestCasLicense cas_license;
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
EXPECT_CALL(*cas_license.policy_engine_, IsExpired())
|
||||
.WillOnce(Return(false))
|
||||
.WillOnce(Return(true));
|
||||
|
||||
EXPECT_FALSE(cas_license.IsExpired());
|
||||
EXPECT_TRUE(cas_license.IsExpired());
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, RestoreLicense) {
|
||||
TestCasLicense cas_license;
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
|
||||
std::string license_file_data = CreateLicenseFileData();
|
||||
EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _));
|
||||
EXPECT_CALL(*strict_mock_, DeriveKeysFromSessionKey(_, _, _, _, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, LoadLicense(_, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, LoadRenewal(_, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
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)
|
||||
.status_code());
|
||||
}
|
||||
25
tests/src/cas_session_map_test.cpp
Normal file
25
tests/src/cas_session_map_test.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
|
||||
#include "widevine_cas_session.h"
|
||||
#include "widevine_cas_session_map.h"
|
||||
|
||||
using wvcas::CasSessionPtr;
|
||||
using wvcas::WidevineCasSession;
|
||||
using wvcas::WidevineCasSessionMap;
|
||||
|
||||
TEST(WidevineCasSessionMap, HappyPath) {
|
||||
int base_key = rand();
|
||||
std::string key = std::to_string(base_key);
|
||||
WidevineCasSessionMap& map = WidevineCasSessionMap::instance();
|
||||
CasSessionPtr session = std::make_shared<WidevineCasSession>();
|
||||
|
||||
EXPECT_TRUE(map.AddSession(base_key, session));
|
||||
EXPECT_FALSE(map.AddSession(base_key, session));
|
||||
|
||||
EXPECT_EQ(map.GetSession(base_key).get(), session.get());
|
||||
|
||||
map.RemoveSession(base_key);
|
||||
EXPECT_EQ(map.GetSession(base_key).get(), nullptr);
|
||||
}
|
||||
1038
tests/src/crypto_session_test.cpp
Normal file
1038
tests/src/crypto_session_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
292
tests/src/ecm_parser_test.cpp
Normal file
292
tests/src/ecm_parser_test.cpp
Normal file
@@ -0,0 +1,292 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "ecm_parser.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <bitset>
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr int kCaIdSizeBytes = 2;
|
||||
static constexpr int kModeSizeBytes = 1;
|
||||
static constexpr int kVersionSizeBytes = 1;
|
||||
static constexpr int kIVFlagsSizeBytes = 1;
|
||||
static constexpr int kEntitlementKeyIDSizeBytes = 16;
|
||||
static constexpr int kContentKeyIDSizeBytes = 16;
|
||||
static constexpr int kContentKeyDataSize = 16;
|
||||
static constexpr int kWrappedKeyIVSizeBytes = 16;
|
||||
|
||||
static constexpr int kEcmDescriptorSizeBytes =
|
||||
kCaIdSizeBytes + kModeSizeBytes + kVersionSizeBytes + kIVFlagsSizeBytes;
|
||||
|
||||
static constexpr int kECMVersion = 2;
|
||||
// The cipher mode flags field in the ECM V2 is 4 bits.
|
||||
static constexpr uint8_t kAESCBCCryptoModeFlagsVal = (0x0 << 1);
|
||||
static constexpr uint8_t kAESCTRCryptoModeFlagsVal = (0x1 << 1);
|
||||
static constexpr uint8_t kDvbCsa2CryptoModeFlagsVal = (0x2 << 1);
|
||||
static constexpr uint8_t kDvbCsa3CryptoModeFlagsVal = (0x3 << 1);
|
||||
static constexpr uint8_t kDvbOFBCryptoModeFlagsVal = (0x4 << 1);
|
||||
static constexpr uint8_t kDvbSCTECryptoModeFlagsVal = (0x5 << 1);
|
||||
static constexpr uint8_t kRotationFlag = (0x1 << 0);
|
||||
static constexpr uint8_t kContentIVSizeFlag = (0x1 << 6);
|
||||
|
||||
static constexpr uint8_t kEntitlementKeyIDFill = '1';
|
||||
static constexpr uint8_t kEvenContentKeyIDFill = '2';
|
||||
static constexpr uint8_t kEvenContentKeyDataFill = '3';
|
||||
static constexpr uint8_t kEvenWrappedKeyIVFill = '4';
|
||||
static constexpr uint8_t kEvenContentKeyIVFill = '5';
|
||||
static constexpr uint8_t kOddContentKeyIDFill = '6';
|
||||
static constexpr uint8_t kOddContentKeyDataFill = '7';
|
||||
static constexpr uint8_t kOddWrappedKeyIVFill = '8';
|
||||
static constexpr uint8_t kOddContentKeyIVFill = '9';
|
||||
|
||||
static constexpr size_t kMaxEcmSizeBytes = 184;
|
||||
|
||||
static constexpr uint16_t kSectionHeader1 = 0x80;
|
||||
static constexpr uint16_t kSectionHeader2 = 0x81;
|
||||
static constexpr size_t kSectionHeaderSize = 4;
|
||||
} // namespace
|
||||
|
||||
class EcmParserTest : public testing::Test {
|
||||
public:
|
||||
void SetUp() { BuildEcm(); }
|
||||
size_t ContentKeyIVSize(bool content_iv_flag);
|
||||
size_t CalculateEcmSize(bool with_rotation, bool content_iv_flag = false);
|
||||
std::vector<uint8_t> ecm_data_;
|
||||
|
||||
private:
|
||||
void BuildEcm(bool with_rotation = true, bool content_iv_flag = false);
|
||||
};
|
||||
|
||||
size_t EcmParserTest::ContentKeyIVSize(bool content_iv_flag) {
|
||||
// Content key iv is 8 bytes if Content_IV flag is zero, and 16 bytes
|
||||
// othersize.
|
||||
return content_iv_flag ? 16 : 8;
|
||||
}
|
||||
|
||||
size_t EcmParserTest::CalculateEcmSize(bool with_rotation,
|
||||
bool content_iv_flag) {
|
||||
size_t ecm_key_data_size =
|
||||
kContentKeyIDSizeBytes + kContentKeyDataSize + kWrappedKeyIVSizeBytes +
|
||||
kEntitlementKeyIDSizeBytes + ContentKeyIVSize(content_iv_flag);
|
||||
return kEcmDescriptorSizeBytes + ecm_key_data_size * (with_rotation ? 2 : 1);
|
||||
}
|
||||
|
||||
void EcmParserTest::BuildEcm(bool with_rotation, bool content_iv_flag) {
|
||||
ecm_data_.clear();
|
||||
ecm_data_.reserve(CalculateEcmSize(with_rotation, content_iv_flag));
|
||||
ecm_data_.resize(kCaIdSizeBytes, 0);
|
||||
ecm_data_[0] = 0x4A;
|
||||
ecm_data_[1] = 0xD4;
|
||||
ecm_data_.resize(ecm_data_.size() + kVersionSizeBytes, kECMVersion);
|
||||
ecm_data_.resize(ecm_data_.size() + kModeSizeBytes,
|
||||
kAESCBCCryptoModeFlagsVal);
|
||||
uint8_t iv_flag_value = content_iv_flag ? kContentIVSizeFlag : 0;
|
||||
ecm_data_.resize(ecm_data_.size() + kIVFlagsSizeBytes, iv_flag_value);
|
||||
ASSERT_EQ(kEcmDescriptorSizeBytes, ecm_data_.size());
|
||||
|
||||
// Even key fields.
|
||||
ecm_data_.resize(ecm_data_.size() + kEntitlementKeyIDSizeBytes,
|
||||
kEntitlementKeyIDFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kContentKeyIDSizeBytes,
|
||||
kEvenContentKeyIDFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kContentKeyDataSize,
|
||||
kEvenContentKeyDataFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kWrappedKeyIVSizeBytes,
|
||||
kEvenWrappedKeyIVFill);
|
||||
ecm_data_.resize(ecm_data_.size() + ContentKeyIVSize(content_iv_flag),
|
||||
kEvenContentKeyIVFill);
|
||||
ASSERT_EQ(CalculateEcmSize(false, content_iv_flag), ecm_data_.size());
|
||||
|
||||
if (with_rotation) {
|
||||
// Entitlement key id field for odd key.
|
||||
ecm_data_.resize(ecm_data_.size() + kEntitlementKeyIDSizeBytes,
|
||||
kEntitlementKeyIDFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kContentKeyIDSizeBytes,
|
||||
kOddContentKeyIDFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kContentKeyDataSize,
|
||||
kOddContentKeyDataFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kWrappedKeyIVSizeBytes,
|
||||
kOddWrappedKeyIVFill);
|
||||
ecm_data_.resize(ecm_data_.size() + ContentKeyIVSize(content_iv_flag),
|
||||
kOddContentKeyIVFill);
|
||||
ASSERT_EQ(CalculateEcmSize(true, content_iv_flag), ecm_data_.size());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EcmParserTest, FieldsWithoutKeyRotation) {
|
||||
bool content_key_iv_16b = false;
|
||||
ecm_data_.resize(CalculateEcmSize(false, content_key_iv_16b));
|
||||
std::unique_ptr<const wvcas::EcmParser> parser;
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
ASSERT_FALSE(parser->rotation_enabled());
|
||||
|
||||
std::vector<uint8_t> test_data;
|
||||
test_data.resize(kEntitlementKeyIDSizeBytes, kEntitlementKeyIDFill);
|
||||
EXPECT_EQ(test_data,
|
||||
parser->entitlement_key_id(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyIDFill);
|
||||
EXPECT_EQ(test_data, parser->content_key_id(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyDataFill);
|
||||
EXPECT_EQ(test_data,
|
||||
parser->wrapped_key_data(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kWrappedKeyIVSizeBytes, kEvenWrappedKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser->wrapped_key_iv(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(ContentKeyIVSize(content_key_iv_16b), kEvenContentKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser->content_iv(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
EXPECT_TRUE(parser->content_key_id(wvcas::KeySlotId::kOddKeySlot).empty());
|
||||
EXPECT_TRUE(parser->wrapped_key_data(wvcas::KeySlotId::kOddKeySlot).empty());
|
||||
EXPECT_TRUE(parser->wrapped_key_iv(wvcas::KeySlotId::kOddKeySlot).empty());
|
||||
EXPECT_TRUE(parser->content_iv(wvcas::KeySlotId::kOddKeySlot).empty());
|
||||
}
|
||||
|
||||
TEST_F(EcmParserTest, FieldsWithKeyRotation) {
|
||||
ecm_data_[3] |= kRotationFlag;
|
||||
std::unique_ptr<const wvcas::EcmParser> parser;
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
ASSERT_TRUE(parser->rotation_enabled());
|
||||
|
||||
std::vector<uint8_t> test_data;
|
||||
test_data.resize(kEntitlementKeyIDSizeBytes, kEntitlementKeyIDFill);
|
||||
EXPECT_EQ(test_data,
|
||||
parser->entitlement_key_id(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyIDFill);
|
||||
EXPECT_EQ(test_data, parser->content_key_id(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyDataFill);
|
||||
EXPECT_EQ(test_data,
|
||||
parser->wrapped_key_data(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kWrappedKeyIVSizeBytes, kEvenWrappedKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser->wrapped_key_iv(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
bool content_key_iv_16b = false;
|
||||
test_data.resize(ContentKeyIVSize(content_key_iv_16b), kEvenContentKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser->content_iv(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kOddContentKeyIDFill);
|
||||
EXPECT_EQ(test_data, parser->content_key_id(wvcas::KeySlotId::kOddKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kOddContentKeyDataFill);
|
||||
EXPECT_EQ(test_data, parser->wrapped_key_data(wvcas::KeySlotId::kOddKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kWrappedKeyIVSizeBytes, kOddWrappedKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser->wrapped_key_iv(wvcas::KeySlotId::kOddKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(ContentKeyIVSize(content_key_iv_16b), kOddContentKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser->content_iv(wvcas::KeySlotId::kOddKeySlot));
|
||||
}
|
||||
|
||||
TEST_F(EcmParserTest, create) {
|
||||
std::unique_ptr<const wvcas::EcmParser> parser;
|
||||
EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
|
||||
ecm_data_.resize(4);
|
||||
EXPECT_FALSE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
|
||||
ecm_data_.resize(4 + CalculateEcmSize(false));
|
||||
EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
|
||||
ecm_data_.resize(kMaxEcmSizeBytes);
|
||||
EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
|
||||
ecm_data_.resize(CalculateEcmSize(true));
|
||||
EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
EXPECT_FALSE(wvcas::EcmParser::create(ecm_data_, nullptr));
|
||||
|
||||
ecm_data_.resize(CalculateEcmSize(true));
|
||||
EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
EXPECT_FALSE(wvcas::EcmParser::create(ecm_data_, nullptr));
|
||||
}
|
||||
|
||||
TEST_F(EcmParserTest, crypto_mode) {
|
||||
std::unique_ptr<const wvcas::EcmParser> parser;
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kAesCBC);
|
||||
|
||||
ecm_data_[3] = kAESCTRCryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kAesCTR);
|
||||
|
||||
ecm_data_[3] = kDvbCsa2CryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kDvbCsa2);
|
||||
|
||||
ecm_data_[3] = kDvbCsa3CryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kDvbCsa3);
|
||||
|
||||
ecm_data_[3] = kDvbOFBCryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kAesOFB);
|
||||
|
||||
ecm_data_[3] = kDvbSCTECryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kAesSCTE);
|
||||
}
|
||||
|
||||
TEST_F(EcmParserTest, ContentKeyIVSizes) {
|
||||
std::unique_ptr<const wvcas::EcmParser> parser;
|
||||
bool with_rotation = true;
|
||||
bool iv_flag = false;
|
||||
ecm_data_.resize(CalculateEcmSize(with_rotation, iv_flag));
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
EXPECT_EQ(parser->content_iv_size(), ContentKeyIVSize(iv_flag));
|
||||
|
||||
iv_flag = true;
|
||||
ecm_data_[4] = kContentIVSizeFlag;
|
||||
ecm_data_.resize(CalculateEcmSize(with_rotation, iv_flag));
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
EXPECT_EQ(parser->content_iv_size(), ContentKeyIVSize(iv_flag));
|
||||
}
|
||||
|
||||
TEST_F(EcmParserTest, EcmWithSectionHeader) {
|
||||
std::unique_ptr<const wvcas::EcmParser> parser;
|
||||
std::vector<uint8_t> section_header;
|
||||
section_header.resize(kSectionHeaderSize);
|
||||
*reinterpret_cast<uint32_t*>(section_header.data()) = htons(kSectionHeader1);
|
||||
// If ECM is prepended with section header, parsing must still work.
|
||||
ecm_data_.insert(ecm_data_.begin(), section_header.begin(),
|
||||
section_header.end());
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
|
||||
*((uint16_t*)ecm_data_.data()) = htons(kSectionHeader2);
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
|
||||
// Change header size, parsing should still work
|
||||
ecm_data_.erase(ecm_data_.begin());
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
}
|
||||
|
||||
TEST_F(EcmParserTest, AgeRestriction) {
|
||||
std::unique_ptr<const wvcas::EcmParser> parser;
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
EXPECT_EQ(0, parser->age_restriction());
|
||||
|
||||
uint8_t age_restriction = 16;
|
||||
ecm_data_[4] |= age_restriction << 1;
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser));
|
||||
EXPECT_EQ(age_restriction, parser->age_restriction());
|
||||
}
|
||||
934
tests/src/license_key_status_test.cpp
Normal file
934
tests/src/license_key_status_test.cpp
Normal file
@@ -0,0 +1,934 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <vector>
|
||||
#include "cas_types.h"
|
||||
#include "license_key_status.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
namespace {
|
||||
|
||||
static const uint32_t dev_lo_res = 200;
|
||||
static const uint32_t dev_hi_res = 400;
|
||||
static const uint32_t dev_top_res = 800;
|
||||
|
||||
static const uint32_t key_lo_res_min = 151;
|
||||
static const uint32_t key_lo_res_max = 300;
|
||||
static const uint32_t key_hi_res_min = 301;
|
||||
static const uint32_t key_hi_res_max = 450;
|
||||
static const uint32_t key_top_res_min = 451;
|
||||
static const uint32_t key_top_res_max = 650;
|
||||
|
||||
// Content Keys
|
||||
static const KeyId ck_sw_crypto = "c_key_SW_SECURE_CRYPTO";
|
||||
static const KeyId ck_sw_decode = "c_key_SW_SECURE_DECODE";
|
||||
static const KeyId ck_hw_crypto = "c_key_HW_SECURE_CRYPTO";
|
||||
static const KeyId ck_hw_decode = "c_key_HW_SECURE_DECODE";
|
||||
static const KeyId ck_hw_secure = "c_key_HW_SECURE_ALL";
|
||||
|
||||
// Operator Session Keys
|
||||
static const KeyId osk_decrypt = "os_key_generic_decrypt";
|
||||
static const KeyId osk_encrypt = "os_key_generic_encrypt";
|
||||
static const KeyId osk_sign = "os_key_generic_sign";
|
||||
static const KeyId osk_verify = "os_key_generic_verify";
|
||||
static const KeyId osk_encrypt_decrypt = "os_key_generic_encrypt_decrypt";
|
||||
static const KeyId osk_sign_verify = "os_key_generic_sign_verify";
|
||||
static const KeyId osk_all = "os_key_generic_all";
|
||||
|
||||
// HDCP test keys
|
||||
static const KeyId ck_sw_crypto_NO_HDCP = "ck_sw_crypto_NO_HDCP";
|
||||
static const KeyId ck_hw_secure_NO_HDCP = "ck_hw_secure_NO_HDCP";
|
||||
static const KeyId ck_sw_crypto_HDCP_V2_1 = "ck_sw_crypto_HDCP_V2_1";
|
||||
static const KeyId ck_hw_secure_HDCP_V2_1 = "ck_hw_secure_HDCP_V2_1";
|
||||
static const KeyId ck_sw_crypto_HDCP_NO_OUTPUT = "ck_sw_crypto_HDCP_NO_OUT";
|
||||
static const KeyId ck_hw_secure_HDCP_NO_OUTPUT = "ck_hw_secure_HDCP_NO_OUT";
|
||||
|
||||
// Constraint test keys
|
||||
static const KeyId ck_NO_HDCP_lo_res = "ck_NO_HDCP_lo_res";
|
||||
static const KeyId ck_HDCP_NO_OUTPUT_hi_res = "ck_HDCP_NO_OUTPUT_hi_res";
|
||||
static const KeyId ck_HDCP_V2_1_max_res = "ck_HDCP_V2_1_max_res";
|
||||
static const KeyId ck_NO_HDCP_dual_res = "ck_NO_HDCP_dual_res";
|
||||
|
||||
} // namespace
|
||||
|
||||
// protobuf generated classes.
|
||||
using video_widevine::License;
|
||||
using video_widevine::LicenseIdentification;
|
||||
using video_widevine::OFFLINE;
|
||||
using video_widevine::STREAMING;
|
||||
|
||||
typedef ::video_widevine::License::KeyContainer KeyContainer;
|
||||
typedef KeyContainer::VideoResolutionConstraint VideoResolutionConstraint;
|
||||
|
||||
class LicenseKeysTest : public ::testing::Test {
|
||||
protected:
|
||||
enum KeyFlag { kKeyFlagNull, kKeyFlagFalse, kKeyFlagTrue };
|
||||
|
||||
static const KeyFlag kEncryptNull = kKeyFlagNull;
|
||||
static const KeyFlag kEncryptFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kEncryptTrue = kKeyFlagTrue;
|
||||
static const KeyFlag kDecryptNull = kKeyFlagNull;
|
||||
static const KeyFlag kDecryptFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kDecryptTrue = kKeyFlagTrue;
|
||||
static const KeyFlag kSignNull = kKeyFlagNull;
|
||||
static const KeyFlag kSignFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kSignTrue = kKeyFlagTrue;
|
||||
static const KeyFlag kVerifyNull = kKeyFlagNull;
|
||||
static const KeyFlag kVerifyFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kVerifyTrue = kKeyFlagTrue;
|
||||
|
||||
static const KeyFlag kContentSecureFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kContentSecureTrue = kKeyFlagTrue;
|
||||
static const KeyFlag kContentClearFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kContentClearTrue = kKeyFlagTrue;
|
||||
|
||||
virtual void SetUp() {
|
||||
LicenseIdentification* id = license_.mutable_id();
|
||||
id->set_version(1);
|
||||
id->set_type(STREAMING);
|
||||
}
|
||||
|
||||
virtual void AddContentKey(
|
||||
const KeyId& key_id, bool set_level = false,
|
||||
KeyContainer::SecurityLevel level = KeyContainer::SW_SECURE_CRYPTO,
|
||||
bool set_hdcp = false,
|
||||
KeyContainer::OutputProtection::HDCP hdcp_value =
|
||||
KeyContainer::OutputProtection::HDCP_NONE,
|
||||
bool set_constraints = false,
|
||||
std::vector<VideoResolutionConstraint>* constraints = NULL) {
|
||||
KeyContainer* key = license_.add_key();
|
||||
key->set_type(KeyContainer::CONTENT);
|
||||
if (set_level) {
|
||||
key->set_level(level);
|
||||
}
|
||||
if (set_hdcp) {
|
||||
KeyContainer::OutputProtection* pro = key->mutable_required_protection();
|
||||
pro->set_hdcp(hdcp_value);
|
||||
}
|
||||
if (set_constraints) {
|
||||
for (std::vector<VideoResolutionConstraint>::iterator it =
|
||||
constraints->begin();
|
||||
it != constraints->end(); ++it) {
|
||||
VideoResolutionConstraint* constraint =
|
||||
key->add_video_resolution_constraints();
|
||||
constraint->set_min_resolution_pixels(it->min_resolution_pixels());
|
||||
constraint->set_max_resolution_pixels(it->max_resolution_pixels());
|
||||
constraint->mutable_required_protection()->set_hdcp(
|
||||
it->required_protection().hdcp());
|
||||
}
|
||||
}
|
||||
key->set_id(key_id);
|
||||
}
|
||||
|
||||
virtual void AddEntitlementKey(
|
||||
const KeyId& key_id, bool set_level = false,
|
||||
KeyContainer::SecurityLevel level = KeyContainer::SW_SECURE_CRYPTO,
|
||||
bool set_hdcp = false,
|
||||
KeyContainer::OutputProtection::HDCP hdcp_value =
|
||||
KeyContainer::OutputProtection::HDCP_NONE,
|
||||
bool set_constraints = false,
|
||||
std::vector<VideoResolutionConstraint>* constraints = NULL) {
|
||||
AddContentKey(key_id, set_level, level, set_hdcp, hdcp_value,
|
||||
set_constraints, constraints);
|
||||
license_.mutable_key(license_.key_size() - 1)
|
||||
->set_type(KeyContainer::ENTITLEMENT);
|
||||
}
|
||||
|
||||
virtual void AddOperatorSessionKey(const KeyId& key_id,
|
||||
bool set_perms = false,
|
||||
KeyFlag encrypt = kKeyFlagNull,
|
||||
KeyFlag decrypt = kKeyFlagNull,
|
||||
KeyFlag sign = kKeyFlagNull,
|
||||
KeyFlag verify = kKeyFlagNull) {
|
||||
KeyContainer* non_content_key = license_.add_key();
|
||||
non_content_key->set_type(KeyContainer::OPERATOR_SESSION);
|
||||
non_content_key->set_id(key_id);
|
||||
if (set_perms) {
|
||||
KeyContainer::OperatorSessionKeyPermissions* permissions =
|
||||
non_content_key->mutable_operator_session_key_permissions();
|
||||
if (encrypt != kKeyFlagNull) {
|
||||
permissions->set_allow_encrypt(encrypt == kKeyFlagTrue);
|
||||
}
|
||||
if (decrypt != kKeyFlagNull) {
|
||||
permissions->set_allow_decrypt(decrypt == kKeyFlagTrue);
|
||||
}
|
||||
if (sign != kKeyFlagNull) {
|
||||
permissions->set_allow_sign(sign == kKeyFlagTrue);
|
||||
}
|
||||
if (verify != kKeyFlagNull) {
|
||||
permissions->set_allow_signature_verify(verify == kKeyFlagTrue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void AddSigningKey(const KeyId& key_id) {
|
||||
KeyContainer* key = license_.add_key();
|
||||
key->set_type(KeyContainer::SIGNING);
|
||||
key->set_id(key_id);
|
||||
}
|
||||
|
||||
virtual void ExpectAllowedUsageContent(const KeyAllowedUsage& key_usage,
|
||||
KeyFlag secure, KeyFlag clear,
|
||||
KeySecurityLevel key_security_level) {
|
||||
EXPECT_EQ(key_usage.decrypt_to_secure_buffer, secure == kKeyFlagTrue);
|
||||
EXPECT_EQ(key_usage.decrypt_to_clear_buffer, clear == kKeyFlagTrue);
|
||||
EXPECT_EQ(key_usage.key_security_level_, key_security_level);
|
||||
EXPECT_FALSE(key_usage.generic_encrypt);
|
||||
EXPECT_FALSE(key_usage.generic_decrypt);
|
||||
EXPECT_FALSE(key_usage.generic_sign);
|
||||
EXPECT_FALSE(key_usage.generic_verify);
|
||||
}
|
||||
|
||||
virtual void ExpectAllowedUsageOperator(const KeyAllowedUsage& key_usage,
|
||||
KeyFlag encrypt, KeyFlag decrypt,
|
||||
KeyFlag sign, KeyFlag verify) {
|
||||
EXPECT_FALSE(key_usage.decrypt_to_secure_buffer);
|
||||
EXPECT_FALSE(key_usage.decrypt_to_clear_buffer);
|
||||
EXPECT_EQ(key_usage.generic_encrypt, encrypt == kKeyFlagTrue);
|
||||
EXPECT_EQ(key_usage.generic_decrypt, decrypt == kKeyFlagTrue);
|
||||
EXPECT_EQ(key_usage.generic_sign, sign == kKeyFlagTrue);
|
||||
EXPECT_EQ(key_usage.generic_verify, verify == kKeyFlagTrue);
|
||||
}
|
||||
|
||||
virtual int NumContentKeys() { return content_key_count_; }
|
||||
|
||||
virtual void StageContentKeys() {
|
||||
content_key_count_ = 0;
|
||||
AddContentKey(ck_sw_crypto, true, KeyContainer::SW_SECURE_CRYPTO);
|
||||
content_key_count_++;
|
||||
AddContentKey(ck_sw_decode, true, KeyContainer::SW_SECURE_DECODE);
|
||||
content_key_count_++;
|
||||
AddContentKey(ck_hw_crypto, true, KeyContainer::HW_SECURE_CRYPTO);
|
||||
content_key_count_++;
|
||||
AddContentKey(ck_hw_decode, true, KeyContainer::HW_SECURE_DECODE);
|
||||
content_key_count_++;
|
||||
AddContentKey(ck_hw_secure, true, KeyContainer::HW_SECURE_ALL);
|
||||
content_key_count_++;
|
||||
license_keys_.SetFromLicense(license_);
|
||||
}
|
||||
|
||||
virtual void StageOperatorSessionKeys() {
|
||||
AddOperatorSessionKey(osk_decrypt, true, kEncryptNull, kDecryptTrue,
|
||||
kSignNull, kVerifyNull);
|
||||
AddOperatorSessionKey(osk_encrypt, true, kEncryptTrue, kDecryptNull,
|
||||
kSignNull, kVerifyNull);
|
||||
AddOperatorSessionKey(osk_sign, true, kEncryptNull, kDecryptNull, kSignTrue,
|
||||
kVerifyNull);
|
||||
AddOperatorSessionKey(osk_verify, true, kEncryptNull, kDecryptNull,
|
||||
kSignNull, kVerifyTrue);
|
||||
AddOperatorSessionKey(osk_encrypt_decrypt, true, kEncryptTrue, kDecryptTrue,
|
||||
kSignNull, kVerifyNull);
|
||||
AddOperatorSessionKey(osk_sign_verify, true, kEncryptNull, kDecryptNull,
|
||||
kSignTrue, kVerifyTrue);
|
||||
AddOperatorSessionKey(osk_all, true, kEncryptTrue, kDecryptTrue, kSignTrue,
|
||||
kVerifyTrue);
|
||||
license_keys_.SetFromLicense(license_);
|
||||
}
|
||||
|
||||
virtual void StageHdcpKeys() {
|
||||
content_key_count_ = 0;
|
||||
AddContentKey(ck_sw_crypto_NO_HDCP, true, KeyContainer::SW_SECURE_CRYPTO,
|
||||
true, KeyContainer::OutputProtection::HDCP_NONE);
|
||||
content_key_count_++;
|
||||
AddContentKey(ck_hw_secure_NO_HDCP, true, KeyContainer::HW_SECURE_ALL, true,
|
||||
KeyContainer::OutputProtection::HDCP_NONE);
|
||||
content_key_count_++;
|
||||
|
||||
AddContentKey(ck_sw_crypto_HDCP_V2_1, true, KeyContainer::SW_SECURE_CRYPTO,
|
||||
true, KeyContainer::OutputProtection::HDCP_V2_1);
|
||||
content_key_count_++;
|
||||
AddContentKey(ck_hw_secure_HDCP_V2_1, true, KeyContainer::HW_SECURE_ALL,
|
||||
true, KeyContainer::OutputProtection::HDCP_V2_1);
|
||||
content_key_count_++;
|
||||
|
||||
AddContentKey(ck_sw_crypto_HDCP_NO_OUTPUT, true,
|
||||
KeyContainer::SW_SECURE_CRYPTO, true,
|
||||
KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT);
|
||||
content_key_count_++;
|
||||
AddContentKey(ck_hw_secure_HDCP_NO_OUTPUT, true,
|
||||
KeyContainer::HW_SECURE_ALL, true,
|
||||
KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT);
|
||||
content_key_count_++;
|
||||
license_keys_.SetFromLicense(license_);
|
||||
}
|
||||
|
||||
virtual void AddConstraint(
|
||||
std::vector<VideoResolutionConstraint>& constraints, uint32_t min_res,
|
||||
uint32_t max_res, bool set_hdcp = false,
|
||||
KeyContainer::OutputProtection::HDCP hdcp =
|
||||
KeyContainer::OutputProtection::HDCP_NONE) {
|
||||
VideoResolutionConstraint constraint;
|
||||
constraint.set_min_resolution_pixels(min_res);
|
||||
constraint.set_max_resolution_pixels(max_res);
|
||||
if (set_hdcp) {
|
||||
constraint.mutable_required_protection()->set_hdcp(hdcp);
|
||||
}
|
||||
constraints.push_back(constraint);
|
||||
}
|
||||
|
||||
virtual void StageConstraintKeys() {
|
||||
content_key_count_ = 0;
|
||||
|
||||
std::vector<VideoResolutionConstraint> constraints;
|
||||
|
||||
AddConstraint(constraints, key_lo_res_min, key_lo_res_max);
|
||||
|
||||
AddContentKey(ck_NO_HDCP_lo_res, true, KeyContainer::SW_SECURE_CRYPTO, true,
|
||||
KeyContainer::OutputProtection::HDCP_NONE, true,
|
||||
&constraints);
|
||||
content_key_count_++;
|
||||
|
||||
constraints.clear();
|
||||
AddConstraint(constraints, key_hi_res_min, key_hi_res_max);
|
||||
|
||||
AddContentKey(ck_HDCP_NO_OUTPUT_hi_res, true,
|
||||
KeyContainer::SW_SECURE_CRYPTO, true,
|
||||
KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT, true,
|
||||
&constraints);
|
||||
content_key_count_++;
|
||||
|
||||
constraints.clear();
|
||||
AddConstraint(constraints, key_top_res_min, key_top_res_max);
|
||||
|
||||
AddContentKey(ck_HDCP_V2_1_max_res, true, KeyContainer::SW_SECURE_CRYPTO,
|
||||
true, KeyContainer::OutputProtection::HDCP_V2_1, true,
|
||||
&constraints);
|
||||
content_key_count_++;
|
||||
|
||||
constraints.clear();
|
||||
AddConstraint(constraints, key_lo_res_min, key_lo_res_max);
|
||||
AddConstraint(constraints, key_hi_res_min, key_hi_res_max, true,
|
||||
KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT);
|
||||
|
||||
AddContentKey(ck_NO_HDCP_dual_res, true, KeyContainer::HW_SECURE_ALL, true,
|
||||
KeyContainer::OutputProtection::HDCP_NONE, true,
|
||||
&constraints);
|
||||
content_key_count_++;
|
||||
|
||||
license_keys_.SetFromLicense(license_);
|
||||
}
|
||||
|
||||
virtual void ExpectKeyStatusesEqual(KeyStatusMap& key_status_map,
|
||||
KeyStatus expected_status) {
|
||||
for (KeyStatusMap::iterator it = key_status_map.begin();
|
||||
it != key_status_map.end(); ++it) {
|
||||
EXPECT_TRUE(it->second == expected_status);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void ExpectKeyStatusEqual(KeyStatusMap& key_status_map,
|
||||
const KeyId& key_id,
|
||||
KeyStatus expected_status) {
|
||||
for (KeyStatusMap::iterator it = key_status_map.begin();
|
||||
it != key_status_map.end(); ++it) {
|
||||
if (key_id == it->first) {
|
||||
EXPECT_TRUE(it->second == expected_status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t content_key_count_;
|
||||
LicenseKeys license_keys_;
|
||||
License license_;
|
||||
};
|
||||
|
||||
TEST_F(LicenseKeysTest, Empty) { EXPECT_TRUE(license_keys_.Empty()); }
|
||||
|
||||
TEST_F(LicenseKeysTest, NotEmpty) {
|
||||
const KeyId c_key = "content_key";
|
||||
AddContentKey(c_key);
|
||||
license_keys_.SetFromLicense(license_);
|
||||
EXPECT_FALSE(license_keys_.Empty());
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, BadKeyId) {
|
||||
const KeyId c_key = "content_key";
|
||||
const KeyId os_key = "op_sess_key";
|
||||
const KeyId unk_key = "unknown_key";
|
||||
KeyAllowedUsage allowed_usage;
|
||||
AddContentKey(c_key);
|
||||
AddOperatorSessionKey(os_key);
|
||||
license_keys_.SetFromLicense(license_);
|
||||
EXPECT_FALSE(license_keys_.IsContentKey(unk_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(unk_key));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(unk_key));
|
||||
EXPECT_FALSE(license_keys_.GetAllowedUsage(unk_key, &allowed_usage));
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, SigningKey) {
|
||||
const KeyId c_key = "content_key";
|
||||
const KeyId os_key = "op_sess_key";
|
||||
const KeyId sign_key = "signing_key";
|
||||
KeyAllowedUsage allowed_usage;
|
||||
AddSigningKey(sign_key);
|
||||
AddContentKey(c_key);
|
||||
AddOperatorSessionKey(os_key);
|
||||
license_keys_.SetFromLicense(license_);
|
||||
EXPECT_FALSE(license_keys_.IsContentKey(sign_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(sign_key));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(sign_key));
|
||||
EXPECT_FALSE(license_keys_.GetAllowedUsage(sign_key, &allowed_usage));
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, ContentKey) {
|
||||
const KeyId c_key = "content_key";
|
||||
AddContentKey(c_key);
|
||||
EXPECT_FALSE(license_keys_.IsContentKey(c_key));
|
||||
|
||||
license_keys_.SetFromLicense(license_);
|
||||
EXPECT_TRUE(license_keys_.IsContentKey(c_key));
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, EntitlementKey) {
|
||||
const KeyId e_key = "entitlement_key";
|
||||
const KeyId c_key = "content_key";
|
||||
AddEntitlementKey(e_key);
|
||||
EXPECT_FALSE(license_keys_.IsContentKey(e_key));
|
||||
|
||||
license_keys_.SetFromLicense(license_);
|
||||
// TODO(juce, rfrias): For simplicity entitlement keys are indicated as
|
||||
// content keys. It doesn't break anything, but CanDecryptContent returns true
|
||||
// for and entitlement key id.
|
||||
EXPECT_TRUE(license_keys_.IsContentKey(e_key));
|
||||
|
||||
std::vector<WidevinePsshData_EntitledKey> entitled_keys(1);
|
||||
entitled_keys[0].set_entitlement_key_id(e_key);
|
||||
entitled_keys[0].set_key_id(c_key);
|
||||
EXPECT_FALSE(license_keys_.IsContentKey(c_key));
|
||||
license_keys_.SetEntitledKeys(entitled_keys);
|
||||
EXPECT_TRUE(license_keys_.IsContentKey(c_key));
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, OperatorSessionKey) {
|
||||
const KeyId os_key = "op_sess_key";
|
||||
EXPECT_FALSE(license_keys_.IsContentKey(os_key));
|
||||
AddOperatorSessionKey(os_key);
|
||||
|
||||
license_keys_.SetFromLicense(license_);
|
||||
EXPECT_FALSE(license_keys_.IsContentKey(os_key));
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, CanDecrypt) {
|
||||
const KeyId os_key = "op_sess_key";
|
||||
const KeyId c_key = "content_key";
|
||||
const KeyId e_key = "entitlement_key";
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(c_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(os_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(e_key));
|
||||
AddOperatorSessionKey(os_key);
|
||||
AddContentKey(c_key);
|
||||
AddEntitlementKey(e_key);
|
||||
license_keys_.SetFromLicense(license_);
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(c_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(os_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(e_key));
|
||||
bool new_usable_keys = false;
|
||||
bool any_change = false;
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(c_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(os_key));
|
||||
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusExpired, &new_usable_keys);
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(c_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(os_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(e_key));
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, AllowedUsageNull) {
|
||||
const KeyId os_key = "op_sess_key";
|
||||
const KeyId c_key = "content_key";
|
||||
const KeyId sign_key = "signing_key";
|
||||
const KeyId e_key = "entitlement_key";
|
||||
AddOperatorSessionKey(os_key);
|
||||
AddContentKey(c_key);
|
||||
AddSigningKey(sign_key);
|
||||
AddEntitlementKey(e_key);
|
||||
license_keys_.SetFromLicense(license_);
|
||||
KeyAllowedUsage usage_1;
|
||||
EXPECT_FALSE(license_keys_.GetAllowedUsage(sign_key, &usage_1));
|
||||
|
||||
KeyAllowedUsage usage_2;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(c_key, &usage_2));
|
||||
ExpectAllowedUsageContent(usage_2, kContentClearTrue, kContentSecureTrue,
|
||||
kKeySecurityLevelUnset);
|
||||
|
||||
KeyAllowedUsage usage_3;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(os_key, &usage_3));
|
||||
ExpectAllowedUsageContent(usage_3, kContentClearFalse, kContentSecureFalse,
|
||||
kKeySecurityLevelUnset);
|
||||
|
||||
KeyAllowedUsage usage_4;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(os_key, &usage_4));
|
||||
ExpectAllowedUsageContent(usage_4, kContentClearFalse, kContentSecureFalse,
|
||||
kKeySecurityLevelUnset);
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, AllowedUsageContent) {
|
||||
StageContentKeys();
|
||||
KeyAllowedUsage u_sw_crypto;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_sw_crypto, &u_sw_crypto));
|
||||
ExpectAllowedUsageContent(u_sw_crypto, kContentSecureTrue, kContentClearTrue,
|
||||
kSoftwareSecureCrypto);
|
||||
|
||||
KeyAllowedUsage u_sw_decode;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_sw_decode, &u_sw_decode));
|
||||
ExpectAllowedUsageContent(u_sw_decode, kContentSecureTrue, kContentClearTrue,
|
||||
kSoftwareSecureDecode);
|
||||
|
||||
KeyAllowedUsage u_hw_crypto;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_hw_crypto, &u_hw_crypto));
|
||||
ExpectAllowedUsageContent(u_hw_crypto, kContentSecureTrue, kContentClearTrue,
|
||||
kHardwareSecureCrypto);
|
||||
|
||||
KeyAllowedUsage u_hw_decode;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_hw_decode, &u_hw_decode));
|
||||
ExpectAllowedUsageContent(u_hw_decode, kContentSecureTrue, kContentClearFalse,
|
||||
kHardwareSecureDecode);
|
||||
|
||||
KeyAllowedUsage u_hw_secure;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_hw_secure, &u_hw_secure));
|
||||
ExpectAllowedUsageContent(u_hw_secure, kContentSecureTrue, kContentClearFalse,
|
||||
kHardwareSecureAll);
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, AllowedUsageOperatorSession) {
|
||||
StageOperatorSessionKeys();
|
||||
KeyAllowedUsage u_encrypt;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_encrypt, &u_encrypt));
|
||||
ExpectAllowedUsageOperator(u_encrypt, kEncryptTrue, kDecryptFalse, kSignFalse,
|
||||
kVerifyFalse);
|
||||
|
||||
KeyAllowedUsage u_decrypt;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_decrypt, &u_decrypt));
|
||||
ExpectAllowedUsageOperator(u_decrypt, kEncryptFalse, kDecryptTrue, kSignFalse,
|
||||
kVerifyFalse);
|
||||
|
||||
KeyAllowedUsage u_sign;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_sign, &u_sign));
|
||||
ExpectAllowedUsageOperator(u_sign, kEncryptFalse, kDecryptFalse, kSignTrue,
|
||||
kVerifyFalse);
|
||||
|
||||
KeyAllowedUsage u_verify;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_verify, &u_verify));
|
||||
ExpectAllowedUsageOperator(u_verify, kEncryptFalse, kDecryptFalse, kSignFalse,
|
||||
kVerifyTrue);
|
||||
|
||||
KeyAllowedUsage u_encrypt_decrypt;
|
||||
EXPECT_TRUE(
|
||||
license_keys_.GetAllowedUsage(osk_encrypt_decrypt, &u_encrypt_decrypt));
|
||||
ExpectAllowedUsageOperator(u_encrypt_decrypt, kEncryptTrue, kDecryptTrue,
|
||||
kSignFalse, kVerifyFalse);
|
||||
|
||||
KeyAllowedUsage u_sign_verify;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_sign_verify, &u_sign_verify));
|
||||
ExpectAllowedUsageOperator(u_sign_verify, kEncryptFalse, kDecryptFalse,
|
||||
kSignTrue, kVerifyTrue);
|
||||
|
||||
KeyAllowedUsage u_all;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_all, &u_all));
|
||||
ExpectAllowedUsageOperator(u_all, kEncryptTrue, kDecryptTrue, kSignTrue,
|
||||
kVerifyTrue);
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, ExtractKeyStatuses) {
|
||||
KeyStatusMap key_status_map;
|
||||
StageOperatorSessionKeys();
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
EXPECT_EQ(0u, key_status_map.size());
|
||||
StageContentKeys();
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
EXPECT_EQ(content_key_count_, key_status_map.size());
|
||||
ExpectKeyStatusesEqual(key_status_map, kKeyStatusInternalError);
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, KeyStatusChanges) {
|
||||
bool new_usable_keys = false;
|
||||
bool any_change = false;
|
||||
KeyStatusMap key_status_map;
|
||||
StageOperatorSessionKeys();
|
||||
StageContentKeys();
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
EXPECT_EQ(content_key_count_, key_status_map.size());
|
||||
ExpectKeyStatusesEqual(key_status_map, kKeyStatusInternalError);
|
||||
|
||||
// change to pending
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusPending, &new_usable_keys);
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
EXPECT_EQ(content_key_count_, key_status_map.size());
|
||||
ExpectKeyStatusesEqual(key_status_map, kKeyStatusPending);
|
||||
|
||||
// change to pending (again)
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusPending, &new_usable_keys);
|
||||
EXPECT_FALSE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
EXPECT_EQ(content_key_count_, key_status_map.size());
|
||||
ExpectKeyStatusesEqual(key_status_map, kKeyStatusPending);
|
||||
|
||||
// change to usable
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
EXPECT_EQ(content_key_count_, key_status_map.size());
|
||||
ExpectKeyStatusesEqual(key_status_map, kKeyStatusUsable);
|
||||
|
||||
// change to usable (again)
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
EXPECT_FALSE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
EXPECT_EQ(content_key_count_, key_status_map.size());
|
||||
ExpectKeyStatusesEqual(key_status_map, kKeyStatusUsable);
|
||||
|
||||
// change to expired
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusExpired, &new_usable_keys);
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
EXPECT_EQ(content_key_count_, key_status_map.size());
|
||||
ExpectKeyStatusesEqual(key_status_map, kKeyStatusExpired);
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, HdcpChanges) {
|
||||
bool new_usable_keys = false;
|
||||
bool any_change = false;
|
||||
KeyStatusMap key_status_map;
|
||||
StageHdcpKeys();
|
||||
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
license_keys_.ApplyConstraints(100, HDCP_NONE);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
|
||||
license_keys_.ApplyConstraints(100, HDCP_V1);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_FALSE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_NO_HDCP, kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
|
||||
license_keys_.ApplyConstraints(100, HDCP_V2_2);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1,
|
||||
kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_NO_OUTPUT,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
|
||||
license_keys_.ApplyConstraints(100, HDCP_NO_DIGITAL_OUTPUT);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1,
|
||||
kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT,
|
||||
kKeyStatusUsable);
|
||||
|
||||
license_keys_.ApplyConstraints(100, HDCP_NONE);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, ConstraintChanges) {
|
||||
bool new_usable_keys = false;
|
||||
bool any_change = false;
|
||||
KeyStatusMap key_status_map;
|
||||
StageConstraintKeys();
|
||||
|
||||
// No constraints set by device
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res));
|
||||
|
||||
// Low-res device, no HDCP support
|
||||
license_keys_.ApplyConstraints(dev_lo_res, HDCP_NONE);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
|
||||
// Hi-res device, HDCP_V1 support
|
||||
license_keys_.ApplyConstraints(dev_hi_res, HDCP_V1);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res));
|
||||
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_NO_HDCP, kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
|
||||
// Lo-res device, HDCP V2.2 support
|
||||
license_keys_.ApplyConstraints(dev_lo_res, HDCP_V2_2);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1,
|
||||
kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_NO_OUTPUT,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
|
||||
// Hi-res device, Maximal HDCP support
|
||||
license_keys_.ApplyConstraints(dev_hi_res, HDCP_NO_DIGITAL_OUTPUT);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res));
|
||||
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1,
|
||||
kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT,
|
||||
kKeyStatusUsable);
|
||||
|
||||
// Lo-res device, no HDCP support
|
||||
license_keys_.ApplyConstraints(dev_lo_res, HDCP_NONE);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
|
||||
// Too-high-res -- all keys rejected
|
||||
license_keys_.ApplyConstraints(dev_top_res, HDCP_NONE);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res));
|
||||
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
143
tests/src/mediacas_integration_test.cpp
Normal file
143
tests/src/mediacas_integration_test.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
// Dynamically generated header created during build. The Runtest entry point is
|
||||
// defined in gopkg_carchive.go
|
||||
|
||||
#include "gowvcas_carchive.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
constexpr int kIntegrationTestPassed = 0;
|
||||
|
||||
// Invokes a test. Tests are named to allow them to be run individually. This
|
||||
// may be the best solution. The downside is the test prints PASS for each
|
||||
// individual test.
|
||||
|
||||
// It is also possible to not address them individually but run
|
||||
// them all as a batch. If running the tests as a single batch it may be
|
||||
// possible to pass command line flags to the test to indicate individual tests.
|
||||
// The downside of this is that it forces a user to pass in two command line
|
||||
// flags for each test.
|
||||
int RunNamedTest(const std::string testname) {
|
||||
GoString go_testname = {
|
||||
p : testname.data(),
|
||||
n : static_cast<unsigned>(testname.size())
|
||||
};
|
||||
return RunTest(go_testname);
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasFactoryCreation) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasFactoryCreation"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestDescramblerFactoryCreation) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestDescramblerFactoryCreation"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCreateCasPlugin) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCreateCasPlugin"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCreateCasPluginWithSessionEvent) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCreateCasPluginWithSessionEvent"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasPluginEventPassing) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasPluginEventPassing"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestUniqueIdQuery) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestUniqueIdQuery"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasPluginProvision) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasPluginProvision"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasPluginEmmRequestWithInitData) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCasPluginEmmRequestWithInitData"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasEmmRequestWithPrivateData) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCasEmmRequestWithPrivateData"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasPrivateDataWithGroupLicense) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCasPrivateDataWithGroupLicense"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasWithOfflineEMM) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasWithOfflineEMM"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasCanStoreOfflineEMM) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasCanStoreOfflineEMM"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasCanNotStoreOfflineEMM) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCasCanNotStoreOfflineEMM"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestDescrambler) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestDescrambler"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestSession) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestSession"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestPESDecrypt) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestPESDecrypt"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestConcatentatedPESDecrypt) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestConcatentatedPESDecrypt"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestPlaybackDecrypt) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestPlaybackDecrypt"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasRenewal) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasRenewal"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestRestoreRenewalAndExpiredLicense) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestRestoreRenewalAndExpiredLicense"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestPesHeaderDecrypt) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestPesHeaderDecrypt"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestPesHeaderDecryptInTSPacket) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestPesHeaderDecryptInTSPacket"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestLicenseExpiration) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestLicenseExpiration"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestParentalControl) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestParentalControl"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestRemoveLicense) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestRemoveLicense"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestSessionEventPassing) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestSessionEventPassing"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestTSDecrypt) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestTSDecrypt"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestTSDecryptWithKeyRotation) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestTSDecryptWithKeyRotation"));
|
||||
}
|
||||
86
tests/src/mock_crypto_session.h
Normal file
86
tests/src/mock_crypto_session.h
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef MOCK_CRYPTO_SESSION_H
|
||||
#define MOCK_CRYPTO_SESSION_H
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "crypto_session.h"
|
||||
|
||||
class MockCryptoSession : public wvcas::CryptoSession {
|
||||
public:
|
||||
MockCryptoSession() {}
|
||||
virtual ~MockCryptoSession() {}
|
||||
|
||||
MOCK_METHOD0(initialize, wvcas::CasStatus());
|
||||
MOCK_METHOD0(reset, wvcas::CasStatus());
|
||||
MOCK_METHOD0(close, wvcas::CasStatus());
|
||||
MOCK_METHOD0(provisioning_method, wvcas::CasProvisioningMethod());
|
||||
MOCK_METHOD2(GetKeyData,
|
||||
wvcas::CasStatus(uint8_t* keyData, size_t* keyDataLength));
|
||||
MOCK_METHOD0(supported_certificates, wvcas::SupportedCertificates());
|
||||
MOCK_METHOD1(GenerateNonce, wvcas::CasStatus(uint32_t* nonce));
|
||||
MOCK_METHOD4(GenerateDerivedKeys,
|
||||
wvcas::CasStatus(const uint8_t* mac_key_context,
|
||||
uint32_t mac_key_context_length,
|
||||
const uint8_t* enc_key_context,
|
||||
uint32_t enc_key_context_length));
|
||||
MOCK_METHOD3(PrepareAndSignLicenseRequest,
|
||||
wvcas::CasStatus(const std::string& message,
|
||||
std::string* core_message,
|
||||
std::string* signature));
|
||||
MOCK_METHOD3(PrepareAndSignRenewalRequest,
|
||||
wvcas::CasStatus(const std::string& message,
|
||||
std::string* core_message,
|
||||
std::string* signature));
|
||||
MOCK_METHOD3(PrepareAndSignProvisioningRequest,
|
||||
wvcas::CasStatus(const std::string& message,
|
||||
std::string* core_message,
|
||||
std::string* signature));
|
||||
MOCK_METHOD4(LoadProvisioning,
|
||||
wvcas::CasStatus(const std::string& signed_message,
|
||||
const std::string& core_message,
|
||||
const std::string& signature,
|
||||
std::string* wrapped_private_key));
|
||||
MOCK_METHOD2(GetOEMPublicCertificate,
|
||||
wvcas::CasStatus(uint8_t* public_cert,
|
||||
size_t* public_cert_length));
|
||||
MOCK_METHOD5(GenerateRSASignature,
|
||||
wvcas::CasStatus(const uint8_t* message, size_t message_length,
|
||||
uint8_t* signature, size_t* signature_length,
|
||||
RSA_Padding_Scheme padding_scheme));
|
||||
MOCK_METHOD6(DeriveKeysFromSessionKey,
|
||||
wvcas::CasStatus(const uint8_t* enc_session_key,
|
||||
size_t enc_session_key_length,
|
||||
const uint8_t* mac_key_context,
|
||||
size_t mac_key_context_length,
|
||||
const uint8_t* enc_key_context,
|
||||
size_t enc_key_context_length));
|
||||
MOCK_METHOD3(LoadLicense, wvcas::CasStatus(const std::string& signed_message,
|
||||
const std::string& core_message,
|
||||
const std::string& signature));
|
||||
MOCK_METHOD3(LoadRenewal, wvcas::CasStatus(const std::string& signed_message,
|
||||
const std::string& core_message,
|
||||
const std::string& signature));
|
||||
MOCK_METHOD3(LoadCasECMKeys, wvcas::CasStatus(OEMCrypto_SESSION session,
|
||||
const wvcas::KeySlot* even_key,
|
||||
const wvcas::KeySlot* odd_key));
|
||||
MOCK_METHOD3(SelectKey, wvcas::CasStatus(OEMCrypto_SESSION session,
|
||||
const std::vector<uint8_t>& key_id,
|
||||
wvcas::CryptoMode crypto_mode));
|
||||
MOCK_METHOD2(GetHdcpCapabilities, bool(wvcas::HdcpCapability* current,
|
||||
wvcas::HdcpCapability* max));
|
||||
MOCK_METHOD1(GetDeviceID, wvcas::CasStatus(std::string* buffer));
|
||||
MOCK_METHOD2(LoadDeviceRSAKey,
|
||||
wvcas::CasStatus(const uint8_t* wrapped_rsa_key,
|
||||
size_t wrapped_rsa_key_length));
|
||||
MOCK_METHOD1(CreateEntitledKeySession,
|
||||
wvcas::CasStatus(uint32_t* entitled_key_session_id));
|
||||
MOCK_METHOD1(RemoveEntitledKeySession,
|
||||
wvcas::CasStatus(uint32_t entitled_key_session_id));
|
||||
};
|
||||
|
||||
#endif // MOCK_CRYPTO_SESSION_H
|
||||
356
tests/src/policy_engine_test.cpp
Normal file
356
tests/src/policy_engine_test.cpp
Normal file
@@ -0,0 +1,356 @@
|
||||
#include "policy_engine.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "cas_types.h"
|
||||
#include "cas_util.h"
|
||||
#include "clock.h"
|
||||
#include "license_key_status.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "mock_crypto_session.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::DoAll;
|
||||
using ::testing::NotNull;
|
||||
using ::testing::Return;
|
||||
using ::testing::Sequence;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::StrictMock;
|
||||
using wvcas::HdcpCapability;
|
||||
using wvcas::KeyAllowedUsage;
|
||||
using wvcas::KeyId;
|
||||
using wvcas::KeyStatus;
|
||||
using wvcas::KeyStatusMap;
|
||||
using wvcas::WidevinePsshData_EntitledKey;
|
||||
|
||||
namespace {
|
||||
const int64_t kDurationUnlimited = 0;
|
||||
const int64_t kLicenseStartTime = 1413517500; // ~ 01/01/2013
|
||||
const int64_t kPlaybackStartTime = kLicenseStartTime + 5;
|
||||
const int64_t kRentalDuration = 604800; // 7 days
|
||||
const int64_t kPlaybackDuration = 172800; // 48 hours
|
||||
const int64_t kLicenseDuration = kRentalDuration + kPlaybackDuration;
|
||||
const int64_t kLicenseRenewalPeriod = 120; // 2 minutes
|
||||
const int64_t kLicenseRenewalRetryInterval = 30; // 30 seconds
|
||||
const int64_t kLicenseRenewalRecoveryDuration = 30; // 30 seconds
|
||||
const int64_t kLowDuration = 300; // 5 minutes
|
||||
const int64_t kHighDuration =
|
||||
std::max(std::max(kRentalDuration, kPlaybackDuration), kLicenseDuration);
|
||||
const char* kRenewalServerUrl =
|
||||
"https://test.google.com/license/GetCencLicense";
|
||||
const KeyId kKeyId = "357adc89f1673433c36c621f1b5c41ee";
|
||||
const KeyId kEntitlementKeyId = "entitlementkeyid";
|
||||
const KeyId kAnotherKeyId = "another_key_id";
|
||||
const KeyId kSomeRandomKeyId = "some_random_key_id";
|
||||
const KeyId kUnknownKeyId = "some_random_unknown_key_id";
|
||||
} // namespace
|
||||
|
||||
class MockClock : public wvutil::Clock {
|
||||
public:
|
||||
MockClock() {}
|
||||
virtual ~MockClock() {}
|
||||
|
||||
MOCK_METHOD0(GetCurrentTime, int64_t());
|
||||
};
|
||||
|
||||
class MockLicenseKeys : public wvcas::LicenseKeys {
|
||||
public:
|
||||
MockLicenseKeys() {}
|
||||
virtual ~MockLicenseKeys() {}
|
||||
|
||||
MOCK_METHOD0(Empty, bool());
|
||||
MOCK_METHOD1(IsContentKey, bool(const KeyId& key_id));
|
||||
MOCK_METHOD1(CanDecryptContent, bool(const KeyId& key_id));
|
||||
MOCK_METHOD2(GetAllowedUsage,
|
||||
bool(const KeyId& key_id, KeyAllowedUsage* allowed_usage));
|
||||
MOCK_METHOD2(ApplyStatusChange,
|
||||
bool(KeyStatus new_status, bool* new_usable_keys));
|
||||
MOCK_METHOD1(GetKeyStatus, KeyStatus(const KeyId& key_id));
|
||||
MOCK_METHOD1(ExtractKeyStatuses, void(KeyStatusMap* content_keys));
|
||||
MOCK_METHOD1(MeetsConstraints, bool(const KeyId& key_id));
|
||||
MOCK_METHOD2(ApplyConstraints,
|
||||
void(uint32_t new_resolution, HdcpCapability new_hdcp_level));
|
||||
MOCK_METHOD1(SetFromLicense, void(const video_widevine::License& license));
|
||||
MOCK_METHOD1(SetEntitledKeys,
|
||||
void(const std::vector<WidevinePsshData_EntitledKey>& keys));
|
||||
};
|
||||
|
||||
class MockEventListener : public wvcas::CasEventListener {
|
||||
public:
|
||||
MockEventListener() {}
|
||||
virtual ~MockEventListener() {}
|
||||
|
||||
MOCK_METHOD0(OnSessionRenewalNeeded, void());
|
||||
MOCK_METHOD2(OnSessionKeysChange,
|
||||
void(const KeyStatusMap& keys_status, bool has_new_usable_key));
|
||||
MOCK_METHOD1(OnExpirationUpdate, void(int64_t new_expiry_time_seconds));
|
||||
MOCK_METHOD1(OnNewRenewalServerUrl,
|
||||
void(const std::string& renewal_server_url));
|
||||
MOCK_METHOD0(OnLicenseExpiration, void());
|
||||
MOCK_METHOD2(OnAgeRestrictionUpdated,
|
||||
void(const wvcas::WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction));
|
||||
};
|
||||
|
||||
class TestablePolicyEngine : public wvcas::PolicyEngine {
|
||||
std::unique_ptr<wvcas::LicenseKeys> CreateLicenseKeys() override {
|
||||
std::unique_ptr<StrictMock<MockLicenseKeys> > license_keys =
|
||||
make_unique<StrictMock<MockLicenseKeys> >();
|
||||
license_keys_ = license_keys.get();
|
||||
return license_keys;
|
||||
}
|
||||
std::unique_ptr<wvutil::Clock> CreateClock() override {
|
||||
std::unique_ptr<StrictMock<MockClock> > clock =
|
||||
make_unique<StrictMock<MockClock> >();
|
||||
clock_ = clock.get();
|
||||
return clock;
|
||||
}
|
||||
|
||||
public:
|
||||
MockClock* clock_ = nullptr;
|
||||
MockLicenseKeys* license_keys_ = nullptr;
|
||||
};
|
||||
|
||||
class PolicyEngineTest : public ::testing::Test {
|
||||
public:
|
||||
PolicyEngineTest() {}
|
||||
virtual ~PolicyEngineTest() {}
|
||||
|
||||
void SetUp() {
|
||||
crypto_session_ = std::make_shared<StrictMock<MockCryptoSession> >();
|
||||
policy_engine_.initialize(crypto_session_, &event_listener_);
|
||||
|
||||
ASSERT_NE(policy_engine_.clock_, nullptr);
|
||||
ASSERT_NE(policy_engine_.license_keys_, nullptr);
|
||||
|
||||
license_.set_license_start_time(kLicenseStartTime);
|
||||
video_widevine::LicenseIdentification* id = license_.mutable_id();
|
||||
id->set_version(1);
|
||||
id->set_type(video_widevine::STREAMING);
|
||||
video_widevine::License::KeyContainer* key = license_.add_key();
|
||||
key->set_type(video_widevine::License::KeyContainer::CONTENT);
|
||||
key->set_id(kKeyId);
|
||||
video_widevine::License_Policy* policy = license_.mutable_policy();
|
||||
policy = license_.mutable_policy();
|
||||
policy->set_can_play(true);
|
||||
policy->set_can_persist(false);
|
||||
policy->set_can_renew(false);
|
||||
// This is similar to an OFFLINE policy.
|
||||
policy->set_rental_duration_seconds(kRentalDuration);
|
||||
policy->set_playback_duration_seconds(kPlaybackDuration);
|
||||
policy->set_license_duration_seconds(kLicenseDuration);
|
||||
policy->set_renewal_recovery_duration_seconds(
|
||||
kLicenseRenewalRecoveryDuration);
|
||||
policy->set_renewal_delay_seconds(0);
|
||||
policy->set_renewal_retry_interval_seconds(kLicenseRenewalRetryInterval);
|
||||
policy->set_renew_with_usage(false);
|
||||
}
|
||||
|
||||
MockEventListener event_listener_;
|
||||
std::shared_ptr<StrictMock<MockCryptoSession> > crypto_session_;
|
||||
video_widevine::License license_;
|
||||
TestablePolicyEngine policy_engine_;
|
||||
};
|
||||
|
||||
TEST_F(PolicyEngineTest, CanDecryptContent) {
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, IsContentKey(kKeyId))
|
||||
.WillOnce(Return(false))
|
||||
.WillOnce(Return(true))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, CanDecryptContent(kKeyId))
|
||||
.WillOnce(Return(false))
|
||||
.WillOnce(Return(true));
|
||||
|
||||
// IsContentKey == false : CanDecryptContent is not called.
|
||||
EXPECT_FALSE(policy_engine_.CanDecryptContent(kKeyId));
|
||||
// IsContentKey == true : CanDecryptContent is false.
|
||||
EXPECT_FALSE(policy_engine_.CanDecryptContent(kKeyId));
|
||||
// IsContentKey == true : CanDecryptContent is true.
|
||||
EXPECT_TRUE(policy_engine_.CanDecryptContent(kKeyId));
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, SetLicense_NoKey) {
|
||||
video_widevine::License license;
|
||||
license.mutable_policy();
|
||||
license.set_license_start_time(kLicenseStartTime);
|
||||
|
||||
EXPECT_FALSE(policy_engine_.IsExpired());
|
||||
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_));
|
||||
EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime())
|
||||
.WillOnce(Return(kLicenseStartTime + 1));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyStatusChange(wvcas::kKeyStatusExpired, _))
|
||||
.WillOnce(Return(false));
|
||||
EXPECT_CALL(event_listener_, OnLicenseExpiration);
|
||||
policy_engine_.SetLicense(license);
|
||||
EXPECT_TRUE(policy_engine_.IsExpired());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackSuccess_OfflineLicense) {
|
||||
// CryptoSession
|
||||
EXPECT_CALL(*crypto_session_, GetHdcpCapabilities(NotNull(), NotNull()))
|
||||
.WillRepeatedly(
|
||||
DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT), Return(true)));
|
||||
EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime())
|
||||
.WillOnce(Return(kLicenseStartTime + 1))
|
||||
.WillOnce(Return(kPlaybackStartTime))
|
||||
.WillOnce(Return(kLicenseStartTime + 10));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, Empty())
|
||||
.WillOnce(Return(true))
|
||||
.WillRepeatedly(Return(false));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyStatusChange(wvcas::kKeyStatusUsable, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(true), Return(true)))
|
||||
.WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(false)));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, ExtractKeyStatuses(NotNull()));
|
||||
EXPECT_CALL(event_listener_, OnSessionKeysChange(_, true));
|
||||
EXPECT_CALL(event_listener_,
|
||||
OnExpirationUpdate(kLicenseStartTime + kRentalDuration));
|
||||
EXPECT_CALL(event_listener_,
|
||||
OnExpirationUpdate(kPlaybackStartTime + kPlaybackDuration));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyConstraints(_, HDCP_NO_DIGITAL_OUTPUT))
|
||||
.Times(2);
|
||||
|
||||
policy_engine_.SetLicense(license_);
|
||||
policy_engine_.BeginDecryption();
|
||||
policy_engine_.OnTimerEvent();
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackSuccess_EntitlementLicenseExpiration) {
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_));
|
||||
EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime())
|
||||
.WillOnce(Return(kLicenseStartTime + 1))
|
||||
.WillOnce(Return(kPlaybackStartTime))
|
||||
.WillOnce(Return(kPlaybackStartTime + kPlaybackDuration + 10));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, Empty())
|
||||
.WillOnce(Return(true))
|
||||
.WillRepeatedly(Return(false));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyStatusChange(wvcas::kKeyStatusUsable, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(true), Return(true)))
|
||||
.WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(false)));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyStatusChange(wvcas::kKeyStatusExpired, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(true), Return(false)));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, ExtractKeyStatuses(NotNull()));
|
||||
EXPECT_CALL(event_listener_, OnSessionKeysChange(_, true));
|
||||
EXPECT_CALL(event_listener_, OnLicenseExpiration);
|
||||
EXPECT_CALL(event_listener_,
|
||||
OnExpirationUpdate(kLicenseStartTime + kRentalDuration));
|
||||
EXPECT_CALL(event_listener_,
|
||||
OnExpirationUpdate(kPlaybackStartTime + kPlaybackDuration));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, SetEntitledKeys(_));
|
||||
|
||||
video_widevine::License::KeyContainer* key = license_.mutable_key(0);
|
||||
key->set_type(video_widevine::License::KeyContainer::ENTITLEMENT);
|
||||
key->set_id(kEntitlementKeyId);
|
||||
|
||||
EXPECT_FALSE(policy_engine_.IsExpired());
|
||||
policy_engine_.SetLicense(license_);
|
||||
policy_engine_.BeginDecryption();
|
||||
policy_engine_.OnTimerEvent();
|
||||
|
||||
std::vector<WidevinePsshData_EntitledKey> entitled_keys(1);
|
||||
entitled_keys[0].set_entitlement_key_id(kEntitlementKeyId);
|
||||
entitled_keys[0].set_key_id(kKeyId);
|
||||
policy_engine_.SetEntitledLicenseKeys(entitled_keys);
|
||||
EXPECT_TRUE(policy_engine_.IsExpired());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackSuccess_StreamingLicense) {
|
||||
video_widevine::License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_license_duration_seconds(kLowDuration);
|
||||
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_));
|
||||
EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime())
|
||||
.WillOnce(Return(kLicenseStartTime + 1))
|
||||
.WillOnce(Return(kPlaybackStartTime))
|
||||
.WillOnce(Return(kLicenseStartTime + 10));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, Empty())
|
||||
.WillOnce(Return(true))
|
||||
.WillRepeatedly(Return(false));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyStatusChange(wvcas::kKeyStatusUsable, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(true), Return(true)))
|
||||
.WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(false)));
|
||||
EXPECT_CALL(event_listener_, OnSessionKeysChange(_, true));
|
||||
EXPECT_CALL(event_listener_,
|
||||
OnExpirationUpdate(kLicenseStartTime + kLowDuration));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, ExtractKeyStatuses(NotNull()));
|
||||
EXPECT_CALL(*crypto_session_, GetHdcpCapabilities(_, _))
|
||||
.WillRepeatedly(
|
||||
DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT), Return(false)));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, ApplyConstraints(_, HDCP_NONE))
|
||||
.Times(2);
|
||||
|
||||
policy_engine_.SetLicense(license_);
|
||||
policy_engine_.BeginDecryption();
|
||||
policy_engine_.OnTimerEvent();
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, RenewalEvents) {
|
||||
video_widevine::License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_license_duration_seconds(kLowDuration);
|
||||
policy->set_can_renew(true);
|
||||
policy->set_renewal_delay_seconds(kLicenseRenewalPeriod);;
|
||||
policy->set_renewal_retry_interval_seconds(kLicenseRenewalRetryInterval);
|
||||
|
||||
{
|
||||
Sequence set_license;
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_));
|
||||
EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime())
|
||||
.WillOnce(Return(kLicenseStartTime + 1));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, Empty()).WillOnce(Return(true));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyStatusChange(wvcas::kKeyStatusUsable, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(true), Return(true)));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, ExtractKeyStatuses(NotNull()));
|
||||
EXPECT_CALL(event_listener_,
|
||||
OnExpirationUpdate(kLicenseStartTime + kLowDuration));
|
||||
EXPECT_CALL(event_listener_, OnSessionKeysChange(_, true));
|
||||
policy_engine_.SetLicense(license_);
|
||||
}
|
||||
|
||||
{
|
||||
Sequence begin_decryption;
|
||||
EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime())
|
||||
.WillOnce(Return(kPlaybackStartTime));
|
||||
policy_engine_.BeginDecryption();
|
||||
}
|
||||
|
||||
EXPECT_CALL(*crypto_session_, GetHdcpCapabilities(_, _))
|
||||
.WillRepeatedly(
|
||||
DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT), Return(false)));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyStatusChange(wvcas::kKeyStatusUsable, NotNull()))
|
||||
.WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(false)));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, Empty())
|
||||
.WillRepeatedly(Return(false));
|
||||
|
||||
{
|
||||
Sequence on_timer;
|
||||
EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime())
|
||||
.WillOnce(Return(kLicenseStartTime + 10));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, ApplyConstraints(_, HDCP_NONE))
|
||||
.Times(2);
|
||||
policy_engine_.OnTimerEvent();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, RenewalUrl) {
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_));
|
||||
EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime());
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyStatusChange(_, NotNull()));
|
||||
EXPECT_CALL(event_listener_, OnExpirationUpdate(_));
|
||||
EXPECT_CALL(event_listener_,
|
||||
OnNewRenewalServerUrl(::testing::StrEq(kRenewalServerUrl)));
|
||||
|
||||
video_widevine::License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_renewal_server_url(kRenewalServerUrl);
|
||||
policy_engine_.SetLicense(license_);
|
||||
}
|
||||
66
tests/src/test_properties.cpp
Normal file
66
tests/src/test_properties.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "cas_properties.h"
|
||||
|
||||
// TODO(widevine-eng): Reevaluate using "www" in the company name and model name
|
||||
// fields. For now this is consistent with the values used in unit cdm testing.
|
||||
static constexpr char kGenericCompanyName[] = "www";
|
||||
static constexpr char kGenericModelName[] = "www";
|
||||
static constexpr char kProductName[] = "WidevineCasTests";
|
||||
static constexpr char kKeyArchitectureName[] = "architecture_name";
|
||||
static constexpr char kKeyDeviceName[] = "device_name";
|
||||
static constexpr char kOemcPath[] = "cas_oemc_path.so";
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
bool Properties::GetCompanyName(std::string* company_name) {
|
||||
if (company_name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*company_name = kGenericCompanyName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetModelName(std::string* model_name) {
|
||||
if (model_name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*model_name = kGenericModelName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetProductName(std::string* product_name) {
|
||||
if (product_name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*product_name = kProductName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetArchitectureName(std::string* arch_name) {
|
||||
if (arch_name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*arch_name = kKeyArchitectureName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetDeviceName(std::string* device_name) {
|
||||
if (device_name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*device_name = kKeyDeviceName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetOEMCryptoPath(std::string* path) {
|
||||
if (path == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*path = kOemcPath;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
34
tests/src/timer_test.cpp
Normal file
34
tests/src/timer_test.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "clock.h"
|
||||
#include "timer.h"
|
||||
|
||||
class TimerTest : public wvutil::TimerHandler, public ::testing::Test {
|
||||
public:
|
||||
void OnTimerEvent() override { ++count_; }
|
||||
wvutil::Clock clock_;
|
||||
wvutil::Timer timer_;
|
||||
uint64_t event_time_ = 0;
|
||||
uint64_t count_ = 0;
|
||||
};
|
||||
|
||||
TEST_F(TimerTest, Timer) {
|
||||
timer_.Start(this, 1);
|
||||
EXPECT_TRUE(timer_.IsRunning());
|
||||
sleep(2);
|
||||
timer_.Stop();
|
||||
EXPECT_EQ(2, count_);
|
||||
EXPECT_FALSE(timer_.IsRunning());
|
||||
|
||||
timer_.Start(this, 1);
|
||||
EXPECT_TRUE(timer_.IsRunning());
|
||||
sleep(3);
|
||||
timer_.Stop();
|
||||
EXPECT_EQ(5, count_);
|
||||
EXPECT_FALSE(timer_.IsRunning());
|
||||
}
|
||||
562
tests/src/widevine_cas_api_test.cpp
Normal file
562
tests/src/widevine_cas_api_test.cpp
Normal file
@@ -0,0 +1,562 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include "cas_util.h"
|
||||
#include "cas_license.h"
|
||||
#include "ecm_parser.h"
|
||||
#include "mock_crypto_session.h"
|
||||
#include "string_conversions.h"
|
||||
#include "widevine_cas_api.h"
|
||||
#include "widevine_cas_session_map.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::DoAll;
|
||||
using ::testing::NiceMock;
|
||||
using ::testing::Return;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::StrictMock;
|
||||
|
||||
typedef StrictMock<MockCryptoSession> StrictMockCryptoSession;
|
||||
|
||||
class MockLicense : public wvcas::CasLicense {
|
||||
public:
|
||||
MockLicense() {}
|
||||
~MockLicense() override {}
|
||||
|
||||
MOCK_CONST_METHOD0(IsExpired, bool());
|
||||
MOCK_METHOD5(GenerateEntitlementRequest,
|
||||
wvcas::CasStatus(const std::string& init_data,
|
||||
const std::string& device_certificate,
|
||||
const std::string& wrapped_rsa_key,
|
||||
wvcas::LicenseType license_type,
|
||||
std::string* signed_license_request));
|
||||
MOCK_METHOD2(HandleStoredLicense,
|
||||
wvcas::CasStatus(const std::string& wrapped_rsa_key,
|
||||
const std::string& license_file));
|
||||
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,
|
||||
wvcas::CasStatus(const std::string& entitlement_response,
|
||||
std::string* device_file));
|
||||
MOCK_METHOD0(BeginDecryption, void());
|
||||
MOCK_METHOD0(UpdateLicenseForLicenseRemove, void());
|
||||
};
|
||||
typedef StrictMock<MockLicense> StrictMockLicense;
|
||||
|
||||
class MockEventListener : public wvcas::CasEventListener {
|
||||
public:
|
||||
MockEventListener() {}
|
||||
~MockEventListener() override {}
|
||||
MOCK_METHOD0(OnSessionRenewalNeeded, void());
|
||||
MOCK_METHOD2(OnSessionKeysChange, void(const wvcas::KeyStatusMap& keys_status,
|
||||
bool has_new_usable_key));
|
||||
MOCK_METHOD1(OnExpirationUpdate, void(int64_t new_expiry_time_seconds));
|
||||
MOCK_METHOD1(OnNewRenewalServerUrl,
|
||||
void(const std::string& renewal_server_url));
|
||||
MOCK_METHOD0(OnLicenseExpiration, void());
|
||||
MOCK_METHOD2(OnAgeRestrictionUpdated,
|
||||
void(const wvcas::WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction));
|
||||
};
|
||||
typedef StrictMock<MockEventListener> StrictMockEventListener;
|
||||
|
||||
class MockFile : public wvutil::File {
|
||||
public:
|
||||
MockFile() {}
|
||||
~MockFile() override {}
|
||||
MOCK_METHOD2(Read, ssize_t(char* buffer, size_t bytes));
|
||||
MOCK_METHOD2(Write, ssize_t(const char* buffer, size_t bytes));
|
||||
MOCK_METHOD0(Close, void());
|
||||
};
|
||||
typedef StrictMock<MockFile> StrictMockFile;
|
||||
|
||||
class MockFileSystem : public wvutil::FileSystem {
|
||||
public:
|
||||
MockFileSystem() {}
|
||||
~MockFileSystem() override {}
|
||||
|
||||
// Until gmock is updated to a version post-April 2017, we need this
|
||||
// workaround to test functions that take or return smart pointers.
|
||||
// See
|
||||
// https://github.com/abseil/googletest/blob/master/googlemock/docs/CookBook.md#legacy-workarounds-for-move-only-types
|
||||
std::unique_ptr<wvutil::File> Open(const std::string& buffer, int flags) {
|
||||
return std::unique_ptr<wvutil::File>(DoOpen(buffer, flags));
|
||||
}
|
||||
MOCK_METHOD2(DoOpen, wvutil::File*(const std::string& file_path, int flags));
|
||||
MOCK_METHOD1(Exists, bool(const std::string& file_path));
|
||||
MOCK_METHOD1(Remove, bool(const std::string& file_path));
|
||||
MOCK_METHOD1(FileSize, ssize_t(const std::string& file_path));
|
||||
MOCK_METHOD2(List, bool(const std::string& dir_path,
|
||||
std::vector<std::string>* names));
|
||||
MOCK_CONST_METHOD0(origin, const std::string&());
|
||||
MOCK_METHOD1(SetOrigin, void(const std::string& origin));
|
||||
MOCK_CONST_METHOD0(identifier, const std::string&());
|
||||
MOCK_METHOD1(SetIdentifier, void(const std::string& identifier));
|
||||
MOCK_CONST_METHOD0(IsGlobal, bool());
|
||||
};
|
||||
typedef NiceMock<MockFileSystem> NiceMockFileSystem;
|
||||
|
||||
class MockWidevineSession : public wvcas::WidevineCasSession {
|
||||
class EcmParser : public wvcas::EcmParser {
|
||||
public:
|
||||
EcmParser() {}
|
||||
~EcmParser() override {}
|
||||
};
|
||||
|
||||
public:
|
||||
MockWidevineSession() {}
|
||||
~MockWidevineSession() override {}
|
||||
std::unique_ptr<const wvcas::EcmParser> getEcmParser(
|
||||
const wvcas::CasEcm& ecm) const override {
|
||||
return make_unique<EcmParser>();
|
||||
}
|
||||
MOCK_METHOD2(processEcm, wvcas::CasStatus(const wvcas::CasEcm& ecm,
|
||||
uint8_t parental_control_age));
|
||||
MOCK_METHOD2(HandleProcessEcm,
|
||||
wvcas::CasStatus(const wvcas::WvCasSessionId& sessionId,
|
||||
const wvcas::CasEcm& ecm));
|
||||
};
|
||||
|
||||
class TestWidevineCas : public wvcas::WidevineCas {
|
||||
public:
|
||||
TestWidevineCas() {
|
||||
pass_thru_license_ = make_unique<StrictMockLicense>();
|
||||
pass_thru_crypto_session_ = make_unique<StrictMockCryptoSession>();
|
||||
pass_thru_file_system_ = make_unique<NiceMockFileSystem>();
|
||||
crypto_session_ = pass_thru_crypto_session_.get();
|
||||
license_ = pass_thru_license_.get();
|
||||
file_system_ = pass_thru_file_system_.get();
|
||||
}
|
||||
~TestWidevineCas() override {}
|
||||
|
||||
std::shared_ptr<wvcas::CryptoSession> getCryptoSession() override {
|
||||
return std::move(pass_thru_crypto_session_);
|
||||
}
|
||||
|
||||
std::unique_ptr<wvcas::CasLicense> getCasLicense() override {
|
||||
return std::move(pass_thru_license_);
|
||||
}
|
||||
|
||||
std::unique_ptr<wvutil::FileSystem> getFileSystem() override {
|
||||
return std::move(pass_thru_file_system_);
|
||||
}
|
||||
|
||||
std::shared_ptr<wvcas::WidevineCasSession> newCasSession() override {
|
||||
return std::make_shared<StrictMock<MockWidevineSession> >();
|
||||
}
|
||||
|
||||
std::unique_ptr<StrictMockLicense> pass_thru_license_;
|
||||
std::unique_ptr<StrictMockCryptoSession> pass_thru_crypto_session_;
|
||||
std::unique_ptr<NiceMockFileSystem> pass_thru_file_system_;
|
||||
|
||||
StrictMockLicense* license_ = nullptr;
|
||||
StrictMockCryptoSession* crypto_session_ = nullptr;
|
||||
NiceMockFileSystem* file_system_ = nullptr;
|
||||
};
|
||||
|
||||
struct EcmTestParams {
|
||||
bool defer_ecm;
|
||||
bool stored_license;
|
||||
};
|
||||
|
||||
class WidevineCasTest : public testing::TestWithParam<EcmTestParams> {
|
||||
public:
|
||||
WidevineCasTest() {}
|
||||
~WidevineCasTest() override {}
|
||||
|
||||
StrictMockEventListener event_listener_;
|
||||
};
|
||||
|
||||
TEST_F(WidevineCasTest, initialize) {
|
||||
TestWidevineCas cas_api;
|
||||
|
||||
EXPECT_CALL(*(cas_api.crypto_session_), initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.initialize(nullptr).status_code());
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasTest, openAndcloseSession) {
|
||||
TestWidevineCas cas_api;
|
||||
|
||||
// Invalid
|
||||
EXPECT_NE(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.openSession(nullptr).status_code());
|
||||
EXPECT_NE(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.closeSession(wvcas::WvCasSessionId()).status_code());
|
||||
|
||||
// Valid
|
||||
EXPECT_CALL(*(cas_api.crypto_session_), initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.initialize(nullptr).status_code());
|
||||
|
||||
EXPECT_CALL(*(cas_api.crypto_session_), CreateEntitledKeySession(_))
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
wvcas::WvCasSessionId sid;
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.openSession(&sid).status_code());
|
||||
|
||||
EXPECT_CALL(*(cas_api.crypto_session_), RemoveEntitledKeySession(_))
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.closeSession(sid).status_code());
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasTest, generateEntitlementRequest) {
|
||||
TestWidevineCas cas_api;
|
||||
|
||||
EXPECT_CALL(*(cas_api.crypto_session_), initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.initialize(nullptr).status_code());
|
||||
|
||||
// Invalid parameter.
|
||||
std::string request, init_data;
|
||||
EXPECT_NE(
|
||||
wvcas::CasStatusCode::kNoError,
|
||||
cas_api.generateEntitlementRequest(init_data, nullptr).status_code());
|
||||
|
||||
// GenerateEntitlementRequest returns an error.
|
||||
EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest(_, _, _, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatus(
|
||||
wvcas::CasStatusCode::kCasLicenseError, "forced failure")));
|
||||
EXPECT_NE(
|
||||
wvcas::CasStatusCode::kNoError,
|
||||
cas_api.generateEntitlementRequest(init_data, &request).status_code());
|
||||
|
||||
// HandleStoredLicense returns an error.
|
||||
// Call to Open will return a unique_ptr, freeing this mock_file object.
|
||||
MockFile* mock_file = new MockFile();
|
||||
size_t mock_filesize = 10;
|
||||
|
||||
EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(*cas_api.file_system_, FileSize(_))
|
||||
.WillRepeatedly(Return(mock_filesize));
|
||||
EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _))
|
||||
.WillRepeatedly(Return(mock_file));
|
||||
EXPECT_CALL(*mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize));
|
||||
|
||||
EXPECT_CALL(*cas_api.license_, HandleStoredLicense(_, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatus(
|
||||
wvcas::CasStatusCode::kCasLicenseError, "forced failure")));
|
||||
EXPECT_NE(
|
||||
wvcas::CasStatusCode::kNoError,
|
||||
cas_api.generateEntitlementRequest(init_data, &request).status_code());
|
||||
|
||||
mock_file = new MockFile();
|
||||
EXPECT_CALL(*cas_api.file_system_,
|
||||
DoOpen(_, _)).WillRepeatedly(Return(mock_file));
|
||||
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(_, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillRepeatedly(Return(true));
|
||||
|
||||
EXPECT_EQ(
|
||||
wvcas::CasStatusCode::kCasLicenseError,
|
||||
cas_api.generateEntitlementRequest(init_data, &request).status_code());
|
||||
|
||||
// Unable to remove the expired license file, return InvalidLicenseFile error.
|
||||
mock_file = new MockFile();
|
||||
EXPECT_CALL(*cas_api.file_system_,
|
||||
DoOpen(_, _)).WillRepeatedly(Return(mock_file));
|
||||
EXPECT_CALL(*mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize));
|
||||
// Unable to remove the expired license file, return InvalidLicenseFile error
|
||||
EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillOnce(Return(false));
|
||||
EXPECT_EQ(
|
||||
wvcas::CasStatusCode::kInvalidLicenseFile,
|
||||
cas_api.generateEntitlementRequest(init_data, &request).status_code());
|
||||
|
||||
// For stored license file not expired, return valid response.
|
||||
mock_file = new MockFile();
|
||||
EXPECT_CALL(*cas_api.file_system_,
|
||||
DoOpen(_, _)).WillRepeatedly(Return(mock_file));
|
||||
EXPECT_CALL(*mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize));
|
||||
// For stored license file not expired, return valid response
|
||||
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false));
|
||||
EXPECT_EQ(
|
||||
wvcas::CasStatusCode::kNoError,
|
||||
cas_api.generateEntitlementRequest(init_data, &request).status_code());
|
||||
|
||||
// Valid response for new request.
|
||||
mock_file = new MockFile();
|
||||
EXPECT_CALL(*cas_api.file_system_,
|
||||
DoOpen(_, _)).WillRepeatedly(Return(mock_file));
|
||||
// Valid response for new request
|
||||
EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(false));
|
||||
EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest(_, _, _, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(
|
||||
wvcas::CasStatusCode::kNoError,
|
||||
cas_api.generateEntitlementRequest(init_data, &request).status_code());
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasTest, GenerateLicenseRenewal) {
|
||||
TestWidevineCas cas_api;
|
||||
|
||||
EXPECT_CALL(*(cas_api.crypto_session_), initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.initialize(nullptr).status_code());
|
||||
|
||||
// Invalid parameter.
|
||||
EXPECT_NE(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.generateEntitlementRenewalRequest(nullptr).status_code());
|
||||
|
||||
// GenerateEntitlementRenewalRequest returns an error.
|
||||
std::string request;
|
||||
EXPECT_CALL(*cas_api.license_, GenerateEntitlementRenewalRequest(_, _))
|
||||
.WillOnce(Return(wvcas::CasStatus(wvcas::CasStatusCode::kCasLicenseError,
|
||||
"forced failure")));
|
||||
EXPECT_NE(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.generateEntitlementRenewalRequest(&request).status_code());
|
||||
|
||||
// Valid
|
||||
EXPECT_CALL(*cas_api.license_, GenerateEntitlementRenewalRequest(_, _))
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.generateEntitlementRenewalRequest(&request).status_code());
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasTest, EntitlementRenewalResponse) {
|
||||
TestWidevineCas cas_api;
|
||||
|
||||
EXPECT_CALL(*cas_api.crypto_session_, initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.initialize(nullptr).status_code());
|
||||
|
||||
// Empty response.
|
||||
std::string init_data;
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kCasLicenseError,
|
||||
cas_api.handleEntitlementRenewalResponse("", init_data).status_code());
|
||||
|
||||
// Valid.
|
||||
EXPECT_CALL(*cas_api.license_, HandleEntitlementRenewalResponse(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.handleEntitlementRenewalResponse("response", init_data).status_code());
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasTest, RemoveExpiredLicenseInTimerEvent) {
|
||||
TestWidevineCas cas_api;
|
||||
|
||||
EXPECT_CALL(*cas_api.crypto_session_, initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.initialize(nullptr).status_code());
|
||||
|
||||
// Unable to remove: License is expired but mediaId object is null.
|
||||
EXPECT_CALL(*cas_api.license_, IsExpired()).WillOnce(Return(true));
|
||||
EXPECT_CALL(*cas_api.file_system_, Exists(_)).Times(0);
|
||||
cas_api.OnTimerEvent();
|
||||
|
||||
// CasMediaId has been initialized.
|
||||
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;
|
||||
EXPECT_EQ(
|
||||
wvcas::CasStatusCode::kNoError,
|
||||
cas_api.generateEntitlementRequest(init_data, &request).status_code());
|
||||
|
||||
// Valid: License is expired then start to remove.
|
||||
EXPECT_CALL(*cas_api.license_, IsExpired()).WillOnce(Return(true));
|
||||
EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillOnce(Return(true));
|
||||
cas_api.OnTimerEvent();
|
||||
|
||||
// Valid: License is expired but now it does not exist on file system.
|
||||
EXPECT_CALL(*cas_api.license_, IsExpired()).WillOnce(Return(true));
|
||||
EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillOnce(Return(false));
|
||||
EXPECT_CALL(*cas_api.file_system_, Remove(_)).Times(0);
|
||||
cas_api.OnTimerEvent();
|
||||
}
|
||||
|
||||
// Test ecm processing with both license storage and license request.
|
||||
EcmTestParams params[] = {
|
||||
{false, false}, // No deferral, No offline license
|
||||
{true, false}, // Defer ecms, No offline license
|
||||
{false, true}, // No deferral, Offline license
|
||||
{true, true}, // Defer ecms, Offline license
|
||||
};
|
||||
INSTANTIATE_TEST_SUITE_P(ECMProcessing, WidevineCasTest,
|
||||
testing::ValuesIn(params));
|
||||
|
||||
TEST_P(WidevineCasTest, ECMProcessing) {
|
||||
TestWidevineCas cas_api;
|
||||
bool test_deferred_ecm = GetParam().defer_ecm;
|
||||
bool test_stored_license = GetParam().stored_license;
|
||||
|
||||
EXPECT_CALL(*cas_api.crypto_session_, initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.initialize(nullptr).status_code());
|
||||
|
||||
wvcas::WvCasSessionId video_sid;
|
||||
wvcas::WvCasSessionId audio_sid;
|
||||
// In real implementation, these ids are generated by OEMCrypto.
|
||||
uint32_t video_session_id = 1;
|
||||
uint32_t audio_session_id = 2;
|
||||
EXPECT_CALL(*(cas_api.crypto_session_), CreateEntitledKeySession(_))
|
||||
.WillOnce(DoAll(SetArgPointee<0>(video_session_id),
|
||||
Return(wvcas::CasStatus::OkStatus())));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.openSession(&video_sid).status_code());
|
||||
EXPECT_EQ(video_sid, video_session_id);
|
||||
|
||||
EXPECT_CALL(*(cas_api.crypto_session_), CreateEntitledKeySession(_))
|
||||
.WillOnce(DoAll(SetArgPointee<0>(audio_session_id),
|
||||
Return(wvcas::CasStatus::OkStatus())));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.openSession(&audio_sid).status_code());
|
||||
EXPECT_EQ(audio_sid, audio_session_id);
|
||||
|
||||
wvcas::CasSessionPtr video_session =
|
||||
wvcas::WidevineCasSessionMap::instance().GetSession(video_sid);
|
||||
wvcas::CasSessionPtr audio_session =
|
||||
wvcas::WidevineCasSessionMap::instance().GetSession(audio_sid);
|
||||
|
||||
ASSERT_TRUE(video_session);
|
||||
ASSERT_TRUE(audio_session);
|
||||
|
||||
std::string video_ecm_str("video_ecm");
|
||||
std::string audio_ecm_str("audio_ecm");
|
||||
wvcas::CasEcm video_ecm(video_ecm_str.begin(), video_ecm_str.end());
|
||||
wvcas::CasEcm audio_ecm(audio_ecm_str.begin(), audio_ecm_str.end());
|
||||
|
||||
int expected_process_ecm_calls = 1;
|
||||
if (test_deferred_ecm) {
|
||||
expected_process_ecm_calls = 2;
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kDeferedEcmProcessing,
|
||||
cas_api.processEcm(video_sid, video_ecm).status_code());
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kDeferedEcmProcessing,
|
||||
cas_api.processEcm(audio_sid, audio_ecm).status_code());
|
||||
}
|
||||
// Two ecm streams * the number of processed ecms.
|
||||
int expected_begin_decryption_calls = expected_process_ecm_calls * 2;
|
||||
|
||||
EXPECT_CALL(*reinterpret_cast<MockWidevineSession*>(video_session.get()),
|
||||
processEcm(video_ecm, 0))
|
||||
.Times(expected_process_ecm_calls);
|
||||
EXPECT_CALL(*reinterpret_cast<MockWidevineSession*>(audio_session.get()),
|
||||
processEcm(audio_ecm, 0))
|
||||
.Times(expected_process_ecm_calls);
|
||||
EXPECT_CALL(*cas_api.license_, BeginDecryption())
|
||||
.Times(expected_begin_decryption_calls);
|
||||
|
||||
if (test_stored_license) {
|
||||
std::string request;
|
||||
std::string file("license_file");
|
||||
|
||||
EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(*cas_api.file_system_, FileSize(_))
|
||||
.WillOnce(Return(file.size()));
|
||||
auto* file_handle = new NiceMock<MockFile>;
|
||||
EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _))
|
||||
.WillOnce(Return(file_handle));
|
||||
|
||||
EXPECT_CALL(*file_handle, Read(_, _)).WillOnce(Return(file.size()));
|
||||
EXPECT_CALL(*cas_api.license_, HandleStoredLicense(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.generateEntitlementRequest("init_data", &request)
|
||||
.status_code());
|
||||
} else {
|
||||
// Empty response.
|
||||
std::string init_data;
|
||||
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, _))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.handleEntitlementResponse("response", init_data)
|
||||
.status_code());
|
||||
}
|
||||
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.processEcm(video_sid, video_ecm).status_code());
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.processEcm(audio_sid, audio_ecm).status_code());
|
||||
|
||||
EXPECT_CALL(*(cas_api.crypto_session_),
|
||||
RemoveEntitledKeySession(video_session_id));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.closeSession(video_sid).status_code());
|
||||
EXPECT_CALL(*(cas_api.crypto_session_),
|
||||
RemoveEntitledKeySession(audio_session_id));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.closeSession(audio_sid).status_code());
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasTest, RemoveLicense) {
|
||||
TestWidevineCas cas_api;
|
||||
|
||||
EXPECT_CALL(*cas_api.crypto_session_, initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.initialize(nullptr).status_code());
|
||||
|
||||
// mediaId is not initialized.
|
||||
std::string mocked_file_name;
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kCasLicenseError,
|
||||
cas_api.RemoveLicense(mocked_file_name).status_code());
|
||||
|
||||
// CasMediaId has been initialized.
|
||||
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;
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.generateEntitlementRequest(init_data, &request)
|
||||
.status_code());
|
||||
|
||||
// Unable to remove license file, return InvalidLicenseFile error.
|
||||
EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillOnce(Return(false));
|
||||
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kInvalidLicenseFile,
|
||||
cas_api.RemoveLicense(mocked_file_name).status_code());
|
||||
|
||||
// Happy case: remove the unused license file
|
||||
MockFile mock_file;
|
||||
size_t mock_filesize = 10;
|
||||
EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(*cas_api.file_system_, FileSize(_)).WillRepeatedly(Return(mock_filesize));
|
||||
EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)).WillRepeatedly(Return(&mock_file));
|
||||
EXPECT_CALL(mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize));
|
||||
EXPECT_CALL(mock_file, Close()).WillRepeatedly(Return());
|
||||
EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(*cas_api.license_, UpdateLicenseForLicenseRemove()).Times(0);
|
||||
|
||||
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_file_name = kBasePathPrefix + wvutil::b2a_hex(hash)
|
||||
+ std::string(".lic");
|
||||
|
||||
EXPECT_CALL(*cas_api.license_, UpdateLicenseForLicenseRemove()).Times(1);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.RemoveLicense(full_file_name).status_code());
|
||||
}
|
||||
283
tests/src/widevine_cas_session_test.cpp
Normal file
283
tests/src/widevine_cas_session_test.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cas_util.h"
|
||||
#include "mock_crypto_session.h"
|
||||
#include "string_conversions.h"
|
||||
#include "widevine_cas_session.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::DoAll;
|
||||
using ::testing::Eq;
|
||||
using ::testing::Invoke;
|
||||
using ::testing::NiceMock;
|
||||
using ::testing::NotNull;
|
||||
using ::testing::Return;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::StrictMock;
|
||||
|
||||
static const char kEvenEntitlementKeyId[] = "even_entitlement_key_id";
|
||||
static const char kOddEntitlementKeyId[] = "odd_entitlement_key_id";
|
||||
static const char kEvenKeyId[] = "even_key_id";
|
||||
static const char kOddKeyId[] = "odd_key_id";
|
||||
static const char kEvenWrappedKey[] = "even_wrapped_content_key";
|
||||
static const char kOddWrappedKey[] = "odd_wrapped_content_key";
|
||||
static const char kEvenWrappedKeyIv[] = "even_wrapped_content_key_iv";
|
||||
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;
|
||||
|
||||
MATCHER(IsValidKeyEvenSlotData, "") {
|
||||
if (nullptr == arg) {
|
||||
*result_listener << " keyslot is nullptr";
|
||||
return false;
|
||||
}
|
||||
std::string value = kEvenEntitlementKeyId;
|
||||
if (arg->entitlement_key_id !=
|
||||
std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " entitlement key_id is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kEvenKeyId;
|
||||
if (arg->key_id != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " key_id is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kEvenWrappedKey;
|
||||
if (arg->wrapped_key != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " wrapped_key is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kEvenWrappedKeyIv;
|
||||
if (arg->wrapped_key_iv != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " wapped_key_iv is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kEvenContentIv;
|
||||
if (arg->content_iv != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " content_iv is invalid";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MATCHER(IsValidKeyOddSlotData, "") {
|
||||
if (nullptr == arg) {
|
||||
*result_listener << " keyslot is nullptr";
|
||||
return false;
|
||||
}
|
||||
std::string value = kOddEntitlementKeyId;
|
||||
if (arg->entitlement_key_id !=
|
||||
std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " entitlement key_id is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kOddKeyId;
|
||||
if (arg->key_id != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " key_id is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kOddWrappedKey;
|
||||
if (arg->wrapped_key != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " wrapped_key is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kOddWrappedKeyIv;
|
||||
if (arg->wrapped_key_iv != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " wapped_key_iv is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kOddContentIv;
|
||||
if (arg->content_iv != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " content_iv is invalid";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class MockEcmParser : public wvcas::EcmParser {
|
||||
public:
|
||||
MOCK_CONST_METHOD0(sequence_count, uint8_t());
|
||||
MOCK_CONST_METHOD0(age_restriction, uint8_t());
|
||||
MOCK_CONST_METHOD0(crypto_mode, wvcas::CryptoMode());
|
||||
MOCK_CONST_METHOD0(rotation_enabled, bool());
|
||||
MOCK_CONST_METHOD1(entitlement_key_id,
|
||||
const std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_CONST_METHOD1(content_key_id,
|
||||
const std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_CONST_METHOD1(wrapped_key_data,
|
||||
const std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_CONST_METHOD1(wrapped_key_iv,
|
||||
const std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_CONST_METHOD1(content_iv,
|
||||
const std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
};
|
||||
|
||||
class CasSessionTest : public ::testing::Test {
|
||||
public:
|
||||
CasSessionTest() {}
|
||||
virtual ~CasSessionTest() {}
|
||||
};
|
||||
|
||||
// Allow getEcmParser to return a mocked ecm.
|
||||
class TestCasSession : public wvcas::WidevineCasSession {
|
||||
public:
|
||||
TestCasSession() {}
|
||||
virtual ~TestCasSession() {}
|
||||
|
||||
std::unique_ptr<const wvcas::EcmParser> getEcmParser(
|
||||
const wvcas::CasEcm& ecm) const;
|
||||
|
||||
std::vector<uint8_t> entitlement_key_id(wvcas::KeySlotId id) const {
|
||||
std::string key_id;
|
||||
if (id == wvcas::KeySlotId::kEvenKeySlot) {
|
||||
key_id = kEvenEntitlementKeyId;
|
||||
} else if (id == wvcas::KeySlotId::kOddKeySlot) {
|
||||
key_id = kOddEntitlementKeyId;
|
||||
}
|
||||
return std::vector<uint8_t>(key_id.begin(), key_id.end());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> content_key_id(wvcas::KeySlotId id) const {
|
||||
std::string key_id;
|
||||
if (id == wvcas::KeySlotId::kEvenKeySlot) {
|
||||
key_id = kEvenKeyId;
|
||||
} else if (id == wvcas::KeySlotId::kOddKeySlot) {
|
||||
key_id = kOddKeyId;
|
||||
}
|
||||
return std::vector<uint8_t>(key_id.begin(), key_id.end());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> wrapped_key_data(wvcas::KeySlotId id) const {
|
||||
std::string key;
|
||||
if (id == wvcas::KeySlotId::kEvenKeySlot) {
|
||||
key = kEvenWrappedKey;
|
||||
} else if (id == wvcas::KeySlotId::kOddKeySlot) {
|
||||
key = kOddWrappedKey;
|
||||
}
|
||||
return std::vector<uint8_t>(key.begin(), key.end());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> wrapped_key_iv(wvcas::KeySlotId id) const {
|
||||
std::string iv;
|
||||
if (id == wvcas::KeySlotId::kEvenKeySlot) {
|
||||
iv = kEvenWrappedKeyIv;
|
||||
} else if (id == wvcas::KeySlotId::kOddKeySlot) {
|
||||
iv = kOddWrappedKeyIv;
|
||||
}
|
||||
return std::vector<uint8_t>(iv.begin(), iv.end());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> content_iv(wvcas::KeySlotId id) const {
|
||||
std::string iv;
|
||||
if (id == wvcas::KeySlotId::kEvenKeySlot) {
|
||||
iv = kEvenContentIv;
|
||||
} else if (id == wvcas::KeySlotId::kOddKeySlot) {
|
||||
iv = kOddContentIv;
|
||||
}
|
||||
return std::vector<uint8_t>(iv.begin(), iv.end());
|
||||
}
|
||||
|
||||
void set_age_restriction(uint8_t age_restriction) {
|
||||
age_restriction_ = age_restriction;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t age_restriction_ = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<const wvcas::EcmParser> TestCasSession::getEcmParser(
|
||||
const wvcas::CasEcm& ecm) const {
|
||||
std::unique_ptr<NiceMock<MockEcmParser>> mock_ecm_parser(
|
||||
new NiceMock<MockEcmParser>);
|
||||
ON_CALL(*mock_ecm_parser, sequence_count()).WillByDefault(Return(0));
|
||||
ON_CALL(*mock_ecm_parser, age_restriction()).WillByDefault(Return(
|
||||
age_restriction_));
|
||||
ON_CALL(*mock_ecm_parser, crypto_mode())
|
||||
.WillByDefault(Return(wvcas::CryptoMode::kAesCTR));
|
||||
ON_CALL(*mock_ecm_parser, rotation_enabled()).WillByDefault(Return(true));
|
||||
ON_CALL(*mock_ecm_parser, entitlement_key_id(_))
|
||||
.WillByDefault(Invoke(this, &TestCasSession::entitlement_key_id));
|
||||
ON_CALL(*mock_ecm_parser, content_key_id(_))
|
||||
.WillByDefault(Invoke(this, &TestCasSession::content_key_id));
|
||||
ON_CALL(*mock_ecm_parser, wrapped_key_data(_))
|
||||
.WillByDefault(Invoke(this, &TestCasSession::wrapped_key_data));
|
||||
ON_CALL(*mock_ecm_parser, wrapped_key_iv(_))
|
||||
.WillByDefault(Invoke(this, &TestCasSession::wrapped_key_iv));
|
||||
ON_CALL(*mock_ecm_parser, content_iv(_))
|
||||
.WillByDefault(Invoke(this, &TestCasSession::content_iv));
|
||||
return std::unique_ptr<const wvcas::EcmParser>(mock_ecm_parser.release());
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, processEcm) {
|
||||
TestCasSession session;
|
||||
std::shared_ptr<StrictMock<MockCryptoSession>> mock =
|
||||
std::make_shared<StrictMock<MockCryptoSession>>();
|
||||
|
||||
uint32_t session_id;
|
||||
EXPECT_CALL(*mock, CreateEntitledKeySession(NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<0>(kEntitledKeySessionId),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.initialize(mock, &session_id).status_code());
|
||||
EXPECT_EQ(session_id, kEntitledKeySessionId);
|
||||
|
||||
wvcas::CasEcm ecm(184);
|
||||
EXPECT_CALL(*mock,
|
||||
LoadCasECMKeys(session_id, IsValidKeyEvenSlotData(),
|
||||
IsValidKeyOddSlotData()));
|
||||
session.processEcm(ecm, 0);
|
||||
EXPECT_CALL(*mock, RemoveEntitledKeySession(session_id));
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, parentalControl) {
|
||||
TestCasSession session;
|
||||
std::shared_ptr<StrictMock<MockCryptoSession>> mock =
|
||||
std::make_shared<StrictMock<MockCryptoSession>>();
|
||||
uint32_t session_id;
|
||||
EXPECT_CALL(*mock, CreateEntitledKeySession(NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<0>(kEntitledKeySessionId),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.initialize(mock, &session_id).status_code());
|
||||
EXPECT_EQ(session_id, kEntitledKeySessionId);
|
||||
|
||||
EXPECT_CALL(*mock, LoadCasECMKeys(session_id, IsValidKeyEvenSlotData(),
|
||||
IsValidKeyOddSlotData()));
|
||||
wvcas::CasEcm ecm(184);
|
||||
session.set_age_restriction(0); // No restriction.
|
||||
// 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());
|
||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.processEcm(ecm, 13).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());
|
||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.processEcm(ecm, 10).status_code());
|
||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.processEcm(ecm, 13).status_code());
|
||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kAccessDeniedByParentalControl,
|
||||
session.processEcm(ecm, 3).status_code());
|
||||
EXPECT_CALL(*mock, RemoveEntitledKeySession(session_id));
|
||||
}
|
||||
19
tests/src/wv_cas_test_main.cpp
Normal file
19
tests/src/wv_cas_test_main.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <iostream>
|
||||
|
||||
#include "OEMCryptoCAS.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace wvutil {
|
||||
extern LogPriority g_cutoff;
|
||||
} // namespace wvutil
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
wvutil::g_cutoff = wvutil::LOG_INFO;
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Reference in New Issue
Block a user