Regular update

Plugin:
1. Process ECM v3 and send fingerprinting/service_blocking events
2. Rmove unused function Ctr128Add
3. Add support for ECM v3

OEMCrypto:
1. Update API description of OEMCrypto_LoadCasECMKeys
2. Fix android build files for ODK
3. Load content keys to shared memory
4. Move KCB check to LoadCasKeys call
5. Support even/odd content keys to share entitlement key
This commit is contained in:
Lu Chen
2021-01-05 10:16:26 -08:00
parent 66d8498d2c
commit 00785b2ccd
38 changed files with 2234 additions and 747 deletions

View File

@@ -6,6 +6,8 @@ cc_binary {
"src/cas_license_test.cpp",
"src/crypto_session_test.cpp",
"src/ecm_parser_test.cpp",
"src/ecm_parser_v2_test.cpp",
"src/ecm_parser_v3_test.cpp",
"src/test_properties.cpp",
"src/widevine_cas_session_test.cpp",
"src/cas_session_map_test.cpp",

View File

@@ -4,49 +4,23 @@
#include "ecm_parser.h"
#include <arpa/inet.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <bitset>
#include <tuple>
#include "media_cas.pb.h"
namespace wvcas {
namespace {
constexpr int kCasIdSizeBytes = 2;
constexpr int kModeSizeBytes = 1;
constexpr int kVersionSizeBytes = 1;
constexpr int kIVFlagsSizeBytes = 1;
constexpr int kEntitlementKeyIDSizeBytes = 16;
constexpr int kContentKeyIDSizeBytes = 16;
constexpr int kContentKeyDataSize = 16;
constexpr int kWrappedKeyIVSizeBytes = 16;
constexpr int kEcmHeaderSizeBytes = kCasIdSizeBytes + kVersionSizeBytes;
constexpr int kEcmVersion2 = 2;
constexpr int kEcmVersion3 = 3;
constexpr int kEcmDescriptorSizeBytes =
kCasIdSizeBytes + kModeSizeBytes + kVersionSizeBytes + kIVFlagsSizeBytes;
constexpr int kECMVersion = 2;
// The cipher mode flags field in the ECM V2 is 4 bits.
constexpr uint8_t kAESCBCCryptoModeFlagsVal = (0x0 << 1);
constexpr uint8_t kAESCTRCryptoModeFlagsVal = (0x1 << 1);
constexpr uint8_t kDvbCsa2CryptoModeFlagsVal = (0x2 << 1);
constexpr uint8_t kDvbCsa3CryptoModeFlagsVal = (0x3 << 1);
constexpr uint8_t kDvbOFBCryptoModeFlagsVal = (0x4 << 1);
constexpr uint8_t kDvbSCTECryptoModeFlagsVal = (0x5 << 1);
constexpr uint8_t kRotationFlag = (0x1 << 0);
constexpr uint8_t kContentIVSizeFlag = (0x1 << 6);
constexpr uint8_t kEntitlementKeyIDFill = '1';
constexpr uint8_t kEvenContentKeyIDFill = '2';
constexpr uint8_t kEvenContentKeyDataFill = '3';
constexpr uint8_t kEvenWrappedKeyIVFill = '4';
constexpr uint8_t kEvenContentKeyIVFill = '5';
constexpr uint8_t kOddContentKeyIDFill = '6';
constexpr uint8_t kOddContentKeyDataFill = '7';
constexpr uint8_t kOddWrappedKeyIVFill = '8';
constexpr uint8_t kOddContentKeyIVFill = '9';
constexpr size_t kMaxEcmSizeBytes = 184;
constexpr size_t kValidEcmV2SizeBytes = 165;
constexpr uint16_t kSectionHeader1 = 0x80;
constexpr uint16_t kSectionHeader2 = 0x81;
@@ -56,242 +30,56 @@ constexpr uint16_t kWidevineCasId = 0x4AD4;
// New Widevine CAS IDs 0x56C0 to 0x56C9 (all inclusive).
constexpr uint16_t kWidevineNewCasIdLowerBound = 0x56C0;
constexpr uint16_t kWidevineNewCasIdUpperBound = 0x56C9;
} // namespace
class EcmParserTest : public testing::Test {
protected:
void SetUp() {
BuildEcm(kWidevineCasId, /*with_rotation=*/true, /*content_iv_flag=*/false);
std::vector<uint8_t> BuildEcm(uint16_t cas_id, uint8_t version) {
std::vector<uint8_t> ecm_data;
ecm_data.resize(kEcmHeaderSizeBytes);
ecm_data[0] = cas_id >> 8;
ecm_data[1] = cas_id & 0xff;
ecm_data[2] = version;
// Put some dummy data to make the ECM a valid one.
if (version <= 2) {
ecm_data.resize(kValidEcmV2SizeBytes);
} else {
video_widevine::EcmPayload ecm_payload;
ecm_payload.mutable_even_key_data()->set_entitlement_key_id("123");
video_widevine::SignedEcmPayload signed_ecm_payload;
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
ecm_data.resize(ecm_data.size() + signed_ecm_payload.ByteSize());
signed_ecm_payload.SerializeToArray(ecm_data.data() + kEcmHeaderSizeBytes,
signed_ecm_payload.ByteSize());
}
size_t ContentKeyIVSize(bool content_iv_flag);
size_t CalculateEcmSize(bool with_rotation, bool content_iv_flag = false);
void BuildEcm(uint16_t cas_id, bool with_rotation, bool content_iv_flag);
std::vector<uint8_t> ecm_data_;
std::unique_ptr<const wvcas::EcmParser> parser_;
};
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;
return ecm_data;
}
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);
// Verifies ECM parser can be created with different version.
class EcmParserVersionTest : public testing::Test,
public ::testing::WithParamInterface<uint8_t> {};
TEST_P(EcmParserVersionTest, CreateSuccess) {
std::vector<uint8_t> ecm_data = BuildEcm(kWidevineCasId, GetParam());
ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) != nullptr);
}
void EcmParserTest::BuildEcm(uint16_t cas_id, bool with_rotation,
bool content_iv_flag) {
ecm_data_.clear();
ecm_data_.reserve(CalculateEcmSize(with_rotation, content_iv_flag));
ecm_data_.resize(kCasIdSizeBytes, 0);
ecm_data_[0] = cas_id >> 8;
ecm_data_[1] = cas_id & 0xff;
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));
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;
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) {
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) {
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) {
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, AgeRestriction) {
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());
}
INSTANTIATE_TEST_SUITE_P(EcmParserVersionTest, EcmParserVersionTest,
::testing::Values(kEcmVersion2, kEcmVersion3));
// Verifies CAS ID returned by the parser must be expected ones.
class EcmParserCasIdTest
: public EcmParserTest,
public ::testing::WithParamInterface<::testing::tuple<uint16_t, bool>> {
protected:
void SetUp() override {
const uint16_t cas_id = ::testing::get<0>(GetParam());
BuildEcm(cas_id, /*with_rotation=*/true, /*content_iv_flag=*/false);
}
};
: public testing::Test,
public ::testing::WithParamInterface<::testing::tuple<uint16_t, bool>> {};
TEST_P(EcmParserCasIdTest, ValidateCasIds) {
bool expected_result = ::testing::get<1>(GetParam());
ASSERT_EQ(wvcas::EcmParser::create(ecm_data_, &parser_), expected_result);
const uint16_t cas_id = ::testing::get<0>(GetParam());
std::vector<uint8_t> ecm_data = BuildEcm(cas_id, kEcmVersion2);
const bool is_valid_id = ::testing::get<1>(GetParam());
if (is_valid_id) {
ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) != nullptr);
} else {
ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) == nullptr);
}
}
INSTANTIATE_TEST_SUITE_P(EcmWithLegacyCasId, EcmParserCasIdTest,
@@ -315,23 +103,28 @@ INSTANTIATE_TEST_SUITE_P(
// Verifies Section header and pointer field may be prepended to ECM.
class EcmParserSectionHeaderTest
: public EcmParserTest,
: public testing::Test,
public ::testing::WithParamInterface<uint8_t> {};
TEST_P(EcmParserSectionHeaderTest, EcmWithSectionHeaderOnly) {
std::vector<uint8_t> ecm_data = BuildEcm(kWidevineCasId, kEcmVersion2);
const std::vector<uint8_t> section_header = {GetParam(), 0, 0};
ecm_data_.insert(ecm_data_.begin(), section_header.begin(),
section_header.end());
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
ecm_data.insert(ecm_data.begin(), section_header.begin(),
section_header.end());
ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) != nullptr);
}
TEST_P(EcmParserSectionHeaderTest, EcmWithSectionHeaderAndPointerField) {
std::vector<uint8_t> ecm_data = BuildEcm(kWidevineCasId, kEcmVersion2);
const std::vector<uint8_t> section_header = {kPointerFieldZero, GetParam(), 0,
0};
ecm_data_.insert(ecm_data_.begin(), section_header.begin(),
section_header.end());
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
ecm_data.insert(ecm_data.begin(), section_header.begin(),
section_header.end());
ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) != nullptr);
}
INSTANTIATE_TEST_SUITE_P(EcmWithSectionHeader, EcmParserSectionHeaderTest,
::testing::Values(kSectionHeader1, kSectionHeader2));
} // namespace
} // namespace wvcas

