blob: 9a48f2e6f512ce8047ff39fdac221816f22c3e40
1 | /* |
2 | * Decryption protocol handler |
3 | * Copyright (c) 2011 Martin Storsjo |
4 | * |
5 | * This file is part of FFmpeg. |
6 | * |
7 | * FFmpeg is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Lesser General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2.1 of the License, or (at your option) any later version. |
11 | * |
12 | * FFmpeg is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Lesser General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Lesser General Public |
18 | * License along with FFmpeg; if not, write to the Free Software |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
20 | */ |
21 | |
22 | #include "avformat.h" |
23 | #include "libavutil/aes.h" |
24 | #include "libavutil/avstring.h" |
25 | #include "libavutil/opt.h" |
26 | #include "internal.h" |
27 | #include "url.h" |
28 | |
29 | // encourage reads of 4096 bytes - 1 block is always retained. |
30 | #define MAX_BUFFER_BLOCKS 257 |
31 | #define BLOCKSIZE 16 |
32 | |
33 | typedef struct CryptoContext { |
34 | const AVClass *class; |
35 | URLContext *hd; |
36 | uint8_t inbuffer [BLOCKSIZE*MAX_BUFFER_BLOCKS], |
37 | outbuffer[BLOCKSIZE*MAX_BUFFER_BLOCKS]; |
38 | uint8_t *outptr; |
39 | int indata, indata_used, outdata; |
40 | int64_t position; // position in file - used in seek |
41 | int flags; |
42 | int eof; |
43 | uint8_t *key; |
44 | int keylen; |
45 | uint8_t *iv; |
46 | int ivlen; |
47 | uint8_t *decrypt_key; |
48 | int decrypt_keylen; |
49 | uint8_t *decrypt_iv; |
50 | int decrypt_ivlen; |
51 | uint8_t *encrypt_key; |
52 | int encrypt_keylen; |
53 | uint8_t *encrypt_iv; |
54 | int encrypt_ivlen; |
55 | struct AVAES *aes_decrypt; |
56 | struct AVAES *aes_encrypt; |
57 | uint8_t *write_buf; |
58 | unsigned int write_buf_size; |
59 | uint8_t pad[BLOCKSIZE]; |
60 | int pad_len; |
61 | } CryptoContext; |
62 | |
63 | #define OFFSET(x) offsetof(CryptoContext, x) |
64 | #define D AV_OPT_FLAG_DECODING_PARAM |
65 | #define E AV_OPT_FLAG_ENCODING_PARAM |
66 | static const AVOption options[] = { |
67 | {"key", "AES encryption/decryption key", OFFSET(key), AV_OPT_TYPE_BINARY, .flags = D|E }, |
68 | {"iv", "AES encryption/decryption initialization vector", OFFSET(iv), AV_OPT_TYPE_BINARY, .flags = D|E }, |
69 | {"decryption_key", "AES decryption key", OFFSET(decrypt_key), AV_OPT_TYPE_BINARY, .flags = D }, |
70 | {"decryption_iv", "AES decryption initialization vector", OFFSET(decrypt_iv), AV_OPT_TYPE_BINARY, .flags = D }, |
71 | {"encryption_key", "AES encryption key", OFFSET(encrypt_key), AV_OPT_TYPE_BINARY, .flags = E }, |
72 | {"encryption_iv", "AES encryption initialization vector", OFFSET(encrypt_iv), AV_OPT_TYPE_BINARY, .flags = E }, |
73 | { NULL } |
74 | }; |
75 | |
76 | static const AVClass crypto_class = { |
77 | .class_name = "crypto", |
78 | .item_name = av_default_item_name, |
79 | .option = options, |
80 | .version = LIBAVUTIL_VERSION_INT, |
81 | }; |
82 | |
83 | static int set_aes_arg(URLContext *h, uint8_t **buf, int *buf_len, |
84 | uint8_t *default_buf, int default_buf_len, |
85 | const char *desc) |
86 | { |
87 | if (!*buf_len) { |
88 | if (!default_buf_len) { |
89 | av_log(h, AV_LOG_ERROR, "%s not set\n", desc); |
90 | return AVERROR(EINVAL); |
91 | } else if (default_buf_len != BLOCKSIZE) { |
92 | av_log(h, AV_LOG_ERROR, |
93 | "invalid %s size (%d bytes, block size is %d)\n", |
94 | desc, default_buf_len, BLOCKSIZE); |
95 | return AVERROR(EINVAL); |
96 | } |
97 | *buf = av_memdup(default_buf, default_buf_len); |
98 | if (!*buf) |
99 | return AVERROR(ENOMEM); |
100 | *buf_len = default_buf_len; |
101 | } else if (*buf_len != BLOCKSIZE) { |
102 | av_log(h, AV_LOG_ERROR, |
103 | "invalid %s size (%d bytes, block size is %d)\n", |
104 | desc, *buf_len, BLOCKSIZE); |
105 | return AVERROR(EINVAL); |
106 | } |
107 | return 0; |
108 | } |
109 | |
110 | static int crypto_open2(URLContext *h, const char *uri, int flags, AVDictionary **options) |
111 | { |
112 | const char *nested_url; |
113 | int ret = 0; |
114 | CryptoContext *c = h->priv_data; |
115 | c->flags = flags; |
116 | |
117 | if (!av_strstart(uri, "crypto+", &nested_url) && |
118 | !av_strstart(uri, "crypto:", &nested_url)) { |
119 | av_log(h, AV_LOG_ERROR, "Unsupported url %s\n", uri); |
120 | ret = AVERROR(EINVAL); |
121 | goto err; |
122 | } |
123 | |
124 | if (flags & AVIO_FLAG_READ) { |
125 | if ((ret = set_aes_arg(h, &c->decrypt_key, &c->decrypt_keylen, |
126 | c->key, c->keylen, "decryption key")) < 0) |
127 | goto err; |
128 | if ((ret = set_aes_arg(h, &c->decrypt_iv, &c->decrypt_ivlen, |
129 | c->iv, c->ivlen, "decryption IV")) < 0) |
130 | goto err; |
131 | } |
132 | |
133 | if (flags & AVIO_FLAG_WRITE) { |
134 | if ((ret = set_aes_arg(h, &c->encrypt_key, &c->encrypt_keylen, |
135 | c->key, c->keylen, "encryption key")) < 0) |
136 | if (ret < 0) |
137 | goto err; |
138 | if ((ret = set_aes_arg(h, &c->encrypt_iv, &c->encrypt_ivlen, |
139 | c->iv, c->ivlen, "encryption IV")) < 0) |
140 | goto err; |
141 | } |
142 | |
143 | if ((ret = ffurl_open_whitelist(&c->hd, nested_url, flags, |
144 | &h->interrupt_callback, options, |
145 | h->protocol_whitelist, h->protocol_blacklist, h)) < 0) { |
146 | av_log(h, AV_LOG_ERROR, "Unable to open resource: %s\n", nested_url); |
147 | goto err; |
148 | } |
149 | |
150 | if (flags & AVIO_FLAG_READ) { |
151 | c->aes_decrypt = av_aes_alloc(); |
152 | if (!c->aes_decrypt) { |
153 | ret = AVERROR(ENOMEM); |
154 | goto err; |
155 | } |
156 | ret = av_aes_init(c->aes_decrypt, c->decrypt_key, BLOCKSIZE * 8, 1); |
157 | if (ret < 0) |
158 | goto err; |
159 | |
160 | // pass back information about the context we openned |
161 | if (c->hd->is_streamed) |
162 | h->is_streamed = c->hd->is_streamed; |
163 | } |
164 | |
165 | if (flags & AVIO_FLAG_WRITE) { |
166 | c->aes_encrypt = av_aes_alloc(); |
167 | if (!c->aes_encrypt) { |
168 | ret = AVERROR(ENOMEM); |
169 | goto err; |
170 | } |
171 | ret = av_aes_init(c->aes_encrypt, c->encrypt_key, BLOCKSIZE * 8, 0); |
172 | if (ret < 0) |
173 | goto err; |
174 | // for write, we must be streamed |
175 | // - linear write only for crytpo aes-128-cbc |
176 | h->is_streamed = 1; |
177 | } |
178 | |
179 | err: |
180 | return ret; |
181 | } |
182 | |
183 | static int crypto_read(URLContext *h, uint8_t *buf, int size) |
184 | { |
185 | CryptoContext *c = h->priv_data; |
186 | int blocks; |
187 | retry: |
188 | if (c->outdata > 0) { |
189 | size = FFMIN(size, c->outdata); |
190 | memcpy(buf, c->outptr, size); |
191 | c->outptr += size; |
192 | c->outdata -= size; |
193 | c->position = c->position + size; |
194 | return size; |
195 | } |
196 | // We avoid using the last block until we've found EOF, |
197 | // since we'll remove PKCS7 padding at the end. So make |
198 | // sure we've got at least 2 blocks, so we can decrypt |
199 | // at least one. |
200 | while (c->indata - c->indata_used < 2*BLOCKSIZE) { |
201 | int n = ffurl_read(c->hd, c->inbuffer + c->indata, |
202 | sizeof(c->inbuffer) - c->indata); |
203 | if (n <= 0) { |
204 | c->eof = 1; |
205 | break; |
206 | } |
207 | c->indata += n; |
208 | } |
209 | blocks = (c->indata - c->indata_used) / BLOCKSIZE; |
210 | if (!blocks) |
211 | return AVERROR_EOF; |
212 | if (!c->eof) |
213 | blocks--; |
214 | av_aes_crypt(c->aes_decrypt, c->outbuffer, c->inbuffer + c->indata_used, |
215 | blocks, c->decrypt_iv, 1); |
216 | c->outdata = BLOCKSIZE * blocks; |
217 | c->outptr = c->outbuffer; |
218 | c->indata_used += BLOCKSIZE * blocks; |
219 | if (c->indata_used >= sizeof(c->inbuffer)/2) { |
220 | memmove(c->inbuffer, c->inbuffer + c->indata_used, |
221 | c->indata - c->indata_used); |
222 | c->indata -= c->indata_used; |
223 | c->indata_used = 0; |
224 | } |
225 | if (c->eof) { |
226 | // Remove PKCS7 padding at the end |
227 | int padding = c->outbuffer[c->outdata - 1]; |
228 | c->outdata -= padding; |
229 | } |
230 | goto retry; |
231 | } |
232 | |
233 | static int64_t crypto_seek(URLContext *h, int64_t pos, int whence) |
234 | { |
235 | CryptoContext *c = h->priv_data; |
236 | int64_t block; |
237 | int64_t newpos; |
238 | |
239 | if (c->flags & AVIO_FLAG_WRITE) { |
240 | av_log(h, AV_LOG_ERROR, |
241 | "Crypto: seek not supported for write\r\n"); |
242 | /* seems the most appropriate error to return */ |
243 | return AVERROR(ESPIPE); |
244 | } |
245 | |
246 | // reset eof, else we won't read it correctly if we already hit eof. |
247 | c->eof = 0; |
248 | |
249 | switch (whence) { |
250 | case SEEK_SET: |
251 | break; |
252 | case SEEK_CUR: |
253 | pos = pos + c->position; |
254 | break; |
255 | case SEEK_END: { |
256 | int64_t newpos = ffurl_seek( c->hd, pos, AVSEEK_SIZE ); |
257 | if (newpos < 0) { |
258 | av_log(h, AV_LOG_ERROR, |
259 | "Crypto: seek_end - can't get file size (pos=%lld)\r\n", (long long int)pos); |
260 | return newpos; |
261 | } |
262 | pos = newpos - pos; |
263 | } |
264 | break; |
265 | case AVSEEK_SIZE: { |
266 | int64_t newpos = ffurl_seek( c->hd, pos, AVSEEK_SIZE ); |
267 | return newpos; |
268 | } |
269 | break; |
270 | default: |
271 | av_log(h, AV_LOG_ERROR, |
272 | "Crypto: no support for seek where 'whence' is %d\r\n", whence); |
273 | return AVERROR(EINVAL); |
274 | } |
275 | |
276 | c->outdata = 0; |
277 | c->indata = 0; |
278 | c->indata_used = 0; |
279 | c->outptr = c->outbuffer; |
280 | |
281 | // identify the block containing the IV for the |
282 | // next block we will decrypt |
283 | block = pos/BLOCKSIZE; |
284 | if (block == 0) { |
285 | // restore the iv to the seed one - this is the iv for the FIRST block |
286 | memcpy( c->decrypt_iv, c->iv, c->ivlen ); |
287 | c->position = 0; |
288 | } else { |
289 | // else, go back one block - we will get av_cyrpt to read this block |
290 | // which it will then store use as the iv. |
291 | // note that the DECRYPTED result will not be correct, |
292 | // but will be discarded |
293 | block--; |
294 | c->position = (block * BLOCKSIZE); |
295 | } |
296 | |
297 | newpos = ffurl_seek( c->hd, c->position, SEEK_SET ); |
298 | if (newpos < 0) { |
299 | av_log(h, AV_LOG_ERROR, |
300 | "Crypto: nested protocol no support for seek or seek failed\n"); |
301 | return newpos; |
302 | } |
303 | |
304 | // read and discard from here up to required position |
305 | // (which will set the iv correctly to it). |
306 | if (pos - c->position) { |
307 | uint8_t buff[BLOCKSIZE*2]; // maximum size of pos-c->position |
308 | int len = pos - c->position; |
309 | int res; |
310 | |
311 | while (len > 0) { |
312 | // note: this may not return all the bytes first time |
313 | res = crypto_read(h, buff, len); |
314 | if (res < 0) |
315 | break; |
316 | len -= res; |
317 | } |
318 | |
319 | // if we did not get all the bytes |
320 | if (len != 0) { |
321 | char errbuf[100] = "unknown error"; |
322 | av_strerror(res, errbuf, sizeof(errbuf)); |
323 | av_log(h, AV_LOG_ERROR, |
324 | "Crypto: discard read did not get all the bytes (%d remain) - read returned (%d)-%s\n", |
325 | len, res, errbuf); |
326 | return AVERROR(EINVAL); |
327 | } |
328 | } |
329 | |
330 | return c->position; |
331 | } |
332 | |
333 | static int crypto_write(URLContext *h, const unsigned char *buf, int size) |
334 | { |
335 | CryptoContext *c = h->priv_data; |
336 | int total_size, blocks, pad_len, out_size; |
337 | int ret = 0; |
338 | |
339 | total_size = size + c->pad_len; |
340 | pad_len = total_size % BLOCKSIZE; |
341 | out_size = total_size - pad_len; |
342 | blocks = out_size / BLOCKSIZE; |
343 | |
344 | if (out_size) { |
345 | av_fast_malloc(&c->write_buf, &c->write_buf_size, out_size); |
346 | |
347 | if (!c->write_buf) |
348 | return AVERROR(ENOMEM); |
349 | |
350 | if (c->pad_len) { |
351 | memcpy(&c->pad[c->pad_len], buf, BLOCKSIZE - c->pad_len); |
352 | av_aes_crypt(c->aes_encrypt, c->write_buf, c->pad, 1, c->encrypt_iv, 0); |
353 | blocks--; |
354 | } |
355 | |
356 | av_aes_crypt(c->aes_encrypt, |
357 | &c->write_buf[c->pad_len ? BLOCKSIZE : 0], |
358 | &buf[c->pad_len ? BLOCKSIZE - c->pad_len : 0], |
359 | blocks, c->encrypt_iv, 0); |
360 | |
361 | ret = ffurl_write(c->hd, c->write_buf, out_size); |
362 | if (ret < 0) |
363 | return ret; |
364 | |
365 | memcpy(c->pad, &buf[size - pad_len], pad_len); |
366 | } else |
367 | memcpy(&c->pad[c->pad_len], buf, size); |
368 | |
369 | c->pad_len = pad_len; |
370 | |
371 | return size; |
372 | } |
373 | |
374 | static int crypto_close(URLContext *h) |
375 | { |
376 | CryptoContext *c = h->priv_data; |
377 | int ret = 0; |
378 | |
379 | if (c->aes_encrypt) { |
380 | uint8_t out_buf[BLOCKSIZE]; |
381 | int pad = BLOCKSIZE - c->pad_len; |
382 | |
383 | memset(&c->pad[c->pad_len], pad, pad); |
384 | av_aes_crypt(c->aes_encrypt, out_buf, c->pad, 1, c->encrypt_iv, 0); |
385 | ret = ffurl_write(c->hd, out_buf, BLOCKSIZE); |
386 | } |
387 | |
388 | if (c->hd) |
389 | ffurl_close(c->hd); |
390 | av_freep(&c->aes_decrypt); |
391 | av_freep(&c->aes_encrypt); |
392 | av_freep(&c->write_buf); |
393 | return ret; |
394 | } |
395 | |
396 | const URLProtocol ff_crypto_protocol = { |
397 | .name = "crypto", |
398 | .url_open2 = crypto_open2, |
399 | .url_seek = crypto_seek, |
400 | .url_read = crypto_read, |
401 | .url_write = crypto_write, |
402 | .url_close = crypto_close, |
403 | .priv_data_size = sizeof(CryptoContext), |
404 | .priv_data_class = &crypto_class, |
405 | .flags = URL_PROTOCOL_FLAG_NESTED_SCHEME, |
406 | }; |
407 |