[rtmpdump] [PATCH] Add adobe authentication support

Martin Storsjo martin at martin.st
Tue Aug 30 20:38:24 CEST 2011


From: Sergiy <piratfm at gmail.com>

To use this, add flashver=FMLE/3.0\20(compatible;\20FMSc/1.0)
and pubUser=<username> pubUser=<password> to the RTMP url.

This should allow publishing both to generic FMS and to akamai.
---
This depends on the patch for adding support for gnutls/nettle.
The gnutls codepaths are untested at the moment (and the other
are hopefully untouched from Sergiy's initial patch), I'll try to
get access to an akamai account for testing, unless someone can
volunteer to test through the patch with all crypto backends
(openssl, gnutls/gcrypt, gnutls/nettle, polarssl).

 librtmp/rtmp.c |  366 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 librtmp/rtmp.h |    9 ++
 2 files changed, 372 insertions(+), 3 deletions(-)

diff --git a/librtmp/rtmp.c b/librtmp/rtmp.c
index c9d5c83..a826cb4 100644
--- a/librtmp/rtmp.c
+++ b/librtmp/rtmp.c
@@ -27,6 +27,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
+#include <time.h>
 
 #include "rtmp_sys.h"
 #include "log.h"
@@ -34,11 +35,22 @@
 #ifdef CRYPTO
 #ifdef USE_POLARSSL
 #include <polarssl/havege.h>
+#include <polarssl/md5.h>
+#include <polarssl/base64.h>
+#define MD5_DIGEST_LENGTH 16
 #elif defined(USE_GNUTLS) || defined(USE_GNUTLS_NETTLE)
 #include <gnutls/gnutls.h>
+#define MD5_DIGEST_LENGTH 16
+#ifdef USE_GNUTLS_NETTLE
+#include <nettle/base64.h>
+#include <nettle/md5.h>
+#endif
 #else	/* USE_OPENSSL */
 #include <openssl/ssl.h>
 #include <openssl/rc4.h>
+#include <openssl/md5.h>
+#include <openssl/bio.h>
+#include <openssl/buffer.h>
 #endif
 TLS_CTX RTMP_TLS_ctx;
 #endif
@@ -499,6 +511,10 @@ static struct urlopt {
   	"Buffer time in milliseconds" },
   { AVC("timeout"),   OFF(Link.timeout),       OPT_INT, 0,
   	"Session timeout in seconds" },
+  { AVC("pubUser"),   OFF(Link.pubUser),     OPT_STR, 0,
+      "Publisher username" },
+  { AVC("pubPasswd"),   OFF(Link.pubPasswd), OPT_STR, 0,
+      "Publisher password" },
   { {NULL,0}, 0, 0}
 };
 
@@ -2305,6 +2321,272 @@ AV_clear(RTMP_METHOD *vals, int num)
   free(vals);
 }
 