View File

@@ -0,0 +1,270 @@
// 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_v2.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace {
constexpr int kCasIdSizeBytes = 2;
constexpr int kModeSizeBytes = 1;
constexpr int kVersionSizeBytes = 1;
constexpr int kIVFlagsSizeBytes = 1;
constexpr int kEntitlementKeyIDSizeBytes = 16;
constexpr int kContentKeyIDSizeBytes = 16;
constexpr int kContentKeyDataSize = 16;
constexpr int kWrappedKeyIVSizeBytes = 16;
constexpr int kEcmDescriptorSizeBytes =
kCasIdSizeBytes + kModeSizeBytes + kVersionSizeBytes + kIVFlagsSizeBytes;
constexpr int kECMVersion = 2;
// The cipher mode flags field in the ECM V2 is 4 bits.
constexpr uint8_t kAESCBCCryptoModeFlagsVal = (0x0 << 1);
constexpr uint8_t kAESCTRCryptoModeFlagsVal = (0x1 << 1);
constexpr uint8_t kDvbCsa2CryptoModeFlagsVal = (0x2 << 1);
constexpr uint8_t kDvbCsa3CryptoModeFlagsVal = (0x3 << 1);
constexpr uint8_t kDvbOFBCryptoModeFlagsVal = (0x4 << 1);
constexpr uint8_t kDvbSCTECryptoModeFlagsVal = (0x5 << 1);
constexpr uint8_t kRotationFlag = (0x1 << 0);
constexpr uint8_t kContentIVSizeFlag = (0x1 << 6);
constexpr uint8_t kEntitlementKeyIDFill = '1';
constexpr uint8_t kEvenContentKeyIDFill = '2';
constexpr uint8_t kEvenContentKeyDataFill = '3';
constexpr uint8_t kEvenWrappedKeyIVFill = '4';
constexpr uint8_t kEvenContentKeyIVFill = '5';
constexpr uint8_t kOddContentKeyIDFill = '6';
constexpr uint8_t kOddContentKeyDataFill = '7';
constexpr uint8_t kOddWrappedKeyIVFill = '8';
constexpr uint8_t kOddContentKeyIVFill = '9';
constexpr size_t kMaxEcmSizeBytes = 184;
constexpr uint16_t kWidevineCasId = 0x4AD4;
} // namespace
class EcmParserV2Test : public testing::Test {
protected:
void SetUp() { BuildEcm(/*with_rotation=*/true, /*content_iv_flag=*/false); }
size_t ContentKeyIVSize(bool content_iv_flag);
size_t CalculateEcmSize(bool with_rotation, bool content_iv_flag = false);
void BuildEcm(bool with_rotation, bool content_iv_flag);
std::vector<uint8_t> ecm_data_;
std::unique_ptr<const wvcas::EcmParserV2> parser_;
};
size_t EcmParserV2Test::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 EcmParserV2Test::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 EcmParserV2Test::BuildEcm(bool with_rotation, bool content_iv_flag) {
ecm_data_.clear();
ecm_data_.reserve(CalculateEcmSize(with_rotation, content_iv_flag));
ecm_data_.resize(kCasIdSizeBytes, 0);
ecm_data_[0] = kWidevineCasId >> 8;
ecm_data_[1] = kWidevineCasId & 0xff;
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(EcmParserV2Test, FieldsWithoutKeyRotation) {
bool content_key_iv_16b = false;
ecm_data_.resize(CalculateEcmSize(false, content_key_iv_16b));
ASSERT_TRUE(wvcas::EcmParserV2::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(EcmParserV2Test, FieldsWithKeyRotation) {
ecm_data_[3] |= kRotationFlag;
ASSERT_TRUE(wvcas::EcmParserV2::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(EcmParserV2Test, create) {
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
ecm_data_.resize(4);
EXPECT_FALSE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
ecm_data_.resize(4 + CalculateEcmSize(false));
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
ecm_data_.resize(kMaxEcmSizeBytes);
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
ecm_data_.resize(CalculateEcmSize(true));
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
EXPECT_FALSE(wvcas::EcmParserV2::create(ecm_data_, nullptr));
ecm_data_.resize(CalculateEcmSize(true));
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
EXPECT_FALSE(wvcas::EcmParserV2::create(ecm_data_, nullptr));
}
TEST_F(EcmParserV2Test, crypto_mode) {
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesCBC);
ecm_data_[3] = kAESCTRCryptoModeFlagsVal;
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesCTR);
ecm_data_[3] = kDvbCsa2CryptoModeFlagsVal;
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kDvbCsa2);
ecm_data_[3] = kDvbCsa3CryptoModeFlagsVal;
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kDvbCsa3);
ecm_data_[3] = kDvbOFBCryptoModeFlagsVal;
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesOFB);
ecm_data_[3] = kDvbSCTECryptoModeFlagsVal;
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesSCTE);
}
TEST_F(EcmParserV2Test, ContentKeyIVSizes) {
bool with_rotation = true;
bool iv_flag = false;
ecm_data_.resize(CalculateEcmSize(with_rotation, iv_flag));
ASSERT_TRUE(wvcas::EcmParserV2::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::EcmParserV2::create(ecm_data_, &parser_));
EXPECT_EQ(parser_->content_iv_size(), ContentKeyIVSize(iv_flag));
}
TEST_F(EcmParserV2Test, AgeRestriction) {
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
EXPECT_EQ(0, parser_->age_restriction());
uint8_t age_restriction = 16;
ecm_data_[4] |= age_restriction << 1;
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
EXPECT_EQ(age_restriction, parser_->age_restriction());
}
TEST_F(EcmParserV2Test, Version) {
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
EXPECT_EQ(parser_->version(), kECMVersion);
}

