直播

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

2016-11-01  本文已影响178人  FlyingPenguin

文章结构

**文章结构**

基本概念

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**

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**

实际例子:


**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

上一篇 下一篇

猜你喜欢

热点阅读