From patchwork Fri Jul 16 17:19:30 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nachiket Tarate X-Patchwork-Id: 28946 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a5d:965a:0:0:0:0:0 with SMTP id d26csp2016983ios; Fri, 16 Jul 2021 11:22:07 -0700 (PDT) X-Google-Smtp-Source: ABdhPJwRRfXBRCn6yL0psFZwBQHsOH37URS9Zur4NqkrU3rMT1Y4+QT8vzexyt3owzltZn4rjQ0N X-Received: by 2002:a17:906:4e09:: with SMTP id z9mr7305354eju.226.1626459727512; Fri, 16 Jul 2021 11:22:07 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1626459727; cv=none; d=google.com; s=arc-20160816; b=nhQmZjcz6LT6W/x0ujN+S22edbJxJQ6DGVOA47tErhx8/c2XOEt4x4sf62UbIK5y3a xzH9WJcjLPAdvG6PrUrrGTcHhIRxyGtnQGojVD0u3zCTTJtYMRfXCDL08+tG+fiItoc2 RmGajtJp906+WfdUIdLb7mklTWiFUYkLakH4rPFLk3YPcYwn/DpVW6h5P8bWc17xZ7sD rdHpc6Dli95i/+8YMJ8d+isZbxTUfMFoBQjwweoWyfpRM2fXQyjhaRHKZS+P/LQR85o9 NxwJWmVWcPut21B6+Pfx3x4ITDsu4T5ZpMo9oTXgyJmwQh8i19+XJYpHZg+y0q5nF07Q CuIw== 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=r8G4uyKQxAV/Qm0vARw719phjxCazqubFH5PSsp+EMg=; b=xgk611UcISM6Gt3CGBacCwLHMV+lNEZX4iPci6RcWqbnWftfUoXNQLG+coS9MBQ7FZ qYkXZHeyfkSMi3/UiLyOsVdUTr1LV8ZBxaU0B++sX+5Fb9uUOFkUi1wSjgC68CPIxCfC pzxnbPTGd5ZaLWvmgwfz56ybCsUYGRGkJqcgtT+YRiW+bdENpm3n3cElArUTZPKdPVTX lFQujXpTLppEqHPljFb0c2ZnMf77g3mZAn9+sXQeI50g05C6d5fTFOMIv3vuKW+edXfN OS5vQV9AhDeLwokwGRwd0y6gjBVFqDpc5DqjJCj1TPYyAlB3He72Cbknw0Atk18H+6rS NHWQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=Et9SiVez; 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 h20si11610126ejt.70.2021.07.16.11.22.06; Fri, 16 Jul 2021 11:22:07 -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=Et9SiVez; 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 0480768A606; Fri, 16 Jul 2021 21:22:03 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-pl1-f179.google.com (mail-pl1-f179.google.com [209.85.214.179]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 667D468A28C for ; Fri, 16 Jul 2021 21:21:56 +0300 (EEST) Received: by mail-pl1-f179.google.com with SMTP id x16so5720193plg.3 for ; Fri, 16 Jul 2021 11:21:56 -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=eQ7liI9ndJ7Ri83LbqMBXX0flG815YoTGVT7pT0zhuo=; b=Et9SiVezBMu6RiAIOcxTGokO0bn2pbY9JdU5xf2zQzHBiu67BBVaXiEwQzq5YFJFPS FwM8v6RlcMN1HmJb70b36AW+bfqfZ/ui5usW9Idm08d7pNmSX4PBiZb5SsBymg0KX9A7 x0JDCmG2/I0j3UVnxgXFzShYR++Lxoa1I7Rnz4TAYp+MQwZ2eKQl0L3l4aJLQmQQVd3n HS53HP32s8tT2AcBiOerT26cpeMLViwAgCxhyukaA2CjUU8ieXf2X5J0fGhJBrzrvth3 vbUYwoh5RMjVLrSdeqpY3JgbCavhFv0AfV/m/GKnfyN4DuLLUJbqCoUvKmvLJOcurVSu 8Ypg== 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=eQ7liI9ndJ7Ri83LbqMBXX0flG815YoTGVT7pT0zhuo=; b=bCxgsDQ/ema0ux+pMrNDm28zAejYe4Csc4PlSn9IsahXDmw+/ElDdOO8nCWxfVzBuX iCMk4TmzDd4rMg0Am0ZmzRqBxNr2f7stGQxnegWLgFhq1toICMEMXOWotte/dQD14WXP TBR1VpjpqmOp0DFhF7gbvG7RczTDHksJV31+WAZFdOj9AOk/wKHXem7gjLX7VtXcfG+8 YedeMrAQLWMeXw1GSEbOXdosrHVpwnDBESZk+sqcVW5zuX+lRS3C6X/58JkWWPySDALi yFUCPLh4OQm+r5vJS5AGS5S4LOugL3HiaCecfnrjjse1nIs4qgi2yBBroPa4otGh57ay +zhg== X-Gm-Message-State: AOAM5322T3RtoVYbSRKGHsUd77aWeK4CQ6FFEPhQe1AHT3VjpMIXdwo9 mOTTlLksJQ2mzPrOYyNrusoWoREPcGRzbS+E X-Received: by 2002:a17:902:ea8a:b029:12a:fb40:d7ed with SMTP id x10-20020a170902ea8ab029012afb40d7edmr8393350plb.46.1626455987305; Fri, 16 Jul 2021 10:19:47 -0700 (PDT) Received: from localhost.localdomain ([2401:4900:30cb:56ce:2598:843e:f055:e9fc]) by smtp.gmail.com with ESMTPSA id y6sm8768325pjr.48.2021.07.16.10.19.44 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 16 Jul 2021 10:19:46 -0700 (PDT) From: Nachiket Tarate To: ffmpeg-devel@ffmpeg.org Date: Fri, 16 Jul 2021 22:49:30 +0530 Message-Id: <20210716171930.5462-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: AlXKVcScFWyw 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 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;