diff --git a/libwvdrmengine/cdm/core/include/license.h b/libwvdrmengine/cdm/core/include/license.h index 6737e161..0f1f2f50 100644 --- a/libwvdrmengine/cdm/core/include/license.h +++ b/libwvdrmengine/cdm/core/include/license.h @@ -140,6 +140,7 @@ class CdmLicense { bool initialized_; std::set loaded_keys_; std::string provider_session_token_; + video_widevine::ProtocolVersion protocol_version_; bool renew_with_client_id_; bool is_offline_; diff --git a/libwvdrmengine/cdm/core/include/privacy_crypto.h b/libwvdrmengine/cdm/core/include/privacy_crypto.h index 9904a518..5ac0bf30 100644 --- a/libwvdrmengine/cdm/core/include/privacy_crypto.h +++ b/libwvdrmengine/cdm/core/include/privacy_crypto.h @@ -83,6 +83,7 @@ bool ExtractExtensionValueFromCertificate(const std::string& cert, std::string Md5Hash(const std::string& data); std::string Sha256Hash(const std::string& data); +std::string Sha512Hash(const std::string& data); } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index 6e6e54a3..79eeec8c 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -113,7 +113,8 @@ std::vector ExtractEntitlementKeys(const License& license) { return key_array; } -std::vector ExtractContentKeys(const License& license) { +std::vector ExtractContentKeys( + const License& license, video_widevine::ProtocolVersion version) { std::vector key_array; // Extract content key(s) @@ -130,21 +131,20 @@ std::vector ExtractContentKeys(const License& license) { // TODO(b/232464183): When we switch to License Protocol 2.2, there will // no longer be padding on these keys, so this // removal code must be removed at the same time. - if (license.key(i).key().size() != - CONTENT_KEY_SIZE + LICENSE_PROTOCOL_2_1_PADDING && - license.key(i).key().size() != - MAC_KEY_SIZE + LICENSE_PROTOCOL_2_1_PADDING) { + const auto padding = version <= video_widevine::VERSION_2_1 + ? LICENSE_PROTOCOL_2_1_PADDING + : 0; + if (license.key(i).key().size() != CONTENT_KEY_SIZE + padding && + license.key(i).key().size() != MAC_KEY_SIZE + padding) { LOGE( "Skipping key %s because it is an unexpected size. Expected: %zu " "or %zu, Actual: %zu", - license.key(i).id().c_str(), - CONTENT_KEY_SIZE + LICENSE_PROTOCOL_2_1_PADDING, - MAC_KEY_SIZE + LICENSE_PROTOCOL_2_1_PADDING, - license.key(i).key().size()); + license.key(i).id().c_str(), CONTENT_KEY_SIZE + padding, + MAC_KEY_SIZE + padding, license.key(i).key().size()); continue; } const size_t length = - license.key(i).key().size() - LICENSE_PROTOCOL_2_1_PADDING; + license.key(i).key().size() - padding; key.set_key_data(license.key(i).key().substr(0, length)); key.set_key_data_iv(license.key(i).iv()); if (license.key(i).has_key_control()) { @@ -199,6 +199,7 @@ CdmLicense::CdmLicense(const CdmSessionId& session_id) policy_engine_(nullptr), session_id_(session_id), initialized_(false), + protocol_version_(video_widevine::VERSION_2_2), renew_with_client_id_(false), is_offline_(false), use_privacy_mode_(false), @@ -210,6 +211,7 @@ CdmLicense::CdmLicense(const CdmSessionId& session_id, wvutil::Clock* clock) policy_engine_(nullptr), session_id_(session_id), initialized_(false), + protocol_version_(video_widevine::VERSION_2_2), renew_with_client_id_(false), is_offline_(false), use_privacy_mode_(false), @@ -244,6 +246,13 @@ bool CdmLicense::Init(bool use_privacy_mode, return false; } + uint32_t api_version; + if (!session->GetApiVersion(&api_version)) { + api_version = 16; + } + protocol_version_ = api_version >= 19 ? video_widevine::VERSION_2_2 + : video_widevine::VERSION_2_1; + crypto_session_ = session; policy_engine_ = policy_engine; use_privacy_mode_ = use_privacy_mode; @@ -338,7 +347,7 @@ CdmResponseType CdmLicense::PrepareKeyRequest( return CdmResponseType(LICENSE_REQUEST_NONCE_GENERATION_ERROR); } license_request.set_key_control_nonce(license_nonce_); - license_request.set_protocol_version(video_widevine::VERSION_2_1); + license_request.set_protocol_version(protocol_version_); // License request is complete. Serialize it. std::string serialized_license_req; @@ -494,7 +503,7 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( current_license->set_seconds_since_last_played(seconds_since_last_played); } - license_request.set_protocol_version(video_widevine::VERSION_2_1); + license_request.set_protocol_version(protocol_version_); // License request is complete. Serialize it. std::string serialized_license_req; @@ -619,7 +628,7 @@ CdmResponseType CdmLicense::HandleKeyResponse( CdmLicenseKeyType key_type = kLicenseKeyTypeEntitlement; std::vector key_array = ExtractEntitlementKeys(license); if (key_array.empty()) { - key_array = ExtractContentKeys(license); + key_array = ExtractContentKeys(license, protocol_version_); key_type = kLicenseKeyTypeContent; } if (key_array.empty()) { @@ -794,6 +803,7 @@ CdmResponseType CdmLicense::RestoreOfflineLicense( LOGW("Could not parse original request."); } else { license_nonce_ = original_license_request.key_control_nonce(); + protocol_version_ = original_license_request.protocol_version(); } CdmResponseType sts = HandleKeyResponse(true, license_response); @@ -1091,9 +1101,11 @@ CdmResponseType CdmLicense::HandleContentKeyResponse( LOGE("No content keys provided"); return CdmResponseType(NO_CONTENT_KEY); } - const CdmResponseType resp = - crypto_session_->LoadLicense(key_request_, session_key, msg, core_message, - signature, kLicenseKeyTypeContent); + const CdmResponseType resp = crypto_session_->LoadLicense( + protocol_version_ <= video_widevine::VERSION_2_1 + ? key_request_ + : Sha512Hash(key_request_), + session_key, msg, core_message, signature, kLicenseKeyTypeContent); if (KEY_ADDED == resp) { loaded_keys_.clear(); for (const CryptoKey& key : key_array) { @@ -1113,9 +1125,11 @@ CdmResponseType CdmLicense::HandleEntitlementKeyResponse( LOGE("No entitlement keys provided"); return CdmResponseType(NO_CONTENT_KEY); } - const CdmResponseType resp = - crypto_session_->LoadLicense(key_request_, session_key, msg, core_message, - signature, kLicenseKeyTypeEntitlement); + const CdmResponseType resp = crypto_session_->LoadLicense( + protocol_version_ <= video_widevine::VERSION_2_1 + ? key_request_ + : Sha512Hash(key_request_), + session_key, msg, core_message, signature, kLicenseKeyTypeEntitlement); if (KEY_ADDED != resp) { return resp; diff --git a/libwvdrmengine/cdm/core/src/privacy_crypto_boringssl.cpp b/libwvdrmengine/cdm/core/src/privacy_crypto_boringssl.cpp index 6377642b..40f7bb1b 100644 --- a/libwvdrmengine/cdm/core/src/privacy_crypto_boringssl.cpp +++ b/libwvdrmengine/cdm/core/src/privacy_crypto_boringssl.cpp @@ -417,4 +417,11 @@ std::string Sha256Hash(const std::string& data) { return hash; } +std::string Sha512Hash(const std::string& data) { + std::string hash(SHA512_DIGEST_LENGTH, '\0'); + SHA512(reinterpret_cast(data.data()), data.size(), + reinterpret_cast(&hash[0])); + return hash; +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/src/privacy_crypto_dummy.cpp b/libwvdrmengine/cdm/core/src/privacy_crypto_dummy.cpp index c016b478..26ef4d77 100644 --- a/libwvdrmengine/cdm/core/src/privacy_crypto_dummy.cpp +++ b/libwvdrmengine/cdm/core/src/privacy_crypto_dummy.cpp @@ -13,6 +13,8 @@ # include # define SHA256 CC_SHA256 # define SHA256_DIGEST_LENGTH CC_SHA256_DIGEST_LENGTH +# define SHA512 CC_SHA512 +# define SHA512_DIGEST_LENGTH CC_SHA512_DIGEST_LENGTH # define MD5 CC_MD5 # define MD5_DIGEST_LENGTH CC_MD5_DIGEST_LENGTH #else @@ -69,4 +71,10 @@ std::string Sha256Hash(const std::string& data) { return hash; } +std::string Sha512Hash(const std::string& data) { + std::string hash(SHA512_DIGEST_LENGTH, '\0'); + SHA512(data.data(), data.size(), reinterpret_cast(&hash[0])); + return hash; +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/license_unittest.cpp b/libwvdrmengine/cdm/core/test/license_unittest.cpp index 394479b4..81627d7c 100644 --- a/libwvdrmengine/cdm/core/test/license_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/license_unittest.cpp @@ -315,7 +315,7 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidation) { // Supported certificates set by SetUp(). EXPECT_CALL(*crypto_session_, GetSupportedCertificateTypes(NotNull())); EXPECT_CALL(*crypto_session_, GetApiVersion(NotNull())) - .WillOnce( + .WillRepeatedly( DoAll(SetArgPointee<0>(crypto_session_api_version), Return(true))); EXPECT_CALL(*crypto_session_, GetResourceRatingTier(NotNull())) .WillOnce(DoAll(SetArgPointee<0>(resource_rating_tier), Return(true))); @@ -450,7 +450,7 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidationV15) { Return(CdmResponseType(NO_ERROR)))); EXPECT_CALL(*crypto_session_, GetSupportedCertificateTypes(NotNull())); EXPECT_CALL(*crypto_session_, GetApiVersion(NotNull())) - .WillOnce( + .WillRepeatedly( DoAll(SetArgPointee<0>(crypto_session_api_version), Return(true))); EXPECT_CALL(*crypto_session_, GetResourceRatingTier(NotNull())) .WillOnce(DoAll(SetArgPointee<0>(resource_rating_tier), Return(true))); @@ -617,7 +617,7 @@ TEST_P(CdmLicenseEntitledKeyTest, LoadsEntitledKeys) { // Set up the CdmLicense with the mocks and fake entitlement key CreateCdmLicense(); - EXPECT_TRUE(cdm_license_->Init(true, kDefaultServiceCertificate, + ASSERT_TRUE(cdm_license_->Init(true, kDefaultServiceCertificate, crypto_session_, policy_engine_)); cdm_license_->set_entitlement_keys(entitlement_license); diff --git a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h index a3097ee8..80be4662 100644 --- a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h +++ b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h @@ -1025,6 +1025,9 @@ OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, * Refer to the Signing Messages Sent to a Server section above for more * details about the signature algorithm. * + * Starting in OEMCrypto v19, the |message| buffer must be hashed using SHA512 + * before signing. + * * NOTE: if signature pointer is null and/or input signature_length is zero, * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output * signature_length to the size needed to receive the output signature. diff --git a/libwvdrmengine/oemcrypto/test/oec_session_util.cpp b/libwvdrmengine/oemcrypto/test/oec_session_util.cpp index 31265aaa..5620e94a 100644 --- a/libwvdrmengine/oemcrypto/test/oec_session_util.cpp +++ b/libwvdrmengine/oemcrypto/test/oec_session_util.cpp @@ -782,17 +782,18 @@ void LicenseRoundTrip::VerifyRequestSignature( if (api_version_ > global_features.api_version) api_version_ = global_features.api_version; + vector sign_source; if (global_features.api_version < 17) { - const std::vector subdata(data.begin() + core_message_length, - data.end()); - session()->VerifyRsaSignature(subdata, generated_signature.data(), - generated_signature.size(), kSign_RSASSA_PSS); - SHA256(data.data(), core_message_length, request_hash_); + sign_source.assign(data.begin() + core_message_length, data.end()); + } else if (global_features.api_version < 19) { + sign_source = data; } else { - session()->VerifySignature(data, generated_signature.data(), - generated_signature.size(), kSign_RSASSA_PSS); - SHA256(data.data(), core_message_length, request_hash_); + sign_source.resize(SHA512_DIGEST_LENGTH); + SHA512(data.data(), data.size(), sign_source.data()); } + session()->VerifySignature(sign_source, generated_signature.data(), + generated_signature.size(), kSign_RSASSA_PSS); + SHA256(data.data(), core_message_length, request_hash_); } void LicenseRoundTrip::FillAndVerifyCoreRequest( @@ -998,7 +999,8 @@ void LicenseRoundTrip::FillCoreResponseSubstrings() { } void LicenseRoundTrip::EncryptResponse(bool force_clear_kcb) { - ASSERT_NO_FATAL_FAILURE(session_->GenerateDerivedKeysFromSessionKey()); + const auto context = session_->GetDefaultContext(!skip_request_hash_); + ASSERT_NO_FATAL_FAILURE(session_->GenerateDerivedKeysFromSessionKey(context)); encrypted_response_data_ = response_data_; uint8_t iv_buffer[KEY_IV_SIZE]; memcpy(iv_buffer, &response_data_.mac_key_iv[0], KEY_IV_SIZE); @@ -1129,7 +1131,8 @@ OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session, core_response_.key_array_length * sizeof(*core_response_.key_array)); } - const vector context = session->GetDefaultContext(); + const vector context = + session->GetDefaultContext(!skip_request_hash_); // Some tests adjust the offset to be beyond the length of the message. Here, // we create a duplicate of the main message buffer so that these offsets do @@ -1674,18 +1677,24 @@ void Session::GenerateNonce(int* error_counter) { } } -vector Session::GetDefaultContext() { +vector Session::GetDefaultContext(bool do_hash) { /* Context string * This context string is normally created by the CDM layer * from a license request message. * They are used to test MAC and ENC key generation. */ - return wvutil::a2b_hex( + auto ret = wvutil::a2b_hex( "0a4c08001248000000020000101907d9ffde13aa95c122678053362136bdf840" "8f8276e4c2d87ec52b61aa1b9f646e58734930acebe899b3e464189a14a87202" "fb02574e70640bd22ef44b2d7e3912250a230a14080112100915007caa9b5931" "b76a3a85f046523e10011a09393837363534333231180120002a0c3138383637" "38373430350000"); + if (do_hash) { + uint8_t hash[SHA512_DIGEST_LENGTH]; + SHA512(ret.data(), ret.size(), hash); + ret.assign(hash, hash + sizeof(hash)); + } + return ret; } // This should only be called if the device uses Provisioning 2.0. A failure in diff --git a/libwvdrmengine/oemcrypto/test/oec_session_util.h b/libwvdrmengine/oemcrypto/test/oec_session_util.h index f5764911..9025ef96 100644 --- a/libwvdrmengine/oemcrypto/test/oec_session_util.h +++ b/libwvdrmengine/oemcrypto/test/oec_session_util.h @@ -444,6 +444,7 @@ class LicenseRoundTrip update_mac_keys_(true), api_version_(kCurrentAPI), expect_request_has_correct_nonce_(true), + skip_request_hash_(global_features.api_version < 19), license_type_(OEMCrypto_ContentLicense), request_hash_() {} void CreateDefaultResponse() override; @@ -518,6 +519,8 @@ class LicenseRoundTrip } // Skip the nonce check when verifying the license request. void skip_nonce_check() { expect_request_has_correct_nonce_ = false; } + // Skip hashing license request before signing/KDF. + void skip_request_hash() { skip_request_hash_ = true; } // This sets the key id of the specified key to the specified string. // This is used to test with different key id lengths. void SetKeyId(size_t index, const string& key_id); @@ -549,6 +552,9 @@ class LicenseRoundTrip // session. This is usually true, but when we are testing how OEMCrypto // handles a bad nonce, we don't want to. bool expect_request_has_correct_nonce_; + // Whether to skip hashing the request before signing and KDF; this is used + // for license protocol 2.2. + bool skip_request_hash_; // Whether this is a content license or an entitlement license. Used in // CreateDefaultResponse. OEMCrypto_LicenseType license_type_; @@ -674,7 +680,7 @@ class Session { // not null, it will be incremented when a nonce flood is detected. void GenerateNonce(int* error_counter = nullptr); // Fill the vector with test context which generate known mac and enc keys. - std::vector GetDefaultContext(); + std::vector GetDefaultContext(bool do_hash = false); // Generate known mac and enc keys using OEMCrypto_GenerateDerivedKeys and // also fill out enc_key_, mac_key_server_, and mac_key_client_. void GenerateDerivedKeysFromKeybox(const wvoec::WidevineKeybox& keybox); diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.cpp index e089b96c..f99287ff 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.cpp @@ -457,6 +457,8 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadVerification) { // This test verifies that LoadKeys still works when the message is not aligned // in memory on a word (2 or 4 byte) boundary. TEST_P(OEMCryptoLicenseTest, LoadKeyUnalignedMessageAPI16) { + license_messages_.skip_request_hash(); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());