]> granicus.if.org Git - rtmpdump/commitdiff
Added server-side handshaking. Will probably split these files later...
authorhyc <hyc@400ebc74-4327-4243-bc38-086b20814532>
Tue, 22 Dec 2009 00:50:58 +0000 (00:50 +0000)
committerhyc <hyc@400ebc74-4327-4243-bc38-086b20814532>
Tue, 22 Dec 2009 00:50:58 +0000 (00:50 +0000)
git-svn-id: svn://svn.mplayerhq.hu/rtmpdump/trunk@106 400ebc74-4327-4243-bc38-086b20814532

handshake.h
rtmp.c
rtmp.h

index 9d46afff7cbeaa7868bead69449e95ed543bc7b5..55ead11a1e3d15775433856ec9d35b6b5ed6988d 100644 (file)
@@ -564,3 +564,313 @@ HandShake(RTMP * r, bool FP9HandShake)
   Log(LOGDEBUG, "%s: Handshaking finished....", __FUNCTION__);
   return true;
 }
+
+static bool
+SHandShake(RTMP * r)
+{
+  int i;
+  int dhposClient = 0;
+  int dhposServer = 0;
+  int digestPosServer = 0;
+  RC4_KEY *keyIn = 0;
+  RC4_KEY *keyOut = 0;
+  bool FP9HandShake = false;
+  bool encrypted;
+
+  char clientsig[RTMP_SIG_SIZE];
+  char serverbuf[RTMP_SIG_SIZE + 1], *serversig = serverbuf+1;
+  char type;
+  uint32_t uptime;
+
+  if (ReadN(r, &type, 1) != 1) /* 0x03 or 0x06 */
+    return false;
+
+  Log(LOGDEBUG, "%s: Type Requested : %02X", __FUNCTION__, type);
+
+  if (type == 3)
+    {
+      encrypted = false;
+      r->Link.protocol = RTMP_PROTOCOL_RTMP;
+    }
+  else if (type == 6 || type == 8)
+    {
+      encrypted = true;
+      FP9HandShake = true;
+      r->Link.protocol = RTMP_PROTOCOL_RTMPE;
+    }
+  else
+    {
+      Log(LOGERROR, "%s: Unknown version %02x",
+         __FUNCTION__, type);
+      return false;
+    }
+
+  serverbuf[0] = type;
+
+  r->Link.rc4keyIn = r->Link.rc4keyOut = 0;
+
+  uptime = htonl(RTMP_GetTime());
+  memcpy(serversig, &uptime, 4);
+
+  if (FP9HandShake)
+    {
+      /* Server version */
+      serversig[4] = 3;
+      serversig[5] = 5;
+      serversig[6] = 1;
+      serversig[7] = 1;
+    }
+  else
+    {
+      memset(&serversig[4], 0, 4);
+    }
+
+  /* generate random data */
+#ifdef _DEBUG
+  for (i = 8; i < RTMP_SIG_SIZE; i++)
+    serversig[i] = 0;
+#else
+  for (i = 8; i < RTMP_SIG_SIZE; i++)
+    serversig[i] = (char) (rand() % 256);
+#endif
+
+  /* set handshake digest */
+  if (FP9HandShake)
+    {
+      if (encrypted)
+       {
+         /* generate Diffie-Hellmann parameters */
+         r->Link.dh = DHInit(1024);
+         if (!r->Link.dh)
+           {
+             Log(LOGERROR, "%s: Couldn't initialize Diffie-Hellmann!",
+                 __FUNCTION__);
+             return false;
+           }
+
+         dhposServer = GetDHOffset2(serversig, RTMP_SIG_SIZE);
+         Log(LOGDEBUG, "%s: DH pubkey position: %d", __FUNCTION__, dhposServer);
+
+         if (!DHGenerateKey(r->Link.dh))
+           {
+             Log(LOGERROR, "%s: Couldn't generate Diffie-Hellmann public key!",
+                 __FUNCTION__);
+             return false;
+           }
+
+         if (!DHGetPublicKey
+             (r->Link.dh, (uint8_t *) &serversig[dhposServer], 128))
+           {
+             Log(LOGERROR, "%s: Couldn't write public key!", __FUNCTION__);
+             return false;
+           }
+       }
+
+      digestPosServer = GetDigestOffset2(serversig, RTMP_SIG_SIZE);    /* reuse this value in verification */
+      Log(LOGDEBUG, "%s: Client digest offset: %d", __FUNCTION__,
+         digestPosServer);
+
+      CalculateDigest(digestPosServer, serversig, GenuineFMSKey, 36,
+                     &serversig[digestPosServer]);
+
+      Log(LOGDEBUG, "%s: Initial server digest: ", __FUNCTION__);
+      LogHex(LOGDEBUG, (char *) serversig + digestPosServer,
+            SHA256_DIGEST_LENGTH);
+    }
+
+#ifdef _DEBUG
+  Log(LOGDEBUG, "Serversig: ");
+  LogHex(LOGDEBUG, serversig, RTMP_SIG_SIZE);
+#endif
+
+  if (!WriteN(r, serverbuf, RTMP_SIG_SIZE + 1))
+    return false;
+
+  if (ReadN(r, clientsig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
+    return false;
+
+  /* decode client response */
+  memcpy(&uptime, clientsig, 4);
+  uptime = ntohl(uptime);
+
+  Log(LOGDEBUG, "%s: Client Uptime : %d", __FUNCTION__, uptime);
+  Log(LOGDEBUG, "%s: Player Version: %d.%d.%d.%d", __FUNCTION__, clientsig[4],
+      clientsig[5], clientsig[6], clientsig[7]);
+
+#ifdef _DEBUG
+  Log(LOGDEBUG, "Client signature:");
+  LogHex(LOGDEBUG, clientsig, RTMP_SIG_SIZE);
+#endif
+
+  if (FP9HandShake)
+    {
+      /* we have to use this signature now to find the correct algorithms for getting the digest and DH positions */
+      int digestPosClient = GetDigestOffset1(clientsig, RTMP_SIG_SIZE);
+
+      if (!VerifyDigest(digestPosClient, clientsig, GenuineFPKey, 30))
+       {
+         Log(LOGWARNING, "Trying different position for client digest!\n");
+         digestPosClient = GetDigestOffset2(clientsig, RTMP_SIG_SIZE);
+
+         if (!VerifyDigest(digestPosServer, clientsig, GenuineFPKey, 30))
+           {
+             Log(LOGERROR, "Couldn't verify the client digest\n");     /* continuing anyway will probably fail */
+             return false;
+           }
+         dhposClient = GetDHOffset2(clientsig, RTMP_SIG_SIZE);
+       }
+      else
+        {
+         dhposClient = GetDHOffset1(clientsig, RTMP_SIG_SIZE);
+        }
+
+      Log(LOGDEBUG, "%s: Client DH public key offset: %d", __FUNCTION__,
+         dhposClient);
+
+      /* generate SWFVerification token (SHA256 HMAC hash of decompressed SWF, key are the last 32 bytes of the server handshake) */
+      if (r->Link.SWFHash.av_len)
+       {
+         const char swfVerify[] = { 0x01, 0x01 };
+
+         memcpy(r->Link.SWFVerificationResponse, swfVerify, 2);
+         AMF_EncodeInt32(&r->Link.SWFVerificationResponse[2], r->Link.SWFSize);
+         AMF_EncodeInt32(&r->Link.SWFVerificationResponse[6], r->Link.SWFSize);
+         HMACsha256(r->Link.SWFHash.av_val, SHA256_DIGEST_LENGTH,
+                    &serversig[RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH],
+                    SHA256_DIGEST_LENGTH, &r->Link.SWFVerificationResponse[10]);
+       }
+
+      /* do Diffie-Hellmann Key exchange for encrypted RTMP */
+      if (encrypted)
+       {
+         /* compute secret key */
+         uint8_t secretKey[128] = { 0 };
+
+         int len =
+           DHComputeSharedSecretKey(r->Link.dh,
+                                    (uint8_t *) &clientsig[dhposClient], 128,
+                                    secretKey);
+         if (len < 0)
+           {
+             Log(LOGDEBUG, "%s: Wrong secret key position!", __FUNCTION__);
+             return false;
+           }
+
+         Log(LOGDEBUG, "%s: Secret key: ", __FUNCTION__);
+         LogHex(LOGDEBUG, (char *) secretKey, 128);
+
+         InitRC4Encryption(secretKey,
+                           (uint8_t *) &clientsig[dhposClient],
+                           (uint8_t *) &serversig[dhposServer],
+                           &keyIn, &keyOut);
+       }
+
+
+      /* calculate response now */
+      char digestResp[SHA256_DIGEST_LENGTH];
+      char *signatureResp = clientsig+RTMP_SIG_SIZE-SHA256_DIGEST_LENGTH;
+
+      HMACsha256(&clientsig[digestPosClient], SHA256_DIGEST_LENGTH,
+                GenuineFMSKey, sizeof(GenuineFMSKey), digestResp);
+      HMACsha256(clientsig, RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH, digestResp,
+                SHA256_DIGEST_LENGTH, signatureResp);
+
+      /* some info output */
+      Log(LOGDEBUG,
+         "%s: Calculated digest key from secure key and server digest: ",
+         __FUNCTION__);
+      LogHex(LOGDEBUG, digestResp, SHA256_DIGEST_LENGTH);
+
+      Log(LOGDEBUG, "%s: Server signature calculated:", __FUNCTION__);
+      LogHex(LOGDEBUG, signatureResp, SHA256_DIGEST_LENGTH);
+    }
+  else
+    {
+      uptime = htonl(RTMP_GetTime());
+      memcpy(clientsig+4, &uptime, 4);
+    }
+
+#ifdef _DEBUG
+  Log(LOGDEBUG, "%s: Sending handshake response: ",
+    __FUNCTION__);
+  LogHex(LOGDEBUG, clientsig, RTMP_SIG_SIZE);
+#endif
+  if (!WriteN(r, clientsig, RTMP_SIG_SIZE))
+    return false;
+
+  /* 2nd part of handshake */
+  if (ReadN(r, clientsig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
+    return false;
+
+#ifdef _DEBUG
+  Log(LOGDEBUG, "%s: 2nd handshake: ", __FUNCTION__);
+  LogHex(LOGDEBUG, clientsig, RTMP_SIG_SIZE);
+#endif
+
+  if (FP9HandShake)
+    {
+      char signature[SHA256_DIGEST_LENGTH];
+      char digest[SHA256_DIGEST_LENGTH];
+
+      Log(LOGDEBUG, "%s: Client sent signature:", __FUNCTION__);
+      LogHex(LOGDEBUG, &clientsig[RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH],
+            SHA256_DIGEST_LENGTH);
+
+      /* verify client response */
+      HMACsha256(&clientsig[digestPosServer], SHA256_DIGEST_LENGTH,
+                GenuineFPKey, sizeof(GenuineFPKey), digest);
+      HMACsha256(clientsig, RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH, digest,
+                SHA256_DIGEST_LENGTH, signature);
+
+      /* show some information */
+      Log(LOGDEBUG, "%s: Digest key: ", __FUNCTION__);
+      LogHex(LOGDEBUG, digest, SHA256_DIGEST_LENGTH);
+
+      Log(LOGDEBUG, "%s: Signature calculated:", __FUNCTION__);
+      LogHex(LOGDEBUG, signature, SHA256_DIGEST_LENGTH);
+      if (memcmp
+         (signature, &clientsig[RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH],
+          SHA256_DIGEST_LENGTH) != 0)
+       {
+         Log(LOGWARNING, "%s: Client not genuine Adobe!", __FUNCTION__);
+         return false;
+       }
+      else
+       {
+         Log(LOGDEBUG, "%s: Genuine Adobe Flash Player", __FUNCTION__);
+       }
+
+      if (encrypted)
+       {
+         /* set keys for encryption from now on */
+         r->Link.rc4keyIn = keyIn;
+         r->Link.rc4keyOut = keyOut;
+
+         char buff[RTMP_SIG_SIZE];
+
+         /* update the keystreams */
+         if (r->Link.rc4keyIn)
+           {
+             RC4(r->Link.rc4keyIn, RTMP_SIG_SIZE, (uint8_t *) buff,
+                 (uint8_t *) buff);
+           }
+
+         if (r->Link.rc4keyOut)
+           {
+             RC4(r->Link.rc4keyOut, RTMP_SIG_SIZE, (uint8_t *) buff,
+                 (uint8_t *) buff);
+           }
+       }
+    }
+  else
+    {
+      if (memcmp(serversig, clientsig, RTMP_SIG_SIZE) != 0)
+       {
+         Log(LOGWARNING, "%s: client signature does not match!",
+             __FUNCTION__);
+       }
+    }
+
+  Log(LOGDEBUG, "%s: Handshaking finished....", __FUNCTION__);
+  return true;
+}
diff --git a/rtmp.c b/rtmp.c
index b3430b2ee3bebd8fb7b80c42df7389a50c1b37ff..630ac5e40cd906735f83beb372f0bd53653f9eb4 100644 (file)
--- a/rtmp.c
+++ b/rtmp.c
@@ -1050,7 +1050,7 @@ RTMP_SendPause(RTMP * r, bool DoPause, double dTime)
   RTMPPacket packet;
   char pbuf[256];
 
-  packet.m_nChannel = 0x08;    // video channel 
+  packet.m_nChannel = 0x08;    // video channel
   packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
   packet.m_packetType = 0x14;  // invoke
   packet.m_nInfoField1 = 0;
@@ -1078,7 +1078,7 @@ SendSeek(RTMP * r, double dTime)
   RTMPPacket packet;
   char pbuf[256];
 
-  packet.m_nChannel = 0x08;    // video channel 
+  packet.m_nChannel = 0x08;    // video channel
   packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
   packet.m_packetType = 0x14;  // invoke
   packet.m_nInfoField1 = 0;
@@ -1163,7 +1163,7 @@ SendCheckBW(RTMP * r)
 
   packet.m_nBodySize = enc - packet.m_body;
 
-  // triggers _onbwcheck and eventually results in _onbwdone 
+  // triggers _onbwcheck and eventually results in _onbwdone
   return SendRTMP(r, &packet, false);
 }
 
@@ -1844,7 +1844,7 @@ ReadPacket(RTMP * r, RTMPPacket * packet)
     {
       packet->m_nInfoField1 = AMF_DecodeInt24(header);
 
-      //Log(LOGDEBUG, "%s, reading RTMP packet chunk on channel %x, headersz %i, timestamp %i, abs timestamp %i", __FUNCTION__, packet.m_nChannel, nSize, packet.m_nInfoField1, packet.m_hasAbsTimestamp); 
+      //Log(LOGDEBUG, "%s, reading RTMP packet chunk on channel %x, headersz %i, timestamp %i, abs timestamp %i", __FUNCTION__, packet.m_nChannel, nSize, packet.m_nInfoField1, packet.m_hasAbsTimestamp);
 
       if (nSize >= 6)
        {
@@ -1896,9 +1896,9 @@ ReadPacket(RTMP * r, RTMPPacket * packet)
     {
       packet->m_nTimeStamp = packet->m_nInfoField1;
 
-      // make packet's timestamp absolute 
+      // make packet's timestamp absolute
       if (!packet->m_hasAbsTimestamp)
-       packet->m_nTimeStamp += r->m_channelTimestamp[packet->m_nChannel];      // timestamps seem to be always relative!! 
+       packet->m_nTimeStamp += r->m_channelTimestamp[packet->m_nChannel];      // timestamps seem to be always relative!!
 
       r->m_channelTimestamp[packet->m_nChannel] = packet->m_nTimeStamp;
 
@@ -1963,25 +1963,25 @@ static bool
 HandShake(RTMP * r, bool FP9HandShake)
 {
   int i;
-  char clientsig[RTMP_SIG_SIZE + 1];
+  char clientbuf[RTMP_SIG_SIZE + 1], *clientsig = clientbuf+1;
   char serversig[RTMP_SIG_SIZE];
 
-  clientsig[0] = 0x03;         // not encrypted
+  clientbuf[0] = 0x03;         // not encrypted
 
   uint32_t uptime = htonl(RTMP_GetTime());
-  memcpy(clientsig + 1, &uptime, 4);
+  memcpy(clientsig, &uptime, 4);
 
-  memset(&clientsig[5], 0, 4);
+  memset(&clientsig[4], 0, 4);
 
 #ifdef _DEBUG
-  for (i = 9; i < RTMP_SIG_SIZE; i++)
+  for (i = 8; i < RTMP_SIG_SIZE; i++)
     clientsig[i] = 0xff;
 #else
-  for (i = 9; i < RTMP_SIG_SIZE; i++)
+  for (i = 8; i < RTMP_SIG_SIZE; i++)
     clientsig[i] = (char) (rand() % 256);
 #endif
 
-  if (!WriteN(r, clientsig, RTMP_SIG_SIZE + 1))
+  if (!WriteN(r, clientbuf, RTMP_SIG_SIZE + 1))
     return false;
 
   char type;
@@ -1990,9 +1990,9 @@ HandShake(RTMP * r, bool FP9HandShake)
 
   Log(LOGDEBUG, "%s: Type Answer   : %02X", __FUNCTION__, type);
 
-  if (type != clientsig[0])
+  if (type != clientbuf[0])
     Log(LOGWARNING, "%s: Type mismatch: client sent %d, server answered %d",
-       __FUNCTION__, clientsig[0], type);
+       __FUNCTION__, clientbuf[0], type);
 
   if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
     return false;
@@ -2011,11 +2011,72 @@ HandShake(RTMP * r, bool FP9HandShake)
   if (!WriteN(r, serversig, RTMP_SIG_SIZE))
     return false;
 
-  char resp[RTMP_SIG_SIZE];
-  if (ReadN(r, resp, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
+  if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
+    return false;
+
+  bool bMatch = (memcmp(serversig, clientsig, RTMP_SIG_SIZE) == 0);
+  if (!bMatch)
+    {
+      Log(LOGWARNING, "%s, client signature does not match!", __FUNCTION__);
+    }
+  return true;
+}
+
+static bool
+SHandShake(RTMP * r)
+{
+  int i;
+  char serverbuf[RTMP_SIG_SIZE + 1], *serversig = serverbuf+1;
+  char clientsig[RTMP_SIG_SIZE];
+  uint32_t uptime;
+
+  if (ReadN(r, serverbuf, 1) != 1)     // 0x03 or 0x06
+    return false;
+
+  Log(LOGDEBUG, "%s: Type Request  : %02X", __FUNCTION__, serverbuf[0]);
+
+  if (serverbuf[0] != 3)
+    {
+      Log(LOGERROR, "%s: Type unknown: client sent %02X",
+         __FUNCTION__, serverbuf[0]);
+      return false;
+    }
+
+  uptime = htonl(RTMP_GetTime());
+  memcpy(serversig, &uptime, 4);
+
+  memset(&serversig[4], 0, 4);
+#ifdef _DEBUG
+  for (i = 8; i < RTMP_SIG_SIZE; i++)
+    serversig[i] = 0xff;
+#else
+  for (i = 8; i < RTMP_SIG_SIZE; i++)
+    serversig[i] = (char) (rand() % 256);
+#endif
+
+  if (!WriteN(r, serverbuf, RTMP_SIG_SIZE + 1))
+    return false;
+
+  if (ReadN(r, clientsig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
+    return false;
+
+  // decode client response
+
+  memcpy(&uptime, clientsig, 4);
+  uptime = ntohl(uptime);
+
+  Log(LOGDEBUG, "%s: Client Uptime : %d", __FUNCTION__, uptime);
+  Log(LOGDEBUG, "%s: Player Version: %d.%d.%d.%d", __FUNCTION__, clientsig[4],
+      clientsig[5], clientsig[6], clientsig[7]);
+
+  // 2nd part of handshake
+  if (!WriteN(r, clientsig, RTMP_SIG_SIZE))
+    return false;
+
+  if (ReadN(r, clientsig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
     return false;
 
-  bool bMatch = (memcmp(resp, clientsig + 1, RTMP_SIG_SIZE) == 0);
+  bool bMatch = (memcmp(serversig, clientsig, RTMP_SIG_SIZE) == 0);
   if (!bMatch)
     {
       Log(LOGWARNING, "%s, client signature does not match!", __FUNCTION__);
@@ -2115,6 +2176,12 @@ SendRTMP(RTMP * r, RTMPPacket * packet, bool queue)
   return true;
 }
 
+bool
+RTMP_Serve(RTMP *r)
+{
+  return SHandShake(r);
+}
+
 void
 RTMP_Close(RTMP * r)
 {
diff --git a/rtmp.h b/rtmp.h
index affe2d1eb1f5a33f5bcd52c525f8c0c0acbe4ecc..406444ef51b04eab5ad2edb666889642eb7457b6 100644 (file)
--- a/rtmp.h
+++ b/rtmp.h
@@ -75,7 +75,7 @@ typedef struct RTMPPacket
   BYTE m_packetType;
   int m_nChannel;
   int32_t m_nInfoField1;       // 3 first bytes
-  int32_t m_nInfoField2;       // last 4 bytes in a long header, absolute timestamp for long headers, relative timestamp for short headers 
+  int32_t m_nInfoField2;       // last 4 bytes in a long header, absolute timestamp for long headers, relative timestamp for short headers
   bool m_hasAbsTimestamp;      // timestamp absolute or relative?
   uint32_t m_nTimeStamp;       // absolute timestamp
   uint32_t m_nBodySize;
@@ -182,6 +182,7 @@ void RTMP_SetupStream(RTMP *r, int protocol,
                      uint32_t dLength, bool bLiveStream, long int timeout);
 
 bool RTMP_Connect(RTMP *r);
+bool RTMP_Serve(RTMP *r);
 
 bool RTMP_IsConnected(RTMP *r);
 bool RTMP_IsTimedout(RTMP *r);