diff options
Diffstat (limited to 'chromium/media/cdm/aes_decryptor.cc')
-rw-r--r-- | chromium/media/cdm/aes_decryptor.cc | 359 |
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) |