diff options
author | xhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-16 01:27:36 +0000 |
---|---|---|
committer | xhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-16 01:27:36 +0000 |
commit | cbdc8c2b4b46c7e7886c9e99bc595a0b9936b381 (patch) | |
tree | fd03ab4a8636680c4c2dff01880751e25ef0cdca /media/crypto | |
parent | 46b7d64e85a9d8d28372d05b59b295dd6ecb0bc8 (diff) | |
download | chromium_src-cbdc8c2b4b46c7e7886c9e99bc595a0b9936b381.zip chromium_src-cbdc8c2b4b46c7e7886c9e99bc595a0b9936b381.tar.gz chromium_src-cbdc8c2b4b46c7e7886c9e99bc595a0b9936b381.tar.bz2 |
Generalize AesDecryptor to make it more spec compliant.
BUG=123260
TEST=media_unittests, encrypted-media layout tests.
Review URL: https://chromiumcodereview.appspot.com/10534096
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@142553 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/crypto')
-rw-r--r-- | media/crypto/aes_decryptor.cc | 82 | ||||
-rw-r--r-- | media/crypto/aes_decryptor.h | 67 | ||||
-rw-r--r-- | media/crypto/aes_decryptor_unittest.cc | 117 | ||||
-rw-r--r-- | media/crypto/decryptor_client.h | 50 |
4 files changed, 260 insertions, 56 deletions
diff --git a/media/crypto/aes_decryptor.cc b/media/crypto/aes_decryptor.cc index cbb75b3..49275fb 100644 --- a/media/crypto/aes_decryptor.cc +++ b/media/crypto/aes_decryptor.cc @@ -6,17 +6,21 @@ #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/symmetric_key.h" #include "media/base/decoder_buffer.h" #include "media/base/decrypt_config.h" +#include "media/crypto/decryptor_client.h" namespace media { // TODO(xhwang): Get real IV from frames. static const char kInitialCounter[] = "0000000000000000"; +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. @@ -48,35 +52,83 @@ static scoped_refptr<DecoderBuffer> DecryptData(const DecoderBuffer& input, decrypted_text.size()); } -AesDecryptor::AesDecryptor() {} +AesDecryptor::AesDecryptor(DecryptorClient* client) + : client_(client) { +} AesDecryptor::~AesDecryptor() { STLDeleteValues(&key_map_); } -void AesDecryptor::AddKey(const uint8* key_id, int key_id_size, - const uint8* key, int key_size) { - CHECK(key_id && key); - CHECK_GT(key_id_size, 0); - CHECK_GT(key_size, 0); +void AesDecryptor::GenerateKeyRequest(const std::string& key_system, + const uint8* init_data, + int init_data_length) { + std::string session_id_string(base::UintToString(next_session_id_++)); - std::string key_id_string(reinterpret_cast<const char*>(key_id), key_id_size); - std::string key_string(reinterpret_cast<const char*>(key) , key_size); + // For now, just fire the event with the |init_data| as the request. + int message_length = init_data_length; + scoped_array<uint8> message(new uint8[message_length]); + memcpy(message.get(), init_data, message_length); + + client_->KeyMessage(key_system, session_id_string, + message.Pass(), message_length, ""); +} + +void AesDecryptor::AddKey(const std::string& key_system, + const uint8* key, + int key_length, + const uint8* init_data, + int init_data_length, + const std::string& session_id) { + CHECK(key); + CHECK_GT(key_length, 0); + + // 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) { + DVLOG(1) << "Invalid key length: " << key_length; + client_->KeyError(key_system, session_id, kUnknownError, 0); + return; + } + // TODO(xhwang): Fix the decryptor to accept no |init_data|. See + // http://crbug.com/123265. Until then, ensure a non-empty value is passed. + static const uint8 kDummyInitData[1] = { 0 }; + if (!init_data) { + init_data = kDummyInitData; + init_data_length = arraysize(kDummyInitData); + } + + // TODO(xhwang): For now, use |init_data| for key ID. Make this more spec + // compliant later (http://crbug.com/123262, http://crbug.com/123265). + 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."; + client_->KeyError(key_system, session_id, kUnknownError, 0); return; } - base::AutoLock auto_lock(lock_); - KeyMap::iterator found = key_map_.find(key_id_string); - if (found != key_map_.end()) { - delete found->second; - key_map_.erase(found); + { + base::AutoLock auto_lock(key_map_lock_); + KeyMap::iterator found = key_map_.find(key_id_string); + if (found != key_map_.end()) { + delete found->second; + key_map_.erase(found); + } + key_map_[key_id_string] = symmetric_key; } - key_map_[key_id_string] = symmetric_key; + + client_->KeyAdded(key_system, session_id); +} + +void AesDecryptor::CancelKeyRequest(const std::string& key_system, + const std::string& session_id) { } scoped_refptr<DecoderBuffer> AesDecryptor::Decrypt( @@ -90,7 +142,7 @@ scoped_refptr<DecoderBuffer> AesDecryptor::Decrypt( crypto::SymmetricKey* key = NULL; { - base::AutoLock auto_lock(lock_); + base::AutoLock auto_lock(key_map_lock_); KeyMap::const_iterator found = key_map_.find(key_id_string); if (found == key_map_.end()) { DVLOG(1) << "Could not find a matching key for given key ID."; diff --git a/media/crypto/aes_decryptor.h b/media/crypto/aes_decryptor.h index d62528f..3471c53 100644 --- a/media/crypto/aes_decryptor.h +++ b/media/crypto/aes_decryptor.h @@ -20,23 +20,55 @@ class SymmetricKey; namespace media { class DecoderBuffer; +class DecryptorClient; // Decrypts AES encrypted buffer into unencrypted buffer. +// All public methods other than Decrypt() will be called on the renderer +// thread. Therefore, these calls should be fast and nonblocking, with key +// events fired asynchronously. Decrypt() will be called on the (video/audio) +// decoder thread synchronously. class MEDIA_EXPORT AesDecryptor { public: - AesDecryptor(); + enum KeyError { + kUnknownError = 1, + kClientError, + kServiceError, + kOutputError, + kHardwareChangeError, + kDomainError + }; + + // The AesDecryptor does not take ownership of the |client|. The |client| + // must be valid throughout the lifetime of the AesDecryptor. + explicit AesDecryptor(DecryptorClient* client); ~AesDecryptor(); - // Add a |key_id| and |key| pair to the key system. The key is not limited to - // a decryption key. It can be any data that the key system accepts, such as - // a license. If multiple calls of this function set different keys for the - // same |key_id|, the older key will be replaced by the newer key. - void AddKey(const uint8* key_id, int key_id_size, - const uint8* key, int key_size); + // Generates a key request. The result of this call will be reported via the + // client's KeyMessage() or KeyError() methods. + void GenerateKeyRequest(const std::string& key_system, + const uint8* init_data, + int init_data_length); + + // Adds a |key| to the key system. The key is not limited to a decryption key. + // It can be any data that the key system accepts, such as a license. + // If multiple calls of this function set different keys for the same + // |key_id|, the older key will be replaced by the newer key. + // The result of this call will be reported via the client's KeyAdded(), + // KeyMessage() or KeyError() methods. + void AddKey(const std::string& key_system, + const uint8* key, + int key_length, + const uint8* init_data, + int init_data_length, + const std::string& session_id); + + // Cancels the key request specified by |session_id|. + void CancelKeyRequest(const std::string& key_system, + const std::string& session_id); - // Decrypt |input| buffer. The |input| should not be NULL. - // Return a DecoderBuffer with the decrypted data if decryption succeeded. - // Return NULL if decryption failed. + // Decrypts the |input| buffer, which should not be NULL. + // Returns a DecoderBuffer with the decrypted data if decryption succeeded. + // Returns NULL if decryption failed. scoped_refptr<DecoderBuffer> Decrypt( const scoped_refptr<DecoderBuffer>& input); @@ -44,8 +76,19 @@ class MEDIA_EXPORT AesDecryptor { // KeyMap owns the crypto::SymmetricKey* and must delete them when they are // not needed any more. typedef base::hash_map<std::string, crypto::SymmetricKey*> KeyMap; - KeyMap key_map_; - base::Lock lock_; + + // Since only Decrypt() is called off the renderer thread, we only need to + // protect |key_map_|, the only member variable that is shared between + // Decrypt() and other methods. + KeyMap key_map_; // Protected by the |key_map_lock_|. + base::Lock key_map_lock_; // Protects the |key_map_|. + + DecryptorClient* client_; + + // Make session ID unique per renderer by making it static. + // TODO(xhwang): Make session ID more strictly defined if needed: + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=16739#c0 + static uint32 next_session_id_; DISALLOW_COPY_AND_ASSIGN(AesDecryptor); }; diff --git a/media/crypto/aes_decryptor_unittest.cc b/media/crypto/aes_decryptor_unittest.cc index 0b33f61..69cca61 100644 --- a/media/crypto/aes_decryptor_unittest.cc +++ b/media/crypto/aes_decryptor_unittest.cc @@ -4,45 +4,93 @@ #include <string> +#include "base/basictypes.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 "testing/gtest/include/gtest/gtest.h" +using ::testing::_; +using ::testing::Gt; +using ::testing::NotNull; +using ::testing::SaveArg; +using ::testing::StrNe; + namespace media { -// |kEncryptedData| is encrypted from |kOriginalData| using |kRightKey|, whose -// length is |kKeySize|. Modifying any of these independently would fail the -// test. -static const char kOriginalData[] = "Original data."; -static const int kEncryptedDataSize = 16; -static const unsigned char kEncryptedData[] = - "\x82\x3A\x76\x92\xEC\x7F\xF8\x85\xEC\x23\x52\xFB\x19\xB1\xB9\x09"; -static const int kKeySize = 16; -static const unsigned char kRightKey[] = "A wonderful key!"; -static const unsigned char kWrongKey[] = "I'm a wrong key."; -static const int kKeyIdSize = 9; -static const unsigned char kKeyId1[] = "Key ID 1."; -static const unsigned char kKeyId2[] = "Key ID 2."; +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 +}; +static const uint8 kRightKey[] = { + 0x41, 0x20, 0x77, 0x6f, 0x6e, 0x64, 0x65, 0x72, + 0x66, 0x75, 0x6c, 0x20, 0x6b, 0x65, 0x79, 0x21 +}; +static const uint8 kWrongKey[] = { + 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 kKeyId2[] = { + 0x4b, 0x65, 0x79, 0x20, 0x49, 0x44, 0x20, 0x32 +}; class AesDecryptorTest : public testing::Test { public: - AesDecryptorTest() { - encrypted_data_ = DecoderBuffer::CopyFrom( - kEncryptedData, kEncryptedDataSize); + AesDecryptorTest() : decryptor_(&client_) { + encrypted_data_ = DecoderBuffer::CopyFrom(kEncryptedData, + arraysize(kEncryptedData)); } protected: - void SetKeyIdForEncryptedData(const uint8* key_id, int key_id_size) { + void GenerateKeyRequest() { + EXPECT_CALL(client_, KeyMessageMock(kClearKeySystem, StrNe(std::string()), + NotNull(), Gt(0), "")) + .WillOnce(SaveArg<1>(&session_id_string_)); + decryptor_.GenerateKeyRequest(kClearKeySystem, + kInitData, arraysize(kInitData)); + } + + template <int KeyIdSize, int KeySize> + void AddKeyAndExpectToSucceed(const uint8 (&key_id)[KeyIdSize], + const uint8 (&key)[KeySize]) { + EXPECT_CALL(client_, KeyAdded(kClearKeySystem, session_id_string_)); + decryptor_.AddKey(kClearKeySystem, key, KeySize, key_id, KeyIdSize, + session_id_string_); + } + + template <int KeyIdSize, int KeySize> + void AddKeyAndExpectToFail(const uint8 (&key_id)[KeyIdSize], + const uint8 (&key)[KeySize]) { + EXPECT_CALL(client_, KeyError(kClearKeySystem, session_id_string_, + AesDecryptor::kUnknownError, 0)); + decryptor_.AddKey(kClearKeySystem, key, KeySize, key_id, KeyIdSize, + session_id_string_); + } + + template <int KeyIdSize> + void SetKeyIdForEncryptedData(const uint8 (&key_id)[KeyIdSize]) { encrypted_data_->SetDecryptConfig( - scoped_ptr<DecryptConfig>(new DecryptConfig(key_id, key_id_size))); + scoped_ptr<DecryptConfig>(new DecryptConfig(key_id, KeyIdSize))); } void DecryptAndExpectToSucceed() { scoped_refptr<DecoderBuffer> decrypted = decryptor_.Decrypt(encrypted_data_); ASSERT_TRUE(decrypted); - int data_length = sizeof(kOriginalData) - 1; + int data_length = sizeof(kOriginalData); ASSERT_EQ(data_length, decrypted->GetDataSize()); EXPECT_EQ(0, memcmp(kOriginalData, decrypted->GetData(), data_length)); } @@ -54,34 +102,45 @@ class AesDecryptorTest : public testing::Test { } scoped_refptr<DecoderBuffer> encrypted_data_; + MockDecryptorClient client_; AesDecryptor decryptor_; + std::string session_id_string_; }; TEST_F(AesDecryptorTest, NormalDecryption) { - decryptor_.AddKey(kKeyId1, kKeyIdSize, kRightKey, kKeySize); - SetKeyIdForEncryptedData(kKeyId1, kKeyIdSize); + GenerateKeyRequest(); + AddKeyAndExpectToSucceed(kKeyId1, kRightKey); + SetKeyIdForEncryptedData(kKeyId1); ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed()); } TEST_F(AesDecryptorTest, WrongKey) { - decryptor_.AddKey(kKeyId1, kKeyIdSize, kWrongKey, kKeySize); - SetKeyIdForEncryptedData(kKeyId1, kKeyIdSize); + GenerateKeyRequest(); + AddKeyAndExpectToSucceed(kKeyId1, kWrongKey); + SetKeyIdForEncryptedData(kKeyId1); ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail()); } TEST_F(AesDecryptorTest, MultipleKeys) { - decryptor_.AddKey(kKeyId1, kKeyIdSize, kRightKey, kKeySize); - decryptor_.AddKey(kKeyId2, kKeyIdSize, kWrongKey, kKeySize); - SetKeyIdForEncryptedData(kKeyId1, kKeyIdSize); + GenerateKeyRequest(); + AddKeyAndExpectToSucceed(kKeyId1, kRightKey); + AddKeyAndExpectToSucceed(kKeyId2, kWrongKey); + SetKeyIdForEncryptedData(kKeyId1); ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed()); } TEST_F(AesDecryptorTest, KeyReplacement) { - SetKeyIdForEncryptedData(kKeyId1, kKeyIdSize); - decryptor_.AddKey(kKeyId1, kKeyIdSize, kWrongKey, kKeySize); + GenerateKeyRequest(); + SetKeyIdForEncryptedData(kKeyId1); + AddKeyAndExpectToSucceed(kKeyId1, kWrongKey); ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail()); - decryptor_.AddKey(kKeyId1, kKeyIdSize, kRightKey, kKeySize); + AddKeyAndExpectToSucceed(kKeyId1, kRightKey); ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed()); } +TEST_F(AesDecryptorTest, WrongSizedKey) { + GenerateKeyRequest(); + AddKeyAndExpectToFail(kKeyId1, kWrongSizedKey); +} + } // media diff --git a/media/crypto/decryptor_client.h b/media/crypto/decryptor_client.h new file mode 100644 index 0000000..c15a2f0 --- /dev/null +++ b/media/crypto/decryptor_client.h @@ -0,0 +1,50 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CRYPTO_DECRYPTOR_CLIENT_H_ +#define MEDIA_CRYPTO_DECRYPTOR_CLIENT_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "media/crypto/aes_decryptor.h" + +namespace media { + +// Interface used by a decryptor to fire key events. +// See: http://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/encrypted-media.html#event-summary +class DecryptorClient { + public: + // Signals that a key has been added. + virtual void KeyAdded(const std::string& key_system, + const std::string& session_id) = 0; + + // Signals that a key error occurred. The |system_code| is key + // system-dependent. For clear key system, the |system_code| is always zero. + virtual void KeyError(const std::string& key_system, + const std::string& session_id, + AesDecryptor::KeyError error_code, + int system_code) = 0; + + // Signals that a key message has been generated. + virtual void KeyMessage(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> message, + int message_length, + const std::string& default_url) = 0; + + // Signals that a key is needed for decryption. |key_system| and |session_id| + // can be empty if the key system has not been selected. + virtual void NeedKey(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> init_data, + int init_data_length) = 0; + + protected: + virtual ~DecryptorClient() {} +}; + +} // namespace media + +#endif // MEDIA_CRYPTO_DECRYPTOR_CLIENT_H_ |