From fc4186e4fda5b651cfba7121c631ffa97bfaa327 Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Sun, 1 Jul 2018 17:56:23 -0700 Subject: [PATCH] Add initial support for key rotation through ce cdm interface. Merge from Widevine repo of http://go/wvgerrit/42941 Bug: 72168544 Test: tested as part of http://go/ag/4674759 Change-Id: I1a2d0f49371e5b3edf1d9dff85b85593f981d1f5 --- libwvdrmengine/cdm/core/include/license.h | 26 +- .../cdm/core/include/license_key_status.h | 23 +- .../cdm/core/include/policy_engine.h | 19 +- .../cdm/core/include/wv_cdm_types.h | 35 +-- libwvdrmengine/cdm/core/src/cdm_engine.cpp | 4 +- libwvdrmengine/cdm/core/src/cdm_session.cpp | 292 ++++++++---------- libwvdrmengine/cdm/core/src/license.cpp | 187 +++++------ .../cdm/core/src/license_key_status.cpp | 65 ++-- .../cdm/core/src/license_protocol.proto | 7 +- libwvdrmengine/cdm/core/src/policy_engine.cpp | 30 +- .../cdm/core/test/license_keys_unittest.cpp | 46 +++ .../cdm/core/test/policy_engine_unittest.cpp | 78 +++++ 12 files changed, 480 insertions(+), 332 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/license.h b/libwvdrmengine/cdm/core/include/license.h index 2c906449..fdb5ef68 100644 --- a/libwvdrmengine/cdm/core/include/license.h +++ b/libwvdrmengine/cdm/core/include/license.h @@ -27,6 +27,10 @@ class PolicyEngine; class CdmSession; class CryptoKey; +using ::google::protobuf::RepeatedPtrField; +using video_widevine::License_KeyContainer; +using video_widevine::WidevinePsshData_EntitledKey; + class CdmLicense { public: CdmLicense(const CdmSessionId& session_id); @@ -50,7 +54,8 @@ class CdmLicense { const CdmKeyResponse& license_response); virtual CdmResponseType HandleKeyUpdateResponse( bool is_renewal, const CdmKeyResponse& license_response); - virtual CdmResponseType HandleSubLicense(const InitializationData& init_data); + virtual CdmResponseType HandleEmbeddedKeyData( + const InitializationData& init_data); virtual bool RestoreOfflineLicense( const CdmKeyMessage& license_request, @@ -101,6 +106,10 @@ class CdmLicense { const std::vector& key_array, const video_widevine::License& license); + CdmResponseType HandleNewEntitledKeys( + const std::vector& wrapped_keys); + CdmResponseType HandleSubLicense(const InitializationData& init_data); + template bool SetTypeAndId(CdmLicenseType license_type, const std::string& request_id, T* content_id); @@ -132,16 +141,19 @@ class CdmLicense { // CdmLicense takes ownership of the clock. CdmLicense(const CdmSessionId& session_id, Clock* clock); - // For sublicense key embedding. This key array will be initilized with any - // sub session keys we may have received in a license response. These keys - // may be used to support key rotation. - std::vector sub_session_key_array_; - // For entitlement key licensing. This holds the keys from the init_data. // These keys are extracted from the pssh when we generate a license request. // It is used to load content keys after we have received a license and // entitelement keys. It is also used in updating the key status info. - std::vector wrapped_keys_; + std::vector wrapped_keys_; + + // For sublicense key embedding. This key array will be initialized with any + // sub session keys we may have received in a license response. These keys + // may be used to support key rotation. + std::vector entitlement_key_array_; + + CdmLicenseKeyType license_key_type_; + RepeatedPtrField entitlement_keys_; #if defined(UNIT_TEST) friend class CdmLicenseTest; #endif diff --git a/libwvdrmengine/cdm/core/include/license_key_status.h b/libwvdrmengine/cdm/core/include/license_key_status.h index b52a641a..865fa294 100644 --- a/libwvdrmengine/cdm/core/include/license_key_status.h +++ b/libwvdrmengine/cdm/core/include/license_key_status.h @@ -16,6 +16,8 @@ namespace wvcdm { class LicenseKeyStatus; +using video_widevine::WidevinePsshData_EntitledKey; + // Holds all content and operator session keys for a session. class LicenseKeys { public: @@ -59,8 +61,11 @@ class LicenseKeys { // Extracts the keys from a license and makes them available for // querying usage and constraint settings. - virtual void SetFromLicense( - const video_widevine::License& license); + virtual void SetFromLicense(const video_widevine::License& license); + + // Sets the keys from the input entitled key data. + virtual void SetEntitledKeys( + const std::vector& keys); private: typedef ::video_widevine::License::KeyContainer KeyContainer; @@ -70,7 +75,12 @@ class LicenseKeys { void Clear(); bool is_initialized_; + // |keys_| can hold either content key statuses, or entitlement key statuses. std::map keys_; + // |content_keyid_to_entitlement_key_id_| maps a content key id to an + // entitlement_key_id. The resulting key id can be used to obtain the current + // key status from |keys_| when using entitlement key licensing. + std::map content_keyid_to_entitlement_key_id_; CORE_DISALLOW_COPY_AND_ASSIGN(LicenseKeys); }; @@ -104,8 +114,8 @@ class LicenseKeyStatus { virtual bool MeetsConstraints() const { return meets_constraints_; } // Applies the given changes in resolution or HDCP settings. - virtual void ApplyConstraints( - uint32_t new_resolution, CryptoSession::HdcpCapability new_hdcp_level); + virtual void ApplyConstraints(uint32_t new_resolution, + CryptoSession::HdcpCapability new_hdcp_level); protected: typedef ::video_widevine::License::KeyContainer KeyContainer; @@ -121,13 +131,10 @@ class LicenseKeyStatus { virtual ~LicenseKeyStatus() {} private: - void ParseContentKey(const KeyContainer& key); void ParseOperatorSessionKey(const KeyContainer& key); - bool HasConstraints() { - return is_content_key_ && constraints_.size() != 0; - } + bool HasConstraints() { return is_content_key_ && constraints_.size() != 0; } void SetConstraints(const ConstraintList& constraints); diff --git a/libwvdrmengine/cdm/core/include/policy_engine.h b/libwvdrmengine/cdm/core/include/policy_engine.h index 9bd0f95e..f3ed42bc 100644 --- a/libwvdrmengine/cdm/core/include/policy_engine.h +++ b/libwvdrmengine/cdm/core/include/policy_engine.h @@ -17,6 +17,7 @@ namespace wvcdm { using video_widevine::LicenseIdentification; +using video_widevine::WidevinePsshData_EntitledKey; class Clock; class CryptoSession; @@ -57,12 +58,18 @@ class PolicyEngine { // permits playback. virtual void SetLicense(const video_widevine::License& license); + // TODO(jfore): Sublicense uses this to update the keys when they are + // changed during key rotation. Drop this method and use SetLicenseKeys + // instead. virtual void UpdateLicenseKeys(const video_widevine::License& license); + // Used to update the currently loaded entitled content keys. + virtual void SetEntitledLicenseKeys( + const std::vector& entitled_keys); + // SetLicenseForRelease is used when releasing a license. The keys in this // license will be ignored, and any old keys will be expired. - virtual void SetLicenseForRelease( - const video_widevine::License& license); + virtual void SetLicenseForRelease(const video_widevine::License& license); // Call this on first decrypt to set the start of playback. virtual void BeginDecryption(void); @@ -73,8 +80,7 @@ class PolicyEngine { // case an exact copy is not what we want to happen. We also will receive an // updated license_start_time from the server. The license will transition to // kLicenseStateCanPlay if the license permits playback. - virtual void UpdateLicense( - const video_widevine::License& license); + virtual void UpdateLicense(const video_widevine::License& license); // Used for notifying the Policy Engine of resolution changes virtual void NotifyResolution(uint32_t width, uint32_t height); @@ -101,8 +107,7 @@ class PolicyEngine { bool IsLicenseForFuture() { return license_state_ == kLicenseStatePending; } bool HasPlaybackStarted(int64_t current_time) { - if (playback_start_time_ == 0) - return false; + if (playback_start_time_ == 0) return false; const int64_t playback_time = current_time - playback_start_time_; return playback_time >= policy_.play_start_grace_period_seconds(); @@ -218,6 +223,6 @@ class PolicyEngine { CORE_DISALLOW_COPY_AND_ASSIGN(PolicyEngine); }; -} // wvcdm +} // namespace wvcdm #endif // WVCDM_CORE_POLICY_ENGINE_H_ diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index 389dc2b6..111ae3a0 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -355,18 +355,19 @@ enum CdmLicenseType { // Like Streaming, but stricter. Does not permit storage of any kind. // Named after the 'temporary' session type in EME, which has this behavior. kLicenseTypeTemporary, - kLicenseTypeSubSession + // TODO(jfore): The kLicenseTypeEmbeddedKeyData currently is to differentiate + // between call types made to GenerateKeyRequest. This type is used to + // differentiate between calls to generate a license renewal and a new pssh + // with embedded keys. Please refer to CdmSession::GenerateKeyRequest. Based + // on code review comments from go/wvgerrit/41860 this license type should not + // be added. This type can be removed once it is no longer needed by + // GenerateKeyRequest. + kLicenseTypeEmbeddedKeyData }; -enum CdmLicenseKeyType { - kLicenseKeyTypeContent, - kLicenseKeyTypeEntitlement -}; +enum CdmLicenseKeyType { kLicenseKeyTypeContent, kLicenseKeyTypeEntitlement }; -enum SecurityLevel { - kLevelDefault, - kLevel3 -}; +enum SecurityLevel { kLevelDefault, kLevel3 }; enum CdmSecurityLevel { kSecurityLevelUninitialized, @@ -435,10 +436,10 @@ struct CdmUsageEntryInfo { CdmKeySetId key_set_id; std::string usage_info_file_name; bool operator==(const CdmUsageEntryInfo& other) const { - return storage_type == other.storage_type && - key_set_id == other.key_set_id && - (storage_type != kStorageUsageInfo || - usage_info_file_name == other.usage_info_file_name); + return storage_type == other.storage_type && + key_set_id == other.key_set_id && + (storage_type != kStorageUsageInfo || + usage_info_file_name == other.usage_info_file_name); } }; @@ -454,9 +455,7 @@ enum CdmKeySecurityLevel { class CdmKeyAllowedUsage { public: - CdmKeyAllowedUsage() { - Clear(); - } + CdmKeyAllowedUsage() { Clear(); } bool Valid() const { return valid_; } void SetValid() { valid_ = true; } @@ -506,9 +505,7 @@ class CdmKeyAllowedUsage { struct CdmCencPatternEncryptionDescriptor { size_t encrypt_blocks; // number of 16 byte blocks to decrypt size_t skip_blocks; // number of 16 byte blocks to leave in clear - CdmCencPatternEncryptionDescriptor() - : encrypt_blocks(0), - skip_blocks(0) {} + CdmCencPatternEncryptionDescriptor() : encrypt_blocks(0), skip_blocks(0) {} }; struct CdmDecryptionParameters { diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index 9435e3c2..257550f6 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -289,7 +289,9 @@ CdmResponseType CdmEngine::GenerateKeyRequest( sts = session->GenerateKeyRequest(init_data, license_type, app_parameters, key_request); - if (KEY_MESSAGE != sts) { + if (KEY_ADDED == sts) { + return sts; + } else if (KEY_MESSAGE != sts) { if (sts == NEED_PROVISIONING) { cert_provisioning_requested_security_level_ = session->GetRequestedSecurityLevel(); diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index cbd8add4..ee47a39b 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -16,9 +16,9 @@ #include "log.h" #include "properties.h" #include "string_conversions.h" +#include "usage_table_header.h" #include "wv_cdm_constants.h" #include "wv_cdm_event_listener.h" -#include "usage_table_header.h" namespace { const size_t kKeySetIdLength = 14; @@ -27,26 +27,26 @@ const size_t kKeySetIdLength = 14; namespace wvcdm { CdmSession::CdmSession(FileSystem* file_system, - metrics::SessionMetrics* metrics) : - metrics_(metrics), - initialized_(false), - closed_(true), - file_handle_(new DeviceFiles(file_system)), - license_received_(false), - is_offline_(false), - is_release_(false), - is_temporary_(false), - security_level_(kSecurityLevelUninitialized), - requested_security_level_(kLevelDefault), - is_initial_decryption_(true), - has_decrypted_since_last_report_(false), - is_initial_usage_update_(true), - is_usage_update_needed_(false), - usage_support_type_(kNonSecureUsageSupport), - usage_table_header_(NULL), - usage_entry_number_(0), - mock_license_parser_in_use_(false), - mock_policy_engine_in_use_(false) { + metrics::SessionMetrics* metrics) + : metrics_(metrics), + initialized_(false), + closed_(true), + file_handle_(new DeviceFiles(file_system)), + license_received_(false), + is_offline_(false), + is_release_(false), + is_temporary_(false), + security_level_(kSecurityLevelUninitialized), + requested_security_level_(kLevelDefault), + is_initial_decryption_(true), + has_decrypted_since_last_report_(false), + is_initial_usage_update_(true), + is_usage_update_needed_(false), + usage_support_type_(kNonSecureUsageSupport), + usage_table_header_(NULL), + usage_entry_number_(0), + mock_license_parser_in_use_(false), + mock_policy_engine_in_use_(false) { assert(metrics_); // metrics_ must not be null. crypto_metrics_ = metrics_->GetCryptoMetrics(); crypto_session_.reset(new CryptoSession(crypto_metrics_)); @@ -77,27 +77,22 @@ CdmResponseType CdmSession::Init( return Init(cdm_client_property_set, NULL, NULL); } -CdmResponseType CdmSession::Init( - CdmClientPropertySet* cdm_client_property_set, - const CdmSessionId* forced_session_id, WvCdmEventListener* event_listener) { +CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set, + const CdmSessionId* forced_session_id, + WvCdmEventListener* event_listener) { if (initialized_) { LOGE("CdmSession::Init: Failed due to previous initialization"); return REINIT_ERROR; } - if (cdm_client_property_set && - cdm_client_property_set->security_level() == - QUERY_VALUE_SECURITY_LEVEL_L3) { + if (cdm_client_property_set && cdm_client_property_set->security_level() == + QUERY_VALUE_SECURITY_LEVEL_L3) { requested_security_level_ = kLevel3; security_level_ = kSecurityLevelL3; } CdmResponseType sts; - M_TIME( - sts = crypto_session_->Open(requested_security_level_), - crypto_metrics_, - crypto_session_open_, - sts, - requested_security_level_); + M_TIME(sts = crypto_session_->Open(requested_security_level_), + crypto_metrics_, crypto_session_open_, sts, requested_security_level_); if (NO_ERROR != sts) return sts; security_level_ = crypto_session_->GetSecurityLevel(); @@ -137,12 +132,10 @@ CdmResponseType CdmSession::Init( } bool load_cert_sts; M_TIME( - load_cert_sts = crypto_session_->LoadCertificatePrivateKey( - wrapped_key), - crypto_metrics_, - crypto_session_load_certificate_private_key_, + load_cert_sts = crypto_session_->LoadCertificatePrivateKey(wrapped_key), + crypto_metrics_, crypto_session_load_certificate_private_key_, load_cert_sts); - if(!load_cert_sts) { + if (!load_cert_sts) { return NEED_PROVISIONING; } client_token_type = kClientTokenDrmCert; @@ -172,17 +165,17 @@ CdmResponseType CdmSession::Init( if (!mock_license_parser_in_use_) license_parser_.reset(new CdmLicense(session_id_)); if (!mock_policy_engine_in_use_) - policy_engine_.reset(new PolicyEngine( - session_id_, event_listener, crypto_session_.get())); + policy_engine_.reset( + new PolicyEngine(session_id_, event_listener, crypto_session_.get())); std::string service_certificate; if (!Properties::GetServiceCertificate(session_id_, &service_certificate)) service_certificate.clear(); - if (!license_parser_->Init( - client_token, client_token_type, serial_number, - Properties::UsePrivacyMode(session_id_), service_certificate, - crypto_session_.get(), policy_engine_.get())) + if (!license_parser_->Init(client_token, client_token_type, serial_number, + Properties::UsePrivacyMode(session_id_), + service_certificate, crypto_session_.get(), + policy_engine_.get())) return LICENSE_PARSER_INIT_ERROR; license_received_ = false; @@ -214,32 +207,37 @@ CdmResponseType CdmSession::RestoreOfflineSession( &offline_key_renewal_response_, &offline_release_server_url_, &playback_start_time, &last_playback_time, &grace_period_end_time, &app_parameters_, &usage_entry_, &usage_entry_number_)) { - LOGE("CdmSession::RestoreOfflineSession: failed to retrieve license. " - "key set id = %s", key_set_id.c_str()); + LOGE( + "CdmSession::RestoreOfflineSession: failed to retrieve license. " + "key set id = %s", + key_set_id.c_str()); return GET_LICENSE_ERROR; } // Do not restore a released offline license, unless a release retry if (!(license_type == kLicenseTypeRelease || license_state == DeviceFiles::kLicenseStateActive)) { - LOGE("CdmSession::RestoreOfflineSession: invalid offline license state = " - "%d, type = %d", license_state, license_type); + LOGE( + "CdmSession::RestoreOfflineSession: invalid offline license state = " + "%d, type = %d", + license_state, license_type); return GET_RELEASED_LICENSE_ERROR; } std::string provider_session_token; if (usage_support_type_ == kUsageEntrySupport) { if (!license_parser_->ExtractProviderSessionToken( - key_response_, &provider_session_token) || + key_response_, &provider_session_token) || usage_table_header_ == NULL) { provider_session_token.clear(); } else { - CdmResponseType sts = - usage_table_header_->LoadEntry(crypto_session_.get(), usage_entry_, - usage_entry_number_); + CdmResponseType sts = usage_table_header_->LoadEntry( + crypto_session_.get(), usage_entry_, usage_entry_number_); if (sts != NO_ERROR) { - LOGE("CdmSession::RestoreOfflineSession: failed to load usage entry = " - "%d", sts); + LOGE( + "CdmSession::RestoreOfflineSession: failed to load usage entry = " + "%d", + sts); return sts; } } @@ -264,12 +262,15 @@ CdmResponseType CdmSession::RestoreOfflineSession( CdmResponseType sts = usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_); if (sts != NO_ERROR) { - LOGE("CdmSession::RestoreOfflineSession failed to update usage entry = " - "%d", sts); + LOGE( + "CdmSession::RestoreOfflineSession failed to update usage entry = " + "%d", + sts); return sts; } if (!StoreLicense(license_state)) { - LOGW("CdmSession::RestoreUsageSession: unable to save updated usage " + LOGW( + "CdmSession::RestoreUsageSession: unable to save updated usage " "info"); } } @@ -302,7 +303,7 @@ CdmResponseType CdmSession::RestoreUsageSession( crypto_session_.get(), usage_entry_, usage_entry_number_); if (sts != NO_ERROR) { LOGE("CdmSession::RestoreUsageSession: failed to load usage entry = %d", - sts); + sts); return sts; } } @@ -317,11 +318,12 @@ CdmResponseType CdmSession::RestoreUsageSession( usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_); if (sts != NO_ERROR) { LOGE("CdmSession::RestoreUsageSession: failed to update usage entry: %d", - sts); + sts); return sts; } if (!UpdateUsageInfo()) { - LOGW("CdmSession::RestoreUsageSession: unable to save updated usage " + LOGW( + "CdmSession::RestoreUsageSession: unable to save updated usage " "info"); } } @@ -376,8 +378,12 @@ CdmResponseType CdmSession::GenerateKeyRequestInternal( case kLicenseTypeRelease: is_release_ = true; break; - case kLicenseTypeSubSession: - return license_parser_->HandleSubLicense(init_data); + // TODO(jfore): CdmSession assumes a call to this method once a license has + // been received is a call to generate a license renewal message. Use of + // this enum differentiates the two calls. See "else if (license_received_)" + // below. + case kLicenseTypeEmbeddedKeyData: + return license_parser_->HandleEmbeddedKeyData(init_data); default: LOGE("CdmSession::GenerateKeyRequest: unrecognized license type: %ld", license_type); @@ -419,11 +425,9 @@ CdmResponseType CdmSession::GenerateKeyRequestInternal( app_parameters_ = app_parameters; CdmResponseType status = license_parser_->PrepareKeyRequest( - init_data, license_type, - app_parameters, &key_request->message, - &key_request->url); - if (status != KEY_MESSAGE) - return status; + init_data, license_type, app_parameters, &key_request->message, + &key_request->url); + if (status != KEY_MESSAGE) return status; key_request_ = key_request->message; if (is_offline_) { @@ -476,13 +480,10 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) { // Update or delete entry if usage table header+entries are supported if (usage_support_type_ == kUsageEntrySupport && - !provider_session_token.empty() && - usage_table_header_ != NULL) { + !provider_session_token.empty() && usage_table_header_ != NULL) { if (sts != KEY_ADDED) { - CdmResponseType delete_sts = - usage_table_header_->DeleteEntry(usage_entry_number_, - file_handle_.get(), - crypto_metrics_); + CdmResponseType delete_sts = usage_table_header_->DeleteEntry( + usage_entry_number_, file_handle_.get(), crypto_metrics_); if (delete_sts != NO_ERROR) { LOGW("CdmSession::AddKey: Delete usage entry failed = %d", delete_sts); @@ -578,17 +579,16 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) { // Playback may not begin until either the start time passes or the license // is updated, so we treat this Decrypt call as invalid. - if (params.is_encrypted) { - if (!policy_engine_->CanDecryptContent(*params.key_id)) { - if (policy_engine_->IsLicenseForFuture()) - return DECRYPT_NOT_READY; - if (!policy_engine_->IsSufficientOutputProtection(*params.key_id)) - return INSUFFICIENT_OUTPUT_PROTECTION; - return NEED_KEY; - } + if (params.is_encrypted && + !policy_engine_->CanDecryptContent(*params.key_id)) { + if (policy_engine_->IsLicenseForFuture()) return DECRYPT_NOT_READY; + if (!policy_engine_->IsSufficientOutputProtection(*params.key_id)) + return INSUFFICIENT_OUTPUT_PROTECTION; + return NEED_KEY; + } - if (!policy_engine_->CanUseKey(*params.key_id, security_level_)) - return KEY_PROHIBITED_FOR_SECURITY_LEVEL; + if (!policy_engine_->CanUseKey(*params.key_id, security_level_)) { + return KEY_PROHIBITED_FOR_SECURITY_LEVEL; } CdmResponseType status = crypto_session_->Decrypt(params); @@ -616,8 +616,7 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) { // License renewal // GenerateRenewalRequest() - Construct valid renewal request for the current // session keys. -CdmResponseType CdmSession::GenerateRenewalRequest( - CdmKeyRequest* key_request) { +CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyRequest* key_request) { if (!initialized_) { LOGE("CdmSession::GenerateRenewalRequest: not initialized"); return NOT_INITIALIZED_ERROR; @@ -659,8 +658,7 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) { return KEY_ADDED; } -CdmResponseType CdmSession::GenerateReleaseRequest( - CdmKeyRequest* key_request) { +CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyRequest* key_request) { if (!initialized_) { LOGE("CdmSession::GenerateReleaseRequest: not initialized"); return NOT_INITIALIZED_ERROR; @@ -677,11 +675,13 @@ CdmResponseType CdmSession::GenerateReleaseRequest( if (has_provider_session_token() && usage_support_type_ == kUsageEntrySupport) { - status = usage_table_header_->UpdateEntry(crypto_session_.get(), - &usage_entry_); + status = + usage_table_header_->UpdateEntry(crypto_session_.get(), &usage_entry_); if (status != NO_ERROR) { - LOGE("CdmSession::GenerateReleaseRequest: Update usage entry failed = " - "%d", status); + LOGE( + "CdmSession::GenerateReleaseRequest: Update usage entry failed = " + "%d", + status); return status; } } @@ -691,8 +691,7 @@ CdmResponseType CdmSession::GenerateReleaseRequest( return RELEASE_KEY_REQUEST_ERROR; } else if (!usage_provider_session_token_.empty()) { if (usage_support_type_ == kUsageEntrySupport) { - if (!UpdateUsageInfo()) - return RELEASE_USAGE_INFO_FAILED; + if (!UpdateUsageInfo()) return RELEASE_USAGE_INFO_FAILED; } } @@ -725,7 +724,7 @@ CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_number) { } if (usage_support_type_ != kUsageEntrySupport) { LOGE("CdmSession::DeleteUsageEntry: Unexpected usage type supported: %d", - usage_support_type_); + usage_support_type_); return INCORRECT_USAGE_SUPPORT_TYPE_1; } @@ -734,12 +733,8 @@ CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_number) { CdmResponseType sts; crypto_session_->Close(); crypto_session_.reset(new CryptoSession(crypto_metrics_)); - M_TIME( - sts = crypto_session_->Open(requested_security_level_), - crypto_metrics_, - crypto_session_open_, - sts, - requested_security_level_); + M_TIME(sts = crypto_session_->Open(requested_security_level_), + crypto_metrics_, crypto_session_open_, sts, requested_security_level_); if (sts != NO_ERROR) return sts; usage_table_header_ = NULL; @@ -756,8 +751,7 @@ CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_number) { } return usage_table_header_->DeleteEntry(usage_entry_number, - file_handle_.get(), - crypto_metrics_); + file_handle_.get(), crypto_metrics_); } bool CdmSession::IsKeyLoaded(const KeyId& key_id) { @@ -833,11 +827,10 @@ CdmResponseType CdmSession::StoreLicense() { std::string app_id; GetApplicationId(&app_id); - if (!file_handle_->StoreUsageInfo(provider_session_token, key_request_, - key_response_, - DeviceFiles::GetUsageInfoFileName(app_id), - key_set_id_, usage_entry_, - usage_entry_number_)) { + if (!file_handle_->StoreUsageInfo( + provider_session_token, key_request_, key_response_, + DeviceFiles::GetUsageInfoFileName(app_id), key_set_id_, usage_entry_, + usage_entry_number_)) { LOGE("CdmSession::StoreLicense: Unable to store usage info"); // Usage info file is corrupt. Delete current usage entry and file. switch (usage_support_type_) { @@ -969,11 +962,8 @@ CdmResponseType CdmSession::UpdateUsageTableInformation() { crypto_session_->GetUsageSupportType(&usage_support_type); if (sts == NO_ERROR && usage_support_type == kUsageTableSupport) { - M_TIME( - sts = crypto_session_->UpdateUsageInformation(), - crypto_metrics_, - crypto_session_update_usage_information_, - sts); + M_TIME(sts = crypto_session_->UpdateUsageInformation(), crypto_metrics_, + crypto_session_update_usage_information_, sts); return sts; } @@ -982,12 +972,12 @@ CdmResponseType CdmSession::UpdateUsageTableInformation() { CdmResponseType CdmSession::UpdateUsageEntryInformation() { if (usage_support_type_ != kUsageEntrySupport || - !has_provider_session_token() || - usage_table_header_ == NULL) { - LOGE("CdmSession::UpdateUsageEntryInformation: Unexpected state, " + !has_provider_session_token() || usage_table_header_ == NULL) { + LOGE( + "CdmSession::UpdateUsageEntryInformation: Unexpected state, " "usage support type: %d, PST present: %s, usage table header available" - ": %s", usage_support_type_, - has_provider_session_token() ? "yes" : "no", + ": %s", + usage_support_type_, has_provider_session_token() ? "yes" : "no", usage_table_header_ == NULL ? "no" : "yes"); return INCORRECT_USAGE_SUPPORT_TYPE_2; } @@ -1003,9 +993,8 @@ CdmResponseType CdmSession::UpdateUsageEntryInformation() { if (sts != NO_ERROR) return sts; if (is_offline_) - StoreLicense(is_release_ - ? DeviceFiles::kLicenseStateReleasing - : DeviceFiles::kLicenseStateActive); + StoreLicense(is_release_ ? DeviceFiles::kLicenseStateReleasing + : DeviceFiles::kLicenseStateActive); else if (!usage_provider_session_token_.empty()) UpdateUsageInfo(); @@ -1022,18 +1011,10 @@ CdmResponseType CdmSession::GenericEncrypt(const std::string& in_buffer, return PARAMETER_NULL; } CdmResponseType sts; - M_TIME( - sts = crypto_session_->GenericEncrypt( - in_buffer, - key_id, - iv, - algorithm, - out_buffer), - crypto_metrics_, - crypto_session_generic_encrypt_, - sts, - metrics::Pow2Bucket(in_buffer.size()), - algorithm); + M_TIME(sts = crypto_session_->GenericEncrypt(in_buffer, key_id, iv, algorithm, + out_buffer), + crypto_metrics_, crypto_session_generic_encrypt_, sts, + metrics::Pow2Bucket(in_buffer.size()), algorithm); return sts; } @@ -1047,18 +1028,10 @@ CdmResponseType CdmSession::GenericDecrypt(const std::string& in_buffer, return PARAMETER_NULL; } CdmResponseType sts; - M_TIME( - sts = crypto_session_->GenericDecrypt( - in_buffer, - key_id, - iv, - algorithm, - out_buffer), - crypto_metrics_, - crypto_session_generic_decrypt_, - sts, - metrics::Pow2Bucket(in_buffer.size()), - algorithm); + M_TIME(sts = crypto_session_->GenericDecrypt(in_buffer, key_id, iv, algorithm, + out_buffer), + crypto_metrics_, crypto_session_generic_decrypt_, sts, + metrics::Pow2Bucket(in_buffer.size()), algorithm); return sts; } @@ -1072,16 +1045,9 @@ CdmResponseType CdmSession::GenericSign(const std::string& message, } CdmResponseType sts; M_TIME( - sts = crypto_session_->GenericSign( - message, - key_id, - algorithm, - signature), - crypto_metrics_, - crypto_session_generic_sign_, - sts, - metrics::Pow2Bucket(message.size()), - algorithm); + sts = crypto_session_->GenericSign(message, key_id, algorithm, signature), + crypto_metrics_, crypto_session_generic_sign_, sts, + metrics::Pow2Bucket(message.size()), algorithm); return sts; } @@ -1090,17 +1056,10 @@ CdmResponseType CdmSession::GenericVerify(const std::string& message, CdmSigningAlgorithm algorithm, const std::string& signature) { CdmResponseType sts; - M_TIME( - sts = crypto_session_->GenericVerify( - message, - key_id, - algorithm, - signature), - crypto_metrics_, - crypto_session_generic_verify_, - sts, - metrics::Pow2Bucket(message.size()), - algorithm); + M_TIME(sts = crypto_session_->GenericVerify(message, key_id, algorithm, + signature), + crypto_metrics_, crypto_session_generic_verify_, sts, + metrics::Pow2Bucket(message.size()), algorithm); return sts; } @@ -1117,8 +1076,7 @@ bool CdmSession::UpdateUsageInfo() { usage_data.usage_entry_number = usage_entry_number_; return file_handle_->UpdateUsageInfo( - DeviceFiles::GetUsageInfoFileName(app_id), - usage_provider_session_token_, + DeviceFiles::GetUsageInfoFileName(app_id), usage_provider_session_token_, usage_data); } diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index a1179e15..2b9d9e29 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -212,7 +212,8 @@ CdmLicense::CdmLicense(const CdmSessionId& session_id) renew_with_client_id_(false), is_offline_(false), use_privacy_mode_(false), - clock_(new Clock()) {} + clock_(new Clock()), + license_key_type_(kLicenseKeyTypeContent) {} CdmLicense::CdmLicense(const CdmSessionId& session_id, Clock* clock) : crypto_session_(NULL), @@ -221,7 +222,8 @@ CdmLicense::CdmLicense(const CdmSessionId& session_id, Clock* clock) initialized_(false), renew_with_client_id_(false), is_offline_(false), - use_privacy_mode_(false) { + use_privacy_mode_(false), + license_key_type_(kLicenseKeyTypeContent) { clock_.reset(clock); } @@ -638,6 +640,7 @@ CdmResponseType CdmLicense::HandleKeyResponse( LOGE("CdmLicense::HandleKeyResponse : No content keys."); return NO_CONTENT_KEY; } + license_key_type_ = key_type; if (license.has_srm_update()) crypto_session_->LoadSrm(license.srm_update()); @@ -661,6 +664,7 @@ CdmResponseType CdmLicense::HandleKeyResponse( CdmResponseType resp = NO_CONTENT_KEY; if (kLicenseKeyTypeEntitlement == key_type) { + entitlement_key_array_ = key_array; resp = HandleEntitlementKeyResponse(signed_response.msg(), signed_response.signature(), mac_key_iv, mac_key, key_array, license); @@ -752,55 +756,11 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( } } -CdmResponseType CdmLicense::HandleSubLicense( +CdmResponseType CdmLicense::HandleEmbeddedKeyData( const InitializationData& init_data) { - std::vector subkeys = - init_data.ExtractSublicenseKeys(); - std::set loaded_keys; - // Build a license with the rotated keys. - License license; - for (size_t i = 0; i < subkeys.size(); ++i) { - SignedMessage sm; - if (!sm.ParseFromString(subkeys[i].key_msg())) { - return LICENSE_REQUEST_INVALID_SUBLICENSE; - } - License_KeyContainer keyc; - if (!keyc.ParseFromString(sm.msg())) { - return LICENSE_REQUEST_INVALID_SUBLICENSE; - } - size_t length; - std::vector keys; - keys.resize(1); - keys[0].set_key_id(keyc.id()); - - // Strip PKCS#5 padding from sublicense content keys. - // TODO(jfore): Refactor this to use ExtractContentKeys. - if (keyc.key().size() > KEY_SIZE) { - length = keyc.key().size() - KEY_SIZE; - } else { - length = 0; - } - keys[0].set_key_data(keyc.key().substr(0, length)); - keys[0].set_key_data_iv(keyc.iv()); - keys[0].set_key_control(keyc.key_control().key_control_block()); - keys[0].set_key_control_iv(keyc.key_control().iv()); - keys[0].set_track_label(keyc.track_label()); - //TODO: passing empty cipher_mode and srm_req params - OK? - CdmResponseType result = crypto_session_->LoadKeys( - sm.msg(), sm.signature(), std::string(), std::string(), keys, - std::string(), std::string(), kLicenseKeyTypeContent); - if (result != KEY_ADDED) { - LOGE("CdmLicense::HandleSubLicense: LoadKeys() call failed, result=%d", - result); - return result; - } - loaded_keys.insert(keyc.id()); - *license.add_key() = keyc; - } - loaded_keys_.swap(loaded_keys); - policy_engine_->UpdateLicenseKeys(license); - - return KEY_MESSAGE; + return (license_key_type_ == kLicenseKeyTypeEntitlement + ? HandleNewEntitledKeys(init_data.ExtractWrappedKeys()) + : HandleSubLicense(init_data)); } bool CdmLicense::RestoreOfflineLicense( @@ -1143,61 +1103,112 @@ CdmResponseType CdmLicense::HandleEntitlementKeyResponse( return resp; } - std::vector entitled_key_array; - entitled_key_array.reserve(key_array.size()); + // Save the entitlement keys for future use to handle key changes. + entitlement_keys_.CopyFrom(license.key()); + policy_engine_->SetLicense(license); - for (std::vector::iterator wk = - wrapped_keys_.begin(); - wk != wrapped_keys_.end(); wk++) { - for (std::vector::const_iterator key = key_array.begin(); - key != key_array.end(); key++) { - if (wk->wrapping_key_id() == key->key_id()) { + return HandleNewEntitledKeys(wrapped_keys_); +} + +CdmResponseType CdmLicense::HandleNewEntitledKeys( + const std::vector& wrapped_keys) { + std::vector entitled_key_array; + entitled_key_array.reserve(entitlement_keys_.size()); + + for (RepeatedPtrField::const_iterator kc = + entitlement_keys_.begin(); + kc != entitlement_keys_.end(); kc++) { + if (kc->type() != video_widevine::License::KeyContainer::ENTITLEMENT) { + continue; + } + for (std::vector::const_iterator wk = + wrapped_keys.begin(); + wk != wrapped_keys.end(); wk++) { + if (wk->entitlement_key_id() == kc->id()) { + // Add a new entry to the key array to load oemcrypto. entitled_key_array.resize(entitled_key_array.size() + 1); + + // Strip PKCS#5 padding from entitled content keys. + std::string content_key = wk->key(); + if (content_key.size() > KEY_SIZE) { + content_key.resize(KEY_SIZE); + } + CryptoKey& this_entry = entitled_key_array.back(); this_entry.set_key_id(wk->key_id()); - this_entry.set_key_data(wk->wrapped_key()); - this_entry.set_key_data_iv(wk->wrapping_iv()); - this_entry.set_entitlement_key_id(wk->wrapping_key_id()); + this_entry.set_key_data_iv(wk->iv()); + this_entry.set_entitlement_key_id(wk->entitlement_key_id()); + this_entry.set_key_data(content_key); } } } - resp = crypto_session_->LoadEntitledContentKeys(entitled_key_array); + CdmResponseType resp = + crypto_session_->LoadEntitledContentKeys(entitled_key_array); if (KEY_ADDED == resp) { loaded_keys_.clear(); - for (std::vector::const_iterator it = - wrapped_keys_.begin(); - it != wrapped_keys_.end(); ++it) { + for (std::vector::const_iterator it = + wrapped_keys.begin(); + it != wrapped_keys.end(); ++it) { loaded_keys_.insert(it->key_id()); } - // TODO(jfore): Move the information to build this "license" to the - // entitlement key session. It is used to update the policy engine and - // key status when using entitlement licenses. It may become unnecessary - // if policy manager ius changed to allow setting keys from the wrapped - // keys from init_data. - video_widevine::License entitled_license; - entitled_license.mutable_policy()->CopyFrom(license.policy()); - entitled_license.mutable_id()->CopyFrom(license.id()); - entitled_license.mutable_key()->CopyFrom(license.key()); - entitled_license.set_license_start_time(license.license_start_time()); - for (size_t i = 0; i < wrapped_keys_.size(); ++i) { - for (int x = 0; x < entitled_license.key().size(); ++x) { - if (entitled_license.key(x).id() == - wrapped_keys_[i].wrapping_key_id()) { - video_widevine::License::KeyContainer* kc = - entitled_license.mutable_key(x); - kc->set_type(video_widevine::License::KeyContainer::CONTENT); - kc->set_key(wrapped_keys_[i].wrapped_key()); - kc->set_id(wrapped_keys_[i].key_id()); - } - } - } - policy_engine_->SetLicense(entitled_license); + policy_engine_->SetEntitledLicenseKeys(wrapped_keys); } return resp; } +CdmResponseType CdmLicense::HandleSubLicense( + const InitializationData& init_data) { + std::vector subkeys = + init_data.ExtractSublicenseKeys(); + std::set loaded_keys; + // Build a license with the rotated keys. + License license; + for (size_t i = 0; i < subkeys.size(); ++i) { + SignedMessage sm; + if (!sm.ParseFromString(subkeys[i].key_msg())) { + return LICENSE_REQUEST_INVALID_SUBLICENSE; + } + License_KeyContainer keyc; + if (!keyc.ParseFromString(sm.msg())) { + return LICENSE_REQUEST_INVALID_SUBLICENSE; + } + size_t length; + std::vector keys; + keys.resize(1); + keys[0].set_key_id(keyc.id()); + + // Strip PKCS#5 padding from sublicense content keys. + // TODO(jfore): Refactor this to use ExtractContentKeys. + if (keyc.key().size() > KEY_SIZE) { + length = keyc.key().size() - KEY_SIZE; + } else { + length = 0; + } + keys[0].set_key_data(keyc.key().substr(0, length)); + keys[0].set_key_data_iv(keyc.iv()); + keys[0].set_key_control(keyc.key_control().key_control_block()); + keys[0].set_key_control_iv(keyc.key_control().iv()); + keys[0].set_track_label(keyc.track_label()); + // TODO: passing empty cipher_mode and srm_req params - OK? + CdmResponseType result = crypto_session_->LoadKeys( + sm.msg(), sm.signature(), std::string(), std::string(), keys, + std::string(), std::string(), kLicenseKeyTypeContent); + if (result != KEY_ADDED) { + LOGE("CdmLicense::HandleSubLicense: LoadKeys() call failed, result=%d", + result); + return result; + } + loaded_keys.insert(keyc.id()); + *license.add_key() = keyc; + } + loaded_keys_.swap(loaded_keys); + policy_engine_->UpdateLicenseKeys(license); + + return KEY_MESSAGE; +} + template bool CdmLicense::SetTypeAndId(CdmLicenseType license_type, const std::string& request_id, T* content_id) { diff --git a/libwvdrmengine/cdm/core/src/license_key_status.cpp b/libwvdrmengine/cdm/core/src/license_key_status.cpp index d858a839..8f17abcc 100644 --- a/libwvdrmengine/cdm/core/src/license_key_status.cpp +++ b/libwvdrmengine/cdm/core/src/license_key_status.cpp @@ -35,9 +35,10 @@ wvcdm::CryptoSession::HdcpCapability ProtobufHdcpToOemCryptoHdcp( case OutputProtection::HDCP_NO_DIGITAL_OUTPUT: return HDCP_NO_DIGITAL_OUTPUT; default: - LOGE("ContentKeyStatus::ProtobufHdcpToOemCryptoHdcp: " - "Unknown HDCP Level: input=%d, returning HDCP_NO_DIGITAL_OUTPUT", - input); + LOGE( + "ContentKeyStatus::ProtobufHdcpToOemCryptoHdcp: " + "Unknown HDCP Level: input=%d, returning HDCP_NO_DIGITAL_OUTPUT", + input); return HDCP_NO_DIGITAL_OUTPUT; } } @@ -67,6 +68,8 @@ namespace wvcdm { bool LicenseKeys::IsContentKey(const std::string& key_id) { if (keys_.count(key_id) > 0) { return keys_[key_id]->IsContentKey(); + } else if (content_keyid_to_entitlement_key_id_.count(key_id) > 0) { + return true; } else { return false; } @@ -75,6 +78,12 @@ bool LicenseKeys::IsContentKey(const std::string& key_id) { bool LicenseKeys::CanDecryptContent(const std::string& key_id) { if (keys_.count(key_id) > 0) { return keys_[key_id]->CanDecryptContent(); + } else if (content_keyid_to_entitlement_key_id_.count(key_id) > 0) { + if (keys_.count(content_keyid_to_entitlement_key_id_[key_id]) > 0) { + return keys_[content_keyid_to_entitlement_key_id_[key_id]] + ->CanDecryptContent(); + } + return false; } else { return false; } @@ -84,6 +93,12 @@ bool LicenseKeys::GetAllowedUsage(const KeyId& key_id, CdmKeyAllowedUsage* allowed_usage) { if (keys_.count(key_id) > 0) { return keys_[key_id]->GetAllowedUsage(allowed_usage); + } else if (content_keyid_to_entitlement_key_id_.count(key_id) > 0) { + if (keys_.count(content_keyid_to_entitlement_key_id_[key_id]) > 0) { + return keys_[content_keyid_to_entitlement_key_id_[key_id]] + ->CanDecryptContent(); + } + return false; } else { return false; } @@ -140,29 +155,46 @@ void LicenseKeys::ApplyConstraints( } } -void LicenseKeys::SetFromLicense( - const video_widevine::License& license) { +void LicenseKeys::SetFromLicense(const video_widevine::License& license) { this->Clear(); for (int32_t key_index = 0; key_index < license.key_size(); ++key_index) { const KeyContainer& key = license.key(key_index); if (key.has_id() && (key.type() == KeyContainer::CONTENT || - key.type() == KeyContainer::OPERATOR_SESSION)) { + key.type() == KeyContainer::OPERATOR_SESSION || + key.type() == KeyContainer::ENTITLEMENT)) { const KeyId& key_id = key.id(); keys_[key_id] = new LicenseKeyStatus(key); } } } -LicenseKeyStatus::LicenseKeyStatus(const KeyContainer& key) : - is_content_key_(false), - key_status_(kKeyStatusInternalError), - meets_constraints_(true), - default_hdcp_level_(HDCP_NONE) { +void LicenseKeys::SetEntitledKeys( + const std::vector& keys) { + for (std::vector::const_iterator key = + keys.begin(); + key != keys.end(); key++) { + // Check to see if we have an entitlement key for this content key. + std::map::iterator entitlement = + keys_.find(key->entitlement_key_id()); + if (entitlement == keys_.end()) { + continue; + } + // And set the new content key id. + content_keyid_to_entitlement_key_id_[key->key_id()] = + key->entitlement_key_id(); + } +} +LicenseKeyStatus::LicenseKeyStatus(const KeyContainer& key) + : is_content_key_(false), + key_status_(kKeyStatusInternalError), + meets_constraints_(true), + default_hdcp_level_(HDCP_NONE) { allowed_usage_.Clear(); constraints_.Clear(); - if (key.type() == KeyContainer::CONTENT) { + if (key.type() == KeyContainer::CONTENT || + key.type() == KeyContainer::ENTITLEMENT) { ParseContentKey(key); } else if (key.type() == KeyContainer::OPERATOR_SESSION) { ParseOperatorSessionKey(key); @@ -171,9 +203,8 @@ LicenseKeyStatus::LicenseKeyStatus(const KeyContainer& key) : void LicenseKeyStatus::ParseContentKey(const KeyContainer& key) { is_content_key_ = true; - if (key.has_level() && - ((key.level() == KeyContainer::HW_SECURE_DECODE) || - (key.level() == KeyContainer::HW_SECURE_ALL))) { + if (key.has_level() && ((key.level() == KeyContainer::HW_SECURE_DECODE) || + (key.level() == KeyContainer::HW_SECURE_ALL))) { allowed_usage_.decrypt_to_clear_buffer = false; allowed_usage_.decrypt_to_secure_buffer = true; } else { @@ -251,8 +282,7 @@ bool LicenseKeyStatus::CanDecryptContent() { } bool LicenseKeyStatus::GetAllowedUsage(CdmKeyAllowedUsage* allowed_usage) { - if (NULL == allowed_usage) - return false; + if (NULL == allowed_usage) return false; *allowed_usage = allowed_usage_; return true; } @@ -288,7 +318,6 @@ bool LicenseKeyStatus::ApplyStatusChange(CdmKeyStatus new_status, // device's current HDCP level. void LicenseKeyStatus::ApplyConstraints( uint32_t video_pixels, CryptoSession::HdcpCapability new_hdcp_level) { - VideoResolutionConstraint* current_constraint = NULL; if (HasConstraints() && video_pixels != HDCP_UNSPECIFIED_VIDEO_RESOLUTION) { current_constraint = GetConstraintForRes(video_pixels, constraints_); diff --git a/libwvdrmengine/cdm/core/src/license_protocol.proto b/libwvdrmengine/cdm/core/src/license_protocol.proto index 8eea5124..1ba2e41a 100644 --- a/libwvdrmengine/cdm/core/src/license_protocol.proto +++ b/libwvdrmengine/cdm/core/src/license_protocol.proto @@ -103,8 +103,11 @@ message License { CONTENT = 2; KEY_CONTROL = 3; OPERATOR_SESSION = 4; - SUB_SESSION = 5; - ENTITLEMENT = 6; + // TODO(jfore): Drop subsession type once subsession support is removed + // from the cdm. For now, SUB_SESSION is defined as type 6 so that it + // is defined to satisfy the build. + SUB_SESSION = 6; + ENTITLEMENT = 5; } // The SecurityLevel enumeration allows the server to communicate the level diff --git a/libwvdrmengine/cdm/core/src/policy_engine.cpp b/libwvdrmengine/cdm/core/src/policy_engine.cpp index a2c2e539..c3b2d4d0 100644 --- a/libwvdrmengine/cdm/core/src/policy_engine.cpp +++ b/libwvdrmengine/cdm/core/src/policy_engine.cpp @@ -178,6 +178,11 @@ void PolicyEngine::UpdateLicenseKeys(const video_widevine::License& license) { NotifyKeysChange(kKeyStatusUsable); } +void PolicyEngine::SetEntitledLicenseKeys( + const std::vector& entitled_keys) { + license_keys_->SetEntitledKeys(entitled_keys); +} + void PolicyEngine::SetLicenseForRelease(const License& license) { license_id_.Clear(); license_id_.CopyFrom(license.id()); @@ -257,9 +262,7 @@ void PolicyEngine::BeginDecryption() { } } -void PolicyEngine::DecryptionEvent() { - last_playback_time_ = GetCurrentTime(); -} +void PolicyEngine::DecryptionEvent() { last_playback_time_ = GetCurrentTime(); } void PolicyEngine::NotifyResolution(uint32_t width, uint32_t height) { SetDeviceResolution(width, height); @@ -280,9 +283,8 @@ CdmResponseType PolicyEngine::Query(CdmQueryMap* query_response) { } (*query_response)[QUERY_KEY_LICENSE_TYPE] = - license_id_.type() == video_widevine::STREAMING - ? QUERY_VALUE_STREAMING - : QUERY_VALUE_OFFLINE; + license_id_.type() == video_widevine::STREAMING ? QUERY_VALUE_STREAMING + : QUERY_VALUE_OFFLINE; (*query_response)[QUERY_KEY_PLAY_ALLOWED] = policy_.can_play() ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; (*query_response)[QUERY_KEY_PERSIST_ALLOWED] = @@ -294,7 +296,8 @@ CdmResponseType PolicyEngine::Query(CdmQueryMap* query_response) { ss.str(""); ss << GetPlaybackDurationRemaining(current_time); (*query_response)[QUERY_KEY_PLAYBACK_DURATION_REMAINING] = ss.str(); - (*query_response)[QUERY_KEY_RENEWAL_SERVER_URL] = policy_.renewal_server_url(); + (*query_response)[QUERY_KEY_RENEWAL_SERVER_URL] = + policy_.renewal_server_url(); return NO_ERROR; } @@ -419,10 +422,8 @@ int64_t PolicyEngine::GetRentalExpiryTime() { } int64_t PolicyEngine::GetExpiryTime( - int64_t current_time, - bool ignore_soft_enforce_playback_duration) { - if (!HasPlaybackStarted(current_time)) - return GetRentalExpiryTime(); + int64_t current_time, bool ignore_soft_enforce_playback_duration) { + if (!HasPlaybackStarted(current_time)) return GetRentalExpiryTime(); const int64_t hard_limit = GetHardLicenseExpiryTime(); if (policy_.playback_duration_seconds() == 0) return hard_limit; @@ -433,8 +434,7 @@ int64_t PolicyEngine::GetExpiryTime( const int64_t expiry_time = playback_start_time_ + policy_.playback_duration_seconds(); - if (hard_limit == NEVER_EXPIRES) - return expiry_time; + if (hard_limit == NEVER_EXPIRES) return expiry_time; return std::min(hard_limit, expiry_time); } @@ -495,8 +495,8 @@ void PolicyEngine::NotifyKeysChange(CdmKeyStatus new_status) { if (new_status == kKeyStatusUsable) { CheckDeviceHdcpStatus(); } - keys_changed = license_keys_->ApplyStatusChange(new_status, - &has_new_usable_key); + keys_changed = + license_keys_->ApplyStatusChange(new_status, &has_new_usable_key); if (event_listener_ && keys_changed) { CdmKeyStatusMap content_keys; license_keys_->ExtractKeyStatuses(&content_keys); diff --git a/libwvdrmengine/cdm/core/test/license_keys_unittest.cpp b/libwvdrmengine/cdm/core/test/license_keys_unittest.cpp index 8ea902a1..16ce2007 100644 --- a/libwvdrmengine/cdm/core/test/license_keys_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/license_keys_unittest.cpp @@ -127,6 +127,20 @@ class LicenseKeysTest : public ::testing::Test { key->set_id(key_id); } + virtual void AddEntitlementKey( + const KeyId& key_id, bool set_level = false, + KeyContainer::SecurityLevel level = KeyContainer::SW_SECURE_CRYPTO, + bool set_hdcp = false, + KeyContainer::OutputProtection::HDCP hdcp_value = + KeyContainer::OutputProtection::HDCP_NONE, + bool set_constraints = false, + std::vector* constraints = NULL) { + AddContentKey(key_id, set_level, level, set_hdcp, hdcp_value, + set_constraints, constraints); + license_.mutable_key(license_.key_size() - 1) + ->set_type(KeyContainer::ENTITLEMENT); + } + virtual void AddOperatorSessionKey( const KeyId& key_id, bool set_perms = false, KeyFlag encrypt = kKeyFlagNull, KeyFlag decrypt = kKeyFlagNull, @@ -375,6 +389,26 @@ TEST_F(LicenseKeysTest, ContentKey) { EXPECT_TRUE(license_keys_.IsContentKey(c_key)); } +TEST_F(LicenseKeysTest, EntitlementKey) { + const KeyId e_key = "entitlement_key"; + const KeyId c_key = "content_key"; + AddEntitlementKey(e_key); + EXPECT_FALSE(license_keys_.IsContentKey(e_key)); + + license_keys_.SetFromLicense(license_); + // TODO(juce, rfrias): For simplicity entitlement keys are indicated as + // content keys. It doesn't break anything, but CanDecryptContent returns true + // for and entitlement key id. + EXPECT_TRUE(license_keys_.IsContentKey(e_key)); + + std::vector entitled_keys(1); + entitled_keys[0].set_entitlement_key_id(e_key); + entitled_keys[0].set_key_id(c_key); + EXPECT_FALSE(license_keys_.IsContentKey(c_key)); + license_keys_.SetEntitledKeys(entitled_keys); + EXPECT_TRUE(license_keys_.IsContentKey(c_key)); +} + TEST_F(LicenseKeysTest, OperatorSessionKey) { const KeyId os_key = "op_sess_key"; EXPECT_FALSE(license_keys_.IsContentKey(os_key)); @@ -387,13 +421,17 @@ TEST_F(LicenseKeysTest, OperatorSessionKey) { TEST_F(LicenseKeysTest, CanDecrypt) { const KeyId os_key = "op_sess_key"; const KeyId c_key = "content_key"; + const KeyId e_key = "entitlement_key"; EXPECT_FALSE(license_keys_.CanDecryptContent(c_key)); EXPECT_FALSE(license_keys_.CanDecryptContent(os_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(e_key)); AddOperatorSessionKey(os_key); AddContentKey(c_key); + AddEntitlementKey(e_key); license_keys_.SetFromLicense(license_); EXPECT_FALSE(license_keys_.CanDecryptContent(c_key)); EXPECT_FALSE(license_keys_.CanDecryptContent(os_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(e_key)); bool new_usable_keys = false; bool any_change = false; any_change = license_keys_.ApplyStatusChange(kKeyStatusUsable, @@ -409,15 +447,18 @@ TEST_F(LicenseKeysTest, CanDecrypt) { EXPECT_FALSE(new_usable_keys); EXPECT_FALSE(license_keys_.CanDecryptContent(c_key)); EXPECT_FALSE(license_keys_.CanDecryptContent(os_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(e_key)); } TEST_F(LicenseKeysTest, AllowedUsageNull) { const KeyId os_key = "op_sess_key"; const KeyId c_key = "content_key"; const KeyId sign_key = "signing_key"; + const KeyId e_key = "entitlement_key"; AddOperatorSessionKey(os_key); AddContentKey(c_key); AddSigningKey(sign_key); + AddEntitlementKey(e_key); license_keys_.SetFromLicense(license_); CdmKeyAllowedUsage usage_1; EXPECT_FALSE(license_keys_.GetAllowedUsage(sign_key, &usage_1)); @@ -431,6 +472,11 @@ TEST_F(LicenseKeysTest, AllowedUsageNull) { EXPECT_TRUE(license_keys_.GetAllowedUsage(os_key, &usage_3)); ExpectAllowedUsageContent(usage_3, kContentClearFalse, kContentSecureFalse, kKeySecurityLevelUnset); + + CdmKeyAllowedUsage usage_4; + EXPECT_TRUE(license_keys_.GetAllowedUsage(os_key, &usage_4)); + ExpectAllowedUsageContent(usage_4, kContentClearFalse, kContentSecureFalse, + kKeySecurityLevelUnset); } TEST_F(LicenseKeysTest, AllowedUsageContent) { diff --git a/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp b/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp index d9bfc6bc..81968152 100644 --- a/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/policy_engine_unittest.cpp @@ -38,6 +38,7 @@ const int64_t kHighDuration = const char* kRenewalServerUrl = "https://test.google.com/license/GetCencLicense"; const KeyId kKeyId = "357adc89f1673433c36c621f1b5c41ee"; +const KeyId kEntitlementKeyId = "entitlementkeyid"; const KeyId kAnotherKeyId = "another_key_id"; const KeyId kSomeRandomKeyId = "some_random_key_id"; const KeyId kUnknownKeyId = "some_random_unknown_key_id"; @@ -157,6 +158,16 @@ class PolicyEngineTest : public ::testing::Test { expected_has_new_usable_key)); } + void ExpectSessionKeysChange(CdmKeyStatus expected_key_status, + bool expected_has_new_usable_key, + KeyId expected_keyid) { + EXPECT_CALL(mock_event_listener_, + OnSessionKeysChange( + kSessionId, UnorderedElementsAre( + Pair(expected_keyid, expected_key_status)), + expected_has_new_usable_key)); + } + void ExpectSessionKeysChange(CdmKeyStatus expected_key1_status, CdmKeyStatus expected_key2_status, bool expected_has_new_usable_key) { @@ -206,6 +217,73 @@ TEST_F(PolicyEngineTest, PlaybackSuccess_OfflineLicense) { EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); } +TEST_F(PolicyEngineTest, PlaybackSuccess_EntitlementLicense) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kPlaybackStartTime)) + .WillOnce(Return(kLicenseStartTime + 10)); + + ExpectSessionKeysChange(kKeyStatusUsable, true, kEntitlementKeyId); + EXPECT_CALL(mock_event_listener_, + OnExpirationUpdate(_, kLicenseStartTime + kRentalDuration)); + EXPECT_CALL(mock_event_listener_, + OnExpirationUpdate(_, kPlaybackStartTime + kPlaybackDuration)); + + EXPECT_CALL(crypto_session_, GetHdcpCapabilities(_, _)) + .WillRepeatedly( + DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT), + Return(true))); + + License::KeyContainer* key = license_.mutable_key(0); + key->set_type(License::KeyContainer::ENTITLEMENT); + key->set_id(kEntitlementKeyId); + + policy_engine_->SetLicense(license_); + policy_engine_->BeginDecryption(); + policy_engine_->OnTimerEvent(); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId)); + + std::vector entitled_keys(1); + entitled_keys[0].set_entitlement_key_id(kEntitlementKeyId); + entitled_keys[0].set_key_id(kKeyId); + policy_engine_->SetEntitledLicenseKeys(entitled_keys); + + EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kSomeRandomKeyId)); +} + +TEST_F(PolicyEngineTest, PlaybackSuccess_EntitlementLicenseExpiration) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kPlaybackStartTime)) + .WillOnce(Return(kPlaybackStartTime + kPlaybackDuration + 10)) + .WillOnce(Return(kPlaybackStartTime + kPlaybackDuration + 11)); + + ExpectSessionKeysChange(kKeyStatusUsable, true, kEntitlementKeyId); + ExpectSessionKeysChange(kKeyStatusExpired, false, kEntitlementKeyId); + EXPECT_CALL(mock_event_listener_, + OnExpirationUpdate(_, kLicenseStartTime + kRentalDuration)); + EXPECT_CALL(mock_event_listener_, + OnExpirationUpdate(_, kPlaybackStartTime + kPlaybackDuration)); + + License::KeyContainer* key = license_.mutable_key(0); + key->set_type(License::KeyContainer::ENTITLEMENT); + key->set_id(kEntitlementKeyId); + + policy_engine_->SetLicense(license_); + policy_engine_->BeginDecryption(); + policy_engine_->OnTimerEvent(); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId)); + + std::vector entitled_keys(1); + entitled_keys[0].set_entitlement_key_id(kEntitlementKeyId); + entitled_keys[0].set_key_id(kKeyId); + policy_engine_->SetEntitledLicenseKeys(entitled_keys); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId)); + policy_engine_->OnTimerEvent(); + EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId)); +} + TEST_F(PolicyEngineTest, PlaybackSuccess_StreamingLicense) { License_Policy* policy = license_.mutable_policy(); policy->set_license_duration_seconds(kLowDuration);