View File

@@ -0,0 +1,291 @@
// 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_v3.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <vector>
#include "media_cas.pb.h"
namespace wvcas {
namespace {
using video_widevine::EcmMetaData;
using video_widevine::EcmPayload;
using video_widevine::SignedEcmPayload;
constexpr int kEcmVersion = 3;
constexpr uint16_t kWidevineCasId = 0x4AD4;
constexpr int kEcmHeaderSizeByte = 3;
constexpr char kWrappedKeyIv[] = "wrapped_key_iv..";
constexpr char kWrappedKeyIv2[] = "wrapped_key_iv.2";
constexpr char kEntitlementId[] = "entitlement_id..";
constexpr char kEntitlementId2[] = "entitlement_id.2";
constexpr char kContentIv[] = "c_iv....c_iv....";
constexpr char kContentIv2[] = "c_iv....c_iv...2";
constexpr char kWrappedContentKey[] = "wrapped_key.....";
constexpr char kWrappedContentKey2[] = "wrapped_key....2";
void WriteEcmHeader(std::vector<uint8_t>* ecm) {
ecm->push_back(kWidevineCasId >> 8);
ecm->push_back(kWidevineCasId & 0xff);
ecm->push_back(kEcmVersion);
}
std::vector<uint8_t> GenerateEcm(const SignedEcmPayload& signed_ecm_payload) {
std::vector<uint8_t> ecm;
WriteEcmHeader(&ecm);
ecm.resize(kEcmHeaderSizeByte + signed_ecm_payload.ByteSize());
signed_ecm_payload.SerializeToArray(ecm.data() + kEcmHeaderSizeByte,
signed_ecm_payload.ByteSize());
return ecm;
}
TEST(EcmParserV3Test, CreateWithEmptyEcmFail) {
std::vector<uint8_t> ecm;
EXPECT_TRUE(EcmParserV3::Create(ecm) == nullptr);
}
TEST(EcmParserV3Test, CreateWithOnlyEcmHeaderFail) {
std::vector<uint8_t> ecm;
WriteEcmHeader(&ecm);
EXPECT_TRUE(EcmParserV3::Create(ecm) == nullptr);
}
TEST(EcmParserV3Test, CreateWithInvalidSignedEcmPayloadFail) {
std::vector<uint8_t> ecm;
WriteEcmHeader(&ecm);
// appends some chars as payload
ecm.resize(100, 'x');
EXPECT_TRUE(EcmParserV3::Create(ecm) == nullptr);
}
TEST(EcmParserV3Test, CreateWithInvalidSerializedEcmFail) {
SignedEcmPayload signed_ecm_payload;
signed_ecm_payload.set_serialized_payload("invalid");
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
EXPECT_TRUE(EcmParserV3::Create(ecm) == nullptr);
}
TEST(EcmParserV3Test, CreateWithEvenKeySuccess) {
EcmPayload ecm_payload;
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId);
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(kWrappedContentKey);
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv);
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv);
SignedEcmPayload signed_ecm_payload;
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_EQ(parser->version(), kEcmVersion);
EXPECT_EQ(parser->age_restriction(), 0);
EXPECT_EQ(parser->crypto_mode(), CryptoMode::kInvalid);
EXPECT_FALSE(parser->has_fingerprinting());
EXPECT_FALSE(parser->has_service_blocking());
EXPECT_EQ(parser->ecm_serialized_payload(), ecm_payload.SerializeAsString());
EXPECT_TRUE(parser->signature().empty());
EXPECT_FALSE(parser->rotation_enabled());
EXPECT_EQ(parser->content_iv_size(), 16);
std::vector<uint8_t> result =
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
result = parser->content_iv(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv);
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv);
}
TEST(EcmParserV3Test, CreateWithEvenOddKeysSuccess) {
EcmPayload ecm_payload;
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId);
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(kWrappedContentKey);
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv);
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv);
ecm_payload.mutable_odd_key_data()->set_entitlement_key_id(kEntitlementId2);
ecm_payload.mutable_odd_key_data()->set_wrapped_key_data(kWrappedContentKey2);
ecm_payload.mutable_odd_key_data()->set_content_iv(kContentIv2);
ecm_payload.mutable_odd_key_data()->set_wrapped_key_iv(kWrappedKeyIv2);
SignedEcmPayload signed_ecm_payload;
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_TRUE(parser->rotation_enabled());
std::vector<uint8_t> result =
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
result = parser->content_iv(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv);
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv);
result = parser->entitlement_key_id(KeySlotId::kOddKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId2);
result = parser->wrapped_key_data(KeySlotId::kOddKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey2);
result = parser->content_iv(KeySlotId::kOddKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv2);
result = parser->wrapped_key_iv(KeySlotId::kOddKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv2);
}
TEST(EcmParserV3Test, CreateWithOmittedOddKeyFieldsSuccess) {
EcmPayload ecm_payload;
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId);
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(kWrappedContentKey);
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv);
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv);
ecm_payload.mutable_odd_key_data()->set_wrapped_key_data(kWrappedContentKey2);
SignedEcmPayload signed_ecm_payload;
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_TRUE(parser->rotation_enabled());
std::vector<uint8_t> result =
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
result = parser->content_iv(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv);
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv);
result = parser->entitlement_key_id(KeySlotId::kOddKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
result = parser->wrapped_key_data(KeySlotId::kOddKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey2);
result = parser->content_iv(KeySlotId::kOddKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv);
result = parser->wrapped_key_iv(KeySlotId::kOddKeySlot);
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv);
}
TEST(EcmParserV3Test, AgeRestrictionSuccess) {
const int expected_age_restriction = 3;
EcmPayload ecm_payload;
ecm_payload.mutable_meta_data()->set_age_restriction(
expected_age_restriction);
SignedEcmPayload signed_ecm_payload;
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_EQ(parser->age_restriction(), expected_age_restriction);
}
class EcmParserV3AgeRestrictionTest
: public testing::Test,
public testing::WithParamInterface<uint8_t> {};
TEST_P(EcmParserV3AgeRestrictionTest, ExpectedAgeRestriction) {
const uint8_t expected_age_restriction = GetParam();
EcmPayload ecm_payload;
ecm_payload.mutable_meta_data()->set_age_restriction(
expected_age_restriction);
SignedEcmPayload signed_ecm_payload;
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_EQ(parser->age_restriction(), expected_age_restriction);
}
INSTANTIATE_TEST_SUITE_P(EcmParserV3AgeRestrictionTest,
EcmParserV3AgeRestrictionTest,
testing::Values(0, 3, 18));
class EcmParserV3CipherModeTest
: public testing::Test,
public testing::WithParamInterface<
testing::tuple<CryptoMode, EcmMetaData::CipherMode>> {};
TEST_P(EcmParserV3CipherModeTest, ExpectedCipherMode) {
const CryptoMode expected = std::get<0>(GetParam());
EcmPayload ecm_payload;
ecm_payload.mutable_meta_data()->set_cipher_mode(std::get<1>(GetParam()));
SignedEcmPayload signed_ecm_payload;
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_EQ(parser->crypto_mode(), expected);
}
INSTANTIATE_TEST_SUITE_P(
EcmParserV3CipherModeTest, EcmParserV3CipherModeTest,
testing::Values(
std::make_tuple(CryptoMode::kAesCBC, EcmMetaData::AES_CBC),
std::make_tuple(CryptoMode::kAesCTR, EcmMetaData::AES_CTR),
std::make_tuple(CryptoMode::kDvbCsa2, EcmMetaData::DVB_CSA2),
std::make_tuple(CryptoMode::kDvbCsa3, EcmMetaData::DVB_CSA3),
std::make_tuple(CryptoMode::kAesOFB, EcmMetaData::AES_OFB),
std::make_tuple(CryptoMode::kAesSCTE, EcmMetaData::AES_SCTE52)));
TEST(EcmParserV3Test, FingerprintingSuccess) {
EcmPayload ecm_payload;
ecm_payload.mutable_fingerprinting()->set_control("control");
SignedEcmPayload signed_ecm_payload;
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_TRUE(parser->has_fingerprinting());
EXPECT_EQ(parser->fingerprinting().SerializeAsString(),
ecm_payload.fingerprinting().SerializeAsString());
}
TEST(EcmParserV3Test, ServiceBlockingSuccess) {
EcmPayload ecm_payload;
ecm_payload.mutable_service_blocking()->add_device_groups("group");
SignedEcmPayload signed_ecm_payload;
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_TRUE(parser->has_service_blocking());
EXPECT_EQ(parser->service_blocking().SerializeAsString(),
ecm_payload.service_blocking().SerializeAsString());
}
TEST(EcmParserV3Test, SignatureSuccess) {
const std::string expected_signature = "signature";
SignedEcmPayload signed_ecm_payload;
signed_ecm_payload.set_signature(expected_signature);
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
ASSERT_TRUE(parser != nullptr);
EXPECT_EQ(parser->signature(), expected_signature);
}
} // namespace
} // namespace wvcas

