Cas Client repo update.

-Parse EMM in Cas plugin
-Entitlement key rotation support
-Multi_content_license support
This commit is contained in:
huihli
2021-06-28 11:25:24 -07:00
parent 5c81c1aa9e
commit 4e4f8c468f
5 changed files with 349 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef EMM_PARSER_H
#define EMM_PARSER_H
#include <memory>
#include <vector>
#include "cas_types.h"
#include "media_cas.pb.h"
namespace wvcas {
using video_widevine::EmmPayload;
class EmmParser {
public:
EmmParser(const EmmParser&) = delete;
EmmParser& operator=(const EmmParser&) = delete;
virtual ~EmmParser() = default;
// The EmmParser factory method.
// The methods validates the passed in |emm|. If validation is successful, it
// constructs and returns an EmmParser. Otherwise, nullptr is returned.
static std::unique_ptr<const EmmParser> Create(const CasEmm& emm);
// Accessor methods.
virtual uint64_t timestamp() const { return timestamp_; }
virtual std::string signature() const { return signature_; }
virtual EmmPayload emm_payload() const { return emm_payload_; };
protected:
// Called by the factory create and unit test.
EmmParser() = default;
private:
bool Parse(int start_index, const CasEmm& emm);
uint8_t version_;
uint64_t timestamp_;
EmmPayload emm_payload_;
std::string signature_;
};
} // namespace wvcas
#endif // EMM_PARSER_H

75
plugin/src/emm_parser.cpp Normal file
View File

@@ -0,0 +1,75 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "emm_parser.h"
#include "log.h"
namespace wvcas {
namespace {
// ETSI ETR 289 specifies table ids 0x82 to 0x8F are for CA System private
// usage, which are typically used by EMM, with one table id for each EMM type.
constexpr uint16_t kSectionHeader = 0x82;
constexpr size_t kSectionHeaderSize = 3;
constexpr size_t kSectionHeaderWithPointerSize = 4;
constexpr uint8_t kPointerFieldZero = 0x00;
// Returns the possible starting index of EMM. -1 will be returned in case of
// error. It assumes the pointer field will always set to 0, if present.
int find_emm_start_index(const CasEmm& cas_emm) {
if (cas_emm.empty()) {
return -1;
}
// Case 1: Pointer field (always set to 0); section header; EMM.
if (cas_emm[0] == kPointerFieldZero) {
return kSectionHeaderWithPointerSize < cas_emm.size()
? kSectionHeaderWithPointerSize
: -1;
}
// Case 2: Section header (3 bytes), EMM.
if (cas_emm[0] == kSectionHeader) {
return kSectionHeaderSize < cas_emm.size() ? kSectionHeaderSize : -1;
}
// Case 3: EMM.
return 0;
}
} // namespace
std::unique_ptr<const EmmParser> EmmParser::Create(const CasEmm& emm) {
auto parser = std::unique_ptr<EmmParser>(new EmmParser());
if (!parser->Parse(find_emm_start_index(emm), emm)) {
return nullptr;
}
return parser;
}
bool EmmParser::Parse(int start_index, const CasEmm& emm) {
if (start_index < 0) {
return false;
}
video_widevine::SignedEmmPayload signed_emm;
if (!signed_emm.ParseFromArray(emm.data() + start_index,
emm.size() - start_index)) {
LOGE("Failed to parse signed EMM.");
return false;
}
signature_ = signed_emm.signature();
if (signature_.empty()) {
LOGE("No signature in the EMM.");
return false;
}
if (!emm_payload_.ParseFromString(signed_emm.serialized_payload())) {
LOGE("Failed to parse EMM payload.");
return false;
}
timestamp_ = emm_payload_.timestamp_secs();
return true;
}
} // namespace wvcas

View File

