/* Copyright (C) 2006-2009 Werner Dittmann This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /*F * Authors: Werner Dittmann */ #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace GnuZrtpCodes; /* disabled...but used in testing and debugging, probably should have a controlling #define... * static void hexdump(const char* title, const unsigned char *s, int l) { int n=0; if (s == NULL) return; fprintf(stderr, "%s",title); for( ; n < l ; ++n) { if((n%16) == 0) fprintf(stderr, "\n%04x",n); fprintf(stderr, " %02x",s[n]); } fprintf(stderr, "\n"); } */ /* * This method simplifies detection of libzrtpcpp inside Automake, configure * and friends */ #ifdef __cplusplus extern "C" { #endif int ZrtpAvailable() { return 1; } #ifdef __cplusplus } #endif ZRtp::ZRtp(uint8_t *myZid, ZrtpCallback *cb, std::string id, ZrtpConfigure* config, bool mitmm, bool sasSignSupport): callback(cb), dhContext(NULL), DHss(NULL), auxSecret(NULL), auxSecretLength(0), rs1Valid(false), rs2Valid(false), msgShaContext(NULL), multiStream(false), multiStreamAvailable(false), pbxSecretTmp(NULL), configureAlgos(*config) { enableMitmEnrollment = config->isTrustedMitM(); paranoidMode = config->isParanoidMode(); // setup the implicit hash function pointers and length hashLengthImpl = SHA256_DIGEST_LENGTH; hashFunctionImpl = sha256; hashListFunctionImpl = sha256; hmacFunctionImpl = hmac_sha256; hmacListFunctionImpl = hmac_sha256; /* * Generate H0 as a random number (256 bits, 32 bytes) and then * the hash chain, refer to chapter 9. Use the implicit hash function. */ randomZRTP(H0, HASH_IMAGE_SIZE); sha256(H0, HASH_IMAGE_SIZE, H1); // hash H0 and generate H1 sha256(H1, HASH_IMAGE_SIZE, H2); // H2 sha256(H2, HASH_IMAGE_SIZE, H3); // H3 zrtpHello.configureHello(&configureAlgos); zrtpHello.setH3(H3); // set H3 in Hello, included in helloHash memcpy(zid, myZid, ZID_SIZE); zrtpHello.setZid(zid); if (mitmm) // this session acts for a trusted MitM (PBX) zrtpHello.setMitmMode(); if (sasSignSupport) // the application supports SAS signing zrtpHello.setSasSign(); setClientId(id); // set id, compute HMAC and final helloHash stateEngine = new ZrtpStateClass(this); } ZRtp::~ZRtp() { stopZrtp(); if (DHss != NULL) { delete DHss; DHss = NULL; } if (stateEngine != NULL) { delete stateEngine; stateEngine = NULL; } if (dhContext != NULL) { delete dhContext; dhContext = NULL; } if (msgShaContext != NULL) { closeHashCtx(msgShaContext, NULL); msgShaContext = NULL; } if (auxSecret != NULL) { delete auxSecret; auxSecret = NULL; auxSecretLength = 0; } memset(hmacKeyI, 0, MAX_DIGEST_LENGTH); memset(hmacKeyR, 0, MAX_DIGEST_LENGTH); memset(zrtpKeyI, 0, MAX_DIGEST_LENGTH); memset(zrtpKeyR, 0, MAX_DIGEST_LENGTH); /* * Clear the Initiator's srtp key and salt */ memset(srtpKeyI, 0, MAX_DIGEST_LENGTH); memset(srtpSaltI, 0, MAX_DIGEST_LENGTH); /* * Clear he Responder's srtp key and salt */ memset(srtpKeyR, 0, MAX_DIGEST_LENGTH); memset(srtpSaltR, 0, MAX_DIGEST_LENGTH); memset(zrtpSession, 0, MAX_DIGEST_LENGTH); } void ZRtp::processZrtpMessage(uint8_t *message, uint32_t pSSRC) { Event_t ev; peerSSRC = pSSRC; ev.type = ZrtpPacket; ev.packet = message; if (stateEngine != NULL) { stateEngine->processEvent(&ev); } } void ZRtp::processTimeout() { Event_t ev; ev.type = Timer; if (stateEngine != NULL) { stateEngine->processEvent(&ev); } } #ifdef oldgoclear bool ZRtp::handleGoClear(uint8_t *message) { char *msg, first, last; msg = (char *)message + 4; first = tolower(*msg); last = tolower(*(msg+6)); if (first == 'g' && last == 'r') { Event_t ev; ev.type = ZrtpGoClear; ev.packet = message; if (stateEngine != NULL) { stateEngine->processEvent(&ev); } return true; } else { return false; } } #endif void ZRtp::startZrtpEngine() { Event_t ev; if (stateEngine != NULL && stateEngine->inState(Initial)) { ev.type = ZrtpInitial; stateEngine->processEvent(&ev); } } void ZRtp::stopZrtp() { Event_t ev; if (stateEngine != NULL) { ev.type = ZrtpClose; stateEngine->processEvent(&ev); } } bool ZRtp::inState(int32_t state) { if (stateEngine != NULL) { return stateEngine->inState(state); } else { return false; } } ZrtpPacketHello* ZRtp::prepareHello() { return &zrtpHello; } ZrtpPacketHelloAck* ZRtp::prepareHelloAck() { return &zrtpHelloAck; } /* * At this point we will assume the role of Initiator. This role may change * in case we have a commit-clash. Refer to chapter 5.2 in the spec how * to break this tie. */ ZrtpPacketCommit* ZRtp::prepareCommit(ZrtpPacketHello *hello, uint32_t* errMsg) { sendInfo(Info, InfoHelloReceived); if (memcmp(hello->getVersion(), zrtpVersion, ZRTP_WORD_SIZE-1) != 0) { *errMsg = UnsuppZRTPVersion; return NULL; } // Save our peer's (presumably the Responder) ZRTP id memcpy(peerZid, hello->getZid(), ZID_SIZE); if (memcmp(peerZid, zid, ZID_SIZE) == 0) { // peers have same ZID???? *errMsg = EqualZIDHello; return NULL; } memcpy(peerH3, hello->getH3(), HASH_IMAGE_SIZE); /* * The Following section extracts the algorithm from the peer's Hello * packet. Always the preferend offered algorithms are * used. If the received Hello does not contain algo specifiers * or offers only unsupported optional algos then replace * these with mandatory algos and put them into the Commit packet. * Refer to the findBest*() functions. * If this is a MultiStream ZRTP object then do not get the cipher, * authentication from hello packet but use the pre-initialized values * as proposed by the standard. If we switch to responder mode the * commit packet may contain other algos - see function * prepareConfirm2MultiStream(...). */ sasType = findBestSASType(hello); if (!multiStream) { authLength = findBestAuthLen(hello); pubKey = findBestPubkey(hello); cipher = findBestCipher(hello, pubKey); hash = findBestHash(hello); multiStreamAvailable = checkMultiStream(hello); } else { if (checkMultiStream(hello)) { return prepareCommitMultiStream(hello); } else { // we are in multi-stream but peer does not offer multi-stream // return error code to other party - unsupported PK, must be Mult *errMsg = UnsuppPKExchange; return NULL; } } setNegotiatedHash(hash); // Modify here when introducing new DH key agreement, for example // elliptic curves. dhContext = new ZrtpDH(pubKey->getName()); dhContext->generatePublicKey(); dhContext->getPubKeyBytes(pubKeyBytes); sendInfo(Info, InfoCommitDHGenerated); // Prepare IV data that we will use during confirm packet encryption. randomZRTP(randomIV, sizeof(randomIV)); /* * Prepare our DHPart2 packet here. Required to compute HVI. If we stay * in Initiator role then we reuse this packet later in prepareDHPart2(). * To create this DH packet we have to compute the retained secret ids * first. Thus get our peer's retained secret data first. */ ZIDRecord zidRec(peerZid); ZIDFile *zidFile = ZIDFile::getInstance(); zidFile->getRecord(&zidRec); //Compute the Initator's and Responder's retained secret ids. computeSharedSecretSet(zidRec); // Check if a PBX application set the MitM flag. if (hello->isMitmMode()) { mitmSeen = true; } // Flag to record that fact that we have a MitM key of the other peer. peerIsEnrolled = zidRec.isMITMKeyAvailable(); signSasSeen = hello->isSasSign(); // Construct a DHPart2 message (Initiator's DH message). This packet // is required to compute the HVI (Hash Value Initiator), refer to // chapter 5.4.1.1. // Fill the values in the DHPart2 packet zrtpDH2.setPubKeyType(pubKey->getName()); zrtpDH2.setMessageType((uint8_t*)DHPart2Msg); zrtpDH2.setRs1Id(rs1IDi); zrtpDH2.setRs2Id(rs2IDi); zrtpDH2.setAuxSecretId(auxSecretIDi); zrtpDH2.setPbxSecretId(pbxSecretIDi); zrtpDH2.setPv(pubKeyBytes); zrtpDH2.setH1(H1); int32_t len = zrtpDH2.getLength() * ZRTP_WORD_SIZE; // Compute HMAC over DH2, excluding the HMAC field (HMAC_SIZE) // and store in DH2. Key to HMAC is H0, use HASH_IMAGE_SIZE bytes only. // Must use implicit HMAC functions. uint8_t hmac[IMPL_MAX_DIGEST_LENGTH]; uint32_t macLen; hmacFunctionImpl(H0, HASH_IMAGE_SIZE, (uint8_t*)zrtpDH2.getHeaderBase(), len-(HMAC_SIZE), hmac, &macLen); zrtpDH2.setHMAC(hmac); // Compute the HVI, refer to chapter 5.4.1.1 of the specification computeHvi(&zrtpDH2, hello); zrtpCommit.setZid(zid); zrtpCommit.setHashType((uint8_t*)hash->getName()); zrtpCommit.setCipherType((uint8_t*)cipher->getName()); zrtpCommit.setAuthLen((uint8_t*)authLength->getName()); zrtpCommit.setPubKeyType((uint8_t*)pubKey->getName()); zrtpCommit.setSasType((uint8_t*)sasType->getName()); zrtpCommit.setHvi(hvi); zrtpCommit.setH2(H2); len = zrtpCommit.getLength() * ZRTP_WORD_SIZE; // Compute HMAC over Commit, excluding the HMAC field (HMAC_SIZE) // and store in Hello. Key to HMAC is H1, use HASH_IMAGE_SIZE bytes only. // Must use implicit HMAC functions. hmacFunctionImpl(H1, HASH_IMAGE_SIZE, (uint8_t*)zrtpCommit.getHeaderBase(), len-(HMAC_SIZE), hmac, &macLen); zrtpCommit.setHMAC(hmac); // hash first messages to produce overall message hash // First the Responder's Hello message, second the Commit (always Initator's). // Must use negotiated hash. int32_t helloLen = hello->getLength() * ZRTP_WORD_SIZE; msgShaContext = createHashCtx(); hashCtxFunction(msgShaContext, (unsigned char*)hello->getHeaderBase(), helloLen); hashCtxFunction(msgShaContext, (unsigned char*)zrtpCommit.getHeaderBase(), len); // store Hello data temporarily until we can check HMAC after receiving Commit as // Responder or DHPart1 as Initiator storeMsgTemp(hello); // calculate hash over the received Hello packet - is peer's hello hash. // Use implicit hash algorithm hashFunctionImpl((unsigned char*)hello->getHeaderBase(), helloLen, peerHelloHash); memcpy(peerHelloVersion, hello->getVersion(), ZRTP_WORD_SIZE); peerHelloVersion[ZRTP_WORD_SIZE] = 0; return &zrtpCommit; } ZrtpPacketCommit* ZRtp::prepareCommitMultiStream(ZrtpPacketHello *hello) { randomZRTP(hvi, ZRTP_WORD_SIZE*4); // This is the Multi-Stream NONCE size zrtpCommit.setZid(zid); zrtpCommit.setHashType((uint8_t*)hash->getName()); zrtpCommit.setCipherType((uint8_t*)cipher->getName()); zrtpCommit.setAuthLen((uint8_t*)authLength->getName()); zrtpCommit.setPubKeyType((uint8_t*)"Mult"); // this is fixed because of Multi Stream mode zrtpCommit.setSasType((uint8_t*)sasType->getName()); zrtpCommit.setNonce(hvi); zrtpCommit.setH2(H2); int32_t len = zrtpCommit.getLength() * ZRTP_WORD_SIZE; // Compute HMAC over Commit, excluding the HMAC field (HMAC_SIZE) // and store in Hello. Key to HMAC is H1, use HASH_IMAGE_SIZE bytes only. // Must use the implicit HMAC function. uint8_t hmac[IMPL_MAX_DIGEST_LENGTH]; uint32_t macLen; hmacFunctionImpl(H1, HASH_IMAGE_SIZE, (uint8_t*)zrtpCommit.getHeaderBase(), len-(HMAC_SIZE), hmac, &macLen); zrtpCommit.setHMACMulti(hmac); // hash first messages to produce overall message hash // First the Responder's Hello message, second the Commit // (always Initator's). // Must use the negotiated hash. msgShaContext = createHashCtx(); int32_t helloLen = hello->getLength() * ZRTP_WORD_SIZE; hashCtxFunction(msgShaContext, (unsigned char*)hello->getHeaderBase(), helloLen); hashCtxFunction(msgShaContext, (unsigned char*)zrtpCommit.getHeaderBase(), len); // store Hello data temporarily until we can check HMAC after receiving Commit as // Responder or DHPart1 as Initiator storeMsgTemp(hello); // calculate hash over the received Hello packet - is peer's hello hash. // Use implicit hash algorithm hashFunctionImpl((unsigned char*)hello->getHeaderBase(), helloLen, peerHelloHash); memcpy(peerHelloVersion, hello->getVersion(), ZRTP_WORD_SIZE); peerHelloVersion[ZRTP_WORD_SIZE] = 0; return &zrtpCommit; } /* * At this point we will take the role of the Responder. We may have been in * the role of the Initiator before and already sent a commit packet that * clashed with a commit packet from our peer. If our HVI was lower than our * peer's HVI then we switched to Responder and handle our peer's commit packet * here. This method takes care to delete and refresh data left over from a * possible Initiator preparation. This belongs to prepared DH data, message * hash SHA context */ ZrtpPacketDHPart* ZRtp::prepareDHPart1(ZrtpPacketCommit *commit, uint32_t* errMsg) { sendInfo(Info, InfoRespCommitReceived); // The following code check the hash chain according chapter 10 to detect // false ZRTP packets. // Must use the implicit hash function. uint8_t tmpH3[IMPL_MAX_DIGEST_LENGTH]; memcpy(peerH2, commit->getH2(), HASH_IMAGE_SIZE); hashFunctionImpl(peerH2, HASH_IMAGE_SIZE, tmpH3); if (memcmp(tmpH3, peerH3, HASH_IMAGE_SIZE) != 0) { *errMsg = IgnorePacket; return NULL; } // Check HMAC of previous Hello packet stored in temporary buffer. The // HMAC key of peer's Hello packet is peer's H2 that is contained in the // Commit packet. Refer to chapter 9.1. if (!checkMsgHmac(peerH2)) { sendInfo(Severe, SevereHelloHMACFailed); *errMsg = CriticalSWError; return NULL; } // check if we support the commited Cipher type AlgorithmEnum* cp = &zrtpSymCiphers.getByName((const char*)commit->getCipherType()); if (!cp->isValid()) { // no match - something went wrong *errMsg = UnsuppCiphertype; return NULL; } cipher = cp; // check if we support the commited Authentication length cp = &zrtpAuthLengths.getByName((const char*)commit->getAuthLen()); if (!cp->isValid()) { // no match - something went wrong *errMsg = UnsuppSRTPAuthTag; return NULL; } authLength = cp; // check if we support the commited hash type cp = &zrtpHashes.getByName((const char*)commit->getHashType()); if (!cp->isValid()) { // no match - something went wrong *errMsg = UnsuppHashType; return NULL; } // check if the peer's commited hash is the same that we used when // preparing our commit packet. If not do the necessary resets and // recompute some data. if (*(int32_t*)(hash->getName()) != *(int32_t*)(cp->getName())) { hash = cp; setNegotiatedHash(hash); ZIDRecord zidRec(peerZid); ZIDFile *zidFile = ZIDFile::getInstance(); zidFile->getRecord(&zidRec); // Compute the Initator's and Responder's retained secret ids // with the committed hash. computeSharedSecretSet(zidRec); } // check if we support the commited pub key type cp = &zrtpPubKeys.getByName((const char*)commit->getPubKeysType()); if (!cp->isValid()) { // no match - something went wrong *errMsg = UnsuppPKExchange; return NULL; } pubKey = cp; // check if we support the commited SAS type cp = &zrtpSasTypes.getByName((const char*)commit->getSasType()); if (!cp->isValid()) { // no match - something went wrong *errMsg = UnsuppSASScheme; return NULL; } sasType = cp; // dhContext cannot be NULL - always setup during prepareCommit() // check if we can use the dhContext prepared by prepareCOmmit(), // if not delete old DH context and generate new one // The algorithm names are 4 chars only, thus we can cast to int32_t if (*(int32_t*)(dhContext->getDHtype()) != *(int32_t*)(pubKey->getName())) { delete dhContext; dhContext = new ZrtpDH(pubKey->getName()); dhContext->generatePublicKey(); } sendInfo(Info, InfoDH1DHGenerated); dhContext->getPubKeyBytes(pubKeyBytes); // Setup a DHPart1 packet. zrtpDH1.setPubKeyType(pubKey->getName()); zrtpDH1.setMessageType((uint8_t*)DHPart1Msg); zrtpDH1.setRs1Id(rs1IDr); zrtpDH1.setRs2Id(rs2IDr); zrtpDH1.setAuxSecretId(auxSecretIDr); zrtpDH1.setPbxSecretId(pbxSecretIDr); zrtpDH1.setPv(pubKeyBytes); zrtpDH1.setH1(H1); int32_t len = zrtpDH1.getLength() * ZRTP_WORD_SIZE; // Compute HMAC over DHPart1, excluding the HMAC field (HMAC_SIZE) // and store in DHPart1. // Use implicit Hash function uint8_t hmac[IMPL_MAX_DIGEST_LENGTH]; uint32_t macLen; hmacFunctionImpl(H0, HASH_IMAGE_SIZE, (uint8_t*)zrtpDH1.getHeaderBase(), len-(HMAC_SIZE), hmac, &macLen); zrtpDH1.setHMAC(hmac); // We are definitly responder. Save the peer's hvi for later compare. myRole = Responder; memcpy(peerHvi, commit->getHvi(), HVI_SIZE); // We are responder. Release a possibly pre-computed SHA context // because this was prepared for Initiator. Then create a new one. if (msgShaContext != NULL) { closeHashCtx(msgShaContext, NULL); } msgShaContext = createHashCtx(); // Hash messages to produce overall message hash: // First the Responder's (my) Hello message, second the Commit // (always Initator's), then the DH1 message (which is always a // Responder's message). // Must use negotiated hash hashCtxFunction(msgShaContext, (unsigned char*)zrtpHello.getHeaderBase(), zrtpHello.getLength() * ZRTP_WORD_SIZE); hashCtxFunction(msgShaContext, (unsigned char*)commit->getHeaderBase(), commit->getLength() * ZRTP_WORD_SIZE); hashCtxFunction(msgShaContext, (unsigned char*)zrtpDH1.getHeaderBase(), zrtpDH1.getLength() * ZRTP_WORD_SIZE); // store Commit data temporarily until we can check HMAC after we got DHPart2 storeMsgTemp(commit); return &zrtpDH1; } /* * At this point we will take the role of the Initiator. */ ZrtpPacketDHPart* ZRtp::prepareDHPart2(ZrtpPacketDHPart *dhPart1, uint32_t* errMsg) { uint8_t* pvr; sendInfo(Info, InfoInitDH1Received); // Because we are initiator the protocol engine didn't receive Commit // thus could not store a peer's H2. A two step SHA256 is required to // re-compute H3. Then compare with peer's H3 from peer's Hello packet. // Must use implicit hash function. uint8_t tmpHash[IMPL_MAX_DIGEST_LENGTH]; hashFunctionImpl(dhPart1->getH1(), HASH_IMAGE_SIZE, tmpHash); // Compute peer's H2 memcpy(peerH2, tmpHash, HASH_IMAGE_SIZE); hashFunctionImpl(peerH2, HASH_IMAGE_SIZE, tmpHash); // Compute peer's H3 (tmpHash) if (memcmp(tmpHash, peerH3, HASH_IMAGE_SIZE) != 0) { *errMsg = IgnorePacket; return NULL; } // Check HMAC of previous Hello packet stored in temporary buffer. The // HMAC key of the Hello packet is peer's H2 that was computed above. // Refer to chapter 9.1 and chapter 10. if (!checkMsgHmac(peerH2)) { sendInfo(Severe, SevereHelloHMACFailed); *errMsg = CriticalSWError; return NULL; } // get memory to store DH result TODO: make it fixed memory DHss = new uint8_t[dhContext->getDhSize()]; if (DHss == NULL) { *errMsg = CriticalSWError; return NULL; } // get and check Responder's public value, see chap. 5.4.3 in the spec pvr = dhPart1->getPv(); if (!dhContext->checkPubKey(pvr)) { *errMsg = DHErrorWrongPV; return NULL; } dhContext->computeSecretKey(pvr, DHss); myRole = Initiator; // We are Initiator: the Responder's Hello and the Initiator's (our) Commit // are already hashed in the context. Now hash the Responder's DH1 and then // the Initiator's (our) DH2 in that order. // Use the negotiated hash function. hashCtxFunction(msgShaContext, (unsigned char*)dhPart1->getHeaderBase(), dhPart1->getLength() * ZRTP_WORD_SIZE); hashCtxFunction(msgShaContext, (unsigned char*)zrtpDH2.getHeaderBase(), zrtpDH2.getLength() * ZRTP_WORD_SIZE); // Compute the message Hash closeHashCtx(msgShaContext, messageHash); msgShaContext = NULL; // To compute the keys for the Initiator we need the retained secrets of our // peer. Get them from the storage. ZIDRecord zidRec(peerZid); ZIDFile *zid = ZIDFile::getInstance(); zid->getRecord(&zidRec); // Now compute the S0, all dependend keys and the new RS1. The function // also performs sign SAS callback if it's active. generateKeysInitiator(dhPart1, zidRec); zid->saveRecord(&zidRec); delete dhContext; dhContext = NULL; // TODO: at initiator we can call signSAS at this point, don't dealy until confirm1 reveived // store DHPart1 data temporarily until we can check HMAC after receiving Confirm1 storeMsgTemp(dhPart1); return &zrtpDH2; } /* * At this point we are Responder. */ ZrtpPacketConfirm* ZRtp::prepareConfirm1(ZrtpPacketDHPart* dhPart2, uint32_t* errMsg) { uint8_t* pvi; sendInfo(Info, InfoRespDH2Received); // Because we are responder we received a Commit and stored its H2. // Now re-compute H2 from received H1 and compare with stored peer's H2. // Use implicit hash function uint8_t tmpHash[IMPL_MAX_DIGEST_LENGTH]; hashFunctionImpl(dhPart2->getH1(), HASH_IMAGE_SIZE, tmpHash); if (memcmp(tmpHash, peerH2, HASH_IMAGE_SIZE) != 0) { *errMsg = IgnorePacket; return NULL; } // Check HMAC of Commit packet stored in temporary buffer. The // HMAC key of the Commit packet is peer's H1 that is contained in // DHPart2. Refer to chapter 9.1 and chapter 10. if (!checkMsgHmac(dhPart2->getH1())) { sendInfo(Severe, SevereCommitHMACFailed); *errMsg = CriticalSWError; return NULL; } // Now we have the peer's pvi. Because we are responder re-compute my hvi // using my Hello packet and the Initiator's DHPart2 and compare with // hvi sent in commit packet. If it doesn't macht then a MitM attack // may have occured. computeHvi(dhPart2, &zrtpHello); if (memcmp(hvi, peerHvi, HVI_SIZE) != 0) { *errMsg = DHErrorWrongHVI; return NULL; } DHss = new uint8_t[dhContext->getDhSize()]; if (DHss == NULL) { *errMsg = CriticalSWError; return NULL; } // Get and check the Initiator's public value, see chap. 5.4.2 of the spec pvi = dhPart2->getPv(); if (!dhContext->checkPubKey(pvi)) { *errMsg = DHErrorWrongPV; return NULL; } dhContext->computeSecretKey(pvi, DHss); // Hash the Initiator's DH2 into the message Hash (other messages already // prepared, see method prepareDHPart1(). // Use neotiated hash function hashCtxFunction(msgShaContext, (unsigned char*)dhPart2->getHeaderBase(), dhPart2->getLength() * ZRTP_WORD_SIZE); closeHashCtx(msgShaContext, messageHash); msgShaContext = NULL; // To compute the Keys for the Initiator we need the retained secrets of our // peer. Get them from the storage. ZIDRecord zidRec(peerZid); ZIDFile *zid = ZIDFile::getInstance(); zid->getRecord(&zidRec); /* * The expected shared secret Ids were already computed when we built the * DHPart1 packet. Generate s0, all depended keys, and the new RS1 value * for the ZID record. The functions also performs sign SAS callback if it's active. */ generateKeysResponder(dhPart2, zidRec); zid->saveRecord(&zidRec); delete dhContext; dhContext = NULL; // Fill in Confirm1 packet. zrtpConfirm1.setMessageType((uint8_t*)Confirm1Msg); // Check if user verfied the SAS in a previous call and thus verfied // the retained secret. Don't set the verified flag if paranoidMode is true. if (zidRec.isSasVerified() && !paranoidMode) { zrtpConfirm1.setSASFlag(); } zrtpConfirm1.setExpTime(0xFFFFFFFF); zrtpConfirm1.setIv(randomIV); zrtpConfirm1.setHashH0(H0); // if this run at PBX user agent enrollment service then set flag in confirm // packet and store the MitM key if (enrollmentMode) { computePBXSecret(); zrtpConfirm1.setPBXEnrollment(); writeEnrollmentPBX(); } uint8_t confMac[MAX_DIGEST_LENGTH]; uint32_t macLen; // Encrypt and HMAC with Responder's key - we are Respondere here int hmlen = (zrtpConfirm1.getLength() - 9) * ZRTP_WORD_SIZE; cipher->getEncrypt()(zrtpKeyR, cipher->getKeylen(), randomIV, zrtpConfirm1.getHashH0(), hmlen); hmacFunction(hmacKeyR, hashLength, (unsigned char*)zrtpConfirm1.getHashH0(), hmlen, confMac, &macLen); zrtpConfirm1.setHmac(confMac); // store DHPart2 data temporarily until we can check HMAC after receiving Confirm2 storeMsgTemp(dhPart2); return &zrtpConfirm1; } /* * At this point we are Responder. */ ZrtpPacketConfirm* ZRtp::prepareConfirm1MultiStream(ZrtpPacketCommit* commit, uint32_t* errMsg) { sendInfo(Info, InfoRespCommitReceived); // The following code checks the hash chain according chapter 10 to detect // false ZRTP packets. // Use implicit hash function uint8_t tmpH3[IMPL_MAX_DIGEST_LENGTH]; memcpy(peerH2, commit->getH2(), HASH_IMAGE_SIZE); hashFunctionImpl(peerH2, HASH_IMAGE_SIZE, tmpH3); if (memcmp(tmpH3, peerH3, HASH_IMAGE_SIZE) != 0) { *errMsg = IgnorePacket; return NULL; } // Check HMAC of previous Hello packet stored in temporary buffer. The // HMAC key of peer's Hello packet is peer's H2 that is contained in the // Commit packet. Refer to chapter 9.1. if (!checkMsgHmac(peerH2)) { sendInfo(Severe, SevereHelloHMACFailed); *errMsg = CriticalSWError; return NULL; } // check if Commit contains "Mult" as pub key type AlgorithmEnum* cp = &zrtpPubKeys.getByName((const char*)commit->getPubKeysType()); if (!cp->isValid() || *(int32_t*)(cp->getName()) != *(int32_t*)mult) { *errMsg = UnsuppPKExchange; return NULL; } // check if we support the commited cipher cp = &zrtpSymCiphers.getByName((const char*)commit->getCipherType()); if (!cp->isValid()) { // no match - something went wrong *errMsg = UnsuppCiphertype; return NULL; } cipher = cp; // check if we support the commited Authentication length cp = &zrtpAuthLengths.getByName((const char*)commit->getAuthLen()); if (!cp->isValid()) { // no match - something went wrong *errMsg = UnsuppSRTPAuthTag; return NULL; } authLength = cp; // check if we support the commited hash type cp = &zrtpHashes.getByName((const char*)commit->getHashType()); if (!cp->isValid()) { // no match - something went wrong *errMsg = UnsuppHashType; return NULL; } // check if the peer's commited hash is the same that we used when // preparing our commit packet. If not do the necessary resets and // recompute some data. if (*(int32_t*)(hash->getName()) != *(int32_t*)(cp->getName())) { hash = cp; setNegotiatedHash(hash); } myRole = Responder; // We are responder. Release a possibly pre-computed SHA256 context // because this was prepared for Initiator. Then create a new one. if (msgShaContext != NULL) { closeHashCtx(msgShaContext, NULL); } msgShaContext = createHashCtx(); // Hash messages to produce overall message hash: // First the Responder's (my) Hello message, second the Commit // (always Initator's) // use negotiated hash hashCtxFunction(msgShaContext, (unsigned char*)zrtpHello.getHeaderBase(), zrtpHello.getLength() * ZRTP_WORD_SIZE); hashCtxFunction(msgShaContext, (unsigned char*)commit->getHeaderBase(), commit->getLength() * ZRTP_WORD_SIZE); closeHashCtx(msgShaContext, messageHash); msgShaContext = NULL; generateKeysMultiStream(); // Fill in Confirm1 packet. zrtpConfirm1.setMessageType((uint8_t*)Confirm1Msg); zrtpConfirm1.setExpTime(0xFFFFFFFF); zrtpConfirm1.setIv(randomIV); zrtpConfirm1.setHashH0(H0); uint8_t confMac[MAX_DIGEST_LENGTH]; uint32_t macLen; // Encrypt and HMAC with Responder's key - we are Respondere here int32_t hmlen = (zrtpConfirm1.getLength() - 9) * ZRTP_WORD_SIZE; cipher->getEncrypt()(zrtpKeyR, cipher->getKeylen(), randomIV, zrtpConfirm1.getHashH0(), hmlen); // Use negotiated HMAC (hash) hmacFunction(hmacKeyR, hashLength, (unsigned char*)zrtpConfirm1.getHashH0(), hmlen, confMac, &macLen); zrtpConfirm1.setHmac(confMac); // Store Commit data temporarily until we can check HMAC after receiving Confirm2 storeMsgTemp(commit); return &zrtpConfirm1; } /* * At this point we are Initiator. */ ZrtpPacketConfirm* ZRtp::prepareConfirm2(ZrtpPacketConfirm* confirm1, uint32_t* errMsg) { sendInfo(Info, InfoInitConf1Received); uint8_t confMac[MAX_DIGEST_LENGTH]; uint32_t macLen; // Use the Responder's keys here because we are Initiator here and // receive packets from Responder int16_t hmlen = (confirm1->getLength() - 9) * ZRTP_WORD_SIZE; // Use negotiated HMAC (hash) hmacFunction(hmacKeyR, hashLength, (unsigned char*)confirm1->getHashH0(), hmlen, confMac, &macLen); if (memcmp(confMac, confirm1->getHmac(), HMAC_SIZE) != 0) { *errMsg = ConfirmHMACWrong; return NULL; } cipher->getDecrypt()(zrtpKeyR, cipher->getKeylen(), confirm1->getIv(), confirm1->getHashH0(), hmlen); std::string cs(cipher->getReadable()); cs.append("/").append(pubKey->getName()); // Check HMAC of DHPart1 packet stored in temporary buffer. The // HMAC key of the DHPart1 packet is peer's H0 that is contained in // Confirm1. Refer to chapter 9. if (!checkMsgHmac(confirm1->getHashH0())) { sendInfo(Severe, SevereDH1HMACFailed); *errMsg = CriticalSWError; return NULL; } signatureLength = confirm1->getSignatureLength(); if (signSasSeen && signatureLength > 0) { signatureData = confirm1->getSignatureData(); callback->checkSASSignature(sasHash); // TODO: error handling if checkSASSignature returns false. } /* * The Confirm1 is ok, handle the Retained secret stuff and inform * GUI about state. */ bool sasFlag = confirm1->isSASFlag(); // Initialize a ZID record to get peer's retained secrets ZIDRecord zidRec(peerZid); ZIDFile *zid = ZIDFile::getInstance(); zid->getRecord(&zidRec); // Our peer did not confirm the SAS in last session, thus reset // our SAS flag too. Reset the flag also if paranoidMode is true. if (!sasFlag || paranoidMode) { zidRec.resetSasVerified(); } // get verified flag from current RS1 before set a new RS1. This // may not be set even if peer's flag is set in confirm1 message. sasFlag = zidRec.isSasVerified(); callback->srtpSecretsOn(cs, SAS, sasFlag); // now we are ready to save the new RS1 which inherits the verified // flag from old RS1 zidRec.setNewRs1((const uint8_t*)newRs1); zid->saveRecord(&zidRec); // now generate my Confirm2 message zrtpConfirm2.setMessageType((uint8_t*)Confirm2Msg); zrtpConfirm2.setHashH0(H0); if (sasFlag) { zrtpConfirm2.setSASFlag(); } zrtpConfirm2.setExpTime(0xFFFFFFFF); zrtpConfirm2.setIv(randomIV); // Compute PBX secret if we are in enrollemnt mode (PBX user agent) // or enrollment was enabled at normal user agent and flag in confirm packet if (enrollmentMode || (enableMitmEnrollment && confirm1->isPBXEnrollment())) { computePBXSecret(); // if this runs at PBX user agent enrollment service then set flag in confirm // packet and store the MitM key. The PBX user agent service always stores // its MitM key. if (enrollmentMode) { zrtpConfirm2.setPBXEnrollment(); writeEnrollmentPBX(); } } // Encrypt and HMAC with Initiator's key - we are Initiator here hmlen = (zrtpConfirm2.getLength() - 9) * ZRTP_WORD_SIZE; cipher->getEncrypt()(zrtpKeyI, cipher->getKeylen(), randomIV, zrtpConfirm2.getHashH0(), hmlen); // Use negotiated HMAC (hash) hmacFunction(hmacKeyI, hashLength, (unsigned char*)zrtpConfirm2.getHashH0(), hmlen, confMac, &macLen); zrtpConfirm2.setHmac(confMac); // Ask for enrollment only if enabled via configuration and the // confirm1 packet contains the enrollment flag. The enrolling user // agent stores the MitM key only if the user accepts the enrollment // request. if (enableMitmEnrollment && confirm1->isPBXEnrollment()) { callback->zrtpAskEnrollment(EnrollmentRequest); } return &zrtpConfirm2; } /** * Save the computed MitM secret to the ZID record of the peer */ void ZRtp::writeEnrollmentPBX() { // Initialize a ZID record to get peer's retained secrets ZIDRecord zidRec(peerZid); ZIDFile *zid = ZIDFile::getInstance(); zid->getRecord(&zidRec); if (pbxSecretTmp != NULL) { zidRec.setMiTMData(pbxSecretTmp); } zid->saveRecord(&zidRec); } /* * At this point we are Initiator. */ ZrtpPacketConfirm* ZRtp::prepareConfirm2MultiStream(ZrtpPacketConfirm* confirm1, uint32_t* errMsg) { // check Confirm1 packet using the keys // prepare Confirm2 packet // don't update SAS, RS sendInfo(Info, InfoInitConf1Received); uint8_t confMac[MAX_DIGEST_LENGTH]; uint32_t macLen; closeHashCtx(msgShaContext, messageHash); msgShaContext = NULL; myRole = Initiator; generateKeysMultiStream(); // Use the Responder's keys here because we are Initiator here and // receive packets from Responder int32_t hmlen = (confirm1->getLength() - 9) * ZRTP_WORD_SIZE; // Use negotiated HMAC (hash) hmacFunction(hmacKeyR, hashLength, (unsigned char*)confirm1->getHashH0(), hmlen, confMac, &macLen); if (memcmp(confMac, confirm1->getHmac(), HMAC_SIZE) != 0) { *errMsg = ConfirmHMACWrong; return NULL; } cipher->getDecrypt()(zrtpKeyR, cipher->getKeylen(), confirm1->getIv(), confirm1->getHashH0(), hmlen); std::string cs(cipher->getReadable()); // Because we are initiator the protocol engine didn't receive Commit and // because we are using multi-stream mode here we also did not receive a DHPart1 and // thus could not store a responder's H2 or H1. A two step hash is required to // re-compute H1, H2. // USe implicit hash function. uint8_t tmpHash[IMPL_MAX_DIGEST_LENGTH]; hashFunctionImpl(confirm1->getHashH0(), HASH_IMAGE_SIZE, tmpHash); // Compute peer's H1 in tmpHash hashFunctionImpl(tmpHash, HASH_IMAGE_SIZE, tmpHash); // Compute peer's H2 in tmpHash memcpy(peerH2, tmpHash, HASH_IMAGE_SIZE); // copy and truncate to peerH2 // Check HMAC of previous Hello packet stored in temporary buffer. The // HMAC key of the Hello packet is peer's H2 that was computed above. // Refer to chapter 9.1 and chapter 10. if (!checkMsgHmac(peerH2)) { sendInfo(Severe, SevereHelloHMACFailed); *errMsg = CriticalSWError; return NULL; } // TODO: here we have a SAS signature from reponder, call checkSASsignature (save / compare in case of resend) // Inform GUI about security state, don't show SAS and its state std::string cs1(""); callback->srtpSecretsOn(cs, cs1, true); // now generate my Confirm2 message zrtpConfirm2.setMessageType((uint8_t*)Confirm2Msg); zrtpConfirm2.setHashH0(H0); zrtpConfirm2.setExpTime(0xFFFFFFFF); zrtpConfirm2.setIv(randomIV); // Encrypt and HMAC with Initiator's key - we are Initiator here hmlen = (zrtpConfirm2.getLength() - 9) * ZRTP_WORD_SIZE; cipher->getEncrypt()(zrtpKeyI, cipher->getKeylen(), randomIV, zrtpConfirm2.getHashH0(), hmlen); // Use negotiated HMAC (hash) hmacFunction(hmacKeyI, hashLength, (unsigned char*)zrtpConfirm2.getHashH0(), hmlen, confMac, &macLen); zrtpConfirm2.setHmac(confMac); return &zrtpConfirm2; } /* * At this point we are Responder. */ ZrtpPacketConf2Ack* ZRtp::prepareConf2Ack(ZrtpPacketConfirm *confirm2, uint32_t* errMsg) { sendInfo(Info, InfoRespConf2Received); uint8_t confMac[MAX_DIGEST_LENGTH]; uint32_t macLen; // Use the Initiator's keys here because we are Responder here and // reveice packets from Initiator int16_t hmlen = (confirm2->getLength() - 9) * ZRTP_WORD_SIZE; // Use negotiated HMAC (hash) hmacFunction(hmacKeyI, hashLength, (unsigned char*)confirm2->getHashH0(), hmlen, confMac, &macLen); if (memcmp(confMac, confirm2->getHmac(), HMAC_SIZE) != 0) { *errMsg = ConfirmHMACWrong; return NULL; } cipher->getDecrypt()(zrtpKeyI, cipher->getKeylen(), confirm2->getIv(), confirm2->getHashH0(), hmlen); std::string cs(cipher->getReadable()); if (!multiStream) { // Check HMAC of DHPart2 packet stored in temporary buffer. The // HMAC key of the DHPart2 packet is peer's H0 that is contained in // Confirm2. Refer to chapter 9.1 and chapter 10. if (!checkMsgHmac(confirm2->getHashH0())) { sendInfo(Severe, SevereDH2HMACFailed); *errMsg = CriticalSWError; return NULL; } signatureLength = confirm2->getSignatureLength(); if (signSasSeen && signatureLength > 0) { signatureData = confirm2->getSignatureData(); callback->checkSASSignature(sasHash); // TODO: error handling if checkSASSignature returns false. } /* * The Confirm2 is ok, handle the Retained secret stuff and inform * GUI about state. */ bool sasFlag = confirm2->isSASFlag(); // Initialize a ZID record to get peer's retained secrets ZIDRecord zidRec(peerZid); ZIDFile *zid = ZIDFile::getInstance(); zid->getRecord(&zidRec); // Our peer did not confirm the SAS in last session, thus reset // our SAS flag too. Reset the flag also if paranoidMode is true. if (!sasFlag || paranoidMode) { zidRec.resetSasVerified(); } // Now get the resulting SAS verified flag from current RS1 before setting a new RS1. // It's a combination of our SAS verfied flag and peer's verified flag. Only if both // were set (true) then sasFlag becomes true. sasFlag = zidRec.isSasVerified(); cs.append("/").append(pubKey->getName()); callback->srtpSecretsOn(cs, SAS, sasFlag); // save new RS1, this inherits the verified flag from old RS1 zidRec.setNewRs1((const uint8_t*)newRs1); zid->saveRecord(&zidRec); // Ask for enrollment only if enabled via configuration and the // confirm packet contains the enrollment flag. The enrolling user // agent stores the MitM key only if the user accepts the enrollment // request. if (enableMitmEnrollment && confirm2->isPBXEnrollment()) { computePBXSecret(); callback->zrtpAskEnrollment(EnrollmentRequest); } } else { // Check HMAC of Commit packet stored in temporary buffer. The // HMAC key of the Commit packet is initiator's H1 // use implicit hash function. uint8_t tmpHash[IMPL_MAX_DIGEST_LENGTH]; hashFunctionImpl(confirm2->getHashH0(), HASH_IMAGE_SIZE, tmpHash); // Compute initiator's H1 in tmpHash if (!checkMsgHmac(tmpHash)) { sendInfo(Severe, SevereCommitHMACFailed); *errMsg = CriticalSWError; return NULL; } std::string cs1(""); // Inform GUI about security state, don't show SAS and its state callback->srtpSecretsOn(cs, cs1, true); } return &zrtpConf2Ack; } ZrtpPacketErrorAck* ZRtp::prepareErrorAck(ZrtpPacketError* epkt) { sendInfo(ZrtpError, epkt->getErrorCode() * -1); return &zrtpErrorAck; } ZrtpPacketError* ZRtp::prepareError(uint32_t errMsg) { zrtpError.setErrorCode(errMsg); return &zrtpError; } ZrtpPacketPingAck* ZRtp::preparePingAck(ZrtpPacketPing* ppkt) { if (ppkt->getLength() != 6) // A PING packet must have a length of 6 words return NULL; // Because we do not support ZRTP proxy mode use the truncated ZID. // If this code shall be used in ZRTP proxy implementation the computation // of the endpoint hash must be enhanced (see chaps 5.15ff and 5.16) zrtpPingAck.setLocalEpHash(zid); zrtpPingAck.setRemoteEpHash(ppkt->getEpHash()); zrtpPingAck.setSSRC(peerSSRC); return &zrtpPingAck; } ZrtpPacketRelayAck* ZRtp::prepareRelayAck(ZrtpPacketSASrelay* srly, uint32_t* errMsg) { // handle and render SAS relay data only if the peer announced that it is a trusted // PBX. Don't handle SAS relay in paranoidMode. if (!mitmSeen || paranoidMode) return &zrtpRelayAck; uint8_t* hkey, *ekey; // If we are responder then the PBX used it's Initiator keys if (myRole == Responder) { hkey = hmacKeyI; ekey = zrtpKeyI; } else { hkey = hmacKeyR; ekey = zrtpKeyR; } uint8_t confMac[MAX_DIGEST_LENGTH]; uint32_t macLen; // Use the Initiator's keys here because we are Responder here and // reveice packets from Initiator int16_t hmlen = (srly->getLength() - 9) * ZRTP_WORD_SIZE; // Use negotiated HMAC (hash) hmacFunction(hkey, hashLength, (unsigned char*)srly->getFiller(), hmlen, confMac, &macLen); if (memcmp(confMac, srly->getHmac(), HMAC_SIZE) != 0) { *errMsg = ConfirmHMACWrong; return NULL; // TODO - check error handling } cipher->getDecrypt()(ekey, cipher->getKeylen(), srly->getIv(), (uint8_t*)srly->getFiller(), hmlen); const uint8_t* render = srly->getSas(); const uint8_t* newSasHash = srly->getTrustedSas(); bool sasHashNull = true; for (int i = 0; i < HASH_IMAGE_SIZE; i++) { if (newSasHash[i] != 0) { sasHashNull = false; break; } } // Check if new SAS is null or a trusted MitM relationship doesn't exist. // If this is the case then don't render and don't show the new SAS - use // the computed SAS hash but we may use a different SAS rendering algorithm to // render the computed SAS. if (sasHashNull || !peerIsEnrolled) { newSasHash = sasHash; } // If other SAS schemes required - check here and use others AlgorithmEnum* renderAlgo = &zrtpSasTypes.getByName((const char*)render); uint8_t sasBytes[4];; if (renderAlgo->isValid()) { sasBytes[0] = newSasHash[0]; sasBytes[1] = newSasHash[1]; sasBytes[2] = newSasHash[2] & 0xf0; sasBytes[3] = 0; } SAS = Base32(sasBytes, 20).getEncoded(); std::string cs(cipher->getReadable()); cs.append("/").append(pubKey->getName()).append("/MitM"); callback->srtpSecretsOn(cs, SAS, false); return &zrtpRelayAck; } // TODO Implement GoClear handling ZrtpPacketClearAck* ZRtp::prepareClearAck(ZrtpPacketGoClear* gpkt) { sendInfo(Warning, WarningGoClearReceived); return &zrtpClearAck; } ZrtpPacketGoClear* ZRtp::prepareGoClear(uint32_t errMsg) { ZrtpPacketGoClear* gclr = &zrtpGoClear; gclr->clrClearHmac(); return gclr; } /* * The next functions look up and return a prefered algorithm. These * functions work as follows: * - If the Hello packet does not contain an algorithm (number of algorithms * is zero) then return the mandatory algorithm. * - Build a list of algorithm names and ids from configuration data. If * the configuration data does not contain a mandatory algorithm append * the mandatory algorithm to the list and ids. * - Build a list of algorithm names from the Hello message. If * the Hello message does not contain a mandatory algorithm append * the mandatory algorithm to the list. * - Lookup a matching algorithm. The list built from Hello takes * precedence in the lookup (indexed by the outermost loop). * * This guarantees that we always return a supported alogrithm respecting * the order of algorithms in the Hello message * * The mandatory algorithms are: (internal enums are our prefered algoritms) * Hash: S256 (SHA 256) (internal enum Sha256) * Symmetric Cipher: AES1 (AES 128) (internal enum Aes128) * SRTP Authentication: HS32 and HS80 (32/80 bits) (internal enum AuthLen32) * Key Agreement: DH3k (3072 Diffie-Helman) (internal enum Dh3072) * */ AlgorithmEnum* ZRtp::findBestHash(ZrtpPacketHello *hello) { int i; int ii; int numAlgosOffered; AlgorithmEnum* algosOffered[ZrtpConfigure::maxNoOfAlgos+1]; int numAlgosConf; AlgorithmEnum* algosConf[ZrtpConfigure::maxNoOfAlgos+1]; bool mandatoryFound = false; // If Hello does not contain any hash names return Sha256, its mandatory int num = hello->getNumHashes(); if (num == 0) { return &zrtpHashes.getByName(mandatoryHash); } // Build list of configured hash algorithm names, append mandatory algos // if necessary. numAlgosConf = configureAlgos.getNumConfiguredAlgos(HashAlgorithm); for (i = 0; i < numAlgosConf; i++) { algosConf[i] = &configureAlgos.getAlgoAt(HashAlgorithm, i); if (*(int32_t*)(algosConf[i]->getName()) == *(int32_t*)mandatoryHash) { mandatoryFound = true; } } if (!mandatoryFound) { algosConf[numAlgosConf++] = &zrtpHashes.getByName(mandatoryHash); } // Build list of offered known algos in Hello, append mandatory algos if necessary mandatoryFound = false; for (numAlgosOffered = 0, i = 0; i < num; i++) { algosOffered[numAlgosOffered] = &zrtpHashes.getByName((const char*)hello->getHashType(i)); if (!algosOffered[numAlgosOffered]->isValid()) continue; if (*(int32_t*)(algosOffered[numAlgosOffered++]->getName()) == *(int32_t*)mandatoryHash) { mandatoryFound = true; } } if (!mandatoryFound) { algosOffered[numAlgosOffered++] = &zrtpHashes.getByName(mandatoryHash); } // Lookup offered algos in configured algos. Because of appended // mandatory algorithms at least one match will happen for (i = 0; i < numAlgosOffered; i++) { for (ii = 0; ii < numAlgosConf; ii++) { if (*(int32_t*)(algosOffered[i]->getName()) == *(int32_t*)(algosConf[ii]->getName())) { return algosConf[ii]; } } } return &zrtpHashes.getByName(mandatoryHash); } AlgorithmEnum* ZRtp::findBestCipher(ZrtpPacketHello *hello, AlgorithmEnum* pk) { int i; int ii; int numAlgosOffered; AlgorithmEnum* algosOffered[ZrtpConfigure::maxNoOfAlgos+1]; int numAlgosConf; AlgorithmEnum* algosConf[ZrtpConfigure::maxNoOfAlgos+1]; bool mandatoryFound = false; int num = hello->getNumCiphers(); if (num == 0 || (*(int32_t*)(pk->getName()) == *(int32_t*)dh2k)) { return &zrtpSymCiphers.getByName(aes1); } // Build list of configured cipher algorithm names, append mandatory algos // if necessary. numAlgosConf = configureAlgos.getNumConfiguredAlgos(CipherAlgorithm); for (i = 0; i < numAlgosConf; i++) { algosConf[i] = &configureAlgos.getAlgoAt(CipherAlgorithm, i); if (*(int32_t*)(algosConf[i]->getName()) == *(int32_t*)mandatoryCipher) { mandatoryFound = true; } } if (!mandatoryFound) { algosConf[numAlgosConf++] = &zrtpSymCiphers.getByName(mandatoryCipher); } // Build list of offered known algos names in Hello, append mandatory algos if // necessary mandatoryFound = false; for (numAlgosOffered = 0, i = 0; i < num; i++) { algosOffered[numAlgosOffered] = &zrtpSymCiphers.getByName((const char*)hello->getCipherType(i)); if (!algosOffered[numAlgosOffered]->isValid()) continue; if (*(int32_t*)(algosOffered[numAlgosOffered++]->getName()) == *(int32_t*)mandatoryCipher) { mandatoryFound = true; } } if (!mandatoryFound) { algosOffered[numAlgosOffered++] = &zrtpSymCiphers.getByName(mandatoryCipher); } // Lookup offered algos in configured algos. Because of appended // mandatory algorithms at least one match will happen for (i = 0; i < numAlgosOffered; i++) { for (ii = 0; ii < numAlgosConf; ii++) { if (*(int32_t*)(algosOffered[i]->getName()) == *(int32_t*)(algosConf[ii]->getName())) { return algosConf[ii]; } } } return &zrtpSymCiphers.getByName(mandatoryCipher); } AlgorithmEnum* ZRtp::findBestPubkey(ZrtpPacketHello *hello) { int i; int ii; int numAlgosOffered; AlgorithmEnum* algosOffered[ZrtpConfigure::maxNoOfAlgos+1]; int numAlgosConf; AlgorithmEnum* algosConf[ZrtpConfigure::maxNoOfAlgos+1]; bool mandatoryFound = false; int num = hello->getNumPubKeys(); if (num == 0) { return &zrtpPubKeys.getByName(mandatoryPubKey); } // Build list of configured pubkey algorithm names, append mandatory algos // if necessary. // The list must include real public key algorithms only, so skip // mult-stream mode, preshared and alike. numAlgosConf = configureAlgos.getNumConfiguredAlgos(PubKeyAlgorithm); for (i = 0, ii = 0; i < numAlgosConf; i++) { algosConf[ii] = &configureAlgos.getAlgoAt(PubKeyAlgorithm, ii); if (*(int32_t*)(algosConf[ii]->getName()) == *(int32_t*)mult) { continue; // skip multi-stream mode } if (*(int32_t*)(algosConf[ii++]->getName()) == *(int32_t*)mandatoryPubKey) { mandatoryFound = true; } } numAlgosConf = ii; if (!mandatoryFound) { algosConf[numAlgosConf++] = &zrtpPubKeys.getByName(mandatoryPubKey); } // Build list of offered known algos in Hello, append mandatory algos if necessary mandatoryFound = false; for (numAlgosOffered = 0, i = 0; i < num; i++) { algosOffered[numAlgosOffered] = &zrtpPubKeys.getByName((const char*)hello->getPubKeyType(i)); if (!algosOffered[numAlgosOffered]->isValid()) continue; if (*(int32_t*)(algosOffered[numAlgosOffered++]->getName()) == *(int32_t*)mandatoryPubKey) { mandatoryFound = true; } } if (!mandatoryFound) { algosOffered[numAlgosOffered++] = &zrtpPubKeys.getByName(mandatoryPubKey); } // Lookup offered algos in configured algos. Because of appended // mandatory algorithms at least one match will happen for (i = 0; i < numAlgosOffered; i++) { for (ii = 0; ii < numAlgosConf; ii++) { if (*(int32_t*)(algosOffered[i]->getName()) == *(int32_t*)(algosConf[ii]->getName())) { return algosConf[ii]; } } } return &zrtpPubKeys.getByName(mandatoryPubKey); } AlgorithmEnum* ZRtp::findBestSASType(ZrtpPacketHello *hello) { int i; int ii; int numAlgosOffered; AlgorithmEnum* algosOffered[ZrtpConfigure::maxNoOfAlgos+1]; int numAlgosConf; AlgorithmEnum* algosConf[ZrtpConfigure::maxNoOfAlgos+1]; bool mandatoryFound = false; int num = hello->getNumSas(); if (num == 0) { return &zrtpSasTypes.getByName(mandatorySasType); } // Buildlist of configured SAS algorithm names, append mandatory algos // if necessary. numAlgosConf = configureAlgos.getNumConfiguredAlgos(SasType); for (i = 0; i < numAlgosConf; i++) { algosConf[i] = &configureAlgos.getAlgoAt(SasType, i); if (*(int32_t*)(algosConf[i]->getName()) == *(int32_t*)mandatorySasType) { mandatoryFound = true; } } if (!mandatoryFound) { algosConf[numAlgosConf++] = &zrtpSasTypes.getByName(mandatorySasType); } // Build list of offered known algos in Hello, append mandatory algos if necessary for (numAlgosOffered = 0, i = 0; i < num; i++) { algosOffered[numAlgosOffered] = &zrtpSasTypes.getByName((const char*)hello->getSasType(i)); if (!algosOffered[numAlgosOffered]->isValid()) continue; if (*(int32_t*)(algosOffered[numAlgosOffered++]->getName()) == *(int32_t*)mandatorySasType) { mandatoryFound = true; } } if (!mandatoryFound) { algosOffered[numAlgosOffered++] = &zrtpSasTypes.getByName(mandatorySasType); } // Lookup offered algos in configured algos. Because of appended // mandatory algorithms at least one match will happen for (i = 0; i < numAlgosOffered; i++) { for (ii = 0; ii < numAlgosConf; ii++) { if (*(int32_t*)(algosOffered[i]->getName()) == *(int32_t*)(algosConf[ii]->getName())) { return algosConf[ii]; } } } return &zrtpSasTypes.getByName(mandatorySasType); } AlgorithmEnum* ZRtp::findBestAuthLen(ZrtpPacketHello *hello) { int i; int ii; int numAlgosOffered; AlgorithmEnum* algosOffered[ZrtpConfigure::maxNoOfAlgos+2]; int numAlgosConf; AlgorithmEnum* algosConf[ZrtpConfigure::maxNoOfAlgos+2]; bool mandatoryFound_1 = false; bool mandatoryFound_2 = false; int num = hello->getNumAuth(); if (num == 0) { return &zrtpAuthLengths.getByName(mandatoryAuthLen_1); } // Build list of configured SAS algorithm names, append mandatory algos // if necessary. numAlgosConf = configureAlgos.getNumConfiguredAlgos(AuthLength); for (i = 0; i < numAlgosConf; i++) { algosConf[i] = &configureAlgos.getAlgoAt(AuthLength, i); if (*(int32_t*)(algosConf[i]->getName()) == *(int32_t*)mandatoryAuthLen_1) { mandatoryFound_1 = true; } if (*(int32_t*)(algosConf[i]->getName()) == *(int32_t*)mandatoryAuthLen_2) { mandatoryFound_2 = true; } } if (!mandatoryFound_1) { algosConf[numAlgosConf++] = &zrtpAuthLengths.getByName(mandatoryAuthLen_1); } if (!mandatoryFound_2) { algosConf[numAlgosConf++] = &zrtpAuthLengths.getByName(mandatoryAuthLen_2); } // Build list of offered known algos in Hello, append mandatory algos if necessary for (numAlgosOffered = 0, i = 0; i < num; i++) { algosOffered[numAlgosOffered] = &zrtpAuthLengths.getByName((const char*)hello->getAuthLen(i)); if (!algosOffered[numAlgosOffered]->isValid()) continue; if (*(int32_t*)(algosOffered[numAlgosOffered]->getName()) == *(int32_t*)mandatoryAuthLen_1) { mandatoryFound_1 = true; } if (*(int32_t*)(algosOffered[numAlgosOffered++]->getName()) == *(int32_t*)mandatoryAuthLen_2) { mandatoryFound_2 = true; } } if (!mandatoryFound_1) { algosOffered[numAlgosOffered++] = &zrtpAuthLengths.getByName(mandatoryAuthLen_1); } if (!mandatoryFound_2) { algosOffered[numAlgosOffered++] = &zrtpAuthLengths.getByName(mandatoryAuthLen_2); } // Lookup offered algos in configured algos. Because of appended // mandatory algorithms at least one match will happen for (i = 0; i < numAlgosOffered; i++) { for (ii = 0; ii < numAlgosConf; ii++) { if (*(int32_t*)(algosOffered[i]->getName()) == *(int32_t*)(algosConf[ii]->getName())) { return algosConf[ii]; } } } return &zrtpAuthLengths.getByName(mandatoryAuthLen_1); } bool ZRtp::checkMultiStream(ZrtpPacketHello *hello) { int i; int num = hello->getNumPubKeys(); // Multi Stream mode is mandatory, thus if nothing is offered then it is supported :-) if (num == 0) { return true; } for (i = 0; i < num; i++) { if (*(int32_t*)(hello->getPubKeyType(i)) == *(int32_t*)mult) { return true; } } return false; } bool ZRtp::verifyH2(ZrtpPacketCommit *commit) { uint8_t tmpH3[IMPL_MAX_DIGEST_LENGTH]; sha256(commit->getH2(), HASH_IMAGE_SIZE, tmpH3); if (memcmp(tmpH3, peerH3, HASH_IMAGE_SIZE) != 0) { return false; } return true; } void ZRtp::computeHvi(ZrtpPacketDHPart* dh, ZrtpPacketHello *hello) { unsigned char* data[3]; unsigned int length[3]; /* * populate the vector to compute the HVI hash according to the * ZRTP specification. */ data[0] = (uint8_t*)dh->getHeaderBase(); length[0] = dh->getLength() * ZRTP_WORD_SIZE; data[1] = (uint8_t*)hello->getHeaderBase(); length[1] = hello->getLength() * ZRTP_WORD_SIZE; data[2] = NULL; // terminate data chunks hashListFunction(data, length, hvi); return; } void ZRtp:: computeSharedSecretSet(ZIDRecord &zidRec) { /* * Compute the Initiator's and Reponder's retained shared secret Ids. * Use negotiated HMAC. */ uint8_t randBuf[RS_LENGTH]; uint32_t macLen; if (!zidRec.isRs1Valid()) { randomZRTP(randBuf, RS_LENGTH); hmacFunction(randBuf, RS_LENGTH, (unsigned char*)initiator, strlen(initiator), rs1IDi, &macLen); hmacFunction(randBuf, RS_LENGTH, (unsigned char*)responder, strlen(responder), rs1IDr, &macLen); } else { rs1Valid = true; hmacFunction((unsigned char*)zidRec.getRs1(), RS_LENGTH, (unsigned char*)initiator, strlen(initiator), rs1IDi, &macLen); hmacFunction((unsigned char*)zidRec.getRs1(), RS_LENGTH, (unsigned char*)responder, strlen(responder), rs1IDr, &macLen); } if (!zidRec.isRs2Valid()) { randomZRTP(randBuf, RS_LENGTH); hmacFunction(randBuf, RS_LENGTH, (unsigned char*)initiator, strlen(initiator), rs2IDi, &macLen); hmacFunction(randBuf, RS_LENGTH, (unsigned char*)responder, strlen(responder), rs2IDr, &macLen); } else { rs2Valid = true; hmacFunction((unsigned char*)zidRec.getRs2(), RS_LENGTH, (unsigned char*)initiator, strlen(initiator), rs2IDi, &macLen); hmacFunction((unsigned char*)zidRec.getRs2(), RS_LENGTH, (unsigned char*)responder, strlen(responder), rs2IDr, &macLen); } /* * For the time being we don't support this types of shared secrect. Could be * easily done: somebody sets some data into our ZRtp object, check it here * and use it. Otherwise use the random data. */ randomZRTP(randBuf, RS_LENGTH); hmacFunction(randBuf, RS_LENGTH, (unsigned char*)initiator, strlen(initiator), auxSecretIDi, &macLen); hmacFunction(randBuf, RS_LENGTH, (unsigned char*)responder, strlen(responder), auxSecretIDr, &macLen); if (!zidRec.isMITMKeyAvailable()) { randomZRTP(randBuf, RS_LENGTH); hmacFunction(randBuf, RS_LENGTH, (unsigned char*)initiator, strlen(initiator), pbxSecretIDi, &macLen); hmacFunction(randBuf, RS_LENGTH, (unsigned char*)responder, strlen(responder), pbxSecretIDr, &macLen); } else { hmacFunction((unsigned char*)zidRec.getMiTMData(), RS_LENGTH, (unsigned char*)initiator, strlen(initiator), pbxSecretIDi, &macLen); hmacFunction((unsigned char*)zidRec.getMiTMData(), RS_LENGTH, (unsigned char*)responder, strlen(responder), pbxSecretIDr, &macLen); } } /* * The DH packet for this function is DHPart1 and contains the Responder's * retained secret ids. Compare them with the expected secret ids (refer * to chapter 5.3 in the specification). * When using this method then we are in Initiator role. */ void ZRtp::generateKeysInitiator(ZrtpPacketDHPart *dhPart, ZIDRecord& zidRec) { const uint8_t* setD[3]; int32_t rsFound = 0; setD[0] = setD[1] = setD[2] = NULL; /* * Select the real secrets into setD. The dhPart is DHpart1 message * received from responder. rs1IDr and rs2IDr are the expected ids using * the initator's cached retained secrets. */ int matchingSecrets = 0; if (memcmp(rs1IDr, dhPart->getRs1Id(), HMAC_SIZE) == 0) { setD[matchingSecrets++] = zidRec.getRs1(); rsFound = 0x1; } else if (memcmp(rs1IDr, dhPart->getRs2Id(), HMAC_SIZE) == 0) { setD[matchingSecrets++] = zidRec.getRs1(); rsFound = 0x2; } else if (memcmp(rs2IDr, dhPart->getRs1Id(), HMAC_SIZE) == 0) { setD[matchingSecrets++] = zidRec.getRs2(); rsFound = 0x4; } else if (memcmp(rs2IDr, dhPart->getRs2Id(), HMAC_SIZE) == 0) { setD[matchingSecrets++] = zidRec.getRs2(); rsFound = 0x8; } /* *** Not yet supported if (memcmp(auxSecretIDr, dhPart->getAuxSecretId(), 8) == 0) { DEBUGOUT((fprintf(stdout, "%c: Match for aux secret found\n", zid[0]))); setD[matchingSecrets++] = auxSecret; } */ if (memcmp(pbxSecretIDr, dhPart->getPbxSecretId(), 8) == 0) { DEBUGOUT((fprintf(stdout, "%c: Match for Other_secret found\n", zid[0]))); setD[matchingSecrets++] = zidRec.getMiTMData(); } // Check if some retained secrets found if (rsFound == 0) { // no RS matches found if (rs1Valid || rs2Valid) { // but valid RS records in cache sendInfo(Warning, WarningNoExpectedRSMatch); zidRec.resetSasVerified(); } else { // No valid RS record in cache sendInfo(Warning, WarningNoRSMatch); } } else { // at least one RS matches sendInfo(Info, InfoRSMatchFound); } /* * Ready to generate s0 here. * The formular to compute S0 (Refer to ZRTP specification 5.4.4): * s0 = hash( counter | DHResult | "ZRTP-HMAC-KDF" | ZIDi | ZIDr | \ total_hash | len(s1) | s1 | len(s2) | s2 | len(s3) | s3) * * Note: in this function we are Initiator, thus ZIDi is our zid * (zid), ZIDr is the peer's zid (peerZid). */ /* * These arrays hold the pointers and lengths of the data that must be * hashed to create S0. According to the formula the max number of * elements to hash is 12, add one for the terminating "NULL" */ unsigned char* data[13]; unsigned int length[13]; uint32_t pos = 0; // index into the array // we need a number of length data items, so define them here uint32_t counter, sLen[3]; //Very first element is a fixed counter, big endian counter = 1; counter = htonl(counter); data[pos] = (unsigned char*)&counter; length[pos++] = sizeof(uint32_t); // Next is the DH result itself data[pos] = DHss; length[pos++] = dhContext->getDhSize(); // Next the fixed string "ZRTP-HMAC-KDF" data[pos] = (unsigned char*)KDFString; length[pos++] = strlen(KDFString); // Next is Initiator's id (ZIDi), in this case as Initiator // it is zid data[pos] = zid; length[pos++] = ZID_SIZE; // Next is Responder's id (ZIDr), in this case our peer's id data[pos] = peerZid; length[pos++] = ZID_SIZE; // Next ist total hash (messageHash) itself data[pos] = messageHash; length[pos++] = hashLength; /* * For each matching shared secret hash the length of * the shared secret as 32 bit big-endian number followd by the * shared secret itself. The length of a shared seceret is * currently fixed to RS_LENGTH. If a shared * secret is not used _only_ its length is hased as zero * length. NOTE: if implementing auxSecret and/or pbxSecret -> check * this length stuff again. */ int secretHashLen = RS_LENGTH; secretHashLen = htonl(secretHashLen); // prepare 32 bit big-endian number for (int32_t i = 0; i < 3; i++) { if (setD[i] != NULL) { // a matching secret, set length, then secret sLen[i] = secretHashLen; data[pos] = (unsigned char*)&sLen[i]; length[pos++] = sizeof(uint32_t); data[pos] = (unsigned char*)setD[i]; length[pos++] = RS_LENGTH; } else { // no machting secret, set length 0, skip secret sLen[i] = 0; data[pos] = (unsigned char*)&sLen[i]; length[pos++] = sizeof(uint32_t); } } data[pos] = NULL; hashListFunction(data, length, s0); // hexdump("S0 I", s0, hashLength); memset(DHss, 0, dhContext->getDhSize()); delete[] DHss; DHss = NULL; computeSRTPKeys(); memset(s0, 0, MAX_DIGEST_LENGTH); } /* * The DH packet for this function is DHPart2 and contains the Initiator's * retained secret ids. Compare them with the expected secret ids (refer * to chapter 5.3.1 in the specification). */ void ZRtp::generateKeysResponder(ZrtpPacketDHPart *dhPart, ZIDRecord& zidRec) { const uint8_t* setD[3]; int32_t rsFound = 0; setD[0] = setD[1] = setD[2] = NULL; /* * Select the real secrets into setD */ int matchingSecrets = 0; if (memcmp(rs1IDi, dhPart->getRs1Id(), HMAC_SIZE) == 0) { setD[matchingSecrets++] = zidRec.getRs1(); rsFound = 0x1; } else if (memcmp(rs1IDi, dhPart->getRs2Id(), HMAC_SIZE) == 0) { setD[matchingSecrets++] = zidRec.getRs1(); rsFound = 0x2; } else if (memcmp(rs2IDi, dhPart->getRs2Id(), HMAC_SIZE) == 0) { setD[matchingSecrets++] = zidRec.getRs2(); rsFound |= 0x4; } else if (memcmp(rs2IDi, dhPart->getRs1Id(), HMAC_SIZE) == 0) { setD[matchingSecrets++] = zidRec.getRs2(); rsFound |= 0x8; } /* ***** not yet supported if (memcmp(auxSecretIDi, dhPart->getauxSecretId(), 8) == 0) { DEBUGOUT((fprintf(stdout, "%c: Match for aux secret found\n", zid[0]))); setD[matchingSecrets++] = ; } */ if (memcmp(pbxSecretIDi, dhPart->getPbxSecretId(), 8) == 0) { DEBUGOUT((fprintf(stdout, "%c: Match for PBX secret found\n", zid[0]))); setD[matchingSecrets++] = zidRec.getMiTMData(); } // Check if some retained secrets found if (rsFound == 0) { // no RS matches found if (rs1Valid || rs2Valid) { // but valid RS records in cache sendInfo(Warning, WarningNoExpectedRSMatch); zidRec.resetSasVerified(); } else { // No valid RS record in cache sendInfo(Warning, WarningNoRSMatch); } } else { // at least one RS matches sendInfo(Info, InfoRSMatchFound); } /* * ready to generate s0 here. * The formular to compute S0 (Refer to ZRTP specification 5.4.4): * s0 = hash( counter | DHResult | "ZRTP-HMAC-KDF" | ZIDi | ZIDr | \ total_hash | len(s1) | s1 | len(s2) | s2 | len(s3) | s3) * * Note: in this function we are Responder, thus ZIDi is the peer's zid * (peerZid), ZIDr is our zid. */ /* * These arrays hold the pointers and lengths of the data that must be * hashed to create S0. According to the formula the max number of * elements to hash is 12, add one for the terminating "NULL" */ unsigned char* data[13]; unsigned int length[13]; uint32_t pos = 0; // index into the array // we need a number of length data items, so define them here uint32_t counter, sLen[3]; //Very first element is a fixed counter, big endian counter = 1; counter = htonl(counter); data[pos] = (unsigned char*)&counter; length[pos++] = sizeof(uint32_t); // Next is the DH result itself data[pos] = DHss; length[pos++] = dhContext->getDhSize(); // Next the fixed string "ZRTP-HMAC-KDF" data[pos] = (unsigned char*)KDFString; length[pos++] = strlen(KDFString); // Next is Initiator's id (ZIDi), in this case as Responder // it is peerZid data[pos] = peerZid; length[pos++] = ZID_SIZE; // Next is Responder's id (ZIDr), in this case our own zid data[pos] = zid; length[pos++] = ZID_SIZE; // Next ist total hash (messageHash) itself data[pos] = messageHash; length[pos++] = hashLength; /* * For each matching shared secret hash the length of * the shared secret as 32 bit big-endian number followd by the * shared secret itself. The length of a shared seceret is * currently fixed to SHA256_DIGEST_LENGTH. If a shared * secret is not used _only_ its length is hased as zero * length. NOTE: if implementing auxSecret and/or pbxSecret -> check * this length stuff again. */ int secretHashLen = RS_LENGTH; secretHashLen = htonl(secretHashLen); // prepare 32 bit big-endian number for (int32_t i = 0; i < 3; i++) { if (setD[i] != NULL) { // a matching secret, set length, then secret sLen[i] = secretHashLen; data[pos] = (unsigned char*)&sLen[i]; length[pos++] = sizeof(uint32_t); data[pos] = (unsigned char*)setD[i]; length[pos++] = RS_LENGTH; } else { // no machting secret, set length 0, skip secret sLen[i] = 0; data[pos] = (unsigned char*)&sLen[i]; length[pos++] = sizeof(uint32_t); } } data[pos] = NULL; hashListFunction(data, length, s0); // hexdump("S0 R", s0, hashLength); memset(DHss, 0, dhContext->getDhSize()); delete[] DHss; DHss = NULL; computeSRTPKeys(); memset(s0, 0, MAX_DIGEST_LENGTH); } void ZRtp::KDF(uint8_t* key, uint32_t keyLength, uint8_t* label, int32_t labelLength, uint8_t* context, int32_t contextLength, int32_t L, uint8_t* output) { unsigned char* data[6]; uint32_t length[6]; uint32_t pos = 0; // index into the array uint32_t maclen = 0; // Very first element is a fixed counter, big endian uint32_t counter = 1; counter = htonl(counter); data[pos] = (unsigned char*)&counter; length[pos++] = sizeof(uint32_t); // Next element is the label, null terminated, labelLength includes null byte. data[pos] = label; length[pos++] = labelLength; // Next is the KDF context data[pos] = context; length[pos++] = contextLength; // last element is HMAC length in bits, big endian uint32_t len = htonl(L); data[pos] = (unsigned char*)&len; length[pos++] = sizeof(uint32_t); data[pos] = NULL; // Use negotiated hash. hmacListFunction(key, keyLength, data, length, output, &maclen); } // Compute the Multi Stream mode s0 void ZRtp::generateKeysMultiStream() { // allocate the maximum size, compute real size to use uint8_t KDFcontext[sizeof(peerZid)+sizeof(zid)+sizeof(messageHash)]; int32_t kdfSize = sizeof(peerZid)+sizeof(zid)+hashLength; if (myRole == Responder) { memcpy(KDFcontext, peerZid, sizeof(peerZid)); memcpy(KDFcontext+sizeof(peerZid), zid, sizeof(zid)); } else { memcpy(KDFcontext, zid, sizeof(zid)); memcpy(KDFcontext+sizeof(zid), peerZid, sizeof(peerZid)); } memcpy(KDFcontext+sizeof(zid)+sizeof(peerZid), messageHash, hashLength); KDF(zrtpSession, hashLength, (unsigned char*)zrtpMsk, strlen(zrtpMsk)+1, KDFcontext, kdfSize, hashLength*8, s0); memset(KDFcontext, 0, sizeof(KDFcontext)); computeSRTPKeys(); } void ZRtp::computePBXSecret() { // Construct the KDF context as per ZRTP specification chap 7.3.1: // ZIDi || ZIDr uint8_t KDFcontext[sizeof(peerZid)+sizeof(zid)]; int32_t kdfSize = sizeof(peerZid)+sizeof(zid); if (myRole == Responder) { memcpy(KDFcontext, peerZid, sizeof(peerZid)); memcpy(KDFcontext+sizeof(peerZid), zid, sizeof(zid)); } else { memcpy(KDFcontext, zid, sizeof(zid)); memcpy(KDFcontext+sizeof(zid), peerZid, sizeof(peerZid)); } KDF(zrtpSession, hashLength, (unsigned char*)zrtpTrustedMitm, strlen(zrtpTrustedMitm)+1, KDFcontext, kdfSize, SHA256_DIGEST_LENGTH * 8, pbxSecretTmpBuffer); pbxSecretTmp = pbxSecretTmpBuffer; // set pointer to buffer, signal PBX secret was computed } void ZRtp::computeSRTPKeys() { // allocate the maximum size, compute real size to use uint8_t KDFcontext[sizeof(peerZid)+sizeof(zid)+sizeof(messageHash)]; int32_t kdfSize = sizeof(peerZid)+sizeof(zid)+hashLength; int32_t keyLen = cipher->getKeylen() * 8; if (myRole == Responder) { memcpy(KDFcontext, peerZid, sizeof(peerZid)); memcpy(KDFcontext+sizeof(peerZid), zid, sizeof(zid)); } else { memcpy(KDFcontext, zid, sizeof(zid)); memcpy(KDFcontext+sizeof(zid), peerZid, sizeof(peerZid)); } memcpy(KDFcontext+sizeof(zid)+sizeof(peerZid), messageHash, hashLength); // Inititiator key and salt KDF(s0, hashLength, (unsigned char*)iniMasterKey, strlen(iniMasterKey)+1, KDFcontext, kdfSize, keyLen, srtpKeyI); KDF(s0, hashLength, (unsigned char*)iniMasterSalt, strlen(iniMasterSalt)+1, KDFcontext, kdfSize, 112, srtpSaltI); // Responder key and salt KDF(s0, hashLength, (unsigned char*)respMasterKey, strlen(respMasterKey)+1, KDFcontext, kdfSize, keyLen, srtpKeyR); KDF(s0, hashLength, (unsigned char*)respMasterSalt, strlen(respMasterSalt)+1, KDFcontext, kdfSize, 112, srtpSaltR); // The HMAC keys for GoClear KDF(s0, hashLength, (unsigned char*)iniHmacKey, strlen(iniHmacKey)+1, KDFcontext, kdfSize, hashLength*8, hmacKeyI); KDF(s0, hashLength, (unsigned char*)respHmacKey, strlen(respHmacKey)+1, KDFcontext, kdfSize, hashLength*8, hmacKeyR); // The keys for Confirm messages KDF(s0, hashLength, (unsigned char*)iniZrtpKey, strlen(iniZrtpKey)+1, KDFcontext, kdfSize, keyLen, zrtpKeyI); KDF(s0, hashLength, (unsigned char*)respZrtpKey, strlen(respZrtpKey)+1, KDFcontext, kdfSize, keyLen, zrtpKeyR); if (!multiStream) { // Compute the new Retained Secret KDF(s0, hashLength, (unsigned char*)retainedSec, strlen(retainedSec)+1, KDFcontext, kdfSize, SHA256_DIGEST_LENGTH*8, newRs1); // Compute the ZRTP Session Key KDF(s0, hashLength, (unsigned char*)zrtpSessionKey, strlen(zrtpSessionKey)+1, KDFcontext, kdfSize, hashLength*8, zrtpSession); // perform SAS generation according to chapter 5.5 and 8. // we don't need a speciai sasValue filed. sasValue are the first // (leftmost) 32 bits (4 bytes) of sasHash uint8_t sasBytes[4]; KDF(s0, hashLength, (unsigned char*)sasString, strlen(sasString)+1, KDFcontext, kdfSize, SHA256_DIGEST_LENGTH*8, sasHash); // according to chapter 8 only the leftmost 20 bits of sasValue (aka // sasHash) are used to create the character SAS string of type SAS // base 32 (5 bits per character) sasBytes[0] = sasHash[0]; sasBytes[1] = sasHash[1]; sasBytes[2] = sasHash[2] & 0xf0; sasBytes[3] = 0; SAS = Base32(sasBytes, 20).getEncoded(); if (signSasSeen) callback->signSAS(sasHash); } memset(KDFcontext, 0, sizeof(KDFcontext)); } bool ZRtp::srtpSecretsReady(EnableSecurity part) { SrtpSecret_t sec; sec.symEncAlgorithm = cipher->getAlgoId(); sec.keyInitiator = srtpKeyI; sec.initKeyLen = cipher->getKeylen() * 8; sec.saltInitiator = srtpSaltI; sec.initSaltLen = 112; sec.keyResponder = srtpKeyR; sec.respKeyLen = cipher->getKeylen() * 8; sec.saltResponder = srtpSaltR; sec.respSaltLen = 112; sec.authAlgorithm = authLength->getAlgoId(); sec.srtpAuthTagLen = authLength->getKeylen(); sec.sas = SAS; sec.role = myRole; return callback->srtpSecretsReady(&sec, part); } void ZRtp::setNegotiatedHash(AlgorithmEnum* hash) { switch (zrtpHashes.getOrdinal(*hash)) { case 0: hashLength = SHA256_DIGEST_LENGTH; hashFunction = sha256; hashListFunction = sha256; hmacFunction = hmac_sha256; hmacListFunction = hmac_sha256; createHashCtx = createSha256Context; closeHashCtx = closeSha256Context; hashCtxFunction = sha256Ctx; hashCtxListFunction = sha256Ctx; break; case 1: hashLength = SHA384_DIGEST_LENGTH; hashFunction = sha384; hashListFunction = sha384; hmacFunction = hmac_sha384; hmacListFunction = hmac_sha384; createHashCtx = createSha384Context; closeHashCtx = closeSha384Context; hashCtxFunction = sha384Ctx; hashCtxListFunction = sha384Ctx; break; } } void ZRtp::srtpSecretsOff(EnableSecurity part) { callback->srtpSecretsOff(part); } void ZRtp::SASVerified() { if (paranoidMode) return; // Initialize a ZID record to get peer's retained secrets ZIDRecord zidRec(peerZid); ZIDFile *zid = ZIDFile::getInstance(); zid->getRecord(&zidRec); zidRec.setSasVerified(); zid->saveRecord(&zidRec); } void ZRtp::resetSASVerified() { // Initialize a ZID record to get peer's retained secrets ZIDRecord zidRec(peerZid); ZIDFile *zid = ZIDFile::getInstance(); zid->getRecord(&zidRec); zidRec.resetSasVerified(); zid->saveRecord(&zidRec); } void ZRtp::sendInfo(GnuZrtpCodes::MessageSeverity severity, int32_t subCode) { // We've reached secure state: overwrite the SRTP master key and master salt. if (severity == Info && subCode == InfoSecureStateOn) { memset(srtpKeyI, 0, cipher->getKeylen()); memset(srtpSaltI, 0, 112/8); memset(srtpKeyR, 0, cipher->getKeylen()); memset(srtpSaltR, 0, 112/8); } callback->sendInfo(severity, subCode); } void ZRtp::zrtpNegotiationFailed(GnuZrtpCodes::MessageSeverity severity, int32_t subCode) { callback->zrtpNegotiationFailed(severity, subCode); } void ZRtp::zrtpNotSuppOther() { callback->zrtpNotSuppOther(); } void ZRtp::synchEnter() { callback->synchEnter(); } void ZRtp::synchLeave() { callback->synchLeave(); } int32_t ZRtp::sendPacketZRTP(ZrtpPacketBase *packet) { return ((packet == NULL) ? 0 : callback->sendDataZRTP(packet->getHeaderBase(), (packet->getLength() * 4) + 4)); } int32_t ZRtp::activateTimer(int32_t tm) { return (callback->activateTimer(tm)); } int32_t ZRtp::cancelTimer() { return (callback->cancelTimer()); } void ZRtp::setAuxSecret(uint8_t* data, int32_t length) { if (length > 0) { auxSecret = new uint8_t[length]; auxSecretLength = length; memcpy(auxSecret, data, length); } } void ZRtp::setClientId(std::string id) { if (id.size() < CLIENT_ID_SIZE) { unsigned char tmp[CLIENT_ID_SIZE +1] = {' '}; memcpy(tmp, id.c_str(), id.size()); tmp[CLIENT_ID_SIZE] = 0; zrtpHello.setClientId(tmp); } else { zrtpHello.setClientId((unsigned char*)id.c_str()); } int32_t len = zrtpHello.getLength() * ZRTP_WORD_SIZE; // Hello packet is ready now, compute its HMAC // (excluding the HMAC field (2*ZTP_WORD_SIZE)) and store in Hello // use the implicit hash function uint8_t hmac[IMPL_MAX_DIGEST_LENGTH]; uint32_t macLen; hmacFunctionImpl(H2, HASH_IMAGE_SIZE, (uint8_t*)zrtpHello.getHeaderBase(), len-(2*ZRTP_WORD_SIZE), hmac, &macLen); zrtpHello.setHMAC(hmac); // calculate hash over the final Hello packet, refer to chap 9.1 how to // use this hash in SIP/SDP. hashFunctionImpl((uint8_t*)zrtpHello.getHeaderBase(), len, helloHash); } void ZRtp::storeMsgTemp(ZrtpPacketBase* pkt) { uint32_t length = pkt->getLength() * ZRTP_WORD_SIZE; length = (length > sizeof(tempMsgBuffer)) ? sizeof(tempMsgBuffer) : length; memset(tempMsgBuffer, 0, sizeof(tempMsgBuffer)); memcpy(tempMsgBuffer, (uint8_t*)pkt->getHeaderBase(), length); lengthOfMsgData = length; } bool ZRtp::checkMsgHmac(uint8_t* key) { uint8_t hmac[IMPL_MAX_DIGEST_LENGTH]; uint32_t macLen; int32_t len = lengthOfMsgData-(HMAC_SIZE); // compute HMAC, but exlude the stored HMAC :-) // Use the implicit hash function hmacFunctionImpl(key, HASH_IMAGE_SIZE, tempMsgBuffer, len, hmac, &macLen); return (memcmp(hmac, tempMsgBuffer+len, (HMAC_SIZE)) == 0 ? true : false); } std::string ZRtp::getHelloHash() { std::ostringstream stm; uint8_t* hp = helloHash; stm << zrtpVersion; stm << " "; stm.fill('0'); stm << hex; for (int i = 0; i < hashLengthImpl; i++) { stm.width(2); stm << static_cast(*hp++); } return stm.str(); } std::string ZRtp::getPeerHelloHash() { std::ostringstream stm; if (peerHelloVersion[0] == 0) return std::string(); uint8_t* hp = peerHelloHash; stm << peerHelloVersion; stm << " "; stm.fill('0'); stm << hex; for (int i = 0; i < hashLengthImpl; i++) { stm.width(2); stm << static_cast(*hp++); } return stm.str(); } std::string ZRtp::getMultiStrParams() { // the string will hold binary data - it's opaque to the application std::string str(""); char tmp[MAX_DIGEST_LENGTH + 1 + 1 + 1]; // hash length + cipher + authLength + hash if (inState(SecureState) && !multiStream) { // construct array that holds zrtpSession, cipher type, auth-length, and hash type tmp[0] = zrtpHashes.getOrdinal(*hash); tmp[1] = zrtpAuthLengths.getOrdinal(*authLength); tmp[2] = zrtpSymCiphers.getOrdinal(*cipher); memcpy(tmp+3, zrtpSession, hashLength); str.assign(tmp, hashLength + 1 + 1 + 1); // set chars (bytes) to the string } return str; } void ZRtp::setMultiStrParams(std::string parameters) { char tmp[MAX_DIGEST_LENGTH + 1 + 1 + 1]; // max. hash length + cipher + authLength + hash // First get negotiated hash from parameters, set algorithms and length int i = parameters.at(0) & 0xff; hash = &zrtpHashes.getByOrdinal(i); setNegotiatedHash(hash); // sets hashlength // use string.copy(buffer, num, start=0) to retrieve chars (bytes) from the string parameters.copy(tmp, hashLength + 1 + 1 + 1, 0); i = tmp[1] & 0xff; authLength = &zrtpAuthLengths.getByOrdinal(i); i = tmp[2] & 0xff; cipher = &zrtpSymCiphers.getByOrdinal(i); memcpy(zrtpSession, tmp+3, hashLength); // after setting zrtpSession, cipher, and auth-length set multi-stream to true multiStream = true; stateEngine->setMultiStream(true); } bool ZRtp::isMultiStream() { return multiStream; } bool ZRtp::isMultiStreamAvailable() { return multiStreamAvailable; } void ZRtp::acceptEnrollment(bool accepted) { if (!accepted) { callback->zrtpInformEnrollment(EnrollmentCanceled); return; } // Get peer's zid record to store the pbx (MitM) secret // Initialize a ZID record to get peer's retained secrets ZIDRecord zidRec(peerZid); ZIDFile* zid = ZIDFile::getInstance(); zid->getRecord(&zidRec); if (pbxSecretTmp != NULL) { zidRec.setMiTMData(pbxSecretTmp); callback->zrtpInformEnrollment(EnrollmentOk); } else { callback->zrtpInformEnrollment(EnrollmentFailed); return; } zid->saveRecord(&zidRec); return; } bool ZRtp::setSignatureData(uint8_t* data, int32_t length) { if ((length % 4) != 0) return false; ZrtpPacketConfirm* cfrm = (myRole == Responder) ? &zrtpConfirm1 : &zrtpConfirm2; cfrm->setSignatureLength(length / 4); return cfrm->setSignatureData(data, length); } const uint8_t* ZRtp::getSignatureData() { return signatureData; } int32_t ZRtp::getSignatureLength() { return signatureLength * ZRTP_WORD_SIZE; } void ZRtp::conf2AckSecure() { Event_t ev; ev.type = ZrtpPacket; ev.packet = (uint8_t*)&zrtpConf2Ack; if (stateEngine != NULL) { stateEngine->processEvent(&ev); } } int32_t ZRtp::compareCommit(ZrtpPacketCommit *commit) { // TODO: enhance to compare according to rules defined in chapter 4.2 int32_t len = 0; len = !multiStream ? HVI_SIZE : (4 * ZRTP_WORD_SIZE); return (memcmp(hvi, commit->getHvi(), len)); } bool ZRtp::isEnrollmentMode() { return enrollmentMode; } void ZRtp::setEnrollmentMode(bool enrollmentMode) { this->enrollmentMode = enrollmentMode; } bool ZRtp::isPeerEnrolled() { return peerIsEnrolled; } bool ZRtp::sendSASRelayPacket(uint8_t* sh, std::string render) { uint8_t confMac[MAX_DIGEST_LENGTH]; uint32_t macLen; uint8_t* hkey, *ekey; // If we are responder then the PBX used it's Initiator keys if (myRole == Responder) { hkey = hmacKeyR; ekey = zrtpKeyR; // TODO: check signature length in zrtpConfirm1 and if not zero copy Signature data } else { hkey = hmacKeyI; ekey = zrtpKeyI; // TODO: check signature length in zrtpConfirm2 and if not zero copy Signature data } // Prepare IV data that we will use during confirm packet encryption. randomZRTP(randomIV, sizeof(randomIV)); zrtpSasRelay.setIv(randomIV); zrtpSasRelay.setTrustedSas(sh); zrtpSasRelay.setSas((uint8_t*)render.c_str()); // Encrypt and HMAC with Initiator's key - we are Initiator here int16_t hmlen = (zrtpSasRelay.getLength() - 9) * ZRTP_WORD_SIZE; cipher->getEncrypt()(ekey, cipher->getKeylen(), randomIV, (uint8_t*)zrtpSasRelay.getFiller(), hmlen); // Use negotiated HMAC (hash) hmacFunction(hkey, hashLength, (unsigned char*)zrtpSasRelay.getFiller(), hmlen, confMac, &macLen); zrtpSasRelay.setHmac(confMac); stateEngine->sendSASRelay(&zrtpSasRelay); return true; } std::string ZRtp::getSasType() { std::string sasT(sasType->getName()); return sasT; } uint8_t* ZRtp::getSasHash() { return sasHash; } int32_t ZRtp::getPeerZid(uint8_t* data) { memcpy(data, peerZid, IDENTIFIER_LEN); return IDENTIFIER_LEN; } /** EMACS ** * Local variables: * mode: c++ * c-default-style: ellemtel * c-basic-offset: 4 * End: */