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 1 commit
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
44 changes: 44 additions & 0 deletions src/main/java/org/jitsi/srtp/SrtpCryptoContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,46 @@ private void cryptexPostprocess(ByteArrayBuffer pkt)
ByteArrayUtils.writeInt(pkt, headerOffset, headerTypeAndLen);
}

/** Query whether this packet needs a zero-length header inserted. This is used for packets that have
* CSRCs but no header extensions of their own, when we are using cryptex.
*/
private boolean needZeroLengthHeader(ByteArrayBuffer pkt)
{
return policy.isCryptexEnabled() && !SrtpPacketUtils.getExtensionBit(pkt) &&
SrtpPacketUtils.getCsrcCount(pkt) > 0;
}

/** A packet that has CSRCs but no header extension, when we are using cryptex.
* Insert a zero-length header extension so we can correctly signal that cryptex was used.
* Assumes needZeroLengthHeader returned true.
*/
private void insertZeroLengthHeader(ByteArrayBuffer pkt)
{
int cc = SrtpPacketUtils.getCsrcCount(pkt);
int headerOffset = SrtpPacketUtils.FIXED_HEADER_SIZE + cc * 4;

if (pkt.getOffset() >= 4)
{
/* Move fixed header and CSRCs back. */
System.arraycopy(pkt.getBuffer(), pkt.getOffset(), pkt.getBuffer(), pkt.getOffset() - 4,
headerOffset);
pkt.setOffset(pkt.getOffset() - 4);
}
else
{
/* Move payload forward. */
if (pkt.getBuffer().length < pkt.getOffset() + pkt.getLength() + 4) {
/* Need more buffer. */
bgrozev marked this conversation as resolved.
Show resolved Hide resolved
pkt.grow(4);
}
System.arraycopy(pkt.getBuffer(), pkt.getOffset() + headerOffset, pkt.getBuffer(),
pkt.getOffset() + headerOffset + 4, pkt.getLength() - headerOffset);
}
pkt.setLength(pkt.getLength() + 4);
ByteArrayUtils.writeInt(pkt, headerOffset, 0xBEDE0000);
SrtpPacketUtils.setExtensionBit(pkt);
}

/**
* Performs Counter Mode AES encryption/decryption
*
Expand Down Expand Up @@ -832,6 +872,10 @@ synchronized public SrtpErrorStatus transformPacket(ByteArrayBuffer pkt)
if (policy.isSendReplayEnabled() && (err = checkReplay(seqNo, guessedIndex)) != SrtpErrorStatus.OK)
return err;

if (needZeroLengthHeader(pkt)) {
insertZeroLengthHeader(pkt);
}

boolean useCryptex = useCryptex(pkt, true);

if (useCryptex)
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/org/jitsi/srtp/utils/SrtpPacketUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ public static boolean getExtensionBit(ByteArrayBuffer buf)
return (buffer[offset] & 0x10) == 0x10;
}

/**
* Sets the extension bit of an SRTP packet.
* @param buf The SRTP packet.
*/
public static void setExtensionBit(ByteArrayBuffer buf)
{
byte[] buffer = buf.getBuffer();
int offset = buf.getOffset();

buffer[offset] |= 0x10;
}

/**
* Returns the number of CSRC identifiers included in an SRTP packet.
*
Expand Down
41 changes: 36 additions & 5 deletions src/test/java/org/jitsi/srtp/SrtpValidationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static org.junit.jupiter.api.Assertions.*;
import static org.jitsi.srtp.Assertions.*;

import jakarta.xml.bind.*;
import org.jitsi.utils.*;
import org.jitsi.utils.logging2.*;
import org.junit.jupiter.api.*;
Expand Down Expand Up @@ -301,6 +302,7 @@ public void srtpValidateGcm() throws Exception
ByteArrayBuffer rtpPkt = new ByteArrayBufferImpl(rtp_plaintext_gcm, 0, rtp_plaintext_ref.length);

assertEquals(SrtpErrorStatus.OK, rtpSend.transformPacket(rtpPkt));
assertByteArrayBufferEquals(srtp_ciphertext_gcm, rtpPkt);
assertEquals(srtp_ciphertext_gcm.length, rtpPkt.getLength());
assertArrayEquals(srtp_ciphertext_gcm, rtpPkt.getBuffer());

Expand Down Expand Up @@ -574,6 +576,17 @@ public void skipDecryptionGcm() throws Exception
+ "f4d0ae92"
+ "3c6f479b95a0f7b53133");

/* Plaintext packet with no header extension and CSRC fields. */
private static final byte[] rtp_nohdr_cc =
parseHexBinary("820f123adecafbad" +
"cafebabe" +
"0001e240" +
"0000b26e" +
"abababab" +
"abababababababab" +
"abababab");


