diff --git a/app/fixed_key_encryption_flags.cc b/app/fixed_key_encryption_flags.cc index 7abf39a77b..4eb2e9f499 100644 --- a/app/fixed_key_encryption_flags.cc +++ b/app/fixed_key_encryption_flags.cc @@ -11,13 +11,22 @@ DEFINE_bool(enable_fixed_key_encryption, false, "Enable encryption with fixed key."); +DEFINE_bool(enable_fixed_key_decryption, + false, + "Enable decryption with fixed key."); DEFINE_string(key_id, "", "Key id in hex string format."); DEFINE_string(key, "", "Key in hex string format."); DEFINE_string(pssh, "", "PSSH in hex string format."); static bool IsNotEmptyWithFixedKeyEncryption(const char* flag_name, const std::string& flag_value) { - return FLAGS_enable_fixed_key_encryption ? !flag_value.empty() : true; + if (FLAGS_enable_fixed_key_encryption && flag_value.empty()) + return false; + std::string flag_name_str(flag_name); + if (FLAGS_enable_fixed_key_decryption && (flag_name_str != "pssh") && + flag_value.empty()) + return false; + return true; } static bool dummy_key_id_validator = diff --git a/app/fixed_key_encryption_flags.h b/app/fixed_key_encryption_flags.h index c6e97bc62e..d5d6e00b42 100644 --- a/app/fixed_key_encryption_flags.h +++ b/app/fixed_key_encryption_flags.h @@ -12,6 +12,7 @@ #include DECLARE_bool(enable_fixed_key_encryption); +DECLARE_bool(enable_fixed_key_decryption); DECLARE_string(key_id); DECLARE_string(key); DECLARE_string(pssh); diff --git a/app/packager_main.cc b/app/packager_main.cc index a2a65b755c..4ed88e66c4 100644 --- a/app/packager_main.cc +++ b/app/packager_main.cc @@ -123,19 +123,20 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors, if (stream_iter->input != previous_input) { // New remux job needed. Create demux and job thread. - scoped_ptr demux(new Demuxer(stream_iter->input, NULL)); - Status status = demux->Initialize(); + scoped_ptr demuxer(new Demuxer(stream_iter->input)); + demuxer->SetKeySource(CreateDecryptionKeySource()); + Status status = demuxer->Initialize(); if (!status.ok()) { LOG(ERROR) << "Demuxer failed to initialize: " << status.ToString(); return false; } if (FLAGS_dump_stream_info) { printf("\nFile \"%s\":\n", stream_iter->input.c_str()); - DumpStreamInfo(demux->streams()); + DumpStreamInfo(demuxer->streams()); if (stream_iter->output.empty()) continue; // just need stream info. } - remux_jobs->push_back(new RemuxJob(demux.Pass())); + remux_jobs->push_back(new RemuxJob(demuxer.Pass())); previous_input = stream_iter->input; } DCHECK(!remux_jobs->empty()); diff --git a/app/packager_util.cc b/app/packager_util.cc index 149814f56d..5fd938af21 100644 --- a/app/packager_util.cc +++ b/app/packager_util.cc @@ -100,6 +100,9 @@ scoped_ptr CreateDecryptionKeySource() { scoped_ptr signer(CreateSigner()); decryption_key_source.reset(new WidevineKeySource(FLAGS_key_server_url, signer.Pass())); + } else if (FLAGS_enable_fixed_key_decryption) { + decryption_key_source = KeySource::CreateFromHexStrings( + FLAGS_key_id, FLAGS_key, "", ""); } return decryption_key_source.Pass(); } diff --git a/app/widevine_encryption_flags.cc b/app/widevine_encryption_flags.cc index d7d5d7790e..1585fb204a 100644 --- a/app/widevine_encryption_flags.cc +++ b/app/widevine_encryption_flags.cc @@ -55,6 +55,9 @@ static bool VerifyEncryptionAndDecryptionParams(const char* flag_name, const std::string& flag_value) { DCHECK(flag_name); + const std::string flag_name_str = flag_name; + bool is_common_param = (flag_name_str == "key_server_url") || + (flag_name_str == "signer"); if (FLAGS_enable_widevine_encryption) { if (flag_value.empty()) { fprintf(stderr, @@ -63,12 +66,12 @@ static bool VerifyEncryptionAndDecryptionParams(const char* flag_name, return false; } } else if (FLAGS_enable_widevine_decryption) { - const std::string flag_name_str = flag_name; - if (flag_name_str == "key_server_url") { + if (is_common_param) { if (flag_value.empty()) { fprintf(stderr, - "ERROR: %s required if --enable_widevine_decryption is true\n", - flag_name); + "ERROR: %s required if --enable_widevine_encryption or " + "--enable_widevine_decryption is true\n", + flag_name); return false; } } else { @@ -80,9 +83,8 @@ static bool VerifyEncryptionAndDecryptionParams(const char* flag_name, } } else { if (!flag_value.empty()) { - const std::string flag_name_str = flag_name; fprintf(stderr, "ERROR: %s should only be specified if %s" - " is true\n", flag_name, flag_name_str == "key_server_url" ? + " is true\n", flag_name, is_common_param ? "--enable_widevine_encryption or --enable_widevine_decryption" : "--enable_widevine_encryption"); return false; diff --git a/media/base/decrypt_config.cc b/media/base/decrypt_config.cc index 9648df2f78..7214069e8b 100644 --- a/media/base/decrypt_config.cc +++ b/media/base/decrypt_config.cc @@ -8,8 +8,8 @@ namespace media { -DecryptConfig::DecryptConfig(const std::string& key_id, - const std::string& iv, +DecryptConfig::DecryptConfig(const std::vector& key_id, + const std::vector& iv, const int data_offset, const std::vector& subsamples) : key_id_(key_id), diff --git a/media/base/decrypt_config.h b/media/base/decrypt_config.h index 665c9e9efb..e4645a330b 100644 --- a/media/base/decrypt_config.h +++ b/media/base/decrypt_config.h @@ -19,12 +19,12 @@ namespace media { /// encrypted bytes in a sample should be considered a single logical stream, /// regardless of how they are divided into subsamples, and the clear bytes /// should not be considered as part of decryption. This is logically equivalent -/// to concatenating all @a cypher_bytes portions of subsamples, decrypting that +/// to concatenating all @a cipher_bytes portions of subsamples, decrypting that /// result, and then copying each byte from the decrypted block over the /// corresponding encrypted byte. struct SubsampleEntry { uint16 clear_bytes; - uint32 cypher_bytes; + uint32 cipher_bytes; }; /// Contains all the information that a decryptor needs to decrypt a media @@ -43,22 +43,22 @@ class DecryptConfig { /// @param subsamples defines the clear and encrypted portions of the sample /// as described in SubsampleEntry. A decrypted buffer will be equal /// in size to the sum of the subsample sizes. - DecryptConfig(const std::string& key_id, - const std::string& iv, + DecryptConfig(const std::vector& key_id, + const std::vector& iv, const int data_offset, const std::vector& subsamples); ~DecryptConfig(); - const std::string& key_id() const { return key_id_; } - const std::string& iv() const { return iv_; } + const std::vector& key_id() const { return key_id_; } + const std::vector& iv() const { return iv_; } int data_offset() const { return data_offset_; } const std::vector& subsamples() const { return subsamples_; } private: - const std::string key_id_; + const std::vector key_id_; // Initialization vector. - const std::string iv_; + const std::vector iv_; // Amount of data to be discarded before applying subsample information. const int data_offset_; diff --git a/media/base/demuxer.cc b/media/base/demuxer.cc index 9abfee5031..fe0388e026 100644 --- a/media/base/demuxer.cc +++ b/media/base/demuxer.cc @@ -11,6 +11,7 @@ #include "base/stl_util.h" #include "media/base/container_names.h" #include "media/base/decryptor_source.h" +#include "media/base/key_source.h" #include "media/base/media_sample.h" #include "media/base/media_stream.h" #include "media/base/stream_info.h" @@ -24,10 +25,8 @@ const size_t kBufSize = 0x40000; // 256KB. namespace media { -Demuxer::Demuxer(const std::string& file_name, - DecryptorSource* decryptor_source) - : decryptor_source_(decryptor_source), - file_name_(file_name), +Demuxer::Demuxer(const std::string& file_name) + : file_name_(file_name), media_file_(NULL), init_event_received_(false), buffer_(new uint8[kBufSize]) {} @@ -38,6 +37,10 @@ Demuxer::~Demuxer() { STLDeleteElements(&streams_); } +void Demuxer::SetKeySource(scoped_ptr key_source) { + key_source_ = key_source.Pass(); +} + Status Demuxer::Initialize() { DCHECK(!media_file_); DCHECK(!init_event_received_); @@ -69,7 +72,7 @@ Status Demuxer::Initialize() { parser_->Init(base::Bind(&Demuxer::ParserInitEvent, base::Unretained(this)), base::Bind(&Demuxer::NewSampleEvent, base::Unretained(this)), - base::Bind(&Demuxer::KeyNeededEvent, base::Unretained(this))); + key_source_.get()); if (!parser_->Parse(buffer_.get(), bytes_read)) return Status(error::PARSER_FAILURE, @@ -104,12 +107,6 @@ bool Demuxer::NewSampleEvent(uint32 track_id, return false; } -void Demuxer::KeyNeededEvent(MediaContainerName container, - scoped_ptr init_data, - int init_data_size) { - NOTIMPLEMENTED(); -} - Status Demuxer::Run() { Status status; diff --git a/media/base/demuxer.h b/media/base/demuxer.h index c433967893..970139c733 100644 --- a/media/base/demuxer.h +++ b/media/base/demuxer.h @@ -17,8 +17,8 @@ namespace media { class Decryptor; -class DecryptorSource; class File; +class KeySource; class MediaParser; class MediaSample; class MediaStream; @@ -31,11 +31,15 @@ class Demuxer { /// @param file_name specifies the input source. It uses prefix matching to /// create a proper File object. The user can extend File to support /// a custom File object with its own prefix. - /// @param decryptor_source generates decryptor(s) from decryption - /// initialization data. It can be NULL if the media is not encrypted. - Demuxer(const std::string& file_name, DecryptorSource* decryptor_source); + explicit Demuxer(const std::string& file_name); ~Demuxer(); + /// Set the KeySource for media decryption. + /// @param key_source points to the source of decryption keys. The key + /// source must support fetching of keys for the type of media being + /// demuxed. + void SetKeySource(scoped_ptr key_source); + /// Initialize the Demuxer. Calling other public methods of this class /// without this method returning OK, results in an undefined behavior. /// This method primes the demuxer by parsing portions of the media file to @@ -61,17 +65,14 @@ class Demuxer { void ParserInitEvent(const std::vector >& streams); bool NewSampleEvent(uint32 track_id, const scoped_refptr& sample); - void KeyNeededEvent(MediaContainerName container, - scoped_ptr init_data, - int init_data_size); - DecryptorSource* decryptor_source_; std::string file_name_; File* media_file_; bool init_event_received_; scoped_ptr parser_; std::vector streams_; scoped_ptr buffer_; + scoped_ptr key_source_; DISALLOW_COPY_AND_ASSIGN(Demuxer); }; diff --git a/media/base/key_source.cc b/media/base/key_source.cc index a61868c71f..c1293311f3 100644 --- a/media/base/key_source.cc +++ b/media/base/key_source.cc @@ -25,12 +25,12 @@ KeySource::~KeySource() {} Status KeySource::FetchKeys(const std::vector& content_id, const std::string& policy) { - NOTREACHED(); + // Do nothing for fixed key decryption. return Status::OK; } Status KeySource::FetchKeys(const std::vector& pssh_data) { - NOTREACHED(); + // Do nothing for fixed key decryption. return Status::OK; } @@ -45,6 +45,11 @@ Status KeySource::GetKey(const std::vector& key_id, EncryptionKey* key) { DCHECK(key); DCHECK(encryption_key_); + if (key_id != encryption_key_->key_id) { + return Status(error::NOT_FOUND, std::string("Key for key ID ") + + base::HexEncode(&key_id[0], key_id.size()) + + " was not found."); + } *key = *encryption_key_; return Status::OK; } diff --git a/media/base/media_parser.h b/media/base/media_parser.h index 399134b465..e7b365d092 100644 --- a/media/base/media_parser.h +++ b/media/base/media_parser.h @@ -17,6 +17,7 @@ namespace media { +class KeySource; class MediaSample; class StreamInfo; @@ -40,20 +41,16 @@ class MediaParser { bool(uint32 track_id, const scoped_refptr& media_sample)> NewSampleCB; - /// Called when a new potentially encrypted stream has been parsed. - /// @param init_data is the initialization data associated with the stream. - /// @param init_data_size is the number of bytes of the initialization data. - typedef base::Callback init_data, - int init_data_size)> NeedKeyCB; - /// Initialize the parser with necessary callbacks. Must be called before any /// data is passed to Parse(). /// @param init_cb will be called once enough data has been parsed to /// determine the initial stream configurations. + /// @param new_sample_cb will be called each time a new media sample is + /// available from the parser. May be NULL, and caller retains + /// ownership. virtual void Init(const InitCB& init_cb, const NewSampleCB& new_sample_cb, - const NeedKeyCB& need_key_cb) = 0; + KeySource* decryption_key_source) = 0; /// Flush data currently in the parser and put the parser in a state where it /// can receive data for a new seek point. diff --git a/media/base/media_sample.cc b/media/base/media_sample.cc index 7083beb72c..016d9df9bd 100644 --- a/media/base/media_sample.cc +++ b/media/base/media_sample.cc @@ -10,7 +10,6 @@ #include "base/logging.h" #include "base/strings/stringprintf.h" -#include "media/base/decrypt_config.h" namespace media { @@ -65,15 +64,13 @@ std::string MediaSample::ToString() const { return "End of stream sample\n"; return base::StringPrintf( "dts: %" PRId64 "\n pts: %" PRId64 "\n duration: %" PRId64 "\n " - "is_key_frame: %s\n size: %zu\n side_data_size: %zu\n " - "is_encrypted: %s\n", + "is_key_frame: %s\n size: %zu\n side_data_size: %zu\n", dts_, pts_, duration_, is_key_frame_ ? "true" : "false", data_.size(), - side_data_.size(), - decrypt_config_ ? "true" : "false"); + side_data_.size()); } } // namespace media diff --git a/media/base/media_sample.h b/media/base/media_sample.h index 99c23eda98..060d5dbe2d 100644 --- a/media/base/media_sample.h +++ b/media/base/media_sample.h @@ -14,12 +14,9 @@ #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" -#include "media/base/decrypt_config.h" namespace media { -class DecryptConfig; - /// Class to hold a media sample. class MediaSample : public base::RefCountedThreadSafe { public: @@ -113,16 +110,6 @@ class MediaSample : public base::RefCountedThreadSafe { return side_data_.size(); } - const DecryptConfig* decrypt_config() const { - DCHECK(!end_of_stream()); - return decrypt_config_.get(); - } - - void set_decrypt_config(scoped_ptr decrypt_config) { - DCHECK(!end_of_stream()); - decrypt_config_ = decrypt_config.Pass(); - } - // If there's no data in this buffer, it represents end of stream. bool end_of_stream() const { return data_.size() == 0; } @@ -155,7 +142,6 @@ class MediaSample : public base::RefCountedThreadSafe { // http://www.matroska.org/technical/specs/index.html BlockAdditional[A5]. // Not used by mp4 and other containers. std::vector side_data_; - scoped_ptr decrypt_config_; DISALLOW_COPY_AND_ASSIGN(MediaSample); }; diff --git a/media/base/status.h b/media/base/status.h index c227de6f39..7fb94d198e 100644 --- a/media/base/status.h +++ b/media/base/status.h @@ -65,6 +65,9 @@ enum Code { // The operation timed out. TIME_OUT, + + // Value was not found. + NOT_FOUND, }; } // namespace error diff --git a/media/formats/mp2t/mp2t_media_parser.cc b/media/formats/mp2t/mp2t_media_parser.cc index 97dc372633..fbb1155b8d 100644 --- a/media/formats/mp2t/mp2t_media_parser.cc +++ b/media/formats/mp2t/mp2t_media_parser.cc @@ -155,16 +155,14 @@ Mp2tMediaParser::~Mp2tMediaParser() { void Mp2tMediaParser::Init( const InitCB& init_cb, const NewSampleCB& new_sample_cb, - const NeedKeyCB& need_key_cb) { + KeySource* decryption_key_source) { DCHECK(!is_initialized_); DCHECK(init_cb_.is_null()); DCHECK(!init_cb.is_null()); DCHECK(!new_sample_cb.is_null()); - DCHECK(!need_key_cb.is_null()); init_cb_ = init_cb; new_sample_cb_ = new_sample_cb; - need_key_cb_ = need_key_cb; } void Mp2tMediaParser::Flush() { diff --git a/media/formats/mp2t/mp2t_media_parser.h b/media/formats/mp2t/mp2t_media_parser.h index a5e242a3e9..8bf525e8fe 100644 --- a/media/formats/mp2t/mp2t_media_parser.h +++ b/media/formats/mp2t/mp2t_media_parser.h @@ -34,7 +34,7 @@ class Mp2tMediaParser : public MediaParser { // MediaParser implementation overrides. virtual void Init(const InitCB& init_cb, const NewSampleCB& new_sample_cb, - const NeedKeyCB& need_key_cb) OVERRIDE; + KeySource* decryption_key_source) OVERRIDE; virtual void Flush() OVERRIDE; @@ -74,7 +74,6 @@ class Mp2tMediaParser : public MediaParser { // List of callbacks. InitCB init_cb_; NewSampleCB new_sample_cb_; - NeedKeyCB need_key_cb_; bool sbr_in_mimetype_; diff --git a/media/formats/mp2t/mp2t_media_parser_unittest.cc b/media/formats/mp2t/mp2t_media_parser_unittest.cc index fe57a4807b..f91d803d95 100644 --- a/media/formats/mp2t/mp2t_media_parser_unittest.cc +++ b/media/formats/mp2t/mp2t_media_parser_unittest.cc @@ -97,20 +97,13 @@ class Mp2tMediaParserTest : public testing::Test { return true; } - void OnKeyNeeded(MediaContainerName container_name, - scoped_ptr init_data, - int init_data_size) { - DVLOG(1) << "OnKeyNeeded: " << init_data_size; - } - void InitializeParser() { parser_->Init( base::Bind(&Mp2tMediaParserTest::OnInit, base::Unretained(this)), base::Bind(&Mp2tMediaParserTest::OnNewSample, base::Unretained(this)), - base::Bind(&Mp2tMediaParserTest::OnKeyNeeded, - base::Unretained(this))); + NULL); } bool ParseMpeg2TsFile(const std::string& filename, int append_bytes) { diff --git a/media/formats/mp4/cenc.cc b/media/formats/mp4/cenc.cc index 4014b83636..845681faca 100644 --- a/media/formats/mp4/cenc.cc +++ b/media/formats/mp4/cenc.cc @@ -15,7 +15,7 @@ namespace { // 64-bit (8-byte) or 128-bit (16-byte). bool IsIvSizeValid(size_t iv_size) { return iv_size == 8 || iv_size == 16; } -// 16-bit |clear_bytes| and 32-bit |cypher_bytes|. +// 16-bit |clear_bytes| and 32-bit |cipher_bytes|. const size_t kSubsampleEntrySize = sizeof(uint16) + sizeof(uint32); } // namespace @@ -44,11 +44,11 @@ bool FrameCENCInfo::Parse(uint8 iv_size, BufferReader* reader) { subsamples_.resize(subsample_count); for (uint16 i = 0; i < subsample_count; ++i) { uint16 clear_bytes; - uint32 cypher_bytes; + uint32 cipher_bytes; RCHECK(reader->Read2(&clear_bytes) && - reader->Read4(&cypher_bytes)); + reader->Read4(&cipher_bytes)); subsamples_[i].clear_bytes = clear_bytes; - subsamples_[i].cypher_bytes = cypher_bytes; + subsamples_[i].cipher_bytes = cipher_bytes; } return true; } @@ -65,7 +65,7 @@ void FrameCENCInfo::Write(BufferWriter* writer) const { for (uint16 i = 0; i < subsample_count; ++i) { writer->AppendInt(subsamples_[i].clear_bytes); - writer->AppendInt(subsamples_[i].cypher_bytes); + writer->AppendInt(subsamples_[i].cipher_bytes); } } @@ -81,7 +81,7 @@ size_t FrameCENCInfo::ComputeSize() const { size_t FrameCENCInfo::GetTotalSizeOfSubsamples() const { size_t size = 0; for (size_t i = 0; i < subsamples_.size(); ++i) { - size += subsamples_[i].clear_bytes + subsamples_[i].cypher_bytes; + size += subsamples_[i].clear_bytes + subsamples_[i].cipher_bytes; } return size; } diff --git a/media/formats/mp4/encrypting_fragmenter.cc b/media/formats/mp4/encrypting_fragmenter.cc index c6e0615871..11d3c52de9 100644 --- a/media/formats/mp4/encrypting_fragmenter.cc +++ b/media/formats/mp4/encrypting_fragmenter.cc @@ -144,13 +144,13 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr sample) { SubsampleEntry subsample; subsample.clear_bytes = nalu_length_size_ + 1; - subsample.cypher_bytes = nalu_length - 1; + subsample.cipher_bytes = nalu_length - 1; if (!reader.SkipBytes(nalu_length)) { return Status(error::MUXER_FAILURE, "Sample size does not match nalu_length."); } - EncryptBytes(data + subsample.clear_bytes, subsample.cypher_bytes); + EncryptBytes(data + subsample.clear_bytes, subsample.cipher_bytes); cenc_info.AddSubsample(subsample); data += nalu_length_size_ + nalu_length; } diff --git a/media/formats/mp4/mp4_media_parser.cc b/media/formats/mp4/mp4_media_parser.cc index b97893b1bc..7085d09dbf 100644 --- a/media/formats/mp4/mp4_media_parser.cc +++ b/media/formats/mp4/mp4_media_parser.cc @@ -8,7 +8,11 @@ #include "base/callback_helpers.h" #include "base/logging.h" #include "base/memory/ref_counted.h" +#include "base/strings/string_number_conversions.h" +#include "media/base/aes_encryptor.h" #include "media/base/audio_stream_info.h" +#include "media/base/decrypt_config.h" +#include "media/base/key_source.h" #include "media/base/media_sample.h" #include "media/base/video_stream_info.h" #include "media/formats/mp4/box_definitions.h" @@ -23,6 +27,9 @@ uint64 Rescale(uint64 time_in_old_scale, uint32 old_scale, uint32 new_scale) { return (static_cast(time_in_old_scale) / old_scale) * new_scale; } + +const char kWidevineKeySystemId[] = "edef8ba979d64acea3c827dcd51d21ed"; + } // namespace namespace media { @@ -31,21 +38,22 @@ namespace mp4 { MP4MediaParser::MP4MediaParser() : state_(kWaitingForInit), moof_head_(0), mdat_tail_(0) {} -MP4MediaParser::~MP4MediaParser() {} +MP4MediaParser::~MP4MediaParser() { + STLDeleteValues(&decryptor_map_); +} void MP4MediaParser::Init(const InitCB& init_cb, const NewSampleCB& new_sample_cb, - const NeedKeyCB& need_key_cb) { + KeySource* decryption_key_source) { DCHECK_EQ(state_, kWaitingForInit); DCHECK(init_cb_.is_null()); DCHECK(!init_cb.is_null()); DCHECK(!new_sample_cb.is_null()); - DCHECK(!need_key_cb.is_null()); ChangeState(kParsingBoxes); init_cb_ = init_cb; new_sample_cb_ = new_sample_cb; - need_key_cb_ = need_key_cb; + decryption_key_source_ = decryption_key_source; } void MP4MediaParser::Reset() { @@ -292,7 +300,8 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { } init_cb_.Run(streams); - EmitNeedKeyIfNecessary(moov_->pssh); + if (!FetchKeysIfNecessary(moov_->pssh)) + return false; runs_.reset(new TrackRunIterator(moov_.get())); RCHECK(runs_->Init()); ChangeState(kEmittingSamples); @@ -307,29 +316,40 @@ bool MP4MediaParser::ParseMoof(BoxReader* reader) { if (!runs_) runs_.reset(new TrackRunIterator(moov_.get())); RCHECK(runs_->Init(moof)); - EmitNeedKeyIfNecessary(moof.pssh); + if (!FetchKeysIfNecessary(moof.pssh)) + return false; ChangeState(kEmittingSamples); return true; } -void MP4MediaParser::EmitNeedKeyIfNecessary( +bool MP4MediaParser::FetchKeysIfNecessary( const std::vector& headers) { if (headers.empty()) - return; + return true; - size_t total_size = 0; - for (size_t i = 0; i < headers.size(); i++) - total_size += headers[i].raw_box.size(); - - scoped_ptr init_data(new uint8[total_size]); - size_t pos = 0; - for (size_t i = 0; i < headers.size(); i++) { - memcpy(&init_data.get()[pos], - &headers[i].raw_box[0], - headers[i].raw_box.size()); - pos += headers[i].raw_box.size(); + if (!decryption_key_source_) { + LOG(ERROR) << "Content is encrypted, but content decryption not enabled."; + return false; } - need_key_cb_.Run(CONTAINER_MOV, init_data.Pass(), total_size); + + // TODO(tinskip): Pass in raw 'pssh' boxes to FetchKeys. This will allow + // supporting multiple keysystems. Move this to KeySource. + std::vector widevine_system_id; + base::HexStringToBytes(kWidevineKeySystemId, &widevine_system_id); + for (std::vector::const_iterator iter = + headers.begin(); iter != headers.end(); ++iter) { + if (iter->system_id == widevine_system_id) { + Status status = decryption_key_source_->FetchKeys(iter->data); + if (!status.ok()) { + LOG(ERROR) << "Error fetching decryption keys: " << status; + return false; + } + return true; + } + } + + LOG(ERROR) << "No viable 'pssh' box found for content decryption."; + return false; } bool MP4MediaParser::EnqueueSample(bool* err) { @@ -379,35 +399,20 @@ bool MP4MediaParser::EnqueueSample(bool* err) { if (buf_size < runs_->sample_size()) return false; - scoped_ptr decrypt_config; - std::vector subsamples; + scoped_refptr stream_sample(MediaSample::CopyFrom( + buf, runs_->sample_size(), runs_->is_keyframe())); if (runs_->is_encrypted()) { - decrypt_config = runs_->GetDecryptConfig(); + scoped_ptr decrypt_config = runs_->GetDecryptConfig(); if (!decrypt_config) { *err = true; return false; } - subsamples = decrypt_config->subsamples(); + if (!DecryptSampleBuffer(decrypt_config.get(), + stream_sample->writable_data(), + stream_sample->data_size())) + return false; } - if (decrypt_config) { - if (!subsamples.empty()) { - // Create a new config with the updated subsamples. - decrypt_config.reset(new DecryptConfig(decrypt_config->key_id(), - decrypt_config->iv(), - decrypt_config->data_offset(), - subsamples)); - } - // else, use the existing config. - } - - std::vector frame_buf(buf, buf + runs_->sample_size()); - scoped_refptr stream_sample = MediaSample::CopyFrom( - &frame_buf[0], frame_buf.size(), runs_->is_keyframe()); - - if (decrypt_config) - stream_sample->set_decrypt_config(decrypt_config.Pass()); - stream_sample->set_dts(runs_->dts()); stream_sample->set_pts(runs_->cts()); stream_sample->set_duration(runs_->duration()); @@ -425,6 +430,80 @@ bool MP4MediaParser::EnqueueSample(bool* err) { return true; } +bool MP4MediaParser::DecryptSampleBuffer(const DecryptConfig* decrypt_config, + uint8* 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 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& subsamples = decrypt_config->subsamples(); + uint8* current_ptr = buffer; + const uint8* 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::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 offset) { bool err = false; while (mdat_tail_ < offset) { diff --git a/media/formats/mp4/mp4_media_parser.h b/media/formats/mp4/mp4_media_parser.h index cebae6c99e..0b7614d11f 100644 --- a/media/formats/mp4/mp4_media_parser.h +++ b/media/formats/mp4/mp4_media_parser.h @@ -7,17 +7,23 @@ #ifndef MEDIA_FORMATS_MP4_MP4_MEDIA_PARSER_H_ #define MEDIA_FORMATS_MP4_MP4_MEDIA_PARSER_H_ +#include #include #include "base/basictypes.h" #include "base/callback_forward.h" #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" #include "media/base/media_parser.h" #include "media/base/offset_byte_queue.h" namespace media { +class AesCtrEncryptor; +class DecryptConfig; + namespace mp4 { class BoxReader; @@ -34,7 +40,7 @@ class MP4MediaParser : public MediaParser { /// @{ virtual void Init(const InitCB& init_cb, const NewSampleCB& new_sample_cb, - const NeedKeyCB& need_key_cb) OVERRIDE; + KeySource* decryption_key_source) OVERRIDE; virtual void Flush() OVERRIDE; virtual bool Parse(const uint8* buf, int size) OVERRIDE; /// @} @@ -51,9 +57,13 @@ class MP4MediaParser : public MediaParser { bool ParseMoov(mp4::BoxReader* reader); bool ParseMoof(mp4::BoxReader* reader); - void EmitNeedKeyIfNecessary( + bool FetchKeysIfNecessary( const std::vector& headers); + bool DecryptSampleBuffer(const DecryptConfig* decrypt_config, + uint8* buffer, + size_t buffer_size); + // To retain proper framing, each 'mdat' atom must be read; to limit memory // usage, the atom's data needs to be discarded incrementally as frames are // extracted from the stream. This function discards data from the stream up @@ -73,7 +83,7 @@ class MP4MediaParser : public MediaParser { State state_; InitCB init_cb_; NewSampleCB new_sample_cb_; - NeedKeyCB need_key_cb_; + KeySource* decryption_key_source_; OffsetByteQueue queue_; @@ -90,6 +100,9 @@ class MP4MediaParser : public MediaParser { scoped_ptr moov_; scoped_ptr runs_; + typedef std::map, AesCtrEncryptor*> DecryptorMap; + DecryptorMap decryptor_map_; + DISALLOW_COPY_AND_ASSIGN(MP4MediaParser); }; diff --git a/media/formats/mp4/mp4_media_parser_unittest.cc b/media/formats/mp4/mp4_media_parser_unittest.cc index c67588b475..f567a7b96b 100644 --- a/media/formats/mp4/mp4_media_parser_unittest.cc +++ b/media/formats/mp4/mp4_media_parser_unittest.cc @@ -53,19 +53,11 @@ class MP4MediaParserTest : public testing::Test { return true; } - void KeyNeededF(MediaContainerName container, - scoped_ptr init_data, - int init_data_size) { - DVLOG(1) << "KeyNeededF: " << init_data_size; - EXPECT_TRUE(init_data.get()); - EXPECT_GT(init_data_size, 0); - } - void InitializeParser() { parser_->Init( base::Bind(&MP4MediaParserTest::InitF, base::Unretained(this)), base::Bind(&MP4MediaParserTest::NewSampleF, base::Unretained(this)), - base::Bind(&MP4MediaParserTest::KeyNeededF, base::Unretained(this))); + NULL); } bool ParseMP4File(const std::string& filename, int append_bytes) { diff --git a/media/formats/mp4/track_run_iterator.cc b/media/formats/mp4/track_run_iterator.cc index b6c82d93c2..f128fc3e77 100644 --- a/media/formats/mp4/track_run_iterator.cc +++ b/media/formats/mp4/track_run_iterator.cc @@ -541,10 +541,9 @@ scoped_ptr TrackRunIterator::GetDecryptConfig() { return scoped_ptr(); } - const std::vector& kid = track_encryption().default_kid; return scoped_ptr(new DecryptConfig( - std::string(reinterpret_cast(&kid[0]), kid.size()), - std::string(cenc_info.iv().begin(), cenc_info.iv().end()), + track_encryption().default_kid, + cenc_info.iv(), 0, // No offset to start of media data in MP4 using CENC. cenc_info.subsamples())); } diff --git a/media/formats/mp4/track_run_iterator_unittest.cc b/media/formats/mp4/track_run_iterator_unittest.cc index 8fd879d7e7..4dfc6501d4 100644 --- a/media/formats/mp4/track_run_iterator_unittest.cc +++ b/media/formats/mp4/track_run_iterator_unittest.cc @@ -336,7 +336,7 @@ TEST_F(TrackRunIteratorTest, DecryptConfigTest) { config = iter_->GetDecryptConfig(); EXPECT_EQ(config->subsamples().size(), 2u); EXPECT_EQ(config->subsamples()[0].clear_bytes, 1u); - EXPECT_EQ(config->subsamples()[1].cypher_bytes, 4u); + EXPECT_EQ(config->subsamples()[1].cipher_bytes, 4u); } // It is legal for aux info blocks to be shared among multiple formats. diff --git a/media/test/packager_test.cc b/media/test/packager_test.cc index 335b699122..a075fd349e 100644 --- a/media/test/packager_test.cc +++ b/media/test/packager_test.cc @@ -84,7 +84,7 @@ class FakeClock : public base::Clock { class PackagerTestBasic : public ::testing::TestWithParam { public: - PackagerTestBasic() : decryptor_source_(NULL) {} + PackagerTestBasic() {} virtual void SetUp() OVERRIDE { // Create a test directory for testing, will be deleted after test. @@ -108,9 +108,12 @@ class PackagerTestBasic : public ::testing::TestWithParam { bool single_segment, bool enable_encryption); + void Decrypt(const std::string& input, + const std::string& video_output, + const std::string& audio_output); + protected: base::FilePath test_directory_; - DecryptorSource* decryptor_source_; FakeClock fake_clock_; }; @@ -148,7 +151,7 @@ void PackagerTestBasic::Remux(const std::string& input, bool enable_encryption) { CHECK(!video_output.empty() || !audio_output.empty()); - Demuxer demuxer(GetFullPath(input), decryptor_source_); + Demuxer demuxer(GetFullPath(input)); ASSERT_OK(demuxer.Initialize()); scoped_ptr encryption_key_source( @@ -182,9 +185,9 @@ void PackagerTestBasic::Remux(const std::string& input, if (enable_encryption) { muxer_audio->SetKeySource(encryption_key_source.get(), - KeySource::TRACK_TYPE_SD, - kClearLeadInSeconds, - kCryptoDurationInSeconds); + KeySource::TRACK_TYPE_SD, + kClearLeadInSeconds, + kCryptoDurationInSeconds); } } @@ -192,6 +195,39 @@ void PackagerTestBasic::Remux(const std::string& input, ASSERT_OK(demuxer.Run()); } +void PackagerTestBasic::Decrypt(const std::string& input, + const std::string& video_output, + const std::string& audio_output) { + CHECK(!video_output.empty() || !audio_output.empty()); + + Demuxer demuxer(GetFullPath(input)); + scoped_ptr decryption_key_source( + KeySource::CreateFromHexStrings(kKeyIdHex, kKeyHex, "", "")); + DCHECK(decryption_key_source); + demuxer.SetKeySource(decryption_key_source.Pass()); + ASSERT_OK(demuxer.Initialize()); + + scoped_ptr muxer; + MediaStream* stream(NULL); + if (!video_output.empty()) { + muxer.reset( + new mp4::MP4Muxer(SetupOptions(video_output, true))); + stream = FindFirstVideoStream(demuxer.streams()); + } + if (!audio_output.empty()) { + muxer.reset( + new mp4::MP4Muxer(SetupOptions(audio_output, true))); + stream = FindFirstAudioStream(demuxer.streams()); + } + ASSERT_TRUE(muxer); + ASSERT_TRUE(stream != NULL); + ASSERT_TRUE(stream->info()->is_encrypted()); + muxer->set_clock(&fake_clock_); + muxer->AddStream(stream); + + ASSERT_OK(demuxer.Run()); +} + TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentUnencryptedVideo) { ASSERT_NO_FATAL_FAILURE(Remux(GetParam(), kOutputVideo, @@ -215,11 +251,9 @@ TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentEncryptedVideo) { kSingleSegment, kEnableEncryption)); - // Expect the output to be encrypted. - Demuxer demuxer(GetFullPath(kOutputVideo), decryptor_source_); - ASSERT_OK(demuxer.Initialize()); - ASSERT_EQ(1u, demuxer.streams().size()); - EXPECT_TRUE(demuxer.streams()[0]->info()->is_encrypted()); + ASSERT_NO_FATAL_FAILURE(Decrypt(kOutputVideo, + kOutputVideo2, + kOutputNone)); } TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentEncryptedAudio) { @@ -229,11 +263,9 @@ TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentEncryptedAudio) { kSingleSegment, kEnableEncryption)); - // Expect the output to be encrypted. - Demuxer demuxer(GetFullPath(kOutputAudio), decryptor_source_); - ASSERT_OK(demuxer.Initialize()); - ASSERT_EQ(1u, demuxer.streams().size()); - EXPECT_TRUE(demuxer.streams()[0]->info()->is_encrypted()); + ASSERT_NO_FATAL_FAILURE(Decrypt(kOutputAudio, + kOutputNone, + kOutputAudio2)); } class PackagerTest : public PackagerTestBasic {