// Copyright (c) 1996-2020, Live Networks, Inc. All rights reserved // This code may not be copied or used in any form without permission from Live Networks, Inc. // // The SRTP 'Cryptographic Context', used in all of our uses of SRTP. // Implementation #include "SRTPCryptographicContext.hh" #ifndef NO_OPENSSL #include "HMAC_SHA1.hh" #include #endif #ifdef DEBUG #include #endif SRTPCryptographicContext ::SRTPCryptographicContext(MIKEYState const& mikeyState) #ifndef NO_OPENSSL : fMIKEYState(mikeyState), fHaveReceivedSRTPPackets(False), fSRTCPIndex(0) { // Begin by doing a key derivation, to generate the keying data that we need: performKeyDerivation(); #else { #endif } SRTPCryptographicContext::~SRTPCryptographicContext() { } Boolean SRTPCryptographicContext ::processIncomingSRTPPacket(u_int8_t* buffer, unsigned inPacketSize, unsigned& outPacketSize) { #ifndef NO_OPENSSL do { if (inPacketSize < 12) { // For SRTP, 12 is the minimum packet size (if unauthenticated) #ifdef DEBUG fprintf(stderr, "SRTPCryptographicContext::processIncomingSRTPPacket(): Error: Packet size %d is too short for SRTP!\n", inPacketSize); #endif break; } unsigned const numBytesPastEncryption = SRTP_MKI_LENGTH + (weAuthenticate() ? SRTP_AUTH_TAG_LENGTH : 0); if (inPacketSize <= numBytesPastEncryption) { #ifdef DEBUG fprintf(stderr, "SRTPCryptographicContext::processIncomingSRTPPacket(): Error: Packet size %d is too short for encrpytion and/or authentication!\n", inPacketSize); #endif break; } // Begin by figuring out this packet's 'index' (ROC and RTP sequence number), // and the ROC and RTP sequence number that will be used subsequently, provided that // this packet authenticates OK: u_int16_t const rtpSeqNum = (buffer[2]<<8)|buffer[3]; u_int32_t nextROC, thisPacketsROC; u_int16_t nextHighRTPSeqNum; if (!fHaveReceivedSRTPPackets) { // First time: nextROC = thisPacketsROC = fROC = 0; nextHighRTPSeqNum = rtpSeqNum; } else { // Check whether the sequence number has rolled over, or is out-of-order: u_int16_t const SEQ_NUM_THRESHOLD = 0x1000; if (rtpSeqNum >= fPreviousHighRTPSeqNum) { // normal case, or out-of-order packet that crosses a rollover: if (rtpSeqNum - fPreviousHighRTPSeqNum < SEQ_NUM_THRESHOLD) { // normal case: nextROC = thisPacketsROC = fROC; nextHighRTPSeqNum = rtpSeqNum; } else { // out-of-order packet that crosses rollover: nextROC = fROC; thisPacketsROC = fROC-1; nextHighRTPSeqNum = fPreviousHighRTPSeqNum; } } else { // rollover, or out-of-order packet that crosses a rollover: if (fPreviousHighRTPSeqNum - rtpSeqNum > SEQ_NUM_THRESHOLD) { // rollover: nextROC = thisPacketsROC = fROC+1; nextHighRTPSeqNum = rtpSeqNum; } else { // out-of-order packet (that doesn't cross a rollover): nextROC = thisPacketsROC = fROC; nextHighRTPSeqNum = fPreviousHighRTPSeqNum; } } } if (weAuthenticate()) { // Authenticate the packet. unsigned const numBytesToAuthenticate = inPacketSize - (SRTP_MKI_LENGTH + SRTP_AUTH_TAG_LENGTH); // ASSERT: > 0 u_int8_t const* authenticationTag = &buffer[inPacketSize - SRTP_AUTH_TAG_LENGTH]; if (!verifySRTPAuthenticationTag(buffer, numBytesToAuthenticate, thisPacketsROC, authenticationTag)) { #ifdef DEBUG fprintf(stderr, "SRTPCryptographicContext::processIncomingSRTPPacket(): Failed to authenticate incoming SRTP packet!\n"); #endif break; } } // Now that we've verified the packet, set the 'index values' for next time: fROC = nextROC; fPreviousHighRTPSeqNum = nextHighRTPSeqNum; fHaveReceivedSRTPPackets = True; if (weEncryptSRTP()) { // Decrypt the SRTP packet. It has the index "thisPacketsROC" with "rtpSeqNum": u_int64_t index = (thisPacketsROC<<16)|rtpSeqNum; // Figure out the RTP header size. This will tell us which bytes to decrypt: unsigned rtpHeaderSize = 12; // at least the basic 12-byte header rtpHeaderSize += (buffer[0]&0x0F)*4; // # CSRC identifiers if ((buffer[0]&0x10) != 0) { // There's a RTP extension header. Add its size: if (inPacketSize < rtpHeaderSize + 4) { #ifdef DEBUG fprintf(stderr, "SRTPCryptographicContext::processIncomingSRTPPacket(): Error: Packet size %d is shorter than the minimum specified RTP header size %d!\n", inPacketSize, rtpHeaderSize + 4); #endif break; } u_int16_t hdrExtLength = (buffer[rtpHeaderSize+2]<<8)|buffer[rtpHeaderSize+3]; rtpHeaderSize += 4 + hdrExtLength*4; } unsigned const offsetToEncryptedBytes = rtpHeaderSize; unsigned numEncryptedBytes = inPacketSize - numBytesPastEncryption; // ASSERT: > 0 if (offsetToEncryptedBytes > numEncryptedBytes) { #ifdef DEBUG fprintf(stderr, "SRTPCryptographicContext::processIncomingSRTPPacket(): Error: RTP header size %d (expected <= %d) is too large!\n", rtpHeaderSize, numEncryptedBytes); #endif break; } numEncryptedBytes -= offsetToEncryptedBytes; u_int32_t const SSRC = (buffer[8]<<24)|(buffer[9]<<16)|(buffer[10]<<8)|buffer[11]; decryptSRTPPacket(index, SSRC, &buffer[offsetToEncryptedBytes], numEncryptedBytes); outPacketSize = inPacketSize - numBytesPastEncryption; // trim to what we use } return True; } while (0); #endif // An error occurred in the handling of the packet: return False; } Boolean SRTPCryptographicContext ::processIncomingSRTCPPacket(u_int8_t* buffer, unsigned inPacketSize, unsigned& outPacketSize) { #ifndef NO_OPENSSL do { if (inPacketSize < 12) { // For SRTCP, 8 is the minumum RTCP packet size, but there's also a mandatory // 4-byte "E+SRTCP index" word. #ifdef DEBUG fprintf(stderr, "SRTPCryptographicContext::processIncomingSRTCPPacket(): Error: Packet size %d is too short for SRTCP!\n", inPacketSize); #endif break; } unsigned const numBytesPastEncryption = 4/*E+SRTCP index*/ + SRTP_MKI_LENGTH + (weAuthenticate() ? SRTP_AUTH_TAG_LENGTH : 0); if (inPacketSize <= numBytesPastEncryption) { #ifdef DEBUG fprintf(stderr, "SRTPCryptographicContext::processIncomingSRTCPPacket(): Error: Packet size %d is too short for encrpytion and/or authentication!\n", inPacketSize); #endif break; } if (weAuthenticate()) { // Authenticate the packet. unsigned const numBytesToAuthenticate = inPacketSize - (SRTP_MKI_LENGTH + SRTP_AUTH_TAG_LENGTH); // ASSERT: > 0 u_int8_t const* authenticationTag = &buffer[inPacketSize - SRTP_AUTH_TAG_LENGTH]; if (!verifySRTCPAuthenticationTag(buffer, numBytesToAuthenticate, authenticationTag)) { #ifdef DEBUG fprintf(stderr, "SRTPCryptographicContext::processIncomingSRTCPPacket(): Failed to authenticate incoming SRTCP packet!\n"); #endif break; } } if (weEncryptSRTCP()) { // Decrypt the SRTCP packet: unsigned numEncryptedBytes = inPacketSize - numBytesPastEncryption; // ASSERT: > 0 u_int8_t const* p = &buffer[numEncryptedBytes]; // E + SRTCP index u_int32_t E_plus_SRTCPIndex = (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3]; if ((E_plus_SRTCPIndex&0x80000000) != 0) { // The packet is encrypted unsigned const offsetToEncryptedBytes = 8; if (offsetToEncryptedBytes > numEncryptedBytes) { #ifdef DEBUG fprintf(stderr, "SRTPCryptographicContext::processIncomingSRTCPPacket(): Error: RTCP packet size %d is too small!\n", numEncryptedBytes); #endif break; } numEncryptedBytes -= offsetToEncryptedBytes; u_int32_t index = E_plus_SRTCPIndex&0x7FFFFFFF; u_int32_t const SSRC = (buffer[4]<<24)|(buffer[5]<<16)|(buffer[6]<<8)|buffer[7]; decryptSRTCPPacket(index, SSRC, &buffer[offsetToEncryptedBytes], numEncryptedBytes); } outPacketSize = inPacketSize - numBytesPastEncryption; // trim to what we use } return True; } while (0); #endif // An error occurred in the handling of the packet: return False; } Boolean SRTPCryptographicContext ::processOutgoingSRTCPPacket(u_int8_t* buffer, unsigned inPacketSize, unsigned& outPacketSize) { #ifndef NO_OPENSSL do { // Encrypt the appropriate part of the packet. u_int8_t eFlag = 0x00; if (weEncryptSRTCP()) { unsigned const unencryptedHeaderSize = 8; if (inPacketSize < unencryptedHeaderSize) { // packet is too small // Hack: Let small, non RTCP packets through w/o encryption; they may be used to // punch through NATs outPacketSize = inPacketSize; return True; } unsigned const encryptedDataSize = inPacketSize - unencryptedHeaderSize; u_int8_t* const dataToEncrypt = &buffer[unencryptedHeaderSize]; u_int32_t const ssrc = (buffer[4]<<24)|(buffer[5]<<16)|(buffer[6]<<8)|buffer[7]; encryptSRTCPPacket(fSRTCPIndex, ssrc, dataToEncrypt, encryptedDataSize); eFlag = 0x80; } outPacketSize = inPacketSize; // initially // Add 4 bytes for the 'E' flag and SRTCP index: buffer[outPacketSize++] = (fSRTCPIndex>>24)|eFlag; buffer[outPacketSize++] = fSRTCPIndex>>16; buffer[outPacketSize++] = fSRTCPIndex>>8; buffer[outPacketSize++] = fSRTCPIndex; ++fSRTCPIndex; // for next time // Add the MKI: buffer[outPacketSize++] = MKI()>>24; buffer[outPacketSize++] = MKI()>>16; buffer[outPacketSize++] = MKI()>>8; buffer[outPacketSize++] = MKI(); // Generate and add an authentication tag over the data built so far (except the MKI) outPacketSize += generateSRTCPAuthenticationTag(buffer, outPacketSize-SRTP_MKI_LENGTH, &buffer[outPacketSize]); return True; } while (0); #endif // An error occurred: return False; } #ifndef NO_OPENSSL unsigned SRTPCryptographicContext ::generateSRTCPAuthenticationTag(u_int8_t const* dataToAuthenticate, unsigned numBytesToAuthenticate, u_int8_t* resultAuthenticationTag) { return generateAuthenticationTag(fDerivedKeys.srtcp, dataToAuthenticate, numBytesToAuthenticate, resultAuthenticationTag); } Boolean SRTPCryptographicContext ::verifySRTPAuthenticationTag(u_int8_t* dataToAuthenticate, unsigned numBytesToAuthenticate, u_int32_t roc, u_int8_t const* authenticationTag) { // Append the (4-byte) 'ROC' (roll-over counter) to "dataToAuthenticate" before computing // the authentication tag. We can do this because we have enough space after // "dataToAuthenticate": // - If we're encrypted, then there's assumed to be a (4-byte) MKI there. Just overwrite // that. (If we need the MKI, we could copy it beforehand; later, allow for there being // no MKI #####) // - If we're not encrypted, then the ROC will overwrite part of the existing // authentication tag, so we need to make a copy of this. u_int8_t const* existingAuthenticationTag; Boolean haveMKI = True; // later, allow for there being no MKI ##### u_int8_t authenticationTagCopy[SRTP_AUTH_TAG_LENGTH]; if (fMIKEYState.encryptSRTP() && haveMKI) { existingAuthenticationTag = authenticationTag; } else { memcpy(authenticationTagCopy, authenticationTag, sizeof authenticationTagCopy); existingAuthenticationTag = authenticationTagCopy; } dataToAuthenticate[numBytesToAuthenticate++] = roc>>24; dataToAuthenticate[numBytesToAuthenticate++] = roc>>16; dataToAuthenticate[numBytesToAuthenticate++] = roc>>8; dataToAuthenticate[numBytesToAuthenticate++] = roc; return verifyAuthenticationTag(fDerivedKeys.srtp, dataToAuthenticate, numBytesToAuthenticate, existingAuthenticationTag); } Boolean SRTPCryptographicContext ::verifySRTCPAuthenticationTag(u_int8_t const* dataToAuthenticate, unsigned numBytesToAuthenticate, u_int8_t const* authenticationTag) { return verifyAuthenticationTag(fDerivedKeys.srtcp, dataToAuthenticate, numBytesToAuthenticate, authenticationTag); } void SRTPCryptographicContext ::decryptSRTPPacket(u_int64_t index, u_int32_t ssrc, u_int8_t* data, unsigned numDataBytes) { cryptData(fDerivedKeys.srtp, index, ssrc, data, numDataBytes); } void SRTPCryptographicContext ::decryptSRTCPPacket(u_int32_t index, u_int32_t ssrc, u_int8_t* data, unsigned numDataBytes) { cryptData(fDerivedKeys.srtcp, (u_int64_t)index, ssrc, data, numDataBytes); } void SRTPCryptographicContext ::encryptSRTCPPacket(u_int32_t index, u_int32_t ssrc, u_int8_t* data, unsigned numDataBytes) { cryptData(fDerivedKeys.srtcp, (u_int64_t)index, ssrc, data, numDataBytes); } unsigned SRTPCryptographicContext ::generateAuthenticationTag(derivedKeys& keysToUse, u_int8_t const* dataToAuthenticate, unsigned numBytesToAuthenticate, u_int8_t* resultAuthenticationTag) { if (SRTP_AUTH_TAG_LENGTH > SHA1_DIGEST_LEN) return 0; // sanity check; shouldn't happen u_int8_t computedAuthTag[SHA1_DIGEST_LEN]; HMAC_SHA1(keysToUse.authKey, sizeof keysToUse.authKey, dataToAuthenticate, numBytesToAuthenticate, computedAuthTag); for (unsigned i = 0; i < SRTP_AUTH_TAG_LENGTH; ++i) { resultAuthenticationTag[i] = computedAuthTag[i]; } return SRTP_AUTH_TAG_LENGTH; } Boolean SRTPCryptographicContext ::verifyAuthenticationTag(derivedKeys& keysToUse, u_int8_t const* dataToAuthenticate, unsigned numBytesToAuthenticate, u_int8_t const* authenticationTag) { u_int8_t computedAuthTag[SHA1_DIGEST_LEN]; HMAC_SHA1(keysToUse.authKey, sizeof keysToUse.authKey, dataToAuthenticate, numBytesToAuthenticate, computedAuthTag); if (SRTP_AUTH_TAG_LENGTH > SHA1_DIGEST_LEN) return False; // sanity check for (unsigned i = 0; i < SRTP_AUTH_TAG_LENGTH; ++i) { if (computedAuthTag[i] != authenticationTag[i]) return False; } return True; } void SRTPCryptographicContext::cryptData(derivedKeys& keys, u_int64_t index, u_int32_t ssrc, u_int8_t* data, unsigned numDataBytes) { // Begin by constructing the IV: (salt * 2^16) XOR (ssrc * 2^64) XOR (index * 2^16) u_int8_t iv[SRTP_CIPHER_KEY_LENGTH]; memmove(iv, keys.salt, SRTP_CIPHER_SALT_LENGTH); iv[SRTP_CIPHER_SALT_LENGTH] = iv[SRTP_CIPHER_SALT_LENGTH + 1] = 0; // (This is based upon the fact that SRTP_CIPHER_KEY_LENGTH == SRTP_CIPHER_SALT_LENGTH + 2) iv[sizeof iv-12] ^= ssrc>>24; iv[sizeof iv-11] ^= ssrc>>16; iv[sizeof iv-10] ^= ssrc>>8; iv[sizeof iv-9] ^= ssrc; iv[sizeof iv-8] ^= index>>40; iv[sizeof iv-7] ^= index>>32; iv[sizeof iv-6] ^= index>>24; iv[sizeof iv-5] ^= index>>16; iv[sizeof iv-4] ^= index>>8; iv[sizeof iv-3] ^= index; // Now generate as many blocks of the keystream as we need, by repeatedly encrypting // the IV using our cipher key. (After each step, we increment the IV by 1.) // We then XOR the keystream into the provided data, to do the en/decryption. AES_KEY key; AES_set_encrypt_key(keys.cipherKey, 8*SRTP_CIPHER_KEY_LENGTH, &key); while (numDataBytes > 0) { u_int8_t keyStream[SRTP_CIPHER_KEY_LENGTH]; AES_encrypt(iv, keyStream, &key); unsigned numBytesToUse = numDataBytes < SRTP_CIPHER_KEY_LENGTH ? numDataBytes : SRTP_CIPHER_KEY_LENGTH; for (unsigned i = 0; i < numBytesToUse; ++i) data[i] ^= keyStream[i]; data += numBytesToUse; numDataBytes -= numBytesToUse; // Increment the IV by 1: u_int8_t* ptr = &iv[sizeof iv]; do { --ptr; ++*ptr; } while (*ptr == 0x00); } } void SRTPCryptographicContext::performKeyDerivation() { // Perform a key derivation for the master key+salt, as defined // by RFC 3711, section 4.3: deriveKeysFromMaster(masterKey(), masterSalt(), fDerivedKeys); } #define deriveKey(label, resultKey) deriveSingleKey(masterKey, salt, label, sizeof resultKey, resultKey) void SRTPCryptographicContext ::deriveKeysFromMaster(u_int8_t const* masterKey, u_int8_t const* salt, allDerivedKeys& allKeysResult) { // Derive cipher, salt, and auth keys for both SRTP and SRTCP: deriveKey(label_srtp_encryption, allKeysResult.srtp.cipherKey); deriveKey(label_srtp_msg_auth, allKeysResult.srtp.authKey); deriveKey(label_srtp_salt, allKeysResult.srtp.salt); deriveKey(label_srtcp_encryption, allKeysResult.srtcp.cipherKey); deriveKey(label_srtcp_msg_auth, allKeysResult.srtcp.authKey); deriveKey(label_srtcp_salt, allKeysResult.srtcp.salt); } #define KDF_PRF_CIPHER_BLOCK_LENGTH 16 void SRTPCryptographicContext ::deriveSingleKey(u_int8_t const* masterKey, u_int8_t const* salt, SRTPKeyDerivationLabel label, unsigned resultKeyLength, u_int8_t* resultKey) { // This looks a little different from the mechanism described in RFC 3711, section 4.3, but // it's what the 'libsrtp' code does, so I hope it's functionally equivalent: AES_KEY key; AES_set_encrypt_key(masterKey, 8*SRTP_CIPHER_KEY_LENGTH, &key); u_int8_t counter[KDF_PRF_CIPHER_BLOCK_LENGTH]; // Set the first bytes of "counter" to be the 'salt'; set the remainder to zero: memmove(counter, salt, SRTP_CIPHER_SALT_LENGTH); for (unsigned i = SRTP_CIPHER_SALT_LENGTH; i < sizeof counter; ++i) { counter[i] = 0; } // XOR "label" into byte 7 of "counter": counter[7] ^= label; // And use the resulting "counter" as the plaintext: u_int8_t const* plaintext = counter; unsigned numBytesRemaining = resultKeyLength; while (numBytesRemaining > 0) { u_int8_t ciphertext[KDF_PRF_CIPHER_BLOCK_LENGTH]; AES_encrypt(plaintext, ciphertext, &key); unsigned numBytesToCopy = numBytesRemaining < KDF_PRF_CIPHER_BLOCK_LENGTH ? numBytesRemaining : KDF_PRF_CIPHER_BLOCK_LENGTH; memmove(resultKey, ciphertext, numBytesToCopy); resultKey += numBytesToCopy; numBytesRemaining -= numBytesToCopy; ++counter[15]; // for next time } } #endif