/* Plaintext packet with empty 1-byte header extension and CSRC fields. */
private static final byte[] rtp_1byte_empty_hdrext_cc =
parseHexBinary("920f123adecafbad" +
Expand Down Expand Up @@ -651,14 +664,14 @@ private static void testPacket(SrtpCryptoContext rtpSend, SrtpCryptoContext rtpR


/* Test a single packet, when the reconstructed plaintext is not identical to the original encrypted plaintext. */
private static void testPacketAsymmetric(SrtpCryptoContext rtpSend, SrtpCryptoContext rtpRecv,
private static void testPacketAsymmetricOnce(SrtpCryptoContext rtpSend, SrtpCryptoContext rtpRecv,
byte[] plaintextOrig, byte[] ciphertext, byte[] plainTextDecrypted,
int extraBufSpace)
int extraBufSpace, int offset)
throws Exception
{
ByteArrayBuffer pkt = new ByteArrayBufferImpl(Arrays.copyOf(plaintextOrig,
plaintextOrig.length + extraBufSpace),
0, plaintextOrig.length);
byte[] data = new byte[offset + plaintextOrig.length + extraBufSpace];
System.arraycopy(plaintextOrig, 0, data, offset, plaintextOrig.length);
ByteArrayBuffer pkt = new ByteArrayBufferImpl(data, offset, plaintextOrig.length);

assertEquals(SrtpErrorStatus.OK, rtpSend.transformPacket(pkt));
// Uncomment this to generate or debug ciphertext
Expand All @@ -669,6 +682,20 @@ private static void testPacketAsymmetric(SrtpCryptoContext rtpSend, SrtpCryptoCo
assertByteArrayBufferEquals(plainTextDecrypted, pkt);
}

private static void testPacketAsymmetric(SrtpCryptoContext rtpSend, SrtpCryptoContext rtpRecv,
byte[] plaintextOrig, byte[] ciphertext, byte[] plainTextDecrypted,
int authTagLen)
throws Exception
{
for (int offset = 0; offset < 16; offset++) {
testPacketAsymmetricOnce(rtpSend, rtpRecv, plaintextOrig, ciphertext, plainTextDecrypted, authTagLen, offset);
}
for (int offset = 0; offset < 16; offset++) {
testPacketAsymmetricOnce(rtpSend, rtpRecv, plaintextOrig, ciphertext, plainTextDecrypted, authTagLen, offset + 4);
}
}


/* Test that a packet authenticates and decrypts when unmodified, and
* fails to decrypt when truncated or bit-flipped.
*/
Expand Down Expand Up @@ -736,6 +763,8 @@ public void testCryptexCtrHmac() throws Exception

testPacket(rtpSend, rtpRecv, rtp_1byte_empty_hdrext_cc, srtp_1byte_empty_hdrext_cc_cryptex, policy.getAuthTagLength());
testPacket(rtpSend, rtpRecv, rtp_2byte_empty_hdrext_cc, srtp_2byte_empty_hdrext_cc_cryptex, policy.getAuthTagLength());

testPacketAsymmetric(rtpSend, rtpRecv, rtp_nohdr_cc, srtp_1byte_empty_hdrext_cc_cryptex, rtp_1byte_empty_hdrext_cc, policy.getAuthTagLength() + 4);
}

@Test
Expand Down Expand Up @@ -877,6 +906,8 @@ public void testCryptexGcm() throws Exception

testPacket(rtpSend, rtpRecv, rtp_1byte_empty_hdrext_cc, srtp_1byte_empty_hdrext_cc_cryptex_gcm, policy.getAuthTagLength());
testPacket(rtpSend, rtpRecv, rtp_2byte_empty_hdrext_cc, srtp_2byte_empty_hdrext_cc_cryptex_gcm, policy.getAuthTagLength());

testPacketAsymmetric(rtpSend, rtpRecv, rtp_nohdr_cc, srtp_1byte_empty_hdrext_cc_cryptex_gcm, rtp_1byte_empty_hdrext_cc, policy.getAuthTagLength() + 4);
}

@Test
Expand Down