Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Cryptex header extension encryption #29

Merged
merged 16 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 199 additions & 25 deletions src/main/java/org/jitsi/srtp/SrtpCryptoContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -330,12 +330,130 @@ private long guessIndex(int seqNo)
return (((long) guessedROC) << 16) | seqNo;
}

/**
* Determine if this packet should be processed with "cryptex"
* (header extension encryption)
*/
private boolean useCryptex(ByteArrayBuffer pkt, boolean encrypting)
{
if (!SrtpPacketUtils.getExtensionBit(pkt))
{
return false;
bgrozev marked this conversation as resolved.
Show resolved Hide resolved
}

int type = SrtpPacketUtils.getExtensionType(pkt);
if (encrypting)
{
if (!policy.isCryptexEnabled())
{
return false;
}

switch (type)
{
case 0xBEDE:
case 0x1000:
return true;
default:
return false;
}
}
else
{
switch (type)
{
case 0xC0DE:
case 0xC2DE:
return true;
default:
return false;
}
}
}

/**
* Given an input header type, return the value as transformed
* by cryptex processing. Assumes useCryptex() returns true for this packet.
*/
private int getTransformedHeaderType(int type)
{
switch (type)
{
case 0xBEDE:
return 0xC0DE;
case 0x1000:
return 0xC2DE;
case 0xC0DE:
return 0xBEDE;
case 0xC2DE:
return 0x1000;
default:
/* Can't happen if useCryptex returned true */
throw new IllegalStateException(String.format("Invalid header type 0x%4X", type));
}
}

/**
* Transform a packet's "defined by profile" header extension field for cryptex.
* This should be a packet for which useCryptex() has returned trye.
bgrozev marked this conversation as resolved.
Show resolved Hide resolved
*/
private void transformCryptexType(ByteArrayBuffer pkt)
{
int type = SrtpPacketUtils.getExtensionType(pkt);
int newType = getTransformedHeaderType(type);
SrtpPacketUtils.setExtensionType(pkt, newType);
}

/**
* Pre-process a packet so it is ready for cryptex encryption.
bgrozev marked this conversation as resolved.
Show resolved Hide resolved
* Assumes useCryptex() returned true for this packet.
*/
private void cryptexPreprocess(ByteArrayBuffer pkt)
{
int cc = SrtpPacketUtils.getCsrcCount(pkt);
if (cc == 0)
{
return;
}
int headerOffset = SrtpPacketUtils.FIXED_HEADER_SIZE + cc * 4;

int headerTypeAndLen = ByteArrayUtils.readInt(pkt, headerOffset);

/* Move CSRCs to be contiguous with the payload. */
System.arraycopy(pkt.getBuffer(), SrtpPacketUtils.FIXED_HEADER_SIZE,
pkt.getBuffer(), SrtpPacketUtils.FIXED_HEADER_SIZE + 4, cc * 4);

ByteArrayUtils.writeInt(pkt, SrtpPacketUtils.FIXED_HEADER_SIZE, headerTypeAndLen);
}

/**
* Post-process a packet after cryptex encryption, to restore it to wire format.
* Assumes cryptexPreprocess() was called on this packet.
*/
private void cryptexPostprocess(ByteArrayBuffer pkt)
{
int cc = SrtpPacketUtils.getCsrcCount(pkt);
if (cc == 0)
{
return;
}
int headerOffset = SrtpPacketUtils.FIXED_HEADER_SIZE + cc * 4;

int headerTypeAndLen = ByteArrayUtils.readInt(pkt, SrtpPacketUtils.FIXED_HEADER_SIZE);

/* Move CSRCs to be after the fixed header. */
System.arraycopy(pkt.getBuffer(), SrtpPacketUtils.FIXED_HEADER_SIZE + 4,
pkt.getBuffer(), SrtpPacketUtils.FIXED_HEADER_SIZE, cc * 4);

ByteArrayUtils.writeInt(pkt, headerOffset, headerTypeAndLen);
}

