diff --git a/libwvdrmengine/cdm/core/include/initialization_data.h b/libwvdrmengine/cdm/core/include/initialization_data.h index 4252c1fb..03d55f05 100644 --- a/libwvdrmengine/cdm/core/include/initialization_data.h +++ b/libwvdrmengine/cdm/core/include/initialization_data.h @@ -34,6 +34,7 @@ class InitializationData { CdmHlsMethod hls_method() const { return hls_method_; } std::vector ExtractWrappedKeys() const; + bool contains_entitled_keys() const { return contains_entitled_keys_; } private: bool SelectWidevinePssh(const CdmInitData& init_data, @@ -86,6 +87,7 @@ class InitializationData { bool is_hls_; bool is_webm_; bool is_audio_; + bool contains_entitled_keys_; std::vector hls_iv_; CdmHlsMethod hls_method_; diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index af01ec3a..99ba1954 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -438,8 +438,13 @@ CdmResponseType CdmSession::GenerateKeyRequestInternal( if (is_release_) { return GenerateReleaseRequest(key_request); - } else if (license_received_) { // renewal - return GenerateRenewalRequest(key_request); + } else if (license_received_) { + // A call to GenerateKeyRequest after the initial license has been received + // is either a renewal request or a key rotation event + if (init_data.contains_entitled_keys()) + return license_parser_->HandleEmbeddedKeyData(init_data); + else + return GenerateRenewalRequest(key_request); } else { key_request->type = kKeyRequestTypeInitial; diff --git a/libwvdrmengine/cdm/core/src/initialization_data.cpp b/libwvdrmengine/cdm/core/src/initialization_data.cpp index fa5ae422..d99cee51 100644 --- a/libwvdrmengine/cdm/core/src/initialization_data.cpp +++ b/libwvdrmengine/cdm/core/src/initialization_data.cpp @@ -56,6 +56,7 @@ InitializationData::InitializationData(const std::string& type, is_hls_(false), is_webm_(false), is_audio_(false), + contains_entitled_keys_(false), hls_method_(kHlsMethodNone) { if (type == ISO_BMFF_VIDEO_MIME_TYPE || type == ISO_BMFF_AUDIO_MIME_TYPE || type == CENC_INIT_DATA_FORMAT) { @@ -138,12 +139,19 @@ bool InitializationData::SelectWidevinePssh(const CdmInitData& init_data, continue; } if (pssh.type() == WidevinePsshData_Type_ENTITLED_KEY) { + contains_entitled_keys_ = true; *output = pssh_payloads[i]; return true; } } } + WidevinePsshData pssh; + if (prefer_entitlements && pssh.ParseFromString(pssh_payloads[0])) { + if (pssh.type() == WidevinePsshData_Type_ENTITLED_KEY) + contains_entitled_keys_ = true; + } + // Either there is only one PSSH, this device does not prefer entitlements, // or no entitlement PSSH was found. Regardless of how we got here, select the // first PSSH, which should be a |SINGLE| PSSH. diff --git a/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp b/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp index b67d6d95..5ecfcef0 100644 --- a/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/initialization_data_unittest.cpp @@ -524,12 +524,14 @@ TEST_F(InitializationDataTest, HandlesMultipleWidevinePsshs) { kOemCryptoWithoutEntitlements); EXPECT_FALSE(single_init_data.IsEmpty()); EXPECT_EQ(kSingleKeyWidevinePsshBoxData, single_init_data.data()); + EXPECT_FALSE(single_init_data.contains_entitled_keys()); InitializationData entitled_init_data(ISO_BMFF_VIDEO_MIME_TYPE, kMultipleWidevinePsshBox, kOemCryptoWithEntitlements); EXPECT_FALSE(entitled_init_data.IsEmpty()); EXPECT_EQ(kEntitledKeysWidevinePsshBoxData, entitled_init_data.data()); + EXPECT_TRUE(entitled_init_data.contains_entitled_keys()); } TEST_P(HlsKeyFormatVersionsExtractionTest, ExtractKeyFormatVersions) { diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index 0eb867b2..9c0f3897 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -446,6 +446,54 @@ SubSampleInfo usage_info_sub_samples_icp[] = { wvcdm::a2b_hex("964c2dfda920357c668308d52d33c652"), 0, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample}}; +SubSampleInfo entitlement_with_key_rotation_sub_sample[] = { + { + true, 1, true, true, false, + wvcdm::a2bs_hex("1D9B8E13B59951169348FF8D5B9394C0"), + wvcdm::a2b_hex( + "64ab17b3e3dfab47245c7cce4543d4fc7a26dcf248f19f9b59f3c92601440b36" + "17c8ed0c96c656549e461f38708cd47a434066f8df28ccc28b79252eee3f9c2d" + "7f6c68ebe40141fe818fe082ca523c03d69ddaf183a93c022327fedc5582c5ab" + "ca9d342b71263a67f9cb2336f12108aaaef464f17177e44e9b0c4e56e61da53c" + "2150b4405cc82d994dfd9bf4087c761956d6688a9705db4cf350381085f383c4" + "9666d4aed135c519c1f0b5cba06e287feea96ea367bf54e7368dcf998276c6e4" + "6497e0c50e20fef74e42cb518fe7f22ef27202428688f86404e8278587017012" + "c1d65537c6cbd7dde04aae338d68115a9f430afc100ab83cdadf45dca39db685"), + wvcdm::a2b_hex( + "4bb2ff540e12e4c97248b63abdf30c4474df11ae8f22ba587e5aa9b64d51f8ce" + "209b13cb24f436ac192060690d13d5a1230fe5207287678a3acbaf59b5381186" + "92dcdec42c770afc0545407c243a452214d0497f1a044adc56ac1dba5530d5a5" + "482f9fc67a5e1d1314e864ad85fec9f78657e10f68ae8720b218339c96e878c1" + "c0f09015172d8a52a85b6f09526b98aad6d7326d3799a418581efadd16f9ba3e" + "454945428a36959a296aa14fe05cb8ae7b44ae68d82950f0742d38d86f167c36" + "75e75390d3cc6cd6db267729b2aa81a7e7c4db186e82d4300c4123c0a5de73e9" + "a6bb238bd351769359d1b46c9702270b756038fd54ef609d985eecde58e9a58e"), + wvcdm::a2b_hex("f6f4b1e600a5b67813ed2bded913ba9f"), 0, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample}, + { + true, 1, true, true, false, + wvcdm::a2bs_hex("A0396729B3795B46A5EA7F9919B96A67"), + wvcdm::a2b_hex( + "64ab17b3e3dfab47245c7cce4543d4fc7a26dcf248f19f9b59f3c92601440b36" + "17c8ed0c96c656549e461f38708cd47a434066f8df28ccc28b79252eee3f9c2d" + "7f6c68ebe40141fe818fe082ca523c03d69ddaf183a93c022327fedc5582c5ab" + "ca9d342b71263a67f9cb2336f12108aaaef464f17177e44e9b0c4e56e61da53c" + "2150b4405cc82d994dfd9bf4087c761956d6688a9705db4cf350381085f383c4" + "9666d4aed135c519c1f0b5cba06e287feea96ea367bf54e7368dcf998276c6e4" + "6497e0c50e20fef74e42cb518fe7f22ef27202428688f86404e8278587017012" + "c1d65537c6cbd7dde04aae338d68115a9f430afc100ab83cdadf45dca39db685"), + wvcdm::a2b_hex( + "49067453f9fe1b36fb2b37a2bba927bfe7f5f81ce715047beb99675da809b502" + "a5638f891cfad95c9fefdb32b6e8614ce3d5528032d51644a6cfaaccea2ad0b6" + "dd8bd36a0fb751bf4b70e1cb02266f373be467d3167aed4f3820eb4af884cc39" + "f60f83e060c8674b7e53d7ec8934ec07750d4677ed14ad6f6bacf46f46cf3ea8" + "560f704220bc3e32b9ad21c74aff6dbdbd64f49f38717ab7a05042dfe6fdc56f" + "47ddc384822b9a3fab3445653fa51f2405fcbcfd6d39a7fb8a99777c41960b94" + "74f1deb4b9b242bef609c625af791cba63e8c184b0312d624daba3889307b48b" + "00c362f246c3bc0b36cd41f9ec8eb72eab603f9517c7948f5e317a93ac1a5631"), + wvcdm::a2b_hex("f6f4b1e600a5b67813ed2bded913ba9f"), 0, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample}}; + // License duration and uncertainty window const uint32_t kSingleEncryptedSubSampleIcpLicenseDurationExpiration = 5; const uint32_t kSingleEncryptedSubSampleIcpLicenseExpirationWindow = 2; @@ -530,6 +578,83 @@ std::string kPsshStreamingClip21 = wvcdm::a2bs_hex( "EDEF8BA979D64ACEA3C827DCD51D21ED00000023" // Widevine system id "08011a0d7769646576696e655f746573" // pssh data "74221073747265616d696e675f636c69703231"); +const std::string kPsshEntitlementWithKeyRotation[] = { + wvcdm::a2bs_hex( + "0000003e7073736800000000" // blob size and pssh + "edef8ba979d64acea3c827dcd51d21ed0000001e" // Widevine system id + "2210426f6f5261646c6579666137613664393800" // pssh data + "48e3dc959b0650126a00000002027073" + "736800000000edef8ba979d64acea3c8" + "27dcd51d21ed000001e22210426f6f52" + "61646c657966613761366439380048e3" + "dc959b06501258026a0072580a109ab3" + "fb7eaffc5ee087568b9581ff0e721210" + "4a2da018480c51358c660c4b1a198721" + "1a20a00425ef5fe538adba59a6c4ce9a" + "fbff394351b79552643e08ed3a0cde2c" + "3ca42210931a2ef95b898d804b9a25ef" + "6d2805f072580a100fba9bd0f8e055d5" + "900bbfaabf5033141210f9bfca8cd3b8" + "593897f0b9283cd768f61a209c1d9a96" + "573a4dbba68327f03e598adfde1bd29f" + "1e709f206c2424d82ca14c1c22108428" + "46cffba58c12742418a702138c3a7258" + "0a109fec6ad1d36e56cdaf5c232480f6" + "3f8812101d9b8e13b59951169348ff8d" + "5b9394c01a2058ee6164ae759802e0b1" + "f9f7eeeaa3606faf21182777fe716c01" + "4c2412621543221039c8a6caab12515e" + "3c744a5447e9b72372580a10cd2e9ebf" + "89c257e2a3196c82ac4c76ba1210d48f" + "8f11c2d853289e981f5775db60441a20" + "3b491980b27587592c86e73da27caa12" + "d95acda2295f768746090b55b81c9d61" + "2210dd7f29944aaeb08826ba4fc0dec6" + "c88d72580a10b3fd79204b9d5cbf8f67" + "2eca27255e9e121023feaaf517585fde" + "bdf0d4693f32c1091a20d18a1c0e54b8" + "fbc189b58ccc0dc5a0c541b628b8fe23" + "34c862944b5555ad7f7f221091269127" + "e4feb6a8fa878c6e9781a55f"), + wvcdm::a2bs_hex( + "0000003e7073736800000000ed" // blob size and pssh + "ef8ba979d64acea3c827dcd51d21ed0000001e" // Widevine system id + "2210426f6f5261646c65796661" // pssh data + "3761366439380448e3dc959b0650126a" + "00000002027073736800000000edef8b" + "a979d64acea3c827dcd51d21ed000001" + "e22210426f6f5261646c657966613761" + "366439380448e3dc959b06501258026a" + "0072580a109ab3fb7eaffc5ee087568b" + "9581ff0e721210777bc13c94c4568b84" + "ddaf99de3f03311a2042949649205d0d" + "f4f4a4eec94263561931066a9fc18b61" + "8b3929a168b4c2404222101e7aad142f" + "59edb962816e8d0702356b72580a100f" + "ba9bd0f8e055d5900bbfaabf50331412" + "10537cb8f017ec59e7be3c309e6d7f4b" + "5d1a2089c49c0f0e214c3765cbb37ab5" + "41dc0625c0087fa94317528ee8265431" + "71cabe2210dddba401d4eaaa76437638" + "a29dbdc38472580a109fec6ad1d36e56" + "cdaf5c232480f63f881210a0396729b3" + "795b46a5ea7f9919b96a671a209245a1" + "c821de9c65fb1086d9af8aaca4b7da0e" + "bbd0850a56ab36c23bb71b507e2210b0" + "531d2235575ff9ba5af4545bb43fdd72" + "580a10cd2e9ebf89c257e2a3196c82ac" + "4c76ba121011647820b33352349942cd" + "31ce352e571a20277b4392c76a04335d" + "3eadf22705184eb1adc057a61e372e78" + "30b7a20361d2472210d954557dc853a7" + "42283e6dfe16677a6a72580a10b3fd79" + "204b9d5cbf8f672eca27255e9e1210c4" + "78fb09c0c6531b92c571972c36098b1a" + "20bf17c678ac01685e258192eb4d2d49" + "157c3a07a95342d8be8b2f9f121f596b" + "8622100d9cfe972bc17003b49ecd5f45" + "f3bb28") +}; std::string kProviderSessionTokenStreamingClip3 = wvcdm::a2bs_hex( "4851305A4A4156485A554936444E4931"); @@ -1513,6 +1638,22 @@ class WvCdmRequestLicenseTest : public WvCdmTestBase { EXPECT_NE(0u, key_request.url.size()); } + bool KeyRotationRequest(CdmLicenseType license_type, + const std::string& init_data) { + wvcdm::CdmAppParameterMap app_parameters; + CdmKeyRequest key_request; + CdmResponseType status = + decryptor_.GenerateKeyRequest( + session_id_, key_set_id_, "video/mp4", init_data, + license_type, app_parameters, NULL, kDefaultCdmIdentifier, + &key_request); + EXPECT_EQ(wvcdm::KEY_ADDED, status); + EXPECT_TRUE(key_request.message.empty()); + EXPECT_TRUE(key_request.url.empty()); + return wvcdm::KEY_ADDED == status && key_request.message.empty() + && key_request.url.empty(); + } + void GenerateKeyRelease(CdmKeySetId key_set_id) { GenerateKeyRelease(key_set_id, NULL, NULL); } @@ -2739,6 +2880,29 @@ TEST_F(WvCdmRequestLicenseTest, OfflineLicenseRenewalAndRelease) { VerifyKeyRequestResponse(config_.license_server(), client_auth); } +TEST_F(WvCdmRequestLicenseTest, EntitlementWithKeyRotation) { + decryptor_.OpenSession(config_.key_system(), NULL, kDefaultCdmIdentifier, + NULL, &session_id_); + + // Fetch entitlement license + GenerateKeyRequest(kPsshEntitlementWithKeyRotation[0], kLicenseTypeStreaming); + VerifyKeyRequestResponse(config_.license_server(), config_.client_auth()); + + // Verify that we can decrypt a subsample + ASSERT_TRUE(VerifyDecryption(session_id_, + entitlement_with_key_rotation_sub_sample[0])); + + // Key rotation + ASSERT_TRUE(KeyRotationRequest(kLicenseTypeStreaming, + kPsshEntitlementWithKeyRotation[1])); + + // Verify that we can decrypt a subsample + ASSERT_TRUE(VerifyDecryption(session_id_, + entitlement_with_key_rotation_sub_sample[1])); + + decryptor_.CloseSession(session_id_); +} + TEST_F(WvCdmRequestLicenseTest, RemoveKeys) { ASSERT_EQ(NO_ERROR, decryptor_.OpenSession(config_.key_system(), NULL, kDefaultCdmIdentifier, NULL,