// Test ZRTP extension for ccRTP // // Copyright (C) 2008 Werner Dittmann // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include #include #include using namespace ost; using namespace std; using namespace GnuZrtpCodes; class PacketsPattern { public: inline const InetHostAddress& getDestinationAddress() const { return destinationAddress; } inline const tpport_t getDestinationPort() const { return destinationPort; } uint32 getPacketsNumber() const { return packetsNumber; } uint32 getSsrc() const { return 0xdeadbeef; } const unsigned char* getPacketData(uint32 i) { return data[i%2]; } const size_t getPacketSize(uint32 i) { return strlen((char*)data[i%2]) + 1 ; } private: static const InetHostAddress destinationAddress; static const uint16 destinationPort = 5002; static const uint32 packetsNumber = 10; static const uint32 packetsSize = 12; static const unsigned char* data[]; }; const InetHostAddress PacketsPattern::destinationAddress = InetHostAddress("localhost"); const unsigned char* PacketsPattern::data[] = { (unsigned char*)"0123456789\n", (unsigned char*)"987654321\n" }; PacketsPattern pattern; class ExtZrtpSession : public SymmetricZRTPSession { // ExtZrtpSession(InetMcastAddress& ima, tpport_t port) : // RTPSession(ima,port) {} // // ExtZrtpSession(InetHostAddress& ia, tpport_t port) : // RTPSession(ia,port) {} public: ExtZrtpSession(uint32 ssrc, const InetHostAddress& ia) : SingleThreadRTPSession(ssrc, ia){ cout << "Extended" << endl; } ExtZrtpSession(uint32 ssrc, const InetHostAddress& ia, tpport_t dataPort) : SingleThreadRTPSession(ssrc, ia, dataPort) { cout << "Extended" << endl; } ExtZrtpSession(const InetHostAddress& ia, tpport_t dataPort) : SingleThreadRTPSession(ia, dataPort) { cout << "Extended" << endl; } void onGotGoodbye(const SyncSource& source, const std::string& reason) { cout << "I got a Goodbye packet from " << hex << (int)source.getID() << "@" << dec << source.getNetworkAddress() << ":" << source.getControlTransportPort() << endl; cout << " Goodbye reason: \"" << reason << "\"" << endl; } // redefined from QueueRTCPManager void onGotRR(SyncSource& source, RecvReport& RR, uint8 blocks) { SingleThreadRTPSession::onGotRR(source,RR,blocks); cout << "I got an RR RTCP report from " << hex << (int)source.getID() << "@" << dec << source.getNetworkAddress() << ":" << source.getControlTransportPort() << endl; } }; /** * SymmetricZRTPSession in non-security mode (RTPSession compatible). * * The next two classes show how to use SymmetricZRTPSession * in the same way as RTPSession. This is straightforward, * just don't do any configuration or initialization. */ class SendPacketTransmissionTest: public Thread, public TimerPort { public: void run() { doTest(); } int doTest() { // should be valid? //RTPSession tx(); ExtZrtpSession tx(pattern.getSsrc(), InetHostAddress("localhost")); // SymmetricZRTPSession tx(pattern.getSsrc(), InetHostAddress("localhost")); tx.setSchedulingTimeout(10000); tx.setExpireTimeout(1000000); tx.startRunning(); tx.setPayloadFormat(StaticPayloadFormat(sptPCMU)); if (!tx.addDestination(pattern.getDestinationAddress(), pattern.getDestinationPort()) ) { return 1; } // 2 packets per second (packet duration of 500ms) uint32 period = 500; uint16 inc = tx.getCurrentRTPClockRate()/2; TimerPort::setTimer(period); uint32 i; for (i = 0; i < pattern.getPacketsNumber(); i++ ) { tx.putData(i*inc, pattern.getPacketData(i), pattern.getPacketSize(i)); cout << "Sent some data: " << i << endl; Thread::sleep(TimerPort::getTimer()); TimerPort::incTimer(period); } tx.putData(i*inc, (unsigned char*)"exit", 5); Thread::sleep(TimerPort::getTimer()); return 0; } }; class RecvPacketTransmissionTest: public Thread { public: void run() { doTest(); } int doTest() { ExtZrtpSession rx(pattern.getSsrc()+1, pattern.getDestinationAddress(), pattern.getDestinationPort()); // SymmetricZRTPSession rx(pattern.getSsrc()+1, pattern.getDestinationAddress(), // pattern.getDestinationPort()); rx.setSchedulingTimeout(10000); rx.setExpireTimeout(1000000); rx.startRunning(); rx.setPayloadFormat(StaticPayloadFormat(sptPCMU)); // arbitrary number of loops to provide time to start transmitter if (!rx.addDestination(pattern.getDestinationAddress(), pattern.getDestinationPort()+2) ) { return 1; } for ( int i = 0; i < 5000 ; i++ ) { const AppDataUnit* adu; while ( (adu = rx.getData(rx.getFirstTimestamp())) ) { cerr << "got some data: " << adu->getData() << endl; if (*adu->getData() == 'e') { delete adu; return 0; } delete adu; } Thread::sleep(70); } return 0; } }; /** * SymmetricZRTPSession in security mode. * * The next two classes show how to use SymmetricZRTPSession * using the standard ZRTP handshake an switching to encrypted (SRTP) mode. * The application enables this by calling initialize(...). * Some embedded logging informs about the ZRTP processing. */ class ZrtpSendPacketTransmissionTest: public Thread, public TimerPort { public: void run() { doTest(); } int doTest() { // should be valid? //RTPSession tx(); ExtZrtpSession tx(pattern.getSsrc(), pattern.getDestinationAddress(), pattern.getDestinationPort()+2); tx.initialize("test_t.zid"); tx.setSchedulingTimeout(10000); tx.setExpireTimeout(1000000); tx.startRunning(); tx.setPayloadFormat(StaticPayloadFormat(sptPCMU)); if (!tx.addDestination(pattern.getDestinationAddress(), pattern.getDestinationPort()) ) { return 1; } tx.startZrtp(); // 2 packets per second (packet duration of 500ms) uint32 period = 500; uint16 inc = tx.getCurrentRTPClockRate()/2; TimerPort::setTimer(period); uint32 i; for (i = 0; i < pattern.getPacketsNumber(); i++ ) { tx.putData(i*inc, pattern.getPacketData(i), pattern.getPacketSize(i)); cout << "Sent some data: " << i << endl; Thread::sleep(TimerPort::getTimer()); TimerPort::incTimer(period); } tx.putData(i*inc, (unsigned char*)"exit", 5); Thread::sleep(200); return 0; } }; class ZrtpRecvPacketTransmissionTest: public Thread { public: void run() { doTest(); } int doTest() { ExtZrtpSession rx(pattern.getSsrc()+1, pattern.getDestinationAddress(), pattern.getDestinationPort()); rx.initialize("test_r.zid"); rx.setSchedulingTimeout(10000); rx.setExpireTimeout(1000000); rx.startRunning(); rx.setPayloadFormat(StaticPayloadFormat(sptPCMU)); // arbitrary number of loops to provide time to start transmitter if (!rx.addDestination(pattern.getDestinationAddress(), pattern.getDestinationPort()+2) ) { return 1; } rx.startZrtp(); for ( int i = 0; i < 5000 ; i++ ) { const AppDataUnit* adu; while ( (adu = rx.getData(rx.getFirstTimestamp())) ) { cerr << "got some data: " << adu->getData() << endl; if (*adu->getData() == 'e') { delete adu; return 0; } delete adu; } Thread::sleep(70); } return 0; } }; /** * Simple User Callback class * * This class overwrite some methods from ZrtpUserCallback to get information * about ZRTP processing and information about ZRTP results. The standard * implementation of this class just perform return, thus effectively * supressing any callback or trigger. */ class MyUserCallback: public ZrtpUserCallback { static map infoMap; static map warningMap; static map severeMap; static map zrtpMap; static bool initialized; SymmetricZRTPSession* session; public: MyUserCallback(SymmetricZRTPSession* s) { session = s; if (initialized) { return; } infoMap.insert(pair(InfoHelloReceived, new string("Hello received, preparing a Commit"))); infoMap.insert(pair(InfoCommitDHGenerated, new string("Commit: Generated a public DH key"))); infoMap.insert(pair(InfoRespCommitReceived, new string("Responder: Commit received, preparing DHPart1"))); infoMap.insert(pair(InfoDH1DHGenerated, new string("DH1Part: Generated a public DH key"))); infoMap.insert(pair(InfoInitDH1Received, new string("Initiator: DHPart1 received, preparing DHPart2"))); infoMap.insert(pair(InfoRespDH2Received, new string("Responder: DHPart2 received, preparing Confirm1"))); infoMap.insert(pair(InfoInitConf1Received, new string("Initiator: Confirm1 received, preparing Confirm2"))); infoMap.insert(pair(InfoRespConf2Received, new string("Responder: Confirm2 received, preparing Conf2Ack"))); infoMap.insert(pair(InfoRSMatchFound, new string("At least one retained secrets matches - security OK"))); infoMap.insert(pair(InfoSecureStateOn, new string("Entered secure state"))); infoMap.insert(pair(InfoSecureStateOff, new string("No more security for this session"))); warningMap.insert(pair(WarningDHAESmismatch, new string("Commit contains an AES256 cipher but does not offer a Diffie-Helman 4096"))); warningMap.insert(pair(WarningGoClearReceived, new string("Received a GoClear message"))); warningMap.insert(pair(WarningDHShort, new string("Hello offers an AES256 cipher but does not offer a Diffie-Helman 4096"))); warningMap.insert(pair(WarningNoRSMatch, new string("No retained secret matches - verify SAS"))); warningMap.insert(pair(WarningCRCmismatch, new string("Internal ZRTP packet checksum mismatch - packet dropped"))); warningMap.insert(pair(WarningSRTPauthError, new string("Dropping packet because SRTP authentication failed!"))); warningMap.insert(pair(WarningSRTPreplayError, new string("Dropping packet because SRTP replay check failed!"))); severeMap.insert(pair(SevereHelloHMACFailed, new string("Hash HMAC check of Hello failed!"))); severeMap.insert(pair(SevereCommitHMACFailed, new string("Hash HMAC check of Commit failed!"))); severeMap.insert(pair(SevereDH1HMACFailed, new string("Hash HMAC check of DHPart1 failed!"))); severeMap.insert(pair(SevereDH2HMACFailed, new string("Hash HMAC check of DHPart2 failed!"))); severeMap.insert(pair(SevereCannotSend, new string("Cannot send data - connection or peer down?"))); severeMap.insert(pair(SevereProtocolError, new string("Internal protocol error occured!"))); severeMap.insert(pair(SevereNoTimer, new string("Cannot start a timer - internal resources exhausted?"))); severeMap.insert(pair(SevereTooMuchRetries, new string("Too much retries during ZRTP negotiation - connection or peer down?"))); zrtpMap.insert(pair(MalformedPacket, new string("Malformed packet (CRC OK, but wrong structure)"))); zrtpMap.insert(pair(CriticalSWError, new string("Critical software error"))); zrtpMap.insert(pair(UnsuppZRTPVersion, new string("Unsupported ZRTP version"))); zrtpMap.insert(pair(HelloCompMismatch, new string("Hello components mismatch"))); zrtpMap.insert(pair(UnsuppHashType, new string("Hash type not supported"))); zrtpMap.insert(pair(UnsuppCiphertype, new string("Cipher type not supported"))); zrtpMap.insert(pair(UnsuppPKExchange, new string("Public key exchange not supported"))); zrtpMap.insert(pair(UnsuppSRTPAuthTag, new string("SRTP auth. tag not supported"))); zrtpMap.insert(pair(UnsuppSASScheme, new string("SAS scheme not supported"))); zrtpMap.insert(pair(NoSharedSecret, new string("No shared secret available, DH mode required"))); zrtpMap.insert(pair(DHErrorWrongPV, new string("DH Error: bad pvi or pvr ( == 1, 0, or p-1)"))); zrtpMap.insert(pair(DHErrorWrongHVI, new string("DH Error: hvi != hashed data"))); zrtpMap.insert(pair(SASuntrustedMiTM, new string("Received relayed SAS from untrusted MiTM"))); zrtpMap.insert(pair(ConfirmHMACWrong, new string("Auth. Error: Bad Confirm pkt HMAC"))); zrtpMap.insert(pair(NonceReused, new string("Nonce reuse"))); zrtpMap.insert(pair(EqualZIDHello, new string("Equal ZIDs in Hello"))); zrtpMap.insert(pair(GoCleatNotAllowed, new string("GoClear packet received, but not allowed"))); initialized = true; } void showMessage(GnuZrtpCodes::MessageSeverity sev, int32_t subCode) { string* msg; if (sev == Info) { msg = infoMap[subCode]; if (msg != NULL) { cout << *msg << endl; } } if (sev == Warning) { msg = warningMap[subCode]; if (msg != NULL) { cout << *msg << endl; } } if (sev == Severe) { msg = severeMap[subCode]; if (msg != NULL) { cout << *msg << endl; } } if (sev == ZrtpError) { if (subCode < 0) { // received an error packet from peer subCode *= -1; cout << "Received error packet: "; } else { cout << "Sent error packet: "; } msg = zrtpMap[subCode]; if (msg != NULL) { cout << *msg << endl; } } } void zrtpNegotiationFailed(GnuZrtpCodes::MessageSeverity sev, int32_t subCode) { string* msg; if (sev == ZrtpError) { if (subCode < 0) { // received an error packet from peer subCode *= -1; cout << "Received error packet: "; } else { cout << "Sent error packet: "; } msg = zrtpMap[subCode]; if (msg != NULL) { cout << *msg << endl; } } else { msg = severeMap[subCode]; cout << *msg << endl; } } void secureOn(std::string cipher) { cout << "Using cipher:" << cipher << endl; } void showSAS(std::string sas, bool verified) { cout << "SAS is: " << sas << endl; } }; mapMyUserCallback::infoMap; mapMyUserCallback::warningMap; mapMyUserCallback::severeMap; mapMyUserCallback::zrtpMap; bool MyUserCallback::initialized = false; /** * SymmetricZRTPSession in security mode and using a callback class. * * The next two classes show how to use SymmetricZRTPSession * using the standard ZRTP handshake an switching to encrypted (SRTP) mode. * The application enables this by calling initialize(...). * In addition the application sets a callback class (see above). ZRTP calls * the methods of the callback class and the application may implement * appropriate methods to deal with these triggers. */ class ZrtpSendPacketTransmissionTestCB : public Thread, public TimerPort { public: void run() { doTest(); } int doTest() { // should be valid? //RTPSession tx(); ExtZrtpSession tx(/*pattern.getSsrc(),*/ pattern.getDestinationAddress(), pattern.getDestinationPort()+2); tx.initialize("test_t.zid"); // At this point the Hello hash is available. See ZRTP specification // chapter 9.1 for further information when an how to use the Hello // hash. cout << "TX Hello hash: " << tx.getHelloHash() << endl; cout << "TX Hello hash length: " << tx.getHelloHash().length() << endl; tx.setUserCallback(new MyUserCallback(&tx)); tx.setSchedulingTimeout(10000); tx.setExpireTimeout(1000000); tx.startRunning(); tx.setPayloadFormat(StaticPayloadFormat(sptPCMU)); if (!tx.addDestination(pattern.getDestinationAddress(), pattern.getDestinationPort()) ) { return 1; } tx.startZrtp(); // 2 packets per second (packet duration of 500ms) uint32 period = 500; uint16 inc = tx.getCurrentRTPClockRate()/2; TimerPort::setTimer(period); uint32 i; for (i = 0; i < pattern.getPacketsNumber(); i++ ) { tx.putData(i*inc, pattern.getPacketData(i), pattern.getPacketSize(i)); cout << "Sent some data: " << i << endl; Thread::sleep(TimerPort::getTimer()); TimerPort::incTimer(period); } tx.putData(i*inc, (unsigned char*)"exit", 5); Thread::sleep(TimerPort::getTimer()); return 0; } }; class ZrtpRecvPacketTransmissionTestCB: public Thread { public: void run() { doTest(); } int doTest() { ExtZrtpSession rx( /*pattern.getSsrc()+1,*/ pattern.getDestinationAddress(), pattern.getDestinationPort()); rx.initialize("test_r.zid"); // At this point the Hello hash is available. See ZRTP specification // chapter 9.1 for further information when an how to use the Hello // hash. cout << "RX Hello hash: " << rx.getHelloHash() << endl; cout << "RX Hello hash length: " << rx.getHelloHash().length() << endl; rx.setUserCallback(new MyUserCallback(&rx)); rx.setSchedulingTimeout(10000); rx.setExpireTimeout(1000000); rx.startRunning(); rx.setPayloadFormat(StaticPayloadFormat(sptPCMU)); // arbitrary number of loops to provide time to start transmitter if (!rx.addDestination(pattern.getDestinationAddress(), pattern.getDestinationPort()+2) ) { return 1; } rx.startZrtp(); for ( int i = 0; i < 5000 ; i++ ) { const AppDataUnit* adu; while ( (adu = rx.getData(rx.getFirstTimestamp())) ) { cerr << "got some data: " << adu->getData() << endl; if (*adu->getData() == 'e') { delete adu; return 0; } delete adu; } Thread::sleep(500); } return 0; } }; int main(int argc, char *argv[]) { int result = 0; bool send = false; bool recv = false; char c; /* check args */ while (1) { c = getopt(argc, argv, "rs"); if (c == -1) { break; } switch (c) { case 'r': recv = true; break; case 's': send = true; break; default: cerr << "Wrong Arguments, only -s and -r are accepted" << endl; } } if (send || recv) { if (send) { cout << "Running as sender" << endl; } else { cout << "Running as receiver" << endl; } } else { cerr << "No send or receive argument specificied" << endl; exit(1); } // accept as parameter if must run as --send or --recv #if 0 RecvPacketTransmissionTest *rx; SendPacketTransmissionTest *tx; // run several tests in parallel threads if ( send ) { tx = new SendPacketTransmissionTest(); tx->start(); tx->join(); } else if ( recv ) { rx = new RecvPacketTransmissionTest(); rx->start(); rx->join(); } //#endif //#if 0 ZrtpRecvPacketTransmissionTest *zrx; ZrtpSendPacketTransmissionTest *ztx; if ( send ) { ztx = new ZrtpSendPacketTransmissionTest(); ztx->start(); ztx->join(); } else if ( recv ) { zrx = new ZrtpRecvPacketTransmissionTest(); zrx->start(); zrx->join(); } #endif ZrtpRecvPacketTransmissionTestCB *zrxcb; ZrtpSendPacketTransmissionTestCB *ztxcb; if ( send ) { ztxcb = new ZrtpSendPacketTransmissionTestCB(); ztxcb->start(); ztxcb->join(); } else if ( recv ) { zrxcb = new ZrtpRecvPacketTransmissionTestCB(); zrxcb->start(); zrxcb->join(); } exit(result); } /** EMACS ** * Local variables: * mode: c++ * c-default-style: ellemtel * c-basic-offset: 4 * End: */