1/*
2 * Copyright (C) 2017 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "InitDataRegistry.h"
28
29#if ENABLE(ENCRYPTED_MEDIA)
30
31#include "ISOProtectionSystemSpecificHeaderBox.h"
32#include <JavaScriptCore/DataView.h>
33#include "NotImplemented.h"
34#include "SharedBuffer.h"
35#include <wtf/JSONValues.h>
36#include <wtf/NeverDestroyed.h>
37#include <wtf/text/Base64.h>
38
39
40namespace WebCore {
41
42namespace {
43 const uint32_t kCencMaxBoxSize = 64 * KB;
44 // ContentEncKeyID has this EBML code [47][E2] in WebM,
45 // as per spec the size of the ContentEncKeyID is encoded on 16 bits.
46 // https://matroska.org/technical/specs/index.html#ContentEncKeyID/
47 const uint32_t kWebMMaxContentEncKeyIDSize = 64 * KB; // 2^16
48 const uint32_t kKeyIdsMinKeyIdSizeInBytes = 1;
49 const uint32_t kKeyIdsMaxKeyIdSizeInBytes = 512;
50}
51
52static Optional<Vector<Ref<SharedBuffer>>> extractKeyIDsKeyids(const SharedBuffer& buffer)
53{
54 // 1. Format
55 // https://w3c.github.io/encrypted-media/format-registry/initdata/keyids.html#format
56 if (buffer.size() > std::numeric_limits<unsigned>::max())
57 return WTF::nullopt;
58 String json { buffer.data(), static_cast<unsigned>(buffer.size()) };
59
60 RefPtr<JSON::Value> value;
61 if (!JSON::Value::parseJSON(json, value))
62 return WTF::nullopt;
63
64 RefPtr<JSON::Object> object;
65 if (!value->asObject(object))
66 return WTF::nullopt;
67
68 RefPtr<JSON::Array> kidsArray;
69 if (!object->getArray("kids", kidsArray))
70 return WTF::nullopt;
71
72 Vector<Ref<SharedBuffer>> keyIDs;
73 for (auto& value : *kidsArray) {
74 String keyID;
75 if (!value->asString(keyID))
76 continue;
77
78 Vector<char> keyIDData;
79 if (!WTF::base64URLDecode(keyID, { keyIDData }))
80 continue;
81
82 if (keyIDData.size() < kKeyIdsMinKeyIdSizeInBytes || keyIDData.size() > kKeyIdsMaxKeyIdSizeInBytes)
83 return WTF::nullopt;
84
85 Ref<SharedBuffer> keyIDBuffer = SharedBuffer::create(WTFMove(keyIDData));
86 keyIDs.append(WTFMove(keyIDBuffer));
87 }
88
89 return keyIDs;
90}
91
92static RefPtr<SharedBuffer> sanitizeKeyids(const SharedBuffer& buffer)
93{
94 // 1. Format
95 // https://w3c.github.io/encrypted-media/format-registry/initdata/keyids.html#format
96 auto keyIDBuffer = extractKeyIDsKeyids(buffer);
97 if (!keyIDBuffer)
98 return nullptr;
99
100 auto object = JSON::Object::create();
101 auto kidsArray = JSON::Array::create();
102 for (auto& buffer : keyIDBuffer.value())
103 kidsArray->pushString(WTF::base64URLEncode(buffer->data(), buffer->size()));
104 object->setArray("kids", WTFMove(kidsArray));
105
106 CString jsonData = object->toJSONString().utf8();
107 return SharedBuffer::create(jsonData.data(), jsonData.length());
108}
109
110static Optional<Vector<Ref<SharedBuffer>>> extractKeyIDsCenc(const SharedBuffer& buffer)
111{
112 // 4. Common SystemID and PSSH Box Format
113 // https://w3c.github.io/encrypted-media/format-registry/initdata/cenc.html#common-system
114 if (buffer.size() >= kCencMaxBoxSize)
115 return WTF::nullopt;
116
117 unsigned offset = 0;
118 Vector<Ref<SharedBuffer>> keyIDs;
119
120 auto view = JSC::DataView::create(buffer.tryCreateArrayBuffer(), offset, buffer.size());
121 while (auto optionalBoxType = ISOBox::peekBox(view, offset)) {
122 auto& boxTypeName = optionalBoxType.value().first;
123 auto& boxSize = optionalBoxType.value().second;
124
125 if (boxTypeName != ISOProtectionSystemSpecificHeaderBox::boxTypeName() || boxSize > buffer.size())
126 return WTF::nullopt;
127
128 ISOProtectionSystemSpecificHeaderBox psshBox;
129 if (!psshBox.read(view, offset))
130 return WTF::nullopt;
131
132 for (auto& value : psshBox.keyIDs())
133 keyIDs.append(SharedBuffer::create(WTFMove(value)));
134 }
135
136 return keyIDs;
137}
138
139static RefPtr<SharedBuffer> sanitizeCenc(const SharedBuffer& buffer)
140{
141 // 4. Common SystemID and PSSH Box Format
142 // https://w3c.github.io/encrypted-media/format-registry/initdata/cenc.html#common-system
143 if (!extractKeyIDsCenc(buffer))
144 return nullptr;
145
146 return buffer.copy();
147}
148
149static RefPtr<SharedBuffer> sanitizeWebM(const SharedBuffer& buffer)
150{
151 // Check if the buffer is a valid WebM initData.
152 // The WebM initData is the ContentEncKeyID, so should be less than kWebMMaxContentEncKeyIDSize.
153 if (buffer.isEmpty() || buffer.size() > kWebMMaxContentEncKeyIDSize)
154 return nullptr;
155
156 return buffer.copy();
157}
158
159static Optional<Vector<Ref<SharedBuffer>>> extractKeyIDsWebM(const SharedBuffer& buffer)
160{
161 Vector<Ref<SharedBuffer>> keyIDs;
162 RefPtr<SharedBuffer> sanitizedBuffer = sanitizeWebM(buffer);
163 if (!sanitizedBuffer)
164 return WTF::nullopt;
165
166 // 1. Format
167 // https://w3c.github.io/encrypted-media/format-registry/initdata/webm.html#format
168 keyIDs.append(sanitizedBuffer.releaseNonNull());
169 return keyIDs;
170}
171
172InitDataRegistry& InitDataRegistry::shared()
173{
174 static NeverDestroyed<InitDataRegistry> registry;
175 return registry.get();
176}
177
178InitDataRegistry::InitDataRegistry()
179{
180 registerInitDataType("keyids", { sanitizeKeyids, extractKeyIDsKeyids });
181 registerInitDataType("cenc", { sanitizeCenc, extractKeyIDsCenc });
182 registerInitDataType("webm", { sanitizeWebM, extractKeyIDsWebM });
183}
184
185InitDataRegistry::~InitDataRegistry() = default;
186
187RefPtr<SharedBuffer> InitDataRegistry::sanitizeInitData(const AtomicString& initDataType, const SharedBuffer& buffer)
188{
189 auto iter = m_types.find(initDataType);
190 if (iter == m_types.end() || !iter->value.sanitizeInitData)
191 return nullptr;
192 return iter->value.sanitizeInitData(buffer);
193}
194
195Optional<Vector<Ref<SharedBuffer>>> InitDataRegistry::extractKeyIDs(const AtomicString& initDataType, const SharedBuffer& buffer)
196{
197 auto iter = m_types.find(initDataType);
198 if (iter == m_types.end() || !iter->value.sanitizeInitData)
199 return WTF::nullopt;
200 return iter->value.extractKeyIDs(buffer);
201}
202
203void InitDataRegistry::registerInitDataType(const AtomicString& initDataType, InitDataTypeCallbacks&& callbacks)
204{
205 ASSERT(!m_types.contains(initDataType));
206 m_types.set(initDataType, WTFMove(callbacks));
207}
208
209}
210
211#endif // ENABLE(ENCRYPTED_MEDIA)
212