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 "webcontentdecryptionmodulesession_impl.h"
8 #include "base/callback_helpers.h"
9 #include "base/logging.h"
10 #include "base/numerics/safe_conversions.h"
11 #include "base/stl_util.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "media/base/cdm_key_information.h"
15 #include "media/base/cdm_promise.h"
16 #include "media/base/key_systems.h"
17 #include "media/base/limits.h"
18 #include "media/base/media_keys.h"
19 #include "media/blink/cdm_result_promise.h"
20 #include "media/blink/cdm_session_adapter.h"
21 #include "media/blink/new_session_cdm_result_promise.h"
22 #include "media/blink/webmediaplayer_util.h"
23 #include "media/cdm/json_web_key.h"
24 #include "media/cdm/key_system_names.h"
25 #include "third_party/WebKit/public/platform/WebData.h"
26 #include "third_party/WebKit/public/platform/WebEncryptedMediaKeyInformation.h"
27 #include "third_party/WebKit/public/platform/WebString.h"
28 #include "third_party/WebKit/public/platform/WebURL.h"
29 #include "third_party/WebKit/public/platform/WebVector.h"
31 #if defined(USE_PROPRIETARY_CODECS)
32 #include "media/cdm/cenc_utils.h"
37 const char kCloseSessionUMAName
[] = "CloseSession";
38 const char kGenerateRequestUMAName
[] = "GenerateRequest";
39 const char kLoadSessionUMAName
[] = "LoadSession";
40 const char kRemoveSessionUMAName
[] = "RemoveSession";
41 const char kUpdateSessionUMAName
[] = "UpdateSession";
43 static blink::WebContentDecryptionModuleSession::Client::MessageType
44 convertMessageType(MediaKeys::MessageType message_type
) {
45 switch (message_type
) {
46 case media::MediaKeys::LICENSE_REQUEST
:
47 return blink::WebContentDecryptionModuleSession::Client::MessageType::
49 case media::MediaKeys::LICENSE_RENEWAL
:
50 return blink::WebContentDecryptionModuleSession::Client::MessageType::
52 case media::MediaKeys::LICENSE_RELEASE
:
53 return blink::WebContentDecryptionModuleSession::Client::MessageType::
58 return blink::WebContentDecryptionModuleSession::Client::MessageType::
62 static blink::WebEncryptedMediaKeyInformation::KeyStatus
convertStatus(
63 media::CdmKeyInformation::KeyStatus status
) {
65 case media::CdmKeyInformation::USABLE
:
66 return blink::WebEncryptedMediaKeyInformation::KeyStatus::Usable
;
67 case media::CdmKeyInformation::INTERNAL_ERROR
:
68 return blink::WebEncryptedMediaKeyInformation::KeyStatus::InternalError
;
69 case media::CdmKeyInformation::EXPIRED
:
70 return blink::WebEncryptedMediaKeyInformation::KeyStatus::Expired
;
71 case media::CdmKeyInformation::OUTPUT_RESTRICTED
:
72 return blink::WebEncryptedMediaKeyInformation::KeyStatus::
74 case media::CdmKeyInformation::OUTPUT_DOWNSCALED
:
75 return blink::WebEncryptedMediaKeyInformation::KeyStatus::
77 case media::CdmKeyInformation::KEY_STATUS_PENDING
:
78 return blink::WebEncryptedMediaKeyInformation::KeyStatus::StatusPending
;
82 return blink::WebEncryptedMediaKeyInformation::KeyStatus::InternalError
;
85 static MediaKeys::SessionType
convertSessionType(
86 blink::WebEncryptedMediaSessionType session_type
) {
87 switch (session_type
) {
88 case blink::WebEncryptedMediaSessionType::Temporary
:
89 return MediaKeys::TEMPORARY_SESSION
;
90 case blink::WebEncryptedMediaSessionType::PersistentLicense
:
91 return MediaKeys::PERSISTENT_LICENSE_SESSION
;
92 case blink::WebEncryptedMediaSessionType::PersistentReleaseMessage
:
93 return MediaKeys::PERSISTENT_RELEASE_MESSAGE_SESSION
;
94 case blink::WebEncryptedMediaSessionType::Unknown
:
99 return MediaKeys::TEMPORARY_SESSION
;
102 static bool SanitizeInitData(EmeInitDataType init_data_type
,
103 const unsigned char* init_data
,
104 size_t init_data_length
,
105 std::vector
<uint8
>* sanitized_init_data
,
106 std::string
* error_message
) {
107 if (init_data_length
> limits::kMaxInitDataLength
) {
108 error_message
->assign("Initialization data too long.");
112 switch (init_data_type
) {
113 case EmeInitDataType::WEBM
:
114 sanitized_init_data
->assign(init_data
, init_data
+ init_data_length
);
117 case EmeInitDataType::CENC
:
118 #if defined(USE_PROPRIETARY_CODECS)
119 sanitized_init_data
->assign(init_data
, init_data
+ init_data_length
);
120 if (!ValidatePsshInput(*sanitized_init_data
)) {
121 error_message
->assign("Initialization data for CENC is incorrect.");
126 error_message
->assign("Initialization data type CENC is not supported.");
130 case EmeInitDataType::KEYIDS
: {
131 // Extract the keys and then rebuild the message. This ensures that any
132 // extra data in the provided JSON is dropped.
133 std::string
init_data_string(init_data
, init_data
+ init_data_length
);
135 if (!ExtractKeyIdsFromKeyIdsInitData(init_data_string
, &key_ids
,
139 for (const auto& key_id
: key_ids
) {
140 if (key_id
.size() < limits::kMinKeyIdLength
||
141 key_id
.size() > limits::kMaxKeyIdLength
) {
142 error_message
->assign("Incorrect key size.");
147 CreateKeyIdsInitData(key_ids
, sanitized_init_data
);
151 case EmeInitDataType::UNKNOWN
:
156 error_message
->assign("Initialization data type is not supported.");
160 static bool SanitizeSessionId(const blink::WebString
& session_id
,
161 std::string
* sanitized_session_id
) {
162 // The user agent should thoroughly validate the sessionId value before
163 // passing it to the CDM. At a minimum, this should include checking that
164 // the length and value (e.g. alphanumeric) are reasonable.
165 if (!base::IsStringASCII(session_id
))
168 sanitized_session_id
->assign(
169 base::UTF16ToASCII(base::StringPiece16(session_id
)));
170 if (sanitized_session_id
->length() > limits::kMaxSessionIdLength
)
173 for (const char c
: *sanitized_session_id
) {
174 if (!base::IsAsciiAlpha(c
) && !base::IsAsciiDigit(c
))
181 static bool SanitizeResponse(const std::string
& key_system
,
182 const uint8
* response
,
183 size_t response_length
,
184 std::vector
<uint8
>* sanitized_response
) {
185 // The user agent should thoroughly validate the response before passing it
186 // to the CDM. This may include verifying values are within reasonable limits,
187 // stripping irrelevant data or fields, pre-parsing it, sanitizing it,
188 // and/or generating a fully sanitized version. The user agent should check
189 // that the length and values of fields are reasonable. Unknown fields should
190 // be rejected or removed.
191 if (response_length
> limits::kMaxSessionResponseLength
)
194 if (IsClearKey(key_system
) || IsExternalClearKey(key_system
)) {
195 std::string
key_string(response
, response
+ response_length
);
196 KeyIdAndKeyPairs keys
;
197 MediaKeys::SessionType session_type
= MediaKeys::TEMPORARY_SESSION
;
198 if (!ExtractKeysFromJWKSet(key_string
, &keys
, &session_type
))
201 // Must contain at least one key.
205 for (const auto key_pair
: keys
) {
206 if (key_pair
.first
.size() < limits::kMinKeyIdLength
||
207 key_pair
.first
.size() > limits::kMaxKeyIdLength
) {
212 std::string sanitized_data
= GenerateJWKSet(keys
, session_type
);
213 sanitized_response
->assign(sanitized_data
.begin(), sanitized_data
.end());
217 // TODO(jrummell): Verify responses for Widevine.
218 sanitized_response
->assign(response
, response
+ response_length
);
222 WebContentDecryptionModuleSessionImpl::WebContentDecryptionModuleSessionImpl(
223 const scoped_refptr
<CdmSessionAdapter
>& adapter
)
224 : adapter_(adapter
), is_closed_(false), weak_ptr_factory_(this) {
227 WebContentDecryptionModuleSessionImpl::
228 ~WebContentDecryptionModuleSessionImpl() {
229 if (!session_id_
.empty())
230 adapter_
->UnregisterSession(session_id_
);
233 void WebContentDecryptionModuleSessionImpl::setClientInterface(Client
* client
) {
237 blink::WebString
WebContentDecryptionModuleSessionImpl::sessionId() const {
238 return blink::WebString::fromUTF8(session_id_
);
241 void WebContentDecryptionModuleSessionImpl::initializeNewSession(
242 blink::WebEncryptedMediaInitDataType init_data_type
,
243 const unsigned char* init_data
,
244 size_t init_data_length
,
245 blink::WebEncryptedMediaSessionType session_type
,
246 blink::WebContentDecryptionModuleResult result
) {
248 DCHECK(session_id_
.empty());
250 // From https://w3c.github.io/encrypted-media/#generateRequest.
251 // 5. If the Key System implementation represented by this object's cdm
252 // implementation value does not support initDataType as an Initialization
253 // Data Type, return a promise rejected with a new DOMException whose name
254 // is NotSupportedError. String comparison is case-sensitive.
255 EmeInitDataType eme_init_data_type
= ConvertToEmeInitDataType(init_data_type
);
256 if (!IsSupportedKeySystemWithInitDataType(adapter_
->GetKeySystem(),
257 eme_init_data_type
)) {
258 std::string message
=
259 "The initialization data type is not supported by the key system.";
260 result
.completeWithError(
261 blink::WebContentDecryptionModuleExceptionNotSupportedError
, 0,
262 blink::WebString::fromUTF8(message
));
266 // 9.1 If the init data is not valid for initDataType, reject promise with a
267 // new DOMException whose name is InvalidAccessError.
268 // 9.2 Let sanitized init data be a validated and sanitized version of init
269 // data. The user agent must thoroughly validate the Initialization Data
270 // before passing it to the CDM. This includes verifying that the length
271 // and values of fields are reasonable, verifying that values are within
272 // reasonable limits, and stripping irrelevant, unsupported, or unknown
273 // data or fields. It is recommended that user agents pre-parse, sanitize,
274 // and/or generate a fully sanitized version of the Initialization Data.
275 // If the Initialization Data format specified by initDataType support
276 // multiple entries, the user agent should remove entries that are not
277 // needed by the CDM.
278 // 9.3 If the previous step failed, reject promise with a new DOMException
279 // whose name is InvalidAccessError.
280 std::vector
<uint8
> sanitized_init_data
;
282 if (!SanitizeInitData(eme_init_data_type
, init_data
, init_data_length
,
283 &sanitized_init_data
, &message
)) {
284 result
.completeWithError(
285 blink::WebContentDecryptionModuleExceptionInvalidAccessError
, 0,
286 blink::WebString::fromUTF8(message
));
290 // 9.4 Let session id be the empty string.
291 // (Done in constructor.)
293 // 9.5 Let message be null.
296 // 9.6 Let cdm be the CDM instance represented by this object's cdm
298 // 9.7 Use the cdm to execute the following steps:
299 adapter_
->InitializeNewSession(
300 eme_init_data_type
, sanitized_init_data
, convertSessionType(session_type
),
301 scoped_ptr
<NewSessionCdmPromise
>(new NewSessionCdmResultPromise(
302 result
, adapter_
->GetKeySystemUMAPrefix() + kGenerateRequestUMAName
,
304 &WebContentDecryptionModuleSessionImpl::OnSessionInitialized
,
305 base::Unretained(this)))));
308 void WebContentDecryptionModuleSessionImpl::load(
309 const blink::WebString
& session_id
,
310 blink::WebContentDecryptionModuleResult result
) {
311 DCHECK(!session_id
.isEmpty());
312 DCHECK(session_id_
.empty());
314 std::string sanitized_session_id
;
315 if (!SanitizeSessionId(session_id
, &sanitized_session_id
)) {
316 result
.completeWithError(
317 blink::WebContentDecryptionModuleExceptionInvalidAccessError
, 0,
318 "Invalid session ID.");
322 // TODO(jrummell): Now that there are 2 types of persistent sessions, the
323 // session type should be passed from blink. Type should also be passed in the
324 // constructor (and removed from initializeNewSession()).
325 adapter_
->LoadSession(
326 MediaKeys::PERSISTENT_LICENSE_SESSION
, sanitized_session_id
,
327 scoped_ptr
<NewSessionCdmPromise
>(new NewSessionCdmResultPromise(
328 result
, adapter_
->GetKeySystemUMAPrefix() + kLoadSessionUMAName
,
330 &WebContentDecryptionModuleSessionImpl::OnSessionInitialized
,
331 base::Unretained(this)))));
334 void WebContentDecryptionModuleSessionImpl::update(
335 const uint8
* response
,
336 size_t response_length
,
337 blink::WebContentDecryptionModuleResult result
) {
339 DCHECK(!session_id_
.empty());
341 std::vector
<uint8
> sanitized_response
;
342 if (!SanitizeResponse(adapter_
->GetKeySystem(), response
, response_length
,
343 &sanitized_response
)) {
344 result
.completeWithError(
345 blink::WebContentDecryptionModuleExceptionInvalidAccessError
, 0,
346 "Invalid response.");
350 adapter_
->UpdateSession(
351 session_id_
, sanitized_response
,
352 scoped_ptr
<SimpleCdmPromise
>(new CdmResultPromise
<>(
353 result
, adapter_
->GetKeySystemUMAPrefix() + kUpdateSessionUMAName
)));
356 void WebContentDecryptionModuleSessionImpl::close(
357 blink::WebContentDecryptionModuleResult result
) {
358 DCHECK(!session_id_
.empty());
359 adapter_
->CloseSession(
361 scoped_ptr
<SimpleCdmPromise
>(new CdmResultPromise
<>(
362 result
, adapter_
->GetKeySystemUMAPrefix() + kCloseSessionUMAName
)));
365 void WebContentDecryptionModuleSessionImpl::remove(
366 blink::WebContentDecryptionModuleResult result
) {
367 DCHECK(!session_id_
.empty());
368 adapter_
->RemoveSession(
370 scoped_ptr
<SimpleCdmPromise
>(new CdmResultPromise
<>(
371 result
, adapter_
->GetKeySystemUMAPrefix() + kRemoveSessionUMAName
)));
374 void WebContentDecryptionModuleSessionImpl::OnSessionMessage(
375 MediaKeys::MessageType message_type
,
376 const std::vector
<uint8
>& message
) {
377 DCHECK(client_
) << "Client not set before message event";
378 client_
->message(convertMessageType(message_type
), vector_as_array(&message
),
382 void WebContentDecryptionModuleSessionImpl::OnSessionKeysChange(
383 bool has_additional_usable_key
,
384 CdmKeysInfo keys_info
) {
385 blink::WebVector
<blink::WebEncryptedMediaKeyInformation
> keys(
387 for (size_t i
= 0; i
< keys_info
.size(); ++i
) {
388 const auto& key_info
= keys_info
[i
];
389 keys
[i
].setId(blink::WebData(reinterpret_cast<char*>(&key_info
->key_id
[0]),
390 key_info
->key_id
.size()));
391 keys
[i
].setStatus(convertStatus(key_info
->status
));
392 keys
[i
].setSystemCode(key_info
->system_code
);
395 // Now send the event to blink.
396 client_
->keysStatusesChange(keys
, has_additional_usable_key
);
399 void WebContentDecryptionModuleSessionImpl::OnSessionExpirationUpdate(
400 const base::Time
& new_expiry_time
) {
401 client_
->expirationChanged(new_expiry_time
.ToJsTime());
404 void WebContentDecryptionModuleSessionImpl::OnSessionClosed() {
412 blink::WebContentDecryptionModuleResult::SessionStatus
413 WebContentDecryptionModuleSessionImpl::OnSessionInitialized(
414 const std::string
& session_id
) {
415 // CDM will return NULL if the session to be loaded can't be found.
416 if (session_id
.empty())
417 return blink::WebContentDecryptionModuleResult::SessionNotFound
;
419 DCHECK(session_id_
.empty()) << "Session ID may not be changed once set.";
420 session_id_
= session_id
;
421 return adapter_
->RegisterSession(session_id_
, weak_ptr_factory_
.GetWeakPtr())
422 ? blink::WebContentDecryptionModuleResult::NewSession
423 : blink::WebContentDecryptionModuleResult::SessionAlreadyExists
;