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.
5 #include "content/renderer/media/crypto/proxy_decryptor.h"
10 #include "base/callback_helpers.h"
11 #include "base/logging.h"
12 #include "base/strings/string_util.h"
13 #include "media/base/cdm_callback_promise.h"
14 #include "media/base/cdm_factory.h"
15 #include "media/cdm/json_web_key.h"
16 #include "media/cdm/key_system_names.h"
20 // Special system code to signal a closed persistent session in a SessionError()
21 // call. This is needed because there is no SessionClosed() call in the prefixed
23 const int kSessionClosedSystemCode
= 29127;
25 ProxyDecryptor::ProxyDecryptor(const KeyAddedCB
& key_added_cb
,
26 const KeyErrorCB
& key_error_cb
,
27 const KeyMessageCB
& key_message_cb
)
28 : key_added_cb_(key_added_cb
),
29 key_error_cb_(key_error_cb
),
30 key_message_cb_(key_message_cb
),
32 weak_ptr_factory_(this) {
33 DCHECK(!key_added_cb_
.is_null());
34 DCHECK(!key_error_cb_
.is_null());
35 DCHECK(!key_message_cb_
.is_null());
38 ProxyDecryptor::~ProxyDecryptor() {
39 // Destroy the decryptor explicitly before destroying the plugin.
43 media::Decryptor
* ProxyDecryptor::GetDecryptor() {
44 return media_keys_
? media_keys_
->GetDecryptor() : NULL
;
47 #if defined(ENABLE_BROWSER_CDMS)
48 int ProxyDecryptor::GetCdmId() {
49 return media_keys_
->GetCdmId();
53 bool ProxyDecryptor::InitializeCDM(media::CdmFactory
* cdm_factory
,
54 const std::string
& key_system
,
55 const GURL
& security_origin
) {
56 DVLOG(1) << "InitializeCDM: key_system = " << key_system
;
59 media_keys_
= CreateMediaKeys(cdm_factory
, key_system
, security_origin
);
64 media::IsClearKey(key_system
) || media::IsExternalClearKey(key_system
);
68 // Returns true if |data| is prefixed with |header| and has data after the
70 bool HasHeader(const uint8
* data
, int data_length
, const std::string
& header
) {
71 return static_cast<size_t>(data_length
) > header
.size() &&
72 std::equal(data
, data
+ header
.size(), header
.begin());
75 // Removes the first |length| items from |data|.
76 void StripHeader(std::vector
<uint8
>& data
, size_t length
) {
77 data
.erase(data
.begin(), data
.begin() + length
);
80 bool ProxyDecryptor::GenerateKeyRequest(const std::string
& init_data_type
,
81 const uint8
* init_data
,
82 int init_data_length
) {
83 DVLOG(1) << "GenerateKeyRequest()";
84 const char kPrefixedApiPersistentSessionHeader
[] = "PERSISTENT|";
85 const char kPrefixedApiLoadSessionHeader
[] = "LOAD_SESSION|";
87 SessionCreationType session_creation_type
= TemporarySession
;
88 std::vector
<uint8
> init_data_vector(init_data
, init_data
+ init_data_length
);
89 if (HasHeader(init_data
, init_data_length
, kPrefixedApiLoadSessionHeader
)) {
90 session_creation_type
= LoadSession
;
91 StripHeader(init_data_vector
, strlen(kPrefixedApiLoadSessionHeader
));
92 } else if (HasHeader(init_data
,
94 kPrefixedApiPersistentSessionHeader
)) {
95 session_creation_type
= PersistentSession
;
96 StripHeader(init_data_vector
, strlen(kPrefixedApiPersistentSessionHeader
));
99 scoped_ptr
<media::NewSessionCdmPromise
> promise(
100 new media::CdmCallbackPromise
<std::string
>(
101 base::Bind(&ProxyDecryptor::SetSessionId
,
102 weak_ptr_factory_
.GetWeakPtr(),
103 session_creation_type
),
104 base::Bind(&ProxyDecryptor::OnSessionError
,
105 weak_ptr_factory_
.GetWeakPtr(),
106 std::string()))); // No session id until created.
107 uint8
* init_data_vector_data
=
108 (init_data_vector
.size() > 0) ? &init_data_vector
[0] : nullptr;
110 if (session_creation_type
== LoadSession
) {
111 media_keys_
->LoadSession(
112 std::string(reinterpret_cast<const char*>(init_data_vector_data
),
113 init_data_vector
.size()),
118 media::MediaKeys::SessionType session_type
=
119 session_creation_type
== PersistentSession
120 ? media::MediaKeys::PERSISTENT_SESSION
121 : media::MediaKeys::TEMPORARY_SESSION
;
123 media_keys_
->CreateSession(init_data_type
,
124 init_data_vector_data
,
125 init_data_vector
.size(),
131 void ProxyDecryptor::AddKey(const uint8
* key
,
133 const uint8
* init_data
,
134 int init_data_length
,
135 const std::string
& web_session_id
) {
136 DVLOG(1) << "AddKey()";
138 // In the prefixed API, the session parameter provided to addKey() is
139 // optional, so use the single existing session if it exists.
140 // TODO(jrummell): remove when the prefixed API is removed.
141 std::string
session_id(web_session_id
);
142 if (session_id
.empty()) {
143 if (active_sessions_
.size() == 1) {
144 base::hash_map
<std::string
, bool>::iterator it
= active_sessions_
.begin();
145 session_id
= it
->first
;
147 OnSessionError(std::string(),
148 media::MediaKeys::NOT_SUPPORTED_ERROR
,
150 "SessionId not specified.");
155 scoped_ptr
<media::SimpleCdmPromise
> promise(new media::CdmCallbackPromise
<>(
156 base::Bind(&ProxyDecryptor::OnSessionReady
,
157 weak_ptr_factory_
.GetWeakPtr(),
159 base::Bind(&ProxyDecryptor::OnSessionError
,
160 weak_ptr_factory_
.GetWeakPtr(),
163 // EME WD spec only supports a single array passed to the CDM. For
164 // Clear Key using v0.1b, both arrays are used (|init_data| is key_id).
165 // Since the EME WD spec supports the key as a JSON Web Key,
166 // convert the 2 arrays to a JWK and pass it as the single array.
168 // Decryptor doesn't support empty key ID (see http://crbug.com/123265).
169 // So ensure a non-empty value is passed.
171 static const uint8 kDummyInitData
[1] = {0};
172 init_data
= kDummyInitData
;
173 init_data_length
= arraysize(kDummyInitData
);
177 media::GenerateJWKSet(key
, key_length
, init_data
, init_data_length
);
178 DCHECK(!jwk
.empty());
179 media_keys_
->UpdateSession(session_id
,
180 reinterpret_cast<const uint8
*>(jwk
.data()),
186 media_keys_
->UpdateSession(session_id
, key
, key_length
, promise
.Pass());
189 void ProxyDecryptor::CancelKeyRequest(const std::string
& web_session_id
) {
190 DVLOG(1) << "CancelKeyRequest()";
192 scoped_ptr
<media::SimpleCdmPromise
> promise(new media::CdmCallbackPromise
<>(
193 base::Bind(&ProxyDecryptor::OnSessionClosed
,
194 weak_ptr_factory_
.GetWeakPtr(),
196 base::Bind(&ProxyDecryptor::OnSessionError
,
197 weak_ptr_factory_
.GetWeakPtr(),
199 media_keys_
->RemoveSession(web_session_id
, promise
.Pass());
202 scoped_ptr
<media::MediaKeys
> ProxyDecryptor::CreateMediaKeys(
203 media::CdmFactory
* cdm_factory
,
204 const std::string
& key_system
,
205 const GURL
& security_origin
) {
206 base::WeakPtr
<ProxyDecryptor
> weak_this
= weak_ptr_factory_
.GetWeakPtr();
207 return cdm_factory
->Create(
210 base::Bind(&ProxyDecryptor::OnSessionMessage
, weak_this
),
211 base::Bind(&ProxyDecryptor::OnSessionReady
, weak_this
),
212 base::Bind(&ProxyDecryptor::OnSessionClosed
, weak_this
),
213 base::Bind(&ProxyDecryptor::OnSessionError
, weak_this
),
214 base::Bind(&ProxyDecryptor::OnSessionKeysChange
, weak_this
),
215 base::Bind(&ProxyDecryptor::OnSessionExpirationUpdate
, weak_this
));
218 void ProxyDecryptor::OnSessionMessage(const std::string
& web_session_id
,
219 const std::vector
<uint8
>& message
,
220 const GURL
& destination_url
) {
221 // Assumes that OnSessionCreated() has been called before this.
223 // For ClearKey, convert the message from JSON into just passing the key
224 // as the message. If unable to extract the key, return the message unchanged.
226 std::vector
<uint8
> key
;
227 if (media::ExtractFirstKeyIdFromLicenseRequest(message
, &key
)) {
228 key_message_cb_
.Run(web_session_id
, key
, destination_url
);
233 key_message_cb_
.Run(web_session_id
, message
, destination_url
);
236 void ProxyDecryptor::OnSessionKeysChange(const std::string
& web_session_id
,
237 bool has_additional_usable_key
) {
238 // EME v0.1b doesn't support this event.
241 void ProxyDecryptor::OnSessionExpirationUpdate(
242 const std::string
& web_session_id
,
243 const base::Time
& new_expiry_time
) {
244 // EME v0.1b doesn't support this event.
247 void ProxyDecryptor::OnSessionReady(const std::string
& web_session_id
) {
248 key_added_cb_
.Run(web_session_id
);
251 void ProxyDecryptor::OnSessionClosed(const std::string
& web_session_id
) {
252 base::hash_map
<std::string
, bool>::iterator it
=
253 active_sessions_
.find(web_session_id
);
255 // Latest EME spec separates closing a session ("allows an application to
256 // indicate that it no longer needs the session") and actually closing the
257 // session (done by the CDM at any point "such as in response to a close()
258 // call, when the session is no longer needed, or when system resources are
259 // lost.") Thus the CDM may cause 2 close() events -- one to resolve the
260 // close() promise, and a second to actually close the session. Prefixed EME
261 // only expects 1 close event, so drop the second (and subsequent) events.
262 // However, this means we can't tell if the CDM is generating spurious close()
264 if (it
== active_sessions_
.end())
268 OnSessionError(web_session_id
,
269 media::MediaKeys::NOT_SUPPORTED_ERROR
,
270 kSessionClosedSystemCode
,
271 "Do not close persistent sessions.");
273 active_sessions_
.erase(it
);
276 void ProxyDecryptor::OnSessionError(const std::string
& web_session_id
,
277 media::MediaKeys::Exception exception_code
,
279 const std::string
& error_message
) {
280 // Convert |error_name| back to MediaKeys::KeyError if possible. Prefixed
281 // EME has different error message, so all the specific error events will
283 media::MediaKeys::KeyError error_code
;
284 switch (exception_code
) {
285 case media::MediaKeys::CLIENT_ERROR
:
286 error_code
= media::MediaKeys::kClientError
;
288 case media::MediaKeys::OUTPUT_ERROR
:
289 error_code
= media::MediaKeys::kOutputError
;
292 // This will include all other CDM4 errors and any error generated
294 error_code
= media::MediaKeys::kUnknownError
;
297 key_error_cb_
.Run(web_session_id
, error_code
, system_code
);
300 void ProxyDecryptor::SetSessionId(SessionCreationType session_type
,
301 const std::string
& web_session_id
) {
302 // Loaded sessions are considered persistent.
304 session_type
== PersistentSession
|| session_type
== LoadSession
;
305 active_sessions_
.insert(std::make_pair(web_session_id
, is_persistent
));
307 // For LoadSession(), generate the SessionReady event.
308 if (session_type
== LoadSession
)
309 OnSessionReady(web_session_id
);
312 } // namespace content