/* * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. * * Distributable under LGPL license. * See terms of license at gnu.org. * * * Some of the code in this class is derived from ccRtp's SRTP implementation, * which has the following copyright notice: * Copyright (C) 2004-2006 the Minisip Team This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package net.java.sip.communicator.impl.media.transform.srtp; import org.bouncycastle.crypto.digests.SHA1Digest; import org.bouncycastle.crypto.macs.HMac; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.engines.AESFastEngine; import net.java.sip.communicator.impl.media.transform.*; /** * SRTPCryptoContext class is the core class of SRTP implementation. * There can be multiple SRTP sources in one SRTP session. And each SRTP stream * has a corresponding SRTPCryptoContext object, identified by SSRC. In this * way, different sources can be protected independently. * * SRTPCryptoContext class acts as a manager class and maintains all the * information used in SRTP transformation. It is responsible for deriving * encryption keys / salting keys / authentication keys from master keys. And * it will invoke certain class to encrypt / decrypt (transform / reverse * transform) RTP packets. It will hold a replay check db and do replay check * against incoming packets. * * Refer to section 3.2 in RFC3711 for detailed description of cryptographic * context. * * Cryptographic related parameters, i.e. encryption mode / authentication mode, * master encryption key and master salt key are determined outside the scope * of SRTP implementation. They can be assigned manually, or can be assigned * automatically using some key management protocol, such as MIKEY (RFC3880) or * Phil Zimmermann's ZRTP protocol (draft-zimmermann-avt-zrtp-01). * * @author Bing SU (nova.su@gmail.com) */ public class SRTPCryptoContext { /** * The replay check windows size */ private static final long REPLAY_WINDOW_SIZE = 64; /** * RTP SSRC of this cryptographic context */ private long ssrc; /** * Master key identifier */ private byte[] mki; /** * Roll-Over-Counter, see RFC3711 section 3.2.1 for detailed description */ private int roc; /** * Roll-Over-Counter guessed from packet */ private int guessedROC; /** * RTP sequence number of the packet current processing */ private int seqNum; /** * Whether we have the sequence number of current packet */ private boolean seqNumSet; /** * Key Derivation Rate, used to derive session keys from master keys */ private long keyDerivationRate; /** * Bit mask for replay check */ private long replayWindow; /** * Master encryption key */ private byte[] masterKey; /** * Master salting key */ private byte[] masterSalt; /** * Derived session encryption key */ private byte[] encKey; /** * Derived session authentication key */ private byte[] authKey; /** * Derived session salting key */ private byte[] saltKey; /** * Encryption / Authentication policy for this session */ private final SRTPPolicy policy; /** * The HMAC object we used to do packet authentication */ private HMac hmacSha1; // used for various HMAC computations // The symmetric cipher engines we need here private AESFastEngine AEScipher = null; private AESFastEngine AEScipherF8 = null; // used inside F8 mode only // implements the counter cipher mode for RTP according to RFC 3711 private final SRTPCipherCTR cipherCtr = new SRTPCipherCTR(); // Here some fields that a allocated here or in constructor. The methods // use these fields to avoid too many new operations private final byte[] tagStore; private final byte[] ivStore = new byte[16]; private final byte[] rbStore = new byte[4]; // this is some working store, used by some methods to avoid new operations // the methods must use this only to store some reults for immediate processing private final byte[] tempStore = new byte[100]; /** * Construct an empty SRTPCryptoContext using ssrc. * The other parameters are set to default null value. * * @param ssrc SSRC of this SRTPCryptoContext */ public SRTPCryptoContext(long ssrcIn) { ssrc = ssrcIn; mki = null; roc = 0; guessedROC = 0; seqNum = 0; keyDerivationRate = 0; masterKey = null; masterSalt = null; encKey = null; authKey = null; saltKey = null; seqNumSet = false; policy = null; tagStore = null; } /** * Construct a normal SRTPCryptoContext based on the given parameters. * * @param ssrc * the RTP SSRC that this SRTP cryptographic context protects. * @param roc * the initial Roll-Over-Counter according to RFC 3711. These are * the upper 32 bit of the overall 48 bit SRTP packet index. * Refer to chapter 3.2.1 of the RFC. * @param keyDerivationRate * the key derivation rate defines when to recompute the SRTP * session keys. Refer to chapter 4.3.1 in the RFC. * @param masterKey * byte array holding the master key for this SRTP cryptographic * context. Refer to chapter 3.2.1 of the RFC about the role of * the master key. * @param masterSalt * byte array holding the master salt for this SRTP cryptographic * context. It is used to computer the initialization vector that * in turn is input to compute the session key, session * authentication key and the session salt. * @param policy * SRTP policy for this SRTP cryptographic context, defined the * encryption algorithm, the authentication algorithm, etc */ public SRTPCryptoContext(long ssrcIn, int rocIn, long kdr, byte[] masterK, byte[] masterS, SRTPPolicy policyIn) { ssrc = ssrcIn; mki = null; roc = rocIn; guessedROC = 0; seqNum = 0; keyDerivationRate = kdr; seqNumSet = false; policy = policyIn; masterKey = new byte[policy.getEncKeyLength()]; System.arraycopy(masterK, 0, masterKey, 0, policy .getEncKeyLength()); masterSalt = new byte[policy.getSaltKeyLength()]; System.arraycopy(masterS, 0, masterSalt, 0, policy .getSaltKeyLength()); hmacSha1 = new HMac(new SHA1Digest()); AEScipher = new AESFastEngine(); switch (policy.getEncType()) { case SRTPPolicy.NULL_ENCRYPTION: encKey = null; saltKey = null; break; case SRTPPolicy.AESF8_ENCRYPTION: AEScipherF8 = new AESFastEngine(); case SRTPPolicy.AESCM_ENCRYPTION: encKey = new byte[policy.getEncKeyLength()]; saltKey = new byte[policy.getSaltKeyLength()]; break; } switch (policy.getAuthType()) { case SRTPPolicy.NULL_AUTHENTICATION: authKey = null; tagStore = null; break; case SRTPPolicy.HMACSHA1_AUTHENTICATION: authKey = new byte[policy.getAuthKeyLength()]; tagStore = new byte[hmacSha1.getMacSize()]; break; default: tagStore = null; } } /** * Get the authentication tag length of this SRTP cryptographic context * * @return the authentication tag length of this SRTP cryptographic context */ public int getAuthTagLength() { return policy.getAuthTagLength(); } /** * Get the MKI length of this SRTP cryptographic context * * @return the MKI length of this SRTP cryptographic context */ public int getMKILength() { if (mki != null) { return mki.length; } else { return 0; } } /** * Get the SSRC of this SRTP cryptographic context * * @return the SSRC of this SRTP cryptographic context */ public long getSSRC() { return ssrc; } /** * Get the Roll-Over-Counter of this SRTP cryptographic context * * @return the Roll-Over-Counter of this SRTP cryptographic context */ public int getROC() { return roc; } /** * Set the Roll-Over-Counter of this SRTP cryptographic context * * @param roc the Roll-Over-Counter of this SRTP cryptographic context */ public void setROC(int rocIn) { roc = rocIn; } /** * Transform a RTP packet into a SRTP packet. * This method is called when a normal RTP packet ready to be sent. * * Operations done by the transformation may include: encryption, using * either Counter Mode encryption, or F8 Mode encryption, adding * authentication tag, currently HMC SHA1 method. * * Both encryption and authentication functionality can be turned off * as long as the SRTPPolicy used in this SRTPCryptoContext is requires no * encryption and no authentication. Then the packet will be sent out * untouched. However this is not encouraged. If no SRTP feature is enabled, * then we shall not use SRTP TransformConnector. We should use the original * method (RTPManager managed transportation) instead. * * @param pkt the RTP packet that is going to be sent out */ public void transformPacket(RawPacket pkt) { /* Encrypt the packet using Counter Mode encryption */ if (policy.getEncType() == SRTPPolicy.AESCM_ENCRYPTION) { processPacketAESCM(pkt); } /* Encrypt the packet using F8 Mode encryption */ else if (policy.getEncType() == SRTPPolicy.AESF8_ENCRYPTION) { processPacketAESF8(pkt); } /* Authenticate the packet */ if (policy.getAuthType() == SRTPPolicy.HMACSHA1_AUTHENTICATION) { authenticatePacketHMCSHA1(pkt, roc); pkt.append(tagStore, policy.getAuthTagLength()); } /* Update the ROC if necessary */ int seqNo = PacketManipulator.GetRTPSequenceNumber(pkt); if (seqNo == 0xFFFF) { roc++; } } /** * Transform a SRTP packet into a RTP packet. * This method is called when a SRTP packet is received. * * Operations done by the this operation include: * Authentication check, Packet replay check and decryption. * * Both encryption and authentication functionality can be turned off * as long as the SRTPPolicy used in this SRTPCryptoContext is requires no * encryption and no authentication. Then the packet will be sent out * untouched. However this is not encouraged. If no SRTP feature is enabled, * then we shall not use SRTP TransformConnector. We should use the original * method (RTPManager managed transportation) instead. * * @param pkt the RTP packet that is just received * @return true if the packet can be accepted * false if the packet failed authentication or failed replay check */ public boolean reverseTransformPacket(RawPacket pkt) { int seqNo = PacketManipulator.GetRTPSequenceNumber(pkt); if (!seqNumSet) { seqNumSet = true; seqNum = seqNo; } // Guess the SRTP index (48 bit), see rFC 3711, 3.3.1 // Stores the guessed roc in this.guessedROC long guessedIndex = guessIndex(seqNo); /* Replay control */ if (!checkReplay(seqNo, guessedIndex)) { return false; } /* Authenticate the packet */ if (policy.getAuthType() == SRTPPolicy.HMACSHA1_AUTHENTICATION) { int tagLength = policy.getAuthTagLength(); // get original authentication and store in tempStore pkt.readRegionToBuff(pkt.getLength() - tagLength, tagLength, tempStore); pkt.shrink(tagLength); // save computed authentication in tagStore authenticatePacketHMCSHA1(pkt, guessedROC); for (int i = 0; i < tagLength; i++) { if ((tempStore[i]&0xff) == (tagStore[i]&0xff)) continue; else return false; } } /* Decrypt the packet using Counter Mode encryption*/ if (policy.getEncType() == SRTPPolicy.AESCM_ENCRYPTION) { processPacketAESCM(pkt); } /* Decrypt the packet using F8 Mode encryption*/ else if (policy.getEncType() == SRTPPolicy.AESF8_ENCRYPTION) { processPacketAESF8(pkt); } update(seqNo, guessedIndex); return true; } /** * Perform Counter Mode AES encryption / decryption * @param pkt the RTP packet to be encrypted / decrypted */ public void processPacketAESCM(RawPacket pkt) { long ssrc = PacketManipulator.GetRTPSSRC(pkt); int seqNum = PacketManipulator.GetRTPSequenceNumber(pkt); long index = ((long) this.roc << 16) | (long) seqNum; // byte[] iv = new byte[16]; ivStore[0] = saltKey[0]; ivStore[1] = saltKey[1]; ivStore[2] = saltKey[2]; ivStore[3] = saltKey[3]; int i; for (i = 4; i < 8; i++) { ivStore[i] = (byte) ((0xFF & (ssrc >> ((7 - i) * 8))) ^ this.saltKey[i]); } for (i = 8; i < 14; i++) { ivStore[i] = (byte) ((0xFF & (byte) (index >> ((13 - i) * 8))) ^ this.saltKey[i]); } ivStore[14] = ivStore[15] = 0; final int payloadOffset = PacketManipulator.GetRTPHeaderLength(pkt); final int payloadLength = PacketManipulator.GetRTPPayloadLength(pkt); cipherCtr.process(AEScipher, pkt.getBuffer(), pkt.getOffset() + payloadOffset, payloadLength, ivStore); } /** * Perform F8 Mode AES encryption / decryption * * @param pkt the RTP packet to be encrypted / decrypted */ public void processPacketAESF8(RawPacket pkt) { // byte[] iv = new byte[16]; // 11 bytes of the RTP header are the 11 bytes of the iv // the first byte of the RTP header is not used. System.arraycopy(pkt.getBuffer(), pkt.getOffset(), ivStore, 0, 12); ivStore[0] = 0; // set the ROC in network order into IV ivStore[12] = (byte) (this.roc >> 24); ivStore[13] = (byte) (this.roc >> 16); ivStore[14] = (byte) (this.roc >> 8); ivStore[15] = (byte) this.roc; final int payloadOffset = PacketManipulator.GetRTPHeaderLength(pkt); final int payloadLength = PacketManipulator.GetRTPPayloadLength(pkt); SRTPCipherF8.process(AEScipher, pkt.getBuffer(), pkt.getOffset() + payloadOffset, payloadLength, ivStore, encKey, saltKey, AEScipherF8); } /** * Authenticate a packet using HMC SHA1 method. * Calculated authentication tag is returned. * * @param pkt the RTP packet to be authenticated * @return authentication tag of pkt */ private void authenticatePacketHMCSHA1(RawPacket pkt, int rocIn) { hmacSha1.update(pkt.getBuffer(), 0, pkt.getLength()); // byte[] rb = new byte[4]; rbStore[0] = (byte) (rocIn >> 24); rbStore[1] = (byte) (rocIn >> 16); rbStore[2] = (byte) (rocIn >> 8); rbStore[3] = (byte) rocIn; hmacSha1.update(rbStore, 0, rbStore.length); hmacSha1.doFinal(tagStore, 0); } /** * Checks if a packet is a replayed on based on its sequence number. * * This method supports a 64 packet history relative the the given * sequence number. * * Sequence Number is guaranteed to be real (not faked) through * authentication. * * @param seqNum sequence number of the packet * @return true if this sequence number indicates the packet is not a * replayed one, false if not */ boolean checkReplay(int seqNo, long guessedIndex) { // compute the index of previously received packet and its // delta to the new received packet long localIndex = (((long) this.roc) << 16) | this.seqNum; long delta = guessedIndex - localIndex; if (delta > 0) { /* Packet not yet received */ return true; } else { if (-delta > REPLAY_WINDOW_SIZE) { /* Packet too old */ return false; } else { if (((this.replayWindow >> (-delta)) & 0x1) != 0) { /* Packet already received ! */ return false; } else { /* Packet not yet received */ return true; } } } } /** * Compute the initialization vector, used later by encryption algorithms, * based on the lable, the packet index, key derivation rate and master * salt key. * * @param iv calculated initialization vector * @param label label specified for each type of iv * @param index 48bit RTP packet index * @param kdv key derivation rate of this SRTPCryptoContext * @param masterSalt master salt key */ private void computeIv(long label, long index) { long key_id; if (keyDerivationRate == 0) { key_id = label << 48; } else { key_id = ((label << 48) | (index / keyDerivationRate)); } for (int i = 0; i < 7; i++) { ivStore[i] = masterSalt[i]; } for (int i = 7; i < 14; i++) { ivStore[i] = (byte) ((byte) (0xFF & (key_id >> (8 * (13 - i)))) ^ masterSalt[i]); } ivStore[14] = ivStore[15] = 0; } /** * Derives the srtp session keys from the master key * * @param index * the 48 bit SRTP packet index */ public void deriveSrtpKeys(long index) { // compute the session encryption key long label = 0; computeIv(label, index); KeyParameter encryptionKey = new KeyParameter(masterKey); AEScipher.init(true, encryptionKey); cipherCtr.getCipherStream(AEScipher, encKey, policy.getEncKeyLength(), ivStore); // compute the session authentication key if (authKey != null) { label = 0x01; computeIv(label, index); cipherCtr.getCipherStream(AEScipher, authKey, policy.getAuthKeyLength(), ivStore); KeyParameter key = new KeyParameter(authKey); hmacSha1.init(key); } // compute the session salt label = 0x02; computeIv(label, index); cipherCtr.getCipherStream(AEScipher, saltKey, policy.getSaltKeyLength(), ivStore); // As last step: initialize AES cipher with derived encryption key. encryptionKey = new KeyParameter(encKey); AEScipher.init(true, encryptionKey); } /** * Compute (guess) the new SRTP index based on the sequence number of a * received RTP packet. * * @param seqNum * sequence number of the received RTP packet * @return the new SRTP packet index */ private long guessIndex(int seqNo) { if (this.seqNum < 32768) { if (seqNo - this.seqNum > 32768) { guessedROC = roc - 1; } else { guessedROC = roc; } } else { if (seqNum - 32768 > seqNo) { guessedROC = roc + 1; } else { guessedROC = roc; } } return ((long) guessedROC) << 16 | seqNo; } /** * Update the SRTP packet index. * * This method is called after all checks were successful. * See section 3.3.1 in RFC3711 for detailed description. * * @param seqNum sequence number of the accepted packet */ private void update(int seqNo, long guessedIndex) { long delta = guessedIndex - (((long) this.roc) << 16 | this.seqNum); /* update the replay bit mask */ if( delta > 0 ){ replayWindow = replayWindow << delta; replayWindow |= 1; } else { replayWindow |= ( 1 << delta ); } if (seqNo > seqNum) { seqNum = seqNo & 0xffff; } if (this.guessedROC > this.roc) { roc = guessedROC; seqNum = seqNo & 0xffff; } } /** * Derive a new SRTPCryptoContext for use with a new SSRC * * This method returns a new SRTPCryptoContext initialized with the data of * this SRTPCryptoContext. Replacing the SSRC, Roll-over-Counter, and the * key derivation rate the application cab use this SRTPCryptoContext to * encrypt / decrypt a new stream (Synchronization source) inside one RTP * session. * * Before the application can use this SRTPCryptoContext it must call the * deriveSrtpKeys method. * * @param ssrc * The SSRC for this context * @param roc * The Roll-Over-Counter for this context * @param deriveRate * The key derivation rate for this context * @return a new SRTPCryptoContext with all relevant data set. */ public SRTPCryptoContext deriveContext(long ssrc, int roc, long deriveRate) { SRTPCryptoContext pcc = null; pcc = new SRTPCryptoContext(ssrc, roc, deriveRate, masterKey, masterSalt, policy); return pcc; } }