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 | |
34 | using 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)) |
39 | struct _WebKitMediaClearKeyDecryptPrivate { |
40 | RefPtr<CDMInstanceClearKey> cdmInstance; |
41 | gcry_cipher_hd_t handle; |
42 | }; |
43 | |
44 | static void finalize(GObject*); |
45 | static bool handleKeyResponse(WebKitMediaCommonEncryptionDecrypt* self, RefPtr<CDMInstance>); |
46 | static bool decrypt(WebKitMediaCommonEncryptionDecrypt*, GstBuffer* iv, GstBuffer* keyid, GstBuffer* sample, unsigned subSamplesCount, GstBuffer* subSamples); |
47 | |
48 | GST_DEBUG_CATEGORY_STATIC(webkit_media_clear_key_decrypt_debug_category); |
49 | #define GST_CAT_DEFAULT webkit_media_clear_key_decrypt_debug_category |
50 | |
51 | static 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 | |
59 | static 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 |
65 | G_DEFINE_TYPE(WebKitMediaClearKeyDecrypt, webkit_media_clear_key_decrypt, WEBKIT_TYPE_MEDIA_CENC_DECRYPT); |
66 | |
67 | static 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 | |
93 | static 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 | |
105 | static 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 | |
115 | static 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 | |
122 | static 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 | |
146 | static 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 | |