| /* |
| Copyright (C) 2006-2013 Werner Dittmann |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Lesser General Public License as published by |
| the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| 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/>. |
| */ |
| |
| /* |
| * Authors: Werner Dittmann <Werner.Dittmann@t-online.de> |
| */ |
| #include <sstream> |
| |
| #include <crypto/zrtpDH.h> |
| #include <crypto/hmac256.h> |
| #include <crypto/sha256.h> |
| #include <crypto/hmac384.h> |
| #include <crypto/sha384.h> |
| |
| #include <crypto/skeinMac256.h> |
| #include <crypto/skein256.h> |
| #include <crypto/skeinMac384.h> |
| #include <crypto/skein384.h> |
| |
| #include <crypto/aesCFB.h> |
| #include <crypto/twoCFB.h> |
| |
| #include <libzrtpcpp/ZRtp.h> |
| #include <libzrtpcpp/ZrtpStateClass.h> |
| #include <libzrtpcpp/ZIDCache.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), hash(NULL), cipher(NULL), pubKey(NULL), sasType(NULL), authLength(NULL), |
| multiStream(false), multiStreamAvailable(false), peerIsEnrolled(false), mitmSeen(false), pbxSecretTmp(NULL), |
| enrollmentMode(false), configureAlgos(*config), zidRec(NULL), saveZidRecord(true) { |
| |
| enableMitmEnrollment = config->isTrustedMitM(); |
| signatureData = NULL; |
| 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; |
| |
| memcpy(ownZid, myZid, ZID_SIZE); // save the ZID |
| |
| /* |
| * 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 |
| |
| // configure all supported Hello packet versions |
| zrtpHello_11.configureHello(&configureAlgos); |
| zrtpHello_11.setH3(H3); // set H3 in Hello, included in helloHash |
| zrtpHello_11.setZid(ownZid); |
| zrtpHello_11.setVersion((uint8_t*)zrtpVersion_11); |
| |
| |
| zrtpHello_12.configureHello(&configureAlgos); |
| zrtpHello_12.setH3(H3); // set H3 in Hello, included in helloHash |
| zrtpHello_12.setZid(ownZid); |
| zrtpHello_12.setVersion((uint8_t*)zrtpVersion_12); |
| |
| if (mitmm) { // this session acts for a trusted MitM (PBX) |
| zrtpHello_11.setMitmMode(); |
| zrtpHello_12.setMitmMode(); |
| } |
| if (sasSignSupport) { // the application supports SAS signing |
| zrtpHello_11.setSasSign(); |
| zrtpHello_12.setSasSign(); |
| } |
| |
| // Keep array in ascending order (greater index -> greater version) |
| helloPackets[0].packet = &zrtpHello_11; |
| helloPackets[0].version = zrtpHello_11.getVersionInt(); |
| setClientId(id, &helloPackets[0]); // set id, compute HMAC and final helloHash |
| |
| helloPackets[1].packet = &zrtpHello_12; |
| helloPackets[1].version = zrtpHello_12.getVersionInt(); |
| setClientId(id, &helloPackets[1]); // set id, compute HMAC and final helloHash |
| |
| currentHelloPacket = helloPackets[SUPPORTED_ZRTP_VERSIONS-1].packet; // start with highest supported version |
| helloPackets[SUPPORTED_ZRTP_VERSIONS].packet = NULL; |
| peerHelloVersion[0] = 0; |
| |
| 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; |
| } |
| if (zidRec != NULL) { |
| delete zidRec; |
| zidRec = NULL; |
| } |
| 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, size_t length) { |
| Event_t ev; |
| |
| peerSSRC = pSSRC; |
| ev.type = ZrtpPacket; |
| ev.length = length; |
| 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 currentHelloPacket; |
| } |
| |
| 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) { |
| |
| myRole = Initiator; |
| |
| if (!hello->isLengthOk()) { |
| *errMsg = CriticalSWError; |
| return NULL; |
| } |
| // Save data before detailed checks - may aid in analysing problems |
| peerClientId.assign((char*)hello->getClientId(), ZRTP_WORD_SIZE * 4); |
| memcpy(peerHelloVersion, hello->getVersion(), ZRTP_WORD_SIZE); |
| peerHelloVersion[ZRTP_WORD_SIZE] = 0; |
| |
| // Save our peer's (presumably the Responder) ZRTP id |
| memcpy(peerZid, hello->getZid(), ZID_SIZE); |
| if (memcmp(peerZid, ownZid, ZID_SIZE) == 0) { // peers have same ZID???? |
| *errMsg = EqualZIDHello; |
| return NULL; |
| } |
| memcpy(peerH3, hello->getH3(), HASH_IMAGE_SIZE); |
| |
| int32_t helloLen = hello->getLength() * ZRTP_WORD_SIZE; |
| |
| // calculate hash over the received Hello packet - is peer's hello hash. |
| // Use implicit hash algorithm |
| hashFunctionImpl((unsigned char*)hello->getHeaderBase(), helloLen, peerHelloHash); |
| |
| sendInfo(Info, InfoHelloReceived); |
| |
| /* |
| * 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) { |
| pubKey = findBestPubkey(hello); // Check for public key algorithm first, must set 'hash' as well |
| if (hash == NULL) { |
| *errMsg = UnsuppHashType; |
| return NULL; |
| } |
| if (cipher == NULL) // public key selection may have set the cipher already |
| cipher = findBestCipher(hello, pubKey); |
| if (authLength == NULL) // public key selection may have set the SRTP authLen already |
| authLength = findBestAuthLen(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, |
| * thus get our peer's retained secret data first. |
| */ |
| zidRec = getZidCacheInstance()->getRecord(peerZid); |
| |
| //Compute the Initator's and Responder's retained secret ids. |
| computeSharedSecretSet(zidRec); |
| |
| // Check if a PBX application set the MitM flag. |
| mitmSeen = hello->isMitmMode(); |
| |
| 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(ownZid); |
| 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. |
| 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); |
| |
| return &zrtpCommit; |
| } |
| |
| ZrtpPacketCommit* ZRtp::prepareCommitMultiStream(ZrtpPacketHello *hello) { |
| |
| randomZRTP(hvi, ZRTP_WORD_SIZE*4); // This is the Multi-Stream NONCE size |
| |
| zrtpCommit.setZid(ownZid); |
| 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); |
| |
| return &zrtpCommit; |
| } |
| |
| /* |
| * At this point we will take the role of the Responder. We 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); |
| |
| if (!commit->isLengthOk(ZrtpPacketCommit::DhExchange)) { |
| *errMsg = CriticalSWError; |
| return NULL; |
| } |
| |
| // Check if ZID in Commit is the same as we got in Hello |
| uint8_t tmpZid[ZID_SIZE]; |
| memcpy(tmpZid, commit->getZid(), ZID_SIZE); |
| if (memcmp(peerZid, tmpZid, ZID_SIZE) != 0) { // ZIDs do not match???? |
| sendInfo(Severe, SevereProtocolError); |
| *errMsg = CriticalSWError; |
| return NULL; |
| } |
| |
| // The following code checks 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); |
| // 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; |
| } |
| if (*(int32_t*)(cp->getName()) == *(int32_t*)ec38 || *(int32_t*)(cp->getName()) == *(int32_t*)e414) { |
| if (!(*(int32_t*)(hash->getName()) == *(int32_t*)s384 || *(int32_t*)(hash->getName()) == *(int32_t*)skn3)) { |
| *errMsg = UnsuppHashType; |
| 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); |
| |
| // Re-compute auxSecretIDr because we changed roles *IDr with my H3, *IDi with peer's H3 |
| // Setup a DHPart1 packet. |
| myRole = Responder; |
| computeAuxSecretIds(); // recompute AUX secret ids because we are now Responder, use different H3 |
| |
| 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. |
| memcpy(peerHvi, commit->getHvi(), HVI_SIZE); |
| |
| // We are responder. Release the pre-computed SHA context because it was prepared for Initiator. |
| // Setup and compute for Responder. |
| 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*)currentHelloPacket->getHeaderBase(), currentHelloPacket->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); |
| |
| if (!dhPart1->isLengthOk()) { |
| *errMsg = CriticalSWError; |
| return NULL; |
| } |
| // 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); |
| |
| // 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; |
| // 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); |
| |
| 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); |
| |
| if (!dhPart2->isLengthOk()) { |
| *errMsg = CriticalSWError; |
| return NULL; |
| } |
| // 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, currentHelloPacket); |
| 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; |
| /* |
| * 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. May reset the verify flag in ZID record. |
| */ |
| generateKeysResponder(dhPart2, 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 runs at PBX user agent enrollment service then set flag in confirm |
| // packet and store the MitM key |
| if (enrollmentMode) { |
| // As clarification to RFC6189: store new PBX secret only if we don't have |
| // a matching PBX secret for the peer's ZID. |
| if (!peerIsEnrolled) { |
| computePBXSecret(); |
| zidRec->setMiTMData(pbxSecretTmp); |
| } |
| // Set flag to enable user's client to ask for confirmation or re-confirmation. |
| zrtpConfirm1.setPBXEnrollment(); |
| } |
| 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); |
| |
| if (!commit->isLengthOk(ZrtpPacketCommit::MultiStream)) { |
| *errMsg = CriticalSWError; |
| return NULL; |
| } |
| // 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*)currentHelloPacket->getHeaderBase(), currentHelloPacket->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); |
| |
| if (!confirm1->isLengthOk()) { |
| *errMsg = CriticalSWError; |
| return NULL; |
| } |
| 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(), (uint8_t*)confirm1->getIv(), confirm1->getHashH0(), hmlen); |
| |
| // 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 && confirm1->isSignatureLengthOk()) { |
| 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(); |
| |
| // 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(); |
| |
| // now we are ready to save the new RS1 which inherits the verified |
| // flag from old RS1 |
| zidRec->setNewRs1((const uint8_t*)newRs1); |
| |
| // 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) { |
| // As clarification to RFC6189: store new PBX secret only if we don't have |
| // a matching PBX secret for the peer's ZID. |
| if (!peerIsEnrolled) { |
| computePBXSecret(); |
| zidRec->setMiTMData(pbxSecretTmp); |
| } |
| // Set flag to enable user's client to ask for confirmation or re-confirmation. |
| zrtpConfirm2.setPBXEnrollment(); |
| } |
| } |
| if (saveZidRecord) |
| getZidCacheInstance()->saveRecord(zidRec); |
| |
| // 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()) { |
| // As clarification to RFC6189: if already enrolled (having a matching PBX secret) |
| // ask for reconfirmation. |
| if (!peerIsEnrolled) { |
| callback->zrtpAskEnrollment(EnrollmentRequest); |
| } |
| else { |
| callback->zrtpAskEnrollment(EnrollmentReconfirm); |
| } |
| } |
| return &zrtpConfirm2; |
| } |
| |
| /* |
| * 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); |
| |
| if (!confirm1->isLengthOk()) { |
| *errMsg = CriticalSWError; |
| return NULL; |
| } |
| 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; |
| } |
| // Cast away the const for the IV - the standalone AES CFB modifies IV on return |
| cipher->getDecrypt()(zrtpKeyR, cipher->getKeylen(), (uint8_t*)confirm1->getIv(), confirm1->getHashH0(), hmlen); |
| |
| // 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; |
| } |
| // 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); |
| |
| if (!confirm2->isLengthOk()) { |
| *errMsg = CriticalSWError; |
| return NULL; |
| } |
| 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; |
| } |
| // Cast away the const for the IV - the standalone AES CFB modifies IV on return |
| cipher->getDecrypt()(zrtpKeyI, cipher->getKeylen(), (uint8_t*)confirm2->getIv(), confirm2->getHashH0(), hmlen); |
| |
| 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 && confirm2->isSignatureLengthOk() ) { |
| 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(); |
| // 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(); |
| } |
| |
| // save new RS1, this inherits the verified flag from old RS1 |
| zidRec->setNewRs1((const uint8_t*)newRs1); |
| if (saveZidRecord) |
| getZidCacheInstance()->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(); |
| // As clarification to RFC6189: if already enrolled (having a matching PBX secret) |
| // ask for reconfirmation. |
| if (!peerIsEnrolled) { |
| callback->zrtpAskEnrollment(EnrollmentRequest); |
| } |
| else { |
| callback->zrtpAskEnrollment(EnrollmentReconfirm); |
| } |
| } |
| } |
| 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; |
| } |
| } |
| return &zrtpConf2Ack; |
| } |
| |
| ZrtpPacketErrorAck* ZRtp::prepareErrorAck(ZrtpPacketError* epkt) { |
| if (epkt->getLength() < 4) |
| sendInfo(ZrtpError, CriticalSWError * -1); |
| else |
| 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(ownZid); |
| 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; |
| |
| if (!srly->isLengthOk()) { |
| *errMsg = CriticalSWError; |
| return NULL; |
| } |
| 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; |
| |
| 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 |
| } |
| // Cast away the const for the IV - the standalone AES CFB modifies IV on return |
| cipher->getDecrypt()(ekey, cipher->getKeylen(), (uint8_t*)srly->getIv(), (uint8_t*)srly->getFiller(), hmlen); |
| |
| 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; |
| } |
| } |
| std::string cs(cipher->getReadable()); |
| cs.append("/").append(pubKey->getName()); |
| |
| // 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 |
| // our computed SAS hash but we may use a different SAS rendering algorithm to |
| // render the computed SAS. |
| if (sasHashNull || !peerIsEnrolled) { |
| cs.append("/MitM"); |
| newSasHash = sasHash; |
| } |
| else { |
| cs.append("/SASviaMitM"); |
| } |
| // If other SAS schemes required - check here and use others |
| const uint8_t* render = srly->getSasAlgo(); |
| 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; |
| if (*(int32_t*)b32 == *(int32_t*)(renderAlgo->getName())) { |
| SAS = Base32(sasBytes, 20).getEncoded(); |
| } |
| else { |
| SAS.assign(sas256WordsEven[sasBytes[0]]).append(":").append(sas256WordsOdd[sasBytes[1]]); |
| } |
| } |
| bool verify = zidRec->isSasVerified() && srly->isSASFlag(); |
| callback->srtpSecretsOn(cs, SAS, verify); |
| 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]; |
| |
| // 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. |
| numAlgosConf = configureAlgos.getNumConfiguredAlgos(HashAlgorithm); |
| for (i = 0; i < numAlgosConf; i++) { |
| algosConf[i] = &configureAlgos.getAlgoAt(HashAlgorithm, i); |
| } |
| |
| // Build list of offered known algos in Hello, append mandatory algos if necessary |
| for (numAlgosOffered = 0, i = 0; i < num; i++) { |
| algosOffered[numAlgosOffered] = &zrtpHashes.getByName((const char*)hello->getHashType(i)); |
| if (!algosOffered[numAlgosOffered]->isValid()) |
| continue; |
| numAlgosOffered++; |
| } |
| |
| // Lookup offered algos in configured algos. |
| 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]; |
| |
| 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. |
| numAlgosConf = configureAlgos.getNumConfiguredAlgos(CipherAlgorithm); |
| for (i = 0; i < numAlgosConf; i++) { |
| algosConf[i] = &configureAlgos.getAlgoAt(CipherAlgorithm, i); |
| } |
| // Build list of offered known algos names in Hello. |
| for (numAlgosOffered = 0, i = 0; i < num; i++) { |
| algosOffered[numAlgosOffered] = &zrtpSymCiphers.getByName((const char*)hello->getCipherType(i)); |
| if (!algosOffered[numAlgosOffered]->isValid()) |
| continue; |
| numAlgosOffered++; |
| } |
| // Lookup offered algos in configured algos. Prefer algorithms that appear first in Hello packet (offered). |
| 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]; |
| } |
| } |
| } |
| // If we don't have a match - use the mandatory algorithm |
| return &zrtpSymCiphers.getByName(mandatoryCipher); |
| } |
| |
| // We can have the non-NIST in the list of orderedAlgos even if they are not available |
| // in the code (refer to ZrtpConfigure.cpp). If they are not build in they cannot appear |
| // in'configureAlgos' and thus not in the intersection lists. Thus a ZRTP build that |
| // does not include the non-NIST curves also works without problems. |
| // |
| AlgorithmEnum* ZRtp::findBestPubkey(ZrtpPacketHello *hello) { |
| |
| AlgorithmEnum* peerIntersect[ZrtpConfigure::maxNoOfAlgos+1]; |
| AlgorithmEnum* ownIntersect[ZrtpConfigure::maxNoOfAlgos+1]; |
| |
| // Build list of own pubkey algorithm names, must follow the order |
| // defined in RFC 6189, chapter 4.1.2. |
| const char *orderedAlgos[] = {dh2k, e255, ec25, dh3k, e414, ec38}; |
| int numOrderedAlgos = sizeof(orderedAlgos) / sizeof(const char*); |
| |
| int numAlgosPeer = hello->getNumPubKeys(); |
| if (numAlgosPeer == 0) { |
| hash = findBestHash(hello); // find a hash algorithm |
| return &zrtpPubKeys.getByName(mandatoryPubKey); |
| } |
| // Build own list of intersecting algos, keep own order or algorithms |
| // The list must include real public key algorithms only, so skip mult-stream mode, |
| // preshared and alike. |
| int numAlgosOwn = configureAlgos.getNumConfiguredAlgos(PubKeyAlgorithm); |
| int numOwnIntersect = 0; |
| for (int i = 0; i < numAlgosOwn; i++) { |
| ownIntersect[numOwnIntersect] = &configureAlgos.getAlgoAt(PubKeyAlgorithm, i); |
| if (*(int32_t*)(ownIntersect[numOwnIntersect]->getName()) == *(int32_t*)mult) { |
| continue; // skip multi-stream mode |
| } |
| for (int ii = 0; ii < numAlgosPeer; ii++) { |
| if (*(int32_t*)(ownIntersect[numOwnIntersect]->getName()) == *(int32_t*)(zrtpPubKeys.getByName((const char*)hello->getPubKeyType(ii)).getName())) { |
| numOwnIntersect++; |
| break; |
| } |
| } |
| } |
| // Build list of peer's intersecting algos: take own list as input and build a |
| // list of algorithms that we have in common. The order of the list is according |
| // to peer's Hello packet (peer's preferences). |
| int numPeerIntersect = 0; |
| for (int i = 0; i < numAlgosPeer; i++) { |
| peerIntersect[numPeerIntersect] = &zrtpPubKeys.getByName((const char*)hello->getPubKeyType(i)); |
| for (int ii = 0; ii < numOwnIntersect; ii++) { |
| if (*(int32_t*)(ownIntersect[ii]->getName()) == *(int32_t*)(peerIntersect[numPeerIntersect]->getName())) { |
| numPeerIntersect++; |
| break; |
| } |
| } |
| } |
| if (numPeerIntersect == 0) { // If we don't have a common algorithm - use mandatory algorithms |
| hash = findBestHash(hello); |
| return &zrtpPubKeys.getByName(mandatoryPubKey); |
| } |
| |
| // If we have only one algorithm in common or if the first entry matches - take it. |
| // Otherwise determine which algorithm from the intersection lists is first in the |
| // list of ordered algorithms and select it (RFC6189, section 4.1.2). |
| AlgorithmEnum* useAlgo; |
| if (numPeerIntersect > 1 && *(int32_t*)(ownIntersect[0]->getName()) != *(int32_t*)(peerIntersect[0]->getName())) { |
| int own, peer; |
| |
| const int32_t *name = (int32_t*)ownIntersect[0]->getName(); |
| for (own = 0; own < numOrderedAlgos; own++) { |
| if (*name == *(int32_t*)orderedAlgos[own]) |
| break; |
| } |
| name = (int32_t*)peerIntersect[0]->getName(); |
| for (peer = 0; peer < numOrderedAlgos; peer++) { |
| if (*name == *(int32_t*)orderedAlgos[peer]) |
| break; |
| } |
| if (own < peer) { |
| useAlgo = ownIntersect[0]; |
| } |
| else { |
| useAlgo = peerIntersect[0]; |
| } |
| // find fastest of conf vs intersecting |
| } |
| else { |
| useAlgo = peerIntersect[0]; |
| } |
| int32_t algoName = *(int32_t*)(useAlgo->getName()); |
| |
| // select a corresponding strong hash if necessary. |
| if (algoName == *(int32_t*)ec38 || algoName == *(int32_t*)e414) { |
| hash = getStrongHashOffered(hello, algoName); |
| cipher = getStrongCipherOffered(hello, algoName); |
| } |
| else { |
| hash = getHashOffered(hello, algoName);; |
| cipher = getCipherOffered(hello, algoName); |
| } |
| authLength = getAuthLenOffered(hello, algoName); |
| return useAlgo; |
| } |
| |
| AlgorithmEnum* ZRtp::findBestSASType(ZrtpPacketHello *hello) { |
| |
| int i; |
| int ii; |
| int numAlgosOffered; |
| AlgorithmEnum* algosOffered[ZrtpConfigure::maxNoOfAlgos+1]; |
| |
| int numAlgosConf; |
| AlgorithmEnum* algosConf[ZrtpConfigure::maxNoOfAlgos+1]; |
| |
| int num = hello->getNumSas(); |
| if (num == 0) { |
| return &zrtpSasTypes.getByName(mandatorySasType); |
| } |
| // Build list of configured SAS algorithm names |
| numAlgosConf = configureAlgos.getNumConfiguredAlgos(SasType); |
| for (i = 0; i < numAlgosConf; i++) { |
| algosConf[i] = &configureAlgos.getAlgoAt(SasType, i); |
| } |
| // Build list of offered known algos in Hello, |
| for (numAlgosOffered = 0, i = 0; i < num; i++) { |
| algosOffered[numAlgosOffered] = &zrtpSasTypes.getByName((const char*)hello->getSasType(i)); |
| if (!algosOffered[numAlgosOffered]->isValid()) |
| continue; |
| numAlgosOffered++; |
| } |
| // Lookup offered algos in configured algos. Prefer algorithms that appear first in Hello packet (offered). |
| 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]; |
| } |
| } |
| } |
| // If we don't have a match - use the mandatory algorithm |
| 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]; |
| |
| int num = hello->getNumAuth(); |
| if (num == 0) { |
| return &zrtpAuthLengths.getByName(mandatoryAuthLen_1); |
| } |
| |
| // Build list of configured Authentication tag length algorithm names. |
| numAlgosConf = configureAlgos.getNumConfiguredAlgos(AuthLength); |
| for (i = 0; i < numAlgosConf; i++) { |
| algosConf[i] = &configureAlgos.getAlgoAt(AuthLength, i); |
| } |
| |
| // Build list of offered known algos in Hello. |
| for (numAlgosOffered = 0, i = 0; i < num; i++) { |
| algosOffered[numAlgosOffered] = &zrtpAuthLengths.getByName((const char*)hello->getAuthLen(i)); |
| if (!algosOffered[numAlgosOffered]->isValid()) |
| continue; |
| numAlgosOffered++; |
| } |
| |
| // Lookup offered algos in configured algos. Prefer algorithms that appear first in Hello packet (offered). |
| 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]; |
| } |
| } |
| } |
| // If we don't have a match - use the mandatory algorithm |
| return &zrtpAuthLengths.getByName(mandatoryAuthLen_1); |
| } |
| |
| // The following set of functions implement a 'non-NIST first policy' if nonNist computes |
| // to true. They prefer nonNist algorithms if these are available. Otherwise they use the NIST |
| // counterpart or simply call the according findBest*(...) function. |
| // |
| // Only the findBestPubkey(...) function calls them after it selected the public key algorithm. |
| // If the public key algorithm is non-NIST and if the policy is set to PreferNonNist then |
| // nonNist becomes true. |
| // |
| // The functions work according to the RFC6189 spec: the initiator can select every algorithm |
| // that both parties support. Thus the Initiator can even select an algorithm the wasn't offered |
| // in its own Hello packet but that the Initiator found in the peer's Hello and that is available |
| // for it. |
| // |
| AlgorithmEnum* ZRtp::getStrongHashOffered(ZrtpPacketHello *hello, int32_t algoName) { |
| |
| int numHash = hello->getNumHashes(); |
| bool nonNist = (algoName == *(int32_t*)e414 || algoName == *(int32_t*)e255) && configureAlgos.getSelectionPolicy() == ZrtpConfigure::PreferNonNist; |
| |
| if (nonNist) { |
| for (int i = 0; i < numHash; i++) { |
| int32_t nm = *(int32_t*)(hello->getHashType(i)); |
| if (nm == *(int32_t*)skn3) { |
| return &zrtpHashes.getByName((const char*)hello->getHashType(i)); |
| } |
| } |
| } |
| for (int i = 0; i < numHash; i++) { |
| int32_t nm = *(int32_t*)(hello->getHashType(i)); |
| if (nm == *(int32_t*)s384 || nm == *(int32_t*)skn3) { |
| return &zrtpHashes.getByName((const char*)hello->getHashType(i)); |
| } |
| } |
| return NULL; // returning NULL -> prepareCommit(...) terminates ZRTP, missing strong hash is an error |
| } |
| |
| AlgorithmEnum* ZRtp::getStrongCipherOffered(ZrtpPacketHello *hello, int32_t algoName) { |
| |
| int num = hello->getNumCiphers(); |
| bool nonNist = (algoName == *(int32_t*)e414 || algoName == *(int32_t*)e255) && configureAlgos.getSelectionPolicy() == ZrtpConfigure::PreferNonNist; |
| |
| if (nonNist) { |
| for (int i = 0; i < num; i++) { |
| int32_t nm = *(int32_t*)(hello->getCipherType(i)); |
| if (nm == *(int32_t*)two3) { |
| return &zrtpSymCiphers.getByName((const char*)hello->getCipherType(i)); |
| } |
| } |
| } |
| for (int i = 0; i < num; i++) { |
| int32_t nm = *(int32_t*)(hello->getCipherType(i)); |
| if (nm == *(int32_t*)aes3 || nm == *(int32_t*)two3) { |
| return &zrtpSymCiphers.getByName((const char*)hello->getCipherType(i)); |
| } |
| } |
| return NULL; // returning NULL -> prepareCommit(...) finds the best cipher |
| } |
| |
| AlgorithmEnum* ZRtp::getHashOffered(ZrtpPacketHello *hello, int32_t algoName) { |
| |
| int num = hello->getNumHashes(); |
| bool nonNist = (algoName == *(int32_t*)e414 || algoName == *(int32_t*)e255) && configureAlgos.getSelectionPolicy() == ZrtpConfigure::PreferNonNist; |
| |
| if (nonNist) { |
| for (int i = 0; i < num; i++) { |
| int32_t nm = *(int32_t*)(hello->getHashType(i)); |
| if (nm == *(int32_t*)skn2 || nm == *(int32_t*)skn3) { |
| return &zrtpHashes.getByName((const char*)hello->getHashType(i)); |
| } |
| } |
| } |
| return findBestHash(hello); |
| } |
| |
| AlgorithmEnum* ZRtp::getCipherOffered(ZrtpPacketHello *hello, int32_t algoName) { |
| |
| int num = hello->getNumCiphers(); |
| bool nonNist = (algoName == *(int32_t*)e414 || algoName == *(int32_t*)e255) && configureAlgos.getSelectionPolicy() == ZrtpConfigure::PreferNonNist; |
| |
| if (nonNist) { |
| for (int i = 0; i < num; i++) { |
| int32_t nm = *(int32_t*)(hello->getCipherType(i)); |
| if (nm == *(int32_t*)two2 || nm == *(int32_t*)two3) { |
| return &zrtpSymCiphers.getByName((const char*)hello->getCipherType(i)); |
| } |
| } |
| } |
| return NULL; // returning NULL -> prepareCommit(...) finds the best cipher |
| } |
| |
| AlgorithmEnum* ZRtp::getAuthLenOffered(ZrtpPacketHello *hello, int32_t algoName) { |
| |
| int num = hello->getNumAuth(); |
| bool nonNist = (algoName == *(int32_t*)e414 || algoName == *(int32_t*)e255) && configureAlgos.getSelectionPolicy() == ZrtpConfigure::PreferNonNist; |
| |
| if (nonNist) { |
| for (int i = 0; i < num; i++) { |
| int32_t nm = *(int32_t*)(hello->getAuthLen(i)); |
| if (nm == *(int32_t*)sk32 || nm == *(int32_t*)sk64) { |
| return &zrtpAuthLengths.getByName((const char*)hello->getAuthLen(i)); |
| } |
| } |
| } |
| return findBestAuthLen(hello); |
| } |
| |
| 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]; |
| |
| // packet does not have the correct size, treat H2 verfication as failed. |
| if (!commit->isLengthOk(multiStream ? ZrtpPacketCommit::MultiStream : ZrtpPacketCommit::DhExchange)) |
| return false; |
| |
| 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; |
| |
| fprintf(stderr, "Compute shared secrets\n"); |
| detailInfo.secretsCached = 0; |
| 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); |
| detailInfo.secretsCached = Rs1; |
| } |
| |
| 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); |
| detailInfo.secretsCached |= Rs2; |
| } |
| |
| 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); |
| detailInfo.secretsCached |= Pbx; |
| } |
| computeAuxSecretIds(); |
| } |
| |
| void ZRtp::computeAuxSecretIds() { |
| uint8_t randBuf[RS_LENGTH]; |
| uint32_t macLen; |
| |
| if (auxSecret == NULL) { |
| randomZRTP(randBuf, RS_LENGTH); |
| hmacFunction(randBuf, RS_LENGTH, H3, HASH_IMAGE_SIZE, auxSecretIDi, &macLen); |
| hmacFunction(randBuf, RS_LENGTH, H3, HASH_IMAGE_SIZE, auxSecretIDr, &macLen); |
| } |
| else { |
| if (myRole == Initiator) { // I'm initiator thus use my H3 for initiator's IDi, peerH3 for respnder's IDr |
| hmacFunction(auxSecret, auxSecretLength, H3, HASH_IMAGE_SIZE, auxSecretIDi, &macLen); |
| hmacFunction(auxSecret, auxSecretLength, peerH3, HASH_IMAGE_SIZE, auxSecretIDr, &macLen); |
| } |
| else { |
| hmacFunction(auxSecret, auxSecretLength, peerH3, HASH_IMAGE_SIZE, auxSecretIDi, &macLen); |
| hmacFunction(auxSecret, auxSecretLength, H3, HASH_IMAGE_SIZE, auxSecretIDr, &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; |
| |
| detailInfo.secretsMatchedDH = 0; |
| if (memcmp(rs1IDr, dhPart->getRs1Id(), HMAC_SIZE) == 0 || memcmp(rs1IDr, dhPart->getRs2Id(), HMAC_SIZE) == 0) |
| detailInfo.secretsMatchedDH |= Rs1; |
| if (memcmp(rs2IDr, dhPart->getRs1Id(), HMAC_SIZE) == 0 || memcmp(rs2IDr, dhPart->getRs2Id(), HMAC_SIZE) == 0) |
| detailInfo.secretsMatchedDH |= Rs2; |
| /* |
| * 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. |
| */ |
| // Check which RS we shall use for first place (s1) |
| detailInfo.secretsMatched = 0; |
| if (memcmp(rs1IDr, dhPart->getRs1Id(), HMAC_SIZE) == 0) { |
| setD[0] = zidRec->getRs1(); |
| rsFound = 0x1; |
| detailInfo.secretsMatched = Rs1; |
| } |
| else if (memcmp(rs1IDr, dhPart->getRs2Id(), HMAC_SIZE) == 0) { |
| setD[0] = zidRec->getRs1(); |
| rsFound = 0x2; |
| detailInfo.secretsMatched = Rs1; |
| } |
| else if (memcmp(rs2IDr, dhPart->getRs1Id(), HMAC_SIZE) == 0) { |
| setD[0] = zidRec->getRs2(); |
| rsFound = 0x4; |
| detailInfo.secretsMatched = Rs2; |
| } |
| else if (memcmp(rs2IDr, dhPart->getRs2Id(), HMAC_SIZE) == 0) { |
| setD[0] = zidRec->getRs2(); |
| rsFound = 0x8; |
| detailInfo.secretsMatched = Rs2; |
| } |
| |
| if (memcmp(auxSecretIDr, dhPart->getAuxSecretId(), 8) == 0) { |
| DEBUGOUT((fprintf(stdout, "Initiator: Match for aux secret found\n"))); |
| setD[1] = auxSecret; |
| detailInfo.secretsMatched |= Aux; |
| detailInfo.secretsMatchedDH |= Aux; |
| } |
| if (auxSecret != NULL && (detailInfo.secretsMatched & Aux) == 0) { |
| sendInfo(Warning, WarningNoExpectedAuxMatch); |
| } |
| |
| // check if we have a matching PBX secret and place it third (s3) |
| if (memcmp(pbxSecretIDr, dhPart->getPbxSecretId(), HMAC_SIZE) == 0) { |
| DEBUGOUT((fprintf(stdout, "%c: Match for Other_secret found\n", zid[0]))); |
| setD[2] = zidRec->getMiTMData(); |
| detailInfo.secretsMatched |= Pbx; |
| detailInfo.secretsMatchedDH |= Pbx; |
| // Flag to record that fact that we have a MitM key of the other peer. |
| peerIsEnrolled = true; |
| } |
| // 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(); |
| saveZidRecord = false; // Don't save RS until user verfied/confirmed SAS |
| } |
| 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 = zrtpHtonl(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] = ownZid; |
| 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 = zrtpHtonl(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++] = (i != 1) ? RS_LENGTH : auxSecretLength; |
| } |
| 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; |
| |
| detailInfo.secretsMatchedDH = 0; |
| if (memcmp(rs1IDi, dhPart->getRs1Id(), HMAC_SIZE) == 0 || memcmp(rs1IDi, dhPart->getRs2Id(), HMAC_SIZE) == 0) |
| detailInfo.secretsMatchedDH |= Rs1; |
| if (memcmp(rs2IDi, dhPart->getRs1Id(), HMAC_SIZE) == 0 || memcmp(rs2IDi, dhPart->getRs2Id(), HMAC_SIZE) == 0) |
| detailInfo.secretsMatchedDH |= Rs2; |
| |
| /* |
| * Select the real secrets into setD |
| */ |
| // Check which RS we shall use for first place (s1) |
| detailInfo.secretsMatched = 0; |
| if (memcmp(rs1IDi, dhPart->getRs1Id(), HMAC_SIZE) == 0) { |
| setD[0] = zidRec->getRs1(); |
| rsFound = 0x1; |
| detailInfo.secretsMatched = Rs1; |
| } |
| else if (memcmp(rs1IDi, dhPart->getRs2Id(), HMAC_SIZE) == 0) { |
| setD[0] = zidRec->getRs1(); |
| rsFound = 0x2; |
| detailInfo.secretsMatched = Rs1; |
| } |
| else if (memcmp(rs2IDi, dhPart->getRs1Id(), HMAC_SIZE) == 0) { |
| setD[0] = zidRec->getRs2(); |
| rsFound |= 0x4; |
| detailInfo.secretsMatched = Rs2; |
| } |
| else if (memcmp(rs2IDi, dhPart->getRs2Id(), HMAC_SIZE) == 0) { |
| setD[0] = zidRec->getRs2(); |
| rsFound |= 0x8; |
| detailInfo.secretsMatched = Rs2; |
| } |
| |
| if (memcmp(auxSecretIDi, dhPart->getAuxSecretId(), 8) == 0) { |
| DEBUGOUT((fprintf(stdout, "Responder: Match for aux secret found\n"))); |
| setD[1] = auxSecret; |
| detailInfo.secretsMatched |= Aux; |
| detailInfo.secretsMatchedDH |= Aux; |
| } |
| // If we have an auxSecret but no match from peer - report this. |
| if (auxSecret != NULL && (detailInfo.secretsMatched & Aux) == 0) { |
| sendInfo(Warning, WarningNoExpectedAuxMatch); |
| } |
| |
| if (memcmp(pbxSecretIDi, dhPart->getPbxSecretId(), 8) == 0) { |
| DEBUGOUT((fprintf(stdout, "%c: Match for PBX secret found\n", ownZid[0]))); |
| setD[2] = zidRec->getMiTMData(); |
| detailInfo.secretsMatched |= Pbx; |
| detailInfo.secretsMatchedDH |= Pbx; |
| peerIsEnrolled = true; |
| } |
| // 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(); |
| saveZidRecord = false; // Don't save RS until user verfied/confirmed SAS |
| } |
| 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 = zrtpHtonl(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] = ownZid; |
| 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 = zrtpHtonl(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++] = (i != 1) ? RS_LENGTH : auxSecretLength; |
| } |
| 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 = zrtpHtonl(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 = zrtpHtonl(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(ownZid)+sizeof(messageHash)]; |
| int32_t kdfSize = sizeof(peerZid)+sizeof(ownZid)+hashLength; |
| |
| if (myRole == Responder) { |
| memcpy(KDFcontext, peerZid, sizeof(peerZid)); |
| memcpy(KDFcontext+sizeof(peerZid), ownZid, sizeof(ownZid)); |
| } |
| else { |
| memcpy(KDFcontext, ownZid, sizeof(ownZid)); |
| memcpy(KDFcontext+sizeof(ownZid), peerZid, sizeof(peerZid)); |
| } |
| memcpy(KDFcontext+sizeof(ownZid)+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(ownZid)]; |
| int32_t kdfSize = sizeof(peerZid)+sizeof(ownZid); |
| |
| if (myRole == Responder) { |
| memcpy(KDFcontext, peerZid, sizeof(peerZid)); |
| memcpy(KDFcontext+sizeof(peerZid), ownZid, sizeof(ownZid)); |
| } |
| else { |
| memcpy(KDFcontext, ownZid, sizeof(ownZid)); |
| memcpy(KDFcontext+sizeof(ownZid), 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(ownZid)+sizeof(messageHash)]; |
| int32_t kdfSize = sizeof(peerZid)+sizeof(ownZid)+hashLength; |
| |
| int32_t keyLen = cipher->getKeylen() * 8; |
| |
| if (myRole == Responder) { |
| memcpy(KDFcontext, peerZid, sizeof(peerZid)); |
| memcpy(KDFcontext+sizeof(peerZid), ownZid, sizeof(ownZid)); |
| } |
| else { |
| memcpy(KDFcontext, ownZid, sizeof(ownZid)); |
| memcpy(KDFcontext+sizeof(ownZid), peerZid, sizeof(peerZid)); |
| } |
| memcpy(KDFcontext+sizeof(ownZid)+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); |
| |
| detailInfo.pubKey = detailInfo.sasType = NULL; |
| 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 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; |
| if (*(int32_t*)b32 == *(int32_t*)(sasType->getName())) { |
| SAS = Base32(sasBytes, 20).getEncoded(); |
| } |
| else { |
| SAS.assign(sas256WordsEven[sasBytes[0]]).append(":").append(sas256WordsOdd[sasBytes[1]]); |
| } |
| |
| if (signSasSeen) |
| callback->signSAS(sasHash); |
| |
| detailInfo.pubKey = pubKey->getReadable(); |
| detailInfo.sasType = sasType->getReadable(); |
| } |
| // set algorithm names into detailInfo structure |
| detailInfo.authLength = authLength->getReadable(); |
| detailInfo.cipher = cipher->getReadable(); |
| detailInfo.hash = hash->getReadable(); |
| |
| 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; |
| |
| bool rc = callback->srtpSecretsReady(&sec, part); |
| |
| // The call state engine calls ForSender always after ForReceiver. |
| if (part == ForSender) { |
| std::string cs(cipher->getReadable()); |
| if (!multiStream) { |
| cs.append("/").append(pubKey->getName()); |
| if (mitmSeen) |
| cs.append("/EndAtMitM"); |
| callback->srtpSecretsOn(cs, SAS, zidRec->isSasVerified()); |
| } |
| else { |
| std::string cs1(""); |
| if (mitmSeen) |
| cs.append("/EndAtMitM"); |
| callback->srtpSecretsOn(cs, cs1, true); |
| } |
| } |
| return rc; |
| } |
| |
| |
| 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; |
| |
| case 2: |
| hashLength = SKEIN256_DIGEST_LENGTH; |
| hashFunction = skein256; |
| hashListFunction = skein256; |
| |
| hmacFunction = macSkein256; |
| hmacListFunction = macSkein256; |
| |
| createHashCtx = createSkein256Context; |
| closeHashCtx = closeSkein256Context; |
| hashCtxFunction = skein256Ctx; |
| hashCtxListFunction = skein256Ctx; |
| break; |
| |
| case 3: |
| hashLength = SKEIN384_DIGEST_LENGTH; |
| hashFunction = skein384; |
| hashListFunction = skein384; |
| |
| hmacFunction = macSkein384; |
| hmacListFunction = macSkein384; |
| |
| createHashCtx = createSkein384Context; |
| closeHashCtx = closeSkein384Context; |
| hashCtxFunction = skein384Ctx; |
| hashCtxListFunction = skein384Ctx; |
| break; |
| } |
| } |
| |
| |
| void ZRtp::srtpSecretsOff(EnableSecurity part) { |
| callback->srtpSecretsOff(part); |
| } |
| |
| void ZRtp::SASVerified() { |
| if (paranoidMode) |
| return; |
| |
| zidRec->setSasVerified(); |
| saveZidRecord = true; |
| getZidCacheInstance()->saveRecord(zidRec); |
| } |
| |
| void ZRtp::resetSASVerified() { |
| |
| zidRec->resetSasVerified(); |
| getZidCacheInstance()->saveRecord(zidRec); |
| } |
| |
| void ZRtp::setRs2Valid() { |
| |
| if (zidRec != NULL) { |
| zidRec->setRs2Valid(); |
| if (saveZidRecord) |
| getZidCacheInstance()->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, HelloPacketVersion* hpv) { |
| |
| unsigned char tmp[CLIENT_ID_SIZE +1] = {' '}; |
| memcpy(tmp, id.c_str(), id.size() > CLIENT_ID_SIZE ? CLIENT_ID_SIZE : id.size()); |
| tmp[CLIENT_ID_SIZE] = 0; |
| |
| hpv->packet->setClientId(tmp); |
| |
| int32_t len = hpv->packet->getLength() * ZRTP_WORD_SIZE; |
| |
| // Hello packets are 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*)hpv->packet->getHeaderBase(), len-(2*ZRTP_WORD_SIZE), hmac, &macLen); |
| hpv->packet->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*)hpv->packet->getHeaderBase(), len, hpv->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(int32_t index) { |
| std::ostringstream stm; |
| |
| if (index < 0 || index >= MAX_ZRTP_VERSIONS) |
| return std::string(); |
| |
| uint8_t* hp = helloPackets[index].helloHash; |
| |
| char version[5] = {'\0'}; |
| strncpy(version, (const char*)helloPackets[index].packet->getVersion(), ZRTP_WORD_SIZE); |
| |
| stm << version; |
| 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) { |
| zidRec->resetMITMKeyAvailable(); |
| callback->zrtpInformEnrollment(EnrollmentCanceled); |
| getZidCacheInstance()->saveRecord(zidRec); |
| return; |
| } |
| if (pbxSecretTmp != NULL) { |
| zidRec->setMiTMData(pbxSecretTmp); |
| getZidCacheInstance()->saveRecord(zidRec); |
| callback->zrtpInformEnrollment(EnrollmentOk); |
| } |
| else { |
| callback->zrtpInformEnrollment(EnrollmentFailed); |
| } |
| 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.getHeaderBase(); |
| ev.length = sizeof (Conf2AckPacket_t) + 12; // 12 is fixed ZRTP (RTP) header size |
| |
| if (stateEngine != NULL) { |
| stateEngine->processEvent(&ev); |
| } |
| } |
| |
| int32_t ZRtp::compareCommit(ZrtpPacketCommit *commit) { |
| // TODO: enhance to compare according to rules defined in chapter 4.2, |
| // but we don't support Preshared. |
| 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.setSasAlgo((uint8_t*)render.c_str()); |
| |
| 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; |
| } |
| |
| const ZRtp::zrtpInfo* ZRtp::getDetailInfo() { |
| return &detailInfo; |
| } |
| |
| std::string ZRtp::getPeerClientId() { |
| if (peerClientId.empty()) |
| return std::string(); |
| return peerClientId; |
| } |
| |
| std::string ZRtp::getPeerProtcolVersion() { |
| if (peerHelloVersion[0] == 0) |
| return std::string(); |
| return std::string((char*)peerHelloVersion); |
| } |
| |
| /** EMACS ** |
| * Local variables: |
| * mode: c++ |
| * c-default-style: ellemtel |
| * c-basic-offset: 4 |
| * End: |
| */ |
| |