View File

@@ -115,3 +115,6 @@ TEST(IntegrationTests, TestSessionEventPassing) {
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestSessionEventPassing"));
}
TEST(IntegrationTests, TestProcessEcmV3) {
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestProcessEcmV3"));
}

View File

@@ -91,6 +91,14 @@ class MockEventListener : public wvcas::CasEventListener {
MOCK_METHOD2(OnAgeRestrictionUpdated,
void(const wvcas::WvCasSessionId& sessionId,
uint8_t ecm_age_restriction));
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));
};
class TestablePolicyEngine : public wvcas::PolicyEngine {

View File

@@ -85,6 +85,14 @@ class MockEventListener : public wvcas::CasEventListener {
MOCK_METHOD2(OnAgeRestrictionUpdated,
void(const wvcas::WvCasSessionId& sessionId,
uint8_t ecm_age_restriction));
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));
};
typedef StrictMock<MockEventListener> StrictMockEventListener;
@@ -125,19 +133,9 @@ class MockFileSystem : public wvutil::FileSystem {
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,

View File

@@ -4,6 +4,7 @@
#include "widevine_cas_session.h"
#include <cas_events.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -11,10 +12,13 @@
#include <string>
#include <vector>
#include "cas_types.h"
#include "cas_util.h"
#include "media_cas.pb.h"
#include "mock_crypto_session.h"
#include "string_conversions.h"
namespace {
using ::testing::_;
using ::testing::DoAll;
using ::testing::Eq;
@@ -109,20 +113,24 @@ MATCHER(IsValidKeyOddSlotData, "") {
class MockEcmParser : public wvcas::EcmParser {
public:
MOCK_CONST_METHOD0(sequence_count, uint8_t());
MOCK_CONST_METHOD0(version, uint8_t());
MOCK_CONST_METHOD0(age_restriction, uint8_t());
MOCK_CONST_METHOD0(crypto_mode, wvcas::CryptoMode());
MOCK_CONST_METHOD0(rotation_enabled, bool());
MOCK_CONST_METHOD0(content_iv_size, size_t());
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));
std::vector<uint8_t>(wvcas::KeySlotId id));
MOCK_CONST_METHOD1(content_key_id, 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));
std::vector<uint8_t>(wvcas::KeySlotId id));
MOCK_CONST_METHOD1(wrapped_key_iv, std::vector<uint8_t>(wvcas::KeySlotId id));
MOCK_CONST_METHOD1(content_iv, std::vector<uint8_t>(wvcas::KeySlotId id));
MOCK_CONST_METHOD0(has_fingerprinting, bool());
MOCK_CONST_METHOD0(fingerprinting, video_widevine::Fingerprinting());
MOCK_CONST_METHOD0(has_service_blocking, bool());
MOCK_CONST_METHOD0(service_blocking, video_widevine::ServiceBlocking());
MOCK_CONST_METHOD0(ecm_serialized_payload, std::string());
MOCK_CONST_METHOD0(signature, std::string());
};
class CasSessionTest : public ::testing::Test {
@@ -138,7 +146,7 @@ class TestCasSession : public wvcas::WidevineCasSession {
virtual ~TestCasSession() {}
std::unique_ptr<const wvcas::EcmParser> getEcmParser(
const wvcas::CasEcm& ecm) const;
const wvcas::CasEcm& ecm) const override;
std::vector<uint8_t> entitlement_key_id(wvcas::KeySlotId id) const {
std::string key_id;
@@ -194,15 +202,30 @@ class TestCasSession : public wvcas::WidevineCasSession {
age_restriction_ = age_restriction;
}
void set_fingerprinting_control(const std::string& control) {
fingerprinting_.clear_control();
if (!control.empty()) {
fingerprinting_.set_control(control);
}
}
void set_service_blocking_groups(const std::vector<std::string>& groups) {
service_blocking_.clear_device_groups();
for (auto const& group : groups) {
service_blocking_.add_device_groups(group);
}
}
private:
uint8_t age_restriction_ = 0;
video_widevine::Fingerprinting fingerprinting_;
video_widevine::ServiceBlocking service_blocking_;
};
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())
@@ -218,6 +241,14 @@ std::unique_ptr<const wvcas::EcmParser> TestCasSession::getEcmParser(
.WillByDefault(Invoke(this, &TestCasSession::wrapped_key_iv));
ON_CALL(*mock_ecm_parser, content_iv(_))
.WillByDefault(Invoke(this, &TestCasSession::content_iv));
ON_CALL(*mock_ecm_parser, has_fingerprinting())
.WillByDefault(Return(fingerprinting_.has_control()));
ON_CALL(*mock_ecm_parser, fingerprinting())
.WillByDefault(Return(fingerprinting_));
ON_CALL(*mock_ecm_parser, has_service_blocking())
.WillByDefault(Return(service_blocking_.device_groups_size() > 0));
ON_CALL(*mock_ecm_parser, service_blocking())
.WillByDefault(Return(service_blocking_));
return std::unique_ptr<const wvcas::EcmParser>(mock_ecm_parser.release());
}
@@ -231,7 +262,8 @@ TEST_F(CasSessionTest, processEcm) {
.WillOnce(DoAll(SetArgPointee<0>(kEntitledKeySessionId),
Return(wvcas::CasStatusCode::kNoError)));
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
session.initialize(mock, &session_id).status_code());
session.initialize(mock, /*event_listener=*/nullptr, &session_id)
.status_code());
EXPECT_EQ(session_id, kEntitledKeySessionId);
wvcas::CasEcm ecm(184);
@@ -250,7 +282,8 @@ TEST_F(CasSessionTest, parentalControl) {
.WillOnce(DoAll(SetArgPointee<0>(kEntitledKeySessionId),
Return(wvcas::CasStatusCode::kNoError)));
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
session.initialize(mock, &session_id).status_code());
session.initialize(mock, /*event_listener=*/nullptr, &session_id)
.status_code());
EXPECT_EQ(session_id, kEntitledKeySessionId);
EXPECT_CALL(*mock, LoadCasECMKeys(session_id, IsValidKeyEvenSlotData(),
@@ -281,3 +314,141 @@ TEST_F(CasSessionTest, parentalControl) {
session.processEcm(ecm, 3).status_code());
EXPECT_CALL(*mock, RemoveEntitledKeySession(session_id));
}
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));
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));
};
TEST_F(CasSessionTest, FingerprintingSuccess) {
TestCasSession session;
auto mock_crypto = std::make_shared<MockCryptoSession>();
MockEventListener mock_listener;
uint32_t session_id;
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
.status_code(),
wvcas::CasStatusCode::kNoError);
session.set_fingerprinting_control("control");
std::vector<uint8_t> expected_message = {0x00, 0x00, 0x07, 'c', 'o',
'n', 't', 'r', 'o', 'l'};
EXPECT_CALL(mock_listener,
OnSessionFingerprintingUpdated(session_id, expected_message))
.Times(1);
session.processEcm(wvcas::CasEcm(184, '0'), 0);
}
TEST_F(CasSessionTest, RepeatedFingerprintingNoEventSuccess) {
TestCasSession session;
auto mock_crypto = std::make_shared<MockCryptoSession>();
MockEventListener mock_listener;
uint32_t session_id;
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
.status_code(),
wvcas::CasStatusCode::kNoError);
session.set_fingerprinting_control("control");
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
session.processEcm(wvcas::CasEcm(184, '0'), 0);
// Same fingerprinting will not trigger event.
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(0);
session.processEcm(wvcas::CasEcm(184, '1'), 0);
}
TEST_F(CasSessionTest, DifferentFingerprintingTriggerEventSuccess) {
TestCasSession session;
auto mock_crypto = std::make_shared<MockCryptoSession>();
MockEventListener mock_listener;
uint32_t session_id;
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
.status_code(),
wvcas::CasStatusCode::kNoError);
session.set_fingerprinting_control("control");
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
session.processEcm(wvcas::CasEcm(184, '0'), 0);
// Different fingerprinting will trigger event.
session.set_fingerprinting_control("control2");
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
session.processEcm(wvcas::CasEcm(184, '1'), 0);
// Different fingerprinting (including empty) will trigger event.
session.set_fingerprinting_control("");
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
session.processEcm(wvcas::CasEcm(184, '2'), 0);
}
TEST_F(CasSessionTest, ServiceBlockingSuccess) {
TestCasSession session;
auto mock_crypto = std::make_shared<MockCryptoSession>();
MockEventListener mock_listener;
uint32_t session_id;
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
.status_code(),
wvcas::CasStatusCode::kNoError);
session.set_service_blocking_groups({"Group1", "g2"});
std::vector<uint8_t> expected_message = {0x00, 0x00, 0x06, 'G', 'r',
'o', 'u', 'p', '1', 0x00,
0x00, 0x02, 'g', '2'};
EXPECT_CALL(mock_listener,
OnSessionServiceBlockingUpdated(session_id, expected_message))
.Times(1);
session.processEcm(wvcas::CasEcm(184, '0'), 0);
}
TEST_F(CasSessionTest, RepeatedServiceBlockingNoEventSuccess) {
TestCasSession session;
auto mock_crypto = std::make_shared<MockCryptoSession>();
MockEventListener mock_listener;
uint32_t session_id;
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
.status_code(),
wvcas::CasStatusCode::kNoError);
session.set_service_blocking_groups({"Group1", "g2"});
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
session.processEcm(wvcas::CasEcm(184, '0'), 0);
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(0);
session.processEcm(wvcas::CasEcm(184, '1'), 0);
}
TEST_F(CasSessionTest, DifferentServiceBlockingTriggerEventSuccess) {
TestCasSession session;
auto mock_crypto = std::make_shared<MockCryptoSession>();
MockEventListener mock_listener;
uint32_t session_id;
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
.status_code(),
wvcas::CasStatusCode::kNoError);
session.set_service_blocking_groups({"Group1", "g2"});
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
session.processEcm(wvcas::CasEcm(184, '0'), 0);
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
session.set_service_blocking_groups({"Group1"});
session.processEcm(wvcas::CasEcm(184, '1'), 0);
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
session.set_service_blocking_groups({});
session.processEcm(wvcas::CasEcm(184, '2'), 0);
}
} // namespace