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