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