• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ProxyDecryptor(const CreatePepperCdmCB & create_pepper_cdm_cb,const KeyAddedCB & key_added_cb,const KeyErrorCB & key_error_cb,const KeyMessageCB & key_message_cb)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 
~ProxyDecryptor()62 ProxyDecryptor::~ProxyDecryptor() {
63   // Destroy the decryptor explicitly before destroying the plugin.
64   media_keys_.reset();
65 }
66 
GetDecryptor()67 media::Decryptor* ProxyDecryptor::GetDecryptor() {
68   return media_keys_ ? media_keys_->GetDecryptor() : NULL;
69 }
70 
71 #if defined(ENABLE_BROWSER_CDMS)
GetCdmId()72 int ProxyDecryptor::GetCdmId() {
73   return cdm_id_;
74 }
75 #endif
76 
InitializeCDM(const std::string & key_system,const GURL & security_origin)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|.
HasHeader(const uint8 * data,int data_length,const std::string & 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 
GenerateKeyRequest(const std::string & content_type,const uint8 * init_data,int init_data_length)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 
AddKey(const uint8 * key,int key_length,const uint8 * init_data,int init_data_length,const std::string & web_session_id)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 
CancelKeyRequest(const std::string & web_session_id)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 
CreateMediaKeys(const std::string & key_system,const GURL & security_origin)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 
OnSessionMessage(const std::string & web_session_id,const std::vector<uint8> & message,const GURL & destination_url)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 
OnSessionReady(const std::string & web_session_id)236 void ProxyDecryptor::OnSessionReady(const std::string& web_session_id) {
237   key_added_cb_.Run(web_session_id);
238 }
239 
OnSessionClosed(const std::string & web_session_id)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 
OnSessionError(const std::string & web_session_id,media::MediaKeys::Exception exception_code,uint32 system_code,const std::string & error_message)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 
SetSessionId(bool persistent,const std::string & web_session_id)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