1 | /* |
2 | * Copyright (C) 2016 Metrological Group B.V. |
3 | * Copyright (C) 2016 Igalia S.L. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions |
7 | * are met: |
8 | * |
9 | * 1. Redistributions of source code must retain the above copyright |
10 | * notice, this list of conditions and the following disclaimer. |
11 | * 2. Redistributions in binary form must reproduce the above |
12 | * copyright notice, this list of conditions and the following |
13 | * disclaimer in the documentation and/or other materials provided |
14 | * with the distribution. |
15 | * |
16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
27 | */ |
28 | |
29 | #include "config.h" |
30 | #include "CDMClearKey.h" |
31 | |
32 | #if ENABLE(ENCRYPTED_MEDIA) |
33 | |
34 | #include "CDMKeySystemConfiguration.h" |
35 | #include "CDMRestrictions.h" |
36 | #include "CDMSessionType.h" |
37 | #include "SharedBuffer.h" |
38 | #include <wtf/JSONValues.h> |
39 | #include <wtf/MainThread.h> |
40 | #include <wtf/NeverDestroyed.h> |
41 | #include <wtf/text/Base64.h> |
42 | |
43 | namespace WebCore { |
44 | |
45 | // ClearKey CENC SystemID. |
46 | // https://www.w3.org/TR/eme-initdata-cenc/#common-system |
47 | const uint8_t clearKeyCencSystemId[] = { 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b }; |
48 | const unsigned clearKeyCencSystemIdSize = sizeof(clearKeyCencSystemId); |
49 | const unsigned keyIdSize = 16; |
50 | |
51 | class ClearKeyState { |
52 | using KeyStore = HashMap<String, Vector<CDMInstanceClearKey::Key>>; |
53 | |
54 | public: |
55 | static ClearKeyState& singleton(); |
56 | |
57 | KeyStore& keys() { return m_keys; } |
58 | |
59 | private: |
60 | friend class NeverDestroyed<ClearKeyState>; |
61 | ClearKeyState(); |
62 | KeyStore m_keys; |
63 | }; |
64 | |
65 | ClearKeyState& ClearKeyState::singleton() |
66 | { |
67 | static NeverDestroyed<ClearKeyState> s_state; |
68 | return s_state; |
69 | } |
70 | |
71 | ClearKeyState::ClearKeyState() = default; |
72 | |
73 | static RefPtr<JSON::Object> parseJSONObject(const SharedBuffer& buffer) |
74 | { |
75 | // Fail on large buffers whose size doesn't fit into a 32-bit unsigned integer. |
76 | size_t size = buffer.size(); |
77 | if (size > std::numeric_limits<unsigned>::max()) |
78 | return nullptr; |
79 | |
80 | // Parse the buffer contents as JSON, returning the root object (if any). |
81 | String json { buffer.data(), static_cast<unsigned>(size) }; |
82 | RefPtr<JSON::Value> value; |
83 | RefPtr<JSON::Object> object; |
84 | if (!JSON::Value::parseJSON(json, value) || !value->asObject(object)) |
85 | return nullptr; |
86 | |
87 | return object; |
88 | } |
89 | |
90 | static Optional<Vector<CDMInstanceClearKey::Key>> parseLicenseFormat(const JSON::Object& root) |
91 | { |
92 | // If the 'keys' key is present in the root object, parse the JSON further |
93 | // according to the specified 'license' format. |
94 | auto it = root.find("keys" ); |
95 | if (it == root.end()) |
96 | return WTF::nullopt; |
97 | |
98 | // Retrieve the keys array. |
99 | RefPtr<JSON::Array> keysArray; |
100 | if (!it->value->asArray(keysArray)) |
101 | return WTF::nullopt; |
102 | |
103 | Vector<CDMInstanceClearKey::Key> decodedKeys; |
104 | bool validFormat = std::all_of(keysArray->begin(), keysArray->end(), |
105 | [&decodedKeys] (const auto& value) { |
106 | RefPtr<JSON::Object> keyObject; |
107 | if (!value->asObject(keyObject)) |
108 | return false; |
109 | |
110 | String keyType; |
111 | if (!keyObject->getString("kty" , keyType) || !equalLettersIgnoringASCIICase(keyType, "oct" )) |
112 | return false; |
113 | |
114 | String keyID, keyValue; |
115 | if (!keyObject->getString("kid" , keyID) || !keyObject->getString("k" , keyValue)) |
116 | return false; |
117 | |
118 | Vector<char> keyIDData, keyValueData; |
119 | if (!WTF::base64URLDecode(keyID, { keyIDData }) || !WTF::base64URLDecode(keyValue, { keyValueData })) |
120 | return false; |
121 | |
122 | decodedKeys.append({ CDMInstanceSession::KeyStatus::Usable, SharedBuffer::create(WTFMove(keyIDData)), SharedBuffer::create(WTFMove(keyValueData)) }); |
123 | return true; |
124 | }); |
125 | if (!validFormat) |
126 | return WTF::nullopt; |
127 | return decodedKeys; |
128 | } |
129 | |
130 | static bool parseLicenseReleaseAcknowledgementFormat(const JSON::Object& root) |
131 | { |
132 | // If the 'kids' key is present in the root object, parse the JSON further |
133 | // according to the specified 'license release acknowledgement' format. |
134 | auto it = root.find("kids" ); |
135 | if (it == root.end()) |
136 | return false; |
137 | |
138 | // Retrieve the kids array. |
139 | RefPtr<JSON::Array> kidsArray; |
140 | if (!it->value->asArray(kidsArray)) |
141 | return false; |
142 | |
143 | // FIXME: Return the key IDs and validate them. |
144 | return true; |
145 | } |
146 | |
147 | // https://www.w3.org/TR/eme-initdata-cenc/#common-system |
148 | // 4.1 Definition |
149 | // The SystemID is 1077efec-c0b2-4d02-ace3-3c1e52e2fb4b. |
150 | // The PSSH box format is as follows. It follows version 1 of the 'pssh' box as defined in [CENC]. |
151 | // pssh = [ |
152 | // 0x00, 0x00, 0x00, 0x4c, 0x70, 0x73, 0x73, 0x68, // BMFF box header (76 bytes, 'pssh') |
153 | // 0x01, 0x00, 0x00, 0x00, // Full box header (version = 1, flags = 0) |
154 | // 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // SystemID |
155 | // 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, |
156 | // 0x00, 0x00, 0x00, 0x02, // KidCount (2) |
157 | // 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // First KID ("0123456789012345") |
158 | // 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, |
159 | // 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, // Second KID ("ABCDEFGHIJKLMNOP") |
160 | // 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, |
161 | // 0x00, 0x00, 0x00, 0x00, // Size of Data (0) |
162 | // ]; |
163 | |
164 | // This function extracts the KeyIds count and the location of the first KeyId in initData buffer. |
165 | static std::pair<unsigned, unsigned> (const SharedBuffer& initData) |
166 | { |
167 | std::pair<unsigned, unsigned> keyIdsMap(0, 0); |
168 | |
169 | // Check the initData size. |
170 | if (initData.isEmpty() || initData.size() > std::numeric_limits<unsigned>::max()) |
171 | return keyIdsMap; |
172 | |
173 | const char* data = initData.data(); |
174 | unsigned initDataSize = initData.size(); |
175 | unsigned index = 0; |
176 | unsigned psshSize = 0; |
177 | |
178 | // Search in the concatenated or the simple InitData, the ClearKey PSSH. |
179 | bool foundPssh = false; |
180 | while (true) { |
181 | |
182 | // Check the overflow InitData. |
183 | if (index + 12 + clearKeyCencSystemIdSize >= initDataSize) |
184 | return keyIdsMap; |
185 | |
186 | psshSize = data[index + 2] * 256 + data[index + 3]; |
187 | |
188 | // Check the pssh size |
189 | if (!psshSize) |
190 | return keyIdsMap; |
191 | |
192 | // 12 = BMFF box header + Full box header. |
193 | if (!memcmp(&data[index + 12], clearKeyCencSystemId, clearKeyCencSystemIdSize)) { |
194 | foundPssh = true; |
195 | break; |
196 | } |
197 | index += psshSize; |
198 | } |
199 | |
200 | // Check if the InitData contains the ClearKey PSSH. |
201 | if (!foundPssh) |
202 | return keyIdsMap; |
203 | |
204 | index += (12 + clearKeyCencSystemIdSize); // 12 (BMFF box header + Full box header) + SystemID size. |
205 | |
206 | // Check the overflow. |
207 | if (index + 3 >= initDataSize) |
208 | return keyIdsMap; |
209 | |
210 | keyIdsMap.first = data[index + 3]; // Read the KeyIdsCount. |
211 | index += 4; // KeyIdsCount size. |
212 | |
213 | // Check the overflow. |
214 | if ((index + (keyIdsMap.first * keyIdSize)) >= initDataSize) |
215 | return keyIdsMap; |
216 | |
217 | keyIdsMap.second = index; // The location of the first KeyId in initData. |
218 | |
219 | return keyIdsMap; |
220 | } |
221 | |
222 | // This function checks if the initData sharedBuffer is a valid CENC initData. |
223 | static bool isCencInitData(const SharedBuffer& initData) |
224 | { |
225 | std::pair<unsigned, unsigned> keyIdsMap = extractKeyidsLocationFromCencInitData(initData); |
226 | return ((keyIdsMap.first) && (keyIdsMap.second)); |
227 | } |
228 | |
229 | static Ref<SharedBuffer> (const SharedBuffer& initData) |
230 | { |
231 | Ref<SharedBuffer> keyIds = SharedBuffer::create(); |
232 | |
233 | std::pair<unsigned, unsigned> keyIdsMap = extractKeyidsLocationFromCencInitData(initData); |
234 | unsigned keyIdCount = keyIdsMap.first; |
235 | unsigned index = keyIdsMap.second; |
236 | |
237 | // Check if initData is a valid CENC initData. |
238 | if (!keyIdCount || !index) |
239 | return keyIds; |
240 | |
241 | const char* data = initData.data(); |
242 | |
243 | auto object = JSON::Object::create(); |
244 | auto keyIdsArray = JSON::Array::create(); |
245 | |
246 | // Read the KeyId |
247 | // 9.1.3 License Request Format |
248 | // This section describes the format of the license request provided to the application via the message attribute of the message event. |
249 | // The format is a JSON object containing the following members: |
250 | // "kids" |
251 | // An array of key IDs. Each element of the array is the base64url encoding of the octet sequence containing the key ID value. |
252 | for (unsigned i = 0; i < keyIdCount; i++) { |
253 | String keyId = WTF::base64URLEncode(&data[index], keyIdSize); |
254 | keyIdsArray->pushString(keyId); |
255 | index += keyIdSize; |
256 | } |
257 | |
258 | object->setArray("kids" , WTFMove(keyIdsArray)); |
259 | CString jsonData = object->toJSONString().utf8(); |
260 | keyIds->append(jsonData.data(), jsonData.length()); |
261 | return keyIds; |
262 | } |
263 | |
264 | static Ref<SharedBuffer> (const SharedBuffer& initData) |
265 | { |
266 | Ref<SharedBuffer> keyIds = SharedBuffer::create(); |
267 | |
268 | // Check if initData is a valid WebM initData. |
269 | if (initData.isEmpty() || initData.size() > std::numeric_limits<unsigned>::max()) |
270 | return keyIds; |
271 | |
272 | auto object = JSON::Object::create(); |
273 | auto keyIdsArray = JSON::Array::create(); |
274 | |
275 | // Read the KeyId |
276 | // 9.1.3 License Request Format |
277 | // This section describes the format of the license request provided to the application via the message attribute of the message event. |
278 | // The format is a JSON object containing the following members: |
279 | // "kids" |
280 | // An array of key IDs. Each element of the array is the base64url encoding of the octet sequence containing the key ID value. |
281 | String keyId = WTF::base64URLEncode(initData.data(), initData.size()); |
282 | keyIdsArray->pushString(keyId); |
283 | |
284 | object->setArray("kids" , WTFMove(keyIdsArray)); |
285 | CString jsonData = object->toJSONString().utf8(); |
286 | keyIds->append(jsonData.data(), jsonData.length()); |
287 | return keyIds; |
288 | } |
289 | |
290 | CDMFactoryClearKey& CDMFactoryClearKey::singleton() |
291 | { |
292 | static NeverDestroyed<CDMFactoryClearKey> s_factory; |
293 | return s_factory; |
294 | } |
295 | |
296 | CDMFactoryClearKey::CDMFactoryClearKey() = default; |
297 | CDMFactoryClearKey::~CDMFactoryClearKey() = default; |
298 | |
299 | std::unique_ptr<CDMPrivate> CDMFactoryClearKey::createCDM(const String& keySystem) |
300 | { |
301 | #ifdef NDEBUG |
302 | UNUSED_PARAM(keySystem); |
303 | #else |
304 | ASSERT(supportsKeySystem(keySystem)); |
305 | #endif |
306 | return std::make_unique<CDMPrivateClearKey>(); |
307 | } |
308 | |
309 | bool CDMFactoryClearKey::supportsKeySystem(const String& keySystem) |
310 | { |
311 | // `org.w3.clearkey` is the only supported key system. |
312 | return equalLettersIgnoringASCIICase(keySystem, "org.w3.clearkey" ); |
313 | } |
314 | |
315 | CDMPrivateClearKey::CDMPrivateClearKey() = default; |
316 | CDMPrivateClearKey::~CDMPrivateClearKey() = default; |
317 | |
318 | bool CDMPrivateClearKey::supportsInitDataType(const AtomicString& initDataType) const |
319 | { |
320 | // `keyids` and 'cenc' are the only supported init data type. |
321 | return (equalLettersIgnoringASCIICase(initDataType, "keyids" ) || equalLettersIgnoringASCIICase(initDataType, "cenc" ) || equalLettersIgnoringASCIICase(initDataType, "webm" )); |
322 | } |
323 | |
324 | static bool containsPersistentLicenseType(const Vector<CDMSessionType>& types) |
325 | { |
326 | return std::any_of(types.begin(), types.end(), |
327 | [] (auto& sessionType) { return sessionType == CDMSessionType::PersistentLicense; }); |
328 | } |
329 | |
330 | bool CDMPrivateClearKey::supportsConfiguration(const CDMKeySystemConfiguration& configuration) const |
331 | { |
332 | // Reject any configuration that marks distinctive identifier as required. |
333 | if (configuration.distinctiveIdentifier == CDMRequirement::Required) |
334 | return false; |
335 | |
336 | // Reject any configuration that marks persistent state as required, unless |
337 | // the 'persistent-license' session type has to be supported. |
338 | if (configuration.persistentState == CDMRequirement::Required && !containsPersistentLicenseType(configuration.sessionTypes)) |
339 | return false; |
340 | |
341 | return true; |
342 | } |
343 | |
344 | bool CDMPrivateClearKey::supportsConfigurationWithRestrictions(const CDMKeySystemConfiguration& configuration, const CDMRestrictions& restrictions) const |
345 | { |
346 | // Reject any configuration that marks distincitive identifier as required, or that marks |
347 | // distinctive identifier as optional even when restrictions mark it as denied. |
348 | if ((configuration.distinctiveIdentifier == CDMRequirement::Optional && restrictions.distinctiveIdentifierDenied) |
349 | || configuration.distinctiveIdentifier == CDMRequirement::Required) |
350 | return false; |
351 | |
352 | // Reject any configuration that marks persistent state as optional even when |
353 | // restrictions mark it as denied. |
354 | if (configuration.persistentState == CDMRequirement::Optional && restrictions.persistentStateDenied) |
355 | return false; |
356 | |
357 | // Reject any configuration that marks persistent state as required, unless |
358 | // the 'persistent-license' session type has to be supported. |
359 | if (configuration.persistentState == CDMRequirement::Required && !containsPersistentLicenseType(configuration.sessionTypes)) |
360 | return false; |
361 | |
362 | return true; |
363 | } |
364 | |
365 | bool CDMPrivateClearKey::supportsSessionTypeWithConfiguration(CDMSessionType& sessionType, const CDMKeySystemConfiguration& configuration) const |
366 | { |
367 | // Only support the 'temporary' and 'persistent-license' session types. |
368 | if (sessionType != CDMSessionType::Temporary && sessionType != CDMSessionType::PersistentLicense) |
369 | return false; |
370 | return supportsConfiguration(configuration); |
371 | } |
372 | |
373 | bool CDMPrivateClearKey::supportsRobustness(const String& robustness) const |
374 | { |
375 | // Only empty `robustness` string is supported. |
376 | return robustness.isEmpty(); |
377 | } |
378 | |
379 | CDMRequirement CDMPrivateClearKey::distinctiveIdentifiersRequirement(const CDMKeySystemConfiguration&, const CDMRestrictions& restrictions) const |
380 | { |
381 | // Distinctive identifier is not allowed if it's been denied, otherwise it's optional. |
382 | if (restrictions.distinctiveIdentifierDenied) |
383 | return CDMRequirement::NotAllowed; |
384 | return CDMRequirement::Optional; |
385 | } |
386 | |
387 | CDMRequirement CDMPrivateClearKey::persistentStateRequirement(const CDMKeySystemConfiguration&, const CDMRestrictions& restrictions) const |
388 | { |
389 | // Persistent state is not allowed if it's been denied, otherwise it's optional. |
390 | if (restrictions.persistentStateDenied) |
391 | return CDMRequirement::NotAllowed; |
392 | return CDMRequirement::Optional; |
393 | } |
394 | |
395 | bool CDMPrivateClearKey::distinctiveIdentifiersAreUniquePerOriginAndClearable(const CDMKeySystemConfiguration&) const |
396 | { |
397 | return false; |
398 | } |
399 | |
400 | RefPtr<CDMInstance> CDMPrivateClearKey::createInstance() |
401 | { |
402 | return adoptRef(new CDMInstanceClearKey); |
403 | } |
404 | |
405 | void CDMPrivateClearKey::loadAndInitialize() |
406 | { |
407 | // No-op. |
408 | } |
409 | |
410 | bool CDMPrivateClearKey::supportsServerCertificates() const |
411 | { |
412 | // Server certificates are not supported. |
413 | return false; |
414 | } |
415 | |
416 | bool CDMPrivateClearKey::supportsSessions() const |
417 | { |
418 | // Sessions are supported. |
419 | return true; |
420 | } |
421 | |
422 | bool CDMPrivateClearKey::supportsInitData(const AtomicString& initDataType, const SharedBuffer& initData) const |
423 | { |
424 | // Validate the initData buffer as an JSON object in keyids case. |
425 | if (equalLettersIgnoringASCIICase(initDataType, "keyids" ) && parseJSONObject(initData)) |
426 | return true; |
427 | |
428 | // Validate the initData buffer as CENC initData. |
429 | if (equalLettersIgnoringASCIICase(initDataType, "cenc" ) && isCencInitData(initData)) |
430 | return true; |
431 | |
432 | // Validate the initData buffer as WebM initData. |
433 | if (equalLettersIgnoringASCIICase(initDataType, "webm" ) && !initData.isEmpty()) |
434 | return true; |
435 | |
436 | return false; |
437 | } |
438 | |
439 | RefPtr<SharedBuffer> CDMPrivateClearKey::sanitizeResponse(const SharedBuffer& response) const |
440 | { |
441 | // Validate the response buffer as an JSON object. |
442 | if (!parseJSONObject(response)) |
443 | return nullptr; |
444 | |
445 | return response.copy(); |
446 | } |
447 | |
448 | Optional<String> CDMPrivateClearKey::sanitizeSessionId(const String& sessionId) const |
449 | { |
450 | // Validate the session ID string as an 32-bit integer. |
451 | bool ok; |
452 | sessionId.toUIntStrict(&ok); |
453 | if (!ok) |
454 | return WTF::nullopt; |
455 | return sessionId; |
456 | } |
457 | |
458 | CDMInstanceClearKey::CDMInstanceClearKey() |
459 | { |
460 | } |
461 | |
462 | CDMInstanceClearKey::~CDMInstanceClearKey() = default; |
463 | |
464 | CDMInstance::SuccessValue CDMInstanceClearKey::initializeWithConfiguration(const CDMKeySystemConfiguration&) |
465 | { |
466 | // No-op. |
467 | return Succeeded; |
468 | } |
469 | |
470 | CDMInstance::SuccessValue CDMInstanceClearKey::setDistinctiveIdentifiersAllowed(bool allowed) |
471 | { |
472 | // Reject setting distinctive identifiers as allowed. |
473 | return !allowed ? Succeeded : Failed; |
474 | } |
475 | |
476 | CDMInstance::SuccessValue CDMInstanceClearKey::setPersistentStateAllowed(bool allowed) |
477 | { |
478 | // Reject setting persistent state as allowed. |
479 | return !allowed ? Succeeded : Failed; |
480 | } |
481 | |
482 | CDMInstance::SuccessValue CDMInstanceClearKey::setServerCertificate(Ref<SharedBuffer>&&) |
483 | { |
484 | // Reject setting any server certificate. |
485 | return Failed; |
486 | } |
487 | |
488 | CDMInstance::SuccessValue CDMInstanceClearKey::setStorageDirectory(const String& storageDirectory) |
489 | { |
490 | // Reject any persistent state storage. |
491 | return storageDirectory.isEmpty() ? Succeeded : Failed; |
492 | } |
493 | |
494 | const String& CDMInstanceClearKey::keySystem() const |
495 | { |
496 | static const NeverDestroyed<String> s_keySystem { MAKE_STATIC_STRING_IMPL("org.w3.clearkey" ) }; |
497 | |
498 | return s_keySystem; |
499 | } |
500 | |
501 | RefPtr<CDMInstanceSession> CDMInstanceClearKey::createSession() |
502 | { |
503 | return adoptRef(new CDMInstanceSessionClearKey()); |
504 | } |
505 | |
506 | const Vector<CDMInstanceClearKey::Key> CDMInstanceClearKey::keys() const |
507 | { |
508 | // Return the keys of all sessions. |
509 | Vector<CDMInstanceClearKey::Key> allKeys { }; |
510 | auto locker = holdLock(m_keysMutex); |
511 | size_t initialCapacity = 0; |
512 | for (auto& key : ClearKeyState::singleton().keys().values()) |
513 | initialCapacity += key.size(); |
514 | allKeys.reserveInitialCapacity(initialCapacity); |
515 | |
516 | for (auto& key : ClearKeyState::singleton().keys().values()) |
517 | allKeys.appendVector(key); |
518 | |
519 | return allKeys; |
520 | } |
521 | |
522 | void CDMInstanceSessionClearKey::requestLicense(LicenseType, const AtomicString& initDataType, Ref<SharedBuffer>&& initData, LicenseCallback&& callback) |
523 | { |
524 | static uint32_t s_sessionIdValue = 0; |
525 | ++s_sessionIdValue; |
526 | |
527 | if (equalLettersIgnoringASCIICase(initDataType, "cenc" )) |
528 | initData = extractKeyidsFromCencInitData(initData.get()); |
529 | |
530 | if (equalLettersIgnoringASCIICase(initDataType, "webm" )) |
531 | initData = extractKeyIdFromWebMInitData(initData.get()); |
532 | |
533 | callOnMainThread( |
534 | [weakThis = makeWeakPtr(*this), callback = WTFMove(callback), initData = WTFMove(initData), sessionIdValue = s_sessionIdValue]() mutable { |
535 | if (!weakThis) |
536 | return; |
537 | |
538 | callback(WTFMove(initData), String::number(sessionIdValue), false, Succeeded); |
539 | }); |
540 | } |
541 | |
542 | void CDMInstanceSessionClearKey::updateLicense(const String& sessionId, LicenseType, const SharedBuffer& response, LicenseUpdateCallback&& callback) |
543 | { |
544 | // Use a helper functor that schedules the callback dispatch, avoiding |
545 | // duplicated callOnMainThread() calls. |
546 | auto dispatchCallback = |
547 | [this, &callback](bool sessionWasClosed, Optional<KeyStatusVector>&& changedKeys, SuccessValue succeeded) { |
548 | callOnMainThread( |
549 | [weakThis = makeWeakPtr(*this), callback = WTFMove(callback), sessionWasClosed, changedKeys = WTFMove(changedKeys), succeeded] () mutable { |
550 | if (!weakThis) |
551 | return; |
552 | |
553 | callback(sessionWasClosed, WTFMove(changedKeys), WTF::nullopt, WTF::nullopt, succeeded); |
554 | }); |
555 | }; |
556 | |
557 | // Parse the response buffer as an JSON object. |
558 | RefPtr<JSON::Object> root = parseJSONObject(response); |
559 | if (!root) { |
560 | dispatchCallback(false, WTF::nullopt, SuccessValue::Failed); |
561 | return; |
562 | } |
563 | |
564 | // Parse the response using 'license' formatting, if possible. |
565 | if (auto decodedKeys = parseLicenseFormat(*root)) { |
566 | // Retrieve the target Vector of Key objects for this session. |
567 | auto& keyVector = ClearKeyState::singleton().keys().ensure(sessionId, [] { return Vector<CDMInstanceClearKey::Key> { }; }).iterator->value; |
568 | |
569 | // For each decoded key, find an existing item for the decoded key's ID. If none exist, |
570 | // the key is decoded. Otherwise, the key is updated in case there's a mismatch between |
571 | // the size or data of the existing and proposed key. |
572 | bool keysChanged = false; |
573 | for (auto& key : *decodedKeys) { |
574 | auto it = std::find_if(keyVector.begin(), keyVector.end(), |
575 | [&key] (const CDMInstanceClearKey::Key& containedKey) { |
576 | return containedKey.keyIDData->size() == key.keyIDData->size() |
577 | && !std::memcmp(containedKey.keyIDData->data(), key.keyIDData->data(), containedKey.keyIDData->size()); |
578 | }); |
579 | if (it != keyVector.end()) { |
580 | auto& existingKey = it->keyValueData; |
581 | auto& proposedKey = key.keyValueData; |
582 | |
583 | // Update the existing Key if it differs from the proposed key in key value. |
584 | if (existingKey->size() != proposedKey->size() || std::memcmp(existingKey->data(), proposedKey->data(), existingKey->size())) { |
585 | *it = WTFMove(key); |
586 | keysChanged = true; |
587 | } |
588 | } else { |
589 | // In case a Key for this key ID doesn't exist yet, append the new one to keyVector. |
590 | keyVector.append(WTFMove(key)); |
591 | keysChanged = true; |
592 | } |
593 | } |
594 | |
595 | // In case of changed keys, we have to provide a KeyStatusVector of all the keys for |
596 | // this session. |
597 | Optional<KeyStatusVector> changedKeys; |
598 | if (keysChanged) { |
599 | // First a helper Vector is constructed, cotaining pairs of SharedBuffer RefPtrs |
600 | // representint key ID data, and the corresponding key statuses. |
601 | // We can't use KeyStatusVector here because this Vector has to be sorted, which |
602 | // is not possible to do on Ref<> objects. |
603 | Vector<std::pair<RefPtr<SharedBuffer>, KeyStatus>> keys; |
604 | keys.reserveInitialCapacity(keyVector.size()); |
605 | for (auto& it : keyVector) |
606 | keys.uncheckedAppend(std::pair<RefPtr<SharedBuffer>, KeyStatus> { it.keyIDData, it.status }); |
607 | |
608 | // Sort first by size, second by data. |
609 | std::sort(keys.begin(), keys.end(), |
610 | [] (const auto& a, const auto& b) { |
611 | if (a.first->size() != b.first->size()) |
612 | return a.first->size() < b.first->size(); |
613 | |
614 | return std::memcmp(a.first->data(), b.first->data(), a.first->size()) < 0; |
615 | }); |
616 | |
617 | // Finally construct the mirroring KeyStatusVector object and move it into the |
618 | // Optional<> object that will be passed to the callback. |
619 | KeyStatusVector keyStatusVector; |
620 | keyStatusVector.reserveInitialCapacity(keys.size()); |
621 | for (auto& it : keys) |
622 | keyStatusVector.uncheckedAppend(std::pair<Ref<SharedBuffer>, KeyStatus> { *it.first, it.second }); |
623 | |
624 | changedKeys = WTFMove(keyStatusVector); |
625 | } |
626 | |
627 | dispatchCallback(false, WTFMove(changedKeys), SuccessValue::Succeeded); |
628 | return; |
629 | } |
630 | |
631 | // Parse the response using 'license release acknowledgement' formatting, if possible. |
632 | if (parseLicenseReleaseAcknowledgementFormat(*root)) { |
633 | // FIXME: Retrieve the key ID information and use it to validate the keys for this sessionId. |
634 | ClearKeyState::singleton().keys().remove(sessionId); |
635 | dispatchCallback(true, WTF::nullopt, SuccessValue::Succeeded); |
636 | return; |
637 | } |
638 | |
639 | // Bail in case no format was recognized. |
640 | dispatchCallback(false, WTF::nullopt, SuccessValue::Failed); |
641 | } |
642 | |
643 | void CDMInstanceSessionClearKey::loadSession(LicenseType, const String& sessionId, const String&, LoadSessionCallback&& callback) |
644 | { |
645 | // Use a helper functor that schedules the callback dispatch, avoiding duplicated callOnMainThread() calls. |
646 | auto dispatchCallback = |
647 | [this, &callback](Optional<KeyStatusVector>&& existingKeys, SuccessValue success, SessionLoadFailure loadFailure) { |
648 | callOnMainThread( |
649 | [weakThis = makeWeakPtr(*this), callback = WTFMove(callback), existingKeys = WTFMove(existingKeys), success, loadFailure]() mutable { |
650 | if (!weakThis) |
651 | return; |
652 | |
653 | callback(WTFMove(existingKeys), WTF::nullopt, WTF::nullopt, success, loadFailure); |
654 | }); |
655 | }; |
656 | |
657 | // Construct the KeyStatusVector object, representing all the known keys for this session. |
658 | KeyStatusVector keyStatusVector; |
659 | { |
660 | auto& keys = ClearKeyState::singleton().keys(); |
661 | auto it = keys.find(sessionId); |
662 | if (it == keys.end()) { |
663 | dispatchCallback(WTF::nullopt, Failed, SessionLoadFailure::NoSessionData); |
664 | return; |
665 | } |
666 | |
667 | auto& keyVector = it->value; |
668 | keyStatusVector.reserveInitialCapacity(keyVector.size()); |
669 | for (auto& key : keyVector) |
670 | keyStatusVector.uncheckedAppend(std::pair<Ref<SharedBuffer>, KeyStatus> { *key.keyIDData, key.status }); |
671 | } |
672 | |
673 | dispatchCallback(WTFMove(keyStatusVector), Succeeded, SessionLoadFailure::None); |
674 | } |
675 | |
676 | void CDMInstanceSessionClearKey::closeSession(const String&, CloseSessionCallback&& callback) |
677 | { |
678 | callOnMainThread( |
679 | [weakThis = makeWeakPtr(*this), callback = WTFMove(callback)] () mutable { |
680 | if (!weakThis) |
681 | return; |
682 | |
683 | callback(); |
684 | }); |
685 | } |
686 | |
687 | void CDMInstanceSessionClearKey::removeSessionData(const String& sessionId, LicenseType, RemoveSessionDataCallback&& callback) |
688 | { |
689 | // Use a helper functor that schedules the callback dispatch, avoiding duplicated callOnMainThread() calls. |
690 | auto dispatchCallback = |
691 | [this, &callback](KeyStatusVector&& keyStatusVector, Optional<Ref<SharedBuffer>>&& message, SuccessValue success) { |
692 | callOnMainThread( |
693 | [weakThis = makeWeakPtr(*this), callback = WTFMove(callback), keyStatusVector = WTFMove(keyStatusVector), message = WTFMove(message), success]() mutable { |
694 | if (!weakThis) |
695 | return; |
696 | |
697 | callback(WTFMove(keyStatusVector), WTFMove(message), success); |
698 | }); |
699 | }; |
700 | |
701 | // Construct the KeyStatusVector object, representing released keys, and the message in the |
702 | // 'license release' format. |
703 | KeyStatusVector keyStatusVector; |
704 | RefPtr<SharedBuffer> message; |
705 | { |
706 | // Retrieve information for the given session ID, bailing if none is found. |
707 | auto& keys = ClearKeyState::singleton().keys(); |
708 | auto it = keys.find(sessionId); |
709 | if (it == keys.end()) { |
710 | dispatchCallback(KeyStatusVector { }, WTF::nullopt, SuccessValue::Failed); |
711 | return; |
712 | } |
713 | |
714 | // Retrieve the Key vector, containing all the keys for this session, and |
715 | // then remove the key map entry for this session. |
716 | auto keyVector = WTFMove(it->value); |
717 | keys.remove(it); |
718 | |
719 | // Construct the KeyStatusVector object, pairing key IDs with the 'released' status. |
720 | keyStatusVector.reserveInitialCapacity(keyVector.size()); |
721 | for (auto& key : keyVector) |
722 | keyStatusVector.uncheckedAppend(std::pair<Ref<SharedBuffer>, KeyStatus> { *key.keyIDData, KeyStatus::Released }); |
723 | |
724 | // Construct JSON that represents the 'license release' format, creating a 'kids' array |
725 | // of base64URL-encoded key IDs for all keys that were associated with this session. |
726 | auto rootObject = JSON::Object::create(); |
727 | { |
728 | auto array = JSON::Array::create(); |
729 | for (auto& key : keyVector) { |
730 | ASSERT(key.keyIDData->size() <= std::numeric_limits<unsigned>::max()); |
731 | array->pushString(WTF::base64URLEncode(key.keyIDData->data(), static_cast<unsigned>(key.keyIDData->size()))); |
732 | } |
733 | rootObject->setArray("kids" , WTFMove(array)); |
734 | } |
735 | |
736 | // Copy the JSON data into a SharedBuffer object. |
737 | String messageString = rootObject->toJSONString(); |
738 | CString messageCString = messageString.utf8(); |
739 | message = SharedBuffer::create(messageCString.data(), messageCString.length()); |
740 | } |
741 | |
742 | dispatchCallback(WTFMove(keyStatusVector), Ref<SharedBuffer>(*message), SuccessValue::Succeeded); |
743 | } |
744 | |
745 | void CDMInstanceSessionClearKey::storeRecordOfKeyUsage(const String&) |
746 | { |
747 | } |
748 | |
749 | } // namespace WebCore |
750 | |
751 | #endif // ENABLE(ENCRYPTED_MEDIA) |
752 | |