blob: d2d63432625ddf295bb5c85966a3098195a7641e [file] [log] [blame]
/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
more details.
You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
**********/
// "liveMedia"
// Copyright (c) 1996-2020 Live Networks, Inc. All rights reserved.
// A data structure that implements a MIKEY message (RFC 3830)
// Implementation
#include "MIKEY.hh"
#include <GroupsockHelper.hh> // for our_random32()
////////// MIKEYPayload definition /////////
class MIKEYPayload {
public:
MIKEYPayload(MIKEYState& ourMIKEYState, u_int8_t payloadType);
// create with default values
MIKEYPayload(MIKEYState& ourMIKEYState, u_int8_t payloadType,
u_int8_t const* data, unsigned dataSize);
// create as a copy of existing values
virtual ~MIKEYPayload();
u_int8_t const* data() const { return fData; }
unsigned dataSize() const { return fDataSize; }
MIKEYPayload* next() const { return fNext; }
void setNextPayload(MIKEYPayload* nextPayload);
private:
MIKEYState& fOurMIKEYState;
u_int8_t fPayloadType;
u_int8_t* fData;
unsigned fDataSize;
MIKEYPayload* fNext;
};
////////// MIKEYState implementation //////////
enum MIKEYPayloadType {
KEMAC = 1,
PKE = 2,
DH = 3,
SIGN = 4,
T = 5,
ID = 6,
CERT = 7,
CHASH = 8,
V = 9,
SP = 10,
RAND = 11,
ERR = 12,
KEY_DATA = 13,
HDR = 255
};
MIKEYState::MIKEYState()
: // Set default encryption/authentication parameters:
fEncryptSRTP(True),
fEncryptSRTCP(True),
fMKI(our_random32()),
fUseAuthentication(True),
fHeaderPayload(NULL), fTailPayload(NULL), fTotalPayloadByteCount(0) {
// Fill in our 'key data' (30 bytes) with (pseudo-)random bits:
u_int8_t* p = &fKeyData[0];
u_int32_t random32;
random32 = our_random32();
*p++ = (random32>>24); *p++ = (random32>>16); *p++ = (random32>>8); *p++ = random32; // 0-3
random32 = our_random32();
*p++ = (random32>>24); *p++ = (random32>>16); *p++ = (random32>>8); *p++ = random32; // 4-7
random32 = our_random32();
*p++ = (random32>>24); *p++ = (random32>>16); *p++ = (random32>>8); *p++ = random32; // 8-11
random32 = our_random32();
*p++ = (random32>>24); *p++ = (random32>>16); *p++ = (random32>>8); *p++ = random32; // 12-15
random32 = our_random32();
*p++ = (random32>>24); *p++ = (random32>>16); *p++ = (random32>>8); *p++ = random32; // 16-19
random32 = our_random32();
*p++ = (random32>>24); *p++ = (random32>>16); *p++ = (random32>>8); *p++ = random32; // 20-23
random32 = our_random32();
*p++ = (random32>>24); *p++ = (random32>>16); *p++ = (random32>>8); *p++ = random32; // 24-27
random32 = our_random32();
*p++ = (random32>>24); *p++ = (random32>>16); // 28-29
addNewPayload(new MIKEYPayload(*this, HDR));
addNewPayload(new MIKEYPayload(*this, T));
addNewPayload(new MIKEYPayload(*this, RAND));
addNewPayload(new MIKEYPayload(*this, SP));
addNewPayload(new MIKEYPayload(*this, KEMAC));
}
MIKEYState::~MIKEYState() {
delete fHeaderPayload; // which will delete all the other payloads as well
}
MIKEYState* MIKEYState::createNew(u_int8_t* messageToParse, unsigned messageSize) {
Boolean parsedOK;
MIKEYState* newMIKEYState = new MIKEYState(messageToParse, messageSize, parsedOK);
if (!parsedOK) {
delete newMIKEYState;
newMIKEYState = NULL;
}
delete[] messageToParse;
return newMIKEYState;
}
u_int8_t* MIKEYState::generateMessage(unsigned& messageSize) const {
if (fTotalPayloadByteCount == 0) return NULL;
// ASSERT: fTotalPayloadByteCount == the sum of all of the payloads' "fDataSize"s
messageSize = fTotalPayloadByteCount;
u_int8_t* resultMessage = new u_int8_t[messageSize];
u_int8_t* p = resultMessage;
for (MIKEYPayload* payload = fHeaderPayload; payload != NULL; payload = payload->next()) {
if (payload->data() == NULL) continue;
memcpy(p, payload->data(), payload->dataSize());
p += payload->dataSize();
}
return resultMessage;
}
MIKEYState::MIKEYState(u_int8_t const* messageToParse, unsigned messageSize, Boolean& parsedOK)
: // Set encryption/authentication parameters to default values (that may be overwritten
// later as we parse the message):
fEncryptSRTP(False),
fEncryptSRTCP(False),
fUseAuthentication(False),
fHeaderPayload(NULL), fTailPayload(NULL), fTotalPayloadByteCount(0) {
parsedOK = False; // unless we learn otherwise
// Begin by parsing a HDR payload:
u_int8_t const* ptr = messageToParse;
u_int8_t const* const endPtr = messageToParse + messageSize;
u_int8_t nextPayloadType;
if (!parseHDRPayload(ptr, endPtr, nextPayloadType)) return;
// Then parse each subsequent payload that we see:
while (nextPayloadType != 0) {
if (!parseNonHDRPayload(ptr, endPtr, nextPayloadType)) return;
}
// We succeeded in parsing all the data:
parsedOK = True;
}
void MIKEYState::addNewPayload(MIKEYPayload* newPayload) {
if (fTailPayload == NULL) {
fHeaderPayload = newPayload;
} else {
fTailPayload->setNextPayload(newPayload);
}
fTailPayload = newPayload;
fTotalPayloadByteCount += newPayload->dataSize();
}
#define testSize(n) if (ptr + (n) > endPtr) break
Boolean MIKEYState
::parseHDRPayload(u_int8_t const*& ptr, u_int8_t const* endPtr, u_int8_t& nextPayloadType) {
do {
testSize(10);
nextPayloadType = ptr[2];
u_int8_t numCryptoSessions = ptr[8];
unsigned payloadSize = 10 + numCryptoSessions*(1+4+4);
testSize(payloadSize);
addNewPayload(new MIKEYPayload(*this, HDR, ptr, payloadSize));
ptr += payloadSize;
return True;
} while (0);
// An error occurred:
return False;
}
static u_int32_t get4Bytes(u_int8_t const*& ptr) {
u_int32_t result = (ptr[0]<<24)|(ptr[1]<<16)|(ptr[2]<<8)|ptr[3];
ptr += 4;
return result;
}
static u_int16_t get2Bytes(u_int8_t const*& ptr) {
u_int16_t result = (ptr[0]<<8)|ptr[1];
ptr += 2;
return result;
}
Boolean MIKEYState
::parseNonHDRPayload(u_int8_t const*& ptr, u_int8_t const* endPtr, u_int8_t& nextPayloadType) {
do {
Boolean parseSucceeded = False; // initially
u_int8_t const* payloadStart = ptr;
unsigned payloadSize = 0;
testSize(1);
u_int8_t ourPayloadType = nextPayloadType;
nextPayloadType = *ptr++;
// The parsing depends on "ourPayloadType":
switch (ourPayloadType) {
case T: { // RFC 3830, section 6.6
testSize(1);
u_int8_t TS_type = *ptr++;
unsigned TS_value_len = 0;
switch (TS_type) {
case 0: // NTP-UTC
case 1: { // NTP
TS_value_len = 8;
break;
}
case 2: { // COUNTER
TS_value_len = 4;
break;
}
}
if (TS_value_len == 0) break; // unknown TS_type
testSize(TS_value_len);
payloadSize = 2 + TS_value_len;
parseSucceeded = True;
break;
}
case RAND: { // RFC 3830, section 6.11
testSize(1);
u_int8_t RAND_len = *ptr++;
testSize(RAND_len);
payloadSize = 2 + RAND_len;
parseSucceeded = True;
break;
}
case SP: { // RFC 3830, section 6.10
testSize(4);
++ptr; // skip over "Policy no"
u_int8_t protType = *ptr++;
if (protType != 0/*SRTP*/) break; // unsupported protocol type
u_int16_t policyParam_len = get2Bytes(ptr);
testSize(policyParam_len);
payloadSize = 5 + policyParam_len;
u_int8_t const* payloadEndPtr = payloadStart + payloadSize;
// Look through the "Policy param" section, making sure that:
// - each of the "length" fields make sense
// - for "type"s that we understand, the values make sense
Boolean parsedPolicyParamSection = False;
while (1) {
testSize(2);
u_int8_t ppType = *ptr++;
u_int8_t ppLength = *ptr++;
testSize(ppLength);
if (ptr+ppLength > payloadEndPtr) break; // bad "length"s - too big for the payload
// Check types that we understand:
Boolean policyIsOK = False; // until we learn otherwise
switch (ppType) {
case 0: { // Encryption algorithm: we handle only NULL and AES-CM
if (ppLength != 1) break;
u_int8_t value = ptr[0];
if (value > 1) break; // unsupported algorithm
if (value > 0) fEncryptSRTP = fEncryptSRTCP = True;
// Note: these might get changed by a subsequent "off/on" entry
policyIsOK = True;
break;
}
case 1: { // Session Encr. key length
if (ppLength != 1) break;
u_int8_t value = ptr[0];
if (value != 16) break; // bad/unsupported value
policyIsOK = True;
break;
}
case 2: { // Authentication algorithm: we handle only NULL and HMAC-SHA-1
if (ppLength != 1) break;
u_int8_t value = ptr[0];
if (value > 1) break; // unsupported algorithm
if (value > 0) fUseAuthentication = True;
// Note: this might get changed by a subsequent "off/on" entry
policyIsOK = True;
break;
}
case 3: { // Session Auth. key length
if (ppLength != 1) break;
u_int8_t value = ptr[0];
if (value != 20) break; // bad/unsupported value
policyIsOK = True;
break;
}
case 4: { // Session Salt key length
if (ppLength != 1) break;
u_int8_t value = ptr[0];
if (value != 14) break; // bad/unsupported value
policyIsOK = True;
break;
}
case 7: { // SRTP encryption off/on
if (ppLength != 1) break;
u_int8_t value = ptr[0];
if (value > 1) break; // bad/unsupported value
fEncryptSRTP = value;
policyIsOK = True;
break;
}
case 8: { // SRTCP encryption off/on
if (ppLength != 1) break;
u_int8_t value = ptr[0];
if (value > 1) break; // bad/unsupported value
fEncryptSRTCP = value;
policyIsOK = True;
break;
}
case 10: { // SRTP authentication off/on
if (ppLength != 1) break;
u_int8_t value = ptr[0];
if (value > 1) break; // bad/unsupported value
fUseAuthentication = value;
policyIsOK = True;
break;
}
case 11: { // Authentication tag length
if (ppLength != 1) break;
u_int8_t value = ptr[0];
if (value != 10) break; // bad/unsupported value
policyIsOK = True;
break;
}
default: { // a policy type that we don't handle; still OK
policyIsOK = True;
break;
}
}
if (!policyIsOK) break;
ptr += ppLength;
if (ptr == payloadEndPtr) {
// We've successfully checked all of the "Policy param"s
parsedPolicyParamSection = True;
break;
}
}
if (!parsedPolicyParamSection) break;
parseSucceeded = True;
break;
}
case KEMAC: { // RFC 3830, section 6.2
testSize(3);
u_int8_t encrAlg = *ptr++;
// We currently support only 'NULL' encryption on the key data:
if (encrAlg != 0/*NULL*/) break; // unknown or unsupported key encryption
u_int16_t encrDataLen = get2Bytes(ptr);
testSize(encrDataLen);
// Parse the 'key data sub-payload':
{
u_int8_t const* subPtr = ptr;
if (encrDataLen < 4) break; // not enough space
++subPtr; // skip over the "Next payload" field
// Check the "Type" and "KV" fields; we support only TEK and SPI/MKI
// Note: This means that we'll reject the "a=key-mgmt" SDP MIKEY data from
// an Axis camera, because that doesn't specify SPI/MKI. But that's what
// we want, because the Axis camera's "a=key-mgmt" SDP MIKEY data is meant
// to be ignored by clients.
if (*subPtr++ != ((2<<4)|1)) break; // Type 2 (TEK) | KV 1 (SPI/MKI)
u_int16_t keyDataLen = get2Bytes(subPtr);
// The key data length must be 30 (encryption key length (16) + salt length (14)):
if (keyDataLen != 30) break;
// Make sure we have enough space for the key data and the "SPI Length" field:
if (4+keyDataLen+1 > encrDataLen) break;
// Record the key data:
memmove(fKeyData, subPtr, keyDataLen);
subPtr += keyDataLen;
// Check the "SPI Length"; we support only length 4:
u_int8_t SPILength = *subPtr++;
if (SPILength != 4) break;
// Make a note of the MKI (the next 4 bytes):
if (4+keyDataLen+1+SPILength > encrDataLen) break;
fMKI = get4Bytes(subPtr);
}
ptr += encrDataLen;
testSize(1);
u_int8_t macAlg = *ptr++;
unsigned macLen;
// We currently support only a 'NULL' MAC on the key data:
if (macAlg == 0/*NULL*/) macLen = 0;
else break; // unknown or unsupported MAC algorithm => parse fails
testSize(macLen);
payloadSize = 4 + encrDataLen + 1 + macLen;
parseSucceeded = True;
break;
}
default: {
// Unknown payload type. The parsing fails.
break;
}
}
if (!parseSucceeded) break;
addNewPayload(new MIKEYPayload(*this, ourPayloadType, payloadStart, payloadSize));
ptr = payloadStart + payloadSize;
return True;
} while (0);
// An error occurred:
return False;
}
////////// MIKEYPayload implementation //////////
static void addWord(u_int8_t*& p, u_int32_t word) {
*p++ = word>>24; *p++ = word>>16; *p++ = word>>8; *p++ = word;
}
static void addHalfWord(u_int8_t*& p, u_int16_t halfWord) {
*p++ = halfWord>>8; *p++ = halfWord;
}
static void add1BytePolicyParam(u_int8_t*& p, u_int8_t type, u_int8_t value) {
*p++ = type;
*p++ = 1; // length
*p++ = value;
}
MIKEYPayload::MIKEYPayload(MIKEYState& ourMIKEYState, u_int8_t payloadType)
: fOurMIKEYState(ourMIKEYState), fPayloadType(payloadType), fNext(NULL) {
switch (payloadType) {
case HDR: { // RFC 3830, section 6.1
fDataSize = 19;
fData = new u_int8_t[fDataSize];
u_int8_t* p = fData;
*p++ = 1; // version
*p++ = 0; // Initiator's pre-shared key message
*p++ = 0; // no next payload (initially)
*p++ = 0; // V=0; PRF func: MIKEY-1
u_int32_t const CSB_ID = our_random32();
addWord(p, CSB_ID);
*p++ = 1; // #CS: 1
*p++ = 0; // CS ID map type: SRTP-ID
*p++ = 0; // Policy_no_1
addWord(p, our_random32()); // SSRC_1
addWord(p, 0x00000000); // ROC_1
break;
}
case T: { // RFC 3830, section 6.6
fDataSize = 10;
fData = new u_int8_t[fDataSize];
u_int8_t* p = fData;
*p++ = 0; // no next payload (initially)
*p++ = 0; // TS type: NTP-UTC
// Get the current time, and convert it to a NTP-UTC time:
struct timeval timeNow;
gettimeofday(&timeNow, NULL);
u_int32_t ntpSeconds = timeNow.tv_sec + 0x83AA7E80; // 1970 epoch -> 1900 epoch
addWord(p, ntpSeconds);
double fractionalPart = (timeNow.tv_usec/15625.0)*0x04000000; // 2^32/10^6
u_int32_t ntpFractionOfSecond = (u_int32_t)(fractionalPart+0.5); // round
addWord(p, ntpFractionOfSecond);
break;
}
case RAND: { // RFC 3830, section 6.11
fDataSize = 18;
fData = new u_int8_t[fDataSize];
u_int8_t* p = fData;
*p++ = 0; // no next payload (initially)
unsigned const numRandomWords = 4; // number of 32-bit words making up the RAND value
*p++ = 4*numRandomWords; // RAND len (in bytes)
for (unsigned i = 0; i < numRandomWords; ++i) {
u_int32_t randomNumber = our_random32();
addWord(p, randomNumber);
}
break;
}
case SP: { // RFC 3830, section 6.10
fDataSize = 32;
fData = new u_int8_t[fDataSize];
u_int8_t* p = fData;
*p++ = 0; // no next payload (initially)
*p++ = 0; // Policy number
*p++ = 0; // Protocol type: SRTP
u_int16_t policyParamLen = 27;
addHalfWord(p, policyParamLen);
// Now add the SRTP policy parameters:
add1BytePolicyParam(p, 0/*Encryption algorithm*/,
(fOurMIKEYState.encryptSRTP()||fOurMIKEYState.encryptSRTCP())
? 1/*AES-CM*/ : 0/*NULL*/);
add1BytePolicyParam(p, 1/*Session Encryption key length*/, 16);
add1BytePolicyParam(p, 2/*Authentication algorithm*/,
fOurMIKEYState.useAuthentication()
? 1/*HMAC-SHA-1*/ : 0/*NULL*/);
add1BytePolicyParam(p, 3/*Session Authentication key length*/, 20);
add1BytePolicyParam(p, 4/*Session Salt key length*/, 14);
add1BytePolicyParam(p, 7/*SRTP encryption off/on*/, fOurMIKEYState.encryptSRTP());
add1BytePolicyParam(p, 8/*SRTCP encryption off/on*/, fOurMIKEYState.encryptSRTCP());
add1BytePolicyParam(p, 10/*SRTP authentication off/on*/, fOurMIKEYState.useAuthentication());
add1BytePolicyParam(p, 11/*Authentication tag length*/, 10);
break;
}
case KEMAC: { // RFC 3830, section 6.2
fDataSize = 44;
fData = new u_int8_t[fDataSize];
u_int8_t* p = fData;
*p++ = 0; // no next payload
*p++ = 0; // Encr alg (NULL)
u_int16_t encrDataLen = 39;
addHalfWord(p, encrDataLen);
{ // Key data sub-payload (RFC 3830, section 6.13):
*p++ = 0; // no next payload
*p++ = (2<<4)|1; // Type 2 (TEK) | KV 1 (SPI/MKI)
// Key data len:
u_int16_t const keyDataLen = 30;
addHalfWord(p, keyDataLen);
// Key data:
memcpy(p, fOurMIKEYState.keyData(), keyDataLen);
p += keyDataLen;
{ // KV data (for SPI/MKI) (RFC 3830, section 6.14):
*p++ = 4; // SPI/MKI Length
addWord(p, fOurMIKEYState.MKI());
}
}
*p++ = 0; // MAC alg (NULL)
break;
}
default: {
// Unused payload type. Just in case, allocate 1 byte, for the
// presumed 'next payload type' field.
fDataSize = 1;
fData = new u_int8_t[fDataSize];
fData[0] = 0;
break;
}
}
}
MIKEYPayload::MIKEYPayload(MIKEYState& ourMIKEYState, u_int8_t payloadType,
u_int8_t const* data, unsigned dataSize)
: fOurMIKEYState(ourMIKEYState), fPayloadType(payloadType),
fDataSize(dataSize), fNext(NULL) {
fData = new u_int8_t[fDataSize];
memcpy(fData, data, fDataSize);
}
MIKEYPayload::~MIKEYPayload() {
delete fNext;
}
void MIKEYPayload::setNextPayload(MIKEYPayload* nextPayload) {
fNext = nextPayload;
// We also need to set the 'next payload type' field in our data:
u_int8_t nextPayloadType = nextPayload->fPayloadType;
switch (fPayloadType) {
case HDR: {
fData[2] = nextPayloadType;
break;
}
default: {
if (fData != NULL) fData[0] = nextPayloadType;
break;
}
}
}