Implement stream header for BTPv2. #111

This commit is contained in:
akwizgran
2015-12-14 13:29:28 +00:00
parent 766179ac97
commit f2efe7f2ea
9 changed files with 591 additions and 233 deletions

View File

@@ -1,25 +1,35 @@
package org.briarproject.api.transport; package org.briarproject.api.transport;
import org.briarproject.api.crypto.SecretKey;
public interface TransportConstants { public interface TransportConstants {
/** The length of the pseudo-random tag in bytes. */ /** The length of the pseudo-random tag in bytes. */
int TAG_LENGTH = 16; int TAG_LENGTH = 16;
/** The maximum length of a frame in bytes, including the header and MAC. */ /** The length of the stream header IV in bytes. */
int MAX_FRAME_LENGTH = 1024; int STREAM_HEADER_IV_LENGTH = 12;
/** The length of the message authentication code (MAC) in bytes. */ /** The length of the message authentication code (MAC) in bytes. */
int MAC_LENGTH = 16; int MAC_LENGTH = 16;
/** The length of the stream header in bytes. */
int STREAM_HEADER_LENGTH = STREAM_HEADER_IV_LENGTH + SecretKey.LENGTH
+ MAC_LENGTH;
/** The length of the frame initalisation vector (IV) in bytes. */
int FRAME_IV_LENGTH = 12;
/** The length of the frame header in bytes. */ /** The length of the frame header in bytes. */
int HEADER_LENGTH = 4 + MAC_LENGTH; int FRAME_HEADER_LENGTH = 4 + MAC_LENGTH;
/** The maximum length of a frame in bytes, including the header and MAC. */
int MAX_FRAME_LENGTH = 1024;
/** The maximum total length of the frame payload and padding in bytes. */ /** The maximum total length of the frame payload and padding in bytes. */
int MAX_PAYLOAD_LENGTH = MAX_FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH; int MAX_PAYLOAD_LENGTH = MAX_FRAME_LENGTH - FRAME_HEADER_LENGTH
- MAC_LENGTH;
/** The length of the initalisation vector (IV) in bytes. */
int IV_LENGTH = 12;
/** /**
* The minimum stream length in bytes that all transport plugins must * The minimum stream length in bytes that all transport plugins must

View File

@@ -1,27 +1,28 @@
package org.briarproject.crypto; package org.briarproject.crypto;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH; import org.briarproject.util.ByteUtils;
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.FRAME_IV_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED; import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import org.briarproject.util.ByteUtils;
class FrameEncoder { class FrameEncoder {
static void encodeIv(byte[] iv, long frameNumber, boolean header) { static void encodeIv(byte[] iv, long frameNumber, boolean header) {
if (iv.length < IV_LENGTH) throw new IllegalArgumentException(); if (iv.length < FRAME_IV_LENGTH) throw new IllegalArgumentException();
if (frameNumber < 0 || frameNumber > MAX_32_BIT_UNSIGNED) if (frameNumber < 0 || frameNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
ByteUtils.writeUint32(frameNumber, iv, 0); ByteUtils.writeUint32(frameNumber, iv, 0);
if (header) iv[4] = 1; if (header) iv[4] = 1;
else iv[4] = 0; else iv[4] = 0;
for (int i = 5; i < IV_LENGTH; i++) iv[i] = 0; for (int i = 5; i < FRAME_IV_LENGTH; i++) iv[i] = 0;
} }
static void encodeHeader(byte[] header, boolean finalFrame, static void encodeHeader(byte[] header, boolean finalFrame,
int payloadLength, int paddingLength) { int payloadLength, int paddingLength) {
if (header.length < HEADER_LENGTH) throw new IllegalArgumentException(); if (header.length < FRAME_HEADER_LENGTH)
throw new IllegalArgumentException();
if (payloadLength < 0) throw new IllegalArgumentException(); if (payloadLength < 0) throw new IllegalArgumentException();
if (paddingLength < 0) throw new IllegalArgumentException(); if (paddingLength < 0) throw new IllegalArgumentException();
if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH) if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
@@ -32,17 +33,20 @@ class FrameEncoder {
} }
static boolean isFinalFrame(byte[] header) { static boolean isFinalFrame(byte[] header) {
if (header.length < HEADER_LENGTH) throw new IllegalArgumentException(); if (header.length < FRAME_HEADER_LENGTH)
throw new IllegalArgumentException();
return (header[0] & 0x80) == 0x80; return (header[0] & 0x80) == 0x80;
} }
static int getPayloadLength(byte[] header) { static int getPayloadLength(byte[] header) {
if (header.length < HEADER_LENGTH) throw new IllegalArgumentException(); if (header.length < FRAME_HEADER_LENGTH)
throw new IllegalArgumentException();
return ByteUtils.readUint16(header, 0) & 0x7FFF; return ByteUtils.readUint16(header, 0) & 0x7FFF;
} }
static int getPaddingLength(byte[] header) { static int getPaddingLength(byte[] header) {
if (header.length < HEADER_LENGTH) throw new IllegalArgumentException(); if (header.length < FRAME_HEADER_LENGTH)
throw new IllegalArgumentException();
return ByteUtils.readUint16(header, 2); return ByteUtils.readUint16(header, 2);
} }
} }

View File

@@ -9,31 +9,34 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH; import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH; import static org.briarproject.api.transport.TransportConstants.FRAME_IV_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
// FIXME: Implementation is incomplete, doesn't read the stream header
class StreamDecrypterImpl implements StreamDecrypter { class StreamDecrypterImpl implements StreamDecrypter {
private final InputStream in; private final InputStream in;
private final AuthenticatedCipher frameCipher; private final AuthenticatedCipher cipher;
private final SecretKey frameKey; private final SecretKey streamHeaderKey;
private final byte[] iv, frameHeader, frameCiphertext; private final byte[] frameIv, frameHeader, frameCiphertext;
private SecretKey frameKey;
private long frameNumber; private long frameNumber;
private boolean finalFrame; private boolean finalFrame;
StreamDecrypterImpl(InputStream in, AuthenticatedCipher frameCipher, StreamDecrypterImpl(InputStream in, AuthenticatedCipher cipher,
SecretKey headerKey) { SecretKey streamHeaderKey) {
this.in = in; this.in = in;
this.frameCipher = frameCipher; this.cipher = cipher;
this.frameKey = headerKey; // FIXME this.streamHeaderKey = streamHeaderKey;
iv = new byte[IV_LENGTH]; frameIv = new byte[FRAME_IV_LENGTH];
frameHeader = new byte[HEADER_LENGTH]; frameHeader = new byte[FRAME_HEADER_LENGTH];
frameCiphertext = new byte[MAX_FRAME_LENGTH]; frameCiphertext = new byte[MAX_FRAME_LENGTH];
frameKey = null;
frameNumber = 0; frameNumber = 0;
finalFrame = false; finalFrame = false;
} }
@@ -43,20 +46,23 @@ class StreamDecrypterImpl implements StreamDecrypter {
if (payload.length < MAX_PAYLOAD_LENGTH) if (payload.length < MAX_PAYLOAD_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if (finalFrame) return -1; if (finalFrame) return -1;
// Read the stream header if required
if (frameKey == null) readStreamHeader();
// Read the frame header // Read the frame header
int offset = 0; int offset = 0;
while (offset < HEADER_LENGTH) { while (offset < FRAME_HEADER_LENGTH) {
int read = in.read(frameCiphertext, offset, HEADER_LENGTH - offset); int read = in.read(frameCiphertext, offset,
FRAME_HEADER_LENGTH - offset);
if (read == -1) throw new EOFException(); if (read == -1) throw new EOFException();
offset += read; offset += read;
} }
// Decrypt and authenticate the frame header // Decrypt and authenticate the frame header
FrameEncoder.encodeIv(iv, frameNumber, true); FrameEncoder.encodeIv(frameIv, frameNumber, true);
try { try {
frameCipher.init(false, frameKey, iv); cipher.init(false, frameKey, frameIv);
int decrypted = frameCipher.process(frameCiphertext, 0, int decrypted = cipher.process(frameCiphertext, 0,
HEADER_LENGTH, frameHeader, 0); FRAME_HEADER_LENGTH, frameHeader, 0);
if (decrypted != HEADER_LENGTH - MAC_LENGTH) if (decrypted != FRAME_HEADER_LENGTH - MAC_LENGTH)
throw new RuntimeException(); throw new RuntimeException();
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
throw new FormatException(); throw new FormatException();
@@ -68,7 +74,7 @@ class StreamDecrypterImpl implements StreamDecrypter {
if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH) if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
throw new FormatException(); throw new FormatException();
// Read the payload and padding // Read the payload and padding
int frameLength = HEADER_LENGTH + payloadLength + paddingLength int frameLength = FRAME_HEADER_LENGTH + payloadLength + paddingLength
+ MAC_LENGTH; + MAC_LENGTH;
while (offset < frameLength) { while (offset < frameLength) {
int read = in.read(frameCiphertext, offset, frameLength - offset); int read = in.read(frameCiphertext, offset, frameLength - offset);
@@ -76,10 +82,10 @@ class StreamDecrypterImpl implements StreamDecrypter {
offset += read; offset += read;
} }
// Decrypt and authenticate the payload and padding // Decrypt and authenticate the payload and padding
FrameEncoder.encodeIv(iv, frameNumber, false); FrameEncoder.encodeIv(frameIv, frameNumber, false);
try { try {
frameCipher.init(false, frameKey, iv); cipher.init(false, frameKey, frameIv);
int decrypted = frameCipher.process(frameCiphertext, HEADER_LENGTH, int decrypted = cipher.process(frameCiphertext, FRAME_HEADER_LENGTH,
payloadLength + paddingLength + MAC_LENGTH, payload, 0); payloadLength + paddingLength + MAC_LENGTH, payload, 0);
if (decrypted != payloadLength + paddingLength) if (decrypted != payloadLength + paddingLength)
throw new RuntimeException(); throw new RuntimeException();
@@ -92,4 +98,31 @@ class StreamDecrypterImpl implements StreamDecrypter {
frameNumber++; frameNumber++;
return payloadLength; return payloadLength;
} }
private void readStreamHeader() throws IOException {
byte[] streamHeaderIv = new byte[STREAM_HEADER_IV_LENGTH];
byte[] streamHeaderCiphertext = new byte[STREAM_HEADER_LENGTH];
byte[] streamHeaderPlaintext = new byte[SecretKey.LENGTH];
// Read the stream header
int offset = 0;
while (offset < STREAM_HEADER_LENGTH) {
int read = in.read(streamHeaderCiphertext, offset,
STREAM_HEADER_LENGTH - offset);
if (read == -1) throw new EOFException();
offset += read;
}
// Decrypt and authenticate the stream header
System.arraycopy(streamHeaderCiphertext, 0, streamHeaderIv, 0,
STREAM_HEADER_IV_LENGTH);
try {
cipher.init(false, streamHeaderKey, streamHeaderIv);
int decrypted = cipher.process(streamHeaderCiphertext,
STREAM_HEADER_IV_LENGTH, SecretKey.LENGTH + MAC_LENGTH,
streamHeaderPlaintext, 0);
if (decrypted != SecretKey.LENGTH) throw new RuntimeException();
} catch (GeneralSecurityException e) {
throw new FormatException();
}
frameKey = new SecretKey(streamHeaderPlaintext);
}
} }

View File

@@ -1,18 +1,19 @@
package org.briarproject.crypto; package org.briarproject.crypto;
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import java.io.OutputStream;
import javax.inject.Inject;
import javax.inject.Provider;
import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.crypto.StreamEncrypter; import org.briarproject.api.crypto.StreamEncrypter;
import org.briarproject.api.crypto.StreamEncrypterFactory; import org.briarproject.api.crypto.StreamEncrypterFactory;
import org.briarproject.api.transport.StreamContext; import org.briarproject.api.transport.StreamContext;
import java.io.OutputStream;
import javax.inject.Inject;
import javax.inject.Provider;
import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
class StreamEncrypterFactoryImpl implements StreamEncrypterFactory { class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
private final CryptoComponent crypto; private final CryptoComponent crypto;
@@ -27,15 +28,23 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
public StreamEncrypter createStreamEncrypter(OutputStream out, public StreamEncrypter createStreamEncrypter(OutputStream out,
StreamContext ctx) { StreamContext ctx) {
AuthenticatedCipher cipher = cipherProvider.get();
byte[] tag = new byte[TAG_LENGTH]; byte[] tag = new byte[TAG_LENGTH];
crypto.encodeTag(tag, ctx.getTagKey(), ctx.getStreamNumber()); crypto.encodeTag(tag, ctx.getTagKey(), ctx.getStreamNumber());
AuthenticatedCipher cipher = cipherProvider.get(); byte[] streamHeaderIv = new byte[STREAM_HEADER_IV_LENGTH];
return new StreamEncrypterImpl(out, cipher, ctx.getHeaderKey(), tag); crypto.getSecureRandom().nextBytes(streamHeaderIv);
SecretKey frameKey = crypto.generateSecretKey();
return new StreamEncrypterImpl(out, cipher, tag, streamHeaderIv,
ctx.getHeaderKey(), frameKey);
} }
public StreamEncrypter createInvitationStreamEncrypter(OutputStream out, public StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
SecretKey headerKey) { SecretKey headerKey) {
AuthenticatedCipher cipher = cipherProvider.get(); AuthenticatedCipher cipher = cipherProvider.get();
return new StreamEncrypterImpl(out, cipher, headerKey, null); byte[] streamHeaderIv = new byte[STREAM_HEADER_IV_LENGTH];
crypto.getSecureRandom().nextBytes(streamHeaderIv);
SecretKey frameKey = crypto.generateSecretKey();
return new StreamEncrypterImpl(out, cipher, null, streamHeaderIv,
headerKey, frameKey);
} }
} }

View File

@@ -7,35 +7,41 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH; import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH; import static org.briarproject.api.transport.TransportConstants.FRAME_IV_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED; import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
// FIXME: Implementation is incomplete, doesn't write the stream header
class StreamEncrypterImpl implements StreamEncrypter { class StreamEncrypterImpl implements StreamEncrypter {
private final OutputStream out; private final OutputStream out;
private final AuthenticatedCipher frameCipher; private final AuthenticatedCipher cipher;
private final SecretKey frameKey; private final SecretKey streamHeaderKey, frameKey;
private final byte[] tag, iv, framePlaintext, frameCiphertext; private final byte[] tag, streamHeaderIv;
private final byte[] frameIv, framePlaintext, frameCiphertext;
private long frameNumber; private long frameNumber;
private boolean writeTag; private boolean writeTag, writeStreamHeader;
StreamEncrypterImpl(OutputStream out, AuthenticatedCipher frameCipher, StreamEncrypterImpl(OutputStream out, AuthenticatedCipher cipher,
SecretKey headerKey, byte[] tag) { byte[] tag, byte[] streamHeaderIv, SecretKey streamHeaderKey,
SecretKey frameKey) {
this.out = out; this.out = out;
this.frameCipher = frameCipher; this.cipher = cipher;
this.frameKey = headerKey; // FIXME
this.tag = tag; this.tag = tag;
iv = new byte[IV_LENGTH]; this.streamHeaderIv = streamHeaderIv;
framePlaintext = new byte[HEADER_LENGTH + MAX_PAYLOAD_LENGTH]; this.streamHeaderKey = streamHeaderKey;
this.frameKey = frameKey;
frameIv = new byte[FRAME_IV_LENGTH];
framePlaintext = new byte[FRAME_HEADER_LENGTH + MAX_PAYLOAD_LENGTH];
frameCiphertext = new byte[MAX_FRAME_LENGTH]; frameCiphertext = new byte[MAX_FRAME_LENGTH];
frameNumber = 0; frameNumber = 0;
writeTag = (tag != null); writeTag = (tag != null);
writeStreamHeader = true;
} }
public void writeFrame(byte[] payload, int payloadLength, public void writeFrame(byte[] payload, int payloadLength,
@@ -45,52 +51,75 @@ class StreamEncrypterImpl implements StreamEncrypter {
// Don't allow the frame counter to wrap // Don't allow the frame counter to wrap
if (frameNumber > MAX_32_BIT_UNSIGNED) throw new IOException(); if (frameNumber > MAX_32_BIT_UNSIGNED) throw new IOException();
// Write the tag if required // Write the tag if required
if (writeTag) { if (writeTag) writeTag();
out.write(tag, 0, tag.length); // Write the stream header if required
writeTag = false; if (writeStreamHeader) writeStreamHeader();
}
// Encode the frame header // Encode the frame header
FrameEncoder.encodeHeader(framePlaintext, finalFrame, payloadLength, FrameEncoder.encodeHeader(framePlaintext, finalFrame, payloadLength,
paddingLength); paddingLength);
// Encrypt and authenticate the frame header // Encrypt and authenticate the frame header
FrameEncoder.encodeIv(iv, frameNumber, true); FrameEncoder.encodeIv(frameIv, frameNumber, true);
try { try {
frameCipher.init(true, frameKey, iv); cipher.init(true, frameKey, frameIv);
int encrypted = frameCipher.process(framePlaintext, 0, int encrypted = cipher.process(framePlaintext, 0,
HEADER_LENGTH - MAC_LENGTH, frameCiphertext, 0); FRAME_HEADER_LENGTH - MAC_LENGTH, frameCiphertext, 0);
if (encrypted != HEADER_LENGTH) throw new RuntimeException(); if (encrypted != FRAME_HEADER_LENGTH) throw new RuntimeException();
} catch (GeneralSecurityException badCipher) { } catch (GeneralSecurityException badCipher) {
throw new RuntimeException(badCipher); throw new RuntimeException(badCipher);
} }
// Combine the payload and padding // Combine the payload and padding
System.arraycopy(payload, 0, framePlaintext, HEADER_LENGTH, System.arraycopy(payload, 0, framePlaintext, FRAME_HEADER_LENGTH,
payloadLength); payloadLength);
for (int i = 0; i < paddingLength; i++) for (int i = 0; i < paddingLength; i++)
framePlaintext[HEADER_LENGTH + payloadLength + i] = 0; framePlaintext[FRAME_HEADER_LENGTH + payloadLength + i] = 0;
// Encrypt and authenticate the payload and padding // Encrypt and authenticate the payload and padding
FrameEncoder.encodeIv(iv, frameNumber, false); FrameEncoder.encodeIv(frameIv, frameNumber, false);
try { try {
frameCipher.init(true, frameKey, iv); cipher.init(true, frameKey, frameIv);
int encrypted = frameCipher.process(framePlaintext, HEADER_LENGTH, int encrypted = cipher.process(framePlaintext, FRAME_HEADER_LENGTH,
payloadLength + paddingLength, frameCiphertext, payloadLength + paddingLength, frameCiphertext,
HEADER_LENGTH); FRAME_HEADER_LENGTH);
if (encrypted != payloadLength + paddingLength + MAC_LENGTH) if (encrypted != payloadLength + paddingLength + MAC_LENGTH)
throw new RuntimeException(); throw new RuntimeException();
} catch (GeneralSecurityException badCipher) { } catch (GeneralSecurityException badCipher) {
throw new RuntimeException(badCipher); throw new RuntimeException(badCipher);
} }
// Write the frame // Write the frame
out.write(frameCiphertext, 0, HEADER_LENGTH + payloadLength out.write(frameCiphertext, 0, FRAME_HEADER_LENGTH + payloadLength
+ paddingLength + MAC_LENGTH); + paddingLength + MAC_LENGTH);
frameNumber++; frameNumber++;
} }
private void writeTag() throws IOException {
out.write(tag, 0, tag.length);
writeTag = false;
}
private void writeStreamHeader() throws IOException {
byte[] streamHeaderPlaintext = frameKey.getBytes();
byte[] streamHeaderCiphertext = new byte[STREAM_HEADER_LENGTH];
System.arraycopy(streamHeaderIv, 0, streamHeaderCiphertext, 0,
STREAM_HEADER_IV_LENGTH);
// Encrypt and authenticate the frame key
try {
cipher.init(true, streamHeaderKey, streamHeaderIv);
int encrypted = cipher.process(streamHeaderPlaintext, 0,
SecretKey.LENGTH, streamHeaderCiphertext,
STREAM_HEADER_IV_LENGTH);
if (encrypted != SecretKey.LENGTH + MAC_LENGTH)
throw new RuntimeException();
} catch (GeneralSecurityException badCipher) {
throw new RuntimeException(badCipher);
}
out.write(streamHeaderCiphertext);
writeStreamHeader = false;
}
public void flush() throws IOException { public void flush() throws IOException {
// Write the tag if required // Write the tag if required
if (writeTag) { if (writeTag) writeTag();
out.write(tag, 0, tag.length); // Write the stream header if required
writeTag = false; if (writeStreamHeader) writeStreamHeader();
}
out.flush(); out.flush();
} }
} }

View File

@@ -1,37 +1,226 @@
package org.briarproject.crypto; package org.briarproject.crypto;
import org.briarproject.BriarTestCase; import org.briarproject.BriarTestCase;
import org.briarproject.TestUtils;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.util.ByteUtils;
import org.junit.Test; import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Random;
import static junit.framework.Assert.assertEquals;
import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.fail;
public class StreamDecrypterImplTest extends BriarTestCase { public class StreamDecrypterImplTest extends BriarTestCase {
private final AuthenticatedCipher cipher;
private final SecretKey streamHeaderKey, frameKey;
private final byte[] streamHeaderIv;
private final Random random;
public StreamDecrypterImplTest() {
cipher = new TestAuthenticatedCipher(); // Null cipher
streamHeaderKey = TestUtils.createSecretKey();
frameKey = TestUtils.createSecretKey();
streamHeaderIv = new byte[STREAM_HEADER_IV_LENGTH];
random = new Random();
random.nextBytes(streamHeaderIv);
}
@Test @Test
public void testReadValidFrames() throws Exception { public void testReadValidFrames() throws Exception {
// FIXME byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
int payloadLength = 123, paddingLength = 234;
FrameEncoder.encodeHeader(frameHeader, false, payloadLength,
paddingLength);
byte[] payload = new byte[payloadLength];
random.nextBytes(payload);
byte[] frameHeader1 = new byte[FRAME_HEADER_LENGTH];
int payloadLength1 = 345, paddingLength1 = 456;
FrameEncoder.encodeHeader(frameHeader1, true, payloadLength1,
paddingLength1);
byte[] payload1 = new byte[payloadLength1];
random.nextBytes(payload1);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(streamHeaderIv);
out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);
out.write(payload);
out.write(new byte[paddingLength]);
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader1);
out.write(payload1);
out.write(new byte[paddingLength1]);
out.write(new byte[MAC_LENGTH]);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
StreamDecrypterImpl s = new StreamDecrypterImpl(in, cipher,
streamHeaderKey);
// Read the first frame
byte[] buffer = new byte[MAX_PAYLOAD_LENGTH];
assertEquals(payloadLength, s.readFrame(buffer));
assertArrayStartsWith(payload, buffer, payloadLength);
// Read the second frame
assertEquals(payloadLength1, s.readFrame(buffer));
assertArrayStartsWith(payload1, buffer, payloadLength1);
// End of stream
assertEquals(-1, s.readFrame(buffer));
} }
@Test @Test
public void testTruncatedFrameThrowsException() throws Exception { public void testTruncatedFrameThrowsException() throws Exception {
// FIXME byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
int payloadLength = 123, paddingLength = 234;
FrameEncoder.encodeHeader(frameHeader, false, payloadLength,
paddingLength);
byte[] payload = new byte[payloadLength];
random.nextBytes(payload);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(streamHeaderIv);
out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);
out.write(payload);
out.write(new byte[paddingLength]);
out.write(new byte[MAC_LENGTH - 1]); // Chop off the last byte
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
StreamDecrypterImpl s = new StreamDecrypterImpl(in, cipher,
streamHeaderKey);
// Try to read the truncated frame
byte[] buffer = new byte[MAX_PAYLOAD_LENGTH];
try {
s.readFrame(buffer);
fail();
} catch (IOException expected) {
// Expected
}
} }
@Test @Test
public void testModifiedFrameThrowsException() throws Exception { public void testInvalidPayloadAndPaddingLengthThrowsException()
// FIXME throws Exception {
} byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
// The payload length plus padding length is invalid
int payloadLength = MAX_PAYLOAD_LENGTH - 1, paddingLength = 2;
ByteUtils.writeUint16(payloadLength, frameHeader, 0);
ByteUtils.writeUint16(paddingLength, frameHeader, 2);
byte[] payload = new byte[payloadLength];
random.nextBytes(payload);
@Test ByteArrayOutputStream out = new ByteArrayOutputStream();
public void testInvalidPayloadLengthThrowsException() throws Exception { out.write(streamHeaderIv);
// FIXME out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);
out.write(payload);
out.write(new byte[paddingLength]);
out.write(new byte[MAC_LENGTH]);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
StreamDecrypterImpl s = new StreamDecrypterImpl(in, cipher,
streamHeaderKey);
// Try to read the invalid frame
byte[] buffer = new byte[MAX_PAYLOAD_LENGTH];
try {
s.readFrame(buffer);
fail();
} catch (IOException expected) {
// Expected
}
} }
@Test @Test
public void testNonZeroPaddingThrowsException() throws Exception { public void testNonZeroPaddingThrowsException() throws Exception {
// FIXME byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
int payloadLength = 123, paddingLength = 234;
FrameEncoder.encodeHeader(frameHeader, false, payloadLength,
paddingLength);
byte[] payload = new byte[payloadLength];
random.nextBytes(payload);
// Set one of the padding bytes non-zero
byte[] padding = new byte[paddingLength];
padding[paddingLength - 1] = 1;
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(streamHeaderIv);
out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);
out.write(payload);
out.write(padding);
out.write(new byte[MAC_LENGTH]);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
StreamDecrypterImpl s = new StreamDecrypterImpl(in, cipher,
streamHeaderKey);
// Try to read the invalid frame
byte[] buffer = new byte[MAX_PAYLOAD_LENGTH];
try {
s.readFrame(buffer);
fail();
} catch (IOException expected) {
// Expected
}
} }
@Test @Test
public void testCannotReadBeyondFinalFrame() throws Exception { public void testCannotReadBeyondFinalFrame() throws Exception {
// FIXME byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
int payloadLength = 123, paddingLength = 234;
FrameEncoder.encodeHeader(frameHeader, true, payloadLength,
paddingLength);
byte[] payload = new byte[payloadLength];
random.nextBytes(payload);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(streamHeaderIv);
out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);
out.write(payload);
out.write(new byte[paddingLength]);
out.write(new byte[MAC_LENGTH]);
// Add some data beyond the final frame
out.write(new byte[1024]);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
StreamDecrypterImpl s = new StreamDecrypterImpl(in, cipher,
streamHeaderKey);
// Read the first frame
byte[] buffer = new byte[MAX_PAYLOAD_LENGTH];
assertEquals(payloadLength, s.readFrame(buffer));
assertArrayStartsWith(payload, buffer, payloadLength);
// End of stream
assertEquals(-1, s.readFrame(buffer));
// Yup, definitely end of stream
assertEquals(-1, s.readFrame(buffer));
}
private static void assertArrayStartsWith(byte[] expected, byte[] actual,
int len) {
byte[] prefix = new byte[len];
System.arraycopy(actual, 0, prefix, 0, len);
assertArrayEquals(expected, prefix);
} }
} }

View File

@@ -1,261 +1,345 @@
package org.briarproject.crypto; package org.briarproject.crypto;
import org.briarproject.BriarTestCase; import org.briarproject.BriarTestCase;
import org.briarproject.TestUtils;
import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.crypto.SecretKey;
import org.junit.Test; import org.junit.Test;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.util.Random; import java.util.Random;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH; import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH; import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class StreamEncrypterImplTest extends BriarTestCase { public class StreamEncrypterImplTest extends BriarTestCase {
private final AuthenticatedCipher frameCipher; private final AuthenticatedCipher cipher;
private final SecretKey frameKey; private final SecretKey streamHeaderKey, frameKey;
private final byte[] tag; private final byte[] tag, streamHeaderIv;
private final Random random;
public StreamEncrypterImplTest() { public StreamEncrypterImplTest() {
frameCipher = new TestAuthenticatedCipher(); cipher = new TestAuthenticatedCipher(); // Null cipher
frameKey = new SecretKey(new byte[32]); streamHeaderKey = TestUtils.createSecretKey();
frameKey = TestUtils.createSecretKey();
tag = new byte[TAG_LENGTH]; tag = new byte[TAG_LENGTH];
new Random().nextBytes(tag); streamHeaderIv = new byte[STREAM_HEADER_IV_LENGTH];
random = new Random();
random.nextBytes(tag);
random.nextBytes(streamHeaderIv);
} }
@Test @Test
public void testWriteUnpaddedNonFinalFrameWithTag() throws Exception { public void testWriteUnpaddedNonFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher, StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher, tag,
frameKey, tag); streamHeaderIv, streamHeaderKey, frameKey);
int payloadLength = 123; int payloadLength = 123;
byte[] payload = new byte[payloadLength]; byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload); random.nextBytes(payload);
s.writeFrame(payload, payloadLength, 0, false); s.writeFrame(payload, payloadLength, 0, false);
byte[] header = new byte[HEADER_LENGTH]; // Expect the tag, stream header, frame header, payload and MAC
FrameEncoder.encodeHeader(header, false, payloadLength, 0); ByteArrayOutputStream expected = new ByteArrayOutputStream();
int frameLength = HEADER_LENGTH + payloadLength + MAC_LENGTH; expected.write(tag);
byte[] expected = new byte[TAG_LENGTH + frameLength]; expected.write(streamHeaderIv);
System.arraycopy(tag, 0, expected, 0, TAG_LENGTH); expected.write(frameKey.getBytes());
System.arraycopy(header, 0, expected, TAG_LENGTH, HEADER_LENGTH); expected.write(new byte[MAC_LENGTH]);
System.arraycopy(payload, 0, expected, TAG_LENGTH + HEADER_LENGTH, byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
payloadLength); FrameEncoder.encodeHeader(expectedFrameHeader, false, payloadLength, 0);
assertArrayEquals(expected, out.toByteArray()); expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
} }
@Test @Test
public void testWriteUnpaddedFinalFrameWithTag() throws Exception { public void testWriteUnpaddedFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher, StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher, tag,
frameKey, tag); streamHeaderIv, streamHeaderKey, frameKey);
int payloadLength = 123; int payloadLength = 123;
int frameLength = HEADER_LENGTH + payloadLength + MAC_LENGTH;
byte[] payload = new byte[payloadLength]; byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload); random.nextBytes(payload);
s.writeFrame(payload, payloadLength, 0, true); s.writeFrame(payload, payloadLength, 0, true);
byte[] header = new byte[HEADER_LENGTH]; // Expect the tag, stream header, frame header, payload and MAC
FrameEncoder.encodeHeader(header, true, payloadLength, 0); ByteArrayOutputStream expected = new ByteArrayOutputStream();
byte[] expected = new byte[TAG_LENGTH + frameLength]; expected.write(tag);
System.arraycopy(tag, 0, expected, 0, TAG_LENGTH); expected.write(streamHeaderIv);
System.arraycopy(header, 0, expected, TAG_LENGTH, HEADER_LENGTH); expected.write(frameKey.getBytes());
System.arraycopy(payload, 0, expected, TAG_LENGTH + HEADER_LENGTH, expected.write(new byte[MAC_LENGTH]);
payloadLength); byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
assertArrayEquals(expected, out.toByteArray()); FrameEncoder.encodeHeader(expectedFrameHeader, true, payloadLength, 0);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
} }
@Test @Test
public void testWriteUnpaddedNonFinalFrameWithoutTag() throws Exception { public void testWriteUnpaddedNonFinalFrameWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher, StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher, null,
frameKey, null); streamHeaderIv, streamHeaderKey, frameKey);
int payloadLength = 123; int payloadLength = 123;
int frameLength = HEADER_LENGTH + payloadLength + MAC_LENGTH;
byte[] payload = new byte[payloadLength]; byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload); random.nextBytes(payload);
s.writeFrame(payload, payloadLength, 0, false); s.writeFrame(payload, payloadLength, 0, false);
byte[] header = new byte[HEADER_LENGTH]; // Expect the stream header, frame header, payload and MAC
FrameEncoder.encodeHeader(header, false, payloadLength, 0); ByteArrayOutputStream expected = new ByteArrayOutputStream();
byte[] expected = new byte[frameLength]; expected.write(streamHeaderIv);
System.arraycopy(header, 0, expected, 0, HEADER_LENGTH); expected.write(frameKey.getBytes());
System.arraycopy(payload, 0, expected, HEADER_LENGTH, payloadLength); expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected, out.toByteArray()); byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(expectedFrameHeader, false, payloadLength, 0);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
} }
@Test @Test
public void testWriteUnpaddedFinalFrameWithoutTag() throws Exception { public void testWriteUnpaddedFinalFrameWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher, StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher, null,
frameKey, null); streamHeaderIv, streamHeaderKey, frameKey);
int payloadLength = 123; int payloadLength = 123;
int frameLength = HEADER_LENGTH + payloadLength + MAC_LENGTH;
byte[] payload = new byte[payloadLength]; byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload); random.nextBytes(payload);
s.writeFrame(payload, payloadLength, 0, true); s.writeFrame(payload, payloadLength, 0, true);
byte[] header = new byte[HEADER_LENGTH]; // Expect the stream header, frame header, payload and MAC
FrameEncoder.encodeHeader(header, true, payloadLength, 0); ByteArrayOutputStream expected = new ByteArrayOutputStream();
byte[] expected = new byte[frameLength]; expected.write(streamHeaderIv);
System.arraycopy(header, 0, expected, 0, HEADER_LENGTH); expected.write(frameKey.getBytes());
System.arraycopy(payload, 0, expected, HEADER_LENGTH, payloadLength); expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected, out.toByteArray()); byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(expectedFrameHeader, true, payloadLength, 0);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
} }
@Test @Test
public void testWritePaddedNonFinalFrameWithTag() throws Exception { public void testWritePaddedNonFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher, StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher, tag,
frameKey, tag); streamHeaderIv, streamHeaderKey, frameKey);
int payloadLength = 123, paddingLength = 234; int payloadLength = 123, paddingLength = 234;
int frameLength = HEADER_LENGTH + payloadLength + paddingLength
+ MAC_LENGTH;
byte[] payload = new byte[payloadLength]; byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload); random.nextBytes(payload);
s.writeFrame(payload, payloadLength, paddingLength, false); s.writeFrame(payload, payloadLength, paddingLength, false);
byte[] header = new byte[HEADER_LENGTH]; // Expect the tag, stream header, frame header, payload, padding and MAC
FrameEncoder.encodeHeader(header, false, payloadLength, paddingLength); ByteArrayOutputStream expected = new ByteArrayOutputStream();
byte[] expected = new byte[TAG_LENGTH + frameLength]; expected.write(tag);
System.arraycopy(tag, 0, expected, 0, TAG_LENGTH); expected.write(streamHeaderIv);
System.arraycopy(header, 0, expected, TAG_LENGTH, HEADER_LENGTH); expected.write(frameKey.getBytes());
System.arraycopy(payload, 0, expected, TAG_LENGTH + HEADER_LENGTH, expected.write(new byte[MAC_LENGTH]);
payloadLength); byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
assertArrayEquals(expected, out.toByteArray()); FrameEncoder.encodeHeader(expectedFrameHeader, false, payloadLength,
paddingLength);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[paddingLength]);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
} }
@Test @Test
public void testWritePaddedFinalFrameWithTag() throws Exception { public void testWritePaddedFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher, StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher, tag,
frameKey, tag); streamHeaderIv, streamHeaderKey, frameKey);
int payloadLength = 123, paddingLength = 234; int payloadLength = 123, paddingLength = 234;
int frameLength = HEADER_LENGTH + payloadLength + paddingLength
+ MAC_LENGTH;
byte[] payload = new byte[payloadLength]; byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload); random.nextBytes(payload);
s.writeFrame(payload, payloadLength, paddingLength, true); s.writeFrame(payload, payloadLength, paddingLength, true);
byte[] header = new byte[HEADER_LENGTH]; // Expect the tag, stream header, frame header, payload, padding and MAC
FrameEncoder.encodeHeader(header, true, payloadLength, paddingLength); ByteArrayOutputStream expected = new ByteArrayOutputStream();
byte[] expected = new byte[TAG_LENGTH + frameLength]; expected.write(tag);
System.arraycopy(tag, 0, expected, 0, TAG_LENGTH); expected.write(streamHeaderIv);
System.arraycopy(header, 0, expected, TAG_LENGTH, HEADER_LENGTH); expected.write(frameKey.getBytes());
System.arraycopy(payload, 0, expected, TAG_LENGTH + HEADER_LENGTH, expected.write(new byte[MAC_LENGTH]);
payloadLength); byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
assertArrayEquals(expected, out.toByteArray()); FrameEncoder.encodeHeader(expectedFrameHeader, true, payloadLength,
paddingLength);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[paddingLength]);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
} }
@Test @Test
public void testWritePaddedNonFinalFrameWithoutTag() throws Exception { public void testWritePaddedNonFinalFrameWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher, StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher, null,
frameKey, null); streamHeaderIv, streamHeaderKey, frameKey);
int payloadLength = 123, paddingLength = 234; int payloadLength = 123, paddingLength = 234;
int frameLength = HEADER_LENGTH + payloadLength + paddingLength
+ MAC_LENGTH;
byte[] payload = new byte[payloadLength]; byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload); random.nextBytes(payload);
s.writeFrame(payload, payloadLength, paddingLength, false); s.writeFrame(payload, payloadLength, paddingLength, false);
byte[] header = new byte[HEADER_LENGTH]; // Expect the stream header, frame header, payload, padding and MAC
FrameEncoder.encodeHeader(header, false, payloadLength, paddingLength); ByteArrayOutputStream expected = new ByteArrayOutputStream();
byte[] expected = new byte[frameLength]; expected.write(streamHeaderIv);
System.arraycopy(header, 0, expected, 0, HEADER_LENGTH); expected.write(frameKey.getBytes());
System.arraycopy(payload, 0, expected, HEADER_LENGTH, payloadLength); expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected, out.toByteArray()); byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(expectedFrameHeader, false, payloadLength,
paddingLength);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[paddingLength]);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
} }
@Test @Test
public void testWritePaddedFinalFrameWithoutTag() throws Exception { public void testWritePaddedFinalFrameWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher, StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher, null,
frameKey, null); streamHeaderIv, streamHeaderKey, frameKey);
int payloadLength = 123, paddingLength = 234; int payloadLength = 123, paddingLength = 234;
int frameLength = HEADER_LENGTH + payloadLength + paddingLength
+ MAC_LENGTH;
byte[] payload = new byte[payloadLength]; byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload); random.nextBytes(payload);
s.writeFrame(payload, payloadLength, paddingLength, true); s.writeFrame(payload, payloadLength, paddingLength, true);
byte[] header = new byte[HEADER_LENGTH]; // Expect the stream header, frame header, payload, padding and MAC
FrameEncoder.encodeHeader(header, true, payloadLength, paddingLength); ByteArrayOutputStream expected = new ByteArrayOutputStream();
byte[] expected = new byte[frameLength]; expected.write(streamHeaderIv);
System.arraycopy(header, 0, expected, 0, HEADER_LENGTH); expected.write(frameKey.getBytes());
System.arraycopy(payload, 0, expected, HEADER_LENGTH, payloadLength); expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected, out.toByteArray()); byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(expectedFrameHeader, true, payloadLength,
paddingLength);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[paddingLength]);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
} }
@Test @Test
public void testWriteTwoFrames() throws Exception { public void testWriteTwoFramesWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher, StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher, tag,
frameKey, null); streamHeaderIv, streamHeaderKey, frameKey);
int payloadLength = 123, paddingLength = 234; int payloadLength = 123, paddingLength = 234;
int frameLength = HEADER_LENGTH + payloadLength + paddingLength
+ MAC_LENGTH;
byte[] payload = new byte[payloadLength]; byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload); random.nextBytes(payload);
int payloadLength1 = 345, paddingLength1 = 456; int payloadLength1 = 345, paddingLength1 = 456;
int frameLength1 = HEADER_LENGTH + payloadLength1 + paddingLength1
+ MAC_LENGTH;
byte[] payload1 = new byte[payloadLength1]; byte[] payload1 = new byte[payloadLength1];
new Random().nextBytes(payload1); random.nextBytes(payload1);
s.writeFrame(payload, payloadLength, paddingLength, false); s.writeFrame(payload, payloadLength, paddingLength, false);
s.writeFrame(payload1, payloadLength1, paddingLength1, true); s.writeFrame(payload1, payloadLength1, paddingLength1, true);
byte[] header = new byte[HEADER_LENGTH]; // Expect the tag, stream header, first frame header, payload, padding,
FrameEncoder.encodeHeader(header, false, payloadLength, paddingLength); // MAC, second frame header, payload, padding, MAC
byte[] header1 = new byte[HEADER_LENGTH]; ByteArrayOutputStream expected = new ByteArrayOutputStream();
FrameEncoder.encodeHeader(header1, true, payloadLength1, expected.write(tag);
expected.write(streamHeaderIv);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(expectedFrameHeader, false, payloadLength,
paddingLength);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[paddingLength]);
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader1 = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(expectedFrameHeader1, true, payloadLength1,
paddingLength1); paddingLength1);
byte[] expected = new byte[frameLength + frameLength1]; expected.write(expectedFrameHeader1);
System.arraycopy(header, 0, expected, 0, HEADER_LENGTH); expected.write(payload1);
System.arraycopy(payload, 0, expected, HEADER_LENGTH, payloadLength); expected.write(new byte[paddingLength1]);
System.arraycopy(header1, 0, expected, frameLength, HEADER_LENGTH); expected.write(new byte[MAC_LENGTH]);
System.arraycopy(payload1, 0, expected, frameLength + HEADER_LENGTH,
payloadLength1); assertArrayEquals(expected.toByteArray(), out.toByteArray());
assertArrayEquals(expected, out.toByteArray());
} }
@Test @Test
public void testFlushWritesTagIfNotAlreadyWritten() throws Exception { public void testFlushWritesTagAndStreamHeaderIfNotAlreadyWritten()
throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher, StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher, tag,
frameKey, tag); streamHeaderIv, streamHeaderKey, frameKey);
// Flush the stream once
s.flush(); s.flush();
assertArrayEquals(tag, out.toByteArray());
// Expect the tag and stream header
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(tag);
expected.write(streamHeaderIv);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
} }
@Test @Test
public void testFlushDoesNotWriteTagIfAlreadyWritten() throws Exception { public void testFlushDoesNotWriteTagOrStreamHeaderIfAlreadyWritten()
throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher, StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher, tag,
frameKey, tag); streamHeaderIv, streamHeaderKey, frameKey);
// Flush the stream twice
s.flush(); s.flush();
s.flush(); s.flush();
assertArrayEquals(tag, out.toByteArray());
// Expect the tag and stream header
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(tag);
expected.write(streamHeaderIv);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
} }
@Test @Test
public void testFlushDoesNotWriteTagIfNull() throws Exception { public void testFlushDoesNotWriteTagIfNull() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher, StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher, null,
frameKey, null); streamHeaderIv, streamHeaderKey, frameKey);
// Flush the stream once
s.flush(); s.flush();
assertEquals(0, out.size());
// Expect the stream header
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(streamHeaderIv);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
} }
} }

View File

@@ -8,7 +8,7 @@ import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH; import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
@@ -24,8 +24,8 @@ class TestStreamDecrypter implements StreamDecrypter {
public int readFrame(byte[] payload) throws IOException { public int readFrame(byte[] payload) throws IOException {
int offset = 0; int offset = 0;
while (offset < HEADER_LENGTH) { while (offset < FRAME_HEADER_LENGTH) {
int read = in.read(frame, offset, HEADER_LENGTH - offset); int read = in.read(frame, offset, FRAME_HEADER_LENGTH - offset);
if (read == -1) throw new EOFException(); if (read == -1) throw new EOFException();
offset += read; offset += read;
} }
@@ -37,9 +37,9 @@ class TestStreamDecrypter implements StreamDecrypter {
offset += read; offset += read;
} }
if (!finalFrame && offset < frame.length) throw new EOFException(); if (!finalFrame && offset < frame.length) throw new EOFException();
if (offset < HEADER_LENGTH + payloadLength + MAC_LENGTH) if (offset < FRAME_HEADER_LENGTH + payloadLength + MAC_LENGTH)
throw new FormatException(); throw new FormatException();
System.arraycopy(frame, HEADER_LENGTH, payload, 0, payloadLength); System.arraycopy(frame, FRAME_HEADER_LENGTH, payload, 0, payloadLength);
return payloadLength; return payloadLength;
} }
} }

View File

@@ -6,7 +6,7 @@ import org.briarproject.util.ByteUtils;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH; import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
@@ -31,11 +31,11 @@ class TestStreamEncrypter implements StreamEncrypter {
} }
ByteUtils.writeUint16(payloadLength, frame, 0); ByteUtils.writeUint16(payloadLength, frame, 0);
if (finalFrame) frame[0] |= 0x80; if (finalFrame) frame[0] |= 0x80;
System.arraycopy(payload, 0, frame, HEADER_LENGTH, payloadLength); System.arraycopy(payload, 0, frame, FRAME_HEADER_LENGTH, payloadLength);
for (int i = HEADER_LENGTH + payloadLength; i < frame.length; i++) for (int i = FRAME_HEADER_LENGTH + payloadLength; i < frame.length; i++)
frame[i] = 0; frame[i] = 0;
if (finalFrame) if (finalFrame)
out.write(frame, 0, HEADER_LENGTH + payloadLength + MAC_LENGTH); out.write(frame, 0, FRAME_HEADER_LENGTH + payloadLength + MAC_LENGTH);
else out.write(frame, 0, frame.length); else out.write(frame, 0, frame.length);
} }