From patchwork Sun Aug 15 18:26:26 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nachiket Tarate X-Patchwork-Id: 29544 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6602:2a4a:0:0:0:0 with SMTP id k10csp1402828iov; Sun, 15 Aug 2021 11:27:09 -0700 (PDT) X-Google-Smtp-Source: ABdhPJwqPVioiob0QyLfzYeW27X1ZdPS0tUHQ0ut/KBT5qxrfBC8RwQRBx7fU3hTBJxE+JDp6g32 X-Received: by 2002:a17:906:31cf:: with SMTP id f15mr12753708ejf.272.1629052028939; Sun, 15 Aug 2021 11:27:08 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1629052028; cv=none; d=google.com; s=arc-20160816; b=ql/n3+840hbV0lMSH1IcP1OvJk0ZFHv8k9UX5utCB5nZ4CNLpDaGCB5lVBckadobsg 6X3jqBeldOTHcYLdvFq22GHxFAMU6RjTzJ0mam0FwNPdMu/VdShiG640W+CNdqqAbCM2 gAvaK/Ofy0OAkc6HqapSLLoZSl0pa8SNAqyAlkWN0sMTvoaPtrhPrL+1QQo8S4bQMQyH sJcjF/9SFmtf+UntigW9Zo8JA+6PyBQsglIKLdhWZhvyYfsvg40xp+qCfrRY3z6t5PDa o7QLOOJpX2V6EKRPsNjLFen4aa9QxXNP7rbwAFaHwg8/ruUlWHfhnciEVNFrP0aV4uX7 RYLg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:mime-version:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:message-id:date:to:from:dkim-signature :delivered-to; bh=VF0oYrdES9B9C/+dFniWlB+BNX1FOYlkS0h9r1VhZag=; b=tzE6Mxfomvm4OcuBCNKvOsdo2PqI9i97nMkFl2TkO3ODxQ4pVYbtk2f6CMisc0THy2 5ZnpZQpwxSxtvZ21DHQWl3kI4dG/w9jjTvJSxGTg4oJNygADUXIsSfXqhB09AltMShnS 5vbfgi3COXq4AOhkdb+nYQ+yEzktHFWcoCVz5YC9T4HpPSg5+zCmci05Kan/RuZbCzwv wyA/EddE6/sn3H/FC9TIaEpAv2kKuuv0cGRE3bonBCpKUUtKy5y/4KMrFkeYMcEYjtmG HPKpN+W2HWFbcskS9ntJSM6J3UpnM3ecsC/F9jY2cPGH/I3p/VnV7+OlZhI0FK6HcFcj xogw== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=Ln60sPiU; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id s18si9996186ejh.472.2021.08.15.11.27.08; Sun, 15 Aug 2021 11:27:08 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=Ln60sPiU; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 734B368A4A8; Sun, 15 Aug 2021 21:26:50 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-pl1-f178.google.com (mail-pl1-f178.google.com [209.85.214.178]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id EF31D68A473 for ; Sun, 15 Aug 2021 21:26:43 +0300 (EEST) Received: by mail-pl1-f178.google.com with SMTP id c17so13028237plz.2 for ; Sun, 15 Aug 2021 11:26:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id; bh=FNXDfl1S+JQ0sAz1GYQcfxk3M/WY8ezVaKCkQa5FctU=; b=Ln60sPiU6M6wffVLnypitOVBjF767JYrBu8bBgRr03EJB3lXwQt7CyB4lLUob0MUho cB6y4US9o5Z5936np77TOIFo9tDrnEvgBqFJRVEbxPZLxU1Ywjz06OGNWZLVHJHcPXes kwbO3e/N3KIz8NL+aGQQ5Itt00rkd8KAR6L6EoXY0iIxPpg/vzHdZFwMe7zstkQYu30F F+eTfXsaXEpqFvoNdKmdTJgMZW38XPhFoPfMj+zovqs1ooV0JdQb/jfM4Q4LkOCeJ4ux EAZVsAUYZIEmlWZ7hiTClsxZRDAfl2QlKjShL03VjiyY5tMzlMhkxbYaq3vED+5veCj9 lscg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id; bh=FNXDfl1S+JQ0sAz1GYQcfxk3M/WY8ezVaKCkQa5FctU=; b=FjtRs4BieCv2Fu8LQpZtXRatSSIKjFqXSydkZbx1urZj72eB10AUNBmGFpzxlPKR0X +y779judiMLHouynNK3vac+WkrSktmgGaelxxt36NJyb/P6pZakLazDenBACwA5QZvoD dSYPyq6rhPJSD9FyMSAdVI5+Xb87EPl3NJrs6G+z5l3ByfiQs7YOzQXFgAfZl/tFI22U 6ouwpYdT30boh5kuq2acHwOBi/raJREFOR0M38oO272GrDQSqOmt0dty9M0JP2JRDwCH cRUqKXO0Cx0j1z2Q/WvuCmaEGzch34TigltlKNd2VKgn8+4lWYyuagscTlDVm9Z5DFZw aPiQ== X-Gm-Message-State: AOAM531fzUzDwcEuoQNuCqI57bke/r1Lnb+GQetRYFLBoREt8LcnEGto ojEiOjyMuBNa5nWsmEJRL3icjFZF6ivKEQ== X-Received: by 2002:aa7:9821:0:b0:3e0:f219:6721 with SMTP id q1-20020aa79821000000b003e0f2196721mr12476795pfl.77.1629052001708; Sun, 15 Aug 2021 11:26:41 -0700 (PDT) Received: from localhost.localdomain ([2401:4900:4458:83cd:6c5d:85b2:b4ee:c5e3]) by smtp.gmail.com with ESMTPSA id a2sm10454443pgb.19.2021.08.15.11.26.39 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 15 Aug 2021 11:26:41 -0700 (PDT) From: Nachiket Tarate To: ffmpeg-devel@ffmpeg.org Date: Sun, 15 Aug 2021 23:56:26 +0530 Message-Id: <20210815182626.12744-1-nachiket.programmer@gmail.com> X-Mailer: git-send-email 2.17.1 Subject: [FFmpeg-devel] [PATCH 2/4] libavformat/mov: add support for 'cens', 'cbc1' and 'cbcs' encryption schemes specified in Common Encryption (CENC) standard X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: GX3T11kCaiu0 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 --- 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 46bc7b5aa3..a0f52bd203 100644 --- a/libavformat/mov.c +++ b/libavformat/mov.c @@ -6578,15 +6578,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(); @@ -6602,10 +6737,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++) { @@ -6619,7 +6758,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; } @@ -6632,6 +6782,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; @@ -6646,7 +6878,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;