summaryrefslogtreecommitdiff
path: root/chromium/media/cdm/aes_decryptor.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/media/cdm/aes_decryptor.cc')
-rw-r--r--chromium/media/cdm/aes_decryptor.cc359
1 files changed, 170 insertions, 189 deletions
diff --git a/chromium/media/cdm/aes_decryptor.cc b/chromium/media/cdm/aes_decryptor.cc
index 33717e03a58..de6f83474f0 100644
--- a/chromium/media/cdm/aes_decryptor.cc
+++ b/chromium/media/cdm/aes_decryptor.cc
@@ -4,15 +4,12 @@
#include "media/cdm/aes_decryptor.h"
+#include <list>
#include <vector>
-#include "base/base64.h"
-#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
-#include "base/strings/string_util.h"
-#include "base/values.h"
#include "crypto/encryptor.h"
#include "crypto/symmetric_key.h"
#include "media/base/audio_decoder_config.h"
@@ -20,18 +17,92 @@
#include "media/base/decrypt_config.h"
#include "media/base/video_decoder_config.h"
#include "media/base/video_frame.h"
+#include "media/cdm/json_web_key.h"
namespace media {
-uint32 AesDecryptor::next_session_id_ = 1;
+// Keeps track of the session IDs and DecryptionKeys. The keys are ordered by
+// insertion time (last insertion is first). It takes ownership of the
+// DecryptionKeys.
+class AesDecryptor::SessionIdDecryptionKeyMap {
+ // Use a std::list to actually hold the data. Insertion is always done
+ // at the front, so the "latest" decryption key is always the first one
+ // in the list.
+ typedef std::list<std::pair<uint32, DecryptionKey*> > KeyList;
+
+ public:
+ SessionIdDecryptionKeyMap() {}
+ ~SessionIdDecryptionKeyMap() { STLDeleteValues(&key_list_); }
+
+ // Replaces value if |session_id| is already present, or adds it if not.
+ // This |decryption_key| becomes the latest until another insertion or
+ // |session_id| is erased.
+ void Insert(uint32 session_id, scoped_ptr<DecryptionKey> decryption_key);
+
+ // Deletes the entry for |session_id| if present.
+ void Erase(const uint32 session_id);
+
+ // Returns whether the list is empty
+ bool Empty() const { return key_list_.empty(); }
+
+ // Returns the last inserted DecryptionKey.
+ DecryptionKey* LatestDecryptionKey() {
+ DCHECK(!key_list_.empty());
+ return key_list_.begin()->second;
+ }
+
+ private:
+ // Searches the list for an element with |session_id|.
+ KeyList::iterator Find(const uint32 session_id);
+
+ // Deletes the entry pointed to by |position|.
+ void Erase(KeyList::iterator position);
+
+ KeyList key_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(SessionIdDecryptionKeyMap);
+};
+
+void AesDecryptor::SessionIdDecryptionKeyMap::Insert(
+ uint32 session_id,
+ scoped_ptr<DecryptionKey> decryption_key) {
+ KeyList::iterator it = Find(session_id);
+ if (it != key_list_.end())
+ Erase(it);
+ DecryptionKey* raw_ptr = decryption_key.release();
+ key_list_.push_front(std::make_pair(session_id, raw_ptr));
+}
+
+void AesDecryptor::SessionIdDecryptionKeyMap::Erase(const uint32 session_id) {
+ KeyList::iterator it = Find(session_id);
+ if (it == key_list_.end())
+ return;
+ Erase(it);
+}
+
+AesDecryptor::SessionIdDecryptionKeyMap::KeyList::iterator
+AesDecryptor::SessionIdDecryptionKeyMap::Find(const uint32 session_id) {
+ for (KeyList::iterator it = key_list_.begin(); it != key_list_.end(); ++it) {
+ if (it->first == session_id)
+ return it;
+ }
+ return key_list_.end();
+}
+
+void AesDecryptor::SessionIdDecryptionKeyMap::Erase(
+ KeyList::iterator position) {
+ DCHECK(position->second);
+ delete position->second;
+ key_list_.erase(position);
+}
+
+uint32 AesDecryptor::next_web_session_id_ = 1;
enum ClearBytesBufferSel {
kSrcContainsClearBytes,
kDstContainsClearBytes
};
-typedef std::vector<std::pair<std::string, std::string> > JWKKeys;
-
static void CopySubsamples(const std::vector<SubsampleEntry>& subsamples,
const ClearBytesBufferSel sel,
const uint8* src,
@@ -49,105 +120,6 @@ static void CopySubsamples(const std::vector<SubsampleEntry>& subsamples,
}
}
-// Processes a JSON Web Key to extract the key id and key value. Adds the
-// id/value pair to |jwk_keys| and returns true on success.
-static bool ProcessSymmetricKeyJWK(const DictionaryValue& jwk,
- JWKKeys* jwk_keys) {
- // A symmetric keys JWK looks like the following in JSON:
- // { "kty":"oct",
- // "kid":"AAECAwQFBgcICQoLDA0ODxAREhM=",
- // "k":"FBUWFxgZGhscHR4fICEiIw==" }
- // There may be other properties specified, but they are ignored.
- // Ref: http://tools.ietf.org/html/draft-ietf-jose-json-web-key-14
- // and:
- // http://tools.ietf.org/html/draft-jones-jose-json-private-and-symmetric-key-00
-
- // Have found a JWK, start by checking that it is a symmetric key.
- std::string type;
- if (!jwk.GetString("kty", &type) || type != "oct") {
- DVLOG(1) << "JWK is not a symmetric key";
- return false;
- }
-
- // Get the key id and actual key parameters.
- std::string encoded_key_id;
- std::string encoded_key;
- if (!jwk.GetString("kid", &encoded_key_id)) {
- DVLOG(1) << "Missing 'kid' parameter";
- return false;
- }
- if (!jwk.GetString("k", &encoded_key)) {
- DVLOG(1) << "Missing 'k' parameter";
- return false;
- }
-
- // Key ID and key are base64-encoded strings, so decode them.
- // TODO(jrummell): The JWK spec and the EME spec don't say that 'kid' must be
- // base64-encoded (they don't say anything at all). Verify with the EME spec.
- std::string decoded_key_id;
- std::string decoded_key;
- if (!base::Base64Decode(encoded_key_id, &decoded_key_id) ||
- decoded_key_id.empty()) {
- DVLOG(1) << "Invalid 'kid' value";
- return false;
- }
- if (!base::Base64Decode(encoded_key, &decoded_key) ||
- decoded_key.length() !=
- static_cast<size_t>(DecryptConfig::kDecryptionKeySize)) {
- DVLOG(1) << "Invalid length of 'k' " << decoded_key.length();
- return false;
- }
-
- // Add the decoded key ID and the decoded key to the list.
- jwk_keys->push_back(std::make_pair(decoded_key_id, decoded_key));
- return true;
-}
-
-// Extracts the JSON Web Keys from a JSON Web Key Set. If |input| looks like
-// a valid JWK Set, then true is returned and |jwk_keys| is updated to contain
-// the list of keys found. Otherwise return false.
-static bool ExtractJWKKeys(const std::string& input, JWKKeys* jwk_keys) {
- // TODO(jrummell): The EME spec references a smaller set of allowed ASCII
- // values. Verify with spec that the smaller character set is needed.
- if (!IsStringASCII(input))
- return false;
-
- scoped_ptr<Value> root(base::JSONReader().ReadToValue(input));
- if (!root.get() || root->GetType() != Value::TYPE_DICTIONARY)
- return false;
-
- // A JSON Web Key Set looks like the following in JSON:
- // { "keys": [ JWK1, JWK2, ... ] }
- // (See ProcessSymmetricKeyJWK() for description of JWK.)
- // There may be other properties specified, but they are ignored.
- // Locate the set from the dictionary.
- DictionaryValue* dictionary = static_cast<DictionaryValue*>(root.get());
- ListValue* list_val = NULL;
- if (!dictionary->GetList("keys", &list_val)) {
- DVLOG(1) << "Missing 'keys' parameter or not a list in JWK Set";
- return false;
- }
-
- // Create a local list of keys, so that |jwk_keys| only gets updated on
- // success.
- JWKKeys local_keys;
- for (size_t i = 0; i < list_val->GetSize(); ++i) {
- DictionaryValue* jwk = NULL;
- if (!list_val->GetDictionary(i, &jwk)) {
- DVLOG(1) << "Unable to access 'keys'[" << i << "] in JWK Set";
- return false;
- }
- if (!ProcessSymmetricKeyJWK(*jwk, &local_keys)) {
- DVLOG(1) << "Error from 'keys'[" << i << "]";
- return false;
- }
- }
-
- // Successfully processed all JWKs in the set.
- jwk_keys->swap(local_keys);
- return true;
-}
-
// Decrypts |input| using |key|. Returns a DecoderBuffer with the decrypted
// data if decryption succeeded or NULL if decryption failed.
static scoped_refptr<DecoderBuffer> DecryptData(const DecoderBuffer& input,
@@ -246,22 +218,30 @@ static scoped_refptr<DecoderBuffer> DecryptData(const DecoderBuffer& input,
return output;
}
-AesDecryptor::AesDecryptor(const KeyAddedCB& key_added_cb,
- const KeyErrorCB& key_error_cb,
- const KeyMessageCB& key_message_cb)
- : key_added_cb_(key_added_cb),
- key_error_cb_(key_error_cb),
- key_message_cb_(key_message_cb) {
-}
+AesDecryptor::AesDecryptor(const SessionCreatedCB& session_created_cb,
+ const SessionMessageCB& session_message_cb,
+ const SessionReadyCB& session_ready_cb,
+ const SessionClosedCB& session_closed_cb,
+ const SessionErrorCB& session_error_cb)
+ : session_created_cb_(session_created_cb),
+ session_message_cb_(session_message_cb),
+ session_ready_cb_(session_ready_cb),
+ session_closed_cb_(session_closed_cb),
+ session_error_cb_(session_error_cb) {}
AesDecryptor::~AesDecryptor() {
- STLDeleteValues(&key_map_);
+ key_map_.clear();
}
-bool AesDecryptor::GenerateKeyRequest(const std::string& type,
- const uint8* init_data,
- int init_data_length) {
- std::string session_id_string(base::UintToString(next_session_id_++));
+bool AesDecryptor::CreateSession(uint32 session_id,
+ const std::string& type,
+ const uint8* init_data,
+ int init_data_length) {
+ // Validate that this is a new session.
+ DCHECK(valid_sessions_.find(session_id) == valid_sessions_.end());
+ valid_sessions_.insert(session_id);
+
+ std::string web_session_id_string(base::UintToString(next_web_session_id_++));
// For now, the AesDecryptor does not care about |type|;
// just fire the event with the |init_data| as the request.
@@ -269,74 +249,41 @@ bool AesDecryptor::GenerateKeyRequest(const std::string& type,
if (init_data && init_data_length)
message.assign(init_data, init_data + init_data_length);
- key_message_cb_.Run(session_id_string, message, std::string());
+ session_created_cb_.Run(session_id, web_session_id_string);
+ session_message_cb_.Run(session_id, message, std::string());
return true;
}
-void AesDecryptor::AddKey(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);
-
- // AddKey() is called from update(), where the key(s) are passed as a JSON
- // Web Key (JWK) set. Each JWK needs to be a symmetric key ('kty' = "oct"),
- // with 'kid' being the base64-encoded key id, and 'k' being the
- // base64-encoded key.
- //
- // For backwards compatibility with v0.1b of the spec (where |key| is the raw
- // key and |init_data| is the key id), if |key| is not valid JSON, then
- // attempt to process it as a raw key.
-
- // TODO(xhwang): Add |session_id| check after we figure out how:
- // https://www.w3.org/Bugs/Public/show_bug.cgi?id=16550
-
- std::string key_string(reinterpret_cast<const char*>(key), key_length);
- JWKKeys jwk_keys;
- if (ExtractJWKKeys(key_string, &jwk_keys)) {
- // Since |key| represents valid JSON, init_data must be empty.
- DCHECK(!init_data);
- DCHECK_EQ(init_data_length, 0);
-
- // Make sure that at least one key was extracted.
- if (jwk_keys.empty()) {
- key_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0);
- return;
- }
- for (JWKKeys::iterator it = jwk_keys.begin() ; it != jwk_keys.end(); ++it) {
- if (!AddDecryptionKey(it->first, it->second)) {
- key_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0);
- return;
- }
- }
- } else {
- // v0.1b backwards compatibility support.
- // TODO(jrummell): Remove this code once v0.1b no longer supported.
+void AesDecryptor::UpdateSession(uint32 session_id,
+ const uint8* response,
+ int response_length) {
+ CHECK(response);
+ CHECK_GT(response_length, 0);
+ DCHECK(valid_sessions_.find(session_id) != valid_sessions_.end());
+
+ std::string key_string(reinterpret_cast<const char*>(response),
+ response_length);
+ KeyIdAndKeyPairs keys;
+ if (!ExtractKeysFromJWKSet(key_string, &keys)) {
+ session_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0);
+ return;
+ }
+
+ // Make sure that at least one key was extracted.
+ if (keys.empty()) {
+ session_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0);
+ return;
+ }
- if (key_string.length() !=
+ for (KeyIdAndKeyPairs::iterator it = keys.begin(); it != keys.end(); ++it) {
+ if (it->second.length() !=
static_cast<size_t>(DecryptConfig::kDecryptionKeySize)) {
DVLOG(1) << "Invalid key length: " << key_string.length();
- key_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0);
+ session_error_cb_.Run(session_id, MediaKeys::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);
- if (!AddDecryptionKey(key_id_string, key_string)) {
- // Error logged in AddDecryptionKey()
- key_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0);
+ if (!AddDecryptionKey(session_id, it->first, it->second)) {
+ session_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0);
return;
}
}
@@ -347,10 +294,17 @@ void AesDecryptor::AddKey(const uint8* key,
if (!new_video_key_cb_.is_null())
new_video_key_cb_.Run();
- key_added_cb_.Run(session_id);
+ session_ready_cb_.Run(session_id);
}
-void AesDecryptor::CancelKeyRequest(const std::string& session_id) {
+void AesDecryptor::ReleaseSession(uint32 session_id) {
+ // Validate that this is a reference to an active session and then forget it.
+ std::set<uint32>::iterator it = valid_sessions_.find(session_id);
+ DCHECK(it != valid_sessions_.end());
+ valid_sessions_.erase(it);
+
+ DeleteKeysForSession(session_id);
+ session_closed_cb_.Run(session_id);
}
Decryptor* AesDecryptor::GetDecryptor() {
@@ -441,7 +395,8 @@ void AesDecryptor::DeinitializeDecoder(StreamType stream_type) {
NOTREACHED() << "AesDecryptor does not support audio/video decoding";
}
-bool AesDecryptor::AddDecryptionKey(const std::string& key_id,
+bool AesDecryptor::AddDecryptionKey(const uint32 session_id,
+ const std::string& key_id,
const std::string& key_string) {
scoped_ptr<DecryptionKey> decryption_key(new DecryptionKey(key_string));
if (!decryption_key) {
@@ -455,23 +410,49 @@ bool AesDecryptor::AddDecryptionKey(const std::string& key_id,
}
base::AutoLock auto_lock(key_map_lock_);
- KeyMap::iterator found = key_map_.find(key_id);
- if (found != key_map_.end()) {
- delete found->second;
- key_map_.erase(found);
+ KeyIdToSessionKeysMap::iterator key_id_entry = key_map_.find(key_id);
+ if (key_id_entry != key_map_.end()) {
+ key_id_entry->second->Insert(session_id, decryption_key.Pass());
+ return true;
}
- key_map_[key_id] = decryption_key.release();
+
+ // |key_id| not found, so need to create new entry.
+ scoped_ptr<SessionIdDecryptionKeyMap> inner_map(
+ new SessionIdDecryptionKeyMap());
+ inner_map->Insert(session_id, decryption_key.Pass());
+ key_map_.add(key_id, inner_map.Pass());
return true;
}
AesDecryptor::DecryptionKey* AesDecryptor::GetKey(
const std::string& key_id) const {
base::AutoLock auto_lock(key_map_lock_);
- KeyMap::const_iterator found = key_map_.find(key_id);
- if (found == key_map_.end())
+ KeyIdToSessionKeysMap::const_iterator key_id_found = key_map_.find(key_id);
+ if (key_id_found == key_map_.end())
return NULL;
- return found->second;
+ // Return the key from the "latest" session_id entry.
+ return key_id_found->second->LatestDecryptionKey();
+}
+
+void AesDecryptor::DeleteKeysForSession(const uint32 session_id) {
+ base::AutoLock auto_lock(key_map_lock_);
+
+ // Remove all keys associated with |session_id|. Since the data is optimized
+ // for access in GetKey(), we need to look at each entry in |key_map_|.
+ KeyIdToSessionKeysMap::iterator it = key_map_.begin();
+ while (it != key_map_.end()) {
+ it->second->Erase(session_id);
+ if (it->second->Empty()) {
+ // Need to get rid of the entry for this key_id. This will mess up the
+ // iterator, so we need to increment it first.
+ KeyIdToSessionKeysMap::iterator current = it;
+ ++it;
+ key_map_.erase(current);
+ } else {
+ ++it;
+ }
+ }
}
AesDecryptor::DecryptionKey::DecryptionKey(const std::string& secret)