Minor change

This commit is contained in:
Lu Chen
2020-02-25 13:16:44 -08:00
parent d71d62d272
commit ed5a1d5db1
18 changed files with 358 additions and 103 deletions

View File

@@ -69,6 +69,7 @@ cc_library(
":client_id_util",
"@abseil_repo//absl/synchronization",
"//protos/public:client_identification_cc_proto",
"//protos/public:device_common_cc_proto",
"//protos/public:provisioned_device_info_cc_proto",
"//protos/public:security_profile_cc_proto",
],
@@ -83,6 +84,8 @@ cc_test(
"//base",
"//external:protobuf",
"//testing:gunit_main",
"@abseil_repo//absl/memory",
"//protos/public:device_common_cc_proto",
"//protos/public:security_profile_cc_proto",
],
)
@@ -196,6 +199,7 @@ cc_library(
hdrs = ["device_info_util.h"],
deps = [
"@abseil_repo//absl/strings",
"//protos/public:device_common_cc_proto",
"//protos/public:provisioned_device_info_cc_proto",
],
)

View File

@@ -21,6 +21,7 @@
#include "common/signing_key_util.h"
#include "protos/public/drm_certificate.pb.h"
#include "protos/public/errors.pb.h"
#include "protos/public/license_protocol.pb.h"
#include "protos/public/signed_drm_certificate.pb.h"
namespace widevine {
@@ -80,6 +81,10 @@ class ClientCertAlgorithmRSA : public ClientCertAlgorithm {
return wrapped_session_key_;
}
SignedMessage::SessionKeyType session_key_type() const override {
return SignedMessage::WRAPPED_AES_KEY;
}
private:
std::unique_ptr<RsaPublicKey> rsa_public_key_;
std::string session_key_;
@@ -159,6 +164,10 @@ class ClientCertAlgorithmECC : public ClientCertAlgorithm {
return ephemeral_public_key_;
}
SignedMessage::SessionKeyType session_key_type() const override {
return SignedMessage::EPHERMERAL_ECC_PUBLIC_KEY;
}
private:
std::unique_ptr<ECPublicKey> client_ecc_public_key_;
std::string ephemeral_public_key_;

View File

@@ -40,6 +40,11 @@ class ClientCertAlgorithm {
// field of a license response. This key may be either an encrypted aes key,
// or the bytes of an ephemeral public key.
virtual const std::string& wrapped_session_key() const = 0;
// Returns information on the type session key used in this format. This value
// is intended to be included in the SignedMessage::session_key_type field of
// a license response.
virtual SignedMessage::SessionKeyType session_key_type() const = 0;
};
class CertificateClientCert : public ClientCert {
@@ -62,6 +67,9 @@ class CertificateClientCert : public ClientCert {
return algorithm_->wrapped_session_key();
}
const std::string& key() const override { return algorithm_->session_key(); }
SignedMessage::SessionKeyType key_type() const override {
return algorithm_->session_key_type();
}
const std::string& serial_number() const override {
return device_cert_.serial_number();
}

View File

@@ -60,6 +60,7 @@ class ClientCert {
virtual const std::string& encrypted_key() const = 0;
virtual const std::string& key() const = 0;
virtual SignedMessage::SessionKeyType key_type() const = 0;
virtual const std::string& serial_number() const = 0;
virtual const std::string& service_id() const = 0;
virtual const std::string& signing_key() const = 0;

View File

@@ -20,6 +20,9 @@
namespace widevine {
const char kModDrmMake[] = "company_name";
const char kModDrmModel[] = "model_name";
void AddClientInfo(ClientIdentification* client_id, absl::string_view name,
absl::string_view value) {
ClientIdentification_NameValue* nv = client_id->add_client_info();

View File

@@ -19,6 +19,9 @@
namespace widevine {
extern const char kModDrmMake[];
extern const char kModDrmModel[];
// Append the given name/value pair to client_id->client_info(). Does not
// check for duplicates.
void AddClientInfo(ClientIdentification* client_id, absl::string_view name,

View File

@@ -28,13 +28,17 @@ bool GetCoreProvisioningResponse(
const std::string& serialized_provisioning_response,
const std::string& request_core_message,
std::string* response_core_message) {
if (request_core_message.empty()) {
return false;
}
oemcrypto_core_message::ODK_ProvisioningRequest odk_provisioning_request;
CoreProvisioningRequestFromMessage(request_core_message,
&odk_provisioning_request);
CreateCoreProvisioningResponseFromProto(serialized_provisioning_response,
odk_provisioning_request,
response_core_message);
return true;
if (CoreProvisioningRequestFromMessage(request_core_message,
&odk_provisioning_request)) {
return CreateCoreProvisioningResponseFromProto(
serialized_provisioning_response, odk_provisioning_request,
response_core_message);
}
return false;
}
bool GetCoreRenewalOrReleaseLicenseResponse(

View File

@@ -10,6 +10,7 @@
#include "common/device_info_util.h"
#include "absl/strings/ascii.h"
#include "protos/public/device_common.pb.h"
namespace widevine {
bool IsMatchedMakeModel(const std::string& expected_make,
@@ -29,10 +30,10 @@ bool VerifyMakeModel(const ProvisionedDeviceInfo& device_info,
make_from_client, model_from_client)) {
return true;
}
for (ProvisionedDeviceInfo::ModelInfo product_info :
device_info.model_info()) {
if (IsMatchedMakeModel(product_info.manufacturer(), product_info.model(),
make_from_client, model_from_client)) {
for (DeviceModel product_info : device_info.model_info()) {
if (IsMatchedMakeModel(product_info.manufacturer(),
product_info.model_name(), make_from_client,
model_from_client)) {
return true;
}
}

View File

@@ -88,6 +88,7 @@ class MockClientCert : public ClientCert {
ProtocolVersion protocol_version));
MOCK_CONST_METHOD0(serial_number, const std::string &());
MOCK_CONST_METHOD0(key, const std::string &());
MOCK_CONST_METHOD0(key_type, SignedMessage::SessionKeyType());
MOCK_CONST_METHOD0(service_id, const std::string &());
MOCK_CONST_METHOD0(encrypted_key, const std::string &());
MOCK_CONST_METHOD0(signing_key, const std::string &());

View File

@@ -31,6 +31,9 @@ class KeyboxClientCert : public ClientCert {
const std::string& encrypted_key() const override { return unimplemented_; }
const std::string& key() const override { return device_key_; }
SignedMessage::SessionKeyType key_type() const override {
return SignedMessage::WRAPPED_AES_KEY;
}
const std::string& serial_number() const override { return serial_number_; }
const std::string& service_id() const override { return unimplemented_; }
const std::string& signing_key() const override { return signing_key_; }

View File

@@ -13,14 +13,15 @@
#include "common/client_id_util.h"
#include "protos/public/client_identification.pb.h"
#include "protos/public/device_common.pb.h"
#include "protos/public/provisioned_device_info.pb.h"
#include "protos/public/security_profile.pb.h"
namespace widevine {
using ClientCapabilities = ClientIdentification::ClientCapabilities;
const char kModDrmMake[] = "company_name";
const char kModDrmModel[] = "model_name";
SecurityProfileList::SecurityProfileList(const std::string& profile_namespace)
: profile_namespace_(profile_namespace) {}
int SecurityProfileList::Init() { return AddDefaultProfiles(); }
@@ -30,28 +31,28 @@ int SecurityProfileList::AddDefaultProfiles() {
const bool make_model_not_verified = false;
SecurityProfile profile;
PopulateProfile(SecurityProfile::SECURITY_PROFILE_LEVEL_1,
PopulateProfile(SecurityProfile::SECURITY_PROFILE_LEVEL_1, "WVSP1",
ClientCapabilities::HDCP_NONE,
ClientCapabilities::ANALOG_OUTPUT_UNKNOWN, oemcrypto_8,
make_model_not_verified, ProvisionedDeviceInfo::LEVEL_3,
kResourceTierLow, &profile);
InsertProfile(profile);
PopulateProfile(SecurityProfile::SECURITY_PROFILE_LEVEL_2,
PopulateProfile(SecurityProfile::SECURITY_PROFILE_LEVEL_2, "WVSP2",
ClientCapabilities::HDCP_NONE,
ClientCapabilities::ANALOG_OUTPUT_SUPPORTS_CGMS_A,
oemcrypto_12, make_model_not_verified,
ProvisionedDeviceInfo::LEVEL_2, kResourceTierLow, &profile);
InsertProfile(profile);
PopulateProfile(SecurityProfile::SECURITY_PROFILE_LEVEL_3,
PopulateProfile(SecurityProfile::SECURITY_PROFILE_LEVEL_3, "WVSP3",
ClientCapabilities::HDCP_V1,
ClientCapabilities::ANALOG_OUTPUT_SUPPORTS_CGMS_A,
oemcrypto_12, make_model_not_verified,
ProvisionedDeviceInfo::LEVEL_1, kResourceTierMed, &profile);
InsertProfile(profile);
PopulateProfile(SecurityProfile::SECURITY_PROFILE_LEVEL_4,
PopulateProfile(SecurityProfile::SECURITY_PROFILE_LEVEL_4, "WVSP4",
ClientCapabilities::HDCP_V2_2,
ClientCapabilities::ANALOG_OUTPUT_SUPPORTS_CGMS_A,
oemcrypto_12, make_model_not_verified,
@@ -61,31 +62,15 @@ int SecurityProfileList::AddDefaultProfiles() {
return security_profiles_.size();
}
SecurityProfile::Level SecurityProfileList::GetProfileLevel(
SecurityProfile::Level SecurityProfileList::GetBestProfileLevel(
const ClientIdentification& client_id,
const ProvisionedDeviceInfo& device_info,
SecurityProfile::DrmInfo* drm_info) const {
// Iterate through each SP starting from the strictest first.
absl::ReaderMutexLock lock(&mutex_);
// Profile list is assumed to be sorted.
for (auto& profile : security_profiles_) {
if (profile.min_security_requirements().security_level() <
device_info.security_level()) {
continue;
}
if (profile.min_security_requirements().oemcrypto_version() >
client_id.client_capabilities().oem_crypto_api_version()) {
continue;
}
if (profile.min_output_requirements().hdcp_version() >
client_id.client_capabilities().max_hdcp_version()) {
continue;
}
if (profile.min_output_requirements().analog_output_capabilities() >
client_id.client_capabilities().analog_output_capabilities()) {
continue;
}
if (profile.min_security_requirements().resource_rating_tier() >
client_id.client_capabilities().resource_rating_tier()) {
if (!IsProfileAllowed(profile, client_id, device_info)) {
continue;
}
if (drm_info != nullptr) {
@@ -96,6 +81,42 @@ SecurityProfile::Level SecurityProfileList::GetProfileLevel(
return SecurityProfile::SECURITY_PROFILE_LEVEL_UNDEFINED;
}
int SecurityProfileList::GetAllowedProfilesFromList(
const std::vector<std::string>& profiles_to_check,
const ClientIdentification& client_id,
const ProvisionedDeviceInfo& device_info,
std::vector<std::string>* profiles_to_allow) const {
if (profiles_to_allow == nullptr) {
return 0;
}
absl::ReaderMutexLock lock(&mutex_);
for (auto& profile_name : profiles_to_check) {
SecurityProfile profile;
if (GetProfileByName(profile_name, &profile)) {
if (IsProfileAllowed(profile, client_id, device_info)) {
profiles_to_allow->push_back(profile.name());
}
}
}
return profiles_to_allow->size();
}
int SecurityProfileList::GetAllowedProfiles(
const ClientIdentification& client_id,
const ProvisionedDeviceInfo& device_info,
std::vector<std::string>* profiles_to_allow) const {
if (profiles_to_allow == nullptr) {
return 0;
}
absl::ReaderMutexLock lock(&mutex_);
for (auto& profile : security_profiles_) {
if (IsProfileAllowed(profile, client_id, device_info)) {
profiles_to_allow->push_back(profile.name());
}
}
return profiles_to_allow->size();
}
bool SecurityProfileList::GetDrmInfo(const ClientIdentification& client_id,
const ProvisionedDeviceInfo& device_info,
SecurityProfile::DrmInfo* drm_info) const {
@@ -115,14 +136,14 @@ bool SecurityProfileList::GetDrmInfo(const ClientIdentification& client_id,
drm_info->mutable_security()->set_request_model_info_status(false);
drm_info->mutable_request_model_info()->set_manufacturer(
GetClientInfo(client_id, kModDrmMake));
drm_info->mutable_request_model_info()->set_model(
drm_info->mutable_request_model_info()->set_model_name(
GetClientInfo(client_id, kModDrmModel));
drm_info->set_system_id(device_info.system_id());
return true;
}
bool SecurityProfileList::PopulateProfile(
const SecurityProfile::Level profile_level,
const SecurityProfile::Level profile_level, const std::string& profile_name,
const ClientCapabilities::HdcpVersion min_hdcp_version,
const ClientCapabilities::AnalogOutputCapabilities
analog_output_capabilities,
@@ -134,6 +155,7 @@ bool SecurityProfileList::PopulateProfile(
return false;
}
profile_to_create->set_level(profile_level);
profile_to_create->set_name(profile_name);
profile_to_create->mutable_min_output_requirements()->set_hdcp_version(
min_hdcp_version);
profile_to_create->mutable_min_output_requirements()
@@ -149,8 +171,8 @@ bool SecurityProfileList::PopulateProfile(
return true;
}
bool SecurityProfileList::GetProfile(SecurityProfile::Level level,
SecurityProfile* security_profile) {
bool SecurityProfileList::GetProfileByLevel(
SecurityProfile::Level level, SecurityProfile* security_profile) const {
absl::ReaderMutexLock lock(&mutex_);
for (auto& profile : security_profiles_) {
if (profile.level() == level) {
@@ -163,10 +185,27 @@ bool SecurityProfileList::GetProfile(SecurityProfile::Level level,
return false;
}
bool SecurityProfileList::GetProfileByName(
const std::string& name, SecurityProfile* security_profile) const {
absl::ReaderMutexLock lock(&mutex_);
for (auto& profile : security_profiles_) {
if (profile.name() == name) {
if (security_profile != nullptr) {
*security_profile = profile;
}
return true;
}
}
return false;
}
bool SecurityProfileList::InsertProfile(
const SecurityProfile& profile_to_insert) {
// Check if profile already exist.
if (GetProfile(profile_to_insert.level(), nullptr)) {
if (GetProfileByLevel(profile_to_insert.level(), nullptr)) {
return false;
}
if (GetProfileByName(profile_to_insert.name(), nullptr)) {
return false;
}
absl::WriterMutexLock lock(&mutex_);
@@ -182,4 +221,30 @@ bool SecurityProfileList::CompareProfileLevel(const SecurityProfile& p1,
return (p1.level() > p2.level());
}
bool SecurityProfileList::IsProfileAllowed(
const SecurityProfile& profile, const ClientIdentification& client_id,
const ProvisionedDeviceInfo& device_info) const {
if (profile.min_security_requirements().security_level() <
device_info.security_level()) {
return false;
}
if (profile.min_security_requirements().oemcrypto_version() >
client_id.client_capabilities().oem_crypto_api_version()) {
return false;
}
if (profile.min_output_requirements().hdcp_version() >
client_id.client_capabilities().max_hdcp_version()) {
return false;
}
if (profile.min_output_requirements().analog_output_capabilities() >
client_id.client_capabilities().analog_output_capabilities()) {
return false;
}
if (profile.min_security_requirements().resource_rating_tier() >
client_id.client_capabilities().resource_rating_tier()) {
return false;
}
return true;
}
} // namespace widevine

View File

@@ -32,7 +32,7 @@ const uint32_t kResourceTierHigh = 3;
class SecurityProfileList {
public:
SecurityProfileList() {}
explicit SecurityProfileList(const std::string& profile_namespace);
~SecurityProfileList() {}
// Initialize the security profile list. The list is initially empty, this
@@ -44,24 +44,11 @@ class SecurityProfileList {
// if successfully inserted, false if unable to insert.
bool InsertProfile(const SecurityProfile& profile_to_insert);
// Return the highest security level based on the device capabilities.
// If |drm_info| is not null, |drm_info| is populated with the device data.
SecurityProfile::Level GetProfileLevel(
const ClientIdentification& client_id,
const ProvisionedDeviceInfo& device_info,
SecurityProfile::DrmInfo* drm_info) const;
// Return the device security capabilities. |drm_info| is populated with
// data from |client_id| and |device_info|. |drm_info| must not be null and
// is owned by the caller.
bool GetDrmInfo(const ClientIdentification& client_id,
const ProvisionedDeviceInfo& device_info,
SecurityProfile::DrmInfo* drm_info) const;
// Populate |profile_to_create| with the specified output protections and
// security parameters. All input parameters are used hence should be set.
bool PopulateProfile(
const SecurityProfile::Level profile_level,
const std::string& profile_name,
const ClientCapabilities::HdcpVersion min_hdcp_version,
const ClientCapabilities::AnalogOutputCapabilities
analog_output_capabilities,
@@ -70,11 +57,45 @@ class SecurityProfileList {
const uint32_t resource_rating_tier,
SecurityProfile* profile_to_create) const;
// Return the highest security level based on the device capabilities.
// If |drm_info| is not null, |drm_info| is populated with the device data.
SecurityProfile::Level GetBestProfileLevel(
const ClientIdentification& client_id,
const ProvisionedDeviceInfo& device_info,
SecurityProfile::DrmInfo* drm_info) const;
// Populates |profiles_to_allow| with a list of profiles that meet the
// requirements for the this device. The number of profiles is returned.
int GetAllowedProfiles(const ClientIdentification& client_id,
const ProvisionedDeviceInfo& device_info,
std::vector<std::string>* profiles_to_allow) const;
// Populates |profiles_allow| with a list of profiles from the specified
// |profiles_to_check| list that meet the requirements for the this device.
// The number of profiles is returned.
int GetAllowedProfilesFromList(
const std::vector<std::string>& profiles_to_check,
const ClientIdentification& client_id,
const ProvisionedDeviceInfo& device_info,
std::vector<std::string>* profiles_to_allow) const;
// Return true if a profile exist matching the specified |level|.
// |security_profile| is owned by the caller and is populated if a profile
// exist.
bool GetProfile(SecurityProfile::Level level,
SecurityProfile* security_profile);
bool GetProfileByLevel(SecurityProfile::Level level,
SecurityProfile* security_profile) const;
// Return true if a profile exist matching the specified |name|.
// |security_profile| is owned by the caller and is populated if a profile
// exist.
bool GetProfileByName(const std::string& name,
SecurityProfile* security_profile) const;
// Return the device security capabilities. |drm_info| is populated with
// data from |client_id| and |device_info|. |drm_info| must not be null and
// is owned by the caller.
bool GetDrmInfo(const ClientIdentification& client_id,
const ProvisionedDeviceInfo& device_info,
SecurityProfile::DrmInfo* drm_info) const;
private:
// Initialize the list with Widevine default profiles. The size of the
@@ -84,12 +105,14 @@ class SecurityProfileList {
static bool CompareProfileLevel(const SecurityProfile& p1,
const SecurityProfile& p2);
bool IsProfileAllowed(const SecurityProfile& profile,
const ClientIdentification& client_id,
const ProvisionedDeviceInfo& device_info) const;
mutable absl::Mutex mutex_;
// Widevine security profiles
// Security profiles
std::string profile_namespace_;
std::vector<SecurityProfile> security_profiles_ ABSL_GUARDED_BY(mutex_);
// Custom security profiles
std::map<std::string, SecurityProfile> custom_security_profiles_
ABSL_GUARDED_BY(mutex_);
};
} // namespace widevine

View File

@@ -12,6 +12,8 @@
#include "google/protobuf/util/message_differencer.h"
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "absl/memory/memory.h"
#include "protos/public/device_common.pb.h"
#include "protos/public/security_profile.pb.h"
namespace widevine {
@@ -47,6 +49,8 @@ class SecurityProfileListTest : public ::testing::Test {
->set_resource_rating_tier(kResourceTierHigh);
test_profile_1_.mutable_min_security_requirements()
->set_request_model_info_status(make_model_not_verified);
std::string profile_namespace = "widevine_test";
profile_list_ = absl::make_unique<SecurityProfileList>(profile_namespace);
ClientIdentification_NameValue *nv = client_id_.add_client_info();
nv->set_name(kMakeName);
@@ -65,7 +69,7 @@ class SecurityProfileListTest : public ::testing::Test {
device_info_.set_system_id(kSystemId);
}
SecurityProfile test_profile_1_;
SecurityProfileList profile_list_;
std::unique_ptr<SecurityProfileList> profile_list_;
ClientIdentification client_id_;
ProvisionedDeviceInfo device_info_;
};
@@ -73,19 +77,24 @@ class SecurityProfileListTest : public ::testing::Test {
TEST_F(SecurityProfileListTest, InsertProfile) {
// This test will not initialize the SecurityProfileList, hence it's empty.
// Insert test profile 1 into the list.
EXPECT_TRUE(profile_list_.InsertProfile(test_profile_1_));
// Should not allow insertion of an alreadfy existing level.
EXPECT_FALSE(profile_list_.InsertProfile(test_profile_1_));
EXPECT_TRUE(profile_list_->InsertProfile(test_profile_1_));
// Should not allow insertion of an already existing level.
EXPECT_FALSE(profile_list_->InsertProfile(test_profile_1_));
SecurityProfile profile;
ASSERT_EQ(SecurityProfile::SECURITY_PROFILE_LEVEL_1,
profile_list_.GetProfile(test_profile_1_.level(), &profile));
// Should not allow insertion of an already existing name.
// Make sure the level is not the same as already inserted level_1.
profile.set_level(SecurityProfile::SECURITY_PROFILE_LEVEL_2);
profile.set_name(test_profile_1_.name());
EXPECT_FALSE(profile_list_->InsertProfile(test_profile_1_));
ASSERT_TRUE(profile_list_->GetProfileByLevel(
SecurityProfile::SECURITY_PROFILE_LEVEL_1, &profile));
EXPECT_TRUE(
google::protobuf::util::MessageDifferencer::Equals(test_profile_1_, profile));
}
TEST_F(SecurityProfileListTest, GetDrmInfo) {
SecurityProfile::DrmInfo drm_info;
ASSERT_TRUE(profile_list_.GetDrmInfo(client_id_, device_info_, &drm_info));
ASSERT_TRUE(profile_list_->GetDrmInfo(client_id_, device_info_, &drm_info));
EXPECT_EQ(client_id_.client_capabilities().max_hdcp_version(),
drm_info.output().hdcp_version());
EXPECT_EQ(client_id_.client_capabilities().analog_output_capabilities(),
@@ -102,12 +111,12 @@ TEST_F(SecurityProfileListTest, GetDrmInfo) {
// make_mode status is currently hard-coded to false.
EXPECT_EQ(false, drm_info.security().request_model_info_status());
EXPECT_EQ(kMakeValue, drm_info.request_model_info().manufacturer());
EXPECT_EQ(kModelValue, drm_info.request_model_info().model());
EXPECT_EQ(kModelValue, drm_info.request_model_info().model_name());
}
TEST_F(SecurityProfileListTest, ProfileLevels) {
SecurityProfile::DrmInfo drm_info;
profile_list_.Init();
profile_list_->Init();
client_id_.mutable_client_capabilities()->set_max_hdcp_version(
ClientCapabilities::HDCP_NONE);
@@ -119,21 +128,24 @@ TEST_F(SecurityProfileListTest, ProfileLevels) {
device_info_.set_security_level(ProvisionedDeviceInfo::LEVEL_3);
// Lowest profile level requires OEMCrypto version 8.
ASSERT_EQ(SecurityProfile::SECURITY_PROFILE_LEVEL_UNDEFINED,
profile_list_.GetProfileLevel(client_id_, device_info_, &drm_info));
ASSERT_EQ(
SecurityProfile::SECURITY_PROFILE_LEVEL_UNDEFINED,
profile_list_->GetBestProfileLevel(client_id_, device_info_, &drm_info));
// Move up to profile 1
client_id_.mutable_client_capabilities()->set_oem_crypto_api_version(8);
ASSERT_EQ(SecurityProfile::SECURITY_PROFILE_LEVEL_1,
profile_list_.GetProfileLevel(client_id_, device_info_, &drm_info));
ASSERT_EQ(
SecurityProfile::SECURITY_PROFILE_LEVEL_1,
profile_list_->GetBestProfileLevel(client_id_, device_info_, &drm_info));
// Move up to profile 2
client_id_.mutable_client_capabilities()->set_analog_output_capabilities(
ClientCapabilities::ANALOG_OUTPUT_SUPPORTS_CGMS_A);
client_id_.mutable_client_capabilities()->set_oem_crypto_api_version(12);
device_info_.set_security_level(ProvisionedDeviceInfo::LEVEL_2);
ASSERT_EQ(SecurityProfile::SECURITY_PROFILE_LEVEL_2,
profile_list_.GetProfileLevel(client_id_, device_info_, &drm_info));
ASSERT_EQ(
SecurityProfile::SECURITY_PROFILE_LEVEL_2,
profile_list_->GetBestProfileLevel(client_id_, device_info_, &drm_info));
// Move up to profile 3
client_id_.mutable_client_capabilities()->set_max_hdcp_version(
@@ -141,16 +153,59 @@ TEST_F(SecurityProfileListTest, ProfileLevels) {
device_info_.set_security_level(ProvisionedDeviceInfo::LEVEL_1);
client_id_.mutable_client_capabilities()->set_resource_rating_tier(
kResourceTierMed);
ASSERT_EQ(SecurityProfile::SECURITY_PROFILE_LEVEL_3,
profile_list_.GetProfileLevel(client_id_, device_info_, &drm_info));
ASSERT_EQ(
SecurityProfile::SECURITY_PROFILE_LEVEL_3,
profile_list_->GetBestProfileLevel(client_id_, device_info_, &drm_info));
// Move up to profile 4
client_id_.mutable_client_capabilities()->set_max_hdcp_version(
ClientCapabilities::HDCP_V2_2);
client_id_.mutable_client_capabilities()->set_resource_rating_tier(
kResourceTierHigh);
ASSERT_EQ(SecurityProfile::SECURITY_PROFILE_LEVEL_4,
profile_list_.GetProfileLevel(client_id_, device_info_, &drm_info));
ASSERT_EQ(
SecurityProfile::SECURITY_PROFILE_LEVEL_4,
profile_list_->GetBestProfileLevel(client_id_, device_info_, &drm_info));
}
TEST_F(SecurityProfileListTest, FindProfile) {
// This test will not initialize the SecurityProfileList, hence it's empty.
// Insert test profile 1 into the list.
SecurityProfile profile1;
profile1.set_level(SecurityProfile::SECURITY_PROFILE_LEVEL_1);
profile1.set_name("profile1");
SecurityProfile profile2;
profile2.set_level(SecurityProfile::SECURITY_PROFILE_LEVEL_2);
profile2.set_name("profile2");
SecurityProfile profile3;
profile3.set_level(SecurityProfile::SECURITY_PROFILE_LEVEL_3);
profile3.set_name("profile3");
// Insert profiles 1 & 2, but not 3..
EXPECT_TRUE(profile_list_->InsertProfile(profile1));
EXPECT_TRUE(profile_list_->InsertProfile(profile2));
// Find the profile by its level.
SecurityProfile profile;
EXPECT_TRUE(profile_list_->GetProfileByLevel(profile1.level(), &profile));
EXPECT_EQ(profile1.name(), profile.name());
EXPECT_EQ(profile1.level(), profile.level());
EXPECT_TRUE(profile_list_->GetProfileByLevel(profile2.level(), &profile));
EXPECT_EQ(profile2.name(), profile.name());
EXPECT_EQ(profile2.level(), profile.level());
EXPECT_FALSE(profile_list_->GetProfileByName(profile3.name(), nullptr));
// Find the profile by its name.
EXPECT_TRUE(profile_list_->GetProfileByName(profile1.name(), &profile));
EXPECT_EQ(profile1.name(), profile.name());
EXPECT_EQ(profile1.level(), profile.level());
EXPECT_TRUE(profile_list_->GetProfileByName(profile2.name(), &profile));
EXPECT_EQ(profile2.name(), profile.name());
EXPECT_EQ(profile2.level(), profile.level());
EXPECT_FALSE(profile_list_->GetProfileByName(profile3.name(), nullptr));
}
} // namespace security_profile

View File

@@ -874,12 +874,19 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
DCHECK(stream_info->ecm);
// Generate serialized ECM.
CryptoMode crypto_mode = stream_info->crypto_mode == CryptoMode::kInvalid
? ecmg_config_->crypto_mode
: stream_info->crypto_mode;
std::vector<EntitledKeyInfo> keys;
keys.reserve(ecmg_config_->number_of_content_keys);
for (size_t i = 0; i < ecmg_config_->number_of_content_keys; i++) {
DCHECK(params.cp_cw_combinations[i].cp == params.cp_number + i);
keys.emplace_back();
keys[i].key_value = params.cp_cw_combinations[i].cw;
// Make content key to 16 bytes if crypto mode is Csa2.
if (crypto_mode == CryptoMode::kDvbCsa2 && keys[i].key_value.size() == 8) {
keys[i].key_value = keys[i].key_value + keys[i].key_value;
}
keys[i].key_id = crypto_util::DeriveKeyId(keys[i].key_value);
keys[i].content_iv = stream_info->content_ivs.empty()
? content_ivs_[i]

View File

@@ -39,7 +39,9 @@ static constexpr size_t kEcmId = 2;
static constexpr size_t kNominalCpDuration = 0x64;
static constexpr size_t kCpNumber = 0;
static constexpr char kContentKeyEven[] = "0123456701234567";
static constexpr char kContentKeyEven8Bytes[] = "01234567";
static constexpr char kContentKeyOdd[] = "abcdefghabcdefgh";
static constexpr char kContentKeyOdd8Bytes[] = "abcdefgh";
static constexpr char kEntitlementKeyIdEven[] = "0123456701234567";
static constexpr char kEntitlementKeyValueEven[] =
"01234567012345670123456701234567";
@@ -48,6 +50,7 @@ static constexpr char kEntitlementKeyValueOdd[] =
"abcdefghabcdefghabcdefghabcdefgh";
static constexpr size_t kAgeRestriction = 3;
static constexpr char kCryptoMode[] = "AesScte";
static constexpr char kCryptoModeCsa2[] = "DvbCsa2";
static constexpr char kTrackTypesSD[] = "SD";
static constexpr char kTrackTypesHD[] = "HD";
@@ -78,14 +81,16 @@ class EcmgClientHandlerTest : public ::testing::Test {
}
void SetupValidChannel() {
handler_->HandleRequest(kTestEcmgChannelSetup, response_, &response_len_);
handler_->HandleRequest(kTestEcmgChannelSetupWithPrivateParameters,
response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
}
void SetupValidChannelStream() {
SetupValidChannel();
handler_->HandleRequest(kTestEcmgStreamSetup, response_, &response_len_);
handler_->HandleRequest(kTestEcmgStreamSetupWithPrivateParameters,
response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
}
@@ -320,7 +325,33 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgEcmResponse), response_len_);
}
//
TEST_F(EcmgClientHandlerTest, SuccessSequenceCsa2) {
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
kCryptoModeCsa2, request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
BuildStreamSetupRequest(
kChannelId, kStreamId, kEcmId, kNominalCpDuration, kTrackTypesSD,
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
{kContentKeyEven, kContentKeyEven}, request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
{kCpNumber, kContentKeyEven8Bytes},
{kCpNumber + 1, kContentKeyOdd8Bytes}};
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgEcmResponse), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 27));
}
TEST_F(EcmgClientHandlerTest, SuccessChannelError) {
SetupValidChannel();
handler_->HandleRequest(kTestEcmgChannelError, response_, &response_len_);
@@ -408,6 +439,18 @@ TEST_F(EcmgClientHandlerTest, WrongParameterInsufficientKey) {
response_len_);
}
TEST_F(EcmgClientHandlerTest, WrongContentKeySize) {
SetupValidChannelStream();
// Content keys should be 16 byes.
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
{kCpNumber, kContentKeyEven8Bytes},
{kCpNumber + 1, kContentKeyOdd8Bytes}};
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
CheckStreamError(INVALID_VALUE_FOR_DVB_PARAMETER, response_, response_len_);
}
TEST_F(EcmgClientHandlerTest, WrongParameterSuperCasId) {
// Setup a channel with an unexpected super cas id (expecting kSuperCasId).
BuildChannelSetupRequest(kChannelId, 0, kAgeRestriction, kCryptoMode,

View File

@@ -60,8 +60,20 @@ Status WvCasEcm::GenerateEcm(const WvCasContentKeyInfo& even_key,
LOG(ERROR) << "Failed to get initialize ECM class." << status;
return status;
}
EntitledKeyInfo entitled_even_key = ConvertToEntitledKeyInfo(even_key);
EntitledKeyInfo entitled_odd_key = ConvertToEntitledKeyInfo(odd_key);
// Make content key to 16 bytes if crypto mode is Csa2.
if (ecm_param_.crypto_mode == CryptoMode::kDvbCsa2) {
if (entitled_even_key.key_value.size() == 8) {
entitled_even_key.key_value =
entitled_even_key.key_value + entitled_even_key.key_value;
}
if (entitled_odd_key.key_value.size() == 8) {
entitled_odd_key.key_value =
entitled_odd_key.key_value + entitled_odd_key.key_value;
}
}
return cas_ecm->GenerateEcm(&entitled_even_key, &entitled_odd_key, track_type,
serialized_ecm);
}
@@ -79,6 +91,12 @@ Status WvCasEcm::GenerateSingleKeyEcm(const WvCasContentKeyInfo& key,
}
EntitledKeyInfo entitled_key = ConvertToEntitledKeyInfo(key);
// Make content key to 16 bytes if crypto mode is Csa2.
if (ecm_param_.crypto_mode == CryptoMode::kDvbCsa2) {
if (entitled_key.key_value.size() == 8) {
entitled_key.key_value = entitled_key.key_value + entitled_key.key_value;
}
}
return cas_ecm->GenerateSingleKeyEcm(&entitled_key, track_type,
serialized_ecm);
}

View File

@@ -22,11 +22,12 @@ namespace cas {
// Information needed to generate content key portion of ECM.
// Fields:
// |key_id| key ID for the content key, must be 16 bytes.
// |key| content key value (aka control word), must be 16 bytes. It will be
// wrapped (encrypted) by corresponding entitlement key (decided by track
// type), together with |wrapped_key_iv| as the initial vector.
// |content_iv| content initial vector, must be 8 or 16 bytes. It is used for
// decrypting the content stream.
// |key| content key value (aka control word), must be 8 (DvbCsa2) or 16 bytes
// (all other crypto modes). It will be wrapped (encrypted) by
// corresponding entitlement key (decided by track type), together with
// |wrapped_key_iv| as the initial vector.
// |content_iv| content initial vector, must be 8 (DvbCsa2) or 16 bytes (all
// other crypto modes). It is used for decrypting the content stream.
// |wrapped_key_iv| must be 16 bytes. It is used to encrypt |key|.
struct WvCasContentKeyInfo {
std::string key_id;

View File

@@ -26,6 +26,7 @@ namespace cas {
namespace {
const char kEvenKey[] = "even_key........"; // 16 bytes
const char kEvenKey8Bytes[] = "even_key"; // 8 bytes
const char kEvenContentIv8Bytes[] = "even_iv."; // 8 bytes
const char kEvenContentIv16Bytes[] = "even_iv.even_iv."; // 16 bytes
const char kEvenEntitlementKeyId[] = "even_ent_key_id."; // 16 bytes
@@ -34,6 +35,7 @@ const char kEvenEntitlementKey[] =
const char kEvenEntitlementWrappedKeyIv[] = "1234567812345678"; // 16 bytes
const char kOddKey[] = "odd_key........."; // 16 bytes
const char kOddKey8Bytes[] = "odd_key."; // 8 bytes
const char kOddContentIv8Bytes[] = "odd_iv.."; // 8 bytes
const char kOddContentIv16Bytes[] = "odd_iv..odd_iv.."; // 16 bytes
const char kOddEntitlementKeyId[] = "odd_ent_key_id.."; // 16 bytes
@@ -48,22 +50,24 @@ static constexpr size_t kSingleKeyEcmWith16BytesContentIvSizeBytes =
kSingleKeyEcmWith8BytesContentIvSizeBytes + 8;
} // namespace
class WvCasEcmTest
: public ::testing::Test,
public ::testing::WithParamInterface<::testing::tuple<
int /*content_iv_size*/, bool /*key_rotation_enabled*/>> {
class WvCasEcmTest : public ::testing::Test,
public ::testing::WithParamInterface<::testing::tuple<
int /*content_iv_size*/, bool /*key_rotation_enabled*/,
std::string /*crypto_mode*/>> {
protected:
WvCasEcmTest() {
content_iv_size_ = std::get<0>(GetParam());
key_rotation_ = std::get<1>(GetParam());
crypto_mode_ = std::get<2>(GetParam());
}
WvCasEcmParameters CreateEcmInitParameters(bool key_rotation,
int content_iv_size) {
int content_iv_size,
std::string crypto_mode) {
WvCasEcmParameters params;
params.content_iv_size = content_iv_size == 8 ? kIvSize8 : kIvSize16;
params.key_rotation_enabled = key_rotation;
params.crypto_mode = CryptoMode::kAesScte;
EXPECT_TRUE(StringToCryptoMode(crypto_mode, &params.crypto_mode));
params.age_restriction = 0;
return params;
}
@@ -95,7 +99,7 @@ class WvCasEcmTest
content_keys.reserve(key_rotation ? 2 : 1);
content_keys.emplace_back();
WvCasContentKeyInfo* content_key = &content_keys.back();
content_key->key = kEvenKey;
content_key->key = crypto_mode_ == "DvbCsa2" ? kEvenKey8Bytes : kEvenKey;
content_key->key_id = crypto_util::DeriveKeyId(content_key->key);
content_key->content_iv =
content_iv_size == 8 ? kEvenContentIv8Bytes : kEvenContentIv16Bytes;
@@ -103,7 +107,7 @@ class WvCasEcmTest
if (key_rotation) {
content_keys.emplace_back();
WvCasContentKeyInfo* content_key = &content_keys.back();
content_key->key = kOddKey;
content_key->key = crypto_mode_ == "DvbCsa2" ? kOddKey8Bytes : kOddKey;
content_key->key_id = crypto_util::DeriveKeyId(content_key->key);
content_key->content_iv =
content_iv_size == 8 ? kOddContentIv8Bytes : kOddContentIv16Bytes;
@@ -115,11 +119,12 @@ class WvCasEcmTest
int content_iv_size_;
bool key_rotation_;
std::string crypto_mode_;
};
TEST_P(WvCasEcmTest, InitializeSuccess) {
WvCasEcmParameters params =
CreateEcmInitParameters(key_rotation_, content_iv_size_);
CreateEcmInitParameters(key_rotation_, content_iv_size_, crypto_mode_);
std::vector<EntitlementKeyInfo> entitlements =
CreateInjectedEntitlements(key_rotation_);
WvCasEcm wv_cas_ecm(params, entitlements);
@@ -127,7 +132,7 @@ TEST_P(WvCasEcmTest, InitializeSuccess) {
TEST_P(WvCasEcmTest, GenerateSingleKeyEcmKeyRotationEnabledError) {
WvCasEcmParameters params = CreateEcmInitParameters(
/*key_rotation*/ true, content_iv_size_);
/*key_rotation*/ true, content_iv_size_, crypto_mode_);
std::vector<EntitlementKeyInfo> entitlements =
CreateInjectedEntitlements(/*key_rotation*/ true);
WvCasEcm wv_cas_ecm(params, entitlements);
@@ -144,7 +149,7 @@ TEST_P(WvCasEcmTest, GenerateSingleKeyEcmKeyRotationEnabledError) {
TEST_P(WvCasEcmTest, GenerateEcmKeyRotationDisabledError) {
WvCasEcmParameters params = CreateEcmInitParameters(
/*key_rotation*/ false, content_iv_size_);
/*key_rotation*/ false, content_iv_size_, crypto_mode_);
std::vector<EntitlementKeyInfo> entitlements =
CreateInjectedEntitlements(/*key_rotation*/ false);
WvCasEcm wv_cas_ecm(params, entitlements);
@@ -162,7 +167,7 @@ TEST_P(WvCasEcmTest, GenerateEcmKeyRotationDisabledError) {
TEST_P(WvCasEcmTest, GenerateEcmSuccess) {
WvCasEcmParameters params =
CreateEcmInitParameters(key_rotation_, content_iv_size_);
CreateEcmInitParameters(key_rotation_, content_iv_size_, crypto_mode_);
std::vector<EntitlementKeyInfo> entitlements =
CreateInjectedEntitlements(key_rotation_);
WvCasEcm wv_cas_ecm(params, entitlements);
@@ -193,9 +198,10 @@ TEST_P(WvCasEcmTest, GenerateEcmSuccess) {
}
}
INSTANTIATE_TEST_SUITE_P(WvCasEcmTests, WvCasEcmTest,
::testing::Combine(::testing::Values(8, 16),
::testing::Bool()));
INSTANTIATE_TEST_SUITE_P(
WvCasEcmTests, WvCasEcmTest,
::testing::Combine(::testing::Values(8, 16), ::testing::Bool(),
::testing::Values("AesScte", "DvbCsa2")));
} // namespace cas
} // namespace widevine