diff mbox series

[FFmpeg-devel] avformat/hls: Re-use crypto URLContext

Message ID 20230929165027.204216-1-luis@scheurenbrand.me
State New
Headers show
Series [FFmpeg-devel] avformat/hls: Re-use crypto URLContext | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Luis Scheurenbrand Sept. 29, 2023, 4:50 p.m. UTC
Reset crypto state and keep nested http context alive.
Previously, an unencrypted file, followed by an encrypted file would
result in a corrupted stream, as the stream would not be closed and
an encrypted file would be forwarded to the demuxer.

fixes: https://trac.ffmpeg.org/ticket/10599
fixes: https://trac.ffmpeg.org/ticket/8490
Signed-off-by: Luis Scheurenbrand <luis@scheurenbrand.me>
---
 libavformat/crypto.c | 55 ++++++++++++++++++++++++++++++++++++++++++++
 libavformat/crypto.h | 38 ++++++++++++++++++++++++++++++
 libavformat/hls.c    | 32 ++++++++++++++++++++++----
 3 files changed, 120 insertions(+), 5 deletions(-)
 create mode 100644 libavformat/crypto.h
diff mbox series

Patch

diff --git a/libavformat/crypto.c b/libavformat/crypto.c
index 75b00020bc..543300c7c0 100644
--- a/libavformat/crypto.c
+++ b/libavformat/crypto.c
@@ -20,8 +20,10 @@ 
  */
 
 #include "libavutil/aes.h"
+#include "libavutil/avassert.h"
 #include "libavutil/avstring.h"
 #include "libavutil/opt.h"
+#include "crypto.h"
 #include "url.h"
 
 // encourage reads of 4096 bytes - 1 block is always retained.
@@ -105,6 +107,59 @@  static int set_aes_arg(URLContext *h, uint8_t **buf, int *buf_len,
     return 0;
 }
 
+URLContext *ff_crypto_get_inner(URLContext *h)
+{
+    CryptoContext *c = h->priv_data;
+    return c->hd;
+}
+
+int ff_crypto_reset(URLContext *h)
+{
+    int ret = 0;
+    CryptoContext *c = h->priv_data;
+    int flags = c->flags;
+
+    if (flags & AVIO_FLAG_READ) {
+        av_assert0(c->decrypt_key);
+        av_assert0(c->decrypt_iv);
+        av_assert0(c->aes_decrypt);
+
+        memcpy(c->decrypt_key, c->key, c->keylen);
+        memcpy(c->decrypt_iv, c->iv, c->ivlen);
+
+        ret = av_aes_init(c->aes_decrypt, c->decrypt_key, BLOCKSIZE * 8, 1);
+        if (ret < 0)
+            goto err;
+    }
+
+    if (flags & AVIO_FLAG_WRITE) {
+        av_assert0(c->encrypt_key);
+        av_assert0(c->encrypt_iv);
+        av_assert0(c->aes_encrypt);
+
+        memcpy(c->encrypt_key, c->key, c->keylen);
+        memcpy(c->encrypt_iv, c->iv, c->ivlen);
+
+        ret = av_aes_init(c->aes_encrypt, c->encrypt_key, BLOCKSIZE * 8, 0);
+        if (ret < 0)
+            goto err;
+    }
+
+    c->indata = c->indata_used = c->outdata = 0;
+    c->position = 0;
+    c->eof = 0;
+    c->outdata = 0;
+    c->outptr = 0;
+
+    av_assert0(c->outbuffer);
+    av_assert0(c->inbuffer);
+    memset(c->outbuffer, 0, sizeof(c->outbuffer));
+    memset(c->inbuffer, 0, sizeof(c->inbuffer));
+
+err:
+    return ret;
+}
+
 static int crypto_open2(URLContext *h, const char *uri, int flags, AVDictionary **options)
 {
     const char *nested_url;
diff --git a/libavformat/crypto.h b/libavformat/crypto.h
new file mode 100644
index 0000000000..7e6d7f0279
--- /dev/null
+++ b/libavformat/crypto.h
@@ -0,0 +1,38 @@ 
+/*
+ * HTTP definitions
+ * Copyright (c) 2010 Josh Allmann
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVFORMAT_CRYPTO_H
+#define AVFORMAT_CRYPTO_H
+
+#include "url.h"
+
+URLContext *ff_crypto_get_inner(URLContext *h);
+
+/**
+ * Resets the crypto context.
+ *
+ * @param h pointer to the resource
+ * @return a negative value if an error condition occurred, 0
+ * otherwise
+ */
+int ff_crypto_reset(URLContext *h);
+
+#endif /* AVFORMAT_CRYPTO_H */
diff --git a/libavformat/hls.c b/libavformat/hls.c
index 332a5a15bb..7ad0ec9aec 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -44,6 +44,7 @@ 
 #include "avio_internal.h"
 #include "id3v2.h"
 #include "url.h"
+#include "crypto.h"
 
 #include "hls_sample_encryption.h"
 
@@ -620,17 +621,38 @@  static int ensure_playlist(HLSContext *c, struct playlist **pls, const char *url
     return 0;
 }
 
+/* compares two segments to ensure the encryption is the same and
+ * we can reuse the crypto and/or http context. */
+static int crypto_matches(const struct segment *lhs, const struct segment *rhs) {
+    if (!lhs || !rhs)
+        return 0;
+    if (lhs->key_type != rhs->key_type)
+        return 0;
+    if (lhs->key_type == KEY_NONE)
+        return 1;
+    
+    return
+        strcmp(lhs->key, rhs->key) == 0 &&
+        memcmp(lhs->iv, rhs->iv, sizeof(lhs->iv)) == 0;
+}
+
 static int open_url_keepalive(AVFormatContext *s, AVIOContext **pb,
                               const char *url, AVDictionary **options)
 {
 #if !CONFIG_HTTP_PROTOCOL
     return AVERROR_PROTOCOL_NOT_FOUND;
 #else
-    int ret;
+    int ret=0;
     URLContext *uc = ffio_geturlcontext(*pb);
     av_assert0(uc);
     (*pb)->eof_reached = 0;
-    ret = ff_http_do_new_request2(uc, url, options);
+
+    if (av_strstart(url, "crypto+", &url)) {
+        ret = ff_crypto_reset(uc);
+        uc = ff_crypto_get_inner(uc);
+    }
+
+    if (ret == 0) ret = ff_http_do_new_request2(uc, url, options);
     if (ret < 0) {
         ff_format_io_close(s, pb);
     }
@@ -1592,7 +1614,7 @@  reload:
 
     seg = next_segment(v);
     if (c->http_multiple == 1 && !v->input_next_requested &&
-        seg && seg->key_type == KEY_NONE && av_strstart(seg->url, "http", NULL)) {
+        seg && av_strstart(seg->url, "http", NULL)) {
         ret = open_input(c, v, seg, &v->input_next);
         if (ret < 0) {
             if (ff_check_interrupt(c->interrupt_callback))
@@ -1624,8 +1646,8 @@  reload:
 
         return ret;
     }
-    if (c->http_persistent &&
-        seg->key_type == KEY_NONE && av_strstart(seg->url, "http", NULL)) {
+    if (c->http_persistent && crypto_matches(seg, next_segment(v)) &&
+        av_strstart(seg->url, "http", NULL)) {
         v->input_read_done = 1;
     } else {
         ff_format_io_close(v->parent, &v->input);