From patchwork Tue Sep 21 18:41:31 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nachiket Tarate X-Patchwork-Id: 30426 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a6b:6506:0:0:0:0:0 with SMTP id z6csp2703827iob; Tue, 21 Sep 2021 11:41:59 -0700 (PDT) X-Google-Smtp-Source: ABdhPJwpKQ/spyYsIFdVytZTSKliAwed9V/culE1gP5He7p9e0m0L5Ve9nV9IY6Vj1t63I+sShEj X-Received: by 2002:a17:906:974d:: with SMTP id o13mr35522560ejy.563.1632249718935; Tue, 21 Sep 2021 11:41:58 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1632249718; cv=none; d=google.com; s=arc-20160816; b=C0SaiLerBWDDjoJH/JHaXj6U0Gv6lBn/SBA0x6QH6QCJbSfBaWpYsrG1n6AheR8kUu FEqC1Ssd/tHHE8r9IKnV1re0VOFMv5ayDXX0XT8ktdY1N+VYnjjXrw9ALjx8YCEdIhwb Dzk8OYssYki7d6e7aTfPYa2AKZsZc1LQsOFAFcOPq8t3nvWzJ4/nP7O+X7a1q96S+d9c KJR3oaEEhcs2+FujnZiDK6iHyJxffMDqG72icziY87FrFNlTkpNBuDhyJ72hoORLUp0h kXQCmPTpsdWjwzXYDJ5RLZM5IyPsoLpeObVihbhtsQVsSVAtl3XXNing4nd50F7smx3c fl8g== 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=jaDDjYW3BGorhIkcw0pop6/Q2+n42QIlqU+mvpj+3Ug=; b=0W3oF4c92t5pQZd3CwWdD4WtGQFyfB6YCkXyXOrqbqB8XStyby8UcsavMc+6YLiDBy Qw5R4SlPdDCWP3zjRhpTcm6O2lggt3pFEYH+YyVJkAxbrOV+nfHO4jijakz5OnpdiTjI IFXVYdouGSC0lGxkoaK/epTxwRXHHwc7ECZhmry4LN+seuTDmQJfRvBYRNC0L2SlcAYg bgQU+IOtzYq0adSiu7rS+1AszeHWuldMjLyGXt36DcxmlmxHpGRR3uO02gvU8gBbkiVp slshCaOE6obTw/gtQ5GfzrAgPxT6rHo6hPKBp+EefTvd/LcglLuJWuQGoMq8Hl6qYFRh kDzg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=miZs4dOE; 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 l17si21885807edv.315.2021.09.21.11.41.58; Tue, 21 Sep 2021 11:41:58 -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=20210112 header.b=miZs4dOE; 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 DE25468B030; Tue, 21 Sep 2021 21:41:55 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-pg1-f175.google.com (mail-pg1-f175.google.com [209.85.215.175]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 7B40668AFE1 for ; Tue, 21 Sep 2021 21:41:49 +0300 (EEST) Received: by mail-pg1-f175.google.com with SMTP id e7so12366pgk.2 for ; Tue, 21 Sep 2021 11:41:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:subject:date:message-id; bh=7SLmGxI/ILWGhFBimRLNZNMv1tekVDXw7ZYKFnxXT90=; b=miZs4dOEcL6XrB4AbkOZ2FcYH1wZMJN4saOQuZ6ClIQAquZAWA8bHhTd9PFT38NMRG F/OJ5ih8G31h31i77rEb41TjcDMNI7NjPsdj5v3r3lwG4wL/J9D+tL11wXvdCDCA8bTk 8k6NZU13ZarVd+p24P9CyAMbnydMrnMYYieIJ7WL44kyfi8GVwe8dfds4xy42QWTXhJg O4L5FgGB8+ltWgom1hglYh+tR/LtZ8LBiB8Av91ern6hnVr8C6q/VzYzoZOXqm+qtMi1 3oGit0iCzoybd3gEZPnidDxPZ6ULKZHnAza4qgkk7aLK0Y2W7t6pPDRfSvoG73S8Q8bq ZR6Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:subject:date:message-id; bh=7SLmGxI/ILWGhFBimRLNZNMv1tekVDXw7ZYKFnxXT90=; b=DpoJ4XSn4gibjVkG0FUKFxrPivb3WZnmZbs0IyWyy1BdUNPIvvLYXCbj64FffVBB4h f1pwPm7Y/L6O9b59/SEGHCN6Py6JJkFsuZeGQBB1kn8TexGuMoKBB/Xe9GI5SXMFz8PK pRJQduPcdBk627aD+cz7XU1NV7a/IIcbDwVAvY8xTMVjixO8FOr9Uu13NWS2Mrs81VOI /4vSrhC+HWTH0Tc3HJ4RaYze3bbDBgWeykmtqoKPCd16rZQe/xb6L87NnTVGt+XocZuu ZEm/L2QVs3jIR1MqDnDSgxaPj1sPMkn9au2MDLHeDDN2UhScQGSMr4f9V6k+9tYO08Yu ZwtA== X-Gm-Message-State: AOAM533t+S7wmANevD0GdJWoMM7dij1JBpJNH76e5PZyGcLfPOKJ//HY 94DM2xJ0OTAsgouG2B7Utz/cEO8xSZE= X-Received: by 2002:a62:8cd7:0:b0:438:26bb:965d with SMTP id m206-20020a628cd7000000b0043826bb965dmr31726589pfd.64.1632249706400; Tue, 21 Sep 2021 11:41:46 -0700 (PDT) Received: from localhost.localdomain ([2401:4900:5023:907:6d04:a3b6:841a:1d61]) by smtp.gmail.com with ESMTPSA id w22sm14649860pgc.56.2021.09.21.11.41.44 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 21 Sep 2021 11:41:45 -0700 (PDT) From: Nachiket Tarate To: ffmpeg-devel@ffmpeg.org Date: Wed, 22 Sep 2021 00:11:31 +0530 Message-Id: <20210921184131.3212-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: IaQODpxbPKSf 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 | 245 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 241 insertions(+), 6 deletions(-) diff --git a/libavformat/isom.h b/libavformat/isom.h index 34a58c79b7..b484250034 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 4bc42d6b20..81312f1371 100644 --- a/libavformat/mov.c +++ b/libavformat/mov.c @@ -6584,15 +6584,149 @@ 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) { + 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) + 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) { + 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(); @@ -6608,10 +6742,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 && !sample->skip_byte_block) { + 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++) { @@ -6625,7 +6763,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; } @@ -6638,6 +6787,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 && !sample->skip_byte_block) { + 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 && !sample->skip_byte_block) { + return cenc_scheme_decrypt(c, sc, sample, input, size); + } else if (sample->scheme == MKBETAG('c','b','c','1') && !sample->crypt_byte_block && !sample->skip_byte_block) { + 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; @@ -6652,7 +6883,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 && frag_stream_info->index_entry) + 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;