/**
* Performs Counter Mode AES encryption/decryption
*
* @param pkt the RTP packet to be encrypted/decrypted
*/
private void processPacketAesCm(ByteArrayBuffer pkt)
private void processPacketAesCm(ByteArrayBuffer pkt, boolean useCryptex)
throws GeneralSecurityException
{
int ssrc = SrtpPacketUtils.getSsrc(pkt);
Expand Down Expand Up @@ -372,18 +490,32 @@ private void processPacketAesCm(ByteArrayBuffer pkt)

ivStore[14] = ivStore[15] = 0;

int rtpHeaderLength = SrtpPacketUtils.getTotalHeaderLength(pkt);

cipher.setIV(ivStore, Cipher.ENCRYPT_MODE);

int encOffset;
if (useCryptex)
{
cryptexPreprocess(pkt);
encOffset = SrtpPacketUtils.FIXED_HEADER_SIZE + 4;
}
else
{
encOffset = SrtpPacketUtils.getTotalHeaderLength(pkt);
}

cipher.process(
pkt.getBuffer(),
pkt.getOffset() + rtpHeaderLength,
pkt.getLength() - rtpHeaderLength);
pkt.getOffset() + encOffset,
pkt.getLength() - encOffset);

if (useCryptex)
{
cryptexPostprocess(pkt);
}
}

private SrtpErrorStatus processPacketAesGcm(ByteArrayBuffer pkt, boolean encrypting,
boolean skipDecryption)
boolean useCryptex, boolean skipDecryption)
{
int ssrc = SrtpPacketUtils.getSsrc(pkt);
int seqNo = SrtpPacketUtils.getSequenceNumber(pkt);
Expand Down Expand Up @@ -431,23 +563,37 @@ private SrtpErrorStatus processPacketAesGcm(ByteArrayBuffer pkt, boolean encrypt
);
}

int rtpHeaderLength = SrtpPacketUtils.getTotalHeaderLength(pkt);

try
{
SrtpCipher cipher = skipDecryption ? cipherAuthOnly : this.cipher;

cipher.setIV(ivStore, encrypting ? Cipher.ENCRYPT_MODE :
Cipher.DECRYPT_MODE);

cipher.processAAD(pkt.getBuffer(), pkt.getOffset(), rtpHeaderLength);
int encOffset;
if (useCryptex)
{
cryptexPreprocess(pkt);
encOffset = SrtpPacketUtils.FIXED_HEADER_SIZE + 4;
}
else
{
encOffset = SrtpPacketUtils.getTotalHeaderLength(pkt);
}

cipher.processAAD(pkt.getBuffer(), pkt.getOffset(), encOffset);

int processLen = cipher.process(
pkt.getBuffer(),
pkt.getOffset() + rtpHeaderLength,
pkt.getLength() - rtpHeaderLength);
pkt.getOffset() + encOffset,
pkt.getLength() - encOffset);

pkt.setLength(processLen + encOffset);

