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
This commit is contained in:
KongQun Yang 2014-06-30 08:40:02 -07:00 committed by Gerrit Code Review
parent 33a87aa84b
commit f609b2947c
4 changed files with 112 additions and 117 deletions

View File

@ -70,8 +70,7 @@ scoped_ptr<EncryptionKeySource> 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: "

View File

@ -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<RequestSigner> signer,
int first_crypto_period_index)
scoped_ptr<RequestSigner> 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,12 +210,14 @@ 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<RefCountedEncryptionKeyMap> ref_counted_encryption_key_map;
Status status = key_pool_.Peek(crypto_period_index,
&ref_counted_encryption_key_map,
Status status =
key_pool_->Peek(crypto_period_index, &ref_counted_encryption_key_map,
kGetKeyTimeoutInSeconds * 1000);
if (!status.ok()) {
if (status.error_code() == error::STOPPED) {
@ -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];
return Status::OK;
}
void WidevineEncryptionKeySource::FetchKeysTask() {
Status status = FetchKeys(first_crypto_period_index_);
if (key_rotation_enabled_) {
// 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(first_crypto_period_index_);
}
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,14 +463,19 @@ 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<RefCountedEncryptionKeyMap>(
key_pool_->Push(scoped_refptr<RefCountedEncryptionKeyMap>(
new RefCountedEncryptionKeyMap(encryption_key_map)),
kInfiniteTimeout);
encryption_key_map->clear();

View File

@ -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 T> 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<RequestSigner> signer,
int first_crypto_period_index);
scoped_ptr<RequestSigner> 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<TrackType, EncryptionKey*> EncryptionKeyMap;
class RefCountedEncryptionKeyMap;
typedef ProducerConsumerQueue<scoped_refptr<RefCountedEncryptionKeyMap> >
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<RequestSigner> 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<scoped_refptr<RefCountedEncryptionKeyMap> > key_pool_;
scoped_ptr<EncryptionKeyQueue> key_pool_;
EncryptionKeyMap encryption_key_map_; // For non key rotation request.
Status common_encryption_request_status_;
DISALLOW_COPY_AND_ASSIGN(WidevineEncryptionKeySource);

View File

@ -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<RequestSigner>(),
first_crypto_period_index));
mock_request_signer_.PassAs<RequestSigner>()));
widevine_encryption_key_source_->set_http_fetcher(
mock_http_fetcher_.PassAs<HttpFetcher>());
}
@ -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