[PATCH] avformat/httpauth: add SHA-256 digest authentication support

Patrik via ffmpeg-devel <[email protected]>
Newsgroups gmane.comp.video.ffmpeg.devel
Message-ID <[email protected]>
The HTTP Digest authentication code only supported MD5 and MD5-sess
algorithms. Servers that offer SHA-256 challenges (e.g. Milestone XProtect
VMS, which sends dual MD5 + SHA-256 WWW-Authenticate headers) could not
be authenticated against, because make_digest_auth() returned NULL for any
algorithm it did not recognize.

Add native SHA-256 and SHA-256-sess digest authentication using the
existing libavutil/sha API:

- Include libavutil/sha.h and add update_sha_strings(), a variadic
  helper analogous to the existing update_md5_strings().
- Branch make_digest_auth() on the parsed algorithm: SHA-256 / SHA-256-sess
  use AVSHA with 32-byte digests (64 hex chars), while MD5 / MD5-sess
  continue to use AVMD5 with 16-byte digests (32 hex chars).
- Widen the hash and hex-string buffers (A1hash, A2hash, response, hash)
  to accommodate the larger SHA-256 output.
- Widen DigestParams.algorithm from 10 to 16 bytes in httpauth.h so that
  "SHA-256-sess" (12 chars + NUL) is not silently truncated.
- When multiple Digest challenges are received, prefer SHA-256 over MD5:
  if a SHA-256 challenge has already been parsed, subsequent weaker
  challenges are skipped; if the current challenge is MD5 and a new
  SHA-256 challenge arrives, the SHA-256 one replaces it.

This follows RFC 7616, which defines SHA-256 as a stronger alternative
to MD5 for HTTP Digest authentication.

Signed-off-by: Patrik <[email protected]>
---
 libavformat/httpauth.c | 120 ++++++++++++++++++++++++++++++-----------
 libavformat/httpauth.h |   2 +-
 2 files changed, 90 insertions(+), 32 deletions(-)

diff --git a/libavformat/httpauth.c b/libavformat/httpauth.c
index 4bc746dbf3..8aa281fe0e 100644
--- a/libavformat/httpauth.c
+++ b/libavformat/httpauth.c
@@ -26,6 +26,7 @@
 #include "internal.h"
 #include "libavutil/random_seed.h"
 #include "libavutil/md5.h"
