409 lines · 12.7 KB · C
RawDownload
/* * Decryption protocol handler * Copyright (c) 2011 Martin Storsjo * * This file is part of FFmpeg. * * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * FFmpeg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "avformat.h"#include "libavutil/aes.h"#include "libavutil/avstring.h"#include "libavutil/opt.h"#include "internal.h"#include "url.h" // encourage reads of 4096 bytes - 1 block is always retained.#define MAX_BUFFER_BLOCKS 257#define BLOCKSIZE 16 typedef struct CryptoContext {    const AVClass *class;    URLContext *hd;    uint8_t inbuffer [BLOCKSIZE*MAX_BUFFER_BLOCKS],            outbuffer[BLOCKSIZE*MAX_BUFFER_BLOCKS];    uint8_t *outptr;    int indata, indata_used, outdata;    int64_t position;  // position in file - used in seek    int flags;    int eof;    uint8_t *key;    int keylen;    uint8_t *iv;    int ivlen;    uint8_t *decrypt_key;    int decrypt_keylen;    uint8_t *decrypt_iv;    int decrypt_ivlen;    uint8_t *encrypt_key;    int encrypt_keylen;    uint8_t *encrypt_iv;    int encrypt_ivlen;    struct AVAES *aes_decrypt;    struct AVAES *aes_encrypt;     uint8_t pad[BLOCKSIZE];    int pad_len; } CryptoContext; #define OFFSET(x) offsetof(CryptoContext, x)#define D AV_OPT_FLAG_DECODING_PARAM#define E AV_OPT_FLAG_ENCODING_PARAMstatic const AVOption options[] = {    {"key", "AES encryption/decryption key",                   OFFSET(key),         AV_OPT_TYPE_BINARY, .flags = D|E },    {"iv",  "AES encryption/decryption initialization vector", OFFSET(iv),          AV_OPT_TYPE_BINARY, .flags = D|E },    {"decryption_key", "AES decryption key",                   OFFSET(decrypt_key), AV_OPT_TYPE_BINARY, .flags = D },    {"decryption_iv",  "AES decryption initialization vector", OFFSET(decrypt_iv),  AV_OPT_TYPE_BINARY, .flags = D },    {"encryption_key", "AES encryption key",                   OFFSET(encrypt_key), AV_OPT_TYPE_BINARY, .flags = E },    {"encryption_iv",  "AES encryption initialization vector", OFFSET(encrypt_iv),  AV_OPT_TYPE_BINARY, .flags = E },    { NULL }}; static const AVClass crypto_class = {    .class_name     = "crypto",    .item_name      = av_default_item_name,    .option         = options,    .version        = LIBAVUTIL_VERSION_INT,}; static int set_aes_arg(CryptoContext *c, uint8_t **buf, int *buf_len,                       uint8_t *default_buf, int default_buf_len,                       const char *desc){    if (!*buf_len) {        if (!default_buf_len) {            av_log(c, AV_LOG_ERROR, "%s not set\n", desc);            return AVERROR(EINVAL);        } else if (default_buf_len != BLOCKSIZE) {            av_log(c, AV_LOG_ERROR,                   "invalid %s size (%d bytes, block size is %d)\n",                   desc, default_buf_len, BLOCKSIZE);            return AVERROR(EINVAL);        }        *buf = av_memdup(default_buf, default_buf_len);        if (!*buf)            return AVERROR(ENOMEM);        *buf_len = default_buf_len;    } else if (*buf_len != BLOCKSIZE) {        av_log(c, AV_LOG_ERROR,               "invalid %s size (%d bytes, block size is %d)\n",               desc, *buf_len, BLOCKSIZE);        return AVERROR(EINVAL);    }    return 0;} static int crypto_open2(URLContext *h, const char *uri, int flags, AVDictionary **options){    const char *nested_url;    int ret = 0;    CryptoContext *c = h->priv_data;    c->flags = flags;     if (!av_strstart(uri, "crypto+", &nested_url) &&        !av_strstart(uri, "crypto:", &nested_url)) {        av_log(h, AV_LOG_ERROR, "Unsupported url %s\n", uri);        ret = AVERROR(EINVAL);        goto err;    }     c->position = 0;     if (flags & AVIO_FLAG_READ) {        if ((ret = set_aes_arg(c, &c->decrypt_key, &c->decrypt_keylen,                               c->key, c->keylen, "decryption key")) < 0)            goto err;        if ((ret = set_aes_arg(c, &c->decrypt_iv, &c->decrypt_ivlen,                               c->iv, c->ivlen, "decryption IV")) < 0)            goto err;    }     if (flags & AVIO_FLAG_WRITE) {        if ((ret = set_aes_arg(c, &c->encrypt_key, &c->encrypt_keylen,                               c->key, c->keylen, "encryption key")) < 0)        if (ret < 0)            goto err;        if ((ret = set_aes_arg(c, &c->encrypt_iv, &c->encrypt_ivlen,                               c->iv, c->ivlen, "encryption IV")) < 0)            goto err;    }     if ((ret = ffurl_open_whitelist(&c->hd, nested_url, flags,                                    &h->interrupt_callback, options,                                    h->protocol_whitelist, h->protocol_blacklist, h)) < 0) {        av_log(h, AV_LOG_ERROR, "Unable to open resource: %s\n", nested_url);        goto err;    }     if (flags & AVIO_FLAG_READ) {        c->aes_decrypt = av_aes_alloc();        if (!c->aes_decrypt) {            ret = AVERROR(ENOMEM);            goto err;        }        ret = av_aes_init(c->aes_decrypt, c->decrypt_key, BLOCKSIZE*8, 1);        if (ret < 0)            goto err;         // pass back information about the context we openned        if (c->hd->is_streamed)            h->is_streamed = c->hd->is_streamed;    }     if (flags & AVIO_FLAG_WRITE) {        c->aes_encrypt = av_aes_alloc();        if (!c->aes_encrypt) {            ret = AVERROR(ENOMEM);            goto err;        }        ret = av_aes_init(c->aes_encrypt, c->encrypt_key, BLOCKSIZE*8, 0);        if (ret < 0)            goto err;        // for write, we must be streamed        // - linear write only for crytpo aes-128-cbc        h->is_streamed = 1;    }     c->pad_len = 0; err:    return ret;} static int crypto_read(URLContext *h, uint8_t *buf, int size){    CryptoContext *c = h->priv_data;    int blocks;retry:    if (c->outdata > 0) {        size = FFMIN(size, c->outdata);        memcpy(buf, c->outptr, size);        c->outptr  += size;        c->outdata -= size;        c->position = c->position + size;        return size;    }    // We avoid using the last block until we've found EOF,    // since we'll remove PKCS7 padding at the end. So make    // sure we've got at least 2 blocks, so we can decrypt    // at least one.    while (c->indata - c->indata_used < 2*BLOCKSIZE) {        int n = ffurl_read(c->hd, c->inbuffer + c->indata,                           sizeof(c->inbuffer) - c->indata);        if (n <= 0) {            c->eof = 1;            break;        }        c->indata += n;    }    blocks = (c->indata - c->indata_used) / BLOCKSIZE;    if (!blocks)        return AVERROR_EOF;    if (!c->eof)        blocks--;    av_aes_crypt(c->aes_decrypt, c->outbuffer, c->inbuffer + c->indata_used,                 blocks, c->decrypt_iv, 1);    c->outdata      = BLOCKSIZE * blocks;    c->outptr       = c->outbuffer;    c->indata_used += BLOCKSIZE * blocks;    if (c->indata_used >= sizeof(c->inbuffer)/2) {        memmove(c->inbuffer, c->inbuffer + c->indata_used,                c->indata - c->indata_used);        c->indata     -= c->indata_used;        c->indata_used = 0;    }    if (c->eof) {        // Remove PKCS7 padding at the end        int padding = c->outbuffer[c->outdata - 1];        c->outdata -= padding;    }    goto retry;} static int64_t crypto_seek(URLContext *h, int64_t pos, int whence){    CryptoContext *c = h->priv_data;    int64_t block;    int64_t newpos;     if (c->flags & AVIO_FLAG_WRITE) {        av_log(h, AV_LOG_ERROR,            "Crypto: seek not supported for write\r\n");        /* seems the most appropriate error to return */        return AVERROR(ESPIPE);    }     // reset eof, else we won't read it correctly if we already hit eof.    c->eof = 0;     switch (whence) {    case SEEK_SET:        break;    case SEEK_CUR:        pos = pos + c->position;        break;    case SEEK_END: {        int64_t newpos = ffurl_seek( c->hd, pos, AVSEEK_SIZE );        if (newpos < 0) {            av_log(h, AV_LOG_ERROR,                "Crypto: seek_end - can't get file size (pos=%lld)\r\n", (long long int)pos);            return newpos;        }        pos = newpos - pos;        }        break;    case AVSEEK_SIZE: {        int64_t newpos = ffurl_seek( c->hd, pos, AVSEEK_SIZE );        return newpos;        }        break;    default:        av_log(h, AV_LOG_ERROR,            "Crypto: no support for seek where 'whence' is %d\r\n", whence);        return AVERROR(EINVAL);    }     c->outdata = 0;    c->indata = 0;    c->indata_used = 0;    c->outptr = c->outbuffer;     // identify the block containing the IV for the    // next block we will decrypt    block = pos/BLOCKSIZE;    if (block == 0) {        // restore the iv to the seed one - this is the iv for the FIRST block        memcpy( c->decrypt_iv, c->iv, c->ivlen );        c->position = 0;    } else {        // else, go back one block - we will get av_cyrpt to read this block        // which it will then store use as the iv.        // note that the DECRYPTED result will not be correct,        // but will be discarded        block--;        c->position = (block * BLOCKSIZE);    }     newpos = ffurl_seek( c->hd, c->position, SEEK_SET );    if (newpos < 0) {        av_log(h, AV_LOG_ERROR,            "Crypto: nested protocol no support for seek or seek failed\n");        return newpos;    }     // read and discard from here up to required position    // (which will set the iv correctly to it).    if (pos - c->position) {        uint8_t buff[BLOCKSIZE*2]; // maximum size of pos-c->position        int len = pos - c->position;        int res;         while (len > 0) {            // note: this may not return all the bytes first time            res = crypto_read(h, buff, len);            if (res < 0)                break;            len -= res;        }         // if we did not get all the bytes        if (len != 0) {            char errbuf[100] = "unknown error";            av_strerror(res, errbuf, sizeof(errbuf));            av_log(h, AV_LOG_ERROR,                "Crypto: discard read did not get all the bytes (%d remain) - read returned (%d)-%s\n",                len, res, errbuf);            return AVERROR(EINVAL);        }    }     return c->position;} static int crypto_write(URLContext *h, const unsigned char *buf, int size){    CryptoContext *c = h->priv_data;    int total_size, blocks, pad_len, out_size;    uint8_t *out_buf;    int ret = 0;     total_size = size + c->pad_len;    pad_len = total_size % BLOCKSIZE;    out_size = total_size - pad_len;    blocks = out_size / BLOCKSIZE;     if (out_size) {        out_buf = av_malloc(out_size);        if (!out_buf)            return AVERROR(ENOMEM);         if (c->pad_len) {            memcpy(&c->pad[c->pad_len], buf, BLOCKSIZE - c->pad_len);            av_aes_crypt(c->aes_encrypt, out_buf, c->pad, 1, c->encrypt_iv, 0);            blocks--;        }         av_aes_crypt(c->aes_encrypt, &out_buf[c->pad_len ? BLOCKSIZE : 0],                             &buf[c->pad_len ? BLOCKSIZE - c->pad_len: 0],                             blocks, c->encrypt_iv, 0);         ret = ffurl_write(c->hd, out_buf, out_size);        av_free(out_buf);        if (ret < 0)            return ret;         memcpy(c->pad, &buf[size - pad_len], pad_len);    } else        memcpy(&c->pad[c->pad_len], buf, size);     c->pad_len = pad_len;     return size;} static int crypto_close(URLContext *h){    CryptoContext *c = h->priv_data;    uint8_t out_buf[BLOCKSIZE];    int ret, pad;     if (c->aes_encrypt) {        pad = BLOCKSIZE - c->pad_len;        memset(&c->pad[c->pad_len], pad, pad);        av_aes_crypt(c->aes_encrypt, out_buf, c->pad, 1, c->encrypt_iv, 0);        if ((ret =  ffurl_write(c->hd, out_buf, BLOCKSIZE)) < 0)            return ret;    }     if (c->hd)        ffurl_close(c->hd);    av_freep(&c->aes_decrypt);    av_freep(&c->aes_encrypt);    return 0;} const URLProtocol ff_crypto_protocol = {    .name            = "crypto",    .url_open2       = crypto_open2,    .url_seek        = crypto_seek,    .url_read        = crypto_read,    .url_write       = crypto_write,    .url_close       = crypto_close,    .priv_data_size  = sizeof(CryptoContext),    .priv_data_class = &crypto_class,    .flags           = URL_PROTOCOL_FLAG_NESTED_SCHEME,};

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t