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   SessionCreationType session_creation_type = TemporarySession;
    106   if (HasHeader(init_data, init_data_length, kPrefixedApiLoadSessionHeader)) {
    107     session_creation_type = LoadSession;
    108   } else if (HasHeader(init_data,
    109                        init_data_length,
    110                        kPrefixedApiPersistentSessionHeader)) {
    111     session_creation_type = PersistentSession;
    112   }
    113 
    114   scoped_ptr<media::NewSessionCdmPromise> promise(
    115       new media::NewSessionCdmPromise(
    116           base::Bind(&ProxyDecryptor::SetSessionId,
    117                      weak_ptr_factory_.GetWeakPtr(),
    118                      session_creation_type),
    119           base::Bind(&ProxyDecryptor::OnSessionError,
    120                      weak_ptr_factory_.GetWeakPtr(),
    121                      std::string())));  // No session id until created.
    122 
    123   if (session_creation_type == LoadSession) {
    124     media_keys_->LoadSession(
    125         std::string(reinterpret_cast<const char*>(
    126                         init_data + strlen(kPrefixedApiLoadSessionHeader)),
    127                     init_data_length - strlen(kPrefixedApiLoadSessionHeader)),
    128         promise.Pass());
    129     return true;
    130   }
    131 
    132   media::MediaKeys::SessionType session_type =
    133       session_creation_type == PersistentSession
    134           ? media::MediaKeys::PERSISTENT_SESSION
    135           : media::MediaKeys::TEMPORARY_SESSION;
    136 
    137   // Convert MIME types used in the prefixed implementation.
    138   std::string init_data_type;
    139   if (content_type == "audio/mp4" || content_type == "video/mp4") {
    140     init_data_type = "cenc";
    141   } else if (content_type == "audio/webm" || content_type == "video/webm") {
    142     init_data_type = "webm";
    143   } else {
    144     NOTREACHED();
    145     init_data_type = content_type;
    146   }
    147 
    148   media_keys_->CreateSession(init_data_type, init_data, init_data_length,
    149                              session_type, promise.Pass());
    150   return true;
    151 }
    152 
    153 void ProxyDecryptor::AddKey(const uint8* key,
    154                             int key_length,
    155                             const uint8* init_data,
    156                             int init_data_length,
    157                             const std::string& web_session_id) {
    158   DVLOG(1) << "AddKey()";
    159 
    160   // In the prefixed API, the session parameter provided to addKey() is
    161   // optional, so use the single existing session if it exists.
    162   // TODO(jrummell): remove when the prefixed API is removed.
    163   std::string session_id(web_session_id);
    164   if (session_id.empty()) {
    165     if (active_sessions_.size() == 1) {
    166       base::hash_map<std::string, bool>::iterator it = active_sessions_.begin();
    167       session_id = it->first;
    168     } else {
    169       OnSessionError(std::string(),
    170                      media::MediaKeys::NOT_SUPPORTED_ERROR,
    171                      0,
    172                      "SessionId not specified.");
    173       return;
    174     }
    175   }
    176 
    177   scoped_ptr<media::SimpleCdmPromise> promise(
    178       new media::SimpleCdmPromise(base::Bind(&ProxyDecryptor::OnSessionReady,
    179                                              weak_ptr_factory_.GetWeakPtr(),
    180                                              web_session_id),
    181                                   base::Bind(&ProxyDecryptor::OnSessionError,
    182                                              weak_ptr_factory_.GetWeakPtr(),
    183                                              web_session_id)));
    184 
    185   // EME WD spec only supports a single array passed to the CDM. For
    186   // Clear Key using v0.1b, both arrays are used (|init_data| is key_id).
    187   // Since the EME WD spec supports the key as a JSON Web Key,
    188   // convert the 2 arrays to a JWK and pass it as the single array.
    189   if (is_clear_key_) {
    190     // Decryptor doesn't support empty key ID (see http://crbug.com/123265).
    191     // So ensure a non-empty value is passed.
    192     if (!init_data) {
    193       static const uint8 kDummyInitData[1] = {0};
    194       init_data = kDummyInitData;
    195       init_data_length = arraysize(kDummyInitData);
    196     }
    197 
    198     std::string jwk =
    199         media::GenerateJWKSet(key, key_length, init_data, init_data_length);
    200     DCHECK(!jwk.empty());
    201     media_keys_->UpdateSession(session_id,
    202                                reinterpret_cast<const uint8*>(jwk.data()),
    203                                jwk.size(),
    204                                promise.Pass());
    205     return;
    206   }
    207 
    208   media_keys_->UpdateSession(session_id, key, key_length, promise.Pass());
    209 }
    210 
    211 void ProxyDecryptor::CancelKeyRequest(const std::string& web_session_id) {
    212   DVLOG(1) << "CancelKeyRequest()";
    213 
    214   scoped_ptr<media::SimpleCdmPromise> promise(
    215       new media::SimpleCdmPromise(base::Bind(&ProxyDecryptor::OnSessionClosed,
    216                                              weak_ptr_factory_.GetWeakPtr(),
    217                                              web_session_id),
    218                                   base::Bind(&ProxyDecryptor::OnSessionError,
    219                                              weak_ptr_factory_.GetWeakPtr(),
    220                                              web_session_id)));
    221   media_keys_->RemoveSession(web_session_id, promise.Pass());
    222 }
    223 
    224 scoped_ptr<media::MediaKeys> ProxyDecryptor::CreateMediaKeys(
    225     const std::string& key_system,
    226     const GURL& security_origin) {
    227   return ContentDecryptionModuleFactory::Create(
    228       key_system,
    229       security_origin,
    230 #if defined(ENABLE_PEPPER_CDMS)
    231       create_pepper_cdm_cb_,
    232 #elif defined(ENABLE_BROWSER_CDMS)
    233       manager_,
    234       &cdm_id_,
    235 #endif  // defined(ENABLE_PEPPER_CDMS)
    236       base::Bind(&ProxyDecryptor::OnSessionMessage,
    237                  weak_ptr_factory_.GetWeakPtr()),
    238       base::Bind(&ProxyDecryptor::OnSessionReady,
    239                  weak_ptr_factory_.GetWeakPtr()),
    240       base::Bind(&ProxyDecryptor::OnSessionClosed,
    241                  weak_ptr_factory_.GetWeakPtr()),
    242       base::Bind(&ProxyDecryptor::OnSessionError,
    243                  weak_ptr_factory_.GetWeakPtr()),
    244       base::Bind(&ProxyDecryptor::OnSessionKeysChange,
    245                  weak_ptr_factory_.GetWeakPtr()),
    246       base::Bind(&ProxyDecryptor::OnSessionExpirationUpdate,
    247                  weak_ptr_factory_.GetWeakPtr()));
    248 }
    249 
    250 void ProxyDecryptor::OnSessionMessage(const std::string& web_session_id,
    251                                       const std::vector<uint8>& message,
    252                                       const GURL& destination_url) {
    253   // Assumes that OnSessionCreated() has been called before this.
    254 
    255   // For ClearKey, convert the message from JSON into just passing the key
    256   // as the message. If unable to extract the key, return the message unchanged.
    257   if (is_clear_key_) {
    258     std::vector<uint8> key;
    259     if (media::ExtractFirstKeyIdFromLicenseRequest(message, &key)) {
    260       key_message_cb_.Run(web_session_id, key, destination_url);
    261       return;
    262     }
    263   }
    264 
    265   key_message_cb_.Run(web_session_id, message, destination_url);
    266 }
    267 
    268 void ProxyDecryptor::OnSessionKeysChange(const std::string& web_session_id,
    269                                          bool has_additional_usable_key) {
    270   // EME v0.1b doesn't support this event.
    271 }
    272 
    273 void ProxyDecryptor::OnSessionExpirationUpdate(
    274     const std::string& web_session_id,
    275     const base::Time& new_expiry_time) {
    276   // EME v0.1b doesn't support this event.
    277 }
    278 
    279 void ProxyDecryptor::OnSessionReady(const std::string& web_session_id) {
    280   key_added_cb_.Run(web_session_id);
    281 }
    282 
    283 void ProxyDecryptor::OnSessionClosed(const std::string& web_session_id) {
    284   base::hash_map<std::string, bool>::iterator it =
    285       active_sessions_.find(web_session_id);
    286 
    287   // Latest EME spec separates closing a session ("allows an application to
    288   // indicate that it no longer needs the session") and actually closing the
    289   // session (done by the CDM at any point "such as in response to a close()
    290   // call, when the session is no longer needed, or when system resources are
    291   // lost.") Thus the CDM may cause 2 close() events -- one to resolve the
    292   // close() promise, and a second to actually close the session. Prefixed EME
    293   // only expects 1 close event, so drop the second (and subsequent) events.
    294   // However, this means we can't tell if the CDM is generating spurious close()
    295   // events.
    296   if (it == active_sessions_.end())
    297     return;
    298 
    299   if (it->second) {
    300     OnSessionError(web_session_id,
    301                    media::MediaKeys::NOT_SUPPORTED_ERROR,
    302                    kSessionClosedSystemCode,
    303                    "Do not close persistent sessions.");
    304   }
    305   active_sessions_.erase(it);
    306 }
    307 
    308 void ProxyDecryptor::OnSessionError(const std::string& web_session_id,
    309                                     media::MediaKeys::Exception exception_code,
    310                                     uint32 system_code,
    311                                     const std::string& error_message) {
    312   // Convert |error_name| back to MediaKeys::KeyError if possible. Prefixed
    313   // EME has different error message, so all the specific error events will
    314   // get lost.
    315   media::MediaKeys::KeyError error_code;
    316   switch (exception_code) {
    317     case media::MediaKeys::CLIENT_ERROR:
    318       error_code = media::MediaKeys::kClientError;
    319       break;
    320     case media::MediaKeys::OUTPUT_ERROR:
    321       error_code = media::MediaKeys::kOutputError;
    322       break;
    323     default:
    324       // This will include all other CDM4 errors and any error generated
    325       // by CDM5 or later.
    326       error_code = media::MediaKeys::kUnknownError;
    327       break;
    328   }
    329   key_error_cb_.Run(web_session_id, error_code, system_code);
    330 }
    331 
    332 void ProxyDecryptor::SetSessionId(SessionCreationType session_type,
    333                                   const std::string& web_session_id) {
    334   // Loaded sessions are considered persistent.
    335   bool is_persistent =
    336       session_type == PersistentSession || session_type == LoadSession;
    337   active_sessions_.insert(std::make_pair(web_session_id, is_persistent));
    338 
    339   // For LoadSession(), generate the SessionReady event.
    340   if (session_type == LoadSession)
    341     OnSessionReady(web_session_id);
    342 }
    343 
    344 }  // namespace content
    345