+#include "libavutil/sha.h"
 #include "urldecode.h"
 
 static void handle_basic_params(void *context, const char *key,
@@ -103,6 +104,15 @@ void ff_http_auth_handle_header(HTTPAuthState *state, const char *key,
             ff_parse_key_value(p, handle_basic_params, state);
         } else if (av_stristart(value, "Digest ", &p) &&
                    state->auth_type <= HTTP_AUTH_DIGEST) {
+            if (state->auth_type == HTTP_AUTH_DIGEST) {
+                int current_is_sha256 = !av_strcasecmp(state->digest_params.algorithm, "SHA-256") ||
+                                        !av_strcasecmp(state->digest_params.algorithm, "SHA-256-sess");
+                int new_is_sha256 = av_stristr(p, "SHA-256") != NULL;
+                if (current_is_sha256 || !new_is_sha256) {
+                    return;
+                }
+            }
+
             state->auth_type = HTTP_AUTH_DIGEST;
             memset(&state->digest_params, 0, sizeof(DigestParams));
             state->realm[0] = 0;
@@ -133,6 +143,20 @@ static void update_md5_strings(struct AVMD5 *md5ctx, ...)
     va_end(vl);
 }
 
+static void update_sha_strings(struct AVSHA *shactx, ...)
+{
+    va_list vl;
+
+    va_start(vl, shactx);
+    while (1) {
+        const char* str = va_arg(vl, const char*);
+        if (!str)
+            break;
+        av_sha_update(shactx, str, strlen(str));
+    }
+    va_end(vl);
+}
+
 /* Generate a digest reply, according to RFC 2617. */
 static char *make_digest_auth(HTTPAuthState *state, const char *username,
                               const char *password, const char *uri,
@@ -144,10 +168,10 @@ static char *make_digest_auth(HTTPAuthState *state, const char *username,
     char cnonce[17];
     char nc[9];
     int i;
-    char A1hash[33], A2hash[33], response[33];
-    struct AVMD5 *md5ctx;
-    uint8_t hash[16];
+    char A1hash[65], A2hash[65], response[65];
+    uint8_t hash[32];
     char *authstr;
+    int is_sha256 = !av_strcasecmp(digest->algorithm, "SHA-256") || !av_strcasecmp(digest->algorithm, "SHA-256-sess");
 
     digest->nc++;
     snprintf(nc, sizeof(nc), "%08x", digest->nc);
@@ -157,42 +181,76 @@ static char *make_digest_auth(HTTPAuthState *state, const char *username,
         cnonce_buf[i] = av_get_random_seed();
     ff_data_to_hex(cnonce, (const uint8_t*) cnonce_buf, sizeof(cnonce_buf), 1);
 
-    md5ctx = av_md5_alloc();
-    if (!md5ctx)
-        return NULL;
+    if (is_sha256) {
+        struct AVSHA *shactx = av_sha_alloc();
+        if (!shactx)
+            return NULL;
+
+        av_sha_init(shactx, 256);
+        update_sha_strings(shactx, username, ":", state->realm, ":", password, NULL);
+        av_sha_final(shactx, hash);
+        ff_data_to_hex(A1hash, hash, 32, 1);
 
-    av_md5_init(md5ctx);
-    update_md5_strings(md5ctx, username, ":", state->realm, ":", password, NULL);
-    av_md5_final(md5ctx, hash);
-    ff_data_to_hex(A1hash, hash, 16, 1);
+        if (!av_strcasecmp(digest->algorithm, "SHA-256-sess")) {
+            av_sha_init(shactx, 256);
+            update_sha_strings(shactx, A1hash, ":", digest->nonce, ":", cnonce, NULL);
+            av_sha_final(shactx, hash);
+            ff_data_to_hex(A1hash, hash, 32, 1);
+        }
+
+        av_sha_init(shactx, 256);
+        update_sha_strings(shactx, method, ":", uri, NULL);
+        av_sha_final(shactx, hash);
+        ff_data_to_hex(A2hash, hash, 32, 1);
+
+        av_sha_init(shactx, 256);
+        update_sha_strings(shactx, A1hash, ":", digest->nonce, NULL);
+        if (!strcmp(digest->qop, "auth") || !strcmp(digest->qop, "auth-int")) {
+            update_sha_strings(shactx, ":", nc, ":", cnonce, ":", digest->qop, NULL);
+        }
+        update_sha_strings(shactx, ":", A2hash, NULL);
+        av_sha_final(shactx, hash);
+        ff_data_to_hex(response, hash, 32, 1);
+
+        av_free(shactx);
+    } else {
+        struct AVMD5 *md5ctx = av_md5_alloc();
+        if (!md5ctx)
+            return NULL;
 
-    if (!strcmp(digest->algorithm, "") || !strcmp(digest->algorithm, "MD5")) {
-    } else if (!strcmp(digest->algorithm, "MD5-sess")) {
         av_md5_init(md5ctx);
-        update_md5_strings(md5ctx, A1hash, ":", digest->nonce, ":", cnonce, NULL);
+        update_md5_strings(md5ctx, username, ":", state->realm, ":", password, NULL);
         av_md5_final(md5ctx, hash);
         ff_data_to_hex(A1hash, hash, 16, 1);
-    } else {
-        /* Unsupported algorithm */
-        av_free(md5ctx);
-        return NULL;
-    }
 
-    av_md5_init(md5ctx);
-    update_md5_strings(md5ctx, method, ":", uri, NULL);
-    av_md5_final(md5ctx, hash);
-    ff_data_to_hex(A2hash, hash, 16, 1);
+        if (!strcmp(digest->algorithm, "") || !strcmp(digest->algorithm, "MD5")) {
+        } else if (!strcmp(digest->algorithm, "MD5-sess")) {
+            av_md5_init(md5ctx);
+            update_md5_strings(md5ctx, A1hash, ":", digest->nonce, ":", cnonce, NULL);
+            av_md5_final(md5ctx, hash);
+            ff_data_to_hex(A1hash, hash, 16, 1);
+        } else {
+            /* Unsupported algorithm */
+            av_free(md5ctx);
+            return NULL;
+        }
+
+        av_md5_init(md5ctx);
+        update_md5_strings(md5ctx, method, ":", uri, NULL);
+        av_md5_final(md5ctx, hash);
+        ff_data_to_hex(A2hash, hash, 16, 1);
 
-    av_md5_init(md5ctx);
-    update_md5_strings(md5ctx, A1hash, ":", digest->nonce, NULL);
-    if (!strcmp(digest->qop, "auth") || !strcmp(digest->qop, "auth-int")) {
-        update_md5_strings(md5ctx, ":", nc, ":", cnonce, ":", digest->qop, NULL);
-    }
-    update_md5_strings(md5ctx, ":", A2hash, NULL);
-    av_md5_final(md5ctx, hash);
-    ff_data_to_hex(response, hash, 16, 1);
+        av_md5_init(md5ctx);
+        update_md5_strings(md5ctx, A1hash, ":", digest->nonce, NULL);
+        if (!strcmp(digest->qop, "auth") || !strcmp(digest->qop, "auth-int")) {
+            update_md5_strings(md5ctx, ":", nc, ":", cnonce, ":", digest->qop, NULL);
+        }
+        update_md5_strings(md5ctx, ":", A2hash, NULL);
+        av_md5_final(md5ctx, hash);
+        ff_data_to_hex(response, hash, 16, 1);
 
-    av_free(md5ctx);
+        av_free(md5ctx);
+    }
 
     if (!strcmp(digest->qop, "") || !strcmp(digest->qop, "auth")) {
     } else if (!strcmp(digest->qop, "auth-int")) {
diff --git a/libavformat/httpauth.h b/libavformat/httpauth.h
index 0e7085901c..b7e1f82db1 100644
--- a/libavformat/httpauth.h
+++ b/libavformat/httpauth.h
@@ -34,7 +34,7 @@ typedef enum HTTPAuthType {
 
 typedef struct DigestParams {
     char nonce[300];       /**< Server specified nonce */
-    char algorithm[10];    /**< Server specified digest algorithm */
+    char algorithm[16];    /**< Server specified digest algorithm */
     char qop[30];          /**< Quality of protection, containing the one
                              *  that we've chosen to use, from the
                              *  alternatives that the server offered. */
-- 
2.43.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.