All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gnu.java.zrtp.ZRtp Maven / Gradle / Ivy

There is a newer version: 4.1.3
Show newest version
/*
 * Copyright (C) 2006-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, see .
 *
 * Authors: Werner Dittmann 
 */

package gnu.java.zrtp;

import gnu.java.zrtp.ZrtpConstants.SupportedSASTypes;
import gnu.java.zrtp.packets.*;
import gnu.java.zrtp.utils.Base32;
import gnu.java.zrtp.utils.EmojiBase32;
import gnu.java.zrtp.utils.ZrtpSecureRandom;
import gnu.java.zrtp.utils.ZrtpUtils;
import gnu.java.zrtp.zidfile.ZidFile;
import gnu.java.zrtp.zidfile.ZidRecord;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.digests.SHA384Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.DHPublicKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECPoint;

import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.EnumSet;

/**
 * The main ZRTP class.
 * 
 * This is the main class of the RTP/SRTP independent part of the GNU ZRTP. It
 * handles the ZRTP HMAC, DH, and other data management. The user of this class
 * needs to know only a few methods and needs to provide only a few external
 * functions to connect to a Timer mechanism and to send data via RTP and SRTP.
 * Refer to the ZrtpCallback class to get detailed information regarding the
 * callback methods required by GNU RTP.
 * 
 * The class ZRTPTransformEngine is the Java JMF specific implementation that extends
 * standard Java JMF and the related RTP transport. Refer to the documentation of
 * ZRTPTransformEngine to get more information about the usage of ZRtp and associated
 * classes.
 * 
 * The main entry into the ZRTP class is the processExtensionHeader() method.
 * 
 * This class does not directly handle the protocol states, timers, and packet
 * resend. The protocol state engine is responsible for these actions.
 * 
 * Example how to use ZRtp:
 * 
 * 
 *   transConnector = (ZrtpTransformConnector)TransformManager.createZRTPConnector(sa);
 *   zrtpEngine = transConnector.getEngine();
 *   zrtpEngine.setUserCallback(new MyCallback());
 *   if (!zrtpEngine.initialize("test_t.zid"))
 *       System.out.println("initialize failed");
 * 
 *    zrtpEngine.startZrtpEngine();
 * 
* * @see ZrtpCallback * * @author Werner Dittmann <[email protected]> * */ public class ZRtp { // max. number of parallel supported ZRTP protocol versions. static final int MAX_ZRTP_VERSIONS = 2; // max. number of parallel supported ZRTP protocol versions. static final int SUPPORTED_ZRTP_VERSIONS = 1; /** * Faster access to Hello packets with different versions. */ static class HelloPacketVersion { int version; ZrtpPacketHello packet; byte[] helloHash; } /** * The state engine takes care of protocol processing. */ private final ZrtpStateClass stateEngine; /** * ZRTP cache entry that holds RS data for a specific ZID. */ ZidRecord zidRec; /** * This is my ZID that I send to the peer. */ private final byte[] zid = new byte[ZidRecord.IDENTIFIER_LENGTH]; /** * The peer's ZID */ private byte[] peerZid; /** * The call back class provides me with the interface to send data and to * deal with timer management of the hosting system. */ private ZrtpCallback callback = null; private AsymmetricCipherKeyPair dhKeyPair = null; private AsymmetricCipherKeyPair ecKeyPair = null; private SecureRandom secRand; /** * The computed DH shared secret */ private byte[] DHss = null; /** * My computed public key */ private byte[] pubKeyBytes = null; /** * My Role in the game */ private ZrtpCallback.Role myRole; /** * The human readable SAS value */ private String SAS; /** * The SAS hash for signaling and alike. Refer to chapters 5.5, 6.13, 9.4. * sasValue and the SAS string are derived from sasHash. */ private byte[] sasHash = null; /** * The variables for the retained shared secrets */ private byte[] rs1IDr = null; private byte[] rs2IDr = null; private byte[] auxSecretIDr = null; private byte[] pbxSecretIDr = null; private byte[] rs1IDi = null; private byte[] rs2IDi = null; private byte[] auxSecretIDi = null; private byte[] pbxSecretIDi = null; /** * Remember is valid rs1 or rs2 records were available */ private boolean rs1Valid = false; private boolean rs2Valid = false; /** * My hvi */ private byte[] hvi = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; /** * The peer's hvi */ private byte[] peerHvi = null; /** * Context to compute the hash and HMAC of selected messages. This is the * negotiated hash algorithm */ private int hashLength; private Digest hashFunction; private Digest hashCtxFunction; private HMac hmacFunction; // These are implicit hash and HMAC settings. private int hashLengthImpl = ZrtpConstants.SHA256_DIGEST_LENGTH; private Digest hashFunctionImpl = new SHA256Digest(); private HMac hmacFunctionImpl = new HMac(new SHA256Digest()); /** * Committed Hash, Cipher, and public key algorithms */ private ZrtpConstants.SupportedHashes hash; private ZrtpConstants.SupportedSymCiphers cipher; private ZrtpConstants.SupportedPubKeys pubKey; /** * The selected SAS type. */ private ZrtpConstants.SupportedSASTypes sasType; /** * The selected authenitaction length. */ private ZrtpConstants.SupportedAuthLengths authLength; /** * The Hash images as defined in chapter 5.1.1 (H0 is a random value, not * stored here). Need full max hash lenght to store hash value but only the * leftmost 128 bits are used in computations and comparisons. */ private byte[] H0 = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; private byte[] H1 = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; private byte[] H2 = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; private byte[] H3 = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; private byte[] peerHelloHash = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; private byte[] peerHelloVersion = null; // need 128 bits only to store peer's values private byte[] peerH2 = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; private byte[] peerH3 = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; /** * The hash over selected messages */ private byte[] messageHash = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; /** * The s0 */ private byte[] s0 = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; /** * The new Retained Secret */ private byte[] newRs1 = null; /** * The GoClear HMAC keys and confirm HMAC key */ private byte[] hmacKeyI = null; private byte[] hmacKeyR = null; /** * The Initiator's srtp key and salt */ private byte[] srtpKeyI = null; private byte[] srtpSaltI = null; /** * The Responder's srtp key and salt */ private byte[] srtpKeyR = null; private byte[] srtpSaltR = null; /** * The keys used to encrypt/decrypt the confirm message */ private byte[] zrtpKeyI = null; private byte[] zrtpKeyR = null; /** * The ZRTP Session Key Refer to chapter 5.4.1.4 */ private byte[] zrtpSession = null; /** * True if this ZRTP instance uses multi-stream mode. */ private boolean multiStream = false; /** * True if the other ZRTP client supports multi-stream mode. */ private boolean multiStreamAvailable = false; /** * Enable MitM (PBX) enrollment * * If set to true then ZRTP honors the PBX enrollment flag in * Commit packets and calls the appropriate user callback * methods. If the parameter is set to false ZRTP ignores the PBX * enrollment flags. */ private boolean enableMitmEnrollment = false; /** * True if a valid trusted MitM key of the other peer is available, i.e. enrolled. */ private boolean peerIsEnrolled; /** * Set to true if the Hello packet contained the M-flag (MitM flag). * We use this later to check some stuff for SAS Relay processing */ private boolean mitmSeen = false; /** * Set to true if the Hello packet contained the S-flag (sign SAS flag). */ private boolean signSasSeen = false; /** * Temporarily store computed pbxSecret, if user accepts enrollment then * it will copied to our ZID record of the PBX (MitM) */ private byte[] pbxSecretTmp = null; /** * If true then we will set the enrollment flag (E) in the confirm * packets. Set to true if the PBX enrollment service started this ZRTP * session. Can be set to true only if mitmMode is also true. */ private boolean enrollmentMode = false; /** * Pre-initialized packets. */ private ZrtpPacketHello zrtpHello_11 = new ZrtpPacketHello(); private ZrtpPacketHello zrtpHello_12 = new ZrtpPacketHello(); private ZrtpPacketHelloAck zrtpHelloAck = new ZrtpPacketHelloAck(); private ZrtpPacketConf2Ack zrtpConf2Ack = new ZrtpPacketConf2Ack(); // ZrtpPacketClearAck zrtpClearAck; // ZrtpPacketGoClear zrtpGoClear; private ZrtpPacketError zrtpError = new ZrtpPacketError(); private ZrtpPacketErrorAck zrtpErrorAck = new ZrtpPacketErrorAck(); private ZrtpPacketDHPart zrtpDH1 = new ZrtpPacketDHPart(); private ZrtpPacketDHPart zrtpDH2 = new ZrtpPacketDHPart(); private ZrtpPacketCommit zrtpCommit = new ZrtpPacketCommit(); private ZrtpPacketConfirm zrtpConfirm1 = new ZrtpPacketConfirm(); private ZrtpPacketConfirm zrtpConfirm2 = new ZrtpPacketConfirm(); private ZrtpPacketPingAck zrtpPingAck = new ZrtpPacketPingAck(); private ZrtpPacketSASRelay zrtpSasRelay = new ZrtpPacketSASRelay(); private ZrtpPacketRelayAck zrtpRelayAck = new ZrtpPacketRelayAck(); HelloPacketVersion helloPackets[] = new HelloPacketVersion[MAX_ZRTP_VERSIONS]; int highestZrtpVersion; // Pointer to Hello packet sent to partner, initialized in ZRtp, modified by ZrtpStateClass ZrtpPacketHello currentHelloPacket; /** * Random IV data to encrypt the confirm data, 128 bit for AES */ private byte[] randomIV = new byte[16]; private byte[] tempMsgBuffer = new byte[1024]; private int lengthOfMsgData; /** * Variables to store signature data. Includes the signature type block */ private byte[] signatureData = null; // will be allocated when needed private int signatureLength = 0; // overall length in bytes private int peerSSRC = 0; // the partner's ssrc /** * Enable or disable paranoid mode. * * The Paranoid mode controls the behaviour and handling of the SAS verify flag. If * Panaoid mode is set to flase then ZRtp applies the normal handling. If Paranoid * mode is set to true then the handling is: * *
    *
  • Force the SAS verify flag to be false at srtpSecretsOn() callback. This gives * the user interface (UI) the indication to handle the SAS as not verified. * See implementation note below.
  • *
  • Don't set the SAS verify flag in the Confirm packets, thus the other * also must report the SAS as not verified.
  • *
  • ignore the SASVerified() function, thus do not set the SAS to verified * in the ZRTP cache.
  • *
  • Disable the Trusted PBX MitM feature. Just send the SASRelay packet * but do not process the relayed data. This protects the user from a malicious * "trusted PBX".
  • *
* ZRtp performs alls other steps during the ZRTP negotiations as usual, in particular it * computes, compares, uses, and stores the retained secrets. This avoids unnecessary warning * messages. The user may enable or disable the Paranoid mode on a call-by-call basis without * breaking the key continuity data. * * Implementation note:
* An application shall always display the SAS code if the SAS verify flag is false. * The application shall also use mechanisms to remind the user to compare the SAS code, for * example useing larger fonts, different colours and other display features. */ private boolean paranoidMode = false; private ZrtpConfigure configureAlgos; public ZRtp(byte[] myZid, ZrtpCallback cb, String id, ZrtpConfigure config) { this(myZid, cb, id, config, false, false); } public ZRtp(byte[] myZid, ZrtpCallback cb, String id, ZrtpConfigure config, boolean mitmMode) { this(myZid, cb, id, config, mitmMode, false); } /** * Constructor initializes all relevant data but does not start the engine. */ public ZRtp(byte[] myZid, ZrtpCallback cb, String id, ZrtpConfigure config, boolean mitmMode, boolean sasSignSupport) { secRand = ZrtpSecureRandom.getInstance(); configureAlgos = config; enableMitmEnrollment = config.isTrustedMitM(); paranoidMode = config.isParanoidMode(); System.arraycopy(myZid, 0, zid, 0, ZidRecord.IDENTIFIER_LENGTH); callback = cb; secRand.nextBytes(randomIV); // IV used in ZRTP packet encryption /* * Generate H0 as a random number (256 bits, 32 bytes) and then the hash chain, refer to chapter 9 */ secRand.nextBytes(H0); // hash H0 and generate H1 hashFunctionImpl.update(H0, 0, ZrtpPacketBase.HASH_IMAGE_SIZE); hashFunctionImpl.doFinal(H1, 0); hashFunctionImpl.update(H1, 0, ZrtpPacketBase.HASH_IMAGE_SIZE); // H2 hashFunctionImpl.doFinal(H2, 0); hashFunctionImpl.update(H2, 0, ZrtpPacketBase.HASH_IMAGE_SIZE); // H3 hashFunctionImpl.doFinal(H3, 0); zrtpHello_11.configureHello(config); zrtpHello_11.setH3(H3); // set H3 in Hello, included in helloHash zrtpHello_11.setZid(zid); zrtpHello_11.setVersion(ZrtpConstants.zrtpVersion_11); zrtpHello_12.configureHello(config); zrtpHello_12.setH3(H3); // set H3 in Hello, included in helloHash zrtpHello_12.setZid(zid); zrtpHello_12.setVersion(ZrtpConstants.zrtpVersion_12); if (mitmMode) { // this session acts for a trusted MitM (PBX) zrtpHello_11.setMitmMode(); zrtpHello_12.setMitmMode(); } if (sasSignSupport) { zrtpHello_11.setSasSign(); zrtpHello_12.setSasSign(); } // Keep array in ascending order (greater index -> greater version) helloPackets[0] = new HelloPacketVersion(); helloPackets[0].helloHash = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; helloPackets[0].packet = zrtpHello_11; helloPackets[0].version = zrtpHello_11.getVersionInt(); setClientId(id, helloPackets[0]); // set id, compute HMAC and final helloHash helloPackets[1] = new HelloPacketVersion(); helloPackets[1].helloHash = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; 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 supported available version stateEngine = new ZrtpStateClass(this); } /* * First the public methods. */ /** * Kick off the ZRTP protocol engine. * * This method calls the ZrtpStateClass#evInitial() state of the state * engine. After this call we are able to process ZRTP packets from our peer * and to process them. */ public void startZrtpEngine() { if (stateEngine != null && stateEngine.isInState(ZrtpStateClass.ZrtpStates.Initial)) { ZrtpStateClass.Event ev = stateEngine.new Event(ZrtpStateClass.EventDataType.ZrtpInitial, null); stateEngine.processEvent(ev); } } /** * Stop ZRTP security. * */ public void stopZrtp() { if (stateEngine != null) { ZrtpStateClass.Event ev = stateEngine.new Event(ZrtpStateClass.EventDataType.ZrtpClose, null); stateEngine.processEvent(ev); } } /** * Process RTP extension header. * * This method expects to get a pointer to the extension header of a RTP * packet. The method checks if this is really a ZRTP packet. If this check * fails the method returns 0 (false) in case this is not a ZRTP packet. We * return a 1 if we processed the ZRTP extension header and the caller may * process RTP data after the extension header as usual. The method return * -1 the call shall dismiss the packet and shall not forward it to further * RTP processing. * * @param extHeader * A pointer to the first byte of the extension header. Refer to * RFC3550. */ public void processZrtpMessage(byte[] extHeader, int ssrc) { peerSSRC = ssrc; if (stateEngine != null) { ZrtpStateClass.Event ev = stateEngine.new Event(ZrtpStateClass.EventDataType.ZrtpPacket, extHeader); stateEngine.processEvent(ev); } } /** * Process a timeout event. * * We got a timeout from the timeout provider. Forward it to the protocol * state engine. * */ public void processTimeout() { if (stateEngine != null) { ZrtpStateClass.Event ev = stateEngine.new Event(ZrtpStateClass.EventDataType.Timer, null); stateEngine.processEvent(ev); } } /** * Check for and handle GoClear ZRTP packet header. * * This method checks if this is a GoClear packet. If not, just return * false. Otherwise handle it according to the specification. * * @param extHeader * A pointer to the first byte of the extension header. Refer to * RFC3550. * @return False if not a GoClear, true otherwise. * * // bool handleGoClear(uint *extHeader); */ /** * Set the auxiliary secret. * * Use this method to set the auxiliary secret data. Refer to ZRTP * specification, chapter 4.3 ff * * @param data * Points to the secret data. */ public void setAuxSecret(byte[] data) { } /** * Check the state of the enrollment mode. * * If true then we will set the enrollment flag (E) in the confirm * packets and performs the enrollment actions. A MitM (PBX) enrollment service * started this ZRTP session. Can be set to true only if mitmMode is also true. * * @return status of the enrollmentMode flag. */ public boolean isEnrollmentMode() { return enrollmentMode; } /** * Check the state of the enrollment mode. * * If true then we will set the enrollment flag (E) in the confirm * packets and perform the enrollment actions. A MitM (PBX) enrollment * service must sets this mode to true. * * Can be set to true only if mitmMode is also true. * * @param enrollmentMode defines the new state of the enrollmentMode flag */ public void setEnrollmentMode(boolean enrollmentMode) { this.enrollmentMode = enrollmentMode; } /** * Check if a peer's cache entry has a vaild MitM key. * * If true then the other peer ha a valid MtiM key, i.e. the peer has performed * the enrollment procedure. A PBX ZRTP Back-2-Back application can use this function * to check which of the peers is enrolled. * * @return True if the other peer has a valid Mitm key (is enrolled). */ public boolean isPeerEnrolled() { return peerIsEnrolled; } /** * Send the SAS relay packet. * * The method creates and sends a SAS relay packet according to the ZRTP * specifications. Usually only a MitM capable user agent (PBX) uses this * function. * * @param sh the full SAS hash value * @param render the SAS rendering algorithm */ public boolean sendSASRelayPacket(byte[] sh, ZrtpConstants.SupportedSASTypes render) { byte[] hkey, ekey; // If we are responder then the PBX used it's Initiator keys if (myRole == ZrtpCallback.Role.Responder) { hkey = hmacKeyR; ekey = zrtpKeyR; } else { hkey = hmacKeyI; ekey = zrtpKeyI; } secRand.nextBytes(randomIV); zrtpSasRelay.setIv(randomIV); zrtpSasRelay.setTrustedSas(sh); zrtpSasRelay.setSasType(render.name); // Encrypt and HMAC with selectedkey byte[] dataToSecure = zrtpSasRelay.getDataToSecure(); try { cipher.cipher.init(true, new ParametersWithIV(new KeyParameter(ekey, 0, cipher.keyLength), randomIV)); int done = cipher.cipher.processBytes(dataToSecure, 0, dataToSecure.length, dataToSecure, 0); cipher.cipher.doFinal(dataToSecure, done); } catch (Exception e) { sendInfo(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereSecurityException)); return false; } byte[] confMac = computeHmac(hkey, hashLength, dataToSecure, dataToSecure.length); zrtpSasRelay.setDataToSecure(dataToSecure); zrtpSasRelay.setHmac(confMac); stateEngine.sendSASRelay(zrtpSasRelay); return true; } /** * Get the commited SAS rendering algorithm for this ZRTP session. * * @return the commited SAS rendering algorithm */ public ZrtpConstants.SupportedSASTypes getSasType() { return sasType; } /** * Get the computed SAS hash for this ZRTP session. * * @return a refernce to the byte array that contains the full * SAS hash. */ public byte[] getSasHash() { return sasHash; } /** * Check current state of the ZRTP state engine * * @param state * The state to check. * @return Returns true id ZRTP engine is in the given state, false * otherwise. */ public boolean inState(ZrtpStateClass.ZrtpStates state) { return stateEngine != null && stateEngine.isInState(state); } /** * Set SAS as verified. * * Call this method if the user confirmed (verfied) the SAS. ZRTP remembers * this together with the retained secrets data. */ public void SASVerified() { if (paranoidMode) return; zidRec.setSasVerified(); ZidFile.getInstance().saveRecord(zidRec); } /** * Reset the SAS verfied flag for the current active user's retained secrets. */ public void resetSASVerified() { zidRec.resetSasVerified(); ZidFile.getInstance().saveRecord(zidRec); } public void setRs2Valid() { if (zidRec != null) { zidRec.setRs2Valid(); ZidFile.getInstance().saveRecord(zidRec); } } /** * Get the ZRTP Hello Hash data. * * Use this method to get the ZRTP Hello Hash data. The method returns the * data as a string. * * @param index * Hello hash of the Hello packet identified by index. Index must be 0 <= index < SUPPORTED_ZRTP_VERSIONS. * * @return a string containing the Hello hash value as hex-digits. The * hello hash is available immediately after class instantiation. */ public String getHelloHash(int index) { String pv = new String(helloPackets[index].packet.getVersion()); String hs = new String(ZrtpUtils.bytesToHexString(helloPackets[index].helloHash, hashLengthImpl)); return pv + " " + hs; } /** * Get the ZRTP Hello Hash data - separate strings. * * Use this method to get the ZRTP Hello Hash data. The method returns the * data as separate strings. * * @param index * Hello hash of the Hello packet identified by index. Index must be 0 <= index < SUPPORTED_ZRTP_VERSIONS. * * @return String array containing the version string at offset 0, the Hello * hash value as hex-digits at offset 1. Hello hash is available * immediately after class instantiation. */ public String[] getHelloHashSep(int index) { String ret[] = new String[2]; ret[0] = new String(helloPackets[index].packet.getVersion()); ret[1] = new String(ZrtpUtils.bytesToHexString(helloPackets[index].helloHash, hashLengthImpl)); return ret; } /** * Get the peer's Hello Hash data. * * Use this method to get the peer's Hello Hash data. The method returns the * data as a string. * * @return a String containing the Hello hash value as hex-digits. * Peer Hello hash is available after we received a Hello packet * from our peer. If peer's hello hash is not available return null. */ public String getPeerHelloHash() { if (peerHelloVersion == null) return null; String pv = new String(peerHelloVersion); String hs = new String(ZrtpUtils.bytesToHexString(peerHelloHash, hashLengthImpl)); return pv + " " + hs; } /** * Get the peer's Hello Hash data - separate strings. * * Use this method to get the peer's Hello Hash data. The method returns the * data as separate strings. * * @return String array containing the version string at offset 0, the Hello * hash value as hex-digits at offset 1. Peer Hello hash is available * after we received a Hello packet from our peer. If peer's hello * hash is not available return null. */ public String[] getPeerHelloHashSep() { String ret[] = new String[2]; if (peerHelloVersion == null) return null; ret[0] = new String(peerHelloVersion); ret[1] = new String(ZrtpUtils.bytesToHexString(peerHelloHash, hashLengthImpl)); return ret; } /** * Get Multi-stream parameters. * * Use this method to get the Multi-stream that were computed during the * ZRTP handshake. An application may use these parameters to enable * multi-stream processing for an associated SRTP session. * * Refer to chapter 5.4.2 in the ZRTP specification for further details and * restriction how and when to use multi-stream mode. * * @return a string that contains the multi-stream parameters. The * application must not modify the contents of this string, it is * opaque data. The application may hand over this string to a new * ZrtpQueue instance to enable multi-stream processing for this * ZrtpQueue. If ZRTP was not started or ZRTP is not yet in secure * state the method returns an empty string. */ public byte[] getMultiStrParams() { byte[] tmp = null; if (inState(ZrtpStateClass.ZrtpStates.SecureState) && !multiStream) { // digest length + cipher + authLength + hash tmp = new byte[hashLength + 1 + 1 + 1]; // construct array that holds zrtpSession, cipher type, auth-length, // and hash tmp[0] = (byte) hash.ordinal(); tmp[1] = (byte) authLength.ordinal(); tmp[2] = (byte) cipher.ordinal(); System.arraycopy(zrtpSession, 0, tmp, 3, hashLength); } return tmp; } /** * Set Multi-stream parameters. * * Use this method to set the parameters required to enable Multi-stream * processing of ZRTP. The multi-stream parameters must be set before the * application starts the ZRTP protocol engine. * * Refer to chapter 5.4.2 in the ZRTP specification for further details of * multi-stream mode. * * @param parameters * A byte array that contains the multi-stream parameters. See * also {@code getMultiStrParams()} */ public void setMultiStrParams(byte[] parameters) { for (ZrtpConstants.SupportedHashes a : ZrtpConstants.SupportedHashes.values()) { if (a.ordinal() == (parameters[0] & 0xff)) { hash = a; break; } } setNegotiatedHash(hash); zrtpSession = new byte[hashLength]; for (ZrtpConstants.SupportedAuthLengths a : ZrtpConstants.SupportedAuthLengths.values()) { if (a.ordinal() == (parameters[1] & 0xff)) { authLength = a; break; } } for (ZrtpConstants.SupportedSymCiphers c : ZrtpConstants.SupportedSymCiphers.values()) { if (c.ordinal() == (parameters[2] & 0xff)) { cipher = c; break; } } System.arraycopy(parameters, 3, zrtpSession, 0, hashLength); multiStream = true; stateEngine.setMultiStream(true); } /** * Check if this ZRTP use Multi-stream. * * Use this method to check if this ZRTP instance uses multi-stream. Even if * the application provided multi-stram parameters it may happen that full * DH mode was used. Refer to chapters 5.2 and 5.4.2 in the ZRTP # when this * may happen. * * @return True if multi-stream is used, false otherwise. */ public boolean isMultiStream() { return multiStream; } /** * Check if the other ZRTP client supports Multi-stream. * * Use this method to check if the other ZRTP client supports Multi-stream * mode. * * @return True if multi-stream is available, false otherwise. */ public boolean isMultiStreamAvailable() { return multiStreamAvailable; } /** * Accept a PBX enrollment request. * * If a PBX service asks to enroll the MiTM key and the user accepts this * request, for example by pressing an OK button, the client application * shall call this method and set the parameter accepted to * true. If the user does not accept the request set the parameter to false. * * @param accepted * True if the enrollment request is accepted, false otherwise. */ public void acceptEnrollment(boolean accepted) { if (!accepted) { callback.zrtpInformEnrollment(ZrtpCodes.InfoEnrollment.EnrollmentCanceled); return; } if (pbxSecretTmp != null) { zidRec.setMiTMData(pbxSecretTmp); callback.zrtpInformEnrollment(ZrtpCodes.InfoEnrollment.EnrollmentOk); } else { callback.zrtpInformEnrollment(ZrtpCodes.InfoEnrollment.EnrollmentFailed); return; } ZidFile.getInstance().saveRecord(zidRec); } /** * Set signature data * * This functions stores signature data and transmitts it during ZRTP * processing to the other party as part of the Confirm packets. Refer to * chapters 7.2ff. * * @param data * The signature data including the signature type block. The * method copies this data into the Confirm packet at signature * type block. The length of the signature data must be multiple * of 4 bytes. * @return True if the method stored the data, false otherwise. */ public boolean setSignatureData(byte[] data) { if ((data.length % 4) != 0) return false; ZrtpPacketConfirm conf = (myRole == ZrtpCallback.Role.Responder) ? zrtpConfirm1 : zrtpConfirm2; conf.setSignatureLength(data.length / 4); return conf.setSignatureData(data); } /** * Get signature data * * This functions returns signature data that was receivied during ZRTP * processing. Refer to chapters 7.2ff. * * @return Signature data in a byte array. */ public byte[] getSignatureData() { return signatureData; } /** * Get length of signature data * * This functions returns the length of signature data that was receivied * during ZRTP processing. Refer to chapters 6.7 and 8.2. * * @return Length in bytes of the received signature data. The method * returns zero if no signature data avilable. */ public int getSignatureLength() { return signatureLength * 4; } /** * Emulate a Conf2Ack packet. * * This method emulates a Conf2Ack packet. According to ZRTP specification * the first valid SRTP packet that the Initiator receives must switch on * secure mode. Refer to chapter 5.6 in the specificaton * */ public void conf2AckSecure() { if (stateEngine != null) { ZrtpStateClass.Event ev = stateEngine.new Event(ZrtpStateClass.EventDataType.ZrtpPacket, zrtpConf2Ack.getHeaderBase()); stateEngine.processEvent(ev); } } /** * Get other party's ZID (ZRTP Identifier) data * * This functions returns the other party's ZID that was receivied during * ZRTP processing. * * The ZID data can be retrieved after ZRTP receive the first Hello packet * from the other party. The application may call this method for example * during SAS processing in showSAS(...) user callback method. * * @return the ZID data as byte array. */ public byte[] getPeerZid() { byte[] ret = new byte[ZidRecord.IDENTIFIER_LENGTH]; System.arraycopy(peerZid, 0, ret, 0, ZidRecord.IDENTIFIER_LENGTH); return ret; } /** * Get remaining time before a "ZRTP not supported by other party" is reported. * * This function calls the protocol state engine to determine how many time is left * in ZRTP's discovery phase (Hello phase). * * @return Time left in milliseconds. */ public long getTimeoutValue() { if(stateEngine != null) { return stateEngine.getTimeoutValue(); } return -1; } /** * Get number of supported ZRTP protocol versions. * * @return the number of supported ZRTP protocol versions. */ public int getNumberSupportedVersions() { return SUPPORTED_ZRTP_VERSIONS; } /** * Get negotiated ZRTP protocol version. * * @return the integer representation of the negotiated ZRTP protocol version. */ public int getCurrentProtocolVersion() { return currentHelloPacket.getVersionInt(); } /* * The following methods are helper functions for ZrtpStateClass. * ZrtpStateClass calls them to prepare packets, send data, report problems, * etc. */ /** * Send a ZRTP packet. * * The state engines calls this method to send a packet via the RTP stack. * * @param packet * Points to the ZRTP packet. * @return false if sending failed, true if packet was send */ protected boolean sendPacketZRTP(ZrtpPacketBase packet) { // the packetBuffer reflects the real size of the data including the CRC // field. return (packet != null && callback.sendDataZRTP(packet.getHeaderBase())); } /** * Activate a Timer using the host callback. * * @param tm * The time in milliseconds. * @return zero if activation failed, one if timer was activated */ protected int activateTimer(int tm) { return callback.activateTimer(tm); } /** * Cancel the active Timer using the host callback. * * @return zero if activation failed, one if timer was activated */ protected int cancelTimer() { return callback.cancelTimer(); } /** * Prepare a Hello packet. * * Just take the preinitialized Hello packet and return it. No further * processing required. * * @return A pointer to the initialized Hello packet. */ protected ZrtpPacketHello prepareHello() { return currentHelloPacket; } /** * Prepare a HelloAck packet. * * Just take the preinitialized HelloAck packet and return it. No further * processing required. * * @return A pointer to the initialized HelloAck packet. */ protected ZrtpPacketHelloAck prepareHelloAck() { return zrtpHelloAck; } /** * Prepare a Commit packet. * * We have received a Hello packet from our peer. Check the offers it makes * to us and select the most appropriate. Using the selected values prepare * a Commit packet and return it to protocol state engine. * * @param hello * Points to the received Hello packet * @return A pointer to the prepared Commit packet */ protected ZrtpPacketCommit prepareCommit(ZrtpPacketHello hello, ZrtpCodes.ZrtpErrorCodes[] errMsg) { if (!hello.isLengthOk()) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } // Save our peer's ZRTP id peerZid = hello.getZid(); // peers have the same ZID? if (ZrtpUtils.byteArrayCompare(peerZid, zid, ZidRecord.IDENTIFIER_LENGTH) == 0) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.EqualZIDHello; return null; } System.arraycopy(hello.getH3(), 0, peerH3, 0, ZrtpPacketBase.HASH_IMAGE_SIZE); // calculate hash over the received Hello packet, it's the peer's hello hash. // // getHeaderBase() returns the full packetBuffer array. The length of // this array includes the CRC which is not part of the helloHash. // Thus compute digest only for the real message length. // Use implicit hash algo int helloLen = hello.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE; hashFunctionImpl.update(hello.getHeaderBase(), 0, helloLen); hashFunctionImpl.doFinal(peerHelloHash, 0); peerHelloVersion = hello.getVersion(); sendInfo(ZrtpCodes.MessageSeverity.Info, EnumSet.of(ZrtpCodes.InfoCodes.InfoHelloReceived)); /* * The Following section extracts the algorithm from the Hello packet. Always the best possible (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. */ sasType = hello.findBestSASType(configureAlgos); if (!multiStream) { pubKey = hello.findBestPubkey(configureAlgos); hash = hello.getSelectedHash(); if (hash == null) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.UnsuppHashType; return null; } cipher = hello.getSelectedCipher(); if (cipher == null) cipher = hello.findBestCipher(configureAlgos, pubKey); authLength = hello.findBestAuthLen(configureAlgos); multiStreamAvailable = hello.checkMultiStream(); } else { if (hello.checkMultiStream()) { return prepareCommitMultiStream(hello); } else { // we are in multi-stream but peer does not offer multi-stream // return error message Unspported PK, we require Mult in the // Hello errMsg[0] = ZrtpCodes.ZrtpErrorCodes.UnsuppPKExchange; return null; } } setNegotiatedHash(hash); if (!fillPubKey()) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } sendInfo(ZrtpCodes.MessageSeverity.Info, EnumSet.of(ZrtpCodes.InfoCodes.InfoCommitDHGenerated)); /* * Prepare our DHPart2 packet here. Required to compute HVI. If we stay in Initiator role then we reuse this * packet later in prepareDHPart2(). To create this DH packet we have to compute the retained secret ids first. * Thus get our peer's retained secret data first. */ zidRec = ZidFile.getInstance().getRecord(peerZid); // Compute the Initator's and Responder's retained secret ids. computeSharedSecretSet(); // Check if a PBX application set the MitM flag. if (hello.isMitmMode()) { mitmSeen = true; } // Flag to record that fact that we have a MitM key of the other peer. peerIsEnrolled = zidRec.isMITMKeyAvailable(); // Check for sign SAS flag and remember it. 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); zrtpDH2.setMessageType(ZrtpConstants.DHPart2Msg); zrtpDH2.setRs1Id(rs1IDi); zrtpDH2.setRs2Id(rs2IDi); zrtpDH2.setAuxSecretId(auxSecretIDi); zrtpDH2.setPbxSecretId(pbxSecretIDi); zrtpDH2.setPv(pubKeyBytes); zrtpDH2.setH1(H1); // Compute HMAC over Hello, excluding the HMAC field (2*ZTP_WORD_SIZE) // and store in DH2 byte[] hmac = computeMsgHmac(H0, zrtpDH2); zrtpDH2.setHMAC(hmac); // Compute the HVI, refer to chapter 5.4.1.1 of the specification computeHvi(zrtpDH2, hello); zrtpCommit.setZid(zid); zrtpCommit.setHashType(hash.name); zrtpCommit.setCipherType(cipher.name); zrtpCommit.setAuthLen(authLength.name); zrtpCommit.setPubKeyType(pubKey.name); zrtpCommit.setSasType(sasType.name); zrtpCommit.setHvi(hvi); zrtpCommit.setH2(H2); // Compute HMAC over Commit, excluding the HMAC field (2*ZTP_WORD_SIZE) // and store in Hello hmac = computeMsgHmac(H1, zrtpCommit); zrtpCommit.setHMAC(hmac); // hash first messages to produce overall message hash // First the Responder's Hello message, second the Commit // (always Initator's) // Use negotiated hash algo. hashCtxFunction.update(hello.getHeaderBase(), 0, hello.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE); final int len = zrtpCommit.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE; hashCtxFunction.update(zrtpCommit.getHeaderBase(), 0, len); // store Hello data temporarily until we can check HMAC after receiving // Commit as // Responder or DHPart1 as Initiator storeMsgTemp(hello); return zrtpCommit; } private boolean fillPubKey() { // Generate the standard DH data and keys according to the selected // DH algorithm if (pubKey == ZrtpConstants.SupportedPubKeys.DH2K || pubKey == ZrtpConstants.SupportedPubKeys.DH3K) { dhKeyPair = pubKey.keyPairGen.generateKeyPair(); pubKeyBytes = ((DHPublicKeyParameters) dhKeyPair.getPublic()).getY().toByteArray(); if (pubKeyBytes.length != pubKey.pubKeySize) { if ((pubKeyBytes = adjustBigBytes(pubKeyBytes, pubKey.pubKeySize)) == null) return false; } } // Here produce the ECDH stuff else if (pubKey == ZrtpConstants.SupportedPubKeys.EC25 || pubKey == ZrtpConstants.SupportedPubKeys.EC38 || pubKey == ZrtpConstants.SupportedPubKeys.E255) { ecKeyPair = pubKey.keyPairGen.generateKeyPair(); byte[] encoded = ((ECPublicKeyParameters) ecKeyPair.getPublic()).getQ() .getEncoded(pubKey == ZrtpConstants.SupportedPubKeys.E255); pubKeyBytes = new byte[pubKey.pubKeySize]; System.arraycopy(encoded, 1, pubKeyBytes, 0, pubKey.pubKeySize); } else { return false; } return true; } protected ZrtpPacketCommit prepareCommitMultiStream(ZrtpPacketHello hello) { // This is the Multi-Stream NONCE size hvi = new byte[ZrtpPacketBase.ZRTP_WORD_SIZE * 4]; secRand.nextBytes(hvi); zrtpCommit.setZid(zid); zrtpCommit.setHashType(hash.name); zrtpCommit.setCipherType(cipher.name); zrtpCommit.setAuthLen(authLength.name); zrtpCommit.setPubKeyType(ZrtpConstants.SupportedPubKeys.MULT.name); zrtpCommit.setSasType(sasType.name); zrtpCommit.setNonce(hvi); zrtpCommit.setH2(H2); int len = zrtpCommit.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE; // Compute HMAC over Commit, excluding the HMAC field (2*ZTP_WORD_SIZE) // and store in Hello byte[] hmac = computeMsgHmac(H1, zrtpCommit); zrtpCommit.setHMACMulti(hmac); // hash first messages to produce overall message hash // First the Responder's Hello message, second the Commit // (always Initator's). Use negotiated Hash algo. hashCtxFunction.update(hello.getHeaderBase(), 0, hello.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE); hashCtxFunction.update(zrtpCommit.getHeaderBase(), 0, len); // store Hello data temporarily until we can check HMAC after receiving // Commit as Responder or DHPart1 as Initiator storeMsgTemp(hello); // calculate hash over the received Hello packet - is peer's hello hash. // // getHeaderBase() returns the full packetBuffer array. The length of // this array includes the CRC which is not part of the helloHash. // Thus compute digest only for the real message length. // Use implicit hash algo int helloLen = hello.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE; hashFunctionImpl.update(hello.getHeaderBase(), 0, helloLen); hashFunctionImpl.doFinal(peerHelloHash, 0); peerHelloVersion = hello.getVersion(); return zrtpCommit; } /** * Prepare the DHPart1 packet. * * This method prepares a DHPart1 packet. The input to the method is always * a Commit packet received from the peer. Also we are in the role of the * Responder. * * When we receive a Commit packet we get the selected ciphers, hashes, etc * and cross-check if this is ok. Then we need to initialize a set of DH * keys according to the selected cipher. Using this data we prepare our * DHPart1 packet. */ protected ZrtpPacketDHPart prepareDHPart1(ZrtpPacketCommit commit, ZrtpCodes.ZrtpErrorCodes[] errMsg) { sendInfo(ZrtpCodes.MessageSeverity.Info, EnumSet.of(ZrtpCodes.InfoCodes.InfoRespCommitReceived)); if (!commit.isLengthOk()) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } // The following code check the hash chain according chapter 10 to // detect false ZRTP packets. // Use implicit hash algo. System.arraycopy(commit.getH2(), 0, peerH2, 0, ZrtpPacketBase.HASH_IMAGE_SIZE); byte[] tmpH3 = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; hashFunctionImpl.update(peerH2, 0, ZrtpPacketBase.HASH_IMAGE_SIZE); hashFunctionImpl.doFinal(tmpH3, 0); if (ZrtpUtils.byteArrayCompare(tmpH3, peerH3, ZrtpPacketBase.HASH_IMAGE_SIZE) != 0) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.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(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereHelloHMACFailed)); errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } // check if we support the commited Cipher type cipher = commit.getCipher(); if (cipher == null) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.UnsuppCiphertype; return null; } // check if we support the commited Authentication length authLength = commit.getAuthlen(); if (authLength == null) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.UnsuppSRTPAuthTag; return null; } ZrtpConstants.SupportedHashes newHash = commit.getHash(); if (newHash == null) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.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 (newHash != hash) { hash = newHash; setNegotiatedHash(hash); computeSharedSecretSet(); // Re-compute the Initator's and Responder's RS. } // check if we support the commited pub key type (check here for // different pubkey - maybe we need to create a new own public // key here if the peer's commit differs with respect to our // preparation done in prepareCommit(...) ZrtpConstants.SupportedPubKeys commitPubKey = commit.getPubKey(); if (commitPubKey == null) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.UnsuppPKExchange; return null; } /* * Saftey check if we can resuse the DH key pair. According to the public key algo check this is usually the * case. If we cannot reuse it refill the pubkey. */ if (commitPubKey != pubKey) { pubKey = commitPubKey; if (!fillPubKey()) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } } // if ECDH 384 is in use then hash must be SHA 384 or better if (pubKey == ZrtpConstants.SupportedPubKeys.EC38 && hash != ZrtpConstants.SupportedHashes.S384) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.UnsuppHashType; return null; } // check if we support the commited SAS type sasType = commit.getSas(); if (sasType == null) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.UnsuppSASScheme; return null; } sendInfo(ZrtpCodes.MessageSeverity.Info, EnumSet.of(ZrtpCodes.InfoCodes.InfoDH1DHGenerated)); // Setup a DHPart1 packet. zrtpDH1.setPubKeyType(pubKey); zrtpDH1.setMessageType(ZrtpConstants.DHPart1Msg); zrtpDH1.setRs1Id(rs1IDr); zrtpDH1.setRs2Id(rs2IDr); zrtpDH1.setAuxSecretId(auxSecretIDr); zrtpDH1.setPbxSecretId(pbxSecretIDr); zrtpDH1.setPv(pubKeyBytes); zrtpDH1.setH1(H1); // Compute HMAC over DHPart1, excluding the HMAC field (2*ZTP_WORD_SIZE) // and store in DHPart1 byte[] hmac = computeMsgHmac(H0, zrtpDH1); zrtpDH1.setHMAC(hmac); // We are definitly responder. Save the peer's hvi for later compare. myRole = ZrtpCallback.Role.Responder; peerHvi = commit.getHvi(); // We are responder. Reset the message SHA context. Use negotiated // hash algo. hashCtxFunction.reset(); // 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) hashCtxFunction.update(currentHelloPacket.getHeaderBase(), 0, currentHelloPacket.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE); hashCtxFunction.update(commit.getHeaderBase(), 0, commit.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE); hashCtxFunction.update(zrtpDH1.getHeaderBase(), 0, zrtpDH1.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE); // store Commit data temporarily until we can check HMAC after receiving // DHPart2 storeMsgTemp(commit); return zrtpDH1; } /** * Prepare the DHPart2 packet. * * This method returns the prepared DHPart2 packet. The input to the method * is always a DHPart1 packet received from the peer. Our peer sends the DH1Part as * response to our Commit packet. Thus we are in the role of the Initiator. * The method uses the DHPart1 data to create the Initiator's secrets. * */ protected ZrtpPacketDHPart prepareDHPart2(ZrtpPacketDHPart dhPart1, ZrtpCodes.ZrtpErrorCodes[] errMsg) { sendInfo(ZrtpCodes.MessageSeverity.Info, EnumSet.of(ZrtpCodes.InfoCodes.InfoInitDH1Received)); if (!dhPart1.isLengthOk()) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.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 hash is required to // re-compute H3. Then compare with peer's H3 from peer's Hello packet. // Use implicit hash algo hashFunctionImpl.update(dhPart1.getH1(), 0, ZrtpPacketBase.HASH_IMAGE_SIZE); // Compute peer's H2 hashFunctionImpl.doFinal(peerH2, 0); byte[] tmpHash = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; // Compute peer's H3 (tmpHash) hashFunctionImpl.update(peerH2, 0, ZrtpPacketBase.HASH_IMAGE_SIZE); hashFunctionImpl.doFinal(tmpHash, 0); if (ZrtpUtils.byteArrayCompare(tmpHash, peerH3, ZrtpPacketBase.HASH_IMAGE_SIZE) != 0) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.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(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereHelloHMACFailed)); errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } // get and check Responder's public value, see chap. 5.4.3 in the spec byte[] pvrBytes = dhPart1.getPv(); int dhSize; if (pubKey == ZrtpConstants.SupportedPubKeys.DH2K || pubKey == ZrtpConstants.SupportedPubKeys.DH3K) { // generate the resonpder's public key from the pvr data and the key // specs, then compute the shared secret. BigInteger pvrBigInt = new BigInteger(1, pvrBytes); if (!checkPubKey(pvrBigInt, pubKey)) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.DHErrorWrongPV; return null; } pubKey.dhContext.init(dhKeyPair.getPrivate()); DHPublicKeyParameters pvr = new DHPublicKeyParameters(pvrBigInt, pubKey.specDh); dhSize = pubKey.pubKeySize; BigInteger bi = pubKey.dhContext.calculateAgreement(pvr); DHss = bi.toByteArray(); } // Here produce the ECDH stuff else if (pubKey == ZrtpConstants.SupportedPubKeys.EC25 || pubKey == ZrtpConstants.SupportedPubKeys.EC38 || pubKey == ZrtpConstants.SupportedPubKeys.E255) { byte[] encoded = new byte[pvrBytes.length + 1]; encoded[0] = (byte)(pubKey == ZrtpConstants.SupportedPubKeys.E255 ? 0x02 // compressed, i.e. X only : 0x04); // uncompressed System.arraycopy(pvrBytes, 0, encoded, 1, pvrBytes.length); ECPoint point = pubKey.curve.decodePoint(encoded); dhSize = pubKey.pubKeySize / 2; pubKey.dhContext.init(ecKeyPair.getPrivate()); BigInteger bi = pubKey.dhContext.calculateAgreement(new ECPublicKeyParameters(point, null)); DHss = bi.toByteArray(); } else { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } if (DHss.length != dhSize) { if ((DHss = adjustBigBytes(DHss, dhSize)) == null) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } } myRole = ZrtpCallback.Role.Initiator; // We are Inititaor: 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 negotiated hash algo. hashCtxFunction.update(dhPart1.getHeaderBase(), 0, dhPart1.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE); hashCtxFunction.update(zrtpDH2.getHeaderBase(), 0, zrtpDH2.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE); // Compute the message Hash hashCtxFunction.doFinal(messageHash, 0); hashCtxFunction = null; // Now compute the S0, all dependend keys and the new RS1. The functions // also performs sign SAS callback if it's active. generateKeysInitiator(dhPart1); // store DHPart1 data temporarily until we can check HMAC after // receiving Confirm1 storeMsgTemp(dhPart1); return zrtpDH2; } /** * Prepare the Confirm1 packet. * * This method prepare the Confirm1 packet. The input to this method is the * DHPart2 packect received from our peer. The peer sends the DHPart2 packet * as response of our DHPart1. Here we are in the role of the Responder. * * The method uses the data of the DHPart2 packet to create the responder's * secrets. * */ protected ZrtpPacketConfirm prepareConfirm1(ZrtpPacketDHPart dhPart2, ZrtpCodes.ZrtpErrorCodes[] errMsg) { sendInfo(ZrtpCodes.MessageSeverity.Info, EnumSet.of(ZrtpCodes.InfoCodes.InfoRespDH2Received)); if (!dhPart2.isLengthOk()) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.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. byte[] tmpHash = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; hashFunctionImpl.update(dhPart2.getH1(), 0, ZrtpPacketBase.HASH_IMAGE_SIZE); hashFunctionImpl.doFinal(tmpHash, 0); if (ZrtpUtils.byteArrayCompare(tmpHash, peerH2, ZrtpPacketBase.HASH_IMAGE_SIZE) != 0) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.IgnorePacket; return null; } // 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 (ZrtpUtils.byteArrayCompare(hvi, peerHvi, ZrtpPacketBase.HVI_SIZE) != 0) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.DHErrorWrongHVI; 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(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereCommitHMACFailed)); errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } // Get and check the Initiator's public value, see chap. 5.4.2 of the // spec byte[] pviBytes = dhPart2.getPv(); int dhSize; if (pubKey == ZrtpConstants.SupportedPubKeys.DH2K || pubKey == ZrtpConstants.SupportedPubKeys.DH3K) { // generate the resonpder's public key from the pvi data and the key // specs, then compute the shared secret. BigInteger pviBigInt = new BigInteger(1, pviBytes); if (!checkPubKey(pviBigInt, pubKey)) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.DHErrorWrongPV; return null; } pubKey.dhContext.init(dhKeyPair.getPrivate()); DHPublicKeyParameters pvi = new DHPublicKeyParameters(pviBigInt, pubKey.specDh); dhSize = pubKey.pubKeySize; BigInteger bi = pubKey.dhContext.calculateAgreement(pvi); DHss = bi.toByteArray(); } // Here produce the ECDH stuff else if (pubKey == ZrtpConstants.SupportedPubKeys.EC25 || pubKey == ZrtpConstants.SupportedPubKeys.EC38 || pubKey == ZrtpConstants.SupportedPubKeys.E255) { byte[] encoded = new byte[pviBytes.length + 1]; encoded[0] = (byte)(pubKey == ZrtpConstants.SupportedPubKeys.E255 ? 0x02 // compressed, i.e. X only : 0x04); // uncompressed System.arraycopy(pviBytes, 0, encoded, 1, pviBytes.length); ECPoint pubPoint = pubKey.curve.decodePoint(encoded); dhSize = pubKey.pubKeySize / 2; pubKey.dhContext.init(ecKeyPair.getPrivate()); BigInteger bi = pubKey.dhContext.calculateAgreement(new ECPublicKeyParameters(pubPoint, null)); DHss = bi.toByteArray(); } else { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } if (DHss.length != dhSize) { if ((DHss = adjustBigBytes(DHss, dhSize)) == null) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } } // Hash the Initiator's DH2 into the message Hash (other messages // already prepared, see method prepareDHPart1(). hashCtxFunction.update(dhPart2.getHeaderBase(), 0, dhPart2.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE); hashCtxFunction.doFinal(messageHash, 0); hashCtxFunction = 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. */ generateKeysResponder(dhPart2); // Fill in Confirm1 packet. zrtpConfirm1.setMessageType(ZrtpConstants.Confirm1Msg); // Check if user verfied the SAS in a previous call and thus verfied // the retained secret. Forward this information to our peer. Don't set // the verified flag if paranoidMode is true. if (zidRec.isSasVerified() && !paranoidMode) { zrtpConfirm1.setSASFlag(); } zrtpConfirm1.setExpTime(0xFFFFFFFF); zrtpConfirm1.setIv(randomIV); zrtpConfirm1.setHashH0(H0); // if this run at PBX user agent enrollment service then set flag in confirm // packet and store the MitM key if (enrollmentMode) { computePBXSecret(); zrtpConfirm1.setPBXEnrollment(); zidRec.setMiTMData(pbxSecretTmp); } // Encrypt and HMAC with Responder's key - we are Respondere here // see ZRTP specification chapter byte[] dataToSecure = zrtpConfirm1.getDataToSecure(); try { cipher.cipher.init(true, new ParametersWithIV(new KeyParameter(zrtpKeyR, 0, cipher.keyLength), randomIV)); int done = cipher.cipher.processBytes(dataToSecure, 0, dataToSecure.length, dataToSecure, 0); cipher.cipher.doFinal(dataToSecure, done); } catch (Exception e) { sendInfo(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereSecurityException)); errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } byte[] confMac = computeHmac(hmacKeyR, hashLength, dataToSecure, dataToSecure.length); zrtpConfirm1.setDataToSecure(dataToSecure); zrtpConfirm1.setHmac(confMac); // store DHPart2 data temporarily until we can check HMAC after // receiving Confirm2 storeMsgTemp(dhPart2); return zrtpConfirm1; } private byte[] adjustBigBytes(byte[] in, int size) { // adjust byte arry if we have a leading zero byte[] tmp; if (in.length > size && in[0] == 0) { tmp = new byte[in.length - 1]; System.arraycopy(in, 1, tmp, 0, tmp.length); return tmp; } // Fill with zeros if too short. if (in.length < size) { int fill = size - in.length; tmp = new byte[size]; System.arraycopy(in, 0, tmp, fill, size - fill); return tmp; } return null; } /* * At this point we are Responder. */ protected ZrtpPacketConfirm prepareConfirm1MultiStream(ZrtpPacketCommit commit, ZrtpCodes.ZrtpErrorCodes[] errMsg) { sendInfo(ZrtpCodes.MessageSeverity.Info, EnumSet.of(ZrtpCodes.InfoCodes.InfoRespCommitReceived)); if (!commit.isLengthOk()) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } // In multi stream mode we don't get a DH packt. Thus we need to // recompute the hash chain starting with Commit's H2. // Use the implicit hash algo System.arraycopy(commit.getH2(), 0, peerH2, 0, ZrtpPacketBase.HASH_IMAGE_SIZE); byte[] tmpH3 = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; hashFunctionImpl.update(peerH2, 0, ZrtpPacketBase.HASH_IMAGE_SIZE); hashFunctionImpl.doFinal(tmpH3, 0); if (ZrtpUtils.byteArrayCompare(tmpH3, peerH3, ZrtpPacketBase.HASH_IMAGE_SIZE) != 0) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.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(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereCommitHMACFailed)); errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } // check if we support the commited pub key type if (commit.getPubKey() != ZrtpConstants.SupportedPubKeys.MULT) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.UnsuppPKExchange; return null; } cipher = commit.getCipher(); if (cipher == null) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.UnsuppCiphertype; return null; } // check if we support the commited Authentication length authLength = commit.getAuthlen(); if (authLength == null) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.UnsuppSRTPAuthTag; return null; } ZrtpConstants.SupportedHashes newHash = commit.getHash(); if (newHash == null) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.UnsuppHashType; return null; } if (newHash != hash) { hash = newHash; setNegotiatedHash(hash); } myRole = ZrtpCallback.Role.Responder; // We are responder. Reset message SHA context hashCtxFunction.reset(); // Hash messages to produce overall message hash: // First the Responder's (my) Hello message, second the Commit // (always Initator's) hashCtxFunction.update(currentHelloPacket.getHeaderBase(), 0, currentHelloPacket.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE); hashCtxFunction.update(commit.getHeaderBase(), 0, commit.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE); hashCtxFunction.doFinal(messageHash, 0); hashCtxFunction = null; generateKeysMultiStream(); // Fill in Confirm1 packet. zrtpConfirm1.setMessageType(ZrtpConstants.Confirm1Msg); zrtpConfirm1.setExpTime(0xFFFFFFFF); zrtpConfirm1.setIv(randomIV); zrtpConfirm1.setHashH0(H0); // Encrypt and HMAC with Responder's key - we are Respondere here // see ZRTP specification chapter xYxY byte[] dataToSecure = zrtpConfirm1.getDataToSecure(); try { cipher.cipher.init(true, new ParametersWithIV(new KeyParameter(zrtpKeyR, 0, cipher.keyLength), randomIV)); int done = cipher.cipher.processBytes(dataToSecure, 0, dataToSecure.length, dataToSecure, 0); cipher.cipher.doFinal(dataToSecure, done); } catch (Exception e) { sendInfo(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereSecurityException)); errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } byte[] confMac = computeHmac(hmacKeyR, hashLength, dataToSecure, dataToSecure.length); zrtpConfirm1.setDataToSecure(dataToSecure); zrtpConfirm1.setHmac(confMac); // Store Commit data temporarily until we can check HMAC after receiving // Confirm2 storeMsgTemp(commit); return zrtpConfirm1; } /** * Prepare the Confirm2 packet. * * This method prepare the Confirm2 packet. The input to this method is the * Confirm1 packet received from our peer. The peer sends the Confirm1 * packet as response of our DHPart2. Here we are in the role of the * Initiator */ protected ZrtpPacketConfirm prepareConfirm2(ZrtpPacketConfirm confirm1, ZrtpCodes.ZrtpErrorCodes[] errMsg) { sendInfo(ZrtpCodes.MessageSeverity.Info, EnumSet.of(ZrtpCodes.InfoCodes.InfoInitConf1Received)); if (!confirm1.isLengthOk()) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } // Use the Responder's keys here to decrypt because we are // Initiator and receive packets from Responder byte[] dataToSecure = confirm1.getDataToSecure(); byte[] confMac = computeHmac(hmacKeyR, hashLength, dataToSecure, dataToSecure.length); if (ZrtpUtils.byteArrayCompare(confMac, confirm1.getHmac(), 2 * ZrtpPacketBase.ZRTP_WORD_SIZE) != 0) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.ConfirmHMACWrong; return null; } try { // Decrypting here cipher.cipher.init(false, new ParametersWithIV(new KeyParameter(zrtpKeyR, 0, cipher.keyLength), confirm1.getIv())); int done = cipher.cipher.processBytes(dataToSecure, 0, dataToSecure.length, dataToSecure, 0); cipher.cipher.doFinal(dataToSecure, done); } catch (Exception e) { sendInfo(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereSecurityException)); errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } confirm1.setDataToSecure(dataToSecure); // 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(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereDH1HMACFailed)); errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } signatureLength = confirm1.getSignatureLength(); if (signSasSeen && signatureLength > 0) { signatureData = confirm1.getSignatureData(); callback.checkSASSignature(sasHash); } /* * The Confirm1 is ok, handle the Retained secret stuff and inform GUI about state. */ // Did our peer verified the SAS during last session? Get its SAS verified flag. boolean sasFlag = confirm1.isSASFlag(); // Our peer did not confirm the SAS in last session, thus reset our stored SAS // flag too. Reset the flag also if paranoidMode is true. if (!sasFlag || paranoidMode) { zidRec.resetSasVerified(); } // Now get the resulting SAS verified flag from current RS1 before setting a new RS1. // It's a combination of our SAS verfied flag and peer's verified flag. Only if both // were set (true) then sasFlag becomes true. sasFlag = zidRec.isSasVerified(); // now we are ready to save the new RS1 which inherits the verified // flag from old RS1 zidRec.setNewRs1(newRs1, -1); // now generate my Confirm2 message zrtpConfirm2.setMessageType(ZrtpConstants.Confirm2Msg); zrtpConfirm2.setHashH0(H0); if (sasFlag) { zrtpConfirm2.setSASFlag(); } zrtpConfirm2.setExpTime(0xFFFFFFFF); zrtpConfirm2.setIv(randomIV); // Compute PBX secret if we are in enrollemnt mode (PBX user agent) // or enrollment was enabled at normal user agent and flag in confirm packet if (enrollmentMode || (enableMitmEnrollment && confirm1.isPBXEnrollment())) { computePBXSecret(); // if this runs at PBX user agent enrollment service then set flag in confirm // packet and store the MitM key. The PBX user agent service always stores // its MitM key. if (enrollmentMode) { zrtpConfirm2.setPBXEnrollment(); zidRec.setMiTMData(pbxSecretTmp); } } ZidFile.getInstance().saveRecord(zidRec); // Encrypt and HMAC with Initiator's key - we are Initiator here dataToSecure = zrtpConfirm2.getDataToSecure(); try { cipher.cipher.init(true, new ParametersWithIV(new KeyParameter(zrtpKeyI, 0, cipher.keyLength), randomIV)); int done = cipher.cipher.processBytes(dataToSecure, 0, dataToSecure.length, dataToSecure, 0); cipher.cipher.doFinal(dataToSecure, done); } catch (Exception e) { sendInfo(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereSecurityException)); errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } confMac = computeHmac(hmacKeyI, hashLength, dataToSecure, dataToSecure.length); zrtpConfirm2.setDataToSecure(dataToSecure); zrtpConfirm2.setHmac(confMac); callback.srtpSecretsOn(cipher.readable + "/" + pubKey, SAS, sasFlag); // 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 && confirm1.isPBXEnrollment()) { callback.zrtpAskEnrollment(ZrtpCodes.InfoEnrollment.EnrollmentRequest); } return zrtpConfirm2; } /* * At this point we are Initiator. */ /** * @param confirm1 The ZRTP confirm1 packet * @param errMsg Arry to return an error code * @return a ZRTP confirm packet, here a confirm2 */ protected ZrtpPacketConfirm prepareConfirm2MultiStream(ZrtpPacketConfirm confirm1, ZrtpCodes.ZrtpErrorCodes[] errMsg) { // check Confirm1 packet using the keys // prepare Confirm2 packet // don't update SAS, RS sendInfo(ZrtpCodes.MessageSeverity.Info, EnumSet.of(ZrtpCodes.InfoCodes.InfoInitConf1Received)); if (!confirm1.isLengthOk()) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } hashCtxFunction.doFinal(messageHash, 0); hashCtxFunction = null; myRole = ZrtpCallback.Role.Initiator; generateKeysMultiStream(); // Use the Responder's keys here to decrypt because we are // Initiator and receive packets from Responder byte[] dataToSecure = confirm1.getDataToSecure(); byte[] confMac = computeHmac(hmacKeyR, hashLength, dataToSecure, dataToSecure.length); if (ZrtpUtils.byteArrayCompare(confMac, confirm1.getHmac(), 2 * ZrtpPacketBase.ZRTP_WORD_SIZE) != 0) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.ConfirmHMACWrong; return null; } try { // Decrypting here cipher.cipher.init(false, new ParametersWithIV(new KeyParameter(zrtpKeyR, 0, cipher.keyLength), confirm1.getIv())); int done = cipher.cipher.processBytes(dataToSecure, 0, dataToSecure.length, dataToSecure, 0); cipher.cipher.doFinal(dataToSecure, done); } catch (Exception e) { sendInfo(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereSecurityException)); errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } confirm1.setDataToSecure(dataToSecure); // 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 and H2. // Use implicit hash algo byte[] tmpHash = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; hashFunctionImpl.update(confirm1.getHashH0(), 0, ZrtpPacketBase.HASH_IMAGE_SIZE); // Compute peer's H1 in // tmpHash hashFunctionImpl.doFinal(tmpHash, 0); // Compute peer's H2 hashFunctionImpl.update(tmpHash, 0, ZrtpPacketBase.HASH_IMAGE_SIZE); hashFunctionImpl.doFinal(peerH2, 0); // 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(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereHelloHMACFailed)); errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } // now generate my Confirm2 message zrtpConfirm2.setMessageType(ZrtpConstants.Confirm2Msg); zrtpConfirm2.setHashH0(H0); zrtpConfirm2.setExpTime(0xFFFFFFFF); zrtpConfirm2.setIv(randomIV); // Encrypt and HMAC with Initiator's key - we are Initiator here dataToSecure = zrtpConfirm2.getDataToSecure(); try { cipher.cipher.init(true, new ParametersWithIV(new KeyParameter(zrtpKeyI, 0, cipher.keyLength), randomIV)); int done = cipher.cipher.processBytes(dataToSecure, 0, dataToSecure.length, dataToSecure, 0); cipher.cipher.doFinal(dataToSecure, done); } catch (Exception e) { sendInfo(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereSecurityException)); errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } confMac = computeHmac(hmacKeyI, hashLength, dataToSecure, dataToSecure.length); zrtpConfirm2.setDataToSecure(dataToSecure); zrtpConfirm2.setHmac(confMac); // Inform GUI about security state, don't show SAS and its state callback.srtpSecretsOn(cipher.readable, null, true); return zrtpConfirm2; } /** * Prepare the Conf2Ack packet. * * This method prepare the Conf2Ack packet. The input to this method is the * Confirm2 packet received from our peer. The peer sends the Confirm2 * packet as response of our Confirm1. Here we are in the role of the * Initiator */ protected ZrtpPacketConf2Ack prepareConf2Ack(ZrtpPacketConfirm confirm2, ZrtpCodes.ZrtpErrorCodes[] errMsg) { sendInfo(ZrtpCodes.MessageSeverity.Info, EnumSet.of(ZrtpCodes.InfoCodes.InfoRespConf2Received)); if (!confirm2.isLengthOk()) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } // Use the Initiator's keys here because we are Responder here and // reveice packets from Initiator byte[] dataToSecure = confirm2.getDataToSecure(); byte[] confMac = computeHmac(hmacKeyI, hashLength, dataToSecure, dataToSecure.length); if (ZrtpUtils.byteArrayCompare(confMac, confirm2.getHmac(), 2 * ZrtpPacketBase.ZRTP_WORD_SIZE) != 0) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.ConfirmHMACWrong; return null; } try { // Decrypting here cipher.cipher.init(false, new ParametersWithIV(new KeyParameter(zrtpKeyI, 0, cipher.keyLength), confirm2.getIv())); int done = cipher.cipher.processBytes(dataToSecure, 0, dataToSecure.length, dataToSecure, 0); cipher.cipher.doFinal(dataToSecure, done); } catch (Exception e) { sendInfo(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereSecurityException)); errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } confirm2.setDataToSecure(dataToSecure); 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(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereDH2HMACFailed)); errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } signatureLength = confirm2.getSignatureLength(); if (signSasSeen && signatureLength > 0) { signatureData = confirm2.getSignatureData(); callback.checkSASSignature(sasHash); } /* * The Confirm2 is ok, handle the Retained secret stuff and inform GUI about state. */ // Did our peer verify the SAS during last session? Get its SAS verified flag. boolean sasFlag = confirm2.isSASFlag(); // Our peer did not confirm the SAS in last session, thus reset our stored SAS // flag too. Reset the flag also if paranoidMode is true. if (!sasFlag || paranoidMode) { zidRec.resetSasVerified(); } // Now get the resulting SAS verified flag from current RS1 before setting a new RS1. // It's a combination of our SAS verfied flag and peer's verified flag. Only if both // were set (true) then sasFlag becomes true. sasFlag = zidRec.isSasVerified(); // save new RS1, this inherits the verified flag from old RS1 zidRec.setNewRs1(newRs1, -1); ZidFile.getInstance().saveRecord(zidRec); // Ask for enrollment only if enabled via configuration and the // confirm packet contains the enrollment flag. The enrolling user // agent stores the MitM key only if the user accepts the enrollment // request. if (enableMitmEnrollment && confirm2.isPBXEnrollment()) { computePBXSecret(); callback.zrtpAskEnrollment(ZrtpCodes.InfoEnrollment.EnrollmentRequest); } callback.srtpSecretsOn(cipher.readable + "/" + pubKey, SAS, sasFlag); } else { byte[] tmpHash = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; hashFunctionImpl.update(confirm2.getHashH0(), 0, ZrtpPacketBase.HASH_IMAGE_SIZE); // Compute initiator's H1 // in tmpHash hashFunctionImpl.doFinal(tmpHash, 0); if (!checkMsgHmac(tmpHash)) { sendInfo(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereCommitHMACFailed)); errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } // Inform GUI about security state, don't show SAS and its state callback.srtpSecretsOn(cipher.readable, null, true); } return zrtpConf2Ack; } /** * Prepare the ErrorAck packet. * * This method prepares the ErrorAck packet. The input to this method is the * Error packet received from the peer. */ protected ZrtpPacketErrorAck prepareErrorAck(ZrtpPacketError epkt) { int code = epkt.getErrorCode(); for (ZrtpCodes.ZrtpErrorCodes zc : ZrtpCodes.ZrtpErrorCodes.values()) { if (zc.value == code) { sendInfo(ZrtpCodes.MessageSeverity.ZrtpError, EnumSet.of(zc)); break; } } return zrtpErrorAck; } /** * Prepare the Error packet. * * This method prepares the Error packet. The input to this method is the * error code to be included into the message. */ protected ZrtpPacketError prepareError(ZrtpCodes.ZrtpErrorCodes errMsg) { zrtpError.setErrorCode(errMsg.value); return zrtpError; } /** * Prepare the PingAck packet. * * This method prepares the PingAck packet. */ protected ZrtpPacketPingAck 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 // chapters 5.15 and 5.16) zrtpPingAck.setLocalEpHash(zid); zrtpPingAck.setRemoteEpHash(ppkt.getEpHash()); zrtpPingAck.setPeerSSRC(peerSSRC); return zrtpPingAck; } protected ZrtpPacketRelayAck prepareRelayAck(ZrtpPacketSASRelay srly, ZrtpCodes.ZrtpErrorCodes[] 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[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; } byte[] hkey, ekey; // If we are responder then the PBX used it's Initiator keys if (myRole == ZrtpCallback.Role.Responder) { hkey = hmacKeyI; ekey = zrtpKeyI; } else { hkey = hmacKeyR; ekey = zrtpKeyR; } byte[] dataToSecure = srly.getDataToSecure(); byte[] relayMac = computeHmac(hkey, hashLength, dataToSecure, dataToSecure.length); if (ZrtpUtils.byteArrayCompare(relayMac, srly.getHmac(), 2 * ZrtpPacketBase.ZRTP_WORD_SIZE) != 0) { errMsg[0] = ZrtpCodes.ZrtpErrorCodes.ConfirmHMACWrong; return null; // TODO - check error handling } try { // Decrypting here cipher.cipher.init(false, new ParametersWithIV(new KeyParameter(ekey, 0, cipher.keyLength), srly.getIv())); int done = cipher.cipher.processBytes(dataToSecure, 0, dataToSecure.length, dataToSecure, 0); cipher.cipher.doFinal(dataToSecure, done); } catch (Exception e) { sendInfo(ZrtpCodes.MessageSeverity.Severe, EnumSet.of(ZrtpCodes.SevereCodes.SevereSecurityException)); errMsg[0] = ZrtpCodes.ZrtpErrorCodes.CriticalSWError; return null; // TODO - check error handling } srly.setDataToSecure(dataToSecure); SupportedSASTypes render = srly.getSas(); byte[] newSasHash = srly.getTrustedSas(); boolean sasHashNull = true; for (byte aNewSasHash : newSasHash) { if (aNewSasHash != 0) { sasHashNull = false; break; } } // Check if new SAS is null or a trusted MitM relationship doesn't exist. // If this is the case then don't render and don't show the new SAS - use // the computed SAS hash but we may use a different SAS rendering algorithm to // render the computed SAS. String mitm = "/SASviaMitM"; if (sasHashNull || !peerIsEnrolled) { newSasHash = sasHash; mitm = "/MitM"; } // If other SAS schemes required - check here and use others if (render == ZrtpConstants.SupportedSASTypes.B32 || render == ZrtpConstants.SupportedSASTypes.B32E) { byte[] sasBytes = new byte[4]; sasBytes[0] = newSasHash[0]; sasBytes[1] = newSasHash[1]; sasBytes[2] = (byte) (newSasHash[2] & 0xf0); sasBytes[3] = 0; if (render == ZrtpConstants.SupportedSASTypes.B32) SAS = Base32.binary2ascii(sasBytes, 20); else SAS = EmojiBase32.binary2ascii(sasBytes, 20); } else { SAS = ZrtpConstants.sas256WordsEven[newSasHash[0]] + ":" + ZrtpConstants.sas256WordsOdd[newSasHash[1]]; } callback.srtpSecretsOn(cipher.readable + "/" + pubKey + mitm, SAS, false); return zrtpRelayAck; } /** * Prepare a ClearAck packet. * * This method checks if the GoClear message is valid. If yes then switch * off SRTP processing, stop sending of RTP packets (pause transmit) and * inform the user about the fact. Only if user confirms the GoClear message * normal RTP processing is resumed. * * @return NULL if GoClear could not be authenticated, a ClearAck packet * otherwise. */ // ZrtpPacketClearAck prepareClearAck(ZrtpPacketGoClear gpkt) {return null;} /** * Prepare a GoClearAck packet w/o HMAC * * Prepare a GoCLear packet without a HMAC but with a short error message. * This type of GoClear is used if something went wrong during the ZRTP * negotiation phase. * * @return A goClear packet without HMAC */ // ZrtpPacketGoClear prepareGoClear(ZrtpCodes.ZrtpErrorCodes[] errMsg) // {return null;} /** * Compare the hvi values. * * Compare a received Commit packet with our Commit packet and returns which * Commit packt is "more important". See chapter 5.2 to get further * information how to compare Commit packets. * * @param commit * Pointer to the peer's commit packet we just received. * @return <0 if our Commit packet is "less important" >0 if ours is "more * important" 0 shouldn't happen because we compare crypto hashes */ protected int compareCommit(ZrtpPacketCommit commit) { if (multiStream) { return (ZrtpUtils.byteArrayCompare(hvi, commit.getNonce(), 4 * ZrtpPacketBase.ZRTP_WORD_SIZE)); } return (ZrtpUtils.byteArrayCompare(hvi, commit.getHvi(), ZrtpPacketBase.HVI_SIZE)); } /** * Verify the H2 hash image. * * Verifies the H2 hash contained in a received commit message. This * functions just verifies H2 but does not store it. * * @param commit * Pointer to the peer's commit packet we just received. * @return true if H2 is ok and verified false if H2 could not be verified */ protected boolean verifyH2(ZrtpPacketCommit commit) { byte[] tmpH3 = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; hashFunctionImpl.update(commit.getH2(), 0, ZrtpPacketBase.HASH_IMAGE_SIZE); hashFunctionImpl.doFinal(tmpH3, 0); return ZrtpUtils.byteArrayCompare(tmpH3, peerH3, ZrtpPacketBase.HASH_IMAGE_SIZE) == 0; } /** * Send information messages to the hosting environment. * * The ZRTP implementation uses this method to send information messages to * the host. Along with the message ZRTP provides a severity indicator that * defines: Info, Warning, Error, Alert. Refer to the MessageSeverity enum * in the ZrtpCallback class. * * @param severity * This defines the message's severity * @param subCode * The subcode identifying the reason. * @see ZrtpCodes.MessageSeverity */ protected void sendInfo(ZrtpCodes.MessageSeverity severity, EnumSet subCode) { // We've reached secure state: overwrite the SRTP master key and master salt. if (severity == ZrtpCodes.MessageSeverity.Info && subCode == EnumSet.of(ZrtpCodes.InfoCodes.InfoSecureStateOn)) { Arrays.fill(srtpKeyI, (byte) 0); Arrays.fill(srtpSaltI, (byte) 0); Arrays.fill(srtpKeyR, (byte) 0); Arrays.fill(srtpSaltR, (byte) 0); } callback.sendInfo(severity, subCode); } /** * ZRTP state engine calls this if the negotiation failed. * * ZRTP calls this method in case ZRTP negotiation failed. The parameters * show the severity as well as some explanatory text. * * @param severity * This defines the message's severity * @param subCode * The subcode identifying the reason. * @see ZrtpCodes.MessageSeverity */ protected void zrtpNegotiationFailed(ZrtpCodes.MessageSeverity severity, EnumSet subCode) { callback.zrtpNegotiationFailed(severity, subCode); } /** * ZRTP state engine calls this method if the other side does not support * ZRTP. * * If the other side does not answer the ZRTP Hello packets then * ZRTP calls this method, * */ protected void zrtpNotSuppOther() { callback.zrtpNotSuppOther(); } /** * Signal SRTP secrets are ready. * * This method calls a callback method to inform the host that the SRTP * secrets are ready. * * @param part * Defines for which part (sender or receiver) to switch on * security * @return Returns false if something went wrong during initialization of * SRTP context. Propagate error back to state engine. */ protected boolean srtpSecretsReady(ZrtpCallback.EnableSecurity part) { ZrtpSrtpSecrets sec = new ZrtpSrtpSecrets(); sec.symEncAlgorithm = cipher.algo; sec.keyInitiator = srtpKeyI; sec.initKeyLen = cipher.keyLength * 8; sec.saltInitiator = srtpSaltI; sec.initSaltLen = 112; sec.keyResponder = srtpKeyR; sec.respKeyLen = cipher.keyLength * 8; sec.saltResponder = srtpSaltR; sec.respSaltLen = 112; sec.authAlgorithm = authLength.algo; sec.srtpAuthTagLen = authLength.length; sec.setRole(myRole); return callback.srtpSecretsReady(sec, part); } /** * Switch off SRTP secrets. * * This method calls a callback method to inform the host that the SRTP * secrets shall be cleared. * * @param part * Defines for which part (sender or receiver) to clear */ protected void srtpSecretsOff(ZrtpCallback.EnableSecurity part) { callback.srtpSecretsOff(part); } // Private internal methods /** * Helper function to store ZRTP message data in a temporary buffer * * This functions first clears the temporary buffer, then stores the * packet's data to it. We use this to check the packet's HMAC after we * received the HMAC key in to following packet. * * @param pkt * Pointer to the packet's ZRTP message */ private void storeMsgTemp(ZrtpPacketBase pkt) { int length = pkt.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE; length = (length > tempMsgBuffer.length) ? tempMsgBuffer.length : length; Arrays.fill(tempMsgBuffer, (byte) 0); System.arraycopy(pkt.getHeaderBase(), 0, tempMsgBuffer, 0, length); lengthOfMsgData = length; } /** * Check a ZRTP message HMAC of a previously stored message. * * This function uses a HMAC key to compute a HMAC of a previous received * and stored ZRTP message. It compares the computed HMAC and the HMAC * stored in the stored message and returns the result. This uses the MACH * with the implicit hash algo. * * @param keyIn * Pointer to the HMAC key. * @return Returns true if the computed HMAC and the stored HMAC match, * false otherwise. */ private boolean checkMsgHmac(byte[] keyIn) { // compute HMAC, but exlude the stored HMAC :-) // Use HMAC with implicit hash algo int len = lengthOfMsgData - (2 * ZrtpPacketBase.ZRTP_WORD_SIZE); // :-) KeyParameter key = new KeyParameter(keyIn, 0, ZrtpPacketBase.HASH_IMAGE_SIZE); hmacFunctionImpl.init(key); hmacFunctionImpl.update(tempMsgBuffer, 0, len); byte data[] = new byte[hashLengthImpl]; hmacFunctionImpl.doFinal(data, 0); byte[] storedMac = ZrtpUtils.readRegion(tempMsgBuffer, len, 2 * ZrtpPacketBase.ZRTP_WORD_SIZE); return (ZrtpUtils.byteArrayCompare(data, storedMac, 2 * ZrtpPacketBase.ZRTP_WORD_SIZE) == 0); } private void setNegotiatedHash(ZrtpConstants.SupportedHashes hash) { if (hash == ZrtpConstants.SupportedHashes.S256) { hashFunction = new SHA256Digest(); hmacFunction = new HMac(new SHA256Digest()); hashCtxFunction = new SHA256Digest(); } else if (hash == ZrtpConstants.SupportedHashes.S384) { hashFunction = new SHA384Digest(); hmacFunction = new HMac(new SHA384Digest()); hashCtxFunction = new SHA384Digest(); } hashLength = hashFunction.getDigestSize(); } /** * Set the client ID for ZRTP Hello message. * * The user of ZRTP must set its id to identify itself in the ZRTP HELLO * message. The maximum length is 16 characters. Shorter id string are * allowed, they will be filled with blanks. A longer id is truncated to 16 * characters. * * The identifier is set in the Hello packet of ZRTP. Thus only after * setting the identifier ZRTP can compute the HMAC and the final helloHash. * * @param hpv * Hello packet version class that holds info about the Hello packet for a * specific protocol version * * @param id * The client's id */ private void setClientId(String id, HelloPacketVersion hpv) { String tmp = " "; if (id.length() < 4 * ZrtpPacketBase.ZRTP_WORD_SIZE) { hpv.packet.setClientId(tmp); } hpv.packet.setClientId(id); int len = hpv.packet.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE; // Hello packet is ready now, compute its HMAC with key H2 // (excluding the HMAC field (2*ZTP_WORD_SIZE)) and store in Hello byte data[] = computeHmacImpl(H2, hashLengthImpl, hpv.packet.getHeaderBase(), len - (2 * ZrtpPacketBase.ZRTP_WORD_SIZE)); hpv.packet.setHMAC(data); // calculate hash over the final Hello packet including the computed and // stored HMAC, refer to chap 9.1 how to use this hash in SIP/SDP. // // getHeaderBase() returns the full packetBuffer array. The length of // this array includes the CRC which is not part of the helloHash. // Thus compute digest only for the real message length. // Use implicit hash algo hashFunctionImpl.update(hpv.packet.getHeaderBase(), 0, len); hashFunctionImpl.doFinal(hpv.helloHash, 0); } /** * Helper function to compute a ZRTP message HMAC. * * This function gets a HMAC key and uses it to compute a HMAC with this key * and the stored data of a previous received ZRTP message. It compares the * computed HMAC and the HMAC stored in the received message and returns the * result. This uses the HAMC with the implicit hash algo. * * @param keyIn * Pointer to the HMAC key. * @return Returns true if the computed HMAC and the stored HMAC match, * false otherwise. */ private byte[] computeMsgHmac(byte[] keyIn, ZrtpPacketBase pkt) { // compute HMAC, but exclude the stored HMAC in length computation:-) int len = (pkt.getLength() - 2) * ZrtpPacketBase.ZRTP_WORD_SIZE; return computeHmacImpl(keyIn, hashLengthImpl, pkt.getHeaderBase(), len); } /** * Compute a HMAC over some data using HMAC with negotiated Hash algorithm. * * @param keyIn * The key to use for the HMAC * @param keyLen * The lenght of key data * @param toSign * The data to sign * @param len * the length of the data to sign * @return the HMAC data */ private byte[] computeHmac(byte[] keyIn, int keyLen, byte[] toSign, int len) { KeyParameter key = new KeyParameter(keyIn, 0, keyLen); hmacFunction.init(key); hmacFunction.update(toSign, 0, len); byte[] retval = new byte[hashLength]; hmacFunction.doFinal(retval, 0); return retval; } /** * Compute a HMAC over some data using HMAC with negotiated Hash algorithm. * * @param keyIn * The key to use for the HMAC * @param keyLen * The lenght of key data * @param toSign * The data to sign * @param len * the length of the data to sign * @return the HMAC data */ private byte[] computeHmacImpl(byte[] keyIn, int keyLen, byte[] toSign, int len) { KeyParameter key = new KeyParameter(keyIn, 0, keyLen); hmacFunctionImpl.init(key); hmacFunctionImpl.update(toSign, 0, len); byte[] retval = new byte[hashLengthImpl]; hmacFunctionImpl.doFinal(retval, 0); return retval; } /** * Compute my hvi value according to ZRTP specification. * * This uses the negotiated Hash algorithm. */ private void computeHvi(ZrtpPacketDHPart dh, ZrtpPacketHello hello) { hashFunction.update(dh.getHeaderBase(), 0, dh.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE); hashFunction.update(hello.getHeaderBase(), 0, hello.getLength() * ZrtpPacketBase.ZRTP_WORD_SIZE); hashFunction.doFinal(hvi, 0); } private void computeSharedSecretSet() { /* * Compute the Initiator's and Reponder's retained shared secret Ids. */ byte[] randBuf = new byte[ZidRecord.RS_LENGTH]; if (!zidRec.isRs1Valid()) { secRand.nextBytes(randBuf); rs1IDi = computeHmac(randBuf, ZidRecord.RS_LENGTH, ZrtpConstants.initiator, ZrtpConstants.initiator.length); rs1IDr = computeHmac(randBuf, ZidRecord.RS_LENGTH, ZrtpConstants.responder, ZrtpConstants.responder.length); } else { rs1Valid = true; rs1IDi = computeHmac(zidRec.getRs1(), ZidRecord.RS_LENGTH, ZrtpConstants.initiator, ZrtpConstants.initiator.length); rs1IDr = computeHmac(zidRec.getRs1(), ZidRecord.RS_LENGTH, ZrtpConstants.responder, ZrtpConstants.responder.length); } if (!zidRec.isRs2Valid()) { secRand.nextBytes(randBuf); rs2IDi = computeHmac(randBuf, ZidRecord.RS_LENGTH, ZrtpConstants.initiator, ZrtpConstants.initiator.length); rs2IDr = computeHmac(randBuf, ZidRecord.RS_LENGTH, ZrtpConstants.responder, ZrtpConstants.responder.length); } else { rs2Valid = true; rs2IDi = computeHmac(zidRec.getRs2(), ZidRecord.RS_LENGTH, ZrtpConstants.initiator, ZrtpConstants.initiator.length); rs2IDr = computeHmac(zidRec.getRs2(), ZidRecord.RS_LENGTH, ZrtpConstants.responder, ZrtpConstants.responder.length); } /* * For the time being we don't support the following type of shared secrect. Could be easily done: somebody sets * some data into our ZRtp object, check it here and use it. Otherwise use the random data. */ secRand.nextBytes(randBuf); auxSecretIDi = computeHmac(randBuf, ZidRecord.RS_LENGTH, ZrtpConstants.initiator, ZrtpConstants.initiator.length); auxSecretIDr = computeHmac(randBuf, ZidRecord.RS_LENGTH, ZrtpConstants.responder, ZrtpConstants.responder.length); if (!zidRec.isMITMKeyAvailable()) { secRand.nextBytes(randBuf); pbxSecretIDi = computeHmac(randBuf, ZidRecord.RS_LENGTH, ZrtpConstants.initiator, ZrtpConstants.initiator.length); pbxSecretIDr = computeHmac(randBuf, ZidRecord.RS_LENGTH, ZrtpConstants.responder, ZrtpConstants.responder.length); } else { pbxSecretIDi = computeHmac(zidRec.getMiTMData(), ZidRecord.RS_LENGTH, ZrtpConstants.initiator, ZrtpConstants.initiator.length); pbxSecretIDr = computeHmac(zidRec.getMiTMData(), ZidRecord.RS_LENGTH, ZrtpConstants.responder, ZrtpConstants.responder.length); } } private void generateKeysMultiStream() { // Compute the Multi Stream mode s0 // Construct the KDF context as per ZRTP specification: // ZIDi || ZIDr || total_hash byte[] KDFcontext = new byte[zid.length + peerZid.length + hashLength]; if (myRole == ZrtpCallback.Role.Responder) { System.arraycopy(peerZid, 0, KDFcontext, 0, peerZid.length); System.arraycopy(zid, 0, KDFcontext, peerZid.length, zid.length); } else { System.arraycopy(zid, 0, KDFcontext, 0, zid.length); System.arraycopy(peerZid, 0, KDFcontext, zid.length, peerZid.length); } System.arraycopy(messageHash, 0, KDFcontext, zid.length + peerZid.length, hashLength); s0 = KDF(zrtpSession, ZrtpConstants.zrtpMsk, KDFcontext, hashLength * 8); computeSRTPKeys(); Arrays.fill(s0, (byte) 0); } /* * The ZRTP KDF function as per ZRT specification 4.5.1 */ private byte[] KDF(byte[] ki, byte[] label, byte[] context, int L) { KeyParameter key = new KeyParameter(ki, 0, hashLength); hmacFunction.init(key); byte[] counter = ZrtpUtils.int32ToArray(1); hmacFunction.update(counter, 0, 4); hmacFunction.update(label, 0, label.length); // the label includes the 0 // byte separator hmacFunction.update(context, 0, context.length); byte[] length = ZrtpUtils.int32ToArray(L); hmacFunction.update(length, 0, 4); byte[] retval = new byte[hashLength]; hmacFunction.doFinal(retval, 0); return retval; } private void computeSRTPKeys() { // Construct the KDF context as per ZRTP specification: // ZIDi || ZIDr || total_hash byte[] KDFcontext = new byte[zid.length + peerZid.length + hashLength]; if (myRole == ZrtpCallback.Role.Responder) { System.arraycopy(peerZid, 0, KDFcontext, 0, peerZid.length); System.arraycopy(zid, 0, KDFcontext, peerZid.length, zid.length); } else { System.arraycopy(zid, 0, KDFcontext, 0, zid.length); System.arraycopy(peerZid, 0, KDFcontext, zid.length, peerZid.length); } System.arraycopy(messageHash, 0, KDFcontext, zid.length + peerZid.length, hashLength); int keyLen = cipher.keyLength * 8; // Inititiator key and salt srtpKeyI = KDF(s0, ZrtpConstants.iniMasterKey, KDFcontext, keyLen); srtpSaltI = KDF(s0, ZrtpConstants.iniMasterSalt, KDFcontext, 112); // Responder key and salt srtpKeyR = KDF(s0, ZrtpConstants.respMasterKey, KDFcontext, keyLen); srtpSaltR = KDF(s0, ZrtpConstants.respMasterSalt, KDFcontext, 112); // The HMAC keys hmacKeyI = KDF(s0, ZrtpConstants.iniHmacKey, KDFcontext, hashLength * 8); hmacKeyR = KDF(s0, ZrtpConstants.respHmacKey, KDFcontext, hashLength * 8); // The keys for Confirm messages zrtpKeyI = KDF(s0, ZrtpConstants.iniZrtpKey, KDFcontext, keyLen); zrtpKeyR = KDF(s0, ZrtpConstants.respZrtpKey, KDFcontext, keyLen); if (!multiStream) { // Compute the new Retained Secret newRs1 = KDF(s0, ZrtpConstants.retainedSec, KDFcontext, ZrtpConstants.SHA256_DIGEST_LENGTH * 8); // Compute the ZRTP Session Key zrtpSession = KDF(s0, ZrtpConstants.zrtpSessionKey, KDFcontext, hashLength * 8); // perform SAS generation according to chapter 5.5 and 8. // we don't need a speciai sasValue field. sasValue are the first // (leftmost) 32 bits (4 bytes) of sasHash sasHash = KDF(s0, ZrtpConstants.sasString, KDFcontext, ZrtpConstants.SHA256_DIGEST_LENGTH * 8); // 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). // If other SAS schemes required - check here and use others if (sasType == ZrtpConstants.SupportedSASTypes.B32 || sasType == ZrtpConstants.SupportedSASTypes.B32E) { byte[] sasBytes = new byte[4]; sasBytes[0] = sasHash[0]; sasBytes[1] = sasHash[1]; sasBytes[2] = (byte) (sasHash[2] & 0xf0); sasBytes[3] = 0; if (sasType == ZrtpConstants.SupportedSASTypes.B32) SAS = Base32.binary2ascii(sasBytes, 20); else SAS = EmojiBase32.binary2ascii(sasBytes, 20); } else { SAS = ZrtpConstants.sas256WordsEven[sasHash[0]&0xff] + ":" + ZrtpConstants.sas256WordsOdd[sasHash[1]&0xff]; } if (signSasSeen) callback.signSAS(sasHash); } } private void computePBXSecret() { // Construct the KDF context as per ZRTP specification chap 7.3.1: // ZIDi || ZIDr byte[] KDFcontext = new byte[zid.length + peerZid.length]; if (myRole == ZrtpCallback.Role.Responder) { System.arraycopy(peerZid, 0, KDFcontext, 0, peerZid.length); System.arraycopy(zid, 0, KDFcontext, peerZid.length, zid.length); } else { System.arraycopy(zid, 0, KDFcontext, 0, zid.length); System.arraycopy(peerZid, 0, KDFcontext, zid.length, peerZid.length); } pbxSecretTmp = KDF(zrtpSession, ZrtpConstants.zrtpTrustedMitm, KDFcontext, ZrtpConstants.SHA256_DIGEST_LENGTH * 8); } private void generateKeysInitiator(ZrtpPacketDHPart dhPart) { byte[][] setD = new byte[3][]; int rsFound = 0; setD[0] = setD[1] = setD[2] = null; /* * Select the real secrets into setD */ if (ZrtpUtils.byteArrayCompare(rs1IDr, dhPart.getRs1Id(), 8) == 0) { setD[0] = zidRec.getRs1(); rsFound = 0x1; } else if (ZrtpUtils.byteArrayCompare(rs1IDr, dhPart.getRs2Id(), 8) == 0) { setD[0] = zidRec.getRs1(); rsFound = 0x2; } else if (ZrtpUtils.byteArrayCompare(rs2IDr, dhPart.getRs1Id(), 8) == 0) { setD[0] = zidRec.getRs2(); rsFound = 0x4; } else if (ZrtpUtils.byteArrayCompare(rs2IDr, dhPart.getRs2Id(), 8) == 0) { setD[0] = zidRec.getRs2(); rsFound = 0x8; } /*********************************************************************** * Not yet supported: if (ZrtpUtils.byteArrayCompare(auxSecretIDr, * dhPart.getAuxSecretId(), 8) == 0) { setD[1] = ; } ********************************************************************* */ if (ZrtpUtils.byteArrayCompare(pbxSecretIDr, dhPart.getPbxSecretId(), 8) == 0) { setD[2] = zidRec.getMiTMData(); } // Check if some retained secrets found if (rsFound == 0) { // no RS matches found if (rs1Valid || rs2Valid) { // but valid RS records in cache sendInfo(ZrtpCodes.MessageSeverity.Warning, EnumSet.of(ZrtpCodes.WarningCodes.WarningNoExpectedRSMatch)); zidRec.resetSasVerified(); } else { // No valid RS record in cache sendInfo(ZrtpCodes.MessageSeverity.Warning, EnumSet.of(ZrtpCodes.WarningCodes.WarningNoRSMatch)); } } else { // at least one RS matches sendInfo(ZrtpCodes.MessageSeverity.Info, EnumSet.of(ZrtpCodes.InfoCodes.InfoRSMatchFound)); } /* * Ready to generate s0 here. The formular to compute S0 (Refer to RFC 6189 * chapter 4.4.1.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). */ // Very first element is a fixed counter, big endian byte[] counter = ZrtpUtils.int32ToArray(1); hashFunction.update(counter, 0, 4); // Next is the DH result itself hashFunction.update(DHss, 0, DHss.length); // Next the fixed string "ZRTP-HMAC-KDF" hashFunction.update(ZrtpConstants.KDFString, 0, ZrtpConstants.KDFString.length); // Next is Initiator's id (ZIDi), in this case as Initiator // it is zid hashFunction.update(zid, 0, zid.length); // Next is Responder's id (ZIDr), in this case our peer's id hashFunction.update(peerZid, 0, peerZid.length); // Next ist total hash (messageHash) itself hashFunction.update(messageHash, 0, 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 for * retained shared secrets. If a shared secret is not used _only_ its * length is hased as zero length. */ // prepare 32 bit big-endian number byte[] secretHashLen = ZrtpUtils.int32ToArray(ZidRecord.RS_LENGTH); byte[] nullinger = new byte[4]; Arrays.fill(nullinger, (byte) 0); for (int i = 0; i < 3; i++) { if (setD[i] != null) { // a matching secret: set length, then secret hashFunction.update(secretHashLen, 0, secretHashLen.length); hashFunction.update(setD[i], 0, setD[i].length); } else { // no machting secret, set length 0, skip secret hashFunction.update(nullinger, 0, nullinger.length); } } s0 = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; hashFunction.doFinal(s0, 0); // ZrtpUtils.hexdump("S0 I", s0, hashLength); Arrays.fill(DHss, (byte) 0); DHss = null; computeSRTPKeys(); Arrays.fill(s0, (byte) 0); } private void generateKeysResponder(ZrtpPacketDHPart dhPart) { byte[][] setD = new byte[3][]; int rsFound = 0; setD[0] = setD[1] = setD[2] = null; /* * Select the real secrets into setD */ if (ZrtpUtils.byteArrayCompare(rs1IDi, dhPart.getRs1Id(), 8) == 0) { setD[0] = zidRec.getRs1(); rsFound = 0x1; } else if (ZrtpUtils.byteArrayCompare(rs1IDi, dhPart.getRs2Id(), 8) == 0) { setD[0] = zidRec.getRs1(); rsFound = 0x2; } else if (ZrtpUtils.byteArrayCompare(rs2IDi, dhPart.getRs1Id(), 8) == 0) { setD[0] = zidRec.getRs2(); rsFound = 0x4; } else if (ZrtpUtils.byteArrayCompare(rs2IDi, dhPart.getRs2Id(), 8) == 0) { setD[0] = zidRec.getRs2(); rsFound = 0x8; } /*********************************************************************** * Not yet supported if (ZrtpUtils.byteArrayCompare(auxSecretIDi, * dhPart.getAuxSecretId(), 8) == 0) { setD[1] = } **********************************************************************/ if (ZrtpUtils.byteArrayCompare(pbxSecretIDi, dhPart.getPbxSecretId(), 8) == 0) { setD[2] = zidRec.getMiTMData(); } // Check if some retained secrets found if (rsFound == 0) { // no RS matches found if (rs1Valid || rs2Valid) { // but valid RS records in cache sendInfo(ZrtpCodes.MessageSeverity.Warning, EnumSet.of(ZrtpCodes.WarningCodes.WarningNoExpectedRSMatch)); zidRec.resetSasVerified(); } else { // No valid RS record in cache sendInfo(ZrtpCodes.MessageSeverity.Warning, EnumSet.of(ZrtpCodes.WarningCodes.WarningNoRSMatch)); } } else { // at least one RS matches sendInfo(ZrtpCodes.MessageSeverity.Info, EnumSet.of(ZrtpCodes.InfoCodes.InfoRSMatchFound)); } /* * ready to generate s0 here. The formular to compute S0 (Refer to RFC 6189 * chapter 4.4.1.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. */ // Very first element is a fixed counter, big endian byte[] counter = ZrtpUtils.int32ToArray(1); hashFunction.update(counter, 0, 4); // Next is the DH result itself hashFunction.update(DHss, 0, DHss.length); // Next the fixed string "ZRTP-HMAC-KDF" hashFunction.update(ZrtpConstants.KDFString, 0, ZrtpConstants.KDFString.length); // Next is Initiator's id (ZIDi), in this case as Responder // it is peerZid hashFunction.update(peerZid, 0, peerZid.length); // Next is Responder's id (ZIDr), in this case our own zid hashFunction.update(zid, 0, zid.length); // Next ist total hash (messageHash) itself hashFunction.update(messageHash, 0, 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 for * retained shared secrets. If a shared secret is not used _only_ its * length is hased as zero length. */ // prepare 32 bit big-endian number byte[] secretHashLen = ZrtpUtils.int32ToArray(ZidRecord.RS_LENGTH); byte[] nullinger = new byte[4]; Arrays.fill(nullinger, (byte) 0); for (int i = 0; i < 3; i++) { if (setD[i] != null) { // a matching secret: set length, then secret hashFunction.update(secretHashLen, 0, secretHashLen.length); hashFunction.update(setD[i], 0, setD[i].length); } else { // no machting secret, set length 0, skip secret hashFunction.update(nullinger, 0, nullinger.length); } } s0 = new byte[ZrtpConstants.MAX_DIGEST_LENGTH]; hashFunction.doFinal(s0, 0); // ZrtpUtils.hexdump("S0 R", s0, hashLength); Arrays.fill(DHss, (byte) 0); DHss = null; computeSRTPKeys(); Arrays.fill(s0, (byte) 0); } private boolean checkPubKey(BigInteger pvr, ZrtpConstants.SupportedPubKeys dhtype) { if (pvr.equals(BigInteger.ONE)) { return false; } if (dhtype == ZrtpConstants.SupportedPubKeys.DH2K) { return !pvr.equals(ZrtpConstants.P2048MinusOne); } return dhtype == ZrtpConstants.SupportedPubKeys.DH3K && !pvr.equals(ZrtpConstants.P3072MinusOne); } // public static void main(String argv[]) { // byte[] data= // {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32}; // ZRtp zrtp = null; // try { // zrtp = new ZRtp(data, null, "GNU ZRTP4J 1.0.0", null); // } catch (GeneralSecurityException e) { // e.printStackTrace(); // } // ZrtpUtils.hexdump("Hello packet", zrtp.zrtpHello.getHeaderBase(), // zrtp.zrtpHello.getHeaderBase().length); // System.err.println("ZRtp done"); // } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy