• 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   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 
AddKey(const uint8 * key,int key_length,const uint8 * init_data,int init_data_length,const std::string & web_session_id)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 
CancelKeyRequest(const std::string & web_session_id)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 
CreateMediaKeys(const std::string & key_system,const GURL & security_origin)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 
OnSessionMessage(const std::string & web_session_id,const std::vector<uint8> & message,const GURL & destination_url)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 
OnSessionKeysChange(const std::string & web_session_id,bool has_additional_usable_key)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 
OnSessionExpirationUpdate(const std::string & web_session_id,const base::Time & new_expiry_time)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 
OnSessionReady(const std::string & web_session_id)279 void ProxyDecryptor::OnSessionReady(const std::string& web_session_id) {
280   key_added_cb_.Run(web_session_id);
281 }
282 
OnSessionClosed(const std::string & web_session_id)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 
OnSessionError(const std::string & web_session_id,media::MediaKeys::Exception exception_code,uint32 system_code,const std::string & error_message)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 
SetSessionId(SessionCreationType session_type,const std::string & web_session_id)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