+
+#ifdef CRYPTO
+static int
+b64enc(const unsigned char *input, int length, char *output, int maxsize)
+{
+#ifdef USE_POLARSSL
+  int buf_size = maxsize;
+  if(base64_encode((unsigned char *) output, &buf_size, input, length) == 0)
+    {
+      output[buf_size] = '\0';
+      return 1;
+    }
+  else
+    {
+      RTMP_Log(RTMP_LOGDEBUG, "%s, error", __FUNCTION__);
+      return 0;
+    }
+#elif defined(USE_GNUTLS)
+    //TODO: gnutls have SRP-base64 encoder, use it if possible
+  static const char b64str[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  while (length && maxsize)
+    {
+      *output++ = b64str[(input[0] >> 2) & 0x3f];
+      if (!--maxsize)
+          break;
+      *output++ = b64str[((input[0] << 4) + (--length ? input[1] >> 4 : 0)) & 0x3f];
+      if (!--maxsize)
+          break;
+      *output++ = (length ? b64str[((input[1] << 2) + (--length ? input[2] >> 6 : 0)) & 0x3f] : '=');
+      if (!--maxsize)
+          break;
+      *output++ = length ? b64str[input[2] & 0x3f] : '=';
+      if (!--maxsize)
+          break;
+      if (length)
+          length--;
+      if (length)
+          input += 3;
+    }
+  if (maxsize)
+      *output = '\0';
+#elif defined(USE_GNUTLS_NETTLE)
+  if (BASE64_ENCODE_RAW_LENGTH(length) <= maxsize)
+    base64_encode_raw(output, length, input);
+  else
+    {
+      RTMP_Log(RTMP_LOGDEBUG, "%s, error", __FUNCTION__);
+      return 0;
+    }
+#else   /* USE_OPENSSL */
+  BIO *bmem, *b64;
+  BUF_MEM *bptr;
+
+  b64 = BIO_new(BIO_f_base64());
+  bmem = BIO_new(BIO_s_mem());
+  b64 = BIO_push(b64, bmem);
+  BIO_write(b64, input, length);
+  if (BIO_flush(b64) == 1)
+    {
+      BIO_get_mem_ptr(b64, &bptr);
+      memcpy(output, bptr->data, bptr->length-1);
+      output[bptr->length-1] = '\0';
+    }
+  else
+    {
+      RTMP_Log(RTMP_LOGDEBUG, "%s, error", __FUNCTION__);
+      return 0;
+    }
+  BIO_free_all(b64);
+#endif
+  return 1;
+}
+
+#ifdef USE_POLARSSL
+#define md5sum(x,y,z)  md5(x,y,z);
+#elif defined(USE_GNUTLS)
+static void md5sum(const unsigned char *data, int len, unsigned char *out)
+{
+    gcry_md_hd_t ctx;
+    gcry_md_open(&ctx, GCRY_MD_MD5, 0);
+    gcry_md_write(ctx, data, len);
+    memcpy(out, gcry_md_read(ctx, 0), MD5_DIGEST_LENGTH);
+    gcry_md_close(ctx);
+}
+#elif defined(USE_GNUTLS_NETTLE)
+static void md5sum(const unsigned char *data, int len, unsigned char *out)
+{
+    struct md5_ctx ctx;
+    md5_init(&ctx);
+    md5_update(&ctx, len, data);
+    md5_digest(&ctx, MD5_DIGEST_LENGTH, out);
+}
+#else
+#define md5sum(x,y,z)  MD5(x,y,z);
+#endif
+
+static const AVal av_authmod_adobe = AVC("authmod=adobe");
+
+static int
+PublisherAuth(RTMP *r, AVal *description)
+{
+  char *token_in = NULL;
+  char *ptr;
+  unsigned char md5sum_val[MD5_DIGEST_LENGTH+1];
+  int challenge2_data;
+#define RESPONSE_LEN 32
+#define CHALLENGE2_LEN 16
+#define SALTED2_LEN (32+8+8+8)
+  char response[RESPONSE_LEN];
+  char challenge2[CHALLENGE2_LEN];
+  char salted2[SALTED2_LEN];
+  AVal pubToken;
+
+  if (strstr(description->av_val, av_authmod_adobe.av_val) != NULL)
+    {
+      if(strstr(description->av_val, "code=403 need auth") != NULL)
+        {
+            if (strstr(r->Link.app.av_val, av_authmod_adobe.av_val) != NULL) {
+              RTMP_Log(RTMP_LOGERROR, "%s, wrong pubUser & pubPasswd for publisher auth", __FUNCTION__);
+              r->Link.pFlags |= RTMP_PUB_CLEAN;
+              return 0;
+            } else if(r->Link.pubUser.av_len && r->Link.pubPasswd.av_len) {
+              pubToken.av_val = malloc(r->Link.pubUser.av_len + av_authmod_adobe.av_len + 8);
+              pubToken.av_len = sprintf(pubToken.av_val, "?%s&user=%s",
+                      av_authmod_adobe.av_val,
+                      r->Link.pubUser.av_val);
+              RTMP_Log(RTMP_LOGDEBUG, "%s, pubToken1: %s", __FUNCTION__, pubToken.av_val);
+              r->Link.pFlags |= RTMP_PUB_NAME;
+            } else {
+              RTMP_Log(RTMP_LOGERROR, "%s, need to set pubUser & pubPasswd for publisher auth", __FUNCTION__);
+              r->Link.pFlags |= RTMP_PUB_CLEAN;
+              return 0;
+            }
+        }
+      else if((token_in = strstr(description->av_val, "?reason=needauth")) != NULL)
+        {
+          char *par, *val = NULL, *orig_ptr;
+          char *user = NULL;
+          char *salt = NULL;
+          char *opaque = NULL;
+          char *challenge = NULL;
+          char *salted1;
+
+          ptr = orig_ptr = strdup(token_in);
+          while (ptr)
+            {
+              par = ptr;
+              ptr = strchr(par, '&');
+              if(ptr)
+                  *ptr++ = '\0';
+
+              val =  strchr(par, '=');
+              if(val)
+                  *val++ = '\0';
+
+              if (strcmp(par, "user") == 0){
+                  user = val;
+              } else if (strcmp(par, "salt") == 0){
+                  salt = val;
+              } else if (strcmp(par, "opaque") == 0){
+                  opaque = val;
+              } else if (strcmp(par, "challenge") == 0){
+                  challenge = val;
+              }
+
+              RTMP_Log(RTMP_LOGDEBUG, "%s, par:\"%s\" = val:\"%s\"", __FUNCTION__, par, val);
+            }
+
+            /* hash1 = base64enc(md5(user + _aodbeAuthSalt + password)) */
+            salted1 = malloc(strlen(user)+strlen(salt)+r->Link.pubPasswd.av_len+1);
+            strcpy(salted1, user);
+            strcat(salted1, salt);
+            strcat(salted1, r->Link.pubPasswd.av_val);
+            md5sum((unsigned char*) salted1, strlen(salted1), md5sum_val);
+            RTMP_Log(RTMP_LOGDEBUG, "%s, md5(%s) =>", __FUNCTION__, salted1);
+            free(salted1);
+
+            RTMP_LogHexString(RTMP_LOGDEBUG, md5sum_val, MD5_DIGEST_LENGTH);
+
+            b64enc(md5sum_val, MD5_DIGEST_LENGTH, salted2, SALTED2_LEN);
+            RTMP_Log(RTMP_LOGDEBUG, "%s, b64(md5_1) = %s", __FUNCTION__, salted2);
+
+            srand( time(NULL) );
+            challenge2_data = rand();
+
+            b64enc((unsigned char *) &challenge2_data, sizeof(int), challenge2, CHALLENGE2_LEN);
+            RTMP_Log(RTMP_LOGDEBUG, "%s, b64(%d) = %s", __FUNCTION__, challenge2_data, challenge2);
+
+            /* response = base64enc(md5(hash1 + opaque + challenge2)) */
+            if (opaque)
+            strcat(salted2, opaque);
+            if (challenge)
+            strcat(salted2, challenge);
+            strcat(salted2, challenge2);
+
+            md5sum((unsigned char*) salted2, strlen(salted2), md5sum_val);
+            RTMP_Log(RTMP_LOGDEBUG, "%s, md5(%s) =>", __FUNCTION__, salted2);
+            RTMP_LogHexString(RTMP_LOGDEBUG, md5sum_val, MD5_DIGEST_LENGTH);
+
+            b64enc(md5sum_val, MD5_DIGEST_LENGTH, response, RESPONSE_LEN);
+            RTMP_Log(RTMP_LOGDEBUG, "%s, b64(md5_2) = %s", __FUNCTION__, response);
+
+            /* have all hashes, create auth token for the end of app */
+            pubToken.av_val = malloc(32 + strlen(challenge2) + strlen(response) + (opaque ? strlen(opaque) : 0));
+            pubToken.av_len = sprintf(pubToken.av_val,
+                    "&challenge=%s&response=%s&opaque=%s",
+                    challenge2,
+                    response,
+                    opaque ? opaque : "");
+            RTMP_Log(RTMP_LOGDEBUG, "%s, pubToken2: %s", __FUNCTION__, pubToken.av_val);
+            free(orig_ptr);
+            r->Link.pFlags |= RTMP_PUB_RESP|RTMP_PUB_CLATE;
+        }
+      else if(strstr(description->av_val, "?reason=authfailed") != NULL)
+        {
+          RTMP_Log(RTMP_LOGERROR, "%s, Authentication failed: wrong password", __FUNCTION__);
+          r->Link.pFlags |= RTMP_PUB_CLEAN;
+          return 0;
+        }
+      else if(strstr(description->av_val, "?reason=nosuchuser") != NULL)
+        {
+          RTMP_Log(RTMP_LOGERROR, "%s, Authentication failed: no such user", __FUNCTION__);
+          r->Link.pFlags |= RTMP_PUB_CLEAN;
+          return 0;
+        }
+      else
+        {
+          RTMP_Log(RTMP_LOGERROR, "%s, Authentication failed: unknown auth mode: %s",
+                  __FUNCTION__, description->av_val);
+          r->Link.pFlags |= RTMP_PUB_CLEAN;
+          return 0;
+        }
+
+      ptr = malloc(r->Link.app.av_len + pubToken.av_len);
+      strncpy(ptr, r->Link.app.av_val, r->Link.app.av_len);
+      strncpy(ptr + r->Link.app.av_len, pubToken.av_val, pubToken.av_len);
+      r->Link.app.av_len += pubToken.av_len;
+      if(r->Link.pFlags & RTMP_PUB_ALLOC)
+          free(r->Link.app.av_val);
+      r->Link.app.av_val = ptr;
+
+      ptr = malloc(r->Link.tcUrl.av_len + pubToken.av_len);
+      strncpy(ptr, r->Link.tcUrl.av_val, r->Link.tcUrl.av_len);
+      strncpy(ptr + r->Link.tcUrl.av_len, pubToken.av_val, pubToken.av_len);
+      r->Link.tcUrl.av_len += pubToken.av_len;
+      if(r->Link.pFlags & RTMP_PUB_ALLOC)
+          free(r->Link.tcUrl.av_val);
+      r->Link.tcUrl.av_val = ptr;
+
+      free(pubToken.av_val);
+      r->Link.pFlags |= RTMP_PUB_ALLOC;
+
+      RTMP_Log(RTMP_LOGDEBUG, "%s, new app: %.*s tcUrl: %.*s playpath: %s", __FUNCTION__,
+              r->Link.app.av_len, r->Link.app.av_val,
+              r->Link.tcUrl.av_len, r->Link.tcUrl.av_val,
+              r->Link.playpath.av_val);
+    }
+  else
+    {
+      return 0;
+    }
+  return 1;
+}
+#endif
+
+
 SAVC(onBWDone);
 SAVC(onFCSubscribe);
 SAVC(onFCUnsubscribe);
@@ -2314,6 +2596,7 @@ SAVC(_error);
 SAVC(close);
 SAVC(code);
 SAVC(level);
+SAVC(description);
 SAVC(onStatus);
 SAVC(playlist_ready);
 static const AVal av_NetStream_Failed = AVC("NetStream.Failed");
@@ -2332,6 +2615,8 @@ AVC("NetStream.Play.PublishNotify");
 static const AVal av_NetStream_Play_UnpublishNotify =
 AVC("NetStream.Play.UnpublishNotify");
 static const AVal av_NetStream_Publish_Start = AVC("NetStream.Publish.Start");
+static const AVal av_NetConnection_Connect_Rejected =
+AVC("NetConnection.Connect.Rejected");
 
 /* Returns 0 for OK/Failed/error, 1 for 'Stop or Complete' */
 static int
@@ -2473,12 +2758,73 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize)
     }
   else if (AVMATCH(&method, &av__error))
     {
+#ifdef CRYPTO
+      AVal methodInvoked = {0};
+      int i;
+
+      if (r->Link.protocol & RTMP_FEATURE_WRITE)
+        {
+          for (i=0; i<r->m_numCalls; i++)
+            {
+              if (r->m_methodCalls[i].num == txn)
+                {
+                  methodInvoked = r->m_methodCalls[i].name;
+                  AV_erase(r->m_methodCalls, &r->m_numCalls, i, FALSE);
+                  break;
+                }
+            }
+          if (!methodInvoked.av_val)
+            {
+              RTMP_Log(RTMP_LOGDEBUG, "%s, received result id %d without matching request",
+                    __FUNCTION__, txn);
+              goto leave;
+            }
+
+          RTMP_Log(RTMP_LOGDEBUG, "%s, received error for method call <%s>", __FUNCTION__,
+          methodInvoked.av_val);
+
+          if (AVMATCH(&methodInvoked, &av_connect))
+            {
+              AMFObject obj2;
+              AVal code, level, description;
+              AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &obj2);
+              AMFProp_GetString(AMF_GetProp(&obj2, &av_code, -1), &code);
+              AMFProp_GetString(AMF_GetProp(&obj2, &av_level, -1), &level);
+              AMFProp_GetString(AMF_GetProp(&obj2, &av_description, -1), &description);
+              RTMP_Log(RTMP_LOGDEBUG, "%s, error description: %s", __FUNCTION__, description.av_val);
+              /* if PublisherAuth returns 1, then reconnect */
+              PublisherAuth(r, &description);
+            }
+        }
+      else
+        {
+          RTMP_Log(RTMP_LOGERROR, "rtmp server sent error");
+        }
+      free(methodInvoked.av_val);
+#else
       RTMP_Log(RTMP_LOGERROR, "rtmp server sent error");
