crtmpserver 中的Handshake(握手操作) -- ValidateClientScheme(验证客户端模式)

文章结构

**文章结构**

基本概念

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,225评论 19 139
  • 原文:https://blog.ethereum.org/2016/02/17/smart-contracts-c...
    orcl_zhang阅读 1,149评论 2 49
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,825评论 18 399
  • 我们有五个月没有见面了,你还是不想见我,你觉得我不是你所期盼的那一位。 过去发生了好多,我的不主动,最终让你失...
    luminous源阅读 124评论 0 1
  • 和家人一起总是最开心的。远嫁心不远,近乡情不怯。 那天晩上高兴多喝了几杯,乃至拿现金发发发,在场的都发[偷笑][偷...
    如缘夏雨阅读 196评论 0 0