| /********** |
| 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; |
| } |
| } |
| } |