美文网首页直播
crtmpserver 中的Handshake(握手操作) --

crtmpserver 中的Handshake(握手操作) --

作者: FlyingPenguin | 来源:发表于2016-11-01 15:36 被阅读178次

    文章结构

    **文章结构**

    基本概念

    RTMP Handshake Diagram
    **RTMP Handshake Diagram**
    注: Adobe公布的rtmp协议是个不完整的协议,上图只是一个基本的流程,实际应用时可能存在不一致.
    crtmpserver中的实际握手过程是:
    **crtmpserver中的实际握手过程** **C1 in C0 + C1** **S2 in S0 + S1 + S2** **S1 in S0 + S1 + S2** **C2**

    可以看出其中C2Copy of S1; S2并不是Copy of C1.

    C1 & S1

    The C1 and S1 packets are 1536 octets long.

    C1 and S1的长度均为1536字节.

    **C1 and S1 bits**
    • Time (4 bytes):
      This field contains a timestamp, which SHOULD be used as the epoch for all future chunks sent from this endpoint.
      This may be 0, or some arbitrary value.
      To synchronize multiple chunkstreams, the endpoint may wish to send the current value of the other chunkstream’s timestamp.
    • Zero (4 bytes):
      This field MUST be all 0s.
    • Random data (1528 bytes):
      This field can contain any arbitrary values.
      Since each endpoint has to distinguish between the response to the handshake it has initiated and the handshake initiated by its peer,this data SHOULD send something sufficiently random.
      But there is no need for cryptographically-secure randomness, or even dynamic values.

    C1 & S1有两种Scheme: Scheme 0 & Scheme 1

    **Scheme 0 & Scheme 1**
    Scheme 0 & Scheme 1的区别主要是: keydigest的顺序.
    HMACSHA256

    HMACSHA256是一种验证算法.
    基于哈希的消息验证代码 (HMAC) 将密钥与消息数据混合,使用哈希函数对结果进行哈希处理,再次将哈希值与密钥混合,然后第二次应用哈希函数。
    输出哈希的长度为 256 位

    握手过程中的客户端验证详解

    Handshake 时序图
    **Handshake in crtmpserver 时序图**
    核心函数InboundRTMPProtocol::PerformHandshake(IOBuffer &buffer, bool encrypted)中完成了对C0+C1的处理(如对C1进行验证),然后形成S0+S1+S2响应报文.
    对客户端发送的C1进行验证
    InboundRTMPProtocol::ValidateClient
    // 对客户端发送的C1进行验证
    bool InboundRTMPProtocol::ValidateClient(IOBuffer &inputBuffer) {
        if (_currentFPVersion == 0) {
            WARN("This version of player doesn't support validation");
            return true;
        }
    
        // 先对scheme 0进行验证
        if (ValidateClientScheme(inputBuffer, 0)) {
            _validationScheme = 0;
            return true;
        }
    
        // 再对scheme 1进行验证
        if (ValidateClientScheme(inputBuffer, 1)) {
            _validationScheme = 1;
            return true;
        }
        FATAL("Unable to validate client");
        return false;
    }
    
    InboundRTMPProtocol::ValidateClientScheme
    bool InboundRTMPProtocol::ValidateClientScheme(IOBuffer &inputBuffer, uint8_t scheme) {
        uint8_t *pBuffer = GETIBPOINTER(inputBuffer);
    
        uint32_t clientDigestOffset = GetDigestOffset(pBuffer, scheme);
    
        uint8_t *pTempBuffer = new uint8_t[1536 - 32];
        memcpy(pTempBuffer, pBuffer, clientDigestOffset);
        memcpy(pTempBuffer + clientDigestOffset, pBuffer + clientDigestOffset + 32,
                1536 - clientDigestOffset - 32);
    
        uint8_t *pTempHash = new uint8_t[512];
        HMACsha256(pTempBuffer, 1536 - 32, genuineFPKey, 30, pTempHash);
    
        bool result = true;
        for (uint32_t i = 0; i < 32; i++) {
            if (pBuffer[clientDigestOffset + i] != pTempHash[i]) {
                result = false;
                break;
            }
        }
    
        delete[] pTempBuffer;
        delete[] pTempHash;
    
        return result;
    }
    

    以scheme 0模式进行举例:


    **InboundRTMPProtocol::ValidateClientScheme in scheme 0 mode**
    • Step 1.
      通过digest的前四个字节计算出clientDigestOffset.
      图中AC的长度即为clientDigestOffset.
      C点向后32字节即为密文即CD段.
    • Step 2.
      AC段和DE段依次拷贝至临时缓冲pTempBuffer.
      利用pTempBuffer利用HMACsha256验证算法进行正向加密,得到
      256字节的密文.
    • Step 3.
      利用Step 2得到的密文的前32位与CD段的密文进行逐字节比较, 如果完全匹配,说明client端发送的C1scheme 0模式.

    实际例子:


    **pBuffer[clientDigestOffset]起32个字节** **pTempHash中的密文**

    通过对pBuffer[clientDigestOffset]起32个字节和pTempHash的前32字节进行逐字节比较, 发现完全相同, 本次验证通过.

    BaseRTMPProtocol::GetDigestOffset
    // 获取Digest的offset
    uint32_t BaseRTMPProtocol::GetDigestOffset(uint8_t *pBuffer, uint8_t schemeNumber) {
        switch (schemeNumber) {
            case 0:
            {
                return GetDigestOffset0(pBuffer);
            }
            case 1:
            {
                return GetDigestOffset1(pBuffer);
            }
            default:
            {
                WARN("Invalid scheme number: %hhu. Defaulting to 0", schemeNumber);
                return GetDigestOffset0(pBuffer);
            }
        }
    }
    
    // scheme 0 获取Digest的offset
    uint32_t BaseRTMPProtocol::GetDigestOffset0(uint8_t *pBuffer) {
        uint32_t offset = pBuffer[8] + pBuffer[9] + pBuffer[10] + pBuffer[11];
        offset = offset % 728;
        offset = offset + 12;
        if (offset + 32 >= 1536) {
            ASSERT("Invalid digest offset");
        }
        return offset;
    }
    
    **BaseRTMPProtocol::GetDigestOffset0**
    // scheme 1 获取Digest的offset
    uint32_t BaseRTMPProtocol::GetDigestOffset1(uint8_t *pBuffer) {
        uint32_t offset = pBuffer[772] + pBuffer[773] + pBuffer[774] + pBuffer[775];
        offset = offset % 728;
        offset = offset + 776;
        if (offset + 32 >= 1536) {
            ASSERT("Invalid digest offset");
        }
        return offset;
    }
    
    
    **BaseRTMPProtocol::GetDigestOffset1**

    References:
    https://en.wikipedia.org/wiki/Real-Time_Messaging_Protocol
    https://technet.microsoft.com/zh-cn/library/hh831711.aspx
    http://blog.sina.com.cn/s/blog_51396f890102ezcp.html
    http://blog.csdn.net/win_lin/article/details/13006803

    相关文章

      网友评论

        本文标题:crtmpserver 中的Handshake(握手操作) --

        本文链接:https://www.haomeiwen.com/subject/rnhmuttx.html