@@ -0,0 +1,133 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "emm_parser.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <string>
#include <vector>
#include "cas_types.h"
namespace wvcas {
namespace {
using video_widevine::EmmPayload;
using video_widevine::SignedEmmPayload;
constexpr uint8_t kSectionHeader = 0x82;
constexpr int64_t kDefaultTimestamp = 1598905921;
constexpr char kDefaultSignature[] = "signature";
class EmmParserTest : public testing::Test {
protected:
EmmParserTest()
: timestamp_(kDefaultTimestamp), signature_(kDefaultSignature) {}
void SetSectionHeader(const std::vector<uint8_t> section_header) {
section_header_.assign(section_header.begin(), section_header.end());
}
void SetTimestamp(uint64_t timestamp) { timestamp_ = timestamp; }
void SetSignedEmm(const std::string& signed_emm) {
serialized_signed_emm_ = signed_emm;
}
void SetEmmPayload(const std::string& serialized_payload) {
serialized_emm_payload_ = serialized_payload;
}
void SetSignature(const std::string& signature) { signature_ = signature; }
std::vector<uint8_t> BuildEmm() const {
std::vector<uint8_t> emm_data(section_header_.begin(),
section_header_.end());
if (!serialized_signed_emm_.empty()) {
emm_data.insert(emm_data.end(), serialized_signed_emm_.begin(),
serialized_signed_emm_.end());
return emm_data;
}
SignedEmmPayload signed_emm;
if (serialized_emm_payload_.empty()) {
EmmPayload emm_payload;
emm_payload.set_timestamp_secs(timestamp_);
emm_payload.SerializeToString(signed_emm.mutable_serialized_payload());
} else {
signed_emm.set_serialized_payload(serialized_emm_payload_);
}
signed_emm.set_signature(signature_);
emm_data.resize(emm_data.size() + signed_emm.ByteSizeLong());
signed_emm.SerializeToArray(&emm_data[section_header_.size()],
emm_data.size());
return emm_data;
}
void ValidateParserAgainstDefault(const EmmParser* const parser) {
ASSERT_NE(parser, nullptr);
EXPECT_EQ(parser->timestamp(), kDefaultTimestamp);
EmmPayload expected_emm_payload;
expected_emm_payload.set_timestamp_secs(timestamp_);
EXPECT_EQ(parser->emm_payload().SerializeAsString(),
expected_emm_payload.SerializeAsString());
EXPECT_EQ(parser->signature(), kDefaultSignature);
}
private:
std::vector<uint8_t> section_header_;
uint64_t timestamp_;
std::string signature_;
std::string serialized_signed_emm_;
std::string serialized_emm_payload_;
};
TEST_F(EmmParserTest, ParseDefaultSuccess) {
auto parser = EmmParser::Create(BuildEmm());
ValidateParserAgainstDefault(parser.get());
}
TEST_F(EmmParserTest, EmmWithSectionHeaderSuccess) {
SetSectionHeader({kSectionHeader, 0, 0});
auto parser = EmmParser::Create(BuildEmm());
ValidateParserAgainstDefault(parser.get());
}
TEST_F(EmmParserTest, EmmWithSectionHeaderAndPointerFieldSuccess) {
SetSectionHeader({0, kSectionHeader, 0, 0});
auto parser = EmmParser::Create(BuildEmm());
ValidateParserAgainstDefault(parser.get());
}
TEST_F(EmmParserTest, EmmWithMalformedEmmCreateFail) {
SetSignedEmm("some emm");
EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull());
}
TEST_F(EmmParserTest, EmmWithMalformedPayloadCreateFail) {
SetEmmPayload("some payload");
EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull());
}
TEST_F(EmmParserTest, EmmWithNoSignatureCreateFail) {
SetSignature("");
EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull());
}
class EmmParserWrongPrefixTest
: public EmmParserTest,
public ::testing::WithParamInterface<std::vector<uint8_t>> {};
TEST_P(EmmParserWrongPrefixTest, EmmWithWrongPrefixCreateFail) {
SetSectionHeader(GetParam());
EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull());
}
INSTANTIATE_TEST_SUITE_P(
EmmParserWrongPrefixes, EmmParserWrongPrefixTest,
::testing::Values(std::vector<uint8_t>({0}),
std::vector<uint8_t>({1, 0, 0}),
std::vector<uint8_t>({kSectionHeader, 0}),
std::vector<uint8_t>({1, kSectionHeader, 0, 0})));
} // namespace
} // namespace wvcas