pkt.setLength(processLen + rtpHeaderLength);
if (useCryptex)
{
cryptexPostprocess(pkt);
}
}
catch (GeneralSecurityException e)
{
Expand All @@ -474,10 +620,10 @@ private SrtpErrorStatus processPacketAesGcm(ByteArrayBuffer pkt, boolean encrypt

/**
* Performs F8 Mode AES encryption/decryption
* @param pkt the RTP packet to be encrypted/decrypted
*
* @param pkt the RTP packet to be encrypted/decrypted
*/
private void processPacketAesF8(ByteArrayBuffer pkt)
private void processPacketAesF8(ByteArrayBuffer pkt, boolean useCryptex)
throws GeneralSecurityException
{
// 11 bytes of the RTP header are the 11 bytes of the iv
Expand All @@ -493,14 +639,28 @@ private void processPacketAesF8(ByteArrayBuffer pkt)
ivStore[14] = (byte) (roc >> 8);
ivStore[15] = (byte) roc;

int rtpHeaderLength = SrtpPacketUtils.getTotalHeaderLength(pkt);

cipher.setIV(ivStore, Cipher.ENCRYPT_MODE);

int encOffset;
if (useCryptex)
{
cryptexPreprocess(pkt);
encOffset = SrtpPacketUtils.FIXED_HEADER_SIZE + 4;
}
else
{
encOffset = SrtpPacketUtils.getTotalHeaderLength(pkt);
}

cipher.process(
pkt.getBuffer(),
pkt.getOffset() + rtpHeaderLength,
pkt.getLength() - rtpHeaderLength);
pkt.getBuffer(),
pkt.getOffset() + encOffset,
pkt.getLength() - encOffset);

if (useCryptex)
{
cryptexPostprocess(pkt);
}
}

/**
Expand Down Expand Up @@ -557,6 +717,8 @@ synchronized public SrtpErrorStatus reverseTransformPacket(ByteArrayBuffer pkt,
long guessedIndex = guessIndex(seqNo);
SrtpErrorStatus ret, err;

boolean useCryptex = useCryptex(pkt, false);

// Replay control
if (policy.isReceiveReplayDisabled() || ((err = checkReplay(seqNo, guessedIndex)) == SrtpErrorStatus.OK))
{
Expand All @@ -570,17 +732,17 @@ synchronized public SrtpErrorStatus reverseTransformPacket(ByteArrayBuffer pkt,
// Decrypt the packet using Counter Mode encryption.
case SrtpPolicy.AESCM_ENCRYPTION:
case SrtpPolicy.TWOFISH_ENCRYPTION:
processPacketAesCm(pkt);
processPacketAesCm(pkt, useCryptex);
break;

case SrtpPolicy.AESGCM_ENCRYPTION:
err = processPacketAesGcm(pkt, false, skipDecryption);
err = processPacketAesGcm(pkt, false, useCryptex, skipDecryption);
break;

// Decrypt the packet using F8 Mode encryption.
case SrtpPolicy.AESF8_ENCRYPTION:
case SrtpPolicy.TWOFISHF8_ENCRYPTION:
processPacketAesF8(pkt);
processPacketAesF8(pkt, useCryptex);
break;
}
}
Expand Down Expand Up @@ -609,6 +771,11 @@ synchronized public SrtpErrorStatus reverseTransformPacket(ByteArrayBuffer pkt,
ret = err;
}

if (ret == SrtpErrorStatus.OK && useCryptex)
{
transformCryptexType(pkt);
}

if (ret != SrtpErrorStatus.OK && seqNumWasJustSet)
{
// We set the initial value of s_l as a result of processing this
Expand Down Expand Up @@ -665,22 +832,29 @@ synchronized public SrtpErrorStatus transformPacket(ByteArrayBuffer pkt)
if (policy.isSendReplayEnabled() && (err = checkReplay(seqNo, guessedIndex)) != SrtpErrorStatus.OK)
return err;

boolean useCryptex = useCryptex(pkt, true);

if (useCryptex)
{
transformCryptexType(pkt);
}

switch (policy.getEncType())
{
// Encrypt the packet using Counter Mode encryption.
case SrtpPolicy.AESCM_ENCRYPTION:
case SrtpPolicy.TWOFISH_ENCRYPTION:
processPacketAesCm(pkt);
processPacketAesCm(pkt, useCryptex);
break;

case SrtpPolicy.AESGCM_ENCRYPTION:
processPacketAesGcm(pkt, true, false);
processPacketAesGcm(pkt, true, useCryptex, false);
break;

// Encrypt the packet using F8 Mode encryption.
case SrtpPolicy.AESF8_ENCRYPTION:
case SrtpPolicy.TWOFISHF8_ENCRYPTION:
processPacketAesF8(pkt);
processPacketAesF8(pkt, true);
break;
}

Expand Down
45 changes: 45 additions & 0 deletions src/main/java/org/jitsi/srtp/SrtpPolicy.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ public class SrtpPolicy
*/
private boolean receiveReplayEnabled = true;

/**
* Whether cryptex (header extension encryption) is enabled
* Note that receiving cryptex is always supported; this only configures
* whether it will be sent.
*/
private boolean cryptexEnabled = false;

/**
* Construct a SrtpPolicy object based on given parameters.
* This class acts as a storage class, so all the parameters are passed in
Expand Down Expand Up @@ -327,4 +334,42 @@ public boolean isReceiveReplayDisabled()
{
return !receiveReplayEnabled;
}

/**
* Set whether cryptex (header extension encryption) is to be enabled,
* as defined in draft-ietf-avtcore-cryptex-08.
* <p>
* Turn this off if you want to send header extensions in the clear.
* Note that decryption of encrypted header extensions (based on the
* appropriate values of the "defined by profile" field) is always supported.
*
* @param enabled {@code true} if sending encrypted header extensions is to be
* enabled; {@code false} if not.
*/
public void setCryptexEnabled(boolean enabled)
{
cryptexEnabled = enabled;
}

/**
* Get whether cryptex (header extension encryption) is enabled,
* as defined in draft-ietf-avtcore-cryptex-08.
*
* @see #isCryptexDisabled
*/
public boolean isCryptexEnabled()
{
return cryptexEnabled;
}

/**
* Get whether cryptex (header extension encryption) is disabled,
* as defined in draft-ietf-avtcore-cryptex-08.
*
* @see #isCryptexEnabled
*/
public boolean isCryptexDisabled()
{
return !cryptexEnabled;
}
}
Loading