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 | |
40 | namespace WebCore { |
41 | |
42 | namespace { |
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 | |
52 | static Optional<Vector<Ref<SharedBuffer>>> (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 | |
92 | static 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 | |
110 | static Optional<Vector<Ref<SharedBuffer>>> (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 | |
139 | static 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 | |
149 | static 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 | |
159 | static Optional<Vector<Ref<SharedBuffer>>> (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 | |
172 | InitDataRegistry& InitDataRegistry::shared() |
173 | { |
174 | static NeverDestroyed<InitDataRegistry> registry; |
175 | return registry.get(); |
176 | } |
177 | |
178 | InitDataRegistry::InitDataRegistry() |
179 | { |
180 | registerInitDataType("keyids" , { sanitizeKeyids, extractKeyIDsKeyids }); |
181 | registerInitDataType("cenc" , { sanitizeCenc, extractKeyIDsCenc }); |
182 | registerInitDataType("webm" , { sanitizeWebM, extractKeyIDsWebM }); |
183 | } |
184 | |
185 | InitDataRegistry::~InitDataRegistry() = default; |
186 | |
187 | RefPtr<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 | |
195 | Optional<Vector<Ref<SharedBuffer>>> InitDataRegistry::(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 | |
203 | void 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 | |