summaryrefslogtreecommitdiffstats
path: root/media/crypto
diff options
context:
space:
mode:
authorxhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-16 01:27:36 +0000
committerxhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-16 01:27:36 +0000
commitcbdc8c2b4b46c7e7886c9e99bc595a0b9936b381 (patch)
treefd03ab4a8636680c4c2dff01880751e25ef0cdca /media/crypto
parent46b7d64e85a9d8d28372d05b59b295dd6ecb0bc8 (diff)
downloadchromium_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.cc82
-rw-r--r--media/crypto/aes_decryptor.h67
-rw-r--r--media/crypto/aes_decryptor_unittest.cc117
-rw-r--r--media/crypto/decryptor_client.h50
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_