文章結構
**文章結構**
基本概念
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**
可以看出其中C2
是Copy 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
的區別主要是: key
和digest
的順序.
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
端發送的C1
是scheme 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