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

Luis Scheurenbrand luis at scheurenbrand.me
Fri Sep 29 18:10:18 EEST 2023


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 at 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 --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);
-- 
2.42.0



More information about the ffmpeg-devel mailing list