| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "media/cdm/proxy_decryptor.h" | |
| 6 | |
| 7 #include <stddef.h> | |
| 8 #include <cstring> | |
| 9 #include <utility> | |
| 10 | |
| 11 #include "base/bind.h" | |
| 12 #include "base/callback_helpers.h" | |
| 13 #include "base/logging.h" | |
| 14 #include "base/macros.h" | |
| 15 #include "base/strings/string_util.h" | |
| 16 #include "build/build_config.h" | |
| 17 #include "media/base/cdm_callback_promise.h" | |
| 18 #include "media/base/cdm_config.h" | |
| 19 #include "media/base/cdm_factory.h" | |
| 20 #include "media/base/cdm_key_information.h" | |
| 21 #include "media/base/key_systems.h" | |
| 22 #include "media/base/media_permission.h" | |
| 23 #include "media/cdm/json_web_key.h" | |
| 24 #include "media/cdm/key_system_names.h" | |
| 25 | |
| 26 namespace media { | |
| 27 | |
| 28 // Special system code to signal a closed persistent session in a SessionError() | |
| 29 // call. This is needed because there is no SessionClosed() call in the prefixed | |
| 30 // EME API. | |
| 31 const int kSessionClosedSystemCode = 29127; | |
| 32 | |
| 33 ProxyDecryptor::PendingGenerateKeyRequestData::PendingGenerateKeyRequestData( | |
| 34 EmeInitDataType init_data_type, | |
| 35 const std::vector<uint8_t>& init_data) | |
| 36 : init_data_type(init_data_type), init_data(init_data) {} | |
| 37 | |
| 38 ProxyDecryptor::PendingGenerateKeyRequestData:: | |
| 39 ~PendingGenerateKeyRequestData() { | |
| 40 } | |
| 41 | |
| 42 ProxyDecryptor::ProxyDecryptor(MediaPermission* media_permission, | |
| 43 bool use_hw_secure_codecs, | |
| 44 const KeyAddedCB& key_added_cb, | |
| 45 const KeyErrorCB& key_error_cb, | |
| 46 const KeyMessageCB& key_message_cb) | |
| 47 : is_creating_cdm_(false), | |
| 48 #if defined(OS_CHROMEOS) || defined(OS_ANDROID) | |
| 49 media_permission_(media_permission), | |
| 50 #endif | |
| 51 use_hw_secure_codecs_(use_hw_secure_codecs), | |
| 52 key_added_cb_(key_added_cb), | |
| 53 key_error_cb_(key_error_cb), | |
| 54 key_message_cb_(key_message_cb), | |
| 55 is_clear_key_(false), | |
| 56 weak_ptr_factory_(this) { | |
| 57 DCHECK(media_permission); | |
| 58 DCHECK(!key_added_cb_.is_null()); | |
| 59 DCHECK(!key_error_cb_.is_null()); | |
| 60 DCHECK(!key_message_cb_.is_null()); | |
| 61 } | |
| 62 | |
| 63 ProxyDecryptor::~ProxyDecryptor() { | |
| 64 // Destroy the decryptor explicitly before destroying the plugin. | |
| 65 media_keys_ = nullptr; | |
| 66 } | |
| 67 | |
| 68 void ProxyDecryptor::CreateCdm(CdmFactory* cdm_factory, | |
| 69 const std::string& key_system, | |
| 70 const GURL& security_origin, | |
| 71 const CdmContextReadyCB& cdm_context_ready_cb) { | |
| 72 DVLOG(1) << __FUNCTION__ << ": key_system = " << key_system; | |
| 73 DCHECK(!is_creating_cdm_); | |
| 74 DCHECK(!media_keys_); | |
| 75 | |
| 76 // TODO(sandersd): Trigger permissions check here and use it to determine | |
| 77 // distinctive identifier support, instead of always requiring the | |
| 78 // permission. http://crbug.com/455271 | |
| 79 CdmConfig cdm_config; | |
| 80 cdm_config.allow_distinctive_identifier = true; | |
| 81 cdm_config.allow_persistent_state = true; | |
| 82 cdm_config.use_hw_secure_codecs = use_hw_secure_codecs_; | |
| 83 | |
| 84 is_creating_cdm_ = true; | |
| 85 | |
| 86 base::WeakPtr<ProxyDecryptor> weak_this = weak_ptr_factory_.GetWeakPtr(); | |
| 87 cdm_factory->Create( | |
| 88 key_system, security_origin, cdm_config, | |
| 89 base::Bind(&ProxyDecryptor::OnSessionMessage, weak_this), | |
| 90 base::Bind(&ProxyDecryptor::OnSessionClosed, weak_this), | |
| 91 base::Bind(&ProxyDecryptor::OnLegacySessionError, weak_this), | |
| 92 base::Bind(&ProxyDecryptor::OnSessionKeysChange, weak_this), | |
| 93 base::Bind(&ProxyDecryptor::OnSessionExpirationUpdate, weak_this), | |
| 94 base::Bind(&ProxyDecryptor::OnCdmCreated, weak_this, key_system, | |
| 95 security_origin, cdm_context_ready_cb)); | |
| 96 } | |
| 97 | |
| 98 void ProxyDecryptor::OnCdmCreated(const std::string& key_system, | |
| 99 const GURL& security_origin, | |
| 100 const CdmContextReadyCB& cdm_context_ready_cb, | |
| 101 const scoped_refptr<MediaKeys>& cdm, | |
| 102 const std::string& /* error_message */) { | |
| 103 is_creating_cdm_ = false; | |
| 104 | |
| 105 if (!cdm) { | |
| 106 cdm_context_ready_cb.Run(nullptr); | |
| 107 } else { | |
| 108 key_system_ = key_system; | |
| 109 security_origin_ = security_origin; | |
| 110 is_clear_key_ = IsClearKey(key_system) || IsExternalClearKey(key_system); | |
| 111 media_keys_ = cdm; | |
| 112 | |
| 113 cdm_context_ready_cb.Run(media_keys_->GetCdmContext()); | |
| 114 } | |
| 115 | |
| 116 for (const auto& request : pending_requests_) | |
| 117 GenerateKeyRequestInternal(request->init_data_type, request->init_data); | |
| 118 | |
| 119 pending_requests_.clear(); | |
| 120 } | |
| 121 | |
| 122 void ProxyDecryptor::GenerateKeyRequest(EmeInitDataType init_data_type, | |
| 123 const uint8_t* init_data, | |
| 124 int init_data_length) { | |
| 125 std::vector<uint8_t> init_data_vector(init_data, | |
| 126 init_data + init_data_length); | |
| 127 | |
| 128 if (is_creating_cdm_) { | |
| 129 pending_requests_.push_back( | |
| 130 new PendingGenerateKeyRequestData(init_data_type, init_data_vector)); | |
| 131 return; | |
| 132 } | |
| 133 | |
| 134 GenerateKeyRequestInternal(init_data_type, init_data_vector); | |
| 135 } | |
| 136 | |
| 137 // Returns true if |data| is prefixed with |header| and has data after the | |
| 138 // |header|. | |
| 139 static bool HasHeader(const std::vector<uint8_t>& data, | |
| 140 const std::string& header) { | |
| 141 return data.size() > header.size() && | |
| 142 std::equal(header.begin(), header.end(), data.begin()); | |
| 143 } | |
| 144 | |
| 145 // Removes the first |length| items from |data|. | |
| 146 static void StripHeader(std::vector<uint8_t>& data, size_t length) { | |
| 147 data.erase(data.begin(), data.begin() + length); | |
| 148 } | |
| 149 | |
| 150 void ProxyDecryptor::GenerateKeyRequestInternal( | |
| 151 EmeInitDataType init_data_type, | |
| 152 const std::vector<uint8_t>& init_data) { | |
| 153 DVLOG(1) << __FUNCTION__; | |
| 154 DCHECK(!is_creating_cdm_); | |
| 155 | |
| 156 if (!media_keys_) { | |
| 157 OnLegacySessionError(std::string(), MediaKeys::NOT_SUPPORTED_ERROR, 0, | |
| 158 "CDM creation failed."); | |
| 159 return; | |
| 160 } | |
| 161 | |
| 162 const char kPrefixedApiPersistentSessionHeader[] = "PERSISTENT|"; | |
| 163 const char kPrefixedApiLoadSessionHeader[] = "LOAD_SESSION|"; | |
| 164 | |
| 165 SessionCreationType session_creation_type = TemporarySession; | |
| 166 std::vector<uint8_t> stripped_init_data = init_data; | |
| 167 if (HasHeader(init_data, kPrefixedApiLoadSessionHeader)) { | |
| 168 session_creation_type = LoadSession; | |
| 169 StripHeader(stripped_init_data, strlen(kPrefixedApiLoadSessionHeader)); | |
| 170 } else if (HasHeader(init_data, kPrefixedApiPersistentSessionHeader)) { | |
| 171 session_creation_type = PersistentSession; | |
| 172 StripHeader(stripped_init_data, | |
| 173 strlen(kPrefixedApiPersistentSessionHeader)); | |
| 174 } | |
| 175 | |
| 176 scoped_ptr<NewSessionCdmPromise> promise(new CdmCallbackPromise<std::string>( | |
| 177 base::Bind(&ProxyDecryptor::SetSessionId, weak_ptr_factory_.GetWeakPtr(), | |
| 178 session_creation_type), | |
| 179 base::Bind(&ProxyDecryptor::OnLegacySessionError, | |
| 180 weak_ptr_factory_.GetWeakPtr(), | |
| 181 std::string()))); // No session id until created. | |
| 182 | |
| 183 if (session_creation_type == LoadSession) { | |
| 184 media_keys_->LoadSession( | |
| 185 MediaKeys::PERSISTENT_LICENSE_SESSION, | |
| 186 std::string(reinterpret_cast<const char*>(stripped_init_data.data()), | |
| 187 stripped_init_data.size()), | |
| 188 std::move(promise)); | |
| 189 return; | |
| 190 } | |
| 191 | |
| 192 MediaKeys::SessionType session_type = | |
| 193 session_creation_type == PersistentSession | |
| 194 ? MediaKeys::PERSISTENT_LICENSE_SESSION | |
| 195 : MediaKeys::TEMPORARY_SESSION; | |
| 196 | |
| 197 // No permission required when AesDecryptor is used or when the key system is | |
| 198 // external clear key. | |
| 199 DCHECK(!key_system_.empty()); | |
| 200 if (CanUseAesDecryptor(key_system_) || IsExternalClearKey(key_system_)) { | |
| 201 OnPermissionStatus(session_type, init_data_type, stripped_init_data, | |
| 202 std::move(promise), true /* granted */); | |
| 203 return; | |
| 204 } | |
| 205 | |
| 206 #if defined(OS_CHROMEOS) || defined(OS_ANDROID) | |
| 207 media_permission_->RequestPermission( | |
| 208 MediaPermission::PROTECTED_MEDIA_IDENTIFIER, security_origin_, | |
| 209 base::Bind(&ProxyDecryptor::OnPermissionStatus, | |
| 210 weak_ptr_factory_.GetWeakPtr(), session_type, init_data_type, | |
| 211 stripped_init_data, base::Passed(&promise))); | |
| 212 #else | |
| 213 OnPermissionStatus(session_type, init_data_type, stripped_init_data, | |
| 214 std::move(promise), true /* granted */); | |
| 215 #endif | |
| 216 } | |
| 217 | |
| 218 void ProxyDecryptor::OnPermissionStatus( | |
| 219 MediaKeys::SessionType session_type, | |
| 220 EmeInitDataType init_data_type, | |
| 221 const std::vector<uint8_t>& init_data, | |
| 222 scoped_ptr<NewSessionCdmPromise> promise, | |
| 223 bool granted) { | |
| 224 // ProxyDecryptor is only used by Prefixed EME, where RequestPermission() is | |
| 225 // only for triggering the permission UI. Later CheckPermission() will be | |
| 226 // called (e.g. in PlatformVerificationFlow on ChromeOS; in BrowserCdmManager | |
| 227 // on Android) and the permission status will be evaluated then. | |
| 228 DVLOG_IF(1, !granted) << "Permission request rejected."; | |
| 229 | |
| 230 media_keys_->CreateSessionAndGenerateRequest(session_type, init_data_type, | |
| 231 init_data, std::move(promise)); | |
| 232 } | |
| 233 | |
| 234 void ProxyDecryptor::AddKey(const uint8_t* key, | |
| 235 int key_length, | |
| 236 const uint8_t* init_data, | |
| 237 int init_data_length, | |
| 238 const std::string& session_id) { | |
| 239 DVLOG(1) << "AddKey()"; | |
| 240 | |
| 241 if (!media_keys_) { | |
| 242 OnLegacySessionError(std::string(), MediaKeys::INVALID_STATE_ERROR, 0, | |
| 243 "CDM is not available."); | |
| 244 return; | |
| 245 } | |
| 246 | |
| 247 // In the prefixed API, the session parameter provided to addKey() is | |
| 248 // optional, so use the single existing session if it exists. | |
| 249 std::string new_session_id(session_id); | |
| 250 if (new_session_id.empty()) { | |
| 251 if (active_sessions_.size() == 1) { | |
| 252 base::hash_map<std::string, bool>::iterator it = active_sessions_.begin(); | |
| 253 new_session_id = it->first; | |
| 254 } else { | |
| 255 OnLegacySessionError(std::string(), MediaKeys::NOT_SUPPORTED_ERROR, 0, | |
| 256 "SessionId not specified."); | |
| 257 return; | |
| 258 } | |
| 259 } | |
| 260 | |
| 261 scoped_ptr<SimpleCdmPromise> promise(new CdmCallbackPromise<>( | |
| 262 base::Bind(&ProxyDecryptor::GenerateKeyAdded, | |
| 263 weak_ptr_factory_.GetWeakPtr(), session_id), | |
| 264 base::Bind(&ProxyDecryptor::OnLegacySessionError, | |
| 265 weak_ptr_factory_.GetWeakPtr(), session_id))); | |
| 266 | |
| 267 // EME WD spec only supports a single array passed to the CDM. For | |
| 268 // Clear Key using v0.1b, both arrays are used (|init_data| is key_id). | |
| 269 // Since the EME WD spec supports the key as a JSON Web Key, | |
| 270 // convert the 2 arrays to a JWK and pass it as the single array. | |
| 271 if (is_clear_key_) { | |
| 272 // Decryptor doesn't support empty key ID (see http://crbug.com/123265). | |
| 273 // So ensure a non-empty value is passed. | |
| 274 if (!init_data) { | |
| 275 static const uint8_t kDummyInitData[1] = {0}; | |
| 276 init_data = kDummyInitData; | |
| 277 init_data_length = arraysize(kDummyInitData); | |
| 278 } | |
| 279 | |
| 280 std::string jwk = | |
| 281 GenerateJWKSet(key, key_length, init_data, init_data_length); | |
| 282 DCHECK(!jwk.empty()); | |
| 283 media_keys_->UpdateSession(new_session_id, | |
| 284 std::vector<uint8_t>(jwk.begin(), jwk.end()), | |
| 285 std::move(promise)); | |
| 286 return; | |
| 287 } | |
| 288 | |
| 289 media_keys_->UpdateSession(new_session_id, | |
| 290 std::vector<uint8_t>(key, key + key_length), | |
| 291 std::move(promise)); | |
| 292 } | |
| 293 | |
| 294 void ProxyDecryptor::CancelKeyRequest(const std::string& session_id) { | |
| 295 DVLOG(1) << "CancelKeyRequest()"; | |
| 296 | |
| 297 if (!media_keys_) { | |
| 298 OnLegacySessionError(std::string(), MediaKeys::INVALID_STATE_ERROR, 0, | |
| 299 "CDM is not available."); | |
| 300 return; | |
| 301 } | |
| 302 | |
| 303 scoped_ptr<SimpleCdmPromise> promise(new CdmCallbackPromise<>( | |
| 304 base::Bind(&ProxyDecryptor::OnSessionClosed, | |
| 305 weak_ptr_factory_.GetWeakPtr(), session_id), | |
| 306 base::Bind(&ProxyDecryptor::OnLegacySessionError, | |
| 307 weak_ptr_factory_.GetWeakPtr(), session_id))); | |
| 308 media_keys_->RemoveSession(session_id, std::move(promise)); | |
| 309 } | |
| 310 | |
| 311 void ProxyDecryptor::OnSessionMessage(const std::string& session_id, | |
| 312 MediaKeys::MessageType message_type, | |
| 313 const std::vector<uint8_t>& message, | |
| 314 const GURL& legacy_destination_url) { | |
| 315 // Assumes that OnSessionCreated() has been called before this. | |
| 316 | |
| 317 // For ClearKey, convert the message from JSON into just passing the key | |
| 318 // as the message. If unable to extract the key, return the message unchanged. | |
| 319 if (is_clear_key_) { | |
| 320 std::vector<uint8_t> key; | |
| 321 if (ExtractFirstKeyIdFromLicenseRequest(message, &key)) { | |
| 322 key_message_cb_.Run(session_id, key, legacy_destination_url); | |
| 323 return; | |
| 324 } | |
| 325 } | |
| 326 | |
| 327 key_message_cb_.Run(session_id, message, legacy_destination_url); | |
| 328 } | |
| 329 | |
| 330 void ProxyDecryptor::OnSessionKeysChange(const std::string& session_id, | |
| 331 bool has_additional_usable_key, | |
| 332 CdmKeysInfo keys_info) { | |
| 333 // EME v0.1b doesn't support this event. | |
| 334 } | |
| 335 | |
| 336 void ProxyDecryptor::OnSessionExpirationUpdate( | |
| 337 const std::string& session_id, | |
| 338 const base::Time& new_expiry_time) { | |
| 339 // EME v0.1b doesn't support this event. | |
| 340 } | |
| 341 | |
| 342 void ProxyDecryptor::GenerateKeyAdded(const std::string& session_id) { | |
| 343 // EME WD doesn't support this event, but it is needed for EME v0.1b. | |
| 344 key_added_cb_.Run(session_id); | |
| 345 } | |
| 346 | |
| 347 void ProxyDecryptor::OnSessionClosed(const std::string& session_id) { | |
| 348 base::hash_map<std::string, bool>::iterator it = | |
| 349 active_sessions_.find(session_id); | |
| 350 | |
| 351 // Latest EME spec separates closing a session ("allows an application to | |
| 352 // indicate that it no longer needs the session") and actually closing the | |
| 353 // session (done by the CDM at any point "such as in response to a close() | |
| 354 // call, when the session is no longer needed, or when system resources are | |
| 355 // lost.") Thus the CDM may cause 2 close() events -- one to resolve the | |
| 356 // close() promise, and a second to actually close the session. Prefixed EME | |
| 357 // only expects 1 close event, so drop the second (and subsequent) events. | |
| 358 // However, this means we can't tell if the CDM is generating spurious close() | |
| 359 // events. | |
| 360 if (it == active_sessions_.end()) | |
| 361 return; | |
| 362 | |
| 363 if (it->second) { | |
| 364 OnLegacySessionError(session_id, MediaKeys::NOT_SUPPORTED_ERROR, | |
| 365 kSessionClosedSystemCode, | |
| 366 "Do not close persistent sessions."); | |
| 367 } | |
| 368 active_sessions_.erase(it); | |
| 369 } | |
| 370 | |
| 371 void ProxyDecryptor::OnLegacySessionError(const std::string& session_id, | |
| 372 MediaKeys::Exception exception_code, | |
| 373 uint32_t system_code, | |
| 374 const std::string& error_message) { | |
| 375 // Convert |error_name| back to MediaKeys::KeyError if possible. Prefixed | |
| 376 // EME has different error message, so all the specific error events will | |
| 377 // get lost. | |
| 378 MediaKeys::KeyError error_code; | |
| 379 switch (exception_code) { | |
| 380 case MediaKeys::CLIENT_ERROR: | |
| 381 error_code = MediaKeys::kClientError; | |
| 382 break; | |
| 383 case MediaKeys::OUTPUT_ERROR: | |
| 384 error_code = MediaKeys::kOutputError; | |
| 385 break; | |
| 386 default: | |
| 387 // This will include all other CDM4 errors and any error generated | |
| 388 // by CDM5 or later. | |
| 389 error_code = MediaKeys::kUnknownError; | |
| 390 break; | |
| 391 } | |
| 392 key_error_cb_.Run(session_id, error_code, system_code); | |
| 393 } | |
| 394 | |
| 395 void ProxyDecryptor::SetSessionId(SessionCreationType session_type, | |
| 396 const std::string& session_id) { | |
| 397 // LoadSession() returns empty |session_id| if the session is not found, so | |
| 398 // convert this into an error. | |
| 399 if (session_type == LoadSession && session_id.empty()) { | |
| 400 OnLegacySessionError(session_id, MediaKeys::INVALID_ACCESS_ERROR, 0, | |
| 401 "Incorrect session id specified for LoadSession()."); | |
| 402 return; | |
| 403 } | |
| 404 | |
| 405 // Loaded sessions are considered persistent. | |
| 406 bool is_persistent = | |
| 407 session_type == PersistentSession || session_type == LoadSession; | |
| 408 active_sessions_.insert(std::make_pair(session_id, is_persistent)); | |
| 409 | |
| 410 // For LoadSession(), generate the KeyAdded event. | |
| 411 if (session_type == LoadSession) | |
| 412 GenerateKeyAdded(session_id); | |
| 413 } | |
| 414 | |
| 415 } // namespace media | |
| OLD | NEW |