summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorfgalligan@chromium.org <fgalligan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-20 14:54:10 +0000
committerfgalligan@chromium.org <fgalligan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-20 14:54:10 +0000
commitd55917908e06bf543c941bf0f6e0f2648eeeece8 (patch)
tree0d236b71de8ddc0d0b33a66e83aadcd5f4e1dae3
parenta858c9222754f3eb210d6e7e8a31c657be653b54 (diff)
downloadchromium_src-d55917908e06bf543c941bf0f6e0f2648eeeece8.zip
chromium_src-d55917908e06bf543c941bf0f6e0f2648eeeece8.tar.gz
chromium_src-d55917908e06bf543c941bf0f6e0f2648eeeece8.tar.bz2
Add support for encrypted WebM files as defined in the RFC.
The WebM parser now reads the HMAC and IV from every encrypted Block and stores that information in the DecryptorConfig object. Added two new elements ContentEncAESSettings and AESSettingsCipherMode used by encrypted WebM files. This is a new code review of https://chromiumcodereview.appspot.com/10535029/ patch set 17 after patch set 16 was reverted. BUG=119845 TEST=media_unittests Review URL: https://chromiumcodereview.appspot.com/10810026 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@147654 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--media/base/decrypt_config.cc20
-rw-r--r--media/base/decrypt_config.h39
-rw-r--r--media/crypto/aes_decryptor.cc186
-rw-r--r--media/crypto/aes_decryptor.h46
-rw-r--r--media/crypto/aes_decryptor_unittest.cc368
-rw-r--r--media/filters/ffmpeg_video_decoder_unittest.cc63
-rw-r--r--media/filters/pipeline_integration_test.cc16
-rw-r--r--media/webm/webm_cluster_parser.cc43
-rw-r--r--media/webm/webm_cluster_parser.h4
-rw-r--r--media/webm/webm_constants.h7
-rw-r--r--media/webm/webm_content_encodings.cc3
-rw-r--r--media/webm/webm_content_encodings.h9
-rw-r--r--media/webm/webm_content_encodings_client.cc29
-rw-r--r--media/webm/webm_parser.cc9
-rw-r--r--media/webm/webm_stream_parser.cc1
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);