From ed5a1d5db1c573b75dfbfb0b9845c216f932443a Mon Sep 17 00:00:00 2001 From: Lu Chen Date: Tue, 25 Feb 2020 13:16:44 -0800 Subject: [PATCH] Minor change --- common/BUILD | 4 + common/certificate_client_cert.cc | 9 ++ common/certificate_client_cert.h | 8 ++ common/client_cert.h | 1 + common/client_id_util.cc | 3 + common/client_id_util.h | 3 + common/core_message_util.cc | 16 ++- common/device_info_util.cc | 9 +- common/device_status_list_test.cc | 1 + common/keybox_client_cert.h | 3 + common/security_profile_list.cc | 125 +++++++++++++----- common/security_profile_list.h | 65 ++++++--- common/security_profile_list_test.cc | 93 ++++++++++--- .../internal/ecmg_client_handler.cc | 7 + .../internal/ecmg_client_handler_test.cc | 49 ++++++- media_cas_packager_sdk/public/wv_cas_ecm.cc | 18 +++ media_cas_packager_sdk/public/wv_cas_ecm.h | 11 +- .../public/wv_cas_ecm_test.cc | 36 ++--- 18 files changed, 358 insertions(+), 103 deletions(-) diff --git a/common/BUILD b/common/BUILD index 99a2071..7cd14f7 100644 --- a/common/BUILD +++ b/common/BUILD @@ -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", ], ) diff --git a/common/certificate_client_cert.cc b/common/certificate_client_cert.cc index 20da973..0c9326f 100644 --- a/common/certificate_client_cert.cc +++ b/common/certificate_client_cert.cc @@ -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 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 client_ecc_public_key_; std::string ephemeral_public_key_; diff --git a/common/certificate_client_cert.h b/common/certificate_client_cert.h index 5c4df86..2013035 100644 --- a/common/certificate_client_cert.h +++ b/common/certificate_client_cert.h @@ -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(); } diff --git a/common/client_cert.h b/common/client_cert.h index 50aace3..675c53e 100644 --- a/common/client_cert.h +++ b/common/client_cert.h @@ -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; diff --git a/common/client_id_util.cc b/common/client_id_util.cc index f9ae933..a997f10 100644 --- a/common/client_id_util.cc +++ b/common/client_id_util.cc @@ -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(); diff --git a/common/client_id_util.h b/common/client_id_util.h index df83733..6115935 100644 --- a/common/client_id_util.h +++ b/common/client_id_util.h @@ -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, diff --git a/common/core_message_util.cc b/common/core_message_util.cc index 8130204..ecc846e 100644 --- a/common/core_message_util.cc +++ b/common/core_message_util.cc @@ -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( diff --git a/common/device_info_util.cc b/common/device_info_util.cc index 8190d82..37cd0a9 100644 --- a/common/device_info_util.cc +++ b/common/device_info_util.cc @@ -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; } } diff --git a/common/device_status_list_test.cc b/common/device_status_list_test.cc index 1746125..3b4dcaf 100644 --- a/common/device_status_list_test.cc +++ b/common/device_status_list_test.cc @@ -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 &()); diff --git a/common/keybox_client_cert.h b/common/keybox_client_cert.h index fcdd50c..a068b8f 100644 --- a/common/keybox_client_cert.h +++ b/common/keybox_client_cert.h @@ -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_; } diff --git a/common/security_profile_list.cc b/common/security_profile_list.cc index 6ebbaae..f9008c4 100644 --- a/common/security_profile_list.cc +++ b/common/security_profile_list.cc @@ -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& profiles_to_check, + const ClientIdentification& client_id, + const ProvisionedDeviceInfo& device_info, + std::vector* 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* 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 diff --git a/common/security_profile_list.h b/common/security_profile_list.h index 352994f..459c014 100644 --- a/common/security_profile_list.h +++ b/common/security_profile_list.h @@ -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* 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& profiles_to_check, + const ClientIdentification& client_id, + const ProvisionedDeviceInfo& device_info, + std::vector* 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 security_profiles_ ABSL_GUARDED_BY(mutex_); - // Custom security profiles - std::map custom_security_profiles_ - ABSL_GUARDED_BY(mutex_); }; } // namespace widevine diff --git a/common/security_profile_list_test.cc b/common/security_profile_list_test.cc index b469dc1..76bd98c 100644 --- a/common/security_profile_list_test.cc +++ b/common/security_profile_list_test.cc @@ -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(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 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 diff --git a/media_cas_packager_sdk/internal/ecmg_client_handler.cc b/media_cas_packager_sdk/internal/ecmg_client_handler.cc index aab275b..e2823d8 100644 --- a/media_cas_packager_sdk/internal/ecmg_client_handler.cc +++ b/media_cas_packager_sdk/internal/ecmg_client_handler.cc @@ -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 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] diff --git a/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc b/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc index 338b3c1..86637b6 100644 --- a/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc +++ b/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc @@ -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 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 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, diff --git a/media_cas_packager_sdk/public/wv_cas_ecm.cc b/media_cas_packager_sdk/public/wv_cas_ecm.cc index 6a8f2cc..09676d2 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecm.cc @@ -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); } diff --git a/media_cas_packager_sdk/public/wv_cas_ecm.h b/media_cas_packager_sdk/public/wv_cas_ecm.h index fe59137..5aa2cb7 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm.h +++ b/media_cas_packager_sdk/public/wv_cas_ecm.h @@ -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; diff --git a/media_cas_packager_sdk/public/wv_cas_ecm_test.cc b/media_cas_packager_sdk/public/wv_cas_ecm_test.cc index 04d6afb..400e6b9 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecm_test.cc @@ -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, ¶ms.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 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 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 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 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