Add WebM decryption support

- Also refactor decryptor management code out of mp4_media_parser.cc
- Move decryptor managment logic to DecryptorSource class to make it
  available for webm as well
- Remove data_offset member from DecryptConfig which is not useful
- Add widevine_pssh_data.proto file

Closes #72

Change-Id: I1d32baf4013ebd3382b5372c7433fae5033a260e
This commit is contained in:
KongQun Yang 2016-01-12 17:32:48 -08:00
parent 940c3571aa
commit 22498e125a
17 changed files with 505 additions and 149 deletions

View File

@ -11,14 +11,9 @@ namespace media {
DecryptConfig::DecryptConfig(const std::vector<uint8_t>& key_id, DecryptConfig::DecryptConfig(const std::vector<uint8_t>& key_id,
const std::vector<uint8_t>& iv, const std::vector<uint8_t>& iv,
const int data_offset,
const std::vector<SubsampleEntry>& subsamples) const std::vector<SubsampleEntry>& subsamples)
: key_id_(key_id), : key_id_(key_id), iv_(iv), subsamples_(subsamples) {
iv_(iv),
data_offset_(data_offset),
subsamples_(subsamples) {
CHECK_GT(key_id.size(), 0u); CHECK_GT(key_id.size(), 0u);
CHECK_GE(data_offset, 0);
} }
DecryptConfig::~DecryptConfig() {} DecryptConfig::~DecryptConfig() {}

View File

@ -38,22 +38,16 @@ class DecryptConfig {
/// @param key_id is the ID that references the decryption key. /// @param key_id is the ID that references the decryption key.
/// @param iv is the initialization vector defined by the encryptor. /// @param iv is the initialization vector defined by the encryptor.
/// @param data_offset is the amount of data that should be discarded from
/// the head of the sample buffer before applying subsample
/// information. A decrypted buffer will be shorter than an encrypted
/// buffer by this amount.
/// @param subsamples defines the clear and encrypted portions of the sample /// @param subsamples defines the clear and encrypted portions of the sample
/// as described in SubsampleEntry. A decrypted buffer will be equal /// as described in SubsampleEntry. A decrypted buffer will be equal
/// in size to the sum of the subsample sizes. /// in size to the sum of the subsample sizes.
DecryptConfig(const std::vector<uint8_t>& key_id, DecryptConfig(const std::vector<uint8_t>& key_id,
const std::vector<uint8_t>& iv, const std::vector<uint8_t>& iv,
const int data_offset,
const std::vector<SubsampleEntry>& subsamples); const std::vector<SubsampleEntry>& subsamples);
~DecryptConfig(); ~DecryptConfig();
const std::vector<uint8_t>& key_id() const { return key_id_; } const std::vector<uint8_t>& key_id() const { return key_id_; }
const std::vector<uint8_t>& iv() const { return iv_; } const std::vector<uint8_t>& iv() const { return iv_; }
int data_offset() const { return data_offset_; }
const std::vector<SubsampleEntry>& subsamples() const { return subsamples_; } const std::vector<SubsampleEntry>& subsamples() const { return subsamples_; }
private: private:
@ -62,9 +56,6 @@ class DecryptConfig {
// Initialization vector. // Initialization vector.
const std::vector<uint8_t> iv_; const std::vector<uint8_t> iv_;
// Amount of data to be discarded before applying subsample information.
const int data_offset_;
// Subsample information. May be empty for some formats, meaning entire frame // Subsample information. May be empty for some formats, meaning entire frame
// (less data ignored by data_offset_) is encrypted. // (less data ignored by data_offset_) is encrypted.
const std::vector<SubsampleEntry> subsamples_; const std::vector<SubsampleEntry> subsamples_;

View File

@ -0,0 +1,85 @@
// Copyright 2016 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "packager/media/base/decryptor_source.h"
#include "packager/base/logging.h"
#include "packager/base/stl_util.h"
namespace edash_packager {
namespace media {
DecryptorSource::DecryptorSource(KeySource* key_source)
: key_source_(key_source) {
CHECK(key_source);
}
DecryptorSource::~DecryptorSource() {
STLDeleteValues(&decryptor_map_);
}
bool DecryptorSource::DecryptSampleBuffer(const DecryptConfig* decrypt_config,
uint8_t* buffer,
size_t buffer_size) {
DCHECK(decrypt_config);
DCHECK(buffer);
// Get the decryptor object.
AesCtrEncryptor* decryptor;
auto found = decryptor_map_.find(decrypt_config->key_id());
if (found == decryptor_map_.end()) {
// Create new AesCtrEncryptor
EncryptionKey key;
Status status(key_source_->GetKey(decrypt_config->key_id(), &key));
if (!status.ok()) {
LOG(ERROR) << "Error retrieving decryption key: " << status;
return false;
}
scoped_ptr<AesCtrEncryptor> aes_ctr_encryptor(new AesCtrEncryptor);
if (!aes_ctr_encryptor->InitializeWithIv(key.key, decrypt_config->iv())) {
LOG(ERROR) << "Failed to initialize AesCtrEncryptor for decryption.";
return false;
}
decryptor = aes_ctr_encryptor.release();
decryptor_map_[decrypt_config->key_id()] = decryptor;
} else {
decryptor = found->second;
}
if (!decryptor->SetIv(decrypt_config->iv())) {
LOG(ERROR) << "Invalid initialization vector.";
return false;
}
if (decrypt_config->subsamples().empty()) {
// Sample not encrypted using subsample encryption. Decrypt whole.
if (!decryptor->Decrypt(buffer, buffer_size, buffer)) {
LOG(ERROR) << "Error during bulk sample decryption.";
return false;
}
return true;
}
// Subsample decryption.
const std::vector<SubsampleEntry>& subsamples = decrypt_config->subsamples();
uint8_t* current_ptr = buffer;
const uint8_t* const buffer_end = buffer + buffer_size;
for (const auto& subsample : subsamples) {
if ((current_ptr + subsample.clear_bytes + subsample.cipher_bytes) >
buffer_end) {
LOG(ERROR) << "Subsamples overflow sample buffer.";
return false;
}
current_ptr += subsample.clear_bytes;
if (!decryptor->Decrypt(current_ptr, subsample.cipher_bytes, current_ptr)) {
LOG(ERROR) << "Error decrypting subsample buffer.";
return false;
}
current_ptr += subsample.cipher_bytes;
}
return true;
}
} // namespace media
} // namespace edash_packager

View File

@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All rights reserved. // Copyright 2016 Google Inc. All rights reserved.
// //
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at // license that can be found in the LICENSE file or at
@ -7,28 +7,30 @@
#ifndef MEDIA_BASE_DECRYPTOR_SOURCE_H_ #ifndef MEDIA_BASE_DECRYPTOR_SOURCE_H_
#define MEDIA_BASE_DECRYPTOR_SOURCE_H_ #define MEDIA_BASE_DECRYPTOR_SOURCE_H_
#include "packager/base/memory/scoped_ptr.h" #include <map>
#include "packager/media/base/container_names.h" #include <vector>
#include "packager/media/base/status.h"
#include "packager/media/base/aes_encryptor.h"
#include "packager/media/base/decrypt_config.h"
#include "packager/media/base/key_source.h"
namespace edash_packager { namespace edash_packager {
namespace media { namespace media {
/// DecryptorSource is responsible for decryption key acquisition. /// DecryptorSource wraps KeySource and is responsible for decryptor management.
class DecryptorSource { class DecryptorSource {
public: public:
DecryptorSource() {} explicit DecryptorSource(KeySource* key_source);
virtual ~DecryptorSource() {} ~DecryptorSource();
/// NeedKey event handler. bool DecryptSampleBuffer(const DecryptConfig* decrypt_config,
/// @param container indicates the container format. uint8_t* buffer,
/// @param init_data specifies container dependent initialization data that size_t buffer_size);
/// is used to initialize the decryption key.
/// @return OK on success, an adequate status on error.
virtual Status OnNeedKey(MediaContainerName container,
const std::string& init_data) = 0;
private: private:
KeySource* key_source_;
std::map<std::vector<uint8_t>, AesCtrEncryptor*> decryptor_map_;
DISALLOW_COPY_AND_ASSIGN(DecryptorSource); DISALLOW_COPY_AND_ASSIGN(DecryptorSource);
}; };

View File

@ -0,0 +1,172 @@
// Copyright 2016 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "packager/media/base/decryptor_source.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "packager/base/macros.h"
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::StrictMock;
using ::testing::Mock;
using ::testing::_;
namespace edash_packager {
namespace media {
namespace {
const uint8_t kKeyId[] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
};
const uint8_t kMockKey[] = {
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
};
const uint8_t kIv[] = {
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
};
const uint8_t kBuffer[] = {
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x04, 0x05, 0x06,
0x07, 0x08, 0x09, 0x0a, 0x0b, 0x05, 0x06, 0x07, 0x08, 0x09,
};
// Expected decrypted buffer with the above kMockKey and kIv.
const uint8_t kExpectedDecryptedBuffer[] = {
0xfd, 0xf9, 0x8b, 0xb2, 0x1d, 0xd3, 0x07, 0x72, 0x51, 0xf4, 0xdf,
0xf9, 0x16, 0x6a, 0x14, 0xcb, 0xde, 0xaa, 0x6a, 0x04, 0x85,
};
const uint8_t kIv2[] = {
0x14, 0x25, 0x36, 0x47, 0x58, 0x69, 0x7a, 0x8b,
};
const uint8_t kBuffer2[] = {0x05, 0x02};
// Expected decrypted buffer with the above kMockKey and kIv2.
const uint8_t kExpectedDecryptedBuffer2[] = {0x20, 0x62};
class MockKeySource : public KeySource {
public:
MOCK_METHOD2(GetKey,
Status(const std::vector<uint8_t>& key_id, EncryptionKey* key));
};
} // namespace
class DecryptorSourceTest : public ::testing::Test {
public:
DecryptorSourceTest()
: decryptor_source_(&mock_key_source_),
key_id_(std::vector<uint8_t>(kKeyId, kKeyId + arraysize(kKeyId))),
buffer_(std::vector<uint8_t>(kBuffer, kBuffer + arraysize(kBuffer))) {}
protected:
StrictMock<MockKeySource> mock_key_source_;
DecryptorSource decryptor_source_;
std::vector<uint8_t> key_id_;
std::vector<uint8_t> buffer_;
};
TEST_F(DecryptorSourceTest, FullSampleDecryption) {
EncryptionKey encryption_key;
encryption_key.key.assign(kMockKey, kMockKey + arraysize(kMockKey));
EXPECT_CALL(mock_key_source_, GetKey(key_id_, _))
.WillOnce(DoAll(SetArgPointee<1>(encryption_key), Return(Status::OK)));
DecryptConfig decrypt_config(key_id_,
std::vector<uint8_t>(kIv, kIv + arraysize(kIv)),
std::vector<SubsampleEntry>());
ASSERT_TRUE(decryptor_source_.DecryptSampleBuffer(&decrypt_config, &buffer_[0],
buffer_.size()));
EXPECT_EQ(std::vector<uint8_t>(
kExpectedDecryptedBuffer,
kExpectedDecryptedBuffer + arraysize(kExpectedDecryptedBuffer)),
buffer_);
// DecryptSampleBuffer can be called repetitively. No GetKey call again with
// the same key id.
buffer_.assign(kBuffer2, kBuffer2 + arraysize(kBuffer2));
DecryptConfig decrypt_config2(
key_id_, std::vector<uint8_t>(kIv2, kIv2 + arraysize(kIv2)),
std::vector<SubsampleEntry>());
ASSERT_TRUE(decryptor_source_.DecryptSampleBuffer(
&decrypt_config2, &buffer_[0], buffer_.size()));
EXPECT_EQ(std::vector<uint8_t>(kExpectedDecryptedBuffer2,
kExpectedDecryptedBuffer2 +
arraysize(kExpectedDecryptedBuffer2)),
buffer_);
}
TEST_F(DecryptorSourceTest, SubsampleDecryption) {
EncryptionKey encryption_key;
encryption_key.key.assign(kMockKey, kMockKey + arraysize(kMockKey));
EXPECT_CALL(mock_key_source_, GetKey(key_id_, _))
.WillOnce(DoAll(SetArgPointee<1>(encryption_key), Return(Status::OK)));
const SubsampleEntry kSubsamples[] = {
{2, 3},
{3, 13},
};
// Expected decrypted buffer with the above subsamples.
const uint8_t kExpectedDecryptedBuffer[] = {
// Subsample[0].clear
0x03, 0x04,
// Subsample[0].cipher
0xfb, 0xfb, 0x89,
// Subsample[1].clear
0x08, 0x09, 0x0a,
// Subsample[1].cipher
0xb0, 0x1f, 0xdd, 0x09, 0x70, 0x5c, 0xfb, 0xd2,
0xfb, 0x18, 0x64, 0x16, 0xc9,
};
DecryptConfig decrypt_config(
key_id_, std::vector<uint8_t>(kIv, kIv + arraysize(kIv)),
std::vector<SubsampleEntry>(kSubsamples,
kSubsamples + arraysize(kSubsamples)));
ASSERT_TRUE(decryptor_source_.DecryptSampleBuffer(
&decrypt_config, &buffer_[0], buffer_.size()));
EXPECT_EQ(std::vector<uint8_t>(
kExpectedDecryptedBuffer,
kExpectedDecryptedBuffer + arraysize(kExpectedDecryptedBuffer)),
buffer_);
}
TEST_F(DecryptorSourceTest, SubsampleDecryptionSizeValidation) {
EncryptionKey encryption_key;
encryption_key.key.assign(kMockKey, kMockKey + arraysize(kMockKey));
EXPECT_CALL(mock_key_source_, GetKey(key_id_, _))
.WillOnce(DoAll(SetArgPointee<1>(encryption_key), Return(Status::OK)));
// Total size exceeds buffer size.
const SubsampleEntry kSubsamples[] = {
{2, 3},
{3, 14},
};
DecryptConfig decrypt_config(
key_id_, std::vector<uint8_t>(kIv, kIv + arraysize(kIv)),
std::vector<SubsampleEntry>(kSubsamples,
kSubsamples + arraysize(kSubsamples)));
ASSERT_FALSE(decryptor_source_.DecryptSampleBuffer(
&decrypt_config, &buffer_[0], buffer_.size()));
}
TEST_F(DecryptorSourceTest, DecryptFailedIfGetKeyFailed) {
EXPECT_CALL(mock_key_source_, GetKey(key_id_, _))
.WillOnce(Return(Status::UNKNOWN));
DecryptConfig decrypt_config(key_id_,
std::vector<uint8_t>(kIv, kIv + arraysize(kIv)),
std::vector<SubsampleEntry>());
ASSERT_FALSE(decryptor_source_.DecryptSampleBuffer(
&decrypt_config, &buffer_[0], buffer_.size()));
}
} // namespace media
} // namespace edash_packager

View File

@ -35,6 +35,7 @@
'demuxer.h', 'demuxer.h',
'decrypt_config.cc', 'decrypt_config.cc',
'decrypt_config.h', 'decrypt_config.h',
'decryptor_source.cc',
'decryptor_source.h', 'decryptor_source.h',
'http_key_fetcher.cc', 'http_key_fetcher.cc',
'http_key_fetcher.h', 'http_key_fetcher.h',
@ -87,6 +88,19 @@
'../../version/version.gyp:version', '../../version/version.gyp:version',
], ],
}, },
{
'target_name': 'widevine_pssh_data_proto',
'type': '<(component)',
'sources': ['widevine_pssh_data.proto'],
'variables': {
'proto_in_dir': '.',
'proto_out_dir': 'packager/media/base',
},
'includes': ['../../build/protoc.gypi'],
'dependencies': [
'../../third_party/protobuf/protobuf.gyp:protobuf_lite',
],
},
{ {
'target_name': 'media_base_unittest', 'target_name': 'media_base_unittest',
'type': '<(gtest_target_type)', 'type': '<(gtest_target_type)',
@ -97,6 +111,7 @@
'buffer_writer_unittest.cc', 'buffer_writer_unittest.cc',
'closure_thread_unittest.cc', 'closure_thread_unittest.cc',
'container_names_unittest.cc', 'container_names_unittest.cc',
'decryptor_source_unittest.cc',
'http_key_fetcher_unittest.cc', 'http_key_fetcher_unittest.cc',
'muxer_util_unittest.cc', 'muxer_util_unittest.cc',
'offset_byte_queue_unittest.cc', 'offset_byte_queue_unittest.cc',

View File

@ -0,0 +1,36 @@
// Copyright 2016 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
//
// This file defines Widevine Pssh Data proto format.
syntax = "proto2";
package edash_packager.media;
message WidevinePsshData {
enum Algorithm {
UNENCRYPTED = 0;
AESCTR = 1;
};
optional Algorithm algorithm = 1;
repeated bytes key_id = 2;
// Content provider name.
optional string provider = 3;
// A content identifier, specified by content provider.
optional bytes content_id = 4;
// The name of a registered policy to be used for this asset.
optional string policy = 6;
// Crypto period index, for media using key rotation.
optional uint32 crypto_period_index = 7;
// Optional protected context for group content. The grouped_license is a
// serialized SignedMessage.
optional bytes grouped_license = 8;
}

View File

@ -11,7 +11,6 @@
#include "packager/base/logging.h" #include "packager/base/logging.h"
#include "packager/base/memory/ref_counted.h" #include "packager/base/memory/ref_counted.h"
#include "packager/base/strings/string_number_conversions.h" #include "packager/base/strings/string_number_conversions.h"
#include "packager/media/base/aes_encryptor.h"
#include "packager/media/base/audio_stream_info.h" #include "packager/media/base/audio_stream_info.h"
#include "packager/media/base/buffer_reader.h" #include "packager/media/base/buffer_reader.h"
#include "packager/media/base/decrypt_config.h" #include "packager/media/base/decrypt_config.h"
@ -90,11 +89,12 @@ const uint8_t kDtsAudioNumChannels = 6;
} // namespace } // namespace
MP4MediaParser::MP4MediaParser() MP4MediaParser::MP4MediaParser()
: state_(kWaitingForInit), moof_head_(0), mdat_tail_(0) {} : state_(kWaitingForInit),
decryption_key_source_(NULL),
moof_head_(0),
mdat_tail_(0) {}
MP4MediaParser::~MP4MediaParser() { MP4MediaParser::~MP4MediaParser() {}
STLDeleteValues(&decryptor_map_);
}
void MP4MediaParser::Init(const InitCB& init_cb, void MP4MediaParser::Init(const InitCB& init_cb,
const NewSampleCB& new_sample_cb, const NewSampleCB& new_sample_cb,
@ -108,6 +108,8 @@ void MP4MediaParser::Init(const InitCB& init_cb,
init_cb_ = init_cb; init_cb_ = init_cb;
new_sample_cb_ = new_sample_cb; new_sample_cb_ = new_sample_cb;
decryption_key_source_ = decryption_key_source; decryption_key_source_ = decryption_key_source;
if (decryption_key_source)
decryptor_source_.reset(new DecryptorSource(decryption_key_source));
} }
void MP4MediaParser::Reset() { void MP4MediaParser::Reset() {
@ -652,11 +654,18 @@ bool MP4MediaParser::EnqueueSample(bool* err) {
scoped_refptr<MediaSample> stream_sample(MediaSample::CopyFrom( scoped_refptr<MediaSample> stream_sample(MediaSample::CopyFrom(
buf, runs_->sample_size(), runs_->is_keyframe())); buf, runs_->sample_size(), runs_->is_keyframe()));
if (runs_->is_encrypted()) { if (runs_->is_encrypted()) {
if (!decryptor_source_) {
*err = true;
LOG(ERROR) << "Encrypted media sample encountered, but decryption is not "
"enabled";
return false;
}
scoped_ptr<DecryptConfig> decrypt_config = runs_->GetDecryptConfig(); scoped_ptr<DecryptConfig> decrypt_config = runs_->GetDecryptConfig();
if (!decrypt_config || if (!decrypt_config ||
!DecryptSampleBuffer(decrypt_config.get(), !decryptor_source_->DecryptSampleBuffer(decrypt_config.get(),
stream_sample->writable_data(), stream_sample->writable_data(),
stream_sample->data_size())) { stream_sample->data_size())) {
*err = true; *err = true;
LOG(ERROR) << "Cannot decrypt samples."; LOG(ERROR) << "Cannot decrypt samples.";
return false; return false;
@ -684,80 +693,6 @@ bool MP4MediaParser::EnqueueSample(bool* err) {
return true; return true;
} }
bool MP4MediaParser::DecryptSampleBuffer(const DecryptConfig* decrypt_config,
uint8_t* buffer,
size_t buffer_size) {
DCHECK(decrypt_config);
DCHECK(buffer);
if (!decryption_key_source_) {
LOG(ERROR) << "Encrypted media sample encountered, but decryption is not "
"enabled";
return false;
}
// Get the encryptor object.
AesCtrEncryptor* encryptor;
DecryptorMap::iterator found = decryptor_map_.find(decrypt_config->key_id());
if (found == decryptor_map_.end()) {
// Create new AesCtrEncryptor
EncryptionKey key;
Status status(decryption_key_source_->GetKey(decrypt_config->key_id(),
&key));
if (!status.ok()) {
LOG(ERROR) << "Error retrieving decryption key: " << status;
return false;
}
scoped_ptr<AesCtrEncryptor> new_encryptor(new AesCtrEncryptor);
if (!new_encryptor->InitializeWithIv(key.key, decrypt_config->iv())) {
LOG(ERROR) << "Failed to initialize AesCtrEncryptor for decryption.";
return false;
}
encryptor = new_encryptor.release();
decryptor_map_[decrypt_config->key_id()] = encryptor;
} else {
encryptor = found->second;
}
if (!encryptor->SetIv(decrypt_config->iv())) {
LOG(ERROR) << "Invalid initialization vector.";
return false;
}
if (decrypt_config->subsamples().empty()) {
// Sample not encrypted using subsample encryption. Decrypt whole.
if (!encryptor->Decrypt(buffer, buffer_size, buffer)) {
LOG(ERROR) << "Error during bulk sample decryption.";
return false;
}
return true;
}
// Subsample decryption.
const std::vector<SubsampleEntry>& subsamples = decrypt_config->subsamples();
uint8_t* current_ptr = buffer;
const uint8_t* buffer_end = buffer + buffer_size;
current_ptr += decrypt_config->data_offset();
if (current_ptr > buffer_end) {
LOG(ERROR) << "Subsample data_offset too large.";
return false;
}
for (std::vector<SubsampleEntry>::const_iterator iter = subsamples.begin();
iter != subsamples.end();
++iter) {
if ((current_ptr + iter->clear_bytes + iter->cipher_bytes) > buffer_end) {
LOG(ERROR) << "Subsamples overflow sample buffer.";
return false;
}
current_ptr += iter->clear_bytes;
if (!encryptor->Decrypt(current_ptr, iter->cipher_bytes, current_ptr)) {
LOG(ERROR) << "Error decrypting subsample buffer.";
return false;
}
current_ptr += iter->cipher_bytes;
}
return true;
}
bool MP4MediaParser::ReadAndDiscardMDATsUntil(const int64_t offset) { bool MP4MediaParser::ReadAndDiscardMDATsUntil(const int64_t offset) {
bool err = false; bool err = false;
while (mdat_tail_ < offset) { while (mdat_tail_ < offset) {

View File

@ -17,15 +17,12 @@
#include "packager/base/memory/scoped_ptr.h" #include "packager/base/memory/scoped_ptr.h"
#include "packager/base/memory/ref_counted.h" #include "packager/base/memory/ref_counted.h"
#include "packager/base/memory/scoped_ptr.h" #include "packager/base/memory/scoped_ptr.h"
#include "packager/media/base/decryptor_source.h"
#include "packager/media/base/media_parser.h" #include "packager/media/base/media_parser.h"
#include "packager/media/base/offset_byte_queue.h" #include "packager/media/base/offset_byte_queue.h"
namespace edash_packager { namespace edash_packager {
namespace media { namespace media {
class AesCtrEncryptor;
class DecryptConfig;
namespace mp4 { namespace mp4 {
class BoxReader; class BoxReader;
@ -70,10 +67,6 @@ class MP4MediaParser : public MediaParser {
bool FetchKeysIfNecessary( bool FetchKeysIfNecessary(
const std::vector<ProtectionSystemSpecificHeader>& headers); const std::vector<ProtectionSystemSpecificHeader>& headers);
bool DecryptSampleBuffer(const DecryptConfig* decrypt_config,
uint8_t* buffer,
size_t buffer_size);
// To retain proper framing, each 'mdat' box must be read; to limit memory // To retain proper framing, each 'mdat' box must be read; to limit memory
// usage, the box's data needs to be discarded incrementally as frames are // usage, the box's data needs to be discarded incrementally as frames are
// extracted from the stream. This function discards data from the stream up // extracted from the stream. This function discards data from the stream up
@ -94,6 +87,7 @@ class MP4MediaParser : public MediaParser {
InitCB init_cb_; InitCB init_cb_;
NewSampleCB new_sample_cb_; NewSampleCB new_sample_cb_;
KeySource* decryption_key_source_; KeySource* decryption_key_source_;
scoped_ptr<DecryptorSource> decryptor_source_;
OffsetByteQueue queue_; OffsetByteQueue queue_;
@ -110,9 +104,6 @@ class MP4MediaParser : public MediaParser {
scoped_ptr<Movie> moov_; scoped_ptr<Movie> moov_;
scoped_ptr<TrackRunIterator> runs_; scoped_ptr<TrackRunIterator> runs_;
typedef std::map<std::vector<uint8_t>, AesCtrEncryptor*> DecryptorMap;
DecryptorMap decryptor_map_;
DISALLOW_COPY_AND_ASSIGN(MP4MediaParser); DISALLOW_COPY_AND_ASSIGN(MP4MediaParser);
}; };

View File

@ -600,7 +600,6 @@ scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() {
return scoped_ptr<DecryptConfig>(new DecryptConfig( return scoped_ptr<DecryptConfig>(new DecryptConfig(
track_encryption().default_kid, track_encryption().default_kid,
sample_encryption_entry.initialization_vector, sample_encryption_entry.initialization_vector,
0, // No offset to start of media data in MP4 using CENC.
sample_encryption_entry.subsamples)); sample_encryption_entry.subsamples));
} }

View File

@ -58,6 +58,7 @@
'../../../third_party/boringssl/boringssl.gyp:boringssl', '../../../third_party/boringssl/boringssl.gyp:boringssl',
'../../../third_party/libwebm/libwebm.gyp:libwebm', '../../../third_party/libwebm/libwebm.gyp:libwebm',
'../../base/media_base.gyp:base', '../../base/media_base.gyp:base',
'../../base/media_base.gyp:widevine_pssh_data_proto',
'../../filters/filters.gyp:filters' '../../filters/filters.gyp:filters'
], ],
}, },

View File

@ -60,7 +60,8 @@ WebMClusterParser::WebMClusterParser(
const std::string& audio_encryption_key_id, const std::string& audio_encryption_key_id,
const std::string& video_encryption_key_id, const std::string& video_encryption_key_id,
const MediaParser::NewSampleCB& new_sample_cb, const MediaParser::NewSampleCB& new_sample_cb,
const MediaParser::InitCB& init_cb) const MediaParser::InitCB& init_cb,
KeySource* decryption_key_source)
: timecode_multiplier_(timecode_scale / 1000.0), : timecode_multiplier_(timecode_scale / 1000.0),
audio_stream_info_(audio_stream_info), audio_stream_info_(audio_stream_info),
video_stream_info_(video_stream_info), video_stream_info_(video_stream_info),
@ -79,6 +80,8 @@ WebMClusterParser::WebMClusterParser(
true, true,
video_default_duration, video_default_duration,
new_sample_cb) { new_sample_cb) {
if (decryption_key_source)
decryptor_source_.reset(new DecryptorSource(decryption_key_source));
for (WebMTracksParser::TextTracks::const_iterator it = text_tracks.begin(); for (WebMTracksParser::TextTracks::const_iterator it = text_tracks.begin();
it != text_tracks.end(); it != text_tracks.end();
++it) { ++it) {
@ -326,11 +329,12 @@ bool WebMClusterParser::OnBlock(bool is_simple_block,
} }
Track* track = NULL; Track* track = NULL;
StreamType stream_type = kStreamAudio; StreamType stream_type = kStreamUnknown;
std::string encryption_key_id; std::string encryption_key_id;
if (track_num == audio_.track_num()) { if (track_num == audio_.track_num()) {
track = &audio_; track = &audio_;
encryption_key_id = audio_encryption_key_id_; encryption_key_id = audio_encryption_key_id_;
stream_type = kStreamAudio;
} else if (track_num == video_.track_num()) { } else if (track_num == video_.track_num()) {
track = &video_; track = &video_;
encryption_key_id = video_encryption_key_id_; encryption_key_id = video_encryption_key_id_;
@ -348,6 +352,7 @@ bool WebMClusterParser::OnBlock(bool is_simple_block,
LOG(ERROR) << "Unexpected track number " << track_num; LOG(ERROR) << "Unexpected track number " << track_num;
return false; return false;
} }
DCHECK_NE(stream_type, kStreamUnknown);
last_block_timecode_ = timecode; last_block_timecode_ = timecode;
@ -384,9 +389,19 @@ bool WebMClusterParser::OnBlock(bool is_simple_block,
buffer = MediaSample::CopyFrom(data + data_offset, size - data_offset, buffer = MediaSample::CopyFrom(data + data_offset, size - data_offset,
additional, additional_size, is_keyframe); additional, additional_size, is_keyframe);
if (decrypt_config) { // An empty iv indicates that this sample is not encrypted.
// TODO(kqyang): Decrypt it if it is encrypted. if (decrypt_config && !decrypt_config->iv().empty()) {
buffer->set_is_encrypted(true); if (!decryptor_source_) {
LOG(ERROR) << "Encrypted media sample encountered, but decryption is "
"not enabled";
return false;
}
if (!decryptor_source_->DecryptSampleBuffer(decrypt_config.get(),
buffer->writable_data(),
buffer->data_size())) {
LOG(ERROR) << "Cannot decrypt samples";
return false;
}
} }
} else { } else {
std::string id, settings, content; std::string id, settings, content;

View File

@ -11,6 +11,7 @@
#include <string> #include <string>
#include "packager/base/memory/scoped_ptr.h" #include "packager/base/memory/scoped_ptr.h"
#include "packager/media/base/decryptor_source.h"
#include "packager/media/base/media_parser.h" #include "packager/media/base/media_parser.h"
#include "packager/media/base/media_sample.h" #include "packager/media/base/media_sample.h"
#include "packager/media/formats/webm/webm_parser.h" #include "packager/media/formats/webm/webm_parser.h"
@ -93,6 +94,28 @@ class WebMClusterParser : public WebMParserClient {
typedef std::map<int, Track> TextTrackMap; typedef std::map<int, Track> TextTrackMap;
public: public:
/// Create a WebMClusterParser from given parameters.
/// @param timecode_scale indicates timecode scale for the clusters.
/// @param audio_stream_info references audio stream information. It will
/// be NULL if there are no audio tracks available.
/// @param video_stream_info references video stream information. It will
/// be NULL if there are no video tracks available.
/// @param audio_default_duration indicates default duration for audio
/// samples.
/// @param video_default_duration indicates default duration for video
/// samples.
/// @param text_tracks contains text track information.
/// @param ignored_tracks contains a list of ignored track ids.
/// @param audio_encryption_key_id indicates the encryption key id for audio
/// samples if there is an audio stream and the audio stream is
/// encrypted. It is empty otherwise.
/// @param video_encryption_key_id indicates the encryption key id for video
/// samples if there is a video stream and the video stream is
/// encrypted. It is empty otherwise.
/// @param new_sample_cb is the callback to emit new samples.
/// @param init_cb is the callback to initialize streams.
/// @param decryption_key_source points to a decryption key source to fetch
/// decryption keys. Should not be NULL if the tracks are encrypted.
WebMClusterParser(int64_t timecode_scale, WebMClusterParser(int64_t timecode_scale,
scoped_refptr<AudioStreamInfo> audio_stream_info, scoped_refptr<AudioStreamInfo> audio_stream_info,
scoped_refptr<VideoStreamInfo> video_stream_info, scoped_refptr<VideoStreamInfo> video_stream_info,
@ -103,7 +126,8 @@ class WebMClusterParser : public WebMParserClient {
const std::string& audio_encryption_key_id, const std::string& audio_encryption_key_id,
const std::string& video_encryption_key_id, const std::string& video_encryption_key_id,
const MediaParser::NewSampleCB& new_sample_cb, const MediaParser::NewSampleCB& new_sample_cb,
const MediaParser::InitCB& init_cb); const MediaParser::InitCB& init_cb,
KeySource* decryption_key_source);
~WebMClusterParser() override; ~WebMClusterParser() override;
/// Resets the parser state so it can accept a new cluster. /// Resets the parser state so it can accept a new cluster.
@ -162,6 +186,8 @@ class WebMClusterParser : public WebMParserClient {
scoped_refptr<AudioStreamInfo> audio_stream_info_; scoped_refptr<AudioStreamInfo> audio_stream_info_;
scoped_refptr<VideoStreamInfo> video_stream_info_; scoped_refptr<VideoStreamInfo> video_stream_info_;
std::set<int64_t> ignored_tracks_; std::set<int64_t> ignored_tracks_;
scoped_ptr<DecryptorSource> decryptor_source_;
std::string audio_encryption_key_id_; std::string audio_encryption_key_id_;
std::string video_encryption_key_id_; std::string video_encryption_key_id_;

View File

@ -23,6 +23,7 @@
using ::testing::HasSubstr; using ::testing::HasSubstr;
using ::testing::InSequence; using ::testing::InSequence;
using ::testing::Return; using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::StrictMock; using ::testing::StrictMock;
using ::testing::Mock; using ::testing::Mock;
using ::testing::_; using ::testing::_;
@ -137,6 +138,23 @@ const uint8_t kEncryptedFrame[] = {
// Some dummy encrypted data // Some dummy encrypted data
0x01, 0x01,
}; };
const uint8_t kMockKey[] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
};
const uint8_t kExpectedDecryptedFrame[] = {
0x41,
};
const uint8_t kClearFrameInEncryptedTrack[] = {
// Block is not encrypted
0x00,
// Some dummy frame data
0x01, 0x02, 0x03,
};
const uint8_t kExpectedClearFrame[] = {
0x01, 0x02, 0x03,
};
const uint8_t kVP8Frame[] = { const uint8_t kVP8Frame[] = {
0x52, 0x04, 0x00, 0x9d, 0x01, 0x2a, 0x40, 0x01, 0xf0, 0x00, 0x00, 0x47, 0x52, 0x04, 0x00, 0x9d, 0x01, 0x2a, 0x40, 0x01, 0xf0, 0x00, 0x00, 0x47,
@ -153,6 +171,12 @@ const uint8_t kVP9Frame[] = {
0xe1, 0xe6, 0xef, 0xff, 0xfd, 0xf7, 0x4f, 0x0f, 0xe1, 0xe6, 0xef, 0xff, 0xfd, 0xf7, 0x4f, 0x0f,
}; };
class MockKeySource : public KeySource {
public:
MOCK_METHOD2(GetKey,
Status(const std::vector<uint8_t>& key_id, EncryptionKey* key));
};
scoped_ptr<Cluster> CreateCluster(int timecode, scoped_ptr<Cluster> CreateCluster(int timecode,
const BlockInfo* block_info, const BlockInfo* block_info,
int block_count) { int block_count) {
@ -284,10 +308,6 @@ bool VerifyTextBuffers(const BlockInfo* block_info_ptr,
return true; return true;
} }
void VerifyEncryptedBuffer(scoped_refptr<MediaSample> buffer) {
EXPECT_TRUE(buffer->is_encrypted());
}
} // namespace } // namespace
class WebMClusterParserTest : public testing::Test { class WebMClusterParserTest : public testing::Test {
@ -382,7 +402,7 @@ class WebMClusterParserTest : public testing::Test {
ignored_tracks, audio_encryption_key_id, video_encryption_key_id, ignored_tracks, audio_encryption_key_id, video_encryption_key_id,
base::Bind(&WebMClusterParserTest::NewSampleEvent, base::Bind(&WebMClusterParserTest::NewSampleEvent,
base::Unretained(this)), base::Unretained(this)),
init_cb); init_cb, &mock_key_source_);
} }
// Create a default version of the parser for test. // Create a default version of the parser for test.
@ -450,6 +470,7 @@ class WebMClusterParserTest : public testing::Test {
BufferQueue audio_buffers_; BufferQueue audio_buffers_;
BufferQueue video_buffers_; BufferQueue video_buffers_;
TextBufferQueueMap text_buffers_map_; TextBufferQueueMap text_buffers_map_;
StrictMock<MockKeySource> mock_key_source_;
private: private:
DISALLOW_COPY_AND_ASSIGN(WebMClusterParserTest); DISALLOW_COPY_AND_ASSIGN(WebMClusterParserTest);
@ -806,6 +827,36 @@ TEST_F(WebMClusterParserTest, ParseVP9) {
} }
TEST_F(WebMClusterParserTest, ParseEncryptedBlock) { TEST_F(WebMClusterParserTest, ParseEncryptedBlock) {
const std::string video_key_id("video_key_id");
EncryptionKey encryption_key;
encryption_key.key.assign(kMockKey, kMockKey + arraysize(kMockKey));
EXPECT_CALL(
mock_key_source_,
GetKey(std::vector<uint8_t>(video_key_id.begin(), video_key_id.end()), _))
.WillOnce(DoAll(SetArgPointee<1>(encryption_key), Return(Status::OK)));
scoped_ptr<Cluster> cluster(
CreateCluster(kEncryptedFrame, arraysize(kEncryptedFrame)));
parser_.reset(CreateParserWithKeyIdsAndAudioCodec(std::string(), video_key_id,
kUnknownAudioCodec));
int result = parser_->Parse(cluster->data(), cluster->size());
EXPECT_EQ(cluster->size(), result);
parser_->Flush();
ASSERT_EQ(1UL, video_buffers_.size());
scoped_refptr<MediaSample> buffer = video_buffers_[0];
EXPECT_EQ(std::vector<uint8_t>(
kExpectedDecryptedFrame,
kExpectedDecryptedFrame + arraysize(kExpectedDecryptedFrame)),
std::vector<uint8_t>(buffer->data(),
buffer->data() + buffer->data_size()));
}
TEST_F(WebMClusterParserTest, ParseEncryptedBlockGetKeyFailed) {
EXPECT_CALL(mock_key_source_, GetKey(_, _)).WillOnce(Return(Status::UNKNOWN));
scoped_ptr<Cluster> cluster( scoped_ptr<Cluster> cluster(
CreateCluster(kEncryptedFrame, arraysize(kEncryptedFrame))); CreateCluster(kEncryptedFrame, arraysize(kEncryptedFrame)));
@ -813,11 +864,7 @@ TEST_F(WebMClusterParserTest, ParseEncryptedBlock) {
std::string(), "video_key_id", kUnknownAudioCodec)); std::string(), "video_key_id", kUnknownAudioCodec));
int result = parser_->Parse(cluster->data(), cluster->size()); int result = parser_->Parse(cluster->data(), cluster->size());
EXPECT_EQ(cluster->size(), result); EXPECT_EQ(-1, result);
parser_->Flush();
ASSERT_EQ(1UL, video_buffers_.size());
scoped_refptr<MediaSample> buffer = video_buffers_[0];
VerifyEncryptedBuffer(buffer);
} }
TEST_F(WebMClusterParserTest, ParseBadEncryptedBlock) { TEST_F(WebMClusterParserTest, ParseBadEncryptedBlock) {
@ -830,6 +877,25 @@ TEST_F(WebMClusterParserTest, ParseBadEncryptedBlock) {
EXPECT_EQ(-1, result); EXPECT_EQ(-1, result);
} }
TEST_F(WebMClusterParserTest, ParseClearFrameInEncryptedTrack) {
scoped_ptr<Cluster> cluster(CreateCluster(
kClearFrameInEncryptedTrack, arraysize(kClearFrameInEncryptedTrack)));
parser_.reset(CreateParserWithKeyIdsAndAudioCodec(
std::string(), "video_key_id", kUnknownAudioCodec));
int result = parser_->Parse(cluster->data(), cluster->size());
EXPECT_EQ(cluster->size(), result);
parser_->Flush();
ASSERT_EQ(1UL, video_buffers_.size());
scoped_refptr<MediaSample> buffer = video_buffers_[0];
EXPECT_EQ(std::vector<uint8_t>(
kExpectedClearFrame,
kExpectedClearFrame + arraysize(kExpectedClearFrame)),
std::vector<uint8_t>(buffer->data(),
buffer->data() + buffer->data_size()));
}
TEST_F(WebMClusterParserTest, ParseInvalidZeroSizedCluster) { TEST_F(WebMClusterParserTest, ParseInvalidZeroSizedCluster) {
const uint8_t kBuffer[] = { const uint8_t kBuffer[] = {
0x1F, 0x43, 0xB6, 0x75, 0x80, // CLUSTER (size = 0) 0x1F, 0x43, 0xB6, 0x75, 0x80, // CLUSTER (size = 0)

View File

@ -56,7 +56,7 @@ bool WebMCreateDecryptConfig(const uint8_t* data,
decrypt_config->reset(new DecryptConfig( decrypt_config->reset(new DecryptConfig(
std::vector<uint8_t>(key_id, key_id + key_id_size), std::vector<uint8_t>(key_id, key_id + key_id_size),
std::vector<uint8_t>(counter_block.begin(), counter_block.end()), std::vector<uint8_t>(counter_block.begin(), counter_block.end()),
frame_offset, std::vector<SubsampleEntry>())); std::vector<SubsampleEntry>()));
*data_offset = frame_offset; *data_offset = frame_offset;
return true; return true;

View File

@ -9,7 +9,9 @@
#include "packager/base/callback.h" #include "packager/base/callback.h"
#include "packager/base/callback_helpers.h" #include "packager/base/callback_helpers.h"
#include "packager/base/logging.h" #include "packager/base/logging.h"
#include "packager/media/base/buffer_writer.h"
#include "packager/media/base/timestamp.h" #include "packager/media/base/timestamp.h"
#include "packager/media/base/widevine_pssh_data.pb.h"
#include "packager/media/formats/webm/webm_cluster_parser.h" #include "packager/media/formats/webm/webm_cluster_parser.h"
#include "packager/media/formats/webm/webm_constants.h" #include "packager/media/formats/webm/webm_constants.h"
#include "packager/media/formats/webm/webm_content_encodings.h" #include "packager/media/formats/webm/webm_content_encodings.h"
@ -185,8 +187,6 @@ int WebMMediaParser::ParseInfoAndTracks(const uint8_t* data, int size) {
tracks_parser.audio_stream_info(); tracks_parser.audio_stream_info();
if (audio_stream_info) { if (audio_stream_info) {
audio_stream_info->set_duration(duration_in_us); audio_stream_info->set_duration(duration_in_us);
if (audio_stream_info->is_encrypted())
OnEncryptedMediaInitData(tracks_parser.audio_encryption_key_id());
} else { } else {
VLOG(1) << "No audio track info found."; VLOG(1) << "No audio track info found.";
} }
@ -195,19 +195,23 @@ int WebMMediaParser::ParseInfoAndTracks(const uint8_t* data, int size) {
tracks_parser.video_stream_info(); tracks_parser.video_stream_info();
if (video_stream_info) { if (video_stream_info) {
video_stream_info->set_duration(duration_in_us); video_stream_info->set_duration(duration_in_us);
if (video_stream_info->is_encrypted())
OnEncryptedMediaInitData(tracks_parser.video_encryption_key_id());
} else { } else {
VLOG(1) << "No video track info found."; VLOG(1) << "No video track info found.";
} }
if (!FetchKeysIfNecessary(tracks_parser.audio_encryption_key_id(),
tracks_parser.video_encryption_key_id())) {
return -1;
}
cluster_parser_.reset(new WebMClusterParser( cluster_parser_.reset(new WebMClusterParser(
info_parser.timecode_scale(), audio_stream_info, video_stream_info, info_parser.timecode_scale(), audio_stream_info, video_stream_info,
tracks_parser.GetAudioDefaultDuration(timecode_scale_in_us), tracks_parser.GetAudioDefaultDuration(timecode_scale_in_us),
tracks_parser.GetVideoDefaultDuration(timecode_scale_in_us), tracks_parser.GetVideoDefaultDuration(timecode_scale_in_us),
tracks_parser.text_tracks(), tracks_parser.ignored_tracks(), tracks_parser.text_tracks(), tracks_parser.ignored_tracks(),
tracks_parser.audio_encryption_key_id(), tracks_parser.audio_encryption_key_id(),
tracks_parser.video_encryption_key_id(), new_sample_cb_, init_cb_)); tracks_parser.video_encryption_key_id(), new_sample_cb_, init_cb_,
decryption_key_source_));
return bytes_parsed; return bytes_parsed;
} }
@ -228,8 +232,30 @@ int WebMMediaParser::ParseCluster(const uint8_t* data, int size) {
return bytes_parsed; return bytes_parsed;
} }
void WebMMediaParser::OnEncryptedMediaInitData(const std::string& key_id) { bool WebMMediaParser::FetchKeysIfNecessary(
NOTIMPLEMENTED() << "WebM decryption is not implemented yet."; const std::string& audio_encryption_key_id,
const std::string& video_encryption_key_id) {
if (audio_encryption_key_id.empty() && video_encryption_key_id.empty())
return true;
// An error will be returned later if the samples need to be derypted.
if (!decryption_key_source_)
return true;
// Generate WidevinePsshData from key_id.
WidevinePsshData widevine_pssh_data;
if (!audio_encryption_key_id.empty())
widevine_pssh_data.add_key_id(audio_encryption_key_id);
if (!video_encryption_key_id.empty())
widevine_pssh_data.add_key_id(video_encryption_key_id);
const std::string serialized_string = widevine_pssh_data.SerializeAsString();
Status status = decryption_key_source_->FetchKeys(
std::vector<uint8_t>(serialized_string.begin(), serialized_string.end()));
if (!status.ok()) {
LOG(ERROR) << "Error fetching decryption keys: " << status;
return false;
}
return true;
} }
} // namespace media } // namespace media

View File

@ -56,8 +56,9 @@ class WebMMediaParser : public MediaParser {
// Returning > 0 indicates success & the number of bytes parsed. // Returning > 0 indicates success & the number of bytes parsed.
int ParseCluster(const uint8_t* data, int size); int ParseCluster(const uint8_t* data, int size);
// Fire needkey event through the |encrypted_media_init_data_cb_|. // Fetch keys for the input key ids. Returns true on success, false otherwise.
void OnEncryptedMediaInitData(const std::string& key_id); bool FetchKeysIfNecessary(const std::string& audio_encryption_key_id,
const std::string& video_encryption_key_id);
State state_; State state_;
InitCB init_cb_; InitCB init_cb_;