[FFmpeg-devel] [PATCH 2/4] libavformat/mov: add support for 'cens', 'cbc1' and 'cbcs' encryption schemes specified in Common Encryption (CENC) standard
Nachiket Tarate
nachiket.programmer at gmail.com
Fri Jul 16 20:19:30 EEST 2021
correct implementation of 'cenc' encryption scheme to support decryption of partial cipher blocks at the end of subsamples
https://www.iso.org/standard/68042.html
Signed-off-by: Nachiket Tarate <nachiket.programmer at gmail.com>
---
libavformat/isom.h | 2 +
libavformat/mov.c | 246 +++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 242 insertions(+), 6 deletions(-)
diff --git a/libavformat/isom.h b/libavformat/isom.h
index ac1b3f3d56..705813844f 100644
--- a/libavformat/isom.h
+++ b/libavformat/isom.h
@@ -237,6 +237,8 @@ typedef struct MOVStreamContext {
int has_sidx; // If there is an sidx entry for this stream.
struct {
struct AVAESCTR* aes_ctr;
+ struct AVAES *aes_ctx;
+ unsigned int frag_index_entry_base;
unsigned int per_sample_iv_size; // Either 0, 8, or 16.
AVEncryptionInfo *default_encrypted_sample;
MOVEncryptionIndex *encryption_index;
diff --git a/libavformat/mov.c b/libavformat/mov.c
index a37a9cc917..0da1bc606f 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -6577,15 +6577,150 @@ static int mov_read_dfla(MOVContext *c, AVIOContext *pb, MOVAtom atom)
return 0;
}
-static int cenc_decrypt(MOVContext *c, MOVStreamContext *sc, AVEncryptionInfo *sample, uint8_t *input, int size)
+static int cenc_scheme_decrypt(MOVContext *c, MOVStreamContext *sc, AVEncryptionInfo *sample, uint8_t *input, int size)
{
int i, ret;
+ int bytes_of_protected_data;
+ int partially_encrypted_block_size;
+ uint8_t *partially_encrypted_block;
+ uint8_t block[16];
- if (sample->scheme != MKBETAG('c','e','n','c') || sample->crypt_byte_block != 0 || sample->skip_byte_block != 0) {
- av_log(c->fc, AV_LOG_ERROR, "Only the 'cenc' encryption scheme is supported\n");
- return AVERROR_PATCHWELCOME;
+ if (!sc->cenc.aes_ctr) {
+ /* initialize the cipher */
+ sc->cenc.aes_ctr = av_aes_ctr_alloc();
+ if (!sc->cenc.aes_ctr) {
+ return AVERROR(ENOMEM);
+ }
+
+ ret = av_aes_ctr_init(sc->cenc.aes_ctr, c->decryption_key);
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ av_aes_ctr_set_full_iv(sc->cenc.aes_ctr, sample->iv);
+
+ if (!sample->subsample_count) {
+ /* decrypt the whole packet */
+ av_aes_ctr_crypt(sc->cenc.aes_ctr, input, input, size);
+ return 0;
+ }
+
+ partially_encrypted_block_size = 0;
+
+ for (i = 0; i < sample->subsample_count; i++) {
+ if (sample->subsamples[i].bytes_of_clear_data + sample->subsamples[i].bytes_of_protected_data > size) {
+ av_log(c->fc, AV_LOG_ERROR, "subsample size exceeds the packet size left\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ /* skip the clear bytes */
+ input += sample->subsamples[i].bytes_of_clear_data;
+ size -= sample->subsamples[i].bytes_of_clear_data;
+
+ /* decrypt the encrypted bytes */
+
+ if (partially_encrypted_block_size != 0)
+ {
+ memcpy(block, partially_encrypted_block, partially_encrypted_block_size);
+ memcpy(block+partially_encrypted_block_size, input, 16-partially_encrypted_block_size);
+ av_aes_ctr_crypt(sc->cenc.aes_ctr, block, block, 16);
+ memcpy(partially_encrypted_block, block, partially_encrypted_block_size);
+ memcpy(input, block+partially_encrypted_block_size, 16-partially_encrypted_block_size);
+ input += 16-partially_encrypted_block_size;
+ size -= 16-partially_encrypted_block_size;
+ bytes_of_protected_data = sample->subsamples[i].bytes_of_protected_data - (16-partially_encrypted_block_size);
+ } else {
+ bytes_of_protected_data = sample->subsamples[i].bytes_of_protected_data;
+ }
+
+ if (i < sample->subsample_count-1) {
+ int num_of_encrypted_blocks = bytes_of_protected_data/16;
+ partially_encrypted_block_size = bytes_of_protected_data%16;
+ if (partially_encrypted_block_size != 0)
+ partially_encrypted_block = input + 16*num_of_encrypted_blocks;
+ av_aes_ctr_crypt(sc->cenc.aes_ctr, input, input, 16*num_of_encrypted_blocks);
+ } else {
+ av_aes_ctr_crypt(sc->cenc.aes_ctr, input, input, bytes_of_protected_data);
+ }
+
+ input += bytes_of_protected_data;
+ size -= bytes_of_protected_data;
+ }
+
+ if (size > 0) {
+ av_log(c->fc, AV_LOG_ERROR, "leftover packet bytes after subsample processing\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ return 0;
+}
+
+static int cbc1_scheme_decrypt(MOVContext *c, MOVStreamContext *sc, AVEncryptionInfo *sample, uint8_t *input, int size)
+{
+ int i, ret;
+ int num_of_encrypted_blocks;
+ uint8_t iv[16];
+
+ if (!sc->cenc.aes_ctx) {
+ /* initialize the cipher */
+ sc->cenc.aes_ctx = av_aes_alloc();
+ if (!sc->cenc.aes_ctx) {
+ return AVERROR(ENOMEM);
+ }
+
+ ret = av_aes_init(sc->cenc.aes_ctx, c->decryption_key, 16 * 8, 1);
+ if (ret < 0) {
+ return ret;
+ }
}
+ memcpy(iv, sample->iv, 16);
+
+ /* whole-block full sample encryption */
+ if (!sample->subsample_count) {
+ /* decrypt the whole packet */
+ av_aes_crypt(sc->cenc.aes_ctx, input, input, size/16, iv, 1);
+ return 0;
+ }
+
+ for (i = 0; i < sample->subsample_count; i++) {
+ if (sample->subsamples[i].bytes_of_clear_data + sample->subsamples[i].bytes_of_protected_data > size) {
+ av_log(c->fc, AV_LOG_ERROR, "subsample size exceeds the packet size left\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (sample->subsamples[i].bytes_of_protected_data % 16 != 0) {
+ av_log(c->fc, AV_LOG_ERROR, "subsample BytesOfProtectedData is not a multiple of 16\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ /* skip the clear bytes */
+ input += sample->subsamples[i].bytes_of_clear_data;
+ size -= sample->subsamples[i].bytes_of_clear_data;
+
+ /* decrypt the encrypted bytes */
+ num_of_encrypted_blocks = sample->subsamples[i].bytes_of_protected_data/16;
+ if (num_of_encrypted_blocks > 0) {
+ av_aes_crypt(sc->cenc.aes_ctx, input, input, num_of_encrypted_blocks, iv, 1);
+ }
+ input += sample->subsamples[i].bytes_of_protected_data;
+ size -= sample->subsamples[i].bytes_of_protected_data;
+ }
+
+ if (size > 0) {
+ av_log(c->fc, AV_LOG_ERROR, "leftover packet bytes after subsample processing\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ return 0;
+}
+
+static int cens_scheme_decrypt(MOVContext *c, MOVStreamContext *sc, AVEncryptionInfo *sample, uint8_t *input, int size)
+{
+ int i, ret, rem_bytes;
+ uint8_t *data;
+
if (!sc->cenc.aes_ctr) {
/* initialize the cipher */
sc->cenc.aes_ctr = av_aes_ctr_alloc();
@@ -6601,10 +6736,14 @@ static int cenc_decrypt(MOVContext *c, MOVStreamContext *sc, AVEncryptionInfo *s
av_aes_ctr_set_full_iv(sc->cenc.aes_ctr, sample->iv);
+ /* whole-block full sample encryption */
if (!sample->subsample_count) {
/* decrypt the whole packet */
av_aes_ctr_crypt(sc->cenc.aes_ctr, input, input, size);
return 0;
+ } else if (sample->crypt_byte_block == 0 && sample->skip_byte_block == 0) {
+ av_log(c->fc, AV_LOG_ERROR, "pattern encryption is not present in 'cens' scheme\n");
+ return AVERROR_INVALIDDATA;
}
for (i = 0; i < sample->subsample_count; i++) {
@@ -6618,7 +6757,18 @@ static int cenc_decrypt(MOVContext *c, MOVStreamContext *sc, AVEncryptionInfo *s
size -= sample->subsamples[i].bytes_of_clear_data;
/* decrypt the encrypted bytes */
- av_aes_ctr_crypt(sc->cenc.aes_ctr, input, input, sample->subsamples[i].bytes_of_protected_data);
+ data = input;
+ rem_bytes = sample->subsamples[i].bytes_of_protected_data;
+ while (rem_bytes > 0) {
+ if (rem_bytes < 16*sample->crypt_byte_block) {
+ break;
+ }
+ av_aes_ctr_crypt(sc->cenc.aes_ctr, data, data, 16*sample->crypt_byte_block);
+ data += 16*sample->crypt_byte_block;
+ rem_bytes -= 16*sample->crypt_byte_block;
+ data += FFMIN(16*sample->skip_byte_block, rem_bytes);
+ rem_bytes -= FFMIN(16*sample->skip_byte_block, rem_bytes);
+ }
input += sample->subsamples[i].bytes_of_protected_data;
size -= sample->subsamples[i].bytes_of_protected_data;
}
@@ -6631,6 +6781,88 @@ static int cenc_decrypt(MOVContext *c, MOVStreamContext *sc, AVEncryptionInfo *s
return 0;
}
+static int cbcs_scheme_decrypt(MOVContext *c, MOVStreamContext *sc, AVEncryptionInfo *sample, uint8_t *input, int size)
+{
+ int i, ret, rem_bytes;
+ uint8_t iv[16];
+ uint8_t *data;
+
+ if (!sc->cenc.aes_ctx) {
+ /* initialize the cipher */
+ sc->cenc.aes_ctx = av_aes_alloc();
+ if (!sc->cenc.aes_ctx) {
+ return AVERROR(ENOMEM);
+ }
+
+ ret = av_aes_init(sc->cenc.aes_ctx, c->decryption_key, 16 * 8, 1);
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ /* whole-block full sample encryption */
+ if (!sample->subsample_count) {
+ /* decrypt the whole packet */
+ memcpy(iv, sample->iv, 16);
+ av_aes_crypt(sc->cenc.aes_ctx, input, input, size/16, iv, 1);
+ return 0;
+ } else if (sample->crypt_byte_block == 0 && sample->skip_byte_block == 0) {
+ av_log(c->fc, AV_LOG_ERROR, "pattern encryption is not present in 'cbcs' scheme\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ for (i = 0; i < sample->subsample_count; i++) {
+ if (sample->subsamples[i].bytes_of_clear_data + sample->subsamples[i].bytes_of_protected_data > size) {
+ av_log(c->fc, AV_LOG_ERROR, "subsample size exceeds the packet size left\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ /* skip the clear bytes */
+ input += sample->subsamples[i].bytes_of_clear_data;
+ size -= sample->subsamples[i].bytes_of_clear_data;
+
+ /* decrypt the encrypted bytes */
+ memcpy(iv, sample->iv, 16);
+ data = input;
+ rem_bytes = sample->subsamples[i].bytes_of_protected_data;
+ while (rem_bytes > 0) {
+ if (rem_bytes < 16*sample->crypt_byte_block) {
+ break;
+ }
+ av_aes_crypt(sc->cenc.aes_ctx, data, data, sample->crypt_byte_block, iv, 1);
+ data += 16*sample->crypt_byte_block;
+ rem_bytes -= 16*sample->crypt_byte_block;
+ data += FFMIN(16*sample->skip_byte_block, rem_bytes);
+ rem_bytes -= FFMIN(16*sample->skip_byte_block, rem_bytes);
+ }
+ input += sample->subsamples[i].bytes_of_protected_data;
+ size -= sample->subsamples[i].bytes_of_protected_data;
+ }
+
+ if (size > 0) {
+ av_log(c->fc, AV_LOG_ERROR, "leftover packet bytes after subsample processing\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ return 0;
+}
+
+static int cenc_decrypt(MOVContext *c, MOVStreamContext *sc, AVEncryptionInfo *sample, uint8_t *input, int size)
+{
+ if (sample->scheme == MKBETAG('c','e','n','c') && sample->crypt_byte_block == 0 && sample->skip_byte_block == 0) {
+ return cenc_scheme_decrypt(c, sc, sample, input, size);
+ } else if (sample->scheme == MKBETAG('c','b','c','1') && sample->crypt_byte_block == 0 && sample->skip_byte_block == 0) {
+ return cbc1_scheme_decrypt(c, sc, sample, input, size);
+ } else if (sample->scheme == MKBETAG('c','e','n','s')) {
+ return cens_scheme_decrypt(c, sc, sample, input, size);
+ } else if (sample->scheme == MKBETAG('c','b','c','s')) {
+ return cbcs_scheme_decrypt(c, sc, sample, input, size);
+ } else {
+ av_log(c->fc, AV_LOG_ERROR, "invalid encryption scheme\n");
+ return AVERROR_INVALIDDATA;
+ }
+}
+
static int cenc_filter(MOVContext *mov, AVStream* st, MOVStreamContext *sc, AVPacket *pkt, int current_index)
{
MOVFragmentStreamInfo *frag_stream_info;
@@ -6645,7 +6877,9 @@ static int cenc_filter(MOVContext *mov, AVStream* st, MOVStreamContext *sc, AVPa
// Note this only supports encryption info in the first sample descriptor.
if (mov->fragment.stsd_id == 1) {
if (frag_stream_info->encryption_index) {
- encrypted_index = current_index - frag_stream_info->index_entry;
+ if (current_index == 0 && frag_stream_info->index_entry > 0)
+ sc->cenc.frag_index_entry_base = frag_stream_info->index_entry;
+ encrypted_index = current_index - (frag_stream_info->index_entry - sc->cenc.frag_index_entry_base);
encryption_index = frag_stream_info->encryption_index;
} else {
encryption_index = sc->cenc.encryption_index;
--
2.17.1
More information about the ffmpeg-devel
mailing list