/*
* Jitsi, 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.neomedia.transform.srtp;
import java.util.Arrays;
import net.java.sip.communicator.impl.neomedia.*;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.macs.*;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersForSkein;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.Mac;
import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.engines.TwofishEngine;
/**
* 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 (RFC3830) or
* Phil Zimmermann's ZRTP protocol.
*
* @author Bing SU (nova.su@gmail.com)
*/
public class SRTCPCryptoContext
{
/**
* The replay check windows size
*/
private static final long REPLAY_WINDOW_SIZE = 64;
/**
* RTCP SSRC of this cryptographic context
*/
private long ssrcCtx;
/**
* Master key identifier
*/
private byte[] mki;
/**
* Index received so far
*/
private int receivedIndex = 0;
/**
* Index sent so far
*/
private int sentIndex = 0;
/**
* 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 Mac mac; // used for various HMAC computations
// The symmetric cipher engines we need here
private BlockCipher cipher = null;
private BlockCipher cipherF8 = 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 SRTCPCryptoContext(long ssrcIn)
{
ssrcCtx = ssrcIn;
mki = null;
masterKey = null;
masterSalt = null;
encKey = null;
authKey = null;
saltKey = null;
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 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
*/
@SuppressWarnings("fallthrough")
public SRTCPCryptoContext(long ssrcIn,
byte[] masterK, byte[] masterS, SRTPPolicy policyIn)
{
ssrcCtx = ssrcIn;
mki = null;
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());
switch (policy.getEncType()) {
case SRTPPolicy.NULL_ENCRYPTION:
encKey = null;
saltKey = null;
break;
case SRTPPolicy.AESF8_ENCRYPTION:
cipherF8 = new AESFastEngine();
case SRTPPolicy.AESCM_ENCRYPTION:
cipher = new AESFastEngine();
encKey = new byte[this.policy.getEncKeyLength()];
saltKey = new byte[this.policy.getSaltKeyLength()];
break;
case SRTPPolicy.TWOFISHF8_ENCRYPTION:
cipherF8 = new TwofishEngine();
case SRTPPolicy.TWOFISH_ENCRYPTION:
cipher = new TwofishEngine();
encKey = new byte[this.policy.getEncKeyLength()];
saltKey = new byte[this.policy.getSaltKeyLength()];
break;
}
switch (policy.getAuthType()) {
case SRTPPolicy.NULL_AUTHENTICATION:
authKey = null;
tagStore = null;
break;
case SRTPPolicy.HMACSHA1_AUTHENTICATION:
mac = new HMac(new SHA1Digest());
authKey = new byte[policy.getAuthKeyLength()];
tagStore = new byte[mac.getMacSize()];
break;
case SRTPPolicy.SKEIN_AUTHENTICATION:
mac = new SkeinMac();
authKey = new byte[policy.getAuthKeyLength()];
tagStore = new byte[policy.getAuthTagLength()];
break;
default:
tagStore = null;
}
}
/**
* Close the crypto context.
*
* The close functions deletes key data and performs a cleanup of the
* crypto context.
*
* Clean up key data, maybe this is the second time. However, sometimes
* we cannot know if the CryptoContext was used and the application called
* deriveSrtpKeys(...) tah would have cleaned the key data.
*
*/
public void close()
{
Arrays.fill(masterKey, (byte)0);
Arrays.fill(masterSalt, (byte)0);
}
/**
* 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;
return 0;
}
/**
* Get the SSRC of this SRTP cryptographic context
*
* @return the SSRC of this SRTP cryptographic context
*/
public long getSSRC()
{
return ssrcCtx;
}
/**
* 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)
{
boolean encrypt = false;
/* Encrypt the packet using Counter Mode encryption */
if (policy.getEncType() == SRTPPolicy.AESCM_ENCRYPTION ||
policy.getEncType() == SRTPPolicy.TWOFISH_ENCRYPTION)
{
processPacketAESCM(pkt, sentIndex);
encrypt = true;
}
/* Encrypt the packet using F8 Mode encryption */
else if (policy.getEncType() == SRTPPolicy.AESF8_ENCRYPTION ||
policy.getEncType() == SRTPPolicy.TWOFISHF8_ENCRYPTION)
{
processPacketAESF8(pkt, sentIndex);
encrypt = true;
}
int index = 0;
if (encrypt)
index = sentIndex | 0x80000000;
// Grow packet storage in one step
pkt.grow(4 + policy.getAuthTagLength());
// Authenticate the packet
// The authenticate method gets the index via parameter and stores
// it in network order in rbStore variable.
if (policy.getAuthType() != SRTPPolicy.NULL_AUTHENTICATION)
{
authenticatePacket(pkt, index);
pkt.append(rbStore, 4);
pkt.append(tagStore, policy.getAuthTagLength());
}
sentIndex++;
sentIndex &= ~0x80000000; // clear possible overflow
}
/**
* Transform a SRTCP packet into a RTCP packet.
* This method is called when a SRTCP packet was 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 requires no
* encryption and no authentication. Then the packet will be sent out
* untouched. However this is not encouraged. If no SRTCP feature is enabled,
* then we shall not use SRTP TransformConnector. We should use the original
* method (RTPManager managed transportation) instead.
*
* @param pkt the received RTCP packet
* @return true if the packet can be accepted
* false if authentication or replay check failed
*/
public boolean reverseTransformPacket(RawPacket pkt)
{
boolean decrypt = false;
int tagLength = policy.getAuthTagLength();
int indexEflag = pkt.getSRTCPIndex(tagLength);
if ((indexEflag & 0x80000000) == 0x80000000)
decrypt = true;
int index = indexEflag & ~0x80000000;
/* Replay control */
if (!checkReplay(index))
{
return false;
}
/* Authenticate the packet */
if (policy.getAuthType() != SRTPPolicy.NULL_AUTHENTICATION)
{
// get original authentication data and store in tempStore
pkt.readRegionToBuff(pkt.getLength() - tagLength, tagLength,
tempStore);
// Shrink packet to remove the authentication tag and index
// because this is part of authenicated data
pkt.shrink(tagLength + 4);
// compute, then save authentication in tagStore
authenticatePacket(pkt, indexEflag);
for (int i = 0; i < tagLength; i++)
{
if ((tempStore[i] & 0xff) == (tagStore[i] & 0xff))
continue;
else
return false;
}
}
if (decrypt)
{
/* Decrypt the packet using Counter Mode encryption */
if (policy.getEncType() == SRTPPolicy.AESCM_ENCRYPTION
|| policy.getEncType() == SRTPPolicy.TWOFISH_ENCRYPTION)
{
processPacketAESCM(pkt, index);
}
/* Decrypt the packet using F8 Mode encryption */
else if (policy.getEncType() == SRTPPolicy.AESF8_ENCRYPTION
|| policy.getEncType() == SRTPPolicy.TWOFISHF8_ENCRYPTION)
{
processPacketAESF8(pkt, index);
}
}
update(index);
return true;
}
/**
* Perform Counter Mode AES encryption / decryption
* @param pkt the RTP packet to be encrypted / decrypted
*/
public void processPacketAESCM(RawPacket pkt, int index)
{
long ssrc = pkt.GetRTCPSSRC();
/* Compute the CM IV (refer to chapter 4.1.1 in RFC 3711):
*
* k_s XX XX XX XX XX XX XX XX XX XX XX XX XX XX
* SSRC XX XX XX XX
* index XX XX XX XX
* ------------------------------------------------------XOR
* IV XX XX XX XX XX XX XX XX XX XX XX XX XX XX 00 00
* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
*/
ivStore[0] = saltKey[0];
ivStore[1] = saltKey[1];
ivStore[2] = saltKey[2];
ivStore[3] = saltKey[3];
// The shifts transform the ssrc and index into network order
ivStore[4] = (byte) (((ssrc >> 24) & 0xff) ^ this.saltKey[4]);
ivStore[5] = (byte) (((ssrc >> 16) & 0xff) ^ this.saltKey[5]);
ivStore[6] = (byte) (((ssrc >> 8) & 0xff) ^ this.saltKey[6]);
ivStore[7] = (byte) ((ssrc & 0xff) ^ this.saltKey[7]);
ivStore[8] = saltKey[8];
ivStore[9] = saltKey[9];
ivStore[10] = (byte) (((index >> 24) & 0xff) ^ this.saltKey[10]);
ivStore[11] = (byte) (((index >> 16) & 0xff) ^ this.saltKey[11]);
ivStore[12] = (byte) (((index >> 8) & 0xff) ^ this.saltKey[12]);
ivStore[13] = (byte) ((index & 0xff) ^ this.saltKey[13]);
ivStore[14] = ivStore[15] = 0;
// Encrypted part excludes fixed header (8 bytes)
final int payloadOffset = 8;
final int payloadLength = pkt.getLength() - payloadOffset;
cipherCtr.process(cipher, 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, int index)
{
// byte[] iv = new byte[16];
// 4 bytes of the iv are zero
// the first byte of the RTP header is not used.
ivStore[0] = 0;
ivStore[1] = 0;
ivStore[2] = 0;
ivStore[3] = 0;
// Need the encryption flag
index = index | 0x80000000;
// set the index and the encrypt flag in network order into IV
ivStore[4] = (byte) (index >> 24);
ivStore[5] = (byte) (index >> 16);
ivStore[6] = (byte) (index >> 8);
ivStore[7] = (byte) index;
// The fixed header follows and fills the rest of the IV
System.arraycopy(pkt.getBuffer(), pkt.getOffset(), ivStore, 8, 8);
// Encrypted part excludes fixed header (8 bytes), index (4 bytes), and
// authentication tag (variable according to policy)
final int payloadOffset = 8;
final int payloadLength
= pkt.getLength() - (4 + policy.getAuthTagLength());
SRTPCipherF8.process(cipher, pkt.getBuffer(),
pkt.getOffset() + payloadOffset,
payloadLength, ivStore, cipherF8);
}
/**
* Authenticate a packet.
*
* Calculated authentication tag is stored in tagStore area.
*
* @param pkt the RTP packet to be authenticated
*/
private void authenticatePacket(RawPacket pkt, int index)
{
mac.update(pkt.getBuffer(), 0, pkt.getLength());
// byte[] rb = new byte[4];
rbStore[0] = (byte) (index >> 24);
rbStore[1] = (byte) (index >> 16);
rbStore[2] = (byte) (index >> 8);
rbStore[3] = (byte) index;
mac.update(rbStore, 0, rbStore.length);
mac.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 index index number of the SRTCP packet
* @return true if this sequence number indicates the packet is not a
* replayed one, false if not
*/
boolean checkReplay(int index)
{
// compute the index of previously received packet and its
// delta to the new received packet
long delta = index - receivedIndex;
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 label.
*
* @param label label specified for each type of iv
*/
private void computeIv(byte label)
{
for (int i = 0; i < 14; i++)
{
ivStore[i] = masterSalt[i];
}
ivStore[7] ^= label;
ivStore[14] = ivStore[15] = 0;
}
/**
* Derives the srtcp session keys from the master key.
*
*/
public void deriveSrtcpKeys()
{
// compute the session encryption key
byte label = 3;
computeIv(label);
KeyParameter encryptionKey = new KeyParameter(masterKey);
cipher.init(true, encryptionKey);
Arrays.fill(masterKey, (byte)0);
cipherCtr.getCipherStream(cipher, encKey, policy.getEncKeyLength(), ivStore);
if (authKey != null)
{
label = 4;
computeIv(label);
cipherCtr.getCipherStream(cipher, authKey,
policy.getAuthKeyLength(), ivStore);
switch ((policy.getAuthType()))
{
case SRTPPolicy.HMACSHA1_AUTHENTICATION:
KeyParameter key = new KeyParameter(authKey);
mac.init(key);
break;
case SRTPPolicy.SKEIN_AUTHENTICATION:
// Skein MAC uses number of bits as MAC size, not just bytes
ParametersForSkein pfs = new ParametersForSkein(
new KeyParameter(authKey),
ParametersForSkein.Skein512, tagStore.length * 8);
mac.init(pfs);
break;
}
}
Arrays.fill(authKey, (byte)0);
// compute the session salt
label = 5;
computeIv(label);
cipherCtr.getCipherStream(cipher, saltKey, policy.getSaltKeyLength(), ivStore);
Arrays.fill(masterSalt, (byte)0);
// As last step: initialize cipher with derived encryption key.
if (cipherF8 != null)
SRTPCipherF8.deriveForIV(cipherF8, encKey, saltKey);
encryptionKey = new KeyParameter(encKey);
cipher.init(true, encryptionKey);
Arrays.fill(encKey, (byte)0);
}
/**
* Update the SRTP packet index.
*
* This method is called after all checks were successful.
*
* @param index index number of the accepted packet
*/
private void update(int index)
{
int delta = receivedIndex - index;
/* update the replay bit mask */
if (delta > 0)
{
replayWindow = replayWindow << delta;
replayWindow |= 1;
}
else
{
replayWindow |= ( 1 << delta );
}
receivedIndex = index;
}
/**
* 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
* @return a new SRTPCryptoContext with all relevant data set.
*/
public SRTCPCryptoContext deriveContext(long ssrc)
{
SRTCPCryptoContext pcc = null;
pcc = new SRTCPCryptoContext(ssrc, masterKey,
masterSalt, policy);
return pcc;
}
}