diff options
author | Mark Purcell <msp@debian.org> | 2013-07-09 15:55:55 +0100 |
---|---|---|
committer | Mark Purcell <msp@debian.org> | 2013-07-09 15:55:55 +0100 |
commit | 669109e369a1be69ff7c4108eb545eff4c5c26d9 (patch) | |
tree | 73c117a2e7dd22a7a6ee315101f6357ab43386ec /src/Zrtp.cpp |
libzrtpcpp (2.3.4-1) unstable; urgency=medium
* New upstream release
- Fixes "CVE-2013-2221 CVE-2013-2222 CVE-2013-2223" (Closes: #714650)
# imported from the archive
Diffstat (limited to 'src/Zrtp.cpp')
-rw-r--r-- | src/Zrtp.cpp | 2527 |
1 files changed, 2527 insertions, 0 deletions
diff --git a/src/Zrtp.cpp b/src/Zrtp.cpp new file mode 100644 index 0000000..2002462 --- /dev/null +++ b/src/Zrtp.cpp @@ -0,0 +1,2527 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +/*F + * Authors: Werner Dittmann <Werner.Dittmann@t-online.de> + */ +#include <sstream> + +#include <libzrtpcpp/crypto/ZrtpDH.h> +#include <libzrtpcpp/crypto/hmac256.h> +#include <libzrtpcpp/crypto/sha256.h> +#include <libzrtpcpp/crypto/hmac384.h> +#include <libzrtpcpp/crypto/sha384.h> +#include <libzrtpcpp/crypto/aesCFB.h> +#include <libzrtpcpp/crypto/twoCFB.h> + +#include <libzrtpcpp/ZRtp.h> +#include <libzrtpcpp/ZrtpStateClass.h> +#include <libzrtpcpp/ZIDFile.h> +#include <libzrtpcpp/ZIDRecord.h> +#include <libzrtpcpp/Base32.h> + +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<uint32_t>(*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<uint32_t>(*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: + */ + |