| 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 | |