Home | History | Annotate | Download | only in crypto
      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 "content/renderer/media/crypto/proxy_decryptor.h"
      6 
      7 #include <cstring>
      8 
      9 #include "base/bind.h"
     10 #include "base/callback_helpers.h"
     11 #include "base/logging.h"
     12 #include "base/strings/string_util.h"
     13 #include "content/renderer/media/crypto/content_decryption_module_factory.h"
     14 #include "media/base/cdm_promise.h"
     15 #include "media/cdm/json_web_key.h"
     16 #include "media/cdm/key_system_names.h"
     17 
     18 #if defined(ENABLE_PEPPER_CDMS)
     19 #include "content/renderer/media/crypto/pepper_cdm_wrapper.h"
     20 #endif  // defined(ENABLE_PEPPER_CDMS)
     21 
     22 #if defined(ENABLE_BROWSER_CDMS)
     23 #include "content/renderer/media/crypto/renderer_cdm_manager.h"
     24 #endif  // defined(ENABLE_BROWSER_CDMS)
     25 
     26 namespace content {
     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::ProxyDecryptor(
     34 #if defined(ENABLE_PEPPER_CDMS)
     35     const CreatePepperCdmCB& create_pepper_cdm_cb,
     36 #elif defined(ENABLE_BROWSER_CDMS)
     37     RendererCdmManager* manager,
     38 #endif  // defined(ENABLE_PEPPER_CDMS)
     39     const KeyAddedCB& key_added_cb,
     40     const KeyErrorCB& key_error_cb,
     41     const KeyMessageCB& key_message_cb)
     42     :
     43 #if defined(ENABLE_PEPPER_CDMS)
     44       create_pepper_cdm_cb_(create_pepper_cdm_cb),
     45 #elif defined(ENABLE_BROWSER_CDMS)
     46       manager_(manager),
     47       cdm_id_(RendererCdmManager::kInvalidCdmId),
     48 #endif  // defined(ENABLE_PEPPER_CDMS)
     49       key_added_cb_(key_added_cb),
     50       key_error_cb_(key_error_cb),
     51       key_message_cb_(key_message_cb),
     52       is_clear_key_(false),
     53       weak_ptr_factory_(this) {
     54 #if defined(ENABLE_PEPPER_CDMS)
     55   DCHECK(!create_pepper_cdm_cb_.is_null());
     56 #endif  // defined(ENABLE_PEPPER_CDMS)
     57   DCHECK(!key_added_cb_.is_null());
     58   DCHECK(!key_error_cb_.is_null());
     59   DCHECK(!key_message_cb_.is_null());
     60 }
     61 
     62 ProxyDecryptor::~ProxyDecryptor() {
     63   // Destroy the decryptor explicitly before destroying the plugin.
     64   media_keys_.reset();
     65 }
     66 
     67 media::Decryptor* ProxyDecryptor::GetDecryptor() {
     68   return media_keys_ ? media_keys_->GetDecryptor() : NULL;
     69 }
     70 
     71 #if defined(ENABLE_BROWSER_CDMS)
     72 int ProxyDecryptor::GetCdmId() {
     73   return cdm_id_;
     74 }
     75 #endif
     76 
     77 bool ProxyDecryptor::InitializeCDM(const std::string& key_system,
     78                                    const GURL& security_origin) {
     79   DVLOG(1) << "InitializeCDM: key_system = " << key_system;
     80 
     81   DCHECK(!media_keys_);
     82   media_keys_ = CreateMediaKeys(key_system, security_origin);
     83   if (!media_keys_)
     84     return false;
     85 
     86   is_clear_key_ =
     87       media::IsClearKey(key_system) || media::IsExternalClearKey(key_system);
     88   return true;
     89 }
     90 
     91 // Returns true if |data| is prefixed with |header| and has data after the
     92 // |header|.
     93 bool HasHeader(const uint8* data, int data_length, const std::string& header) {
     94   return static_cast<size_t>(data_length) > header.size() &&
     95          std::equal(data, data + header.size(), header.begin());
     96 }
     97 
     98 bool ProxyDecryptor::GenerateKeyRequest(const std::string& content_type,
     99                                         const uint8* init_data,
    100                                         int init_data_length) {
    101   DVLOG(1) << "GenerateKeyRequest()";
    102   const char kPrefixedApiPersistentSessionHeader[] = "PERSISTENT|";
    103   const char kPrefixedApiLoadSessionHeader[] = "LOAD_SESSION|";
    104 
    105   bool loadSession =
    106       HasHeader(init_data, init_data_length, kPrefixedApiLoadSessionHeader);
    107   bool persistent = HasHeader(
    108       init_data, init_data_length, kPrefixedApiPersistentSessionHeader);
    109 
    110   scoped_ptr<media::NewSessionCdmPromise> promise(
    111       new media::NewSessionCdmPromise(
    112           base::Bind(&ProxyDecryptor::SetSessionId,
    113                      weak_ptr_factory_.GetWeakPtr(),
    114                      persistent || loadSession),
    115           base::Bind(&ProxyDecryptor::OnSessionError,
    116                      weak_ptr_factory_.GetWeakPtr(),
    117                      std::string())));  // No session id until created.
    118 
    119   if (loadSession) {
    120     media_keys_->LoadSession(
    121         std::string(reinterpret_cast<const char*>(
    122                         init_data + strlen(kPrefixedApiLoadSessionHeader)),
    123                     init_data_length - strlen(kPrefixedApiLoadSessionHeader)),
    124         promise.Pass());
    125     return true;
    126   }
    127 
    128   media::MediaKeys::SessionType session_type =
    129       persistent ? media::MediaKeys::PERSISTENT_SESSION
    130                  : media::MediaKeys::TEMPORARY_SESSION;
    131   media_keys_->CreateSession(
    132       content_type, init_data, init_data_length, session_type, promise.Pass());
    133   return true;
    134 }
    135 
    136 void ProxyDecryptor::AddKey(const uint8* key,
    137                             int key_length,
    138                             const uint8* init_data,
    139                             int init_data_length,
    140                             const std::string& web_session_id) {
    141   DVLOG(1) << "AddKey()";
    142 
    143   // In the prefixed API, the session parameter provided to addKey() is
    144   // optional, so use the single existing session if it exists.
    145   // TODO(jrummell): remove when the prefixed API is removed.
    146   std::string session_id(web_session_id);
    147   if (session_id.empty()) {
    148     if (active_sessions_.size() == 1) {
    149       base::hash_map<std::string, bool>::iterator it = active_sessions_.begin();
    150       session_id = it->first;
    151     } else {
    152       OnSessionError(std::string(),
    153                      media::MediaKeys::NOT_SUPPORTED_ERROR,
    154                      0,
    155                      "SessionId not specified.");
    156       return;
    157     }
    158   }
    159 
    160   scoped_ptr<media::SimpleCdmPromise> promise(
    161       new media::SimpleCdmPromise(base::Bind(&ProxyDecryptor::OnSessionReady,
    162                                              weak_ptr_factory_.GetWeakPtr(),
    163                                              web_session_id),
    164                                   base::Bind(&ProxyDecryptor::OnSessionError,
    165                                              weak_ptr_factory_.GetWeakPtr(),
    166                                              web_session_id)));
    167 
    168   // EME WD spec only supports a single array passed to the CDM. For
    169   // Clear Key using v0.1b, both arrays are used (|init_data| is key_id).
    170   // Since the EME WD spec supports the key as a JSON Web Key,
    171   // convert the 2 arrays to a JWK and pass it as the single array.
    172   if (is_clear_key_) {
    173     // Decryptor doesn't support empty key ID (see http://crbug.com/123265).
    174     // So ensure a non-empty value is passed.
    175     if (!init_data) {
    176       static const uint8 kDummyInitData[1] = {0};
    177       init_data = kDummyInitData;
    178       init_data_length = arraysize(kDummyInitData);
    179     }
    180 
    181     std::string jwk =
    182         media::GenerateJWKSet(key, key_length, init_data, init_data_length);
    183     DCHECK(!jwk.empty());
    184     media_keys_->UpdateSession(session_id,
    185                                reinterpret_cast<const uint8*>(jwk.data()),
    186                                jwk.size(),
    187                                promise.Pass());
    188     return;
    189   }
    190 
    191   media_keys_->UpdateSession(session_id, key, key_length, promise.Pass());
    192 }
    193 
    194 void ProxyDecryptor::CancelKeyRequest(const std::string& web_session_id) {
    195   DVLOG(1) << "CancelKeyRequest()";
    196 
    197   scoped_ptr<media::SimpleCdmPromise> promise(
    198       new media::SimpleCdmPromise(base::Bind(&ProxyDecryptor::OnSessionClosed,
    199                                              weak_ptr_factory_.GetWeakPtr(),
    200                                              web_session_id),
    201                                   base::Bind(&ProxyDecryptor::OnSessionError,
    202                                              weak_ptr_factory_.GetWeakPtr(),
    203                                              web_session_id)));
    204   media_keys_->ReleaseSession(web_session_id, promise.Pass());
    205 }
    206 
    207 scoped_ptr<media::MediaKeys> ProxyDecryptor::CreateMediaKeys(
    208     const std::string& key_system,
    209     const GURL& security_origin) {
    210   return ContentDecryptionModuleFactory::Create(
    211       key_system,
    212       security_origin,
    213 #if defined(ENABLE_PEPPER_CDMS)
    214       create_pepper_cdm_cb_,
    215 #elif defined(ENABLE_BROWSER_CDMS)
    216       manager_,
    217       &cdm_id_,
    218 #endif  // defined(ENABLE_PEPPER_CDMS)
    219       base::Bind(&ProxyDecryptor::OnSessionMessage,
    220                  weak_ptr_factory_.GetWeakPtr()),
    221       base::Bind(&ProxyDecryptor::OnSessionReady,
    222                  weak_ptr_factory_.GetWeakPtr()),
    223       base::Bind(&ProxyDecryptor::OnSessionClosed,
    224                  weak_ptr_factory_.GetWeakPtr()),
    225       base::Bind(&ProxyDecryptor::OnSessionError,
    226                  weak_ptr_factory_.GetWeakPtr()));
    227 }
    228 
    229 void ProxyDecryptor::OnSessionMessage(const std::string& web_session_id,
    230                                       const std::vector<uint8>& message,
    231                                       const GURL& destination_url) {
    232   // Assumes that OnSessionCreated() has been called before this.
    233   key_message_cb_.Run(web_session_id, message, destination_url);
    234 }
    235 
    236 void ProxyDecryptor::OnSessionReady(const std::string& web_session_id) {
    237   key_added_cb_.Run(web_session_id);
    238 }
    239 
    240 void ProxyDecryptor::OnSessionClosed(const std::string& web_session_id) {
    241   base::hash_map<std::string, bool>::iterator it =
    242       active_sessions_.find(web_session_id);
    243 
    244   // Latest EME spec separates closing a session ("allows an application to
    245   // indicate that it no longer needs the session") and actually closing the
    246   // session (done by the CDM at any point "such as in response to a close()
    247   // call, when the session is no longer needed, or when system resources are
    248   // lost.") Thus the CDM may cause 2 close() events -- one to resolve the
    249   // close() promise, and a second to actually close the session. Prefixed EME
    250   // only expects 1 close event, so drop the second (and subsequent) events.
    251   // However, this means we can't tell if the CDM is generating spurious close()
    252   // events.
    253   if (it == active_sessions_.end())
    254     return;
    255 
    256   if (it->second) {
    257     OnSessionError(web_session_id,
    258                    media::MediaKeys::NOT_SUPPORTED_ERROR,
    259                    kSessionClosedSystemCode,
    260                    "Do not close persistent sessions.");
    261   }
    262   active_sessions_.erase(it);
    263 }
    264 
    265 void ProxyDecryptor::OnSessionError(const std::string& web_session_id,
    266                                     media::MediaKeys::Exception exception_code,
    267                                     uint32 system_code,
    268                                     const std::string& error_message) {
    269   // Convert |error_name| back to MediaKeys::KeyError if possible. Prefixed
    270   // EME has different error message, so all the specific error events will
    271   // get lost.
    272   media::MediaKeys::KeyError error_code;
    273   switch (exception_code) {
    274     case media::MediaKeys::CLIENT_ERROR:
    275       error_code = media::MediaKeys::kClientError;
    276       break;
    277     case media::MediaKeys::OUTPUT_ERROR:
    278       error_code = media::MediaKeys::kOutputError;
    279       break;
    280     default:
    281       // This will include all other CDM4 errors and any error generated
    282       // by CDM5 or later.
    283       error_code = media::MediaKeys::kUnknownError;
    284       break;
    285   }
    286   key_error_cb_.Run(web_session_id, error_code, system_code);
    287 }
    288 
    289 void ProxyDecryptor::SetSessionId(bool persistent,
    290                                   const std::string& web_session_id) {
    291   active_sessions_.insert(std::make_pair(web_session_id, persistent));
    292 }
    293 
    294 }  // namespace content
    295