+#endif
     }
   else if (AVMATCH(&method, &av_close))
     {
       RTMP_Log(RTMP_LOGERROR, "rtmp server requested close");
       RTMP_Close(r);
+#ifdef CRYPTO
+      if ((r->Link.protocol & RTMP_FEATURE_WRITE) &&
+              !(r->Link.pFlags & RTMP_PUB_CLEAN) &&
+              (  !(r->Link.pFlags & RTMP_PUB_NAME) ||
+                 !(r->Link.pFlags & RTMP_PUB_RESP) ||
+                 (r->Link.pFlags & RTMP_PUB_CLATE) ) )
+        {
+          /* clean later */
+          if(r->Link.pFlags & RTMP_PUB_CLATE)
+              r->Link.pFlags |= RTMP_PUB_CLEAN;
+          RTMP_Log(RTMP_LOGERROR, "authenticating publisher");
+
+          if (!RTMP_Connect(r, NULL) || !RTMP_ConnectStream(r, 0))
+              goto leave;
+       }
+#endif
     }
   else if (AVMATCH(&method, &av_onStatus))
     {
@@ -3519,9 +3865,6 @@ RTMP_Close(RTMP *r)
   r->m_resplen = 0;
   r->m_unackd = 0;
 
-  free(r->Link.playpath0.av_val);
-  r->Link.playpath0.av_val = NULL;
-
   if (r->Link.lFlags & RTMP_LF_FTCU)
     {
       free(r->Link.tcUrl.av_val);
@@ -3530,6 +3873,20 @@ RTMP_Close(RTMP *r)
     }
 
 #ifdef CRYPTO
+  if (!(r->Link.protocol & RTMP_FEATURE_WRITE) || (r->Link.pFlags & RTMP_PUB_CLEAN))
+    {
+      free(r->Link.playpath0.av_val);
+      r->Link.playpath0.av_val = NULL;
+    }
+  if ((r->Link.protocol & RTMP_FEATURE_WRITE) &&
+      (r->Link.pFlags & RTMP_PUB_CLEAN) &&
+      (r->Link.pFlags & RTMP_PUB_ALLOC))
+    {
+      free(r->Link.app.av_val);
+      r->Link.app.av_val = NULL;
+      free(r->Link.tcUrl.av_val);
+      r->Link.tcUrl.av_val = NULL;
+    }
   if (r->Link.dh)
     {
       MDH_free(r->Link.dh);
@@ -3545,6 +3902,9 @@ RTMP_Close(RTMP *r)
       RC4_free(r->Link.rc4keyOut);
       r->Link.rc4keyOut = NULL;
     }
+#else
+  free(r->Link.playpath0.av_val);
+  r->Link.playpath0.av_val = NULL;
 #endif
 }
 
diff --git a/librtmp/rtmp.h b/librtmp/rtmp.h
index 6b2ae5b..fcf3699 100644
--- a/librtmp/rtmp.h
+++ b/librtmp/rtmp.h
@@ -157,6 +157,8 @@ extern "C"
     AVal subscribepath;
     AVal usherToken;
     AVal token;
+    AVal pubUser;
+    AVal pubPasswd;
     AMFObject extras;
     int edepth;
 
@@ -176,6 +178,13 @@ extern "C"
     int protocol;
     int timeout;		/* connection timeout in seconds */
 
+#define RTMP_PUB_NAME   0x0001  /* send login to server */
+#define RTMP_PUB_RESP   0x0002  /* send salted password hash */
+#define RTMP_PUB_ALLOC  0x0004  /* allocated data for new tcUrl & app */
+#define RTMP_PUB_CLEAN  0x0008  /* need to free allocated data for newer tcUrl & app at exit */
+#define RTMP_PUB_CLATE  0x0010  /* late clean tcUrl & app at exit */
+    int pFlags;
+
     unsigned short socksport;
     unsigned short port;
 
-- 
1.7.3.1



More information about the rtmpdump mailing list