From f609b2947cbb756ef8decb9458158a04e4c0f48c Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Mon, 30 Jun 2014 08:40:02 -0700 Subject: [PATCH] Key Rotation: Get the initial crypto index from first request This change also updates the WidevineEncryptionKeySource to be able to support KeyRotation enabled and disabled requests simultaneously. Change-Id: I5178cafc0dbabbb64ac9af9969d3bf7d8117a4dd --- app/packager_util.cc | 3 +- media/base/widevine_encryption_key_source.cc | 114 +++++++++++------- media/base/widevine_encryption_key_source.h | 26 ++-- ...widevine_encryption_key_source_unittest.cc | 86 ++++--------- 4 files changed, 112 insertions(+), 117 deletions(-) diff --git a/app/packager_util.cc b/app/packager_util.cc index 6d4dfe5b48..1702c414a5 100644 --- a/app/packager_util.cc +++ b/app/packager_util.cc @@ -70,8 +70,7 @@ scoped_ptr CreateEncryptionKeySource() { FLAGS_key_server_url, FLAGS_content_id, FLAGS_policy, - signer.Pass(), - FLAGS_crypto_period_duration == 0 ? kDisableKeyRotation : 0)); + signer.Pass())); Status status = widevine_encryption_key_source->Initialize(); if (!status.ok()) { LOG(ERROR) << "Widevine encryption key source failed to initialize: " diff --git a/media/base/widevine_encryption_key_source.cc b/media/base/widevine_encryption_key_source.cc index 0e1c4efb13..4a6ed3cf24 100644 --- a/media/base/widevine_encryption_key_source.cc +++ b/media/base/widevine_encryption_key_source.cc @@ -14,6 +14,7 @@ #include "base/stl_util.h" #include "base/values.h" #include "media/base/http_fetcher.h" +#include "media/base/producer_consumer_queue.h" #include "media/base/request_signer.h" #define RCHECK(x) \ @@ -26,6 +27,8 @@ namespace { +const bool kEnableKeyRotation = true; + const char kLicenseStatusOK[] = "OK"; // Server may return INTERNAL_ERROR intermittently, which is a transient error // and the next client request may succeed without problem. @@ -128,50 +131,52 @@ WidevineEncryptionKeySource::WidevineEncryptionKeySource( const std::string& server_url, const std::string& content_id, const std::string& policy, - scoped_ptr signer, - int first_crypto_period_index) + scoped_ptr signer) : http_fetcher_(new SimpleHttpFetcher(kHttpTimeoutInSeconds)), server_url_(server_url), content_id_(content_id), policy_(policy), signer_(signer.Pass()), - key_rotation_enabled_(first_crypto_period_index >= 0), crypto_period_count_(kDefaultCryptoPeriodCount), - first_crypto_period_index_(first_crypto_period_index), + key_production_started_(false), + start_key_production_(false, false), + first_crypto_period_index_(0), key_production_thread_( "KeyProductionThread", base::Bind(&WidevineEncryptionKeySource::FetchKeysTask, - base::Unretained(this))), - key_pool_(kDefaultCryptoPeriodCount, - key_rotation_enabled_ ? first_crypto_period_index : 0) { + base::Unretained(this))) { DCHECK(signer_); } WidevineEncryptionKeySource::~WidevineEncryptionKeySource() { - key_pool_.Stop(); - if (key_production_thread_.HasBeenStarted()) + if (key_pool_) + key_pool_->Stop(); + if (key_production_thread_.HasBeenStarted()) { + // Signal the production thread to start key production if it is not + // signaled yet so the thread can be joined. + start_key_production_.Signal(); key_production_thread_.Join(); + } } Status WidevineEncryptionKeySource::Initialize() { - // |first_crypto_period_index| might be updated after starting production. - // Make a local copy for prime later. - const uint32 first_crypto_period_index = first_crypto_period_index_; DCHECK(!key_production_thread_.HasBeenStarted()); key_production_thread_.Start(); - // Perform a GetKey request to find out common encryption request status. - // It also primes the key_pool if successful. - return key_rotation_enabled_ ? GetCryptoPeriodKey(first_crypto_period_index, - TRACK_TYPE_SD, NULL) - : GetKey(TRACK_TYPE_SD, NULL); + // Perform a fetch request to find out if the key source is healthy. + // It also stores the keys fetched for consumption later. + return FetchKeys(!kEnableKeyRotation, 0); } Status WidevineEncryptionKeySource::GetKey(TrackType track_type, EncryptionKey* key) { - DCHECK(key_production_thread_.HasBeenStarted()); - DCHECK(!key_rotation_enabled_); - return GetKeyInternal(0u, track_type, key); + DCHECK(key); + if (encryption_key_map_.find(track_type) == encryption_key_map_.end()) { + return Status(error::INTERNAL_ERROR, + "Cannot find key of type " + TrackTypeToString(track_type)); + } + *key = *encryption_key_map_[track_type]; + return Status::OK; } Status WidevineEncryptionKeySource::GetCryptoPeriodKey( @@ -179,7 +184,20 @@ Status WidevineEncryptionKeySource::GetCryptoPeriodKey( TrackType track_type, EncryptionKey* key) { DCHECK(key_production_thread_.HasBeenStarted()); - DCHECK(key_rotation_enabled_); + // TODO(kqyang): This is not elegant. Consider refactoring later. + { + base::AutoLock scoped_lock(lock_); + if (!key_production_started_) { + // Another client may have a slightly smaller starting crypto period + // index. Set the initial value to account for that. + first_crypto_period_index_ = crypto_period_index ? crypto_period_index - 1 : 0; + DCHECK(!key_pool_); + key_pool_.reset(new EncryptionKeyQueue(crypto_period_count_, + first_crypto_period_index_)); + start_key_production_.Signal(); + key_production_started_ = true; + } + } return GetKeyInternal(crypto_period_index, track_type, key); } @@ -192,13 +210,15 @@ Status WidevineEncryptionKeySource::GetKeyInternal( uint32 crypto_period_index, TrackType track_type, EncryptionKey* key) { + DCHECK(key_pool_); + DCHECK(key); DCHECK_LE(track_type, NUM_VALID_TRACK_TYPES); DCHECK_NE(track_type, TRACK_TYPE_UNKNOWN); scoped_refptr ref_counted_encryption_key_map; - Status status = key_pool_.Peek(crypto_period_index, - &ref_counted_encryption_key_map, - kGetKeyTimeoutInSeconds * 1000); + Status status = + key_pool_->Peek(crypto_period_index, &ref_counted_encryption_key_map, + kGetKeyTimeoutInSeconds * 1000); if (!status.ok()) { if (status.error_code() == error::STOPPED) { CHECK(!common_encryption_request_status_.ok()); @@ -212,27 +232,30 @@ Status WidevineEncryptionKeySource::GetKeyInternal( return Status(error::INTERNAL_ERROR, "Cannot find key of type " + TrackTypeToString(track_type)); } - if (key) - *key = *encryption_key_map[track_type]; + *key = *encryption_key_map[track_type]; return Status::OK; } void WidevineEncryptionKeySource::FetchKeysTask() { - Status status = FetchKeys(first_crypto_period_index_); - if (key_rotation_enabled_) { - while (status.ok()) { - first_crypto_period_index_ += crypto_period_count_; - status = FetchKeys(first_crypto_period_index_); - } + // Wait until key production is signaled. + start_key_production_.Wait(); + if (!key_pool_ || key_pool_->Stopped()) + return; + + Status status = FetchKeys(kEnableKeyRotation, first_crypto_period_index_); + while (status.ok()) { + first_crypto_period_index_ += crypto_period_count_; + status = FetchKeys(kEnableKeyRotation, first_crypto_period_index_); } common_encryption_request_status_ = status; - key_pool_.Stop(); + key_pool_->Stop(); } Status WidevineEncryptionKeySource::FetchKeys( - uint32 first_crypto_period_index) { + bool enable_key_rotation, uint32 first_crypto_period_index) { std::string request; - FillRequest(content_id_, first_crypto_period_index, &request); + FillRequest(content_id_, enable_key_rotation, first_crypto_period_index, + &request); std::string message; Status status = SignRequest(request, &message); @@ -257,7 +280,7 @@ Status WidevineEncryptionKeySource::FetchKeys( } bool transient_error = false; - if (ExtractEncryptionKey(response, &transient_error)) + if (ExtractEncryptionKey(enable_key_rotation, response, &transient_error)) return Status::OK; if (!transient_error) { @@ -281,6 +304,7 @@ Status WidevineEncryptionKeySource::FetchKeys( } void WidevineEncryptionKeySource::FillRequest(const std::string& content_id, + bool enable_key_rotation, uint32 first_crypto_period_index, std::string* request) { DCHECK(request); @@ -313,7 +337,7 @@ void WidevineEncryptionKeySource::FillRequest(const std::string& content_id, request_dict.Set("drm_types", drm_types); // Build key rotation fields. - if (key_rotation_enabled_) { + if (enable_key_rotation) { request_dict.SetInteger("first_crypto_period_index", first_crypto_period_index); request_dict.SetInteger("crypto_period_count", crypto_period_count_); @@ -368,6 +392,7 @@ bool WidevineEncryptionKeySource::DecodeResponse( } bool WidevineEncryptionKeySource::ExtractEncryptionKey( + bool enable_key_rotation, const std::string& response, bool* transient_error) { DCHECK(transient_error); @@ -392,7 +417,7 @@ bool WidevineEncryptionKeySource::ExtractEncryptionKey( const base::ListValue* tracks; RCHECK(license_dict->GetList("tracks", &tracks)); - RCHECK(key_rotation_enabled_ + RCHECK(enable_key_rotation ? tracks->GetSize() >= NUM_VALID_TRACK_TYPES * crypto_period_count_ : tracks->GetSize() >= NUM_VALID_TRACK_TYPES); @@ -403,7 +428,7 @@ bool WidevineEncryptionKeySource::ExtractEncryptionKey( const base::DictionaryValue* track_dict; RCHECK(tracks->GetDictionary(i, &track_dict)); - if (key_rotation_enabled_) { + if (enable_key_rotation) { int crypto_period_index; RCHECK( track_dict->GetInteger("crypto_period_index", &crypto_period_index)); @@ -438,16 +463,21 @@ bool WidevineEncryptionKeySource::ExtractEncryptionKey( } DCHECK(!encryption_key_map.empty()); + if (!enable_key_rotation) { + encryption_key_map_ = encryption_key_map; + return true; + } return PushToKeyPool(&encryption_key_map); } bool WidevineEncryptionKeySource::PushToKeyPool( EncryptionKeyMap* encryption_key_map) { + DCHECK(key_pool_); DCHECK(encryption_key_map); Status status = - key_pool_.Push(scoped_refptr( - new RefCountedEncryptionKeyMap(encryption_key_map)), - kInfiniteTimeout); + key_pool_->Push(scoped_refptr( + new RefCountedEncryptionKeyMap(encryption_key_map)), + kInfiniteTimeout); encryption_key_map->clear(); if (!status.ok()) { DCHECK_EQ(error::STOPPED, status.error_code()); diff --git a/media/base/widevine_encryption_key_source.h b/media/base/widevine_encryption_key_source.h index 5fc1788471..8c0f4b2677 100644 --- a/media/base/widevine_encryption_key_source.h +++ b/media/base/widevine_encryption_key_source.h @@ -11,16 +11,14 @@ #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" +#include "base/synchronization/waitable_event.h" #include "media/base/closure_thread.h" #include "media/base/encryption_key_source.h" -#include "media/base/producer_consumer_queue.h" namespace media { -/// A negative crypto period index disables key rotation. -static const int kDisableKeyRotation = -1; - class HttpFetcher; class RequestSigner; +template class ProducerConsumerQueue; /// WidevineEncryptionKeySource talks to the Widevine encryption service to /// acquire the encryption keys. @@ -30,13 +28,10 @@ class WidevineEncryptionKeySource : public EncryptionKeySource { /// @param content_id the unique id identify the content to be encrypted. /// @param policy specifies the DRM content rights. /// @param signer signs the request message. It should not be NULL. - /// @param first_crypto_period_index indicates the starting crypto period - /// index. Set it to kDisableKeyRotation to disable key rotation. WidevineEncryptionKeySource(const std::string& server_url, const std::string& content_id, const std::string& policy, - scoped_ptr signer, - int first_crypto_period_index); + scoped_ptr signer); virtual ~WidevineEncryptionKeySource(); /// Initialize the key source. Must be called before calling GetKey or @@ -59,6 +54,8 @@ class WidevineEncryptionKeySource : public EncryptionKeySource { private: typedef std::map EncryptionKeyMap; class RefCountedEncryptionKeyMap; + typedef ProducerConsumerQueue > + EncryptionKeyQueue; // Internal routine for getting keys. Status GetKeyInternal(uint32 crypto_period_index, @@ -69,11 +66,12 @@ class WidevineEncryptionKeySource : public EncryptionKeySource { void FetchKeysTask(); // Fetch keys from server. - Status FetchKeys(uint32 first_crypto_period_index); + Status FetchKeys(bool enable_key_rotation, uint32 first_crypto_period_index); // Fill |request| with necessary fields for Widevine encryption request. // |request| should not be NULL. void FillRequest(const std::string& content_id, + bool enable_key_rotation, uint32 first_crypto_period_index, std::string* request); // Sign and properly format |request|. @@ -86,7 +84,8 @@ class WidevineEncryptionKeySource : public EncryptionKeySource { // formatted. |transient_error| will be set to true if it fails and the // failure is because of a transient error from the server. |transient_error| // should not be NULL. - bool ExtractEncryptionKey(const std::string& response, bool* transient_error); + bool ExtractEncryptionKey(bool enable_key_rotation, + const std::string& response, bool* transient_error); // Push the keys to the key pool. bool PushToKeyPool(EncryptionKeyMap* encryption_key_map); @@ -99,11 +98,14 @@ class WidevineEncryptionKeySource : public EncryptionKeySource { std::string policy_; scoped_ptr signer_; - const bool key_rotation_enabled_; const uint32 crypto_period_count_; + base::Lock lock_; + bool key_production_started_; + base::WaitableEvent start_key_production_; uint32 first_crypto_period_index_; ClosureThread key_production_thread_; - ProducerConsumerQueue > key_pool_; + scoped_ptr key_pool_; + EncryptionKeyMap encryption_key_map_; // For non key rotation request. Status common_encryption_request_status_; DISALLOW_COPY_AND_ASSIGN(WidevineEncryptionKeySource); diff --git a/media/base/widevine_encryption_key_source_unittest.cc b/media/base/widevine_encryption_key_source_unittest.cc index 1567bad3e9..ebb695ab2f 100644 --- a/media/base/widevine_encryption_key_source_unittest.cc +++ b/media/base/widevine_encryption_key_source_unittest.cc @@ -132,13 +132,12 @@ class WidevineEncryptionKeySourceTest : public ::testing::Test { mock_http_fetcher_(new MockHttpFetcher()) {} protected: - void CreateWidevineEncryptionKeySource(int first_crypto_period_index) { + void CreateWidevineEncryptionKeySource() { widevine_encryption_key_source_.reset(new WidevineEncryptionKeySource( kServerUrl, kContentId, kPolicy, - mock_request_signer_.PassAs(), - first_crypto_period_index)); + mock_request_signer_.PassAs())); widevine_encryption_key_source_->set_http_fetcher( mock_http_fetcher_.PassAs()); } @@ -180,15 +179,9 @@ TEST_F(WidevineEncryptionKeySourceTest, GenerateSignatureFailure) { EXPECT_CALL(*mock_request_signer_, GenerateSignature(_, _)) .WillOnce(Return(false)); - CreateWidevineEncryptionKeySource(kDisableKeyRotation); + CreateWidevineEncryptionKeySource(); ASSERT_EQ(Status(error::INTERNAL_ERROR, "Signature generation failed."), widevine_encryption_key_source_->Initialize()); - - // GetKey should return the same failure. - EncryptionKey encryption_key; - ASSERT_EQ(Status(error::INTERNAL_ERROR, "Signature generation failed."), - widevine_encryption_key_source_->GetKey( - EncryptionKeySource::TRACK_TYPE_SD, &encryption_key)); } // Check whether expected request message and post data was generated and @@ -208,15 +201,9 @@ TEST_F(WidevineEncryptionKeySourceTest, HttpPostFailure) { EXPECT_CALL(*mock_http_fetcher_, Post(kServerUrl, expected_post_data, _)) .WillOnce(Return(kMockStatus)); - CreateWidevineEncryptionKeySource(kDisableKeyRotation); + CreateWidevineEncryptionKeySource(); ASSERT_EQ(kMockStatus, widevine_encryption_key_source_->Initialize()); - - // GetKey should return the same failure. - EncryptionKey encryption_key; - ASSERT_EQ(kMockStatus, - widevine_encryption_key_source_->GetKey( - EncryptionKeySource::TRACK_TYPE_SD, &encryption_key)); } TEST_F(WidevineEncryptionKeySourceTest, LicenseStatusOK) { @@ -229,7 +216,7 @@ TEST_F(WidevineEncryptionKeySourceTest, LicenseStatusOK) { EXPECT_CALL(*mock_http_fetcher_, Post(_, _, _)) .WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK))); - CreateWidevineEncryptionKeySource(kDisableKeyRotation); + CreateWidevineEncryptionKeySource(); ASSERT_OK(widevine_encryption_key_source_->Initialize()); VerifyKeys(); } @@ -246,7 +233,7 @@ TEST_F(WidevineEncryptionKeySourceTest, RetryOnHttpTimeout) { .WillOnce(Return(Status(error::TIME_OUT, ""))) .WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK))); - CreateWidevineEncryptionKeySource(kDisableKeyRotation); + CreateWidevineEncryptionKeySource(); ASSERT_OK(widevine_encryption_key_source_->Initialize()); VerifyKeys(); } @@ -269,7 +256,7 @@ TEST_F(WidevineEncryptionKeySourceTest, RetryOnTransientError) { .WillOnce(DoAll(SetArgPointee<2>(expected_retried_response), Return(Status::OK))); - CreateWidevineEncryptionKeySource(kDisableKeyRotation); + CreateWidevineEncryptionKeySource(); ASSERT_OK(widevine_encryption_key_source_->Initialize()); VerifyKeys(); } @@ -286,7 +273,7 @@ TEST_F(WidevineEncryptionKeySourceTest, NoRetryOnUnknownError) { EXPECT_CALL(*mock_http_fetcher_, Post(_, _, _)) .WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK))); - CreateWidevineEncryptionKeySource(kDisableKeyRotation); + CreateWidevineEncryptionKeySource(); ASSERT_EQ(error::SERVER_ERROR, widevine_encryption_key_source_->Initialize().error_code()); } @@ -307,8 +294,8 @@ std::string GetMockKey(const std::string& track_type, uint32 index) { return "MockKey" + track_type + "@" + base::UintToString(index); } -std::string GenerateMockLicenseResponse(uint32 initial_crypto_period_index, - uint32 crypto_period_count) { +std::string GenerateMockKeyRotationLicenseResponse( + uint32 initial_crypto_period_index, uint32 crypto_period_count) { const std::string kTrackTypes[] = {"SD", "HD", "AUDIO"}; std::string tracks; for (uint32 index = initial_crypto_period_index; @@ -340,9 +327,18 @@ TEST_F(WidevineEncryptionKeySourceTest, KeyRotationTest) { // Generate expectations in sequence. InSequence dummy; + + // Expecting a non-key rotation enabled request on Initialize(). + EXPECT_CALL(*mock_request_signer_, GenerateSignature(_, _)) + .WillOnce(Return(true)); + std::string mock_response = base::StringPrintf( + kHttpResponseFormat, Base64Encode(GenerateMockLicenseResponse()).c_str()); + EXPECT_CALL(*mock_http_fetcher_, Post(_, _, _)) + .WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK))); + for (uint32 i = 0; i < kCryptoIterations; ++i) { uint32 first_crypto_period_index = - kFirstCryptoPeriodIndex + i * kCryptoPeriodCount; + kFirstCryptoPeriodIndex - 1 + i * kCryptoPeriodCount; std::string expected_message = base::StringPrintf(kCryptoPeriodRequestMessageFormat, Base64Encode(kContentId).c_str(), @@ -354,24 +350,17 @@ TEST_F(WidevineEncryptionKeySourceTest, KeyRotationTest) { std::string mock_response = base::StringPrintf( kHttpResponseFormat, - Base64Encode(GenerateMockLicenseResponse(first_crypto_period_index, - kCryptoPeriodCount)).c_str()); + Base64Encode(GenerateMockKeyRotationLicenseResponse( + first_crypto_period_index, kCryptoPeriodCount)) + .c_str()); EXPECT_CALL(*mock_http_fetcher_, Post(_, _, _)) .WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK))); } - CreateWidevineEncryptionKeySource(kFirstCryptoPeriodIndex); + CreateWidevineEncryptionKeySource(); ASSERT_OK(widevine_encryption_key_source_->Initialize()); EncryptionKey encryption_key; - - // Index before kFirstCryptoPeriodIndex is invalid. - Status status = widevine_encryption_key_source_->GetCryptoPeriodKey( - kFirstCryptoPeriodIndex - 1, - EncryptionKeySource::TRACK_TYPE_SD, - &encryption_key); - EXPECT_EQ(error::INVALID_ARGUMENT, status.error_code()); - for (size_t i = 0; i < arraysize(kCryptoPeriodIndexes); ++i) { const std::string kTrackTypes[] = {"SD", "HD", "AUDIO"}; for (size_t j = 0; j < 3; ++j) { @@ -385,36 +374,11 @@ TEST_F(WidevineEncryptionKeySourceTest, KeyRotationTest) { } // The old crypto period indexes should have been garbage collected. - status = widevine_encryption_key_source_->GetCryptoPeriodKey( + Status status = widevine_encryption_key_source_->GetCryptoPeriodKey( kFirstCryptoPeriodIndex, EncryptionKeySource::TRACK_TYPE_SD, &encryption_key); EXPECT_EQ(error::INVALID_ARGUMENT, status.error_code()); } -class WidevineEncryptionKeySourceDeathTest - : public WidevineEncryptionKeySourceTest {}; - -TEST_F(WidevineEncryptionKeySourceDeathTest, - GetCryptoPeriodKeyOnNonKeyRotationSource) { - CreateWidevineEncryptionKeySource(kDisableKeyRotation); - widevine_encryption_key_source_->Initialize(); - - EncryptionKey encryption_key; - EXPECT_DEBUG_DEATH( - widevine_encryption_key_source_->GetCryptoPeriodKey( - 0, EncryptionKeySource::TRACK_TYPE_SD, &encryption_key), - ""); -} - -TEST_F(WidevineEncryptionKeySourceDeathTest, GetKeyOnKeyRotationSource) { - CreateWidevineEncryptionKeySource(0); - widevine_encryption_key_source_->Initialize(); - - EncryptionKey encryption_key; - EXPECT_DEBUG_DEATH(widevine_encryption_key_source_->GetKey( - EncryptionKeySource::TRACK_TYPE_SD, &encryption_key), - ""); -} - } // namespace media