diff options
-rw-r--r-- | media/base/decrypt_config.cc | 20 | ||||
-rw-r--r-- | media/base/decrypt_config.h | 39 | ||||
-rw-r--r-- | media/crypto/aes_decryptor.cc | 186 | ||||
-rw-r--r-- | media/crypto/aes_decryptor.h | 46 | ||||
-rw-r--r-- | media/crypto/aes_decryptor_unittest.cc | 368 | ||||
-rw-r--r-- | media/filters/ffmpeg_video_decoder_unittest.cc | 63 | ||||
-rw-r--r-- | media/filters/pipeline_integration_test.cc | 16 | ||||
-rw-r--r-- | media/webm/webm_cluster_parser.cc | 43 | ||||
-rw-r--r-- | media/webm/webm_cluster_parser.h | 4 | ||||
-rw-r--r-- | media/webm/webm_constants.h | 7 | ||||
-rw-r--r-- | media/webm/webm_content_encodings.cc | 3 | ||||
-rw-r--r-- | media/webm/webm_content_encodings.h | 9 | ||||
-rw-r--r-- | media/webm/webm_content_encodings_client.cc | 29 | ||||
-rw-r--r-- | media/webm/webm_parser.cc | 9 | ||||
-rw-r--r-- | media/webm/webm_stream_parser.cc | 1 |
15 files changed, 723 insertions, 120 deletions
diff --git a/media/base/decrypt_config.cc b/media/base/decrypt_config.cc index 9ae5f19..fb502df 100644 --- a/media/base/decrypt_config.cc +++ b/media/base/decrypt_config.cc @@ -8,11 +8,25 @@ namespace media { -DecryptConfig::DecryptConfig(const uint8* key_id, int key_id_size) - : key_id_size_(key_id_size) { +DecryptConfig::DecryptConfig(const uint8* key_id, int key_id_size, + const uint8* iv, int iv_size, + const uint8* checksum, int checksum_size, + int encrypted_frame_offset) + : key_id_(new uint8[key_id_size]), + key_id_size_(key_id_size), + iv_(new uint8[iv_size]), + iv_size_(iv_size), + checksum_(checksum_size > 0 ? new uint8[checksum_size] : NULL), + checksum_size_(checksum_size), + encrypted_frame_offset_(encrypted_frame_offset) { CHECK_GT(key_id_size, 0); - key_id_.reset(new uint8[key_id_size]); + CHECK_EQ(iv_size, DecryptConfig::kDecryptionKeySize); + CHECK_GE(checksum_size, 0); + CHECK_GE(encrypted_frame_offset, 0); memcpy(key_id_.get(), key_id, key_id_size); + memcpy(iv_.get(), iv, iv_size); + if (checksum_size > 0) + memcpy(checksum_.get(), checksum, checksum_size); } DecryptConfig::~DecryptConfig() {} diff --git a/media/base/decrypt_config.h b/media/base/decrypt_config.h index 5fca787..68a3fbd 100644 --- a/media/base/decrypt_config.h +++ b/media/base/decrypt_config.h @@ -11,18 +11,49 @@ namespace media { -// Contains all information that a decryptor needs to decrypt. +// Contains all information that a decryptor needs to decrypt a frame. class MEDIA_EXPORT DecryptConfig { public: - explicit DecryptConfig(const uint8* key_id, int key_id_size); + // Keys are always 128 bits. + static const int kDecryptionKeySize = 16; + + // |key_id| is the ID that references the decryption key for this frame. |iv| + // is the initialization vector defined by the encrypted format. Currently + // |iv_size| must be 16 bytes as defined by WebM and ISO. |checksum| is the + // hash value of the encrypted buffer. |checksum| is defined by the + // encrypted format and may be NULL. |encrypted_frame_offset| is the offset + // into the encrypted buffer that the encrypted frame starts. The class + // will copy the data from |key_id|, |iv|, and |checksum|. + DecryptConfig(const uint8* key_id, int key_id_size, + const uint8* iv, int iv_size, + const uint8* checksum, int checksum_size, + int encrypted_frame_offset); ~DecryptConfig(); const uint8* key_id() const { return key_id_.get(); } int key_id_size() const { return key_id_size_; } + const uint8* iv() const { return iv_.get(); } + int iv_size() const { return iv_size_; } + const uint8* checksum() const { return checksum_.get(); } + int checksum_size() const { return checksum_size_; } + int encrypted_frame_offset() const { return encrypted_frame_offset_; } private: - scoped_array<uint8> key_id_; - int key_id_size_; + const scoped_array<uint8> key_id_; + const int key_id_size_; + + // Initialization vector. + const scoped_array<uint8> iv_; + const int iv_size_; + + // Checksum of the data to be verified before decrypting the data. This may + // be NULL for some formats. + const scoped_array<uint8> checksum_; + const int checksum_size_; + + // This is the offset in bytes to where the encrypted data starts within + // the input buffer. + const int encrypted_frame_offset_; DISALLOW_COPY_AND_ASSIGN(DecryptConfig); }; diff --git a/media/crypto/aes_decryptor.cc b/media/crypto/aes_decryptor.cc index f677f0d..e69f406 100644 --- a/media/crypto/aes_decryptor.cc +++ b/media/crypto/aes_decryptor.cc @@ -7,8 +7,8 @@ #include "base/logging.h" #include "base/stl_util.h" #include "base/string_number_conversions.h" -#include "base/string_piece.h" #include "crypto/encryptor.h" +#include "crypto/hmac.h" #include "crypto/symmetric_key.h" #include "media/base/decoder_buffer.h" #include "media/base/decrypt_config.h" @@ -16,31 +16,111 @@ namespace media { -// TODO(xhwang): Get real IV from frames. -static const char kInitialCounter[] = "0000000000000000"; +// The size is from the WebM encrypted specification. Current encrypted WebM +// request for comments specification is here +// http://wiki.webmproject.org/encryption/webm-encryption-rfc +static const int kWebmSha1DigestSize = 20; +static const char kWebmHmacSeed[] = "hmac-key"; +static const char kWebmEncryptionSeed[] = "encryption-key"; uint32 AesDecryptor::next_session_id_ = 1; -// Decrypt |input| using |key|. -// Return a DecoderBuffer with the decrypted data if decryption succeeded. -// Return NULL if decryption failed. +// Derives a key using SHA1 HMAC. |secret| is the base secret to derive +// the key from. |seed| is the known message to the HMAC algorithm. |key_size| +// is how many bytes are returned in the key. Returns a string containing the +// key on success. Returns an empty string on failure. +static std::string DeriveKey(const base::StringPiece& secret, + const base::StringPiece& seed, + int key_size) { + CHECK(!secret.empty()); + CHECK(!seed.empty()); + CHECK_GT(key_size, 0); + + crypto::HMAC hmac(crypto::HMAC::SHA1); + if (!hmac.Init(secret)) { + DVLOG(1) << "Could not initialize HMAC with secret data."; + return std::string(); + } + + scoped_array<uint8> calculated_hmac(new uint8[hmac.DigestLength()]); + if (!hmac.Sign(seed, calculated_hmac.get(), hmac.DigestLength())) { + DVLOG(1) << "Could not calculate HMAC."; + return std::string(); + } + + return std::string(reinterpret_cast<const char*>(calculated_hmac.get()), + key_size); +} + +// Checks data in |input| matches the HMAC in |input|. The check is using the +// SHA1 algorithm. |hmac_key| is the key of the HMAC algorithm. Returns true if +// the integrity check passes. +static bool CheckData(const DecoderBuffer& input, + const base::StringPiece& hmac_key) { + CHECK(input.GetDataSize()); + CHECK(input.GetDecryptConfig()); + CHECK_GT(input.GetDecryptConfig()->checksum_size(), 0); + CHECK(!hmac_key.empty()); + + crypto::HMAC hmac(crypto::HMAC::SHA1); + if (!hmac.Init(hmac_key)) + return false; + + // The HMAC covers the IV and the frame data. + base::StringPiece data_to_check( + reinterpret_cast<const char*>(input.GetData()), input.GetDataSize()); + + scoped_array<uint8> calculated_hmac(new uint8[hmac.DigestLength()]); + if (!hmac.Sign(data_to_check, calculated_hmac.get(), hmac.DigestLength())) + return false; + + DCHECK(input.GetDecryptConfig()->checksum_size() <= + static_cast<int>(hmac.DigestLength())); + if (memcmp(input.GetDecryptConfig()->checksum(), + calculated_hmac.get(), + input.GetDecryptConfig()->checksum_size()) != 0) + return false; + return true; +} + +// Decrypts |input| using |key|. |encrypted_data_offset| is the number of bytes +// into |input| that the encrypted data starts. +// Returns a DecoderBuffer with the decrypted data if decryption succeeded or +// NULL if decryption failed. static scoped_refptr<DecoderBuffer> DecryptData(const DecoderBuffer& input, - crypto::SymmetricKey* key) { + crypto::SymmetricKey* key, + int encrypted_data_offset) { CHECK(input.GetDataSize()); + CHECK(input.GetDecryptConfig()); CHECK(key); - // Initialize encryption data. - // The IV must be exactly as long as the cipher block size. + // Initialize decryptor. crypto::Encryptor encryptor; - if (!encryptor.Init(key, crypto::Encryptor::CBC, kInitialCounter)) { - DVLOG(1) << "Could not initialize encryptor."; + if (!encryptor.Init(key, crypto::Encryptor::CTR, "")) { + DVLOG(1) << "Could not initialize decryptor."; + return NULL; + } + + DCHECK_EQ(input.GetDecryptConfig()->iv_size(), + DecryptConfig::kDecryptionKeySize); + // Set the counter block. + base::StringPiece counter_block( + reinterpret_cast<const char*>(input.GetDecryptConfig()->iv()), + input.GetDecryptConfig()->iv_size()); + if (counter_block.empty()) { + DVLOG(1) << "Could not generate counter block."; + return NULL; + } + if (!encryptor.SetCounter(counter_block)) { + DVLOG(1) << "Could not set counter block."; return NULL; } std::string decrypted_text; - base::StringPiece encrypted_text( - reinterpret_cast<const char*>(input.GetData()), - input.GetDataSize()); + const char* frame = + reinterpret_cast<const char*>(input.GetData() + encrypted_data_offset); + int frame_size = input.GetDataSize() - encrypted_data_offset; + base::StringPiece encrypted_text(frame, frame_size); if (!encryptor.Decrypt(encrypted_text, &decrypted_text)) { DVLOG(1) << "Could not decrypt data."; return NULL; @@ -85,9 +165,7 @@ void AesDecryptor::AddKey(const std::string& key_system, // TODO(xhwang): Add |session_id| check after we figure out how: // https://www.w3.org/Bugs/Public/show_bug.cgi?id=16550 - - const int kSupportedKeyLength = 16; // 128-bit key. - if (key_length != kSupportedKeyLength) { + if (key_length != DecryptConfig::kDecryptionKeySize) { DVLOG(1) << "Invalid key length: " << key_length; client_->KeyError(key_system, session_id, Decryptor::kUnknownError, 0); return; @@ -106,10 +184,17 @@ void AesDecryptor::AddKey(const std::string& key_system, std::string key_id_string(reinterpret_cast<const char*>(init_data), init_data_length); std::string key_string(reinterpret_cast<const char*>(key) , key_length); - crypto::SymmetricKey* symmetric_key = crypto::SymmetricKey::Import( - crypto::SymmetricKey::AES, key_string); - if (!symmetric_key) { - DVLOG(1) << "Could not import key."; + scoped_ptr<DecryptionKey> decryption_key(new DecryptionKey(key_string)); + if (!decryption_key.get()) { + DVLOG(1) << "Could not create key."; + client_->KeyError(key_system, session_id, Decryptor::kUnknownError, 0); + return; + } + + // TODO(fgalligan): When ISO is added we will need to figure out how to + // detect if the encrypted data will contain an HMAC. + if (!decryption_key->Init(true)) { + DVLOG(1) << "Could not initialize decryption key."; client_->KeyError(key_system, session_id, Decryptor::kUnknownError, 0); return; } @@ -121,7 +206,7 @@ void AesDecryptor::AddKey(const std::string& key_system, delete found->second; key_map_.erase(found); } - key_map_[key_id_string] = symmetric_key; + key_map_[key_id_string] = decryption_key.release(); } client_->KeyAdded(key_system, session_id); @@ -140,7 +225,7 @@ void AesDecryptor::Decrypt(const scoped_refptr<DecoderBuffer>& encrypted, // TODO(xhwang): Avoid always constructing a string with StringPiece? std::string key_id_string(reinterpret_cast<const char*>(key_id), key_id_size); - crypto::SymmetricKey* key = NULL; + DecryptionKey* key = NULL; { base::AutoLock auto_lock(key_map_lock_); KeyMap::const_iterator found = key_map_.find(key_id_string); @@ -149,13 +234,28 @@ void AesDecryptor::Decrypt(const scoped_refptr<DecoderBuffer>& encrypted, } if (!key) { + // TODO(fgalligan): Fire a need_key event here and add a test. DVLOG(1) << "Could not find a matching key for given key ID."; decrypt_cb.Run(kError, NULL); return; } - scoped_refptr<DecoderBuffer> decrypted = DecryptData(*encrypted, key); + int checksum_size = encrypted->GetDecryptConfig()->checksum_size(); + // According to the WebM encrypted specification, it is an open question + // what should happen when a frame fails the integrity check. + // http://wiki.webmproject.org/encryption/webm-encryption-rfc + if (checksum_size > 0 && + !key->hmac_key().empty() && + !CheckData(*encrypted, key->hmac_key())) { + DVLOG(1) << "Integrity check failed."; + decrypt_cb.Run(kError, NULL); + return; + } + scoped_refptr<DecoderBuffer> decrypted = + DecryptData(*encrypted, + key->decryption_key(), + encrypted->GetDecryptConfig()->encrypted_frame_offset()); if (!decrypted) { DVLOG(1) << "Decryption failed."; decrypt_cb.Run(kError, NULL); @@ -167,4 +267,42 @@ void AesDecryptor::Decrypt(const scoped_refptr<DecoderBuffer>& encrypted, decrypt_cb.Run(kSuccess, decrypted); } +AesDecryptor::DecryptionKey::DecryptionKey( + const std::string& secret) + : secret_(secret) { +} + +AesDecryptor::DecryptionKey::~DecryptionKey() {} + +bool AesDecryptor::DecryptionKey::Init(bool derive_webm_keys) { + CHECK(!secret_.empty()); + + if (derive_webm_keys) { + std::string raw_key = DeriveKey(secret_, + kWebmEncryptionSeed, + secret_.length()); + if (raw_key.empty()) { + return false; + } + decryption_key_.reset( + crypto::SymmetricKey::Import(crypto::SymmetricKey::AES, raw_key)); + if (!decryption_key_.get()) { + return false; + } + + hmac_key_ = DeriveKey(secret_, kWebmHmacSeed, kWebmSha1DigestSize); + if (hmac_key_.empty()) { + return false; + } + return true; + } + + decryption_key_.reset( + crypto::SymmetricKey::Import(crypto::SymmetricKey::AES, secret_)); + if (!decryption_key_.get()) { + return false; + } + return true; +} + } // namespace media diff --git a/media/crypto/aes_decryptor.h b/media/crypto/aes_decryptor.h index 6d0bf47..224035c 100644 --- a/media/crypto/aes_decryptor.h +++ b/media/crypto/aes_decryptor.h @@ -10,6 +10,8 @@ #include "base/basictypes.h" #include "base/hash_tables.h" #include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_piece.h" #include "base/synchronization/lock.h" #include "media/base/decryptor.h" #include "media/base/media_export.h" @@ -22,7 +24,9 @@ namespace media { class DecryptorClient; -// Decryptor implementation that decrypts AES-encrypted buffer. +// Decrypts an AES encrypted buffer into an unencrypted buffer. The AES +// encryption must be CTR with a key size of 128bits. Optionally checks the +// integrity of the encrypted data. class MEDIA_EXPORT AesDecryptor : public Decryptor { public: // The AesDecryptor does not take ownership of the |client|. The |client| @@ -42,13 +46,49 @@ class MEDIA_EXPORT AesDecryptor : public Decryptor { const std::string& session_id) OVERRIDE; virtual void CancelKeyRequest(const std::string& key_system, const std::string& session_id) OVERRIDE; + // Decrypts |encrypted| buffer. |encrypted| should not be NULL. |encrypted| + // will signal if an integrity check must be performed before decryption. + // Returns a DecoderBuffer with the decrypted data if the decryption + // succeeded through |decrypt_cb|. virtual void Decrypt(const scoped_refptr<DecoderBuffer>& encrypted, const DecryptCB& decrypt_cb) OVERRIDE; private: - // KeyMap owns the crypto::SymmetricKey* and must delete them when they are + // Helper class that manages the decryption key and HMAC key. The HMAC key + // may be NULL. + class DecryptionKey { + public: + explicit DecryptionKey(const std::string& secret); + ~DecryptionKey(); + + // Creates the encryption key and HMAC. If |derive_webm_keys| is true then + // the object will derive the decryption key and the HMAC key from + // |secret_|. + bool Init(bool derive_webm_keys); + + crypto::SymmetricKey* decryption_key() { return decryption_key_.get(); } + base::StringPiece hmac_key() { return base::StringPiece(hmac_key_); } + + private: + // The base secret that is used to derive the decryption key and optionally + // the HMAC key. + const std::string secret_; + + // The key used to decrypt the data. + scoped_ptr<crypto::SymmetricKey> decryption_key_; + + // The key used to perform the integrity check. Currently the HMAC key is + // defined by the WebM encrypted specification. Current encrypted WebM + // request for comments specification is here + // http://wiki.webmproject.org/encryption/webm-encryption-rfc + std::string hmac_key_; + + DISALLOW_COPY_AND_ASSIGN(DecryptionKey); + }; + + // KeyMap owns the DecryptionKey* and must delete them when they are // not needed any more. - typedef base::hash_map<std::string, crypto::SymmetricKey*> KeyMap; + typedef base::hash_map<std::string, DecryptionKey*> KeyMap; // Since only Decrypt() is called off the renderer thread, we only need to // protect |key_map_|, the only member variable that is shared between diff --git a/media/crypto/aes_decryptor_unittest.cc b/media/crypto/aes_decryptor_unittest.cc index 3d9d983..34354a4 100644 --- a/media/crypto/aes_decryptor_unittest.cc +++ b/media/crypto/aes_decryptor_unittest.cc @@ -6,10 +6,12 @@ #include "base/basictypes.h" #include "base/bind.h" +#include "base/sys_byteorder.h" #include "media/base/decoder_buffer.h" #include "media/base/decrypt_config.h" #include "media/base/mock_filters.h" #include "media/crypto/aes_decryptor.h" +#include "media/webm/webm_constants.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -22,32 +24,123 @@ using ::testing::StrNe; namespace media { -static const char kClearKeySystem[] = "org.w3.clearkey"; -static const uint8 kInitData[] = { 0x69, 0x6e, 0x69, 0x74 }; -// |kEncryptedData| is encrypted from |kOriginalData| using |kRightKey|. -// Modifying any of these independently would fail the test. -static const uint8 kOriginalData[] = { - 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, - 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e -}; -static const uint8 kEncryptedData[] = { - 0x82, 0x3A, 0x76, 0x92, 0xEC, 0x7F, 0xF8, 0x85, - 0xEC, 0x23, 0x52, 0xFB, 0x19, 0xB1, 0xB9, 0x09 +// |encrypted_data| is encrypted from |plain_text| using |key|. |key_id| is +// used to distinguish |key|. +struct WebmEncryptedData { + uint8 plain_text[32]; + int plain_text_size; + uint8 key_id[32]; + int key_id_size; + uint8 key[32]; + int key_size; + uint8 encrypted_data[64]; + int encrypted_data_size; }; -static const uint8 kRightKey[] = { - 0x41, 0x20, 0x77, 0x6f, 0x6e, 0x64, 0x65, 0x72, - 0x66, 0x75, 0x6c, 0x20, 0x6b, 0x65, 0x79, 0x21 + +static const char kClearKeySystem[] = "org.w3.clearkey"; + +// Frames 0 & 1 are encrypted with the same key. Frame 2 is encrypted with a +// different key. +const WebmEncryptedData kWebmEncryptedFrames[] = { + { + // plaintext + "Original data.", 14, + // key_id + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, + }, 20, + // key + { 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + }, 16, + // encrypted_data + { 0xfb, 0xe7, 0x1d, 0xbb, 0x4c, 0x23, 0xce, 0xba, + 0xcc, 0xf8, 0xda, 0xc0, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x99, 0xaa, 0xff, 0xb7, + 0x74, 0x02, 0x4e, 0x1c, 0x75, 0x3d, 0xee, 0xcb, + 0x64, 0xf7, + }, 34, + }, + { + // plaintext + "Changed Original data.", 22, + // key_id + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, + }, 20, + // key + { 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + }, 16, + // encrypted_data + { 0x43, 0xe4, 0x78, 0x7a, 0x43, 0xe1, 0x49, 0xbb, + 0x44, 0x38, 0xdf, 0xfc, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xec, 0x8e, 0x87, 0x21, + 0xd3, 0xb9, 0x1c, 0x61, 0xf6, 0x5a, 0x60, 0xaa, + 0x07, 0x0e, 0x96, 0xd0, 0x54, 0x5d, 0x35, 0x9a, + 0x4a, 0xd3, + }, 42, + }, + { + // plaintext + "Original data.", 14, + // key_id + { 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, + 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + }, 13, + // key + { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, + }, 16, + // encrypted_data + { 0xd9, 0x43, 0x30, 0xfd, 0x82, 0x77, 0x62, 0x04, + 0x08, 0xc2, 0x48, 0x89, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x48, 0x5e, 0x4a, 0x41, + 0x2a, 0x8b, 0xf4, 0xc6, 0x47, 0x54, 0x90, 0x34, + 0xf4, 0x8b, + }, 34, + }, }; -static const uint8 kWrongKey[] = { + +static const uint8 kWebmWrongKey[] = { 0x49, 0x27, 0x6d, 0x20, 0x61, 0x20, 0x77, 0x72, 0x6f, 0x6e, 0x67, 0x20, 0x6b, 0x65, 0x79, 0x2e }; -static const uint8 kWrongSizedKey[] = { 0x20, 0x20 }; -static const uint8 kKeyId1[] = { - 0x4b, 0x65, 0x79, 0x20, 0x49, 0x44, 0x20, 0x31 +static const uint8 kWebmWrongSizedKey[] = { 0x20, 0x20 }; + +// This is the encrypted data from frame 0 of |kWebmEncryptedFrames| except +// byte 0 is changed from 0xfb to 0xfc. Bytes 0-11 of WebM encrypted data +// contains the HMAC. +static const unsigned char kWebmFrame0HmacDataChanged[] = { + 0xfc, 0xe7, 0x1d, 0xbb, 0x4c, 0x23, 0xce, 0xba, + 0xcc, 0xf8, 0xda, 0xc0, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x99, 0xaa, 0xff, 0xb7, + 0x74, 0x02, 0x4e, 0x1c, 0x75, 0x3d, 0xee, 0xcb, + 0x64, 0xf7 }; -static const uint8 kKeyId2[] = { - 0x4b, 0x65, 0x79, 0x20, 0x49, 0x44, 0x20, 0x32 + +// This is the encrypted data from frame 0 of |kWebmEncryptedFrames| except +// byte 12 is changed from 0xff to 0x0f. Bytes 12-19 of WebM encrypted data +// contains the IV. +static const unsigned char kWebmFrame0IvDataChanged[] = { + 0xfb, 0xe7, 0x1d, 0xbb, 0x4c, 0x23, 0xce, 0xba, + 0xcc, 0xf8, 0xda, 0xc0, 0x0f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x99, 0xaa, 0xff, 0xb7, + 0x74, 0x02, 0x4e, 0x1c, 0x75, 0x3d, 0xee, 0xcb, + 0x64, 0xf7 +}; + +// This is the encrypted data from frame 0 of |kWebmEncryptedFrames| except +// byte 33 is changed from 0xf7 to 0xf8. Bytes 20+ of WebM encrypted data +// contains the encrypted frame. +static const unsigned char kWebmFrame0FrameDataChanged[] = { + 0xfb, 0xe7, 0x1d, 0xbb, 0x4c, 0x23, 0xce, 0xba, + 0xcc, 0xf8, 0xda, 0xc0, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x99, 0xaa, 0xff, 0xb7, + 0x74, 0x02, 0x4e, 0x1c, 0x75, 0x3d, 0xee, 0xcb, + 0x64, 0xf8 }; class AesDecryptorTest : public testing::Test { @@ -56,60 +149,103 @@ class AesDecryptorTest : public testing::Test { : decryptor_(&client_), decrypt_cb_(base::Bind(&AesDecryptorTest::BufferDecrypted, base::Unretained(this))) { - encrypted_data_ = DecoderBuffer::CopyFrom(kEncryptedData, - arraysize(kEncryptedData)); } protected: - void GenerateKeyRequest() { + // Returns a 16 byte CTR counter block. The CTR counter block format is a + // CTR IV appended with a CTR block counter. |iv| is a CTR IV. |iv_size| is + // the size of |iv| in bytes. + static std::string GenerateCounterBlock(const uint8* iv, int iv_size) { + const int kDecryptionKeySize = 16; + CHECK_GT(iv_size, 0); + CHECK_LE(iv_size, kDecryptionKeySize); + char counter_block_data[kDecryptionKeySize]; + + // Set the IV. + memcpy(counter_block_data, iv, iv_size); + + // Set block counter to all 0's. + memset(counter_block_data + iv_size, 0, kDecryptionKeySize - iv_size); + + return std::string(counter_block_data, kDecryptionKeySize); + } + + // Creates a WebM encrypted buffer that the demuxer would pass to the + // decryptor. |data| is the payload of a WebM encrypted Block. |key_id| is + // initialization data from the WebM file. Every encrypted Block has + // an HMAC and IV prepended to an encrypted frame. Current encrypted WebM + // request for comments specification is here + // http://wiki.webmproject.org/encryption/webm-encryption-rfc + scoped_refptr<DecoderBuffer> CreateWebMEncryptedBuffer(const uint8* data, + int data_size, + const uint8* key_id, + int key_id_size) { + scoped_refptr<DecoderBuffer> encrypted_buffer = DecoderBuffer::CopyFrom( + data + kWebMHmacSize, data_size - kWebMHmacSize); + CHECK(encrypted_buffer); + + uint64 network_iv; + memcpy(&network_iv, data + kWebMHmacSize, sizeof(network_iv)); + const uint64 iv = base::NetToHost64(network_iv); + std::string webm_iv = + GenerateCounterBlock(reinterpret_cast<const uint8*>(&iv), sizeof(iv)); + encrypted_buffer->SetDecryptConfig( + scoped_ptr<DecryptConfig>(new DecryptConfig( + key_id, key_id_size, + reinterpret_cast<const uint8*>(webm_iv.data()), webm_iv.size(), + data, kWebMHmacSize, + sizeof(iv)))); + return encrypted_buffer; + } + + void GenerateKeyRequest(const uint8* key_id, int key_id_size) { EXPECT_CALL(client_, KeyMessageMock(kClearKeySystem, StrNe(std::string()), NotNull(), Gt(0), "")) .WillOnce(SaveArg<1>(&session_id_string_)); - decryptor_.GenerateKeyRequest(kClearKeySystem, - kInitData, arraysize(kInitData)); + decryptor_.GenerateKeyRequest(kClearKeySystem, key_id, key_id_size); } - template <int KeyIdSize, int KeySize> - void AddKeyAndExpectToSucceed(const uint8 (&key_id)[KeyIdSize], - const uint8 (&key)[KeySize]) { + void AddKeyAndExpectToSucceed(const uint8* key_id, int key_id_size, + const uint8* key, int key_size) { EXPECT_CALL(client_, KeyAdded(kClearKeySystem, session_id_string_)); - decryptor_.AddKey(kClearKeySystem, key, KeySize, key_id, KeyIdSize, + decryptor_.AddKey(kClearKeySystem, key, key_size, key_id, key_id_size, session_id_string_); } - template <int KeyIdSize, int KeySize> - void AddKeyAndExpectToFail(const uint8 (&key_id)[KeyIdSize], - const uint8 (&key)[KeySize]) { + void AddKeyAndExpectToFail(const uint8* key_id, int key_id_size, + const uint8* key, int key_size) { EXPECT_CALL(client_, KeyError(kClearKeySystem, session_id_string_, Decryptor::kUnknownError, 0)); - decryptor_.AddKey(kClearKeySystem, key, KeySize, key_id, KeyIdSize, + decryptor_.AddKey(kClearKeySystem, key, key_size, key_id, key_id_size, session_id_string_); } - template <int KeyIdSize> - void SetKeyIdForEncryptedData(const uint8 (&key_id)[KeyIdSize]) { - encrypted_data_->SetDecryptConfig( - scoped_ptr<DecryptConfig>(new DecryptConfig(key_id, KeyIdSize))); - } - MOCK_METHOD2(BufferDecrypted, void(Decryptor::DecryptStatus, const scoped_refptr<DecoderBuffer>&)); - void DecryptAndExpectToSucceed() { + void DecryptAndExpectToSucceed(const uint8* data, int data_size, + const uint8* plain_text, + int plain_text_size, + const uint8* key_id, int key_id_size) { + scoped_refptr<DecoderBuffer> encrypted_data = + CreateWebMEncryptedBuffer(data, data_size, key_id, key_id_size); scoped_refptr<DecoderBuffer> decrypted; EXPECT_CALL(*this, BufferDecrypted(AesDecryptor::kSuccess, NotNull())) .WillOnce(SaveArg<1>(&decrypted)); - decryptor_.Decrypt(encrypted_data_, decrypt_cb_); + decryptor_.Decrypt(encrypted_data, decrypt_cb_); ASSERT_TRUE(decrypted); - int data_length = sizeof(kOriginalData); - ASSERT_EQ(data_length, decrypted->GetDataSize()); - EXPECT_EQ(0, memcmp(kOriginalData, decrypted->GetData(), data_length)); + ASSERT_EQ(plain_text_size, decrypted->GetDataSize()); + EXPECT_EQ(0, memcmp(plain_text, decrypted->GetData(), plain_text_size)); } - void DecryptAndExpectToFail() { + void DecryptAndExpectToFail(const uint8* data, int data_size, + const uint8* plain_text, int plain_text_size, + const uint8* key_id, int key_id_size) { + scoped_refptr<DecoderBuffer> encrypted_data = + CreateWebMEncryptedBuffer(data, data_size, key_id, key_id_size); EXPECT_CALL(*this, BufferDecrypted(AesDecryptor::kError, IsNull())); - decryptor_.Decrypt(encrypted_data_, decrypt_cb_); + decryptor_.Decrypt(encrypted_data, decrypt_cb_); } scoped_refptr<DecoderBuffer> encrypted_data_; @@ -120,39 +256,129 @@ class AesDecryptorTest : public testing::Test { }; TEST_F(AesDecryptorTest, NormalDecryption) { - GenerateKeyRequest(); - AddKeyAndExpectToSucceed(kKeyId1, kRightKey); - SetKeyIdForEncryptedData(kKeyId1); - ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed()); + const WebmEncryptedData& frame = kWebmEncryptedFrames[0]; + GenerateKeyRequest(frame.key_id, frame.key_id_size); + AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size, + frame.key, frame.key_size); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(frame.encrypted_data, + frame.encrypted_data_size, + frame.plain_text, + frame.plain_text_size, + frame.key_id, + frame.key_id_size)); } TEST_F(AesDecryptorTest, WrongKey) { - GenerateKeyRequest(); - AddKeyAndExpectToSucceed(kKeyId1, kWrongKey); - SetKeyIdForEncryptedData(kKeyId1); - ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail()); -} - -TEST_F(AesDecryptorTest, MultipleKeys) { - GenerateKeyRequest(); - AddKeyAndExpectToSucceed(kKeyId1, kRightKey); - AddKeyAndExpectToSucceed(kKeyId2, kWrongKey); - SetKeyIdForEncryptedData(kKeyId1); - ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed()); + const WebmEncryptedData& frame = kWebmEncryptedFrames[0]; + GenerateKeyRequest(frame.key_id, frame.key_id_size); + AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size, + kWebmWrongKey, arraysize(kWebmWrongKey)); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(frame.encrypted_data, + frame.encrypted_data_size, + frame.plain_text, + frame.plain_text_size, + frame.key_id, + frame.key_id_size)); } TEST_F(AesDecryptorTest, KeyReplacement) { - GenerateKeyRequest(); - SetKeyIdForEncryptedData(kKeyId1); - AddKeyAndExpectToSucceed(kKeyId1, kWrongKey); - ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail()); - AddKeyAndExpectToSucceed(kKeyId1, kRightKey); - ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed()); + const WebmEncryptedData& frame = kWebmEncryptedFrames[0]; + GenerateKeyRequest(frame.key_id, frame.key_id_size); + AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size, + kWebmWrongKey, arraysize(kWebmWrongKey)); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(frame.encrypted_data, + frame.encrypted_data_size, + frame.plain_text, + frame.plain_text_size, + frame.key_id, + frame.key_id_size)); + AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size, + frame.key, frame.key_size); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(frame.encrypted_data, + frame.encrypted_data_size, + frame.plain_text, + frame.plain_text_size, + frame.key_id, + frame.key_id_size)); } TEST_F(AesDecryptorTest, WrongSizedKey) { - GenerateKeyRequest(); - AddKeyAndExpectToFail(kKeyId1, kWrongSizedKey); + const WebmEncryptedData& frame = kWebmEncryptedFrames[0]; + GenerateKeyRequest(frame.key_id, frame.key_id_size); + AddKeyAndExpectToFail(frame.key_id, frame.key_id_size, + kWebmWrongSizedKey, arraysize(kWebmWrongSizedKey)); +} + +TEST_F(AesDecryptorTest, MultipleKeysAndFrames) { + const WebmEncryptedData& frame = kWebmEncryptedFrames[0]; + GenerateKeyRequest(frame.key_id, frame.key_id_size); + AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size, + frame.key, frame.key_size); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(frame.encrypted_data, + frame.encrypted_data_size, + frame.plain_text, + frame.plain_text_size, + frame.key_id, + frame.key_id_size)); + + const WebmEncryptedData& frame2 = kWebmEncryptedFrames[2]; + GenerateKeyRequest(frame2.key_id, frame2.key_id_size); + AddKeyAndExpectToSucceed(frame2.key_id, frame2.key_id_size, + frame2.key, frame2.key_size); + + const WebmEncryptedData& frame1 = kWebmEncryptedFrames[1]; + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(frame1.encrypted_data, + frame1.encrypted_data_size, + frame1.plain_text, + frame1.plain_text_size, + frame1.key_id, + frame1.key_id_size)); + + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(frame2.encrypted_data, + frame2.encrypted_data_size, + frame2.plain_text, + frame2.plain_text_size, + frame2.key_id, + frame2.key_id_size)); +} + +TEST_F(AesDecryptorTest, HmacCheckFailure) { + const WebmEncryptedData& frame = kWebmEncryptedFrames[0]; + GenerateKeyRequest(frame.key_id, frame.key_id_size); + AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size, + frame.key, frame.key_size); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(kWebmFrame0HmacDataChanged, + frame.encrypted_data_size, + frame.plain_text, + frame.plain_text_size, + frame.key_id, + frame.key_id_size)); +} + +TEST_F(AesDecryptorTest, IvCheckFailure) { + const WebmEncryptedData& frame = kWebmEncryptedFrames[0]; + GenerateKeyRequest(frame.key_id, frame.key_id_size); + AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size, + frame.key, frame.key_size); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(kWebmFrame0IvDataChanged, + frame.encrypted_data_size, + frame.plain_text, + frame.plain_text_size, + frame.key_id, + frame.key_id_size)); +} + +TEST_F(AesDecryptorTest, DataCheckFailure) { + const WebmEncryptedData& frame = kWebmEncryptedFrames[0]; + GenerateKeyRequest(frame.key_id, frame.key_id_size); + AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size, + frame.key, frame.key_size); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(kWebmFrame0FrameDataChanged, + frame.encrypted_data_size, + frame.plain_text, + frame.plain_text_size, + frame.key_id, + frame.key_id_size)); } -} // media +} // namespace media diff --git a/media/filters/ffmpeg_video_decoder_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc index 15459d3..a21c3c8 100644 --- a/media/filters/ffmpeg_video_decoder_unittest.cc +++ b/media/filters/ffmpeg_video_decoder_unittest.cc @@ -37,7 +37,18 @@ static const gfx::Size kCodedSize(320, 240); static const gfx::Rect kVisibleRect(320, 240); static const AVRational kFrameRate = { 100, 1 }; static const AVRational kAspectRatio = { 1, 1 }; + static const uint8 kKeyId[] = { 0x4b, 0x65, 0x79, 0x20, 0x49, 0x44 }; +static const uint8 kSecretKey[] = { + 0xfb, 0x67, 0x8a, 0x91, 0x19, 0x12, 0x7b, 0x6b, + 0x0b, 0x63, 0x11, 0xf8, 0x6f, 0xe1, 0xc4, 0x2d +}; + +static const uint64 kIv = 3735928559ULL; +static const uint8 kHmac[] = { + 0x16, 0xc0, 0x65, 0x1f, 0xf8, 0x0b, 0x36, 0x16, + 0xb8, 0x32, 0x35, 0x56 +}; ACTION_P(ReturnBuffer, buffer) { arg0.Run(buffer ? DemuxerStream::kOk : DemuxerStream::kAborted, buffer); @@ -65,8 +76,13 @@ class FFmpegVideoDecoderTest : public testing::Test { end_of_stream_buffer_ = DecoderBuffer::CreateEOSBuffer(); i_frame_buffer_ = ReadTestDataFile("vp8-I-frame-320x240"); corrupt_i_frame_buffer_ = ReadTestDataFile("vp8-corrupt-I-frame"); - encrypted_i_frame_buffer_ = ReadTestDataFile( - "vp8-encrypted-I-frame-320x240"); + { + scoped_refptr<DecoderBuffer> temp_buffer = ReadTestDataFile( + "vp8-encrypted-I-frame-320x240"); + encrypted_i_frame_buffer_ = DecoderBuffer::CopyFrom( + temp_buffer->GetData() + arraysize(kHmac), + temp_buffer->GetDataSize() - arraysize(kHmac)); + } config_.Initialize(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat, kCodedSize, kVisibleRect, @@ -195,6 +211,24 @@ class FFmpegVideoDecoderTest : public testing::Test { message_loop_.RunAllPending(); } + // Generates a 16 byte CTR counter block. The CTR counter block format is a + // CTR IV appended with a CTR block counter. |iv| is an 8 byte CTR IV. + static scoped_array<uint8> GenerateCounterBlock(uint64 iv) { + scoped_array<uint8> counter_block_data( + new uint8[DecryptConfig::kDecryptionKeySize]); + + // Set the IV. + memcpy(counter_block_data.get(), &iv, sizeof(iv)); + + // Set block counter to all 0's. + memset(counter_block_data.get() + sizeof(iv), + 0, + DecryptConfig::kDecryptionKeySize - sizeof(iv)); + + return counter_block_data.Pass(); + } + + MOCK_METHOD2(FrameReady, void(VideoDecoder::DecoderStatus, const scoped_refptr<VideoFrame>&)); @@ -375,12 +409,19 @@ TEST_F(FFmpegVideoDecoderTest, DecodeFrame_SmallerHeight) { DecodeIFrameThenTestFile("vp8-I-frame-320x120", 320, 120); } -TEST_F(FFmpegVideoDecoderTest, DecodeEncryptedFrame_Normal) { +// TODO(fgalligan): Enable test when encrypted test data is updated and new +// decryption code is landed. http://crbug.com/132801 +TEST_F(FFmpegVideoDecoderTest, DISABLED_DecodeEncryptedFrame_Normal) { Initialize(); // Simulate decoding a single encrypted frame. + scoped_array<uint8> counter_block(GenerateCounterBlock(kIv)); encrypted_i_frame_buffer_->SetDecryptConfig(scoped_ptr<DecryptConfig>( - new DecryptConfig(kKeyId, arraysize(kKeyId)))); + new DecryptConfig( + kKeyId, arraysize(kKeyId), + counter_block.get(), DecryptConfig::kDecryptionKeySize, + kHmac, arraysize(kHmac), + sizeof(kIv)))); EXPECT_CALL(*decryptor_, Decrypt(encrypted_i_frame_buffer_, _)) .WillRepeatedly(RunDecryptCB(Decryptor::kSuccess, i_frame_buffer_)); @@ -399,8 +440,13 @@ TEST_F(FFmpegVideoDecoderTest, DecodeEncryptedFrame_DecryptError) { Initialize(); // Simulate decoding a single encrypted frame. + scoped_array<uint8> counter_block(GenerateCounterBlock(kIv)); encrypted_i_frame_buffer_->SetDecryptConfig(scoped_ptr<DecryptConfig>( - new DecryptConfig(kKeyId, arraysize(kKeyId)))); + new DecryptConfig( + kKeyId, arraysize(kKeyId), + counter_block.get(), DecryptConfig::kDecryptionKeySize, + kHmac, arraysize(kHmac), + sizeof(kIv)))); EXPECT_CALL(*demuxer_, Read(_)) .WillRepeatedly(ReturnBuffer(encrypted_i_frame_buffer_)); @@ -425,8 +471,13 @@ TEST_F(FFmpegVideoDecoderTest, DecodeEncryptedFrame_CorruptedBufferReturned) { Initialize(); // Simulate decoding a single encrypted frame. + scoped_array<uint8> counter_block(GenerateCounterBlock(kIv)); encrypted_i_frame_buffer_->SetDecryptConfig(scoped_ptr<DecryptConfig>( - new DecryptConfig(kKeyId, arraysize(kKeyId)))); + new DecryptConfig( + kKeyId, arraysize(kKeyId), + counter_block.get(), DecryptConfig::kDecryptionKeySize, + kHmac, arraysize(kHmac), + sizeof(kIv)))); EXPECT_CALL(*demuxer_, Read(_)) .WillRepeatedly(ReturnBuffer(encrypted_i_frame_buffer_)); diff --git a/media/filters/pipeline_integration_test.cc b/media/filters/pipeline_integration_test.cc index c207858..cae8b78 100644 --- a/media/filters/pipeline_integration_test.cc +++ b/media/filters/pipeline_integration_test.cc @@ -18,6 +18,12 @@ static const char kSourceId[] = "SourceId"; static const char kClearKeySystem[] = "org.w3.clearkey"; static const uint8 kInitData[] = { 0x69, 0x6e, 0x69, 0x74 }; +// Key used to encrypt video track in test file "bear-320x240-encrypted.webm". +static const uint8 kSecretKey[] = { + 0xfb, 0x67, 0x8a, 0x91, 0x19, 0x12, 0x7b, 0x6b, + 0x0b, 0x63, 0x11, 0xf8, 0x6f, 0xe1, 0xc4, 0x2d +}; + // Helper class that emulates calls made on the ChunkDemuxer by the // Media Source API. class MockMediaSource : public ChunkDemuxerClient { @@ -96,7 +102,7 @@ class MockMediaSource : public ChunkDemuxerClient { virtual void DemuxerNeedKey(scoped_array<uint8> init_data, int init_data_size) { DCHECK(init_data.get()); - DCHECK_EQ(init_data_size, 16); + DCHECK_GT(init_data_size, 0); DCHECK(decryptor_client_); decryptor_client_->NeedKey("", "", init_data.Pass(), init_data_size); } @@ -166,9 +172,7 @@ class FakeDecryptorClient : public DecryptorClient { EXPECT_FALSE(current_key_system_.empty()); EXPECT_FALSE(current_session_id_.empty()); - // In test file bear-320x240-encrypted.webm, the decryption key is equal to - // |init_data|. - decryptor_.AddKey(current_key_system_, init_data.get(), init_data_length, + decryptor_.AddKey(current_key_system_, kSecretKey, arraysize(kSecretKey), init_data.get(), init_data_length, current_session_id_); } @@ -280,7 +284,9 @@ TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource) { Stop(); } -TEST_F(PipelineIntegrationTest, EncryptedPlayback) { +// TODO(fgalligan): Enable test when encrypted test data is updated and new +// decryption code is landed. http://crbug.com/132801 +TEST_F(PipelineIntegrationTest, DISABLED_EncryptedPlayback) { MockMediaSource source("bear-320x240-encrypted.webm", 219726, true, true); FakeDecryptorClient encrypted_media; StartPipelineWithEncryptedMedia(&source, &encrypted_media); diff --git a/media/webm/webm_cluster_parser.cc b/media/webm/webm_cluster_parser.cc index c5a2fdc..20d131e 100644 --- a/media/webm/webm_cluster_parser.cc +++ b/media/webm/webm_cluster_parser.cc @@ -5,12 +5,31 @@ #include "media/webm/webm_cluster_parser.h" #include "base/logging.h" +#include "base/sys_byteorder.h" #include "media/base/data_buffer.h" #include "media/base/decrypt_config.h" #include "media/webm/webm_constants.h" namespace media { +// Generates a 16 byte CTR counter block. The CTR counter block format is a +// CTR IV appended with a CTR block counter. |iv| is an 8 byte CTR IV. +// Always returns a valid pointer to a buffer of kDecryptionKeySize bytes. +static scoped_array<uint8> GenerateCounterBlock(uint64 iv) { + scoped_array<uint8> counter_block_data( + new uint8[DecryptConfig::kDecryptionKeySize]); + + // Set the IV. + memcpy(counter_block_data.get(), &iv, sizeof(iv)); + + // Set block counter to all 0's. + memset(counter_block_data.get() + sizeof(iv), + 0, + DecryptConfig::kDecryptionKeySize - sizeof(iv)); + + return counter_block_data.Pass(); +} + WebMClusterParser::WebMClusterParser(int64 timecode_scale, int audio_track_num, int video_track_num, @@ -193,15 +212,33 @@ bool WebMClusterParser::OnBlock(int track_num, int timecode, base::TimeDelta timestamp = base::TimeDelta::FromMicroseconds( (cluster_timecode_ + timecode) * timecode_multiplier_); + // Every encrypted Block has an HMAC and IV prepended to it. Current encrypted + // WebM request for comments specification is here + // http://wiki.webmproject.org/encryption/webm-encryption-rfc + bool encrypted = track_num == video_.track_num() && + video_encryption_key_id_.get(); + // If encrypted skip past the HMAC. Encrypted buffers must include the IV and + // the encrypted frame because the decryptor will verify this data before + // decryption. The HMAC and IV will be copied into DecryptConfig. + int offset = (encrypted) ? kWebMHmacSize : 0; + // The first bit of the flags is set when the block contains only keyframes. // http://www.matroska.org/technical/specs/index.html bool is_keyframe = (flags & 0x80) != 0; scoped_refptr<StreamParserBuffer> buffer = - StreamParserBuffer::CopyFrom(data, size, is_keyframe); + StreamParserBuffer::CopyFrom(data + offset, size - offset, is_keyframe); + + if (encrypted) { + uint64 network_iv; + memcpy(&network_iv, data + kWebMHmacSize, sizeof(network_iv)); + const uint64 iv = base::NetToHost64(network_iv); - if (track_num == video_.track_num() && video_encryption_key_id_.get()) { + scoped_array<uint8> counter_block(GenerateCounterBlock(iv)); buffer->SetDecryptConfig(scoped_ptr<DecryptConfig>(new DecryptConfig( - video_encryption_key_id_.get(), video_encryption_key_id_size_))); + video_encryption_key_id_.get(), video_encryption_key_id_size_, + counter_block.get(), DecryptConfig::kDecryptionKeySize, + data, kWebMHmacSize, + sizeof(iv)))); } buffer->SetTimestamp(timestamp); diff --git a/media/webm/webm_cluster_parser.h b/media/webm/webm_cluster_parser.h index 0af0361..2bdb1e7 100644 --- a/media/webm/webm_cluster_parser.h +++ b/media/webm/webm_cluster_parser.h @@ -19,6 +19,10 @@ class MEDIA_EXPORT WebMClusterParser : public WebMParserClient { public: typedef std::deque<scoped_refptr<StreamParserBuffer> > BufferQueue; + // Size is defined by the WebM encryption specification. + // http://wiki.webmproject.org/encryption/webm-encryption-rfc + static const int kIvSize = 8; + WebMClusterParser(int64 timecode_scale, int audio_track_num, int video_track_num, diff --git a/media/webm/webm_constants.h b/media/webm/webm_constants.h index b6ba0a2..93041b2 100644 --- a/media/webm/webm_constants.h +++ b/media/webm/webm_constants.h @@ -12,6 +12,7 @@ namespace media { // WebM element IDs. // This is a subset of the IDs in the Matroska spec. // http://www.matroska.org/technical/specs/index.html +const int kWebMIdAESSettingsCipherMode = 0x47E8; const int kWebMIdAspectRatioType = 0x54B3; const int kWebMIdAttachedFile = 0x61A7; const int kWebMIdAttachmentLink = 0x7446; @@ -62,6 +63,7 @@ const int kWebMIdColorSpace = 0x2EB524; const int kWebMIdContentCompAlgo = 0x4254; const int kWebMIdContentCompression = 0x5034; const int kWebMIdContentCompSettings = 0x4255; +const int kWebMIdContentEncAESSettings = 0x47E7; const int kWebMIdContentEncAlgo = 0x47E1; const int kWebMIdContentEncKeyID = 0x47E2; const int kWebMIdContentEncoding = 0x6240; @@ -197,6 +199,11 @@ const int64 kWebMUnknownSize = GG_LONGLONG(0x00FFFFFFFFFFFFFF); const uint8 kWebMFlagKeyframe = 0x80; +// The size is from the WebM encrypted specification. Current encrypted WebM +// request for comments specification is here +// http://wiki.webmproject.org/encryption/webm-encryption-rfc +const int kWebMHmacSize = 12; + } // namespace media #endif // MEDIA_WEBM_WEBM_CONSTANTS_H_ diff --git a/media/webm/webm_content_encodings.cc b/media/webm/webm_content_encodings.cc index a5b7bfe..540ac99 100644 --- a/media/webm/webm_content_encodings.cc +++ b/media/webm/webm_content_encodings.cc @@ -12,7 +12,8 @@ ContentEncoding::ContentEncoding() scope_(kScopeInvalid), type_(kTypeInvalid), encryption_algo_(kEncAlgoInvalid), - encryption_key_id_size_(0) { + encryption_key_id_size_(0), + cipher_mode_(kCipherModeInvalid) { } ContentEncoding::~ContentEncoding() {} diff --git a/media/webm/webm_content_encodings.h b/media/webm/webm_content_encodings.h index 2947a26..ea903d8 100644 --- a/media/webm/webm_content_encodings.h +++ b/media/webm/webm_content_encodings.h @@ -42,6 +42,11 @@ class MEDIA_EXPORT ContentEncoding { kEncAlgoAes = 5, }; + enum CipherMode { + kCipherModeInvalid = 0, + kCipherModeCtr = 1, + }; + ContentEncoding(); ~ContentEncoding(); @@ -64,6 +69,9 @@ class MEDIA_EXPORT ContentEncoding { void SetEncryptionKeyId(const uint8* encryption_key_id, int size); + CipherMode cipher_mode() const { return cipher_mode_; } + void set_cipher_mode(CipherMode mode) { cipher_mode_ = mode; } + private: int64 order_; Scope scope_; @@ -71,6 +79,7 @@ class MEDIA_EXPORT ContentEncoding { EncryptionAlgo encryption_algo_; scoped_array<uint8> encryption_key_id_; int encryption_key_id_size_; + CipherMode cipher_mode_; DISALLOW_COPY_AND_ASSIGN(ContentEncoding); }; diff --git a/media/webm/webm_content_encodings_client.cc b/media/webm/webm_content_encodings_client.cc index 086f85b..a24b4bf 100644 --- a/media/webm/webm_content_encodings_client.cc +++ b/media/webm/webm_content_encodings_client.cc @@ -50,6 +50,11 @@ WebMParserClient* WebMContentEncodingsClient::OnListStart(int id) { return this; } + if (id == kWebMIdContentEncAESSettings) { + DCHECK(cur_content_encoding_.get()); + return this; + } + // This should not happen if WebMListParser is working properly. DCHECK(false); return NULL; @@ -121,6 +126,13 @@ bool WebMContentEncodingsClient::OnListEnd(int id) { return true; } + if (id == kWebMIdContentEncAESSettings) { + if (cur_content_encoding_->cipher_mode() == + ContentEncoding::kCipherModeInvalid) + cur_content_encoding_->set_cipher_mode(ContentEncoding::kCipherModeCtr); + return true; + } + // This should not happen if WebMListParser is working properly. DCHECK(false); return false; @@ -206,6 +218,23 @@ bool WebMContentEncodingsClient::OnUInt(int id, int64 val) { return true; } + if (id == kWebMIdAESSettingsCipherMode) { + if (cur_content_encoding_->cipher_mode() != + ContentEncoding::kCipherModeInvalid) { + DVLOG(1) << "Unexpected multiple AESSettingsCipherMode."; + return false; + } + + if (val != ContentEncoding::kCipherModeCtr) { + DVLOG(1) << "Unexpected AESSettingsCipherMode " << val << "."; + return false; + } + + cur_content_encoding_->set_cipher_mode( + static_cast<ContentEncoding::CipherMode>(val)); + return true; + } + // This should not happen if WebMListParser is working properly. DCHECK(false); return false; diff --git a/media/webm/webm_parser.cc b/media/webm/webm_parser.cc index 28c847b..9952efa 100644 --- a/media/webm/webm_parser.cc +++ b/media/webm/webm_parser.cc @@ -7,6 +7,9 @@ // This file contains code to parse WebM file elements. It was created // from information in the Matroska spec. // http://www.matroska.org/technical/specs/index.html +// This file contains code for encrypted WebM. Current WebM +// encrypted request for comments specification is here +// http://wiki.webmproject.org/encryption/webm-encryption-rfc #include <iomanip> @@ -232,6 +235,7 @@ static const ElementIdInfo kContentCompressionIds[] = { }; static const ElementIdInfo kContentEncryptionIds[] = { + {LIST, kWebMIdContentEncAESSettings}, {UINT, kWebMIdContentEncAlgo}, {BINARY, kWebMIdContentEncKeyID}, {BINARY, kWebMIdContentSignature}, @@ -240,6 +244,10 @@ static const ElementIdInfo kContentEncryptionIds[] = { {UINT, kWebMIdContentSigHashAlgo}, }; +static const ElementIdInfo kContentEncAESSettingsIds[] = { + {UINT, kWebMIdAESSettingsCipherMode}, +}; + static const ElementIdInfo kCuesIds[] = { {LIST, kWebMIdCuePoint}, }; @@ -376,6 +384,7 @@ static const ListElementInfo kListElementInfo[] = { LIST_ELEMENT_INFO(kWebMIdContentEncoding, 4, kContentEncodingIds), LIST_ELEMENT_INFO(kWebMIdContentCompression, 5, kContentCompressionIds), LIST_ELEMENT_INFO(kWebMIdContentEncryption, 5, kContentEncryptionIds), + LIST_ELEMENT_INFO(kWebMIdContentEncAESSettings, 6, kContentEncAESSettingsIds), LIST_ELEMENT_INFO(kWebMIdCues, 1, kCuesIds), LIST_ELEMENT_INFO(kWebMIdCuePoint, 2, kCuePointIds), LIST_ELEMENT_INFO(kWebMIdCueTrackPositions, 3, kCueTrackPositionsIds), diff --git a/media/webm/webm_stream_parser.cc b/media/webm/webm_stream_parser.cc index 602d227..d141bfd 100644 --- a/media/webm/webm_stream_parser.cc +++ b/media/webm/webm_stream_parser.cc @@ -352,6 +352,7 @@ int WebMStreamParser::ParseInfoAndTracks(const uint8* data, int size) { if (tracks_parser.video_encryption_key_id()) { int key_id_size = tracks_parser.video_encryption_key_id_size(); CHECK_GT(key_id_size, 0); + CHECK_LT(key_id_size, 2048); scoped_array<uint8> key_id(new uint8[key_id_size]); memcpy(key_id.get(), tracks_parser.video_encryption_key_id(), key_id_size); need_key_cb_.Run(key_id.Pass(), key_id_size); |