View File

@@ -0,0 +1,45 @@
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef MOCK_ECM_PARSER_H
#define MOCK_ECM_PARSER_H
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "ecm_parser.h"
class MockEcmParser : public wvcas::EcmParser {
public:
MOCK_METHOD(uint8_t, version, (), (const, override));
MOCK_METHOD(uint8_t, age_restriction, (), (const, override));
MOCK_METHOD(wvcas::CryptoMode, crypto_mode, (), (const, override));
MOCK_METHOD(bool, rotation_enabled, (), (const, override));
MOCK_METHOD(size_t, content_iv_size, (), (const, override));
MOCK_METHOD(std::vector<uint8_t>, entitlement_key_id, (wvcas::KeySlotId id),
(const, override));
MOCK_METHOD(std::vector<uint8_t>, content_key_id, (wvcas::KeySlotId id),
(const, override));
MOCK_METHOD(std::vector<uint8_t>, wrapped_key_data, (wvcas::KeySlotId id),
(const, override));
MOCK_METHOD(std::vector<uint8_t>, wrapped_key_iv, (wvcas::KeySlotId id),
(const, override));
MOCK_METHOD(std::vector<uint8_t>, content_iv, (wvcas::KeySlotId id),
(const, override));
MOCK_METHOD(bool, set_group_id, (const std::string& group_id), (override));
MOCK_METHOD(bool, has_fingerprinting, (), (const, override));
MOCK_METHOD(video_widevine::Fingerprinting, fingerprinting, (),
(const, override));
MOCK_METHOD(bool, has_service_blocking, (), (const, override));
MOCK_METHOD(video_widevine::ServiceBlocking, service_blocking, (),
(const, override));
MOCK_METHOD(std::string, ecm_serialized_payload, (), (const, override));
MOCK_METHOD(std::string, signature, (), (const, override));
MOCK_METHOD(bool, is_entitlement_rotation_enabled, (), (const, override));
MOCK_METHOD(uint32_t, entitlement_period_index, (), (const, override));
MOCK_METHOD(uint32_t, entitlement_rotation_window_left, (),
(const, override));
};
#endif // MOCK_ECM_PARSER_H

View File

@@ -0,0 +1,47 @@
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef MOCK_EVENT_LISTENER_H
#define MOCK_EVENT_LISTENER_H
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "cas_types.h"
class MockEventListener : public wvcas::CasEventListener {
public:
MockEventListener() {}
virtual ~MockEventListener() {}
MOCK_METHOD(void, OnSessionRenewalNeeded, (), (override));
MOCK_METHOD(void, OnSessionKeysChange,
(const wvcas::KeyStatusMap& keys_status, bool has_new_usable_key),
(override));
MOCK_METHOD(void, OnExpirationUpdate, (int64_t new_expiry_time_seconds),
(override));
MOCK_METHOD(void, OnNewRenewalServerUrl,
(const std::string& renewal_server_url), (override));
MOCK_METHOD(void, OnLicenseExpiration, (), (override));
MOCK_METHOD(void, OnAgeRestrictionUpdated,
(const wvcas::WvCasSessionId& sessionId,
uint8_t ecm_age_restriction),
(override));
MOCK_METHOD(void, OnSessionFingerprintingUpdated,
(const int32_t& sessionId,
const std::vector<uint8_t>& fingerprinting),
(override));
MOCK_METHOD(void, OnSessionServiceBlockingUpdated,
(const int32_t& sessionId,
const std::vector<uint8_t>& service_blocking),
(override));
MOCK_METHOD(void, OnFingerprintingUpdated,
(const std::vector<uint8_t>& fingerprinting), (override));
MOCK_METHOD(void, OnServiceBlockingUpdated,
(const std::vector<uint8_t>& service_blocking), (override));
MOCK_METHOD(void, OnEntitlementPeriodUpdateNeeded,
(const std::string& signed_license_request), (override));
};
#endif // MOCK_EVENT_LISTENER_H