avformat/crypto: add seeking support on read

cyrpto allows reading of data which has been aes-128-cbc encrypted given a key and an iv. But it did not handle filetypes which require seeking... e.g. it failed on an encrypted .mp4 file.

example:
take 25.mp4 created with:
ffmpeg -f lavfi -i sine=frequency=1000:beep_factor=2:r=48000:duration=720.0 -f lavfi -i testsrc=duration=720.0:rate=25 -vcodec libx264 -cmp 22 -timecode 10:00:00:00 -r 25 -y out\25.mp4

encrypt with:
openssl enc -aes-128-cbc -K 12345678901234567890123456789012 -iv 12345678901234567890123456789012 -in 25.mp4 -out 25.enc
then to transcode in ffmpeg:
ffmpeg -key 12345678901234567890123456789012 -iv 12345678901234567890123456789012 -i crypto:25.enc -vcodec mpeg4 -r 25 -y 25dec.mp4

prior to this modification, the transcode would fail.

Note also:  crypto previously maked both reads and writes as streamed, which caused the whole file
to be read before the transcode started.  Now, for read only, if the underlying layer is not marked as streamed,
then crypto is not.  This should enable efficient reading of encrypted containers which require seeking.

Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
This commit is contained in:
Simon Hailes 2016-08-30 09:40:14 +01:00 committed by Michael Niedermayer
parent a2fcacc880
commit a13a81a0dc

View File

@ -37,6 +37,8 @@ typedef struct CryptoContext {
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;
@ -110,6 +112,7 @@ static int crypto_open2(URLContext *h, const char *uri, int flags, AVDictionary
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)) {
@ -118,6 +121,8 @@ static int crypto_open2(URLContext *h, const char *uri, int flags, AVDictionary
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)
@ -153,6 +158,10 @@ static int crypto_open2(URLContext *h, const char *uri, int flags, AVDictionary
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) {
@ -164,12 +173,13 @@ static int crypto_open2(URLContext *h, const char *uri, int flags, AVDictionary
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;
h->is_streamed = 1;
err:
return ret;
}
@ -184,6 +194,7 @@ retry:
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,
@ -223,6 +234,106 @@ retry:
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;
@ -288,6 +399,7 @@ static int crypto_close(URLContext *h)
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,