// Copyright 2013 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. #include "media/cdm/proxy_decryptor.h" #include #include "base/bind.h" #include "base/callback_helpers.h" #include "base/logging.h" #include "base/stl_util.h" #include "base/strings/string_util.h" #include "media/base/cdm_callback_promise.h" #include "media/base/cdm_factory.h" #include "media/base/cdm_key_information.h" #include "media/base/key_systems.h" #include "media/base/media_permission.h" #include "media/cdm/json_web_key.h" #include "media/cdm/key_system_names.h" namespace media { // Special system code to signal a closed persistent session in a SessionError() // call. This is needed because there is no SessionClosed() call in the prefixed // EME API. const int kSessionClosedSystemCode = 29127; ProxyDecryptor::PendingGenerateKeyRequestData::PendingGenerateKeyRequestData( EmeInitDataType init_data_type, const std::vector& init_data) : init_data_type(init_data_type), init_data(init_data) { } ProxyDecryptor::PendingGenerateKeyRequestData:: ~PendingGenerateKeyRequestData() { } ProxyDecryptor::ProxyDecryptor(MediaPermission* media_permission, const KeyAddedCB& key_added_cb, const KeyErrorCB& key_error_cb, const KeyMessageCB& key_message_cb) : is_creating_cdm_(false), media_permission_(media_permission), key_added_cb_(key_added_cb), key_error_cb_(key_error_cb), key_message_cb_(key_message_cb), is_clear_key_(false), weak_ptr_factory_(this) { DCHECK(media_permission); DCHECK(!key_added_cb_.is_null()); DCHECK(!key_error_cb_.is_null()); DCHECK(!key_message_cb_.is_null()); } ProxyDecryptor::~ProxyDecryptor() { // Destroy the decryptor explicitly before destroying the plugin. media_keys_.reset(); } void ProxyDecryptor::CreateCdm(CdmFactory* cdm_factory, const std::string& key_system, const GURL& security_origin, const CdmContextReadyCB& cdm_context_ready_cb) { DVLOG(1) << __FUNCTION__ << ": key_system = " << key_system; DCHECK(!is_creating_cdm_); DCHECK(!media_keys_); // TODO(sandersd): Trigger permissions check here and use it to determine // distinctive identifier support, instead of always requiring the // permission. http://crbug.com/455271 bool allow_distinctive_identifier = true; bool allow_persistent_state = true; is_creating_cdm_ = true; base::WeakPtr weak_this = weak_ptr_factory_.GetWeakPtr(); cdm_factory->Create( key_system, allow_distinctive_identifier, allow_persistent_state, security_origin, base::Bind(&ProxyDecryptor::OnSessionMessage, weak_this), base::Bind(&ProxyDecryptor::OnSessionClosed, weak_this), base::Bind(&ProxyDecryptor::OnLegacySessionError, weak_this), base::Bind(&ProxyDecryptor::OnSessionKeysChange, weak_this), base::Bind(&ProxyDecryptor::OnSessionExpirationUpdate, weak_this), base::Bind(&ProxyDecryptor::OnCdmCreated, weak_this, key_system, security_origin, cdm_context_ready_cb)); } void ProxyDecryptor::OnCdmCreated(const std::string& key_system, const GURL& security_origin, const CdmContextReadyCB& cdm_context_ready_cb, scoped_ptr cdm, const std::string& /* error_message */) { is_creating_cdm_ = false; if (!cdm) { cdm_context_ready_cb.Run(nullptr); } else { key_system_ = key_system; security_origin_ = security_origin; is_clear_key_ = IsClearKey(key_system) || IsExternalClearKey(key_system); media_keys_ = cdm.Pass(); cdm_context_ready_cb.Run(media_keys_->GetCdmContext()); } for (const auto& request : pending_requests_) GenerateKeyRequestInternal(request->init_data_type, request->init_data); pending_requests_.clear(); } void ProxyDecryptor::GenerateKeyRequest(EmeInitDataType init_data_type, const uint8* init_data, int init_data_length) { std::vector init_data_vector(init_data, init_data + init_data_length); if (is_creating_cdm_) { pending_requests_.push_back( new PendingGenerateKeyRequestData(init_data_type, init_data_vector)); return; } GenerateKeyRequestInternal(init_data_type, init_data_vector); } // Returns true if |data| is prefixed with |header| and has data after the // |header|. bool HasHeader(const std::vector& data, const std::string& header) { return data.size() > header.size() && std::equal(header.begin(), header.end(), data.begin()); } // Removes the first |length| items from |data|. void StripHeader(std::vector& data, size_t length) { data.erase(data.begin(), data.begin() + length); } void ProxyDecryptor::GenerateKeyRequestInternal( EmeInitDataType init_data_type, const std::vector& init_data) { DVLOG(1) << __FUNCTION__; DCHECK(!is_creating_cdm_); if (!media_keys_) { OnLegacySessionError(std::string(), MediaKeys::NOT_SUPPORTED_ERROR, 0, "CDM creation failed."); return; } const char kPrefixedApiPersistentSessionHeader[] = "PERSISTENT|"; const char kPrefixedApiLoadSessionHeader[] = "LOAD_SESSION|"; SessionCreationType session_creation_type = TemporarySession; std::vector stripped_init_data = init_data; if (HasHeader(init_data, kPrefixedApiLoadSessionHeader)) { session_creation_type = LoadSession; StripHeader(stripped_init_data, strlen(kPrefixedApiLoadSessionHeader)); } else if (HasHeader(init_data, kPrefixedApiPersistentSessionHeader)) { session_creation_type = PersistentSession; StripHeader(stripped_init_data, strlen(kPrefixedApiPersistentSessionHeader)); } scoped_ptr promise(new CdmCallbackPromise( base::Bind(&ProxyDecryptor::SetSessionId, weak_ptr_factory_.GetWeakPtr(), session_creation_type), base::Bind(&ProxyDecryptor::OnLegacySessionError, weak_ptr_factory_.GetWeakPtr(), std::string()))); // No session id until created. if (session_creation_type == LoadSession) { media_keys_->LoadSession( MediaKeys::PERSISTENT_LICENSE_SESSION, std::string( reinterpret_cast(vector_as_array(&stripped_init_data)), stripped_init_data.size()), promise.Pass()); return; } MediaKeys::SessionType session_type = session_creation_type == PersistentSession ? MediaKeys::PERSISTENT_LICENSE_SESSION : MediaKeys::TEMPORARY_SESSION; // No permission required when AesDecryptor is used or when the key system is // external clear key. DCHECK(!key_system_.empty()); if (CanUseAesDecryptor(key_system_) || IsExternalClearKey(key_system_)) { OnPermissionStatus(session_type, init_data_type, stripped_init_data, promise.Pass(), true /* granted */); return; } #if defined(OS_CHROMEOS) || defined(OS_ANDROID) media_permission_->RequestPermission( MediaPermission::PROTECTED_MEDIA_IDENTIFIER, security_origin_, base::Bind(&ProxyDecryptor::OnPermissionStatus, weak_ptr_factory_.GetWeakPtr(), session_type, init_data_type, stripped_init_data, base::Passed(&promise))); #else OnPermissionStatus(session_type, init_data_type, stripped_init_data, promise.Pass(), true /* granted */); #endif } void ProxyDecryptor::OnPermissionStatus( MediaKeys::SessionType session_type, EmeInitDataType init_data_type, const std::vector& init_data, scoped_ptr promise, bool granted) { // ProxyDecryptor is only used by Prefixed EME, where RequestPermission() is // only for triggering the permission UI. Later CheckPermission() will be // called (e.g. in PlatformVerificationFlow on ChromeOS; in BrowserCdmManager // on Android) and the permission status will be evaluated then. DVLOG_IF(1, !granted) << "Permission request rejected."; media_keys_->CreateSessionAndGenerateRequest(session_type, init_data_type, init_data, promise.Pass()); } void ProxyDecryptor::AddKey(const uint8* key, int key_length, const uint8* init_data, int init_data_length, const std::string& session_id) { DVLOG(1) << "AddKey()"; if (!media_keys_) { OnLegacySessionError(std::string(), MediaKeys::INVALID_STATE_ERROR, 0, "CDM is not available."); return; } // In the prefixed API, the session parameter provided to addKey() is // optional, so use the single existing session if it exists. std::string new_session_id(session_id); if (new_session_id.empty()) { if (active_sessions_.size() == 1) { base::hash_map::iterator it = active_sessions_.begin(); new_session_id = it->first; } else { OnLegacySessionError(std::string(), MediaKeys::NOT_SUPPORTED_ERROR, 0, "SessionId not specified."); return; } } scoped_ptr promise(new CdmCallbackPromise<>( base::Bind(&ProxyDecryptor::GenerateKeyAdded, weak_ptr_factory_.GetWeakPtr(), session_id), base::Bind(&ProxyDecryptor::OnLegacySessionError, weak_ptr_factory_.GetWeakPtr(), session_id))); // EME WD spec only supports a single array passed to the CDM. For // Clear Key using v0.1b, both arrays are used (|init_data| is key_id). // Since the EME WD spec supports the key as a JSON Web Key, // convert the 2 arrays to a JWK and pass it as the single array. if (is_clear_key_) { // Decryptor doesn't support empty key ID (see http://crbug.com/123265). // So ensure a non-empty value is passed. if (!init_data) { static const uint8 kDummyInitData[1] = {0}; init_data = kDummyInitData; init_data_length = arraysize(kDummyInitData); } std::string jwk = GenerateJWKSet(key, key_length, init_data, init_data_length); DCHECK(!jwk.empty()); media_keys_->UpdateSession(new_session_id, std::vector(jwk.begin(), jwk.end()), promise.Pass()); return; } media_keys_->UpdateSession(new_session_id, std::vector(key, key + key_length), promise.Pass()); } void ProxyDecryptor::CancelKeyRequest(const std::string& session_id) { DVLOG(1) << "CancelKeyRequest()"; if (!media_keys_) { OnLegacySessionError(std::string(), MediaKeys::INVALID_STATE_ERROR, 0, "CDM is not available."); return; } scoped_ptr promise(new CdmCallbackPromise<>( base::Bind(&ProxyDecryptor::OnSessionClosed, weak_ptr_factory_.GetWeakPtr(), session_id), base::Bind(&ProxyDecryptor::OnLegacySessionError, weak_ptr_factory_.GetWeakPtr(), session_id))); media_keys_->RemoveSession(session_id, promise.Pass()); } void ProxyDecryptor::OnSessionMessage(const std::string& session_id, MediaKeys::MessageType message_type, const std::vector& message, const GURL& legacy_destination_url) { // Assumes that OnSessionCreated() has been called before this. // For ClearKey, convert the message from JSON into just passing the key // as the message. If unable to extract the key, return the message unchanged. if (is_clear_key_) { std::vector key; if (ExtractFirstKeyIdFromLicenseRequest(message, &key)) { key_message_cb_.Run(session_id, key, legacy_destination_url); return; } } key_message_cb_.Run(session_id, message, legacy_destination_url); } void ProxyDecryptor::OnSessionKeysChange(const std::string& session_id, bool has_additional_usable_key, CdmKeysInfo keys_info) { // EME v0.1b doesn't support this event. } void ProxyDecryptor::OnSessionExpirationUpdate( const std::string& session_id, const base::Time& new_expiry_time) { // EME v0.1b doesn't support this event. } void ProxyDecryptor::GenerateKeyAdded(const std::string& session_id) { // EME WD doesn't support this event, but it is needed for EME v0.1b. key_added_cb_.Run(session_id); } void ProxyDecryptor::OnSessionClosed(const std::string& session_id) { base::hash_map::iterator it = active_sessions_.find(session_id); // Latest EME spec separates closing a session ("allows an application to // indicate that it no longer needs the session") and actually closing the // session (done by the CDM at any point "such as in response to a close() // call, when the session is no longer needed, or when system resources are // lost.") Thus the CDM may cause 2 close() events -- one to resolve the // close() promise, and a second to actually close the session. Prefixed EME // only expects 1 close event, so drop the second (and subsequent) events. // However, this means we can't tell if the CDM is generating spurious close() // events. if (it == active_sessions_.end()) return; if (it->second) { OnLegacySessionError(session_id, MediaKeys::NOT_SUPPORTED_ERROR, kSessionClosedSystemCode, "Do not close persistent sessions."); } active_sessions_.erase(it); } void ProxyDecryptor::OnLegacySessionError(const std::string& session_id, MediaKeys::Exception exception_code, uint32 system_code, const std::string& error_message) { // Convert |error_name| back to MediaKeys::KeyError if possible. Prefixed // EME has different error message, so all the specific error events will // get lost. MediaKeys::KeyError error_code; switch (exception_code) { case MediaKeys::CLIENT_ERROR: error_code = MediaKeys::kClientError; break; case MediaKeys::OUTPUT_ERROR: error_code = MediaKeys::kOutputError; break; default: // This will include all other CDM4 errors and any error generated // by CDM5 or later. error_code = MediaKeys::kUnknownError; break; } key_error_cb_.Run(session_id, error_code, system_code); } void ProxyDecryptor::SetSessionId(SessionCreationType session_type, const std::string& session_id) { // Loaded sessions are considered persistent. bool is_persistent = session_type == PersistentSession || session_type == LoadSession; active_sessions_.insert(std::make_pair(session_id, is_persistent)); // For LoadSession(), generate the KeyAdded event. if (session_type == LoadSession) GenerateKeyAdded(session_id); } } // namespace media