From d36ab5657564555fa7b91ef97a44f33260f4a89c Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Sun, 1 Jul 2018 19:41:58 -0700 Subject: [PATCH] Sync the definition of WidevinePssh data Merge from Widevine repo of http://go/wvgerrit/43202 Sync the definition of WidevinePssh data with the latest in support of entitlement keys. bug: 73297961 Fix or remove sublicense support. Test: tested as part of http://go/ag/4674759 Change-Id: Ia9faf82732854a705b4b14430169ce4c8ecbcfcd --- .../cdm/core/include/initialization_data.h | 3 +- .../cdm/core/src/initialization_data.cpp | 17 +-- libwvdrmengine/cdm/core/src/license.cpp | 70 ++++++------ .../cdm/core/src/license_protocol.proto | 105 +++++++++--------- .../test/initialization_data_unittest.cpp | 4 +- .../cdm/core/test/license_unittest.cpp | 4 +- 6 files changed, 104 insertions(+), 99 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/initialization_data.h b/libwvdrmengine/cdm/core/include/initialization_data.h index 5ccfec32..90ea88d4 100644 --- a/libwvdrmengine/cdm/core/include/initialization_data.h +++ b/libwvdrmengine/cdm/core/include/initialization_data.h @@ -34,7 +34,8 @@ class InitializationData { // TODO(jfore): Perhaps this should be a generic structure with the ids for // any type of licensing? std::vector ExtractSublicenseKeys() const; - std::vector ExtractWrappedKeys() const; + std::vector ExtractWrappedKeys() + const; private: bool SelectWidevinePssh(const CdmInitData& init_data, diff --git a/libwvdrmengine/cdm/core/src/initialization_data.cpp b/libwvdrmengine/cdm/core/src/initialization_data.cpp index d025c966..0fad7675 100644 --- a/libwvdrmengine/cdm/core/src/initialization_data.cpp +++ b/libwvdrmengine/cdm/core/src/initialization_data.cpp @@ -89,21 +89,14 @@ InitializationData::InitializationData(const std::string& type, std::vector InitializationData::ExtractSublicenseKeys() const { std::vector keys; - WidevinePsshData cenc_header; - if (!is_cenc_ || !cenc_header.ParseFromString(data_) || - cenc_header.sub_licenses().size() == 0) - return keys; - - keys.reserve(cenc_header.sub_licenses().size()); - for (int i = 0; i < cenc_header.sub_licenses().size(); ++i) { - keys.push_back(cenc_header.sub_licenses(i)); - } + // TODO(jfore): The pssh has changed in ways that are not compatible with + //sublicenses. Restructure or remove sublicense support including this method. return keys; } -std::vector InitializationData::ExtractWrappedKeys() - const { - std::vector keys; +std::vector +InitializationData::ExtractWrappedKeys() const { + std::vector keys; WidevinePsshData cenc_header; if (!is_cenc_ || !cenc_header.ParseFromString(data_) || cenc_header.entitled_keys().size() == 0) diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index fc560e88..2f7fbd49 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -8,9 +8,9 @@ #include #include -#include "clock.h" #include "cdm_session.h" #include "client_identification.h" +#include "clock.h" #include "crypto_key.h" #include "crypto_session.h" #include "device_files.h" @@ -39,6 +39,7 @@ using video_widevine::ClientIdentification_NameValue; using video_widevine::DrmDeviceCertificate; using video_widevine::EncryptedClientIdentification; using video_widevine::License; +using video_widevine::License_KeyContainer; using video_widevine::LicenseError; using video_widevine::LicenseIdentification; using video_widevine::LicenseRequest; @@ -46,7 +47,6 @@ using video_widevine::LicenseRequest_ContentIdentification; using video_widevine::LicenseRequest_ContentIdentification_CencDeprecated; using video_widevine::LicenseRequest_ContentIdentification_ExistingLicense; using video_widevine::LicenseRequest_ContentIdentification_WebmDeprecated; -using video_widevine::License_KeyContainer; using video_widevine::SignedDrmDeviceCertificate; using video_widevine::SignedMessage; @@ -237,11 +237,11 @@ CdmLicense::CdmLicense(const CdmSessionId& session_id, Clock* clock) CdmLicense::~CdmLicense() {} -bool CdmLicense::Init( - const std::string& client_token, CdmClientTokenType client_token_type, - const std::string& device_id, bool use_privacy_mode, - const std::string& signed_service_certificate, CryptoSession* session, - PolicyEngine* policy_engine) { +bool CdmLicense::Init(const std::string& client_token, + CdmClientTokenType client_token_type, + const std::string& device_id, bool use_privacy_mode, + const std::string& signed_service_certificate, + CryptoSession* session, PolicyEngine* policy_engine) { if (clock_.get() == NULL) { LOGE("CdmLicense::Init: clock parameter not provided"); return false; @@ -321,18 +321,19 @@ CdmResponseType CdmLicense::PrepareKeyRequest( // If privacy mode and no service certificate, depending on platform // configuration, request service certificate or declare error if (use_privacy_mode_ && !service_certificate_.has_certificate()) { - if (!Properties::allow_service_certificate_requests()) { - LOGE("CdmLicense::PrepareKeyRequest: failure with privacy mode - " - "no service certificate."); + LOGE( + "CdmLicense::PrepareKeyRequest: failure with privacy mode - " + "no service certificate."); return PRIVACY_MODE_ERROR_1; } stored_init_data_.reset(new InitializationData(init_data)); if (!ServiceCertificate::GetRequest(signed_request)) { - LOGE("CdmLicense::PrepareKeyRequest: failed to prepare a service " - "certificated request"); + LOGE( + "CdmLicense::PrepareKeyRequest: failed to prepare a service " + "certificated request"); return LICENSE_REQUEST_SERVICE_CERTIFICATE_GENERATION_ERROR; } @@ -452,8 +453,9 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( if (renew_with_client_id_) { if (use_privacy_mode_ && !service_certificate_.has_certificate()) { - LOGE("CdmLicense::PrepareKeyUpdateRequest: failure with privacy mode - " - "no service certificate."); + LOGE( + "CdmLicense::PrepareKeyUpdateRequest: failure with privacy mode - " + "no service certificate."); return PRIVACY_MODE_ERROR_2; } } @@ -573,13 +575,11 @@ CdmResponseType CdmLicense::HandleKeyResponse( return INVALID_LICENSE_RESPONSE; } - if (use_privacy_mode_ && - Properties::allow_service_certificate_requests() && + if (use_privacy_mode_ && Properties::allow_service_certificate_requests() && signed_response.type() == SignedMessage::SERVICE_CERTIFICATE) { std::string signed_certificate; - CdmResponseType status = - ServiceCertificate::ParseResponse(license_response, - &signed_certificate); + CdmResponseType status = ServiceCertificate::ParseResponse( + license_response, &signed_certificate); if (status != NO_ERROR) return status; status = service_certificate_.Init(signed_certificate); @@ -591,7 +591,7 @@ CdmResponseType CdmLicense::HandleKeyResponse( if (signed_response.type() != SignedMessage::LICENSE) { LOGE("CdmLicense::HandleKeyResponse: unrecognized signed message type: %d", - signed_response.type()); + signed_response.type()); return INVALID_LICENSE_TYPE; } @@ -641,8 +641,6 @@ CdmResponseType CdmLicense::HandleKeyResponse( if (key_array.empty()) { key_array = ExtractContentKeys(license); key_type = kLicenseKeyTypeContent; - } else if (wrapped_keys_.empty()) { - key_array.clear(); } if (key_array.empty()) { LOGE("CdmLicense::HandleKeyResponse : No content keys."); @@ -659,8 +657,9 @@ CdmResponseType CdmLicense::HandleKeyResponse( if (license.id().has_provider_session_token()) provider_session_token_ = license.id().provider_session_token(); - LOGV("provider_session_token: %s", provider_session_token_.empty() ? - "N/A" : provider_session_token_.c_str()); + LOGV("provider_session_token: %s", provider_session_token_.empty() + ? "N/A" + : provider_session_token_.c_str()); if (license.policy().has_renewal_server_url()) { server_url_ = license.policy().renewal_server_url(); @@ -697,7 +696,9 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( SignedMessage signed_response; if (!signed_response.ParseFromString(license_response)) { - LOGE("CdmLicense::HandleKeyUpdateResponse: Unable to parse signed message"); + LOGE( + "CdmLicense::HandleKeyUpdateResponse: Unable to parse signed " + "message"); return LICENSE_RESPONSE_PARSE_ERROR_2; } @@ -871,7 +872,8 @@ bool CdmLicense::RestoreLicenseForRelease( if (signed_request.type() != SignedMessage::LICENSE_REQUEST) { LOGE( - "CdmLicense::RestoreLicenseForRelease: license request type: expected " + "CdmLicense::RestoreLicenseForRelease: license request type: " + "expected " "= %d, actual = %d", SignedMessage::LICENSE_REQUEST, signed_request.type()); return false; @@ -955,14 +957,17 @@ bool CdmLicense::ExtractProviderSessionToken( } if (signed_response.type() != SignedMessage::LICENSE) { - LOGE("CdmLicense::ExtractProviderSessionToken: unrecognized signed message " - "type: %d", signed_response.type()); - return false; + LOGE( + "CdmLicense::ExtractProviderSessionToken: unrecognized signed message " + "type: %d", + signed_response.type()); + return false; } License license; if (!license.ParseFromString(signed_response.msg())) { - LOGE("CdmLicense::ExtractProviderSessionToken: unable to parse license " + LOGE( + "CdmLicense::ExtractProviderSessionToken: unable to parse license " "response"); return false; } @@ -999,7 +1004,6 @@ CdmResponseType CdmLicense::HandleKeyErrorResponse( CdmResponseType CdmLicense::PrepareClientId( const CdmAppParameterMap& app_parameters, LicenseRequest* license_request) { - wvcdm::ClientIdentification id; CdmResponseType status = id.Init(client_token_, device_id_, crypto_session_); if (status != NO_ERROR) return status; @@ -1100,8 +1104,8 @@ CdmResponseType CdmLicense::HandleEntitlementKeyResponse( const std::string& mac_key_iv, const std::string& mac_key, const std::vector& key_array, const video_widevine::License& license) { - if (key_array.empty() || wrapped_keys_.empty()) { - LOGE("CdmLicense::HandleKeyResponse : No content keys."); + if (key_array.empty()) { + LOGE("CdmLicense::HandleKeyResponse : No entitlement keys."); return NO_CONTENT_KEY; } CdmResponseType resp = crypto_session_->LoadKeys( diff --git a/libwvdrmengine/cdm/core/src/license_protocol.proto b/libwvdrmengine/cdm/core/src/license_protocol.proto index 1ba2e41a..ff3f80ec 100644 --- a/libwvdrmengine/cdm/core/src/license_protocol.proto +++ b/libwvdrmengine/cdm/core/src/license_protocol.proto @@ -743,61 +743,47 @@ message SubLicense { optional bytes group_id = 13; } -// Container for keys which are wrapped using an entitlement key from a master -// license. -message WrappedKey { -// ID of the wrapped key. Required. - optional bytes key_id = 1; - // ID of wrapping key. Required. - optional bytes wrapping_key_id = 2; - // IV used to wrap the key. Required. - optional bytes wrapping_iv = 3; - // Encrypted entitled key. Wrapped with the entitlement key and IV, using - // AES-256-CBC with PKCS#7 padding. Required. - optional bytes wrapped_key = 4; -} - message WidevinePsshData { -// Superceded by protection_scheme. - enum Algorithm { - UNENCRYPTED = 0; - AESCTR = 1; - }; + enum Type { + SINGLE = 0; // Single PSSH to be used to retrieve content keys. + ENTITLEMENT = 1; // Primary PSSH used to retrieve entitlement keys. + ENTITLED_KEY = 2; // Secondary PSSH containing entitled key(s). + } - optional Algorithm algorithm = 1 [deprecated = true]; + message EntitledKey { + // ID of entitlement key used for wrapping |key|. + optional bytes entitlement_key_id = 1; + // ID of the entitled key. + optional bytes key_id = 2; + // Wrapped key. Required. + optional bytes key = 3; + // IV used for wrapping |key|. Required. + optional bytes iv = 4; + } - // Key IDentifier(s). This field is mutually exclusive with content_id, below. - // Only One or the other, but at least one must be present. + // Entitlement or content key IDs. Can onnly present in SINGLE or ENTITLEMENT + // PSSHs. May be repeated to facilitate delivery of multiple keys in a + // single license. Cannot be used in conjunction with content_id or + // group_ids, which are the preferred mechanism. repeated bytes key_ids = 2; - // Content provider name. - optional string provider = 3 [deprecated = true]; - - // A content identifier, specified by content provider. - // This field is mutually exclusive with key_ids, above. Only - // one or the other, but at least one must be present. + // Content identifier which may map to multiple entitlement or content key + // IDs to facilitate the delivery of multiple keys in a single license. + // Cannot be present in conjunction with key_ids, but if used must be in all + // PSSHs. optional bytes content_id = 4; - // Track type. Acceptable values are SD, HD and AUDIO. Used to differentiate - // content keys used by an asset. - // No longer adding track_type to the PSSH since the Widevine license server - // will return keys for all allowed track types in a single license. - optional string track_type = 5 [deprecated = true]; - - // The name of a registered policy to be used for this asset. - optional string policy = 6 [deprecated=true]; - - // Crypto period index, for media using key rotation. + // Crypto period index, for media using key rotation. Always corresponds to + // The content key period. This means that if using entitlement licensing + // the ENTITLED_KEY PSSHs will have sequential crypto_period_index's, whereas + // the ENTITELEMENT PSSHs will have gaps in the sequence. Required if doing + // key rotation. optional uint32 crypto_period_index = 7; - // Optional protected context for group content. The grouped_license is a - // serialized SignedMessage. - optional bytes grouped_license = 8 [deprecated = true]; - // Protection scheme identifying the encryption algorithm. The protection // scheme is represented as a uint32 value. The uint32 contains 4 bytes each // representing a single ascii character in one of the 4CC protection scheme - // values. To be soon deprecated in favor of signaling from content. + // values. To be deprecated in favor of signaling from content. // 'cenc' (AES-CTR) protection_scheme = 0x63656E63, // 'cbc1' (AES-CBC) protection_scheme = 0x63626331, // 'cens' (AES-CTR pattern encryption) protection_scheme = 0x63656E73, @@ -808,18 +794,35 @@ message WidevinePsshData { // of each crypto period in seconds. optional uint32 crypto_period_seconds = 10; - // Required when using content keys that are embedded in content. - repeated SubLicense sub_licenses = 11; + // Type of PSSH. Required if not SINGLE. + optional Type type = 11 [default = SINGLE]; - // IDs of the groups to which the content belongs. A group is a set of - // content IDs. A particular piece of content may belong to multiple groups. - repeated bytes group_ids = 12; + // Key sequence for Widevine-managed keys. Optional. + optional uint32 key_sequence = 12; + + // Group identifiers for all groups to which the content belongs. This can + // be used to deliver licenses to unlock multiple titles / channels. + // Optional, and may only be present in ENTITLEMENT and ENTITLED_KEY PSSHs, and + // not in conjunction with key_ids. + repeated bytes group_ids = 13; // Copy/copies of the content key used to decrypt the media stream in which // the PSSH box is embedded, each wrapped with a different entitlement key. - // May be repeated if using group entitlement keys. Optional, used for content - // key rotation. - repeated WrappedKey entitled_keys = 13; + // May also contain sub-licenses to support devices with OEMCrypto 13 or + // older. May be repeated if using group entitlement keys. Present only in + // PSSHs of type ENTITLED_KEY. + repeated EntitledKey entitled_keys = 14; + + //////////////////////////// Deprecated Fields //////////////////////////// + enum Algorithm { + UNENCRYPTED = 0; + AESCTR = 1; + }; + optional Algorithm algorithm = 1 [deprecated = true]; + optional string provider = 3 [deprecated = true]; + optional string track_type = 5 [deprecated = true]; + optional string policy = 6 [deprecated = true]; + optional bytes grouped_license = 8 [deprecated = true]; } // Signed device certificate definition. diff --git a/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp b/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp index 587d9903..01ea0f60 100644 --- a/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp @@ -546,7 +546,9 @@ TEST_F(InitializationDataTest, HandlesMultipleWidevinePsshs) { EXPECT_EQ(kEntitledKeysWidevinePsshBoxData, entitled_init_data.data()); } -TEST_F(InitializationDataTest, ExtractSubLicense) { +// TODO(jfore): The pssh has changed in ways that are not compatible with +//sublicenses. Restructure or remove sublicense support including this test. +TEST_F(InitializationDataTest, DISABLED_ExtractSubLicense) { InitializationData init_data(ISO_BMFF_VIDEO_MIME_TYPE, kSubLicensePsshBox); ASSERT_FALSE(init_data.IsEmpty()); std::vector keys = diff --git a/libwvdrmengine/cdm/core/test/license_unittest.cpp b/libwvdrmengine/cdm/core/test/license_unittest.cpp index 1f6f5b62..9c13fdb2 100644 --- a/libwvdrmengine/cdm/core/test/license_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/license_unittest.cpp @@ -413,7 +413,9 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidation) { EXPECT_EQ(kNonce, license_request.key_control_nonce()); } -TEST_F(SubLicenseTest, VerifySubSessionData) { +// TODO(jfore): The pssh has changed in ways that are not compatible with +//sublicenses. Restructure or remove sublicense support including this test. +TEST_F(SubLicenseTest, DISABLED_VerifySubSessionData) { bool usage_information_support = true; CryptoSession::HdcpCapability current_hdcp_version = HDCP_NO_DIGITAL_OUTPUT; CryptoSession::HdcpCapability max_hdcp_version = HDCP_V2_1;