From fcfc843a2e73000791068f22a2622c9b4e3114a8 Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Tue, 12 Feb 2019 16:34:24 -0800 Subject: [PATCH] Use IV from Widevine key server if available Fixes #555. Change-Id: Ic077f97884a0c3d26159cd44791298bdd400068d --- packager/media/base/widevine_key_source.cc | 1 + .../base/widevine_key_source_unittest.cc | 100 ++++++++++++------ 2 files changed, 71 insertions(+), 30 deletions(-) diff --git a/packager/media/base/widevine_key_source.cc b/packager/media/base/widevine_key_source.cc index 9a852f4a95..00dbe3b63d 100644 --- a/packager/media/base/widevine_key_source.cc +++ b/packager/media/base/widevine_key_source.cc @@ -453,6 +453,7 @@ bool WidevineKeySource::ExtractEncryptionKey( if (!widevine_classic) { encryption_key->key_id.assign(track.key_id().begin(), track.key_id().end()); + encryption_key->iv.assign(track.iv().begin(), track.iv().end()); if (generate_widevine_protection_system_) { if (track.pssh_size() != 1) { diff --git a/packager/media/base/widevine_key_source_unittest.cc b/packager/media/base/widevine_key_source_unittest.cc index a883d04772..8799f96839 100644 --- a/packager/media/base/widevine_key_source_unittest.cc +++ b/packager/media/base/widevine_key_source_unittest.cc @@ -34,6 +34,9 @@ using ::testing::WithParamInterface; namespace shaka { namespace media { namespace { +const bool kClassic = true; +const bool kHasIv = true; + const char kServerUrl[] = "http://www.foo.com/getcontentkey"; const char kContentId[] = "ContentFoo"; const char kPolicy[] = "PolicyFoo"; @@ -73,6 +76,9 @@ const char kExpectedSignedMessageFormat[] = R"({"request":"%s","signature":"%s","signer":"%s"})"; const char kTrackFormat[] = R"({"type":"%s","key_id":"%s","key":"%s",)" R"("pssh":[{"drm_type":"WIDEVINE","data":"%s"}]})"; +const char kTrackFormatWithIv[] = + R"({"type":"%s","key_id":"%s","key":"%s","iv":"%s",)" + R"("pssh":[{"drm_type":"WIDEVINE","data":"%s"}]})"; const char kTrackFormatWithBoxes[] = R"({"type":"%s","key_id":"%s","key":"%s",)" R"("pssh":[{"drm_type":"WIDEVINE","data":"%s","boxes":"%s"}]})"; @@ -119,6 +125,13 @@ std::string GetMockKey(const std::string& track_type) { return key; } +std::string GetMockIv(const std::string& track_type) { + // IV must be 16 characters. + std::string iv = "MockIv" + track_type; + iv.resize(16, '~'); + return iv; +} + std::string GetMockPsshData() { return kRequestPsshData; } @@ -126,14 +139,14 @@ std::string GetMockPsshData() { std::string GenerateMockLicenseResponseWithBoxes(const std::string& boxes) { const std::string kTrackTypes[] = {"SD", "HD", "UHD1", "UHD2", "AUDIO"}; std::string tracks; - for (size_t i = 0; i < 5; ++i) { + for (const std::string& track_type : kTrackTypes) { if (!tracks.empty()) tracks += ","; - tracks += base::StringPrintf( - kTrackFormatWithBoxes, kTrackTypes[i].c_str(), - Base64Encode(GetMockKeyId(kTrackTypes[i])).c_str(), - Base64Encode(GetMockKey(kTrackTypes[i])).c_str(), - Base64Encode(GetMockPsshData()).c_str(), boxes.c_str()); + tracks += base::StringPrintf(kTrackFormatWithBoxes, track_type.c_str(), + Base64Encode(GetMockKeyId(track_type)).c_str(), + Base64Encode(GetMockKey(track_type)).c_str(), + Base64Encode(GetMockPsshData()).c_str(), + boxes.c_str()); } return base::StringPrintf(kLicenseResponseFormat, "OK", tracks.c_str()); } @@ -141,14 +154,28 @@ std::string GenerateMockLicenseResponseWithBoxes(const std::string& boxes) { std::string GenerateMockLicenseResponse() { const std::string kTrackTypes[] = {"SD", "HD", "UHD1", "UHD2", "AUDIO"}; std::string tracks; - for (size_t i = 0; i < 5; ++i) { + for (const std::string& track_type : kTrackTypes) { if (!tracks.empty()) tracks += ","; - tracks += - base::StringPrintf(kTrackFormat, kTrackTypes[i].c_str(), - Base64Encode(GetMockKeyId(kTrackTypes[i])).c_str(), - Base64Encode(GetMockKey(kTrackTypes[i])).c_str(), - Base64Encode(GetMockPsshData()).c_str()); + tracks += base::StringPrintf(kTrackFormat, track_type.c_str(), + Base64Encode(GetMockKeyId(track_type)).c_str(), + Base64Encode(GetMockKey(track_type)).c_str(), + Base64Encode(GetMockPsshData()).c_str()); + } + return base::StringPrintf(kLicenseResponseFormat, "OK", tracks.c_str()); +} + +std::string GenerateMockLicenseResponseWithIv() { + const std::string kTrackTypes[] = {"SD", "HD", "UHD1", "UHD2", "AUDIO"}; + std::string tracks; + for (const std::string& track_type : kTrackTypes) { + if (!tracks.empty()) + tracks += ","; + tracks += base::StringPrintf(kTrackFormatWithIv, track_type.c_str(), + Base64Encode(GetMockKeyId(track_type)).c_str(), + Base64Encode(GetMockKey(track_type)).c_str(), + Base64Encode(GetMockIv(track_type)).c_str(), + Base64Encode(GetMockPsshData()).c_str()); } return base::StringPrintf(kLicenseResponseFormat, "OK", tracks.c_str()); } @@ -156,13 +183,11 @@ std::string GenerateMockLicenseResponse() { std::string GenerateMockClassicLicenseResponse() { const std::string kTrackTypes[] = {"SD", "HD", "UHD1", "UHD2", "AUDIO"}; std::string tracks; - for (size_t i = 0; i < 5; ++i) { + for (const std::string& track_type : kTrackTypes) { if (!tracks.empty()) tracks += ","; - tracks += base::StringPrintf( - kClassicTrackFormat, - kTrackTypes[i].c_str(), - Base64Encode(GetMockKey(kTrackTypes[i])).c_str()); + tracks += base::StringPrintf(kClassicTrackFormat, track_type.c_str(), + Base64Encode(GetMockKey(track_type)).c_str()); } return base::StringPrintf(kLicenseResponseFormat, "OK", tracks.c_str()); } @@ -237,7 +262,7 @@ class WidevineKeySourceTest : public Test { widevine_key_source_->set_key_fetcher(std::move(mock_key_fetcher_)); } - void VerifyKeys(bool classic) { + void VerifyKeys(bool classic, bool has_iv) { EncryptionKey encryption_key; const std::string kStreamLabels[] = {"SD", "HD", "UHD1", "UHD2", "AUDIO"}; for (const std::string& stream_label : kStreamLabels) { @@ -248,6 +273,10 @@ class WidevineKeySourceTest : public Test { (add_widevine_pssh_ && add_common_pssh_) ? 2 : 1; ASSERT_EQ(num_key_system_info, encryption_key.key_system_info.size()); EXPECT_EQ(GetMockKeyId(stream_label), ToString(encryption_key.key_id)); + if (has_iv) + EXPECT_EQ(GetMockIv(stream_label), ToString(encryption_key.iv)); + else + EXPECT_TRUE(encryption_key.iv.empty()); auto key_system_info_iter = encryption_key.key_system_info.begin(); @@ -307,7 +336,7 @@ TEST_F(WidevineKeySourceTest, RetryOnHttpTimeout) { CreateWidevineKeySource(); ASSERT_OK(widevine_key_source_->FetchKeys(content_id_, kPolicy)); - VerifyKeys(false); + VerifyKeys(!kClassic, !kHasIv); } TEST_F(WidevineKeySourceTest, RetryOnTransientError) { @@ -327,7 +356,7 @@ TEST_F(WidevineKeySourceTest, RetryOnTransientError) { CreateWidevineKeySource(); ASSERT_OK(widevine_key_source_->FetchKeys(content_id_, kPolicy)); - VerifyKeys(false); + VerifyKeys(!kClassic, !kHasIv); } TEST_F(WidevineKeySourceTest, NoRetryOnUnknownError) { @@ -344,6 +373,19 @@ TEST_F(WidevineKeySourceTest, NoRetryOnUnknownError) { widevine_key_source_->FetchKeys(content_id_, kPolicy).error_code()); } +TEST_F(WidevineKeySourceTest, CheckIv) { + std::string mock_response = base::StringPrintf( + kHttpResponseFormat, + Base64Encode(GenerateMockLicenseResponseWithIv()).c_str()); + + EXPECT_CALL(*mock_key_fetcher_, FetchKeys(_, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK))); + + CreateWidevineKeySource(); + ASSERT_OK(widevine_key_source_->FetchKeys(content_id_, kPolicy)); + VerifyKeys(!kClassic, kHasIv); +} + TEST_F(WidevineKeySourceTest, BoxesInResponse) { const char kMockBoxes[] = "mock_pssh_boxes"; std::string mock_response = base::StringPrintf( @@ -410,7 +452,7 @@ TEST_P(WidevineKeySourceParameterizedTest, LicenseStatusCencOK) { CreateWidevineKeySource(); ASSERT_OK(widevine_key_source_->FetchKeys(content_id_, kPolicy)); - VerifyKeys(false); + VerifyKeys(!kClassic, !kHasIv); } TEST_P(WidevineKeySourceParameterizedTest, LicenseStatusCencMalformedResponse) { @@ -444,7 +486,7 @@ TEST_P(WidevineKeySourceParameterizedTest, LicenseStatusCencWithPsshBoxOK) { std::vector pssh_box(std::begin(kRequestPsshBox), std::end(kRequestPsshBox)); ASSERT_OK(widevine_key_source_->FetchKeys(EmeInitDataType::CENC, pssh_box)); - VerifyKeys(false); + VerifyKeys(!kClassic, !kHasIv); } TEST_P(WidevineKeySourceParameterizedTest, LicenseStatusCencWithKeyIdsOK) { @@ -467,7 +509,7 @@ TEST_P(WidevineKeySourceParameterizedTest, LicenseStatusCencWithKeyIdsOK) { std::vector key_id(std::begin(kRequestKeyId), std::end(kRequestKeyId)); ASSERT_OK(widevine_key_source_->FetchKeys(EmeInitDataType::WEBM, key_id)); - VerifyKeys(false); + VerifyKeys(!kClassic, !kHasIv); } TEST_P(WidevineKeySourceParameterizedTest, LicenseStatusClassicOK) { @@ -489,7 +531,7 @@ TEST_P(WidevineKeySourceParameterizedTest, LicenseStatusClassicOK) { EmeInitDataType::WIDEVINE_CLASSIC, std::vector(std::begin(kClassicAssetIdBytes), std::end(kClassicAssetIdBytes)))); - VerifyKeys(true); + VerifyKeys(kClassic, !kHasIv); } TEST_P(WidevineKeySourceParameterizedTest, VerifyEntitlementLicenseRequest) { @@ -539,15 +581,13 @@ std::string GenerateMockKeyRotationLicenseResponse( for (uint32_t index = initial_crypto_period_index; index < initial_crypto_period_index + crypto_period_count; ++index) { - for (size_t i = 0; i < 5; ++i) { + for (const std::string& track_type : kTrackTypes) { if (!tracks.empty()) tracks += ","; tracks += base::StringPrintf( - kCryptoPeriodTrackFormat, - kTrackTypes[i].c_str(), - Base64Encode(GetMockKeyId(kTrackTypes[i])).c_str(), - Base64Encode(GetMockKey(kTrackTypes[i], index)).c_str(), - index); + kCryptoPeriodTrackFormat, track_type.c_str(), + Base64Encode(GetMockKeyId(track_type)).c_str(), + Base64Encode(GetMockKey(track_type, index)).c_str(), index); } } return base::StringPrintf(kLicenseResponseFormat, "OK", tracks.c_str());