1/* GStreamer ClearKey common encryption decryptor
2 *
3 * Copyright (C) 2016 Metrological
4 * Copyright (C) 2016 Igalia S.L
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
19 * Boston, MA 02110-1335, USA.
20 */
21
22#include "config.h"
23#include "WebKitClearKeyDecryptorGStreamer.h"
24
25#if ENABLE(ENCRYPTED_MEDIA) && USE(GSTREAMER)
26
27#include "CDMClearKey.h"
28#include "GStreamerCommon.h"
29#include "GStreamerEMEUtilities.h"
30#include <gcrypt.h>
31#include <gst/base/gstbytereader.h>
32#include <wtf/RunLoop.h>
33
34using namespace WebCore;
35
36#define CLEARKEY_SIZE 16
37
38#define WEBKIT_MEDIA_CK_DECRYPT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_MEDIA_CK_DECRYPT, WebKitMediaClearKeyDecryptPrivate))
39struct _WebKitMediaClearKeyDecryptPrivate {
40 RefPtr<CDMInstanceClearKey> cdmInstance;
41 gcry_cipher_hd_t handle;
42};
43
44static void finalize(GObject*);
45static bool handleKeyResponse(WebKitMediaCommonEncryptionDecrypt* self, RefPtr<CDMInstance>);
46static bool decrypt(WebKitMediaCommonEncryptionDecrypt*, GstBuffer* iv, GstBuffer* keyid, GstBuffer* sample, unsigned subSamplesCount, GstBuffer* subSamples);
47
48GST_DEBUG_CATEGORY_STATIC(webkit_media_clear_key_decrypt_debug_category);
49#define GST_CAT_DEFAULT webkit_media_clear_key_decrypt_debug_category
50
51static GstStaticPadTemplate sinkTemplate = GST_STATIC_PAD_TEMPLATE("sink",
52 GST_PAD_SINK,
53 GST_PAD_ALWAYS,
54 GST_STATIC_CAPS("application/x-cenc, original-media-type=(string)video/x-h264, protection-system=(string)" WEBCORE_GSTREAMER_EME_UTILITIES_CLEARKEY_UUID "; "
55 "application/x-cenc, original-media-type=(string)audio/mpeg, protection-system=(string)" WEBCORE_GSTREAMER_EME_UTILITIES_CLEARKEY_UUID";"
56 "application/x-webm-enc, original-media-type=(string)video/x-vp8;"
57 "application/x-webm-enc, original-media-type=(string)video/x-vp9;"));
58
59static GstStaticPadTemplate srcTemplate = GST_STATIC_PAD_TEMPLATE("src",
60 GST_PAD_SRC,
61 GST_PAD_ALWAYS,
62 GST_STATIC_CAPS("video/x-h264; audio/mpeg; video/x-vp8; video/x-vp9"));
63
64#define webkit_media_clear_key_decrypt_parent_class parent_class
65G_DEFINE_TYPE(WebKitMediaClearKeyDecrypt, webkit_media_clear_key_decrypt, WEBKIT_TYPE_MEDIA_CENC_DECRYPT);
66
67static void webkit_media_clear_key_decrypt_class_init(WebKitMediaClearKeyDecryptClass* klass)
68{
69 GObjectClass* gobjectClass = G_OBJECT_CLASS(klass);
70 gobjectClass->finalize = finalize;
71
72 GstElementClass* elementClass = GST_ELEMENT_CLASS(klass);
73 gst_element_class_add_pad_template(elementClass, gst_static_pad_template_get(&sinkTemplate));
74 gst_element_class_add_pad_template(elementClass, gst_static_pad_template_get(&srcTemplate));
75
76 gst_element_class_set_static_metadata(elementClass,
77 "Decrypt content encrypted using ISOBMFF ClearKey Common Encryption",
78 GST_ELEMENT_FACTORY_KLASS_DECRYPTOR,
79 "Decrypts media that has been encrypted using ISOBMFF ClearKey Common Encryption.",
80 "Philippe Normand <philn@igalia.com>");
81
82 GST_DEBUG_CATEGORY_INIT(webkit_media_clear_key_decrypt_debug_category,
83 "webkitclearkey", 0, "ClearKey decryptor");
84
85 WebKitMediaCommonEncryptionDecryptClass* cencClass = WEBKIT_MEDIA_CENC_DECRYPT_CLASS(klass);
86 cencClass->protectionSystemId = GStreamerEMEUtilities::s_ClearKeyUUID;
87 cencClass->handleKeyResponse = GST_DEBUG_FUNCPTR(handleKeyResponse);
88 cencClass->decrypt = GST_DEBUG_FUNCPTR(decrypt);
89
90 g_type_class_add_private(klass, sizeof(WebKitMediaClearKeyDecryptPrivate));
91}
92
93static void webkit_media_clear_key_decrypt_init(WebKitMediaClearKeyDecrypt* self)
94{
95 WebKitMediaClearKeyDecryptPrivate* priv = WEBKIT_MEDIA_CK_DECRYPT_GET_PRIVATE(self);
96
97 self->priv = priv;
98 new (priv) WebKitMediaClearKeyDecryptPrivate();
99 if (gcry_error_t error = gcry_cipher_open(&(priv->handle), GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE)) {
100 GST_ERROR_OBJECT(self, "Failed to create AES 128 CTR cipher handle: %s", gpg_strerror(error));
101 ASSERT(!error);
102 }
103}
104
105static void finalize(GObject* object)
106{
107 WebKitMediaClearKeyDecrypt* self = WEBKIT_MEDIA_CK_DECRYPT(object);
108 WebKitMediaClearKeyDecryptPrivate* priv = self->priv;
109 gcry_cipher_close(priv->handle);
110 priv->~WebKitMediaClearKeyDecryptPrivate();
111
112 GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object));
113}
114
115static bool handleKeyResponse(WebKitMediaCommonEncryptionDecrypt* self, RefPtr<CDMInstance> cdmInstance)
116{
117 WebKitMediaClearKeyDecryptPrivate* priv = WEBKIT_MEDIA_CK_DECRYPT_GET_PRIVATE(WEBKIT_MEDIA_CK_DECRYPT(self));
118 priv->cdmInstance = downcast<CDMInstanceClearKey>(cdmInstance.get());
119 return priv->cdmInstance;
120}
121
122static bool findAndSetKey(WebKitMediaClearKeyDecryptPrivate* priv, const Ref<SharedBuffer>& keyID)
123{
124 RefPtr<SharedBuffer> keyValue;
125 for (const auto& key : priv->cdmInstance->keys()) {
126 if (*key.keyIDData == keyID) {
127 keyValue = key.keyValueData;
128 break;
129 }
130 }
131
132 if (!keyValue) {
133 GST_ERROR_OBJECT(priv, "failed to find a decryption key");
134 return false;
135 }
136
137 ASSERT(keyValue->size() == CLEARKEY_SIZE);
138 if (gcry_error_t error = gcry_cipher_setkey(priv->handle, keyValue->data(), keyValue->size())) {
139 GST_ERROR_OBJECT(priv, "gcry_cipher_setkey failed: %s", gpg_strerror(error));
140 return false;
141 }
142
143 return true;
144}
145
146static bool decrypt(WebKitMediaCommonEncryptionDecrypt* self, GstBuffer* ivBuffer, GstBuffer* keyIDBuffer, GstBuffer* buffer, unsigned subSampleCount, GstBuffer* subSamplesBuffer)
147{
148 if (!ivBuffer) {
149 GST_ERROR_OBJECT(self, "no IV buffer");
150 return false;
151 }
152
153 auto mappedIVBuffer = WebCore::GstMappedBuffer::create(ivBuffer, GST_MAP_READ);
154 if (!mappedIVBuffer) {
155 GST_ERROR_OBJECT(self, "failed to map IV buffer");
156 return false;
157 }
158
159 uint8_t ctr[CLEARKEY_SIZE];
160 if (mappedIVBuffer->size() == 8) {
161 memset(ctr + 8, 0, 8);
162 memcpy(ctr, mappedIVBuffer->data(), 8);
163 } else {
164 ASSERT(mappedIVBuffer->size() == CLEARKEY_SIZE);
165 memcpy(ctr, mappedIVBuffer->data(), CLEARKEY_SIZE);
166 }
167
168 WebKitMediaClearKeyDecryptPrivate* priv = WEBKIT_MEDIA_CK_DECRYPT_GET_PRIVATE(WEBKIT_MEDIA_CK_DECRYPT(self));
169 gcry_error_t cipherError = gcry_cipher_setctr(priv->handle, ctr, CLEARKEY_SIZE);
170 if (cipherError) {
171 GST_ERROR_OBJECT(self, "gcry_cipher_setctr failed: %s", gpg_strerror(cipherError));
172 return false;
173 }
174
175 if (!buffer) {
176 GST_ERROR_OBJECT(self, "No buffer to decrypt");
177 return false;
178 }
179
180 auto mappedKeyIdBuffer = WebCore::GstMappedBuffer::create(keyIDBuffer, GST_MAP_READ);
181 if (!mappedKeyIdBuffer) {
182 GST_ERROR_OBJECT(self, "Failed to map key id buffer");
183 return false;
184 }
185
186 auto mappedBuffer = WebCore::GstMappedBuffer::create(buffer, GST_MAP_READWRITE);
187 if (!mappedBuffer) {
188 GST_ERROR_OBJECT(self, "Failed to map buffer");
189 return false;
190 }
191
192 findAndSetKey(priv, mappedKeyIdBuffer->createSharedBuffer());
193
194 unsigned position = 0;
195 unsigned sampleIndex = 0;
196
197 if (!subSampleCount) {
198 // Full sample encryption.
199 GST_TRACE_OBJECT(self, "full sample encryption: %zu encrypted bytes", mappedBuffer->size());
200
201 // Check if the buffer is empty.
202 if (mappedBuffer->size()) {
203 cipherError = gcry_cipher_decrypt(priv->handle, mappedBuffer->data(), mappedBuffer->size(), 0, 0);
204 if (cipherError) {
205 GST_ERROR_OBJECT(self, "full sample decryption failed: %s", gpg_strerror(cipherError));
206 return false;
207 }
208 }
209 return true;
210 }
211
212 // Check subSamplesBuffer isn't null.
213 if (!subSamplesBuffer) {
214 GST_ERROR_OBJECT(self, "Error, the subSampleBuffer is null");
215 return false;
216 }
217
218 // Subsample encryption.
219 auto mappedSubSamplesBuffer = WebCore::GstMappedBuffer::create(subSamplesBuffer, GST_MAP_READ);
220 if (!mappedSubSamplesBuffer) {
221 GST_ERROR_OBJECT(self, "Failed to map subsample buffer");
222 return false;
223 }
224
225 GUniquePtr<GstByteReader> reader(gst_byte_reader_new(mappedSubSamplesBuffer->data(), mappedSubSamplesBuffer->size()));
226 GST_DEBUG_OBJECT(self, "position: %d, size: %zu", position, mappedBuffer->size());
227
228 while (position < mappedBuffer->size()) {
229 guint16 nBytesClear = 0;
230 guint32 nBytesEncrypted = 0;
231
232 if (sampleIndex < subSampleCount) {
233 if (!gst_byte_reader_get_uint16_be(reader.get(), &nBytesClear)
234 || !gst_byte_reader_get_uint32_be(reader.get(), &nBytesEncrypted)) {
235 GST_DEBUG_OBJECT(self, "unsupported");
236 return false;
237 }
238 sampleIndex++;
239 } else {
240 nBytesClear = 0;
241 nBytesEncrypted = mappedBuffer->size() - position;
242 }
243
244 GST_TRACE_OBJECT(self, "subsample index %u - %hu bytes clear (todo=%zu)", sampleIndex, nBytesClear, mappedBuffer->size() - position);
245 position += nBytesClear;
246 if (nBytesEncrypted) {
247 GST_TRACE_OBJECT(self, "subsample index %u - %u bytes encrypted (todo=%zu)", sampleIndex, nBytesEncrypted, mappedBuffer->size() - position);
248 cipherError = gcry_cipher_decrypt(priv->handle, mappedBuffer->data() + position, nBytesEncrypted, 0, 0);
249 if (cipherError) {
250 GST_ERROR_OBJECT(self, "sub sample index %u decryption failed: %s", sampleIndex, gpg_strerror(cipherError));
251 return false;
252 }
253 position += nBytesEncrypted;
254 }
255 }
256
257 return true;
258}
259
260#endif // ENABLE(ENCRYPTED_MEDIA) && USE(GSTREAMER)
261