mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Changed to fixed-length frames (mostly untested).
This commit is contained in:
29
api/net/sf/briar/api/crypto/AuthenticatedCipher.java
Normal file
29
api/net/sf/briar/api/crypto/AuthenticatedCipher.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package net.sf.briar.api.crypto;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
|
||||
/**
|
||||
* A wrapper for a provider-dependent cipher class, since javax.crypto.Cipher
|
||||
* doesn't support additional authenticated data until Java 7.
|
||||
*/
|
||||
public interface AuthenticatedCipher {
|
||||
|
||||
/**
|
||||
* Initializes this cipher with a key, an initialisation vector (IV) and
|
||||
* additional authenticated data (AAD).
|
||||
*/
|
||||
void init(int opmode, Key key, byte[] iv, byte[] aad)
|
||||
throws InvalidKeyException;
|
||||
|
||||
/** Encrypts or decrypts data in a single-part operation. */
|
||||
int doFinal(byte[] input, int inputOff, int len, byte[] output,
|
||||
int outputOff) throws IllegalBlockSizeException,
|
||||
BadPaddingException;
|
||||
|
||||
/** Returns the length of the message authenticated code (MAC) in bytes. */
|
||||
int getMacLength();
|
||||
}
|
||||
@@ -36,13 +36,7 @@ public interface CryptoComponent {
|
||||
|
||||
Cipher getTagCipher();
|
||||
|
||||
Cipher getFrameCipher();
|
||||
|
||||
Cipher getFramePeekingCipher();
|
||||
|
||||
IvEncoder getFrameIvEncoder();
|
||||
|
||||
IvEncoder getFramePeekingIvEncoder();
|
||||
AuthenticatedCipher getFrameCipher();
|
||||
|
||||
Signature getSignature();
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package net.sf.briar.api.crypto;
|
||||
|
||||
public interface IvEncoder {
|
||||
|
||||
byte[] encodeIv(long frameNumber);
|
||||
|
||||
void updateIv(byte[] iv, long frameNumber);
|
||||
}
|
||||
@@ -2,13 +2,13 @@ package net.sf.briar.api.plugins;
|
||||
|
||||
public interface InvitationConstants {
|
||||
|
||||
static final long INVITATION_TIMEOUT = 60 * 1000; // 1 minute
|
||||
long INVITATION_TIMEOUT = 60 * 1000; // 1 minute
|
||||
|
||||
static final int CODE_BITS = 19; // Codes must fit into six decimal digits
|
||||
int CODE_BITS = 19; // Codes must fit into six decimal digits
|
||||
|
||||
static final int MAX_CODE = 1 << CODE_BITS - 1;
|
||||
int MAX_CODE = 1 << CODE_BITS - 1;
|
||||
|
||||
static final int HASH_LENGTH = 48;
|
||||
int HASH_LENGTH = 48;
|
||||
|
||||
static final int MAX_PUBLIC_KEY_LENGTH = 120;
|
||||
int MAX_PUBLIC_KEY_LENGTH = 120;
|
||||
}
|
||||
|
||||
@@ -6,12 +6,18 @@ public interface TransportConstants {
|
||||
static final int TAG_LENGTH = 16;
|
||||
|
||||
/** The maximum length of a frame in bytes, including the header and MAC. */
|
||||
static final int MAX_FRAME_LENGTH = 65536; // 2^16, 64 KiB
|
||||
static final int MAX_FRAME_LENGTH = 32768; // 2^15, 32 KiB
|
||||
|
||||
/** The length of the initalisation vector (IV) in bytes. */
|
||||
static final int IV_LENGTH = 12;
|
||||
|
||||
/** The length of the additional authenticated data (AAD) in bytes. */
|
||||
static final int AAD_LENGTH = 6;
|
||||
|
||||
/** The length of the frame header in bytes. */
|
||||
static final int HEADER_LENGTH = 9;
|
||||
static final int HEADER_LENGTH = 2;
|
||||
|
||||
/** The length of the MAC in bytes. */
|
||||
/** The length of the message authentication code (MAC) in bytes. */
|
||||
static final int MAC_LENGTH = 16;
|
||||
|
||||
/**
|
||||
|
||||
70
components/net/sf/briar/crypto/AuthenticatedCipherImpl.java
Normal file
70
components/net/sf/briar/crypto/AuthenticatedCipherImpl.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package net.sf.briar.crypto;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
|
||||
import net.sf.briar.api.crypto.AuthenticatedCipher;
|
||||
|
||||
import org.spongycastle.crypto.DataLengthException;
|
||||
import org.spongycastle.crypto.InvalidCipherTextException;
|
||||
import org.spongycastle.crypto.modes.AEADBlockCipher;
|
||||
import org.spongycastle.crypto.params.AEADParameters;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
class AuthenticatedCipherImpl implements AuthenticatedCipher {
|
||||
|
||||
private final AEADBlockCipher cipher;
|
||||
private final int macLength;
|
||||
|
||||
AuthenticatedCipherImpl(AEADBlockCipher cipher, int macLength) {
|
||||
this.cipher = cipher;
|
||||
this.macLength = macLength;
|
||||
}
|
||||
|
||||
public int doFinal(byte[] input, int inputOff, int len, byte[] output,
|
||||
int outputOff) throws IllegalBlockSizeException,
|
||||
BadPaddingException {
|
||||
int processed = 0;
|
||||
if(len != 0) {
|
||||
processed = cipher.processBytes(input, inputOff, len, output,
|
||||
outputOff);
|
||||
}
|
||||
try {
|
||||
return processed + cipher.doFinal(output, outputOff + processed);
|
||||
} catch(DataLengthException e) {
|
||||
throw new IllegalBlockSizeException(e.getMessage());
|
||||
} catch(InvalidCipherTextException e) {
|
||||
throw new BadPaddingException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void init(int opmode, Key key, byte[] iv, byte[] aad)
|
||||
throws InvalidKeyException {
|
||||
KeyParameter k = new KeyParameter(key.getEncoded());
|
||||
AEADParameters params = new AEADParameters(k, macLength * 8, iv, aad);
|
||||
try {
|
||||
switch(opmode) {
|
||||
case Cipher.ENCRYPT_MODE:
|
||||
case Cipher.WRAP_MODE:
|
||||
cipher.init(true, params);
|
||||
break;
|
||||
case Cipher.DECRYPT_MODE:
|
||||
case Cipher.UNWRAP_MODE:
|
||||
cipher.init(false, params);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
} catch(Exception e) {
|
||||
throw new InvalidKeyException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public int getMacLength() {
|
||||
return macLength;
|
||||
}
|
||||
}
|
||||
@@ -15,14 +15,17 @@ import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyAgreement;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
import net.sf.briar.api.crypto.AuthenticatedCipher;
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.ErasableKey;
|
||||
import net.sf.briar.api.crypto.IvEncoder;
|
||||
import net.sf.briar.api.crypto.KeyParser;
|
||||
import net.sf.briar.api.crypto.MessageDigest;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.util.ByteUtils;
|
||||
|
||||
import org.spongycastle.crypto.engines.AESEngine;
|
||||
import org.spongycastle.crypto.modes.AEADBlockCipher;
|
||||
import org.spongycastle.crypto.modes.GCMBlockCipher;
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
@@ -42,8 +45,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
private static final int SIGNATURE_KEY_PAIR_BITS = 384;
|
||||
private static final String SIGNATURE_ALGO = "ECDSA";
|
||||
private static final String TAG_CIPHER_ALGO = "AES/ECB/NoPadding";
|
||||
private static final String FRAME_CIPHER_ALGO = "AES/GCM/NoPadding";
|
||||
private static final String FRAME_PEEKING_CIPHER_ALGO = "AES/CTR/NoPadding";
|
||||
private static final int GCM_MAC_LENGTH = 16; // 128 bits
|
||||
|
||||
// Labels for key derivation
|
||||
private static final byte[] TAG = { 'T', 'A', 'G' };
|
||||
@@ -275,27 +277,10 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public Cipher getFrameCipher() {
|
||||
try {
|
||||
return Cipher.getInstance(FRAME_CIPHER_ALGO, PROVIDER);
|
||||
} catch(GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Cipher getFramePeekingCipher() {
|
||||
try {
|
||||
return Cipher.getInstance(FRAME_PEEKING_CIPHER_ALGO, PROVIDER);
|
||||
} catch(GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public IvEncoder getFrameIvEncoder() {
|
||||
return new FrameIvEncoder();
|
||||
}
|
||||
|
||||
public IvEncoder getFramePeekingIvEncoder() {
|
||||
return new FramePeekingIvEncoder();
|
||||
public AuthenticatedCipher getFrameCipher() {
|
||||
// This code is specific to BouncyCastle because javax.crypto.Cipher
|
||||
// doesn't support additional authenticated data until Java 7
|
||||
AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
|
||||
return new AuthenticatedCipherImpl(cipher, GCM_MAC_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package net.sf.briar.crypto;
|
||||
|
||||
import net.sf.briar.api.crypto.IvEncoder;
|
||||
import net.sf.briar.util.ByteUtils;
|
||||
|
||||
class FrameIvEncoder implements IvEncoder {
|
||||
|
||||
// AES-GCM uses a 96-bit IV; the bytes 0x00, 0x00, 0x00, 0x02 are
|
||||
// appended internally (see NIST SP 800-38D, section 7.1)
|
||||
private static final int IV_LENGTH = 12;
|
||||
|
||||
public byte[] encodeIv(long frame) {
|
||||
if(frame < 0 || frame > ByteUtils.MAX_32_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
byte[] iv = new byte[IV_LENGTH];
|
||||
updateIv(iv, frame);
|
||||
return iv;
|
||||
}
|
||||
|
||||
public void updateIv(byte[] iv, long frame) {
|
||||
if(frame < 0 || frame > ByteUtils.MAX_32_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
// Encode the frame number as a uint32
|
||||
ByteUtils.writeUint32(frame, iv, 0);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package net.sf.briar.crypto;
|
||||
|
||||
import net.sf.briar.util.ByteUtils;
|
||||
|
||||
class FramePeekingIvEncoder extends FrameIvEncoder {
|
||||
|
||||
// AES/CTR uses a 128-bit IV; to match the AES/GCM IV we have to append
|
||||
// the bytes 0x00, 0x00, 0x00, 0x02 (see NIST SP 800-38D, section 7.1)
|
||||
private static final int IV_LENGTH = 16;
|
||||
|
||||
@Override
|
||||
public byte[] encodeIv(long frame) {
|
||||
if(frame < 0 || frame > ByteUtils.MAX_32_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
byte[] iv = new byte[IV_LENGTH];
|
||||
iv[IV_LENGTH - 1] = 2;
|
||||
updateIv(iv, frame);
|
||||
return iv;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import net.sf.briar.api.crypto.AuthenticatedCipher;
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.ErasableKey;
|
||||
import net.sf.briar.api.crypto.IvEncoder;
|
||||
import net.sf.briar.api.transport.ConnectionReader;
|
||||
import net.sf.briar.api.transport.ConnectionReaderFactory;
|
||||
import net.sf.briar.util.ByteUtils;
|
||||
@@ -30,13 +32,9 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
|
||||
ByteUtils.erase(secret);
|
||||
// Create the reader
|
||||
Cipher tagCipher = crypto.getTagCipher();
|
||||
Cipher frameCipher = crypto.getFrameCipher();
|
||||
Cipher framePeekingCipher = crypto.getFramePeekingCipher();
|
||||
IvEncoder frameIvEncoder = crypto.getFrameIvEncoder();
|
||||
IvEncoder framePeekingIvEncoder = crypto.getFramePeekingIvEncoder();
|
||||
AuthenticatedCipher frameCipher = crypto.getFrameCipher();
|
||||
FrameReader encryption = new IncomingEncryptionLayer(in, tagCipher,
|
||||
frameCipher, framePeekingCipher, frameIvEncoder,
|
||||
framePeekingIvEncoder, tagKey, frameKey, !initiator);
|
||||
return new ConnectionReaderImpl(encryption);
|
||||
frameCipher, tagKey, frameKey, !initiator, MAX_FRAME_LENGTH);
|
||||
return new ConnectionReaderImpl(encryption, MAX_FRAME_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.sf.briar.api.FormatException;
|
||||
import net.sf.briar.api.transport.ConnectionReader;
|
||||
|
||||
class ConnectionReaderImpl extends InputStream implements ConnectionReader {
|
||||
@@ -16,10 +14,9 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
|
||||
|
||||
private int offset = 0, length = 0;
|
||||
|
||||
ConnectionReaderImpl(FrameReader in) {
|
||||
ConnectionReaderImpl(FrameReader in, int frameLength) {
|
||||
this.in = in;
|
||||
frame = new byte[MAX_FRAME_LENGTH];
|
||||
offset = HEADER_LENGTH;
|
||||
frame = new byte[frameLength];
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
@@ -28,8 +25,10 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if(length == -1) return -1;
|
||||
while(length == 0) if(!readFrame()) return -1;
|
||||
while(length <= 0) {
|
||||
if(length == -1) return -1;
|
||||
readFrame();
|
||||
}
|
||||
int b = frame[offset] & 0xff;
|
||||
offset++;
|
||||
length--;
|
||||
@@ -43,8 +42,10 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if(length == -1) return -1;
|
||||
while(length == 0) if(!readFrame()) return -1;
|
||||
while(length <= 0) {
|
||||
if(length == -1) return -1;
|
||||
readFrame();
|
||||
}
|
||||
len = Math.min(len, length);
|
||||
System.arraycopy(frame, offset, b, off, len);
|
||||
offset += len;
|
||||
@@ -52,20 +53,9 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
|
||||
return len;
|
||||
}
|
||||
|
||||
private boolean readFrame() throws IOException {
|
||||
private void readFrame() throws IOException {
|
||||
assert length == 0;
|
||||
if(HeaderEncoder.isLastFrame(frame)) {
|
||||
length = -1;
|
||||
return false;
|
||||
}
|
||||
if(!in.readFrame(frame)) throw new FormatException();
|
||||
offset = HEADER_LENGTH;
|
||||
length = HeaderEncoder.getPayloadLength(frame);
|
||||
// The padding must be all zeroes
|
||||
int padding = HeaderEncoder.getPaddingLength(frame);
|
||||
for(int i = offset + length; i < offset + length + padding; i++) {
|
||||
if(frame[i] != 0) throw new FormatException();
|
||||
}
|
||||
return true;
|
||||
length = in.readFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import net.sf.briar.api.crypto.AuthenticatedCipher;
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.ErasableKey;
|
||||
import net.sf.briar.api.crypto.IvEncoder;
|
||||
import net.sf.briar.api.transport.ConnectionWriter;
|
||||
import net.sf.briar.api.transport.ConnectionWriterFactory;
|
||||
import net.sf.briar.util.ByteUtils;
|
||||
@@ -30,11 +32,10 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
|
||||
ByteUtils.erase(secret);
|
||||
// Create the writer
|
||||
Cipher tagCipher = crypto.getTagCipher();
|
||||
Cipher frameCipher = crypto.getFrameCipher();
|
||||
IvEncoder frameIvEncoder = crypto.getFrameIvEncoder();
|
||||
FrameWriter encryption = new OutgoingEncryptionLayer(
|
||||
out, capacity, tagCipher, frameCipher, frameIvEncoder, tagKey,
|
||||
frameKey);
|
||||
return new ConnectionWriterImpl(encryption);
|
||||
AuthenticatedCipher frameCipher = crypto.getFrameCipher();
|
||||
FrameWriter encryption = new OutgoingEncryptionLayer(out, capacity,
|
||||
tagCipher, frameCipher, tagKey, frameKey, initiator,
|
||||
MAX_FRAME_LENGTH);
|
||||
return new ConnectionWriterImpl(encryption, MAX_FRAME_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package net.sf.briar.transport;
|
||||
|
||||
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -12,7 +11,7 @@ import net.sf.briar.api.transport.ConnectionWriter;
|
||||
|
||||
/**
|
||||
* A ConnectionWriter that buffers its input and writes a frame whenever there
|
||||
* is a full-size frame to write or the flush() method is called.
|
||||
* is a full frame to write or the flush() method is called.
|
||||
* <p>
|
||||
* This class is not thread-safe.
|
||||
*/
|
||||
@@ -20,15 +19,15 @@ class ConnectionWriterImpl extends OutputStream implements ConnectionWriter {
|
||||
|
||||
private final FrameWriter out;
|
||||
private final byte[] frame;
|
||||
private final int frameLength;
|
||||
|
||||
private int offset;
|
||||
private long frameNumber;
|
||||
private int length = 0;
|
||||
private long frameNumber = 0L;
|
||||
|
||||
ConnectionWriterImpl(FrameWriter out) {
|
||||
ConnectionWriterImpl(FrameWriter out, int frameLength) {
|
||||
this.out = out;
|
||||
frame = new byte[MAX_FRAME_LENGTH];
|
||||
offset = HEADER_LENGTH;
|
||||
frameNumber = 0L;
|
||||
this.frameLength = frameLength;
|
||||
frame = new byte[frameLength];
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() {
|
||||
@@ -37,31 +36,31 @@ class ConnectionWriterImpl extends OutputStream implements ConnectionWriter {
|
||||
|
||||
public long getRemainingCapacity() {
|
||||
long capacity = out.getRemainingCapacity();
|
||||
// If there's any data buffered, subtract it and its overhead
|
||||
if(offset > HEADER_LENGTH) capacity -= offset + MAC_LENGTH;
|
||||
// Subtract the overhead from the remaining capacity
|
||||
long frames = (long) Math.ceil((double) capacity / MAX_FRAME_LENGTH);
|
||||
int overheadPerFrame = HEADER_LENGTH + MAC_LENGTH;
|
||||
return Math.max(0L, capacity - frames * overheadPerFrame);
|
||||
int maxPayloadLength = frameLength - HEADER_LENGTH - MAC_LENGTH;
|
||||
long frames = (long) Math.ceil((double) capacity / maxPayloadLength);
|
||||
long overhead = (frames + 1) * (HEADER_LENGTH + MAC_LENGTH);
|
||||
return capacity - overhead - length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if(offset > HEADER_LENGTH || frameNumber > 0L) writeFrame(true);
|
||||
writeFrame(true);
|
||||
out.flush();
|
||||
super.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
if(offset > HEADER_LENGTH) writeFrame(false);
|
||||
if(length > 0) writeFrame(false);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
frame[offset++] = (byte) b;
|
||||
if(offset + MAC_LENGTH == MAX_FRAME_LENGTH) writeFrame(false);
|
||||
frame[HEADER_LENGTH + length] = (byte) b;
|
||||
length++;
|
||||
if(HEADER_LENGTH + length + MAC_LENGTH == frameLength)
|
||||
writeFrame(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,26 +70,26 @@ class ConnectionWriterImpl extends OutputStream implements ConnectionWriter {
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
int available = MAX_FRAME_LENGTH - offset - MAC_LENGTH;
|
||||
int available = frameLength - HEADER_LENGTH - length - MAC_LENGTH;
|
||||
while(available <= len) {
|
||||
System.arraycopy(b, off, frame, offset, available);
|
||||
offset += available;
|
||||
System.arraycopy(b, off, frame, HEADER_LENGTH + length, available);
|
||||
length += available;
|
||||
writeFrame(false);
|
||||
off += available;
|
||||
len -= available;
|
||||
available = MAX_FRAME_LENGTH - offset - MAC_LENGTH;
|
||||
available = frameLength - HEADER_LENGTH - length - MAC_LENGTH;
|
||||
}
|
||||
System.arraycopy(b, off, frame, offset, len);
|
||||
offset += len;
|
||||
System.arraycopy(b, off, frame, HEADER_LENGTH + length, len);
|
||||
length += len;
|
||||
}
|
||||
|
||||
private void writeFrame(boolean lastFrame) throws IOException {
|
||||
if(frameNumber > MAX_32_BIT_UNSIGNED) throw new IllegalStateException();
|
||||
int payload = offset - HEADER_LENGTH;
|
||||
assert payload >= 0;
|
||||
HeaderEncoder.encodeHeader(frame, frameNumber, payload, 0, lastFrame);
|
||||
out.writeFrame(frame);
|
||||
offset = HEADER_LENGTH;
|
||||
int capacity = (int) Math.min(frameLength, out.getRemainingCapacity());
|
||||
int paddingLength = capacity - HEADER_LENGTH - length - MAC_LENGTH;
|
||||
if(paddingLength < 0) throw new IllegalStateException();
|
||||
out.writeFrame(frame, length, lastFrame ? 0 : paddingLength, lastFrame);
|
||||
length = 0;
|
||||
frameNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
53
components/net/sf/briar/transport/FrameEncoder.java
Normal file
53
components/net/sf/briar/transport/FrameEncoder.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import static net.sf.briar.api.transport.TransportConstants.AAD_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||
import net.sf.briar.util.ByteUtils;
|
||||
|
||||
class FrameEncoder {
|
||||
|
||||
static void encodeIv(byte[] iv, long frameNumber) {
|
||||
if(iv.length < IV_LENGTH) throw new IllegalArgumentException();
|
||||
if(frameNumber < 0L || frameNumber > MAX_32_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
ByteUtils.writeUint32(frameNumber, iv, 0);
|
||||
for(int i = 4; i < IV_LENGTH; i++) iv[i] = 0;
|
||||
}
|
||||
|
||||
static void encodeAad(byte[] aad, long frameNumber, int plaintextLength) {
|
||||
if(aad.length < AAD_LENGTH) throw new IllegalArgumentException();
|
||||
if(frameNumber < 0L || frameNumber > MAX_32_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
if(plaintextLength < HEADER_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
if(plaintextLength > MAX_FRAME_LENGTH - MAC_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
ByteUtils.writeUint32(frameNumber, aad, 0);
|
||||
ByteUtils.writeUint16(plaintextLength, aad, 4);
|
||||
}
|
||||
|
||||
static void encodeHeader(byte[] header, boolean lastFrame,
|
||||
int payloadLength) {
|
||||
if(header.length < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
if(payloadLength < 0)
|
||||
throw new IllegalArgumentException();
|
||||
if(payloadLength > MAX_FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
ByteUtils.writeUint16(payloadLength, header, 0);
|
||||
if(lastFrame) header[0] |= 0x80;
|
||||
}
|
||||
|
||||
static boolean isLastFrame(byte[] header) {
|
||||
if(header.length < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
return (header[0] & 0x80) == 0x80;
|
||||
}
|
||||
|
||||
static int getPayloadLength(byte[] header) {
|
||||
if(header.length < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
return ByteUtils.readUint16(header, 0) & 0x7FFF;
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import java.io.IOException;
|
||||
interface FrameReader {
|
||||
|
||||
/**
|
||||
* Reads a frame into the given buffer. Returns false if no more frames can
|
||||
* be read from the connection.
|
||||
* Reads a frame into the given buffer and returns its payload length, or
|
||||
* -1 if no more frames can be read from the connection.
|
||||
*/
|
||||
boolean readFrame(byte[] frame) throws IOException;
|
||||
int readFrame(byte[] frame) throws IOException;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import java.io.IOException;
|
||||
interface FrameWriter {
|
||||
|
||||
/** Writes the given frame. */
|
||||
void writeFrame(byte[] frame) throws IOException;
|
||||
void writeFrame(byte[] frame, int payloadLength, int paddingLength,
|
||||
boolean lastFrame) throws IOException;
|
||||
|
||||
/** Flushes the stack. */
|
||||
void flush() throws IOException;
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import net.sf.briar.util.ByteUtils;
|
||||
|
||||
class HeaderEncoder {
|
||||
|
||||
static void encodeHeader(byte[] header, long frameNumber, int payload,
|
||||
int padding, boolean lastFrame) {
|
||||
if(header.length < HEADER_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
if(frameNumber < 0 || frameNumber > ByteUtils.MAX_32_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
if(payload < 0 || payload > ByteUtils.MAX_16_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
if(padding < 0 || padding > ByteUtils.MAX_16_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
ByteUtils.writeUint32(frameNumber, header, 0);
|
||||
ByteUtils.writeUint16(payload, header, 4);
|
||||
ByteUtils.writeUint16(padding, header, 6);
|
||||
if(lastFrame) header[8] = 1;
|
||||
}
|
||||
|
||||
static boolean checkHeader(byte[] header, int length) {
|
||||
if(header.length < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
int payload = getPayloadLength(header);
|
||||
int padding = getPaddingLength(header);
|
||||
if(HEADER_LENGTH + payload + padding + MAC_LENGTH != length)
|
||||
return false;
|
||||
if(header[8] != 0 && header[8] != 1) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static long getFrameNumber(byte[] header) {
|
||||
if(header.length < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
return ByteUtils.readUint32(header, 0);
|
||||
}
|
||||
|
||||
static int getPayloadLength(byte[] header) {
|
||||
if(header.length < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
return ByteUtils.readUint16(header, 4);
|
||||
}
|
||||
|
||||
static int getPaddingLength(byte[] header) {
|
||||
if(header.length < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
return ByteUtils.readUint16(header, 6);
|
||||
}
|
||||
|
||||
static boolean isLastFrame(byte[] header) {
|
||||
if(header.length < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
return header[8] == 1;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import static javax.crypto.Cipher.DECRYPT_MODE;
|
||||
import static net.sf.briar.api.transport.TransportConstants.AAD_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
|
||||
|
||||
import java.io.EOFException;
|
||||
@@ -11,110 +13,98 @@ import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
import net.sf.briar.api.FormatException;
|
||||
import net.sf.briar.api.crypto.AuthenticatedCipher;
|
||||
import net.sf.briar.api.crypto.ErasableKey;
|
||||
import net.sf.briar.api.crypto.IvEncoder;
|
||||
|
||||
class IncomingEncryptionLayer implements FrameReader {
|
||||
|
||||
private final InputStream in;
|
||||
private final Cipher tagCipher, frameCipher, framePeekingCipher;
|
||||
private final IvEncoder frameIvEncoder, framePeekingIvEncoder;
|
||||
private final Cipher tagCipher;
|
||||
private final AuthenticatedCipher frameCipher;
|
||||
private final ErasableKey tagKey, frameKey;
|
||||
private final int blockSize;
|
||||
private final byte[] frameIv, framePeekingIv, ciphertext;
|
||||
private final byte[] iv, aad, ciphertext;
|
||||
private final int maxFrameLength;
|
||||
|
||||
private boolean readTag;
|
||||
private boolean readTag, lastFrame;
|
||||
private long frameNumber;
|
||||
|
||||
IncomingEncryptionLayer(InputStream in, Cipher tagCipher,
|
||||
Cipher frameCipher, Cipher framePeekingCipher,
|
||||
IvEncoder frameIvEncoder, IvEncoder framePeekingIvEncoder,
|
||||
ErasableKey tagKey, ErasableKey frameKey, boolean readTag) {
|
||||
AuthenticatedCipher frameCipher, ErasableKey tagKey,
|
||||
ErasableKey frameKey, boolean readTag, int maxFrameLength) {
|
||||
this.in = in;
|
||||
this.tagCipher = tagCipher;
|
||||
this.frameCipher = frameCipher;
|
||||
this.framePeekingCipher = framePeekingCipher;
|
||||
this.frameIvEncoder = frameIvEncoder;
|
||||
this.framePeekingIvEncoder = framePeekingIvEncoder;
|
||||
this.tagKey = tagKey;
|
||||
this.frameKey = frameKey;
|
||||
this.readTag = readTag;
|
||||
blockSize = frameCipher.getBlockSize();
|
||||
if(blockSize < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
frameIv = frameIvEncoder.encodeIv(0L);
|
||||
framePeekingIv = framePeekingIvEncoder.encodeIv(0L);
|
||||
ciphertext = new byte[MAX_FRAME_LENGTH];
|
||||
this.maxFrameLength = maxFrameLength;
|
||||
lastFrame = false;
|
||||
iv = new byte[IV_LENGTH];
|
||||
aad = new byte[AAD_LENGTH];
|
||||
ciphertext = new byte[maxFrameLength];
|
||||
frameNumber = 0L;
|
||||
}
|
||||
|
||||
public boolean readFrame(byte[] frame) throws IOException {
|
||||
try {
|
||||
// Read the tag if it hasn't already been read
|
||||
if(readTag) {
|
||||
int offset = 0;
|
||||
public int readFrame(byte[] frame) throws IOException {
|
||||
if(lastFrame) return -1;
|
||||
// Read the tag if required
|
||||
if(readTag) {
|
||||
int offset = 0;
|
||||
try {
|
||||
while(offset < TAG_LENGTH) {
|
||||
int read = in.read(ciphertext, offset,
|
||||
TAG_LENGTH - offset);
|
||||
if(read == -1) {
|
||||
if(offset == 0) return false;
|
||||
throw new EOFException();
|
||||
}
|
||||
int read = in.read(ciphertext, offset, TAG_LENGTH - offset);
|
||||
if(read == -1) throw new EOFException();
|
||||
offset += read;
|
||||
}
|
||||
if(!TagEncoder.decodeTag(ciphertext, tagCipher, tagKey))
|
||||
throw new FormatException();
|
||||
}
|
||||
// Read the first block of the frame
|
||||
int offset = 0;
|
||||
while(offset < blockSize) {
|
||||
int read = in.read(ciphertext, offset, blockSize - offset);
|
||||
if(read == -1) throw new EOFException();
|
||||
offset += read;
|
||||
} catch(IOException e) {
|
||||
frameKey.erase();
|
||||
tagKey.erase();
|
||||
throw e;
|
||||
}
|
||||
if(!TagEncoder.decodeTag(ciphertext, tagCipher, tagKey))
|
||||
throw new FormatException();
|
||||
readTag = false;
|
||||
// Decrypt the first block of the frame to peek at the header
|
||||
framePeekingIvEncoder.updateIv(framePeekingIv, frameNumber);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(framePeekingIv);
|
||||
try {
|
||||
framePeekingCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec);
|
||||
int decrypted = framePeekingCipher.update(ciphertext, 0,
|
||||
blockSize, frame);
|
||||
if(decrypted != blockSize) throw new RuntimeException();
|
||||
} catch(GeneralSecurityException badCipher) {
|
||||
throw new RuntimeException(badCipher);
|
||||
}
|
||||
// Read the frame
|
||||
int ciphertextLength = 0;
|
||||
try {
|
||||
while(ciphertextLength < maxFrameLength) {
|
||||
int read = in.read(ciphertext, ciphertextLength,
|
||||
maxFrameLength - ciphertextLength);
|
||||
if(read == -1) break; // We'll check the length later
|
||||
ciphertextLength += read;
|
||||
}
|
||||
// Parse the frame header
|
||||
int payload = HeaderEncoder.getPayloadLength(frame);
|
||||
int padding = HeaderEncoder.getPaddingLength(frame);
|
||||
int length = HEADER_LENGTH + payload + padding + MAC_LENGTH;
|
||||
if(length > MAX_FRAME_LENGTH) throw new FormatException();
|
||||
// Read the remainder of the frame
|
||||
while(offset < length) {
|
||||
int read = in.read(ciphertext, offset, length - offset);
|
||||
if(read == -1) throw new EOFException();
|
||||
offset += read;
|
||||
}
|
||||
// Decrypt and authenticate the entire frame
|
||||
frameIvEncoder.updateIv(frameIv, frameNumber);
|
||||
ivSpec = new IvParameterSpec(frameIv);
|
||||
try {
|
||||
frameCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec);
|
||||
int decrypted = frameCipher.doFinal(ciphertext, 0, length,
|
||||
frame);
|
||||
if(decrypted != length - MAC_LENGTH)
|
||||
throw new RuntimeException();
|
||||
} catch(GeneralSecurityException badCipher) {
|
||||
throw new RuntimeException(badCipher);
|
||||
}
|
||||
frameNumber++;
|
||||
return true;
|
||||
} catch(IOException e) {
|
||||
frameKey.erase();
|
||||
tagKey.erase();
|
||||
throw e;
|
||||
}
|
||||
int plaintextLength = ciphertextLength - MAC_LENGTH;
|
||||
if(plaintextLength < HEADER_LENGTH) throw new EOFException();
|
||||
// Decrypt and authenticate the frame
|
||||
FrameEncoder.encodeIv(iv, frameNumber);
|
||||
FrameEncoder.encodeAad(aad, frameNumber, plaintextLength);
|
||||
try {
|
||||
frameCipher.init(DECRYPT_MODE, frameKey, iv, aad);
|
||||
int decrypted = frameCipher.doFinal(ciphertext, 0, ciphertextLength,
|
||||
frame, 0);
|
||||
if(decrypted != plaintextLength) throw new RuntimeException();
|
||||
} catch(GeneralSecurityException badCipher) {
|
||||
throw new RuntimeException(badCipher);
|
||||
}
|
||||
// Decode and validate the header
|
||||
lastFrame = FrameEncoder.isLastFrame(frame);
|
||||
if(!lastFrame && ciphertextLength < maxFrameLength)
|
||||
throw new EOFException();
|
||||
int payloadLength = FrameEncoder.getPayloadLength(frame);
|
||||
if(payloadLength > plaintextLength - HEADER_LENGTH)
|
||||
throw new FormatException();
|
||||
// If there's any padding it must be all zeroes
|
||||
for(int i = HEADER_LENGTH + payloadLength; i < plaintextLength; i++)
|
||||
if(frame[i] != 0) throw new FormatException();
|
||||
frameNumber++;
|
||||
return payloadLength;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import static javax.crypto.Cipher.ENCRYPT_MODE;
|
||||
import static net.sf.briar.api.transport.TransportConstants.AAD_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -10,62 +12,85 @@ import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
import net.sf.briar.api.crypto.AuthenticatedCipher;
|
||||
import net.sf.briar.api.crypto.ErasableKey;
|
||||
import net.sf.briar.api.crypto.IvEncoder;
|
||||
|
||||
class OutgoingEncryptionLayer implements FrameWriter {
|
||||
|
||||
private final OutputStream out;
|
||||
private final Cipher tagCipher, frameCipher;
|
||||
private final IvEncoder frameIvEncoder;
|
||||
private final Cipher tagCipher;
|
||||
private final AuthenticatedCipher frameCipher;
|
||||
private final ErasableKey tagKey, frameKey;
|
||||
private final byte[] frameIv, ciphertext;
|
||||
private final byte[] iv, aad, ciphertext;
|
||||
private final int maxFrameLength;
|
||||
|
||||
private boolean writeTag;
|
||||
private long capacity, frameNumber;
|
||||
|
||||
OutgoingEncryptionLayer(OutputStream out, long capacity, Cipher tagCipher,
|
||||
Cipher frameCipher, IvEncoder frameIvEncoder, ErasableKey tagKey,
|
||||
ErasableKey frameKey) {
|
||||
AuthenticatedCipher frameCipher, ErasableKey tagKey,
|
||||
ErasableKey frameKey, boolean writeTag, int maxFrameLength) {
|
||||
this.out = out;
|
||||
this.capacity = capacity;
|
||||
this.tagCipher = tagCipher;
|
||||
this.frameCipher = frameCipher;
|
||||
this.frameIvEncoder = frameIvEncoder;
|
||||
this.tagKey = tagKey;
|
||||
this.frameKey = frameKey;
|
||||
frameIv = frameIvEncoder.encodeIv(0L);
|
||||
ciphertext = new byte[TAG_LENGTH + MAX_FRAME_LENGTH];
|
||||
this.writeTag = writeTag;
|
||||
this.maxFrameLength = maxFrameLength;
|
||||
iv = new byte[IV_LENGTH];
|
||||
aad = new byte[AAD_LENGTH];
|
||||
ciphertext = new byte[maxFrameLength];
|
||||
frameNumber = 0L;
|
||||
}
|
||||
|
||||
public void writeFrame(byte[] frame) throws IOException {
|
||||
int payload = HeaderEncoder.getPayloadLength(frame);
|
||||
int padding = HeaderEncoder.getPaddingLength(frame);
|
||||
int offset = 0, length = HEADER_LENGTH + payload + padding;
|
||||
if(frameNumber == 0) {
|
||||
public void writeFrame(byte[] frame, int payloadLength, int paddingLength,
|
||||
boolean lastFrame) throws IOException {
|
||||
int plaintextLength = HEADER_LENGTH + payloadLength + paddingLength;
|
||||
int ciphertextLength = plaintextLength + MAC_LENGTH;
|
||||
if(ciphertextLength > maxFrameLength)
|
||||
throw new IllegalArgumentException();
|
||||
if(!lastFrame && ciphertextLength < maxFrameLength)
|
||||
throw new IllegalArgumentException();
|
||||
// Write the tag if required
|
||||
if(writeTag) {
|
||||
TagEncoder.encodeTag(ciphertext, tagCipher, tagKey);
|
||||
offset = TAG_LENGTH;
|
||||
try {
|
||||
out.write(ciphertext, 0, TAG_LENGTH);
|
||||
} catch(IOException e) {
|
||||
frameKey.erase();
|
||||
tagKey.erase();
|
||||
throw e;
|
||||
}
|
||||
capacity -= TAG_LENGTH;
|
||||
writeTag = false;
|
||||
}
|
||||
frameIvEncoder.updateIv(frameIv, frameNumber);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(frameIv);
|
||||
// Encode the header
|
||||
FrameEncoder.encodeHeader(frame, lastFrame, payloadLength);
|
||||
// If there's any padding it must all be zeroes
|
||||
for(int i = HEADER_LENGTH + payloadLength; i < plaintextLength; i++)
|
||||
frame[i] = 0;
|
||||
// Encrypt and authenticate the frame
|
||||
FrameEncoder.encodeIv(iv, frameNumber);
|
||||
FrameEncoder.encodeAad(aad, frameNumber, plaintextLength);
|
||||
try {
|
||||
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
|
||||
int encrypted = frameCipher.doFinal(frame, 0, length, ciphertext,
|
||||
offset);
|
||||
if(encrypted != length + MAC_LENGTH) throw new RuntimeException();
|
||||
frameCipher.init(ENCRYPT_MODE, frameKey, iv, aad);
|
||||
int encrypted = frameCipher.doFinal(frame, 0, plaintextLength,
|
||||
ciphertext, 0);
|
||||
if(encrypted != ciphertextLength) throw new RuntimeException();
|
||||
} catch(GeneralSecurityException badCipher) {
|
||||
throw new RuntimeException(badCipher);
|
||||
}
|
||||
// Write the frame
|
||||
try {
|
||||
out.write(ciphertext, 0, offset + length + MAC_LENGTH);
|
||||
out.write(ciphertext, 0, ciphertextLength);
|
||||
} catch(IOException e) {
|
||||
frameKey.erase();
|
||||
tagKey.erase();
|
||||
throw e;
|
||||
}
|
||||
capacity -= offset + length + MAC_LENGTH;
|
||||
capacity -= ciphertextLength;
|
||||
frameNumber++;
|
||||
}
|
||||
|
||||
@@ -74,6 +99,6 @@ class OutgoingEncryptionLayer implements FrameWriter {
|
||||
}
|
||||
|
||||
public long getRemainingCapacity() {
|
||||
return capacity;
|
||||
return writeTag ? capacity - TAG_LENGTH : capacity;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import static javax.crypto.Cipher.DECRYPT_MODE;
|
||||
import static javax.crypto.Cipher.ENCRYPT_MODE;
|
||||
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
@@ -15,7 +17,7 @@ class TagEncoder {
|
||||
// Blank plaintext
|
||||
for(int i = 0; i < TAG_LENGTH; i++) tag[i] = 0;
|
||||
try {
|
||||
tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
|
||||
tagCipher.init(ENCRYPT_MODE, tagKey);
|
||||
int encrypted = tagCipher.doFinal(tag, 0, TAG_LENGTH, tag);
|
||||
if(encrypted != TAG_LENGTH) throw new IllegalArgumentException();
|
||||
} catch(GeneralSecurityException e) {
|
||||
@@ -27,7 +29,7 @@ class TagEncoder {
|
||||
static boolean decodeTag(byte[] tag, Cipher tagCipher, ErasableKey tagKey) {
|
||||
if(tag.length < TAG_LENGTH) throw new IllegalArgumentException();
|
||||
try {
|
||||
tagCipher.init(Cipher.DECRYPT_MODE, tagKey);
|
||||
tagCipher.init(DECRYPT_MODE, tagKey);
|
||||
int decrypted = tagCipher.doFinal(tag, 0, TAG_LENGTH, tag);
|
||||
if(decrypted != TAG_LENGTH) throw new IllegalArgumentException();
|
||||
//The plaintext should be blank
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
<test name='net.sf.briar.ProtocolIntegrationTest'/>
|
||||
<test name='net.sf.briar.crypto.CounterModeTest'/>
|
||||
<test name='net.sf.briar.crypto.ErasableKeyTest'/>
|
||||
<test name='net.sf.briar.crypto.FramePeekingTest'/>
|
||||
<test name='net.sf.briar.crypto.KeyDerivationTest'/>
|
||||
<test name='net.sf.briar.db.BasicH2Test'/>
|
||||
<test name='net.sf.briar.db.DatabaseCleanerImplTest'/>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package net.sf.briar.crypto;
|
||||
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
import net.sf.briar.BriarTestCase;
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.ErasableKey;
|
||||
import net.sf.briar.api.crypto.IvEncoder;
|
||||
import net.sf.briar.util.ByteUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class FramePeekingTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testFramePeeking() throws Exception {
|
||||
CryptoComponent crypto = new CryptoComponentImpl();
|
||||
ErasableKey key = crypto.generateTestKey();
|
||||
|
||||
Cipher frameCipher = crypto.getFrameCipher();
|
||||
IvEncoder frameIvEncoder = crypto.getFrameIvEncoder();
|
||||
byte[] iv = frameIvEncoder.encodeIv(ByteUtils.MAX_32_BIT_UNSIGNED);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
frameCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
|
||||
|
||||
Cipher framePeekingCipher = crypto.getFramePeekingCipher();
|
||||
IvEncoder framePeekingIvEncoder = crypto.getFramePeekingIvEncoder();
|
||||
iv = framePeekingIvEncoder.encodeIv(ByteUtils.MAX_32_BIT_UNSIGNED);
|
||||
ivSpec = new IvParameterSpec(iv);
|
||||
framePeekingCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
|
||||
|
||||
// The ciphers should produce the same ciphertext, apart from the MAC
|
||||
byte[] plaintext = new byte[123];
|
||||
byte[] ciphertext = frameCipher.doFinal(plaintext);
|
||||
byte[] peekingCiphertext = framePeekingCipher.doFinal(plaintext);
|
||||
assertEquals(ciphertext.length, peekingCiphertext.length + MAC_LENGTH);
|
||||
for(int i = 0; i < peekingCiphertext.length; i++) {
|
||||
assertEquals(ciphertext[i], peekingCiphertext[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
package net.sf.briar.protocol.simplex;
|
||||
|
||||
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -127,8 +131,9 @@ public class OutgoingSimplexConnectionTest extends BriarTestCase {
|
||||
will(returnValue(null));
|
||||
}});
|
||||
connection.write();
|
||||
// Nothing should have been written
|
||||
assertEquals(0, out.size());
|
||||
// Nothing should have been written except the tag and an empty frame
|
||||
int nothing = TAG_LENGTH + HEADER_LENGTH + MAC_LENGTH;
|
||||
assertEquals(nothing, out.size());
|
||||
// The transport should have been disposed with exception == false
|
||||
assertTrue(transport.getDisposed());
|
||||
assertFalse(transport.getException());
|
||||
@@ -178,7 +183,8 @@ public class OutgoingSimplexConnectionTest extends BriarTestCase {
|
||||
}});
|
||||
connection.write();
|
||||
// Something should have been written
|
||||
assertTrue(out.size() > UniqueId.LENGTH + message.length);
|
||||
int nothing = TAG_LENGTH + HEADER_LENGTH + MAC_LENGTH;
|
||||
assertTrue(out.size() > nothing + UniqueId.LENGTH + message.length);
|
||||
// The transport should have been disposed with exception == false
|
||||
assertTrue(transport.getDisposed());
|
||||
assertFalse(transport.getException());
|
||||
|
||||
@@ -1,182 +1,12 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import org.junit.Test;
|
||||
|
||||
import net.sf.briar.BriarTestCase;
|
||||
import net.sf.briar.TestUtils;
|
||||
import net.sf.briar.api.FormatException;
|
||||
import net.sf.briar.api.transport.ConnectionReader;
|
||||
|
||||
import org.apache.commons.io.output.ByteArrayOutputStream;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ConnectionReaderImplTest extends BriarTestCase {
|
||||
|
||||
private static final int MAX_PAYLOAD_LENGTH =
|
||||
MAX_FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
|
||||
|
||||
public ConnectionReaderImplTest() throws Exception {
|
||||
super();
|
||||
}
|
||||
|
||||
// FIXME: Write tests
|
||||
@Test
|
||||
public void testLengthZero() throws Exception {
|
||||
byte[] frame = new byte[HEADER_LENGTH + MAC_LENGTH];
|
||||
HeaderEncoder.encodeHeader(frame, 0, 0, 0, true);
|
||||
// Read the frame
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(frame);
|
||||
ConnectionReader r = createConnectionReader(in);
|
||||
// There should be no bytes available before EOF
|
||||
assertEquals(-1, r.getInputStream().read());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLengthOne() throws Exception {
|
||||
byte[] frame = new byte[HEADER_LENGTH + 1 + MAC_LENGTH];
|
||||
HeaderEncoder.encodeHeader(frame, 0, 1, 0, true);
|
||||
// Read the frame
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(frame);
|
||||
ConnectionReader r = createConnectionReader(in);
|
||||
// There should be one byte available before EOF
|
||||
assertEquals(0, r.getInputStream().read());
|
||||
assertEquals(-1, r.getInputStream().read());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaxLength() throws Exception {
|
||||
// First frame: max payload length
|
||||
byte[] frame = new byte[MAX_FRAME_LENGTH];
|
||||
HeaderEncoder.encodeHeader(frame, 0, MAX_PAYLOAD_LENGTH, 0, false);
|
||||
// Second frame: max payload length plus one
|
||||
byte[] frame1 = new byte[MAX_FRAME_LENGTH + 1];
|
||||
HeaderEncoder.encodeHeader(frame1, 1, MAX_PAYLOAD_LENGTH + 1, 0, false);
|
||||
// Concatenate the frames
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(frame);
|
||||
out.write(frame1);
|
||||
// Read the first frame
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
ConnectionReader r = createConnectionReader(in);
|
||||
byte[] read = new byte[MAX_PAYLOAD_LENGTH];
|
||||
TestUtils.readFully(r.getInputStream(), read);
|
||||
// Try to read the second frame
|
||||
byte[] read1 = new byte[MAX_PAYLOAD_LENGTH + 1];
|
||||
try {
|
||||
TestUtils.readFully(r.getInputStream(), read1);
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaxLengthWithPadding() throws Exception {
|
||||
int paddingLength = 10;
|
||||
// First frame: max payload length, including padding
|
||||
byte[] frame = new byte[MAX_FRAME_LENGTH];
|
||||
HeaderEncoder.encodeHeader(frame, 0, MAX_PAYLOAD_LENGTH - paddingLength,
|
||||
paddingLength, false);
|
||||
// Second frame: max payload length plus one, including padding
|
||||
byte[] frame1 = new byte[MAX_FRAME_LENGTH + 1];
|
||||
HeaderEncoder.encodeHeader(frame1, 1,
|
||||
MAX_PAYLOAD_LENGTH + 1 - paddingLength, paddingLength, false);
|
||||
// Concatenate the frames
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(frame);
|
||||
out.write(frame1);
|
||||
// Read the first frame
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
ConnectionReader r = createConnectionReader(in);
|
||||
byte[] read = new byte[MAX_PAYLOAD_LENGTH - paddingLength];
|
||||
TestUtils.readFully(r.getInputStream(), read);
|
||||
// Try to read the second frame
|
||||
byte[] read1 = new byte[MAX_PAYLOAD_LENGTH + 1 - paddingLength];
|
||||
try {
|
||||
TestUtils.readFully(r.getInputStream(), read1);
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonZeroPadding() throws Exception {
|
||||
int payloadLength = 10, paddingLength = 10;
|
||||
byte[] frame = new byte[HEADER_LENGTH + payloadLength + paddingLength
|
||||
+ MAC_LENGTH];
|
||||
HeaderEncoder.encodeHeader(frame, 0, payloadLength, paddingLength,
|
||||
false);
|
||||
// Set a byte of the padding to a non-zero value
|
||||
frame[HEADER_LENGTH + payloadLength] = 1;
|
||||
// Read the frame
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(frame);
|
||||
ConnectionReader r = createConnectionReader(in);
|
||||
// The non-zero padding should be rejected
|
||||
try {
|
||||
r.getInputStream().read();
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleFrames() throws Exception {
|
||||
// First frame: 123-byte payload
|
||||
int payloadLength = 123;
|
||||
byte[] frame = new byte[HEADER_LENGTH + payloadLength + MAC_LENGTH];
|
||||
HeaderEncoder.encodeHeader(frame, 0, payloadLength, 0, false);
|
||||
// Second frame: 1234-byte payload
|
||||
int payloadLength1 = 1234;
|
||||
byte[] frame1 = new byte[HEADER_LENGTH + payloadLength1 + MAC_LENGTH];
|
||||
HeaderEncoder.encodeHeader(frame1, 1, payloadLength1, 0, true);
|
||||
// Concatenate the frames
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(frame);
|
||||
out.write(frame1);
|
||||
// Read the frames
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
ConnectionReader r = createConnectionReader(in);
|
||||
byte[] read = new byte[payloadLength];
|
||||
TestUtils.readFully(r.getInputStream(), read);
|
||||
assertArrayEquals(new byte[payloadLength], read);
|
||||
byte[] read1 = new byte[payloadLength1];
|
||||
TestUtils.readFully(r.getInputStream(), read1);
|
||||
assertArrayEquals(new byte[payloadLength1], read1);
|
||||
assertEquals(-1, r.getInputStream().read());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLastFrameNotMarkedAsSuch() throws Exception {
|
||||
// First frame: 123-byte payload
|
||||
int payloadLength = 123;
|
||||
byte[] frame = new byte[HEADER_LENGTH + payloadLength + MAC_LENGTH];
|
||||
HeaderEncoder.encodeHeader(frame, 0, payloadLength, 0, false);
|
||||
// Second frame: 1234-byte payload
|
||||
int payloadLength1 = 1234;
|
||||
byte[] frame1 = new byte[HEADER_LENGTH + payloadLength1 + MAC_LENGTH];
|
||||
HeaderEncoder.encodeHeader(frame1, 1, payloadLength1, 0, false);
|
||||
// Concatenate the frames
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(frame);
|
||||
out.write(frame1);
|
||||
// Read the frames
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
ConnectionReader r = createConnectionReader(in);
|
||||
byte[] read = new byte[payloadLength];
|
||||
TestUtils.readFully(r.getInputStream(), read);
|
||||
assertArrayEquals(new byte[payloadLength], read);
|
||||
byte[] read1 = new byte[payloadLength1];
|
||||
TestUtils.readFully(r.getInputStream(), read1);
|
||||
assertArrayEquals(new byte[payloadLength1], read1);
|
||||
try {
|
||||
r.getInputStream().read();
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
private ConnectionReader createConnectionReader(InputStream in) {
|
||||
FrameReader encryption = new NullIncomingEncryptionLayer(in);
|
||||
return new ConnectionReaderImpl(encryption);
|
||||
}
|
||||
public void testNothing() {}
|
||||
}
|
||||
|
||||
@@ -1,102 +1,12 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.sf.briar.BriarTestCase;
|
||||
import net.sf.briar.api.transport.ConnectionWriter;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import net.sf.briar.BriarTestCase;
|
||||
|
||||
public class ConnectionWriterImplTest extends BriarTestCase {
|
||||
|
||||
private static final int MAX_PAYLOAD_LENGTH =
|
||||
MAX_FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
|
||||
|
||||
public ConnectionWriterImplTest() throws Exception {
|
||||
super();
|
||||
}
|
||||
|
||||
// FIXME: Write tests
|
||||
@Test
|
||||
public void testFlushWithoutWriteProducesNothing() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ConnectionWriter w = createConnectionWriter(out);
|
||||
w.getOutputStream().flush();
|
||||
w.getOutputStream().flush();
|
||||
w.getOutputStream().flush();
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleByteFrame() throws Exception {
|
||||
// Create a single-byte frame
|
||||
byte[] frame = new byte[HEADER_LENGTH + 1 + MAC_LENGTH];
|
||||
HeaderEncoder.encodeHeader(frame, 0, 1, 0, false);
|
||||
// Check that the ConnectionWriter gets the same results
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ConnectionWriter w = createConnectionWriter(out);
|
||||
w.getOutputStream().write(0);
|
||||
w.getOutputStream().flush();
|
||||
assertArrayEquals(frame, out.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteByteToMaxLengthWritesFrame() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ConnectionWriter w = createConnectionWriter(out);
|
||||
OutputStream out1 = w.getOutputStream();
|
||||
// The first maxPayloadLength - 1 bytes should be buffered
|
||||
for(int i = 0; i < MAX_PAYLOAD_LENGTH - 1; i++) out1.write(0);
|
||||
assertEquals(0, out.size());
|
||||
// The next byte should trigger the writing of a frame
|
||||
out1.write(0);
|
||||
assertEquals(MAX_FRAME_LENGTH, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteArrayToMaxLengthWritesFrame() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ConnectionWriter w = createConnectionWriter(out);
|
||||
OutputStream out1 = w.getOutputStream();
|
||||
// The first maxPayloadLength - 1 bytes should be buffered
|
||||
out1.write(new byte[MAX_PAYLOAD_LENGTH - 1]);
|
||||
assertEquals(0, out.size());
|
||||
// The next maxPayloadLength + 1 bytes should trigger two frames
|
||||
out1.write(new byte[MAX_PAYLOAD_LENGTH + 1]);
|
||||
assertEquals(MAX_FRAME_LENGTH * 2, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleFrames() throws Exception {
|
||||
// First frame: 123-byte payload
|
||||
byte[] frame = new byte[HEADER_LENGTH + 123 + MAC_LENGTH];
|
||||
HeaderEncoder.encodeHeader(frame, 0, 123, 0, false);
|
||||
// Second frame: 1234-byte payload
|
||||
byte[] frame1 = new byte[HEADER_LENGTH + 1234 + MAC_LENGTH];
|
||||
HeaderEncoder.encodeHeader(frame1, 1, 1234, 0, false);
|
||||
// Concatenate the frames
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(frame);
|
||||
out.write(frame1);
|
||||
byte[] expected = out.toByteArray();
|
||||
// Check that the ConnectionWriter gets the same results
|
||||
out.reset();
|
||||
ConnectionWriter w = createConnectionWriter(out);
|
||||
w.getOutputStream().write(new byte[123]);
|
||||
w.getOutputStream().flush();
|
||||
w.getOutputStream().write(new byte[1234]);
|
||||
w.getOutputStream().flush();
|
||||
byte[] actual = out.toByteArray();
|
||||
assertArrayEquals(expected, actual);
|
||||
}
|
||||
|
||||
private ConnectionWriter createConnectionWriter(OutputStream out) {
|
||||
FrameWriter encryption = new NullOutgoingEncryptionLayer(out);
|
||||
return new ConnectionWriterImpl(encryption);
|
||||
}
|
||||
public void testNothing() {}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public class ConnectionWriterTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverhead() throws Exception {
|
||||
public void testOverheadWithTag() throws Exception {
|
||||
ByteArrayOutputStream out =
|
||||
new ByteArrayOutputStream(MIN_CONNECTION_LENGTH);
|
||||
ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out,
|
||||
@@ -53,7 +53,26 @@ public class ConnectionWriterTest extends BriarTestCase {
|
||||
// Check that there really is room for a packet
|
||||
byte[] payload = new byte[MAX_PACKET_LENGTH];
|
||||
w.getOutputStream().write(payload);
|
||||
w.getOutputStream().flush();
|
||||
w.getOutputStream().close();
|
||||
long used = out.size();
|
||||
assertTrue(used >= MAX_PACKET_LENGTH);
|
||||
assertTrue(used <= MIN_CONNECTION_LENGTH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverheadWithoutTag() throws Exception {
|
||||
ByteArrayOutputStream out =
|
||||
new ByteArrayOutputStream(MIN_CONNECTION_LENGTH);
|
||||
ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out,
|
||||
MIN_CONNECTION_LENGTH, secret, false);
|
||||
// Check that the connection writer thinks there's room for a packet
|
||||
long capacity = w.getRemainingCapacity();
|
||||
assertTrue(capacity >= MAX_PACKET_LENGTH);
|
||||
assertTrue(capacity <= MIN_CONNECTION_LENGTH);
|
||||
// Check that there really is room for a packet
|
||||
byte[] payload = new byte[MAX_PACKET_LENGTH];
|
||||
w.getOutputStream().write(payload);
|
||||
w.getOutputStream().close();
|
||||
long used = out.size();
|
||||
assertTrue(used >= MAX_PACKET_LENGTH);
|
||||
assertTrue(used <= MIN_CONNECTION_LENGTH);
|
||||
|
||||
@@ -12,9 +12,9 @@ import java.util.Random;
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import net.sf.briar.BriarTestCase;
|
||||
import net.sf.briar.api.crypto.AuthenticatedCipher;
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.ErasableKey;
|
||||
import net.sf.briar.api.crypto.IvEncoder;
|
||||
import net.sf.briar.api.transport.ConnectionReader;
|
||||
import net.sf.briar.api.transport.ConnectionWriter;
|
||||
import net.sf.briar.crypto.CryptoModule;
|
||||
@@ -26,9 +26,11 @@ import com.google.inject.Injector;
|
||||
|
||||
public class FrameReadWriteTest extends BriarTestCase {
|
||||
|
||||
private final int FRAME_LENGTH = 2048;
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final Cipher tagCipher, frameCipher, framePeekingCipher;
|
||||
private final IvEncoder frameIvEncoder, framePeekingIvEncoder;
|
||||
private final Cipher tagCipher;
|
||||
private final AuthenticatedCipher frameCipher;
|
||||
private final Random random;
|
||||
private final byte[] outSecret;
|
||||
private final ErasableKey tagKey, frameKey;
|
||||
@@ -39,9 +41,6 @@ public class FrameReadWriteTest extends BriarTestCase {
|
||||
crypto = i.getInstance(CryptoComponent.class);
|
||||
tagCipher = crypto.getTagCipher();
|
||||
frameCipher = crypto.getFrameCipher();
|
||||
framePeekingCipher = crypto.getFramePeekingCipher();
|
||||
frameIvEncoder = crypto.getFrameIvEncoder();
|
||||
framePeekingIvEncoder = crypto.getFramePeekingIvEncoder();
|
||||
random = new Random();
|
||||
// Since we're sending frames to ourselves, we only need outgoing keys
|
||||
outSecret = new byte[32];
|
||||
@@ -65,7 +64,7 @@ public class FrameReadWriteTest extends BriarTestCase {
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
TagEncoder.encodeTag(tag, tagCipher, tagKey);
|
||||
// Generate two random frames
|
||||
byte[] frame = new byte[12345];
|
||||
byte[] frame = new byte[1234];
|
||||
random.nextBytes(frame);
|
||||
byte[] frame1 = new byte[321];
|
||||
random.nextBytes(frame1);
|
||||
@@ -75,25 +74,23 @@ public class FrameReadWriteTest extends BriarTestCase {
|
||||
// Write the frames
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
FrameWriter encryptionOut = new OutgoingEncryptionLayer(out,
|
||||
Long.MAX_VALUE, tagCipher, frameCipher, frameIvEncoder, tagCopy,
|
||||
frameCopy);
|
||||
ConnectionWriter writer = new ConnectionWriterImpl(encryptionOut);
|
||||
Long.MAX_VALUE, tagCipher, frameCipher, tagCopy, frameCopy,
|
||||
true, FRAME_LENGTH);
|
||||
ConnectionWriter writer = new ConnectionWriterImpl(encryptionOut,
|
||||
FRAME_LENGTH);
|
||||
OutputStream out1 = writer.getOutputStream();
|
||||
out1.write(frame);
|
||||
out1.flush();
|
||||
out1.write(frame1);
|
||||
out1.flush();
|
||||
// Read the tag back
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
byte[] recoveredTag = new byte[TAG_LENGTH];
|
||||
assertEquals(TAG_LENGTH, in.read(recoveredTag));
|
||||
assertArrayEquals(tag, recoveredTag);
|
||||
assertTrue(TagEncoder.decodeTag(recoveredTag, tagCipher, tagKey));
|
||||
// Read the frames back
|
||||
FrameReader encryptionIn = new IncomingEncryptionLayer(in,
|
||||
tagCipher, frameCipher, framePeekingCipher, frameIvEncoder,
|
||||
framePeekingIvEncoder, tagKey, frameKey, false);
|
||||
ConnectionReader reader = new ConnectionReaderImpl(encryptionIn);
|
||||
byte[] output = out.toByteArray();
|
||||
assertEquals(TAG_LENGTH + FRAME_LENGTH * 2, output.length);
|
||||
// Read the tag and the frames back
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(output);
|
||||
FrameReader encryptionIn = new IncomingEncryptionLayer(in, tagCipher,
|
||||
frameCipher, tagKey, frameKey, true, FRAME_LENGTH);
|
||||
ConnectionReader reader = new ConnectionReaderImpl(encryptionIn,
|
||||
FRAME_LENGTH);
|
||||
InputStream in1 = reader.getInputStream();
|
||||
byte[] recovered = new byte[frame.length];
|
||||
int offset = 0;
|
||||
|
||||
@@ -1,148 +1,12 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
import net.sf.briar.BriarTestCase;
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.ErasableKey;
|
||||
import net.sf.briar.api.crypto.IvEncoder;
|
||||
import net.sf.briar.crypto.CryptoModule;
|
||||
|
||||
import org.apache.commons.io.output.ByteArrayOutputStream;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
public class IncomingEncryptionLayerTest extends BriarTestCase {
|
||||
|
||||
private final Cipher tagCipher, frameCipher, framePeekingCipher;
|
||||
private final IvEncoder frameIvEncoder, framePeekingIvEncoder;
|
||||
private final ErasableKey tagKey, frameKey;
|
||||
|
||||
public IncomingEncryptionLayerTest() {
|
||||
super();
|
||||
Injector i = Guice.createInjector(new CryptoModule());
|
||||
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
|
||||
tagCipher = crypto.getTagCipher();
|
||||
frameCipher = crypto.getFrameCipher();
|
||||
framePeekingCipher = crypto.getFramePeekingCipher();
|
||||
frameIvEncoder = crypto.getFrameIvEncoder();
|
||||
framePeekingIvEncoder = crypto.getFramePeekingIvEncoder();
|
||||
tagKey = crypto.generateTestKey();
|
||||
frameKey = crypto.generateTestKey();
|
||||
}
|
||||
|
||||
// FIXME: Write tests
|
||||
@Test
|
||||
public void testDecryptionWithTag() throws Exception {
|
||||
// Calculate the tag
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
TagEncoder.encodeTag(tag, tagCipher, tagKey);
|
||||
// Calculate the ciphertext for the first frame
|
||||
byte[] plaintext = new byte[HEADER_LENGTH + 123];
|
||||
HeaderEncoder.encodeHeader(plaintext, 0L, 123, 0, false);
|
||||
byte[] iv = frameIvEncoder.encodeIv(0L);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
|
||||
byte[] ciphertext = frameCipher.doFinal(plaintext);
|
||||
// Calculate the ciphertext for the second frame
|
||||
byte[] plaintext1 = new byte[HEADER_LENGTH + 1234];
|
||||
HeaderEncoder.encodeHeader(plaintext1, 1L, 1234, 0, false);
|
||||
frameIvEncoder.updateIv(iv, 1L);
|
||||
ivSpec = new IvParameterSpec(iv);
|
||||
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
|
||||
byte[] ciphertext1 = frameCipher.doFinal(plaintext1, 0,
|
||||
plaintext1.length);
|
||||
// Concatenate the ciphertexts, including the tag
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(tag);
|
||||
out.write(ciphertext);
|
||||
out.write(ciphertext1);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
// Use the encryption layer to decrypt the ciphertext
|
||||
FrameReader decrypter = new IncomingEncryptionLayer(in, tagCipher,
|
||||
frameCipher, framePeekingCipher, frameIvEncoder,
|
||||
framePeekingIvEncoder, tagKey, frameKey, true);
|
||||
// First frame
|
||||
byte[] frame = new byte[MAX_FRAME_LENGTH];
|
||||
assertTrue(decrypter.readFrame(frame));
|
||||
assertEquals(0L, HeaderEncoder.getFrameNumber(frame));
|
||||
int payload = HeaderEncoder.getPayloadLength(frame);
|
||||
assertEquals(123, payload);
|
||||
int padding = HeaderEncoder.getPaddingLength(frame);
|
||||
assertEquals(0, padding);
|
||||
assertEquals(plaintext.length, HEADER_LENGTH + payload + padding);
|
||||
for(int i = 0; i < plaintext.length; i++) {
|
||||
assertEquals(plaintext[i], frame[i]);
|
||||
}
|
||||
// Second frame
|
||||
assertTrue(decrypter.readFrame(frame));
|
||||
assertEquals(1L, HeaderEncoder.getFrameNumber(frame));
|
||||
payload = HeaderEncoder.getPayloadLength(frame);
|
||||
assertEquals(1234, payload);
|
||||
padding = HeaderEncoder.getPaddingLength(frame);
|
||||
assertEquals(0, padding);
|
||||
assertEquals(plaintext1.length, HEADER_LENGTH + payload + padding);
|
||||
for(int i = 0; i < plaintext1.length; i++) {
|
||||
assertEquals(plaintext1[i], frame[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecryptionWithoutTag() throws Exception {
|
||||
// Calculate the ciphertext for the first frame
|
||||
byte[] plaintext = new byte[HEADER_LENGTH + 123];
|
||||
HeaderEncoder.encodeHeader(plaintext, 0L, 123, 0, false);
|
||||
byte[] iv = frameIvEncoder.encodeIv(0L);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
|
||||
byte[] ciphertext = frameCipher.doFinal(plaintext);
|
||||
// Calculate the ciphertext for the second frame
|
||||
byte[] plaintext1 = new byte[HEADER_LENGTH + 1234];
|
||||
HeaderEncoder.encodeHeader(plaintext1, 1L, 1234, 0, false);
|
||||
frameIvEncoder.updateIv(iv, 1L);
|
||||
ivSpec = new IvParameterSpec(iv);
|
||||
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
|
||||
byte[] ciphertext1 = frameCipher.doFinal(plaintext1, 0,
|
||||
plaintext1.length);
|
||||
// Concatenate the ciphertexts, excluding the tag
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(ciphertext);
|
||||
out.write(ciphertext1);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
// Use the encryption layer to decrypt the ciphertext
|
||||
FrameReader decrypter = new IncomingEncryptionLayer(in, tagCipher,
|
||||
frameCipher, framePeekingCipher, frameIvEncoder,
|
||||
framePeekingIvEncoder, tagKey, frameKey, false);
|
||||
// First frame
|
||||
byte[] frame = new byte[MAX_FRAME_LENGTH];
|
||||
assertTrue(decrypter.readFrame(frame));
|
||||
assertEquals(0L, HeaderEncoder.getFrameNumber(frame));
|
||||
int payload = HeaderEncoder.getPayloadLength(frame);
|
||||
assertEquals(123, payload);
|
||||
int padding = HeaderEncoder.getPaddingLength(frame);
|
||||
assertEquals(0, padding);
|
||||
assertEquals(plaintext.length, HEADER_LENGTH + payload + padding);
|
||||
for(int i = 0; i < plaintext.length; i++) {
|
||||
assertEquals(plaintext[i], frame[i]);
|
||||
}
|
||||
// Second frame
|
||||
assertTrue(decrypter.readFrame(frame));
|
||||
assertEquals(1L, HeaderEncoder.getFrameNumber(frame));
|
||||
payload = HeaderEncoder.getPayloadLength(frame);
|
||||
assertEquals(1234, payload);
|
||||
padding = HeaderEncoder.getPaddingLength(frame);
|
||||
assertEquals(0, padding);
|
||||
assertEquals(plaintext1.length, HEADER_LENGTH + payload + padding);
|
||||
for(int i = 0; i < plaintext1.length; i++) {
|
||||
assertEquals(plaintext1[i], frame[i]);
|
||||
}
|
||||
}
|
||||
public void testNothing() {}
|
||||
}
|
||||
|
||||
@@ -19,28 +19,27 @@ class NullIncomingEncryptionLayer implements FrameReader {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
public boolean readFrame(byte[] frame) throws IOException {
|
||||
// Read the frame header
|
||||
int offset = 0, length = HEADER_LENGTH;
|
||||
while(offset < length) {
|
||||
int read = in.read(frame, offset, length - offset);
|
||||
if(read == -1) {
|
||||
if(offset == 0) return false;
|
||||
throw new EOFException();
|
||||
}
|
||||
offset += read;
|
||||
public int readFrame(byte[] frame) throws IOException {
|
||||
// Read the frame
|
||||
int ciphertextLength = 0;
|
||||
while(ciphertextLength < MAX_FRAME_LENGTH) {
|
||||
int read = in.read(frame, ciphertextLength,
|
||||
MAX_FRAME_LENGTH - ciphertextLength);
|
||||
if(read == -1) break; // We'll check the length later
|
||||
ciphertextLength += read;
|
||||
}
|
||||
// Parse the frame header
|
||||
int payload = HeaderEncoder.getPayloadLength(frame);
|
||||
int padding = HeaderEncoder.getPaddingLength(frame);
|
||||
length = HEADER_LENGTH + payload + padding + MAC_LENGTH;
|
||||
if(length > MAX_FRAME_LENGTH) throw new FormatException();
|
||||
// Read the remainder of the frame
|
||||
while(offset < length) {
|
||||
int read = in.read(frame, offset, length - offset);
|
||||
if(read == -1) throw new EOFException();
|
||||
offset += read;
|
||||
}
|
||||
return true;
|
||||
int plaintextLength = ciphertextLength - MAC_LENGTH;
|
||||
if(plaintextLength < HEADER_LENGTH) throw new EOFException();
|
||||
// Decode and validate the header
|
||||
boolean lastFrame = FrameEncoder.isLastFrame(frame);
|
||||
if(!lastFrame && ciphertextLength < MAX_FRAME_LENGTH)
|
||||
throw new EOFException();
|
||||
int payloadLength = FrameEncoder.getPayloadLength(frame);
|
||||
if(payloadLength > plaintextLength - HEADER_LENGTH)
|
||||
throw new FormatException();
|
||||
// If there's any padding it must be all zeroes
|
||||
for(int i = HEADER_LENGTH + payloadLength; i < plaintextLength; i++)
|
||||
if(frame[i] != 0) throw new FormatException();
|
||||
return payloadLength;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,18 @@ class NullOutgoingEncryptionLayer implements FrameWriter {
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
public void writeFrame(byte[] frame) throws IOException {
|
||||
int payload = HeaderEncoder.getPayloadLength(frame);
|
||||
int padding = HeaderEncoder.getPaddingLength(frame);
|
||||
int length = HEADER_LENGTH + payload + padding + MAC_LENGTH;
|
||||
out.write(frame, 0, length);
|
||||
capacity -= length;
|
||||
public void writeFrame(byte[] frame, int payloadLength, int paddingLength,
|
||||
boolean lastFrame) throws IOException {
|
||||
int plaintextLength = HEADER_LENGTH + payloadLength + paddingLength;
|
||||
int ciphertextLength = plaintextLength + MAC_LENGTH;
|
||||
// Encode the header
|
||||
FrameEncoder.encodeHeader(frame, lastFrame, payloadLength);
|
||||
// If there's any padding it must all be zeroes
|
||||
for(int i = HEADER_LENGTH + payloadLength; i < plaintextLength; i++)
|
||||
frame[i] = 0;
|
||||
// Write the frame
|
||||
out.write(frame, 0, ciphertextLength);
|
||||
capacity -= ciphertextLength;
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
|
||||
@@ -1,77 +1,12 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
import net.sf.briar.BriarTestCase;
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.ErasableKey;
|
||||
import net.sf.briar.api.crypto.IvEncoder;
|
||||
import net.sf.briar.crypto.CryptoModule;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import net.sf.briar.BriarTestCase;
|
||||
|
||||
public class OutgoingEncryptionLayerTest extends BriarTestCase {
|
||||
|
||||
private final Cipher tagCipher, frameCipher;
|
||||
private final IvEncoder frameIvEncoder;
|
||||
private final ErasableKey tagKey, frameKey;
|
||||
|
||||
public OutgoingEncryptionLayerTest() {
|
||||
super();
|
||||
Injector i = Guice.createInjector(new CryptoModule());
|
||||
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
|
||||
tagCipher = crypto.getTagCipher();
|
||||
frameCipher = crypto.getFrameCipher();
|
||||
frameIvEncoder = crypto.getFrameIvEncoder();
|
||||
tagKey = crypto.generateTestKey();
|
||||
frameKey = crypto.generateTestKey();
|
||||
}
|
||||
|
||||
// FIXME: Write tests
|
||||
@Test
|
||||
public void testEncryptionWithTag() throws Exception {
|
||||
// Calculate the expected tag
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
TagEncoder.encodeTag(tag, tagCipher, tagKey);
|
||||
// Calculate the expected ciphertext for the first frame
|
||||
byte[] iv = frameIvEncoder.encodeIv(0L);
|
||||
byte[] plaintext = new byte[HEADER_LENGTH + 123];
|
||||
HeaderEncoder.encodeHeader(plaintext, 0L, 123, 0, false);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
|
||||
byte[] ciphertext = frameCipher.doFinal(plaintext);
|
||||
// Calculate the expected ciphertext for the second frame
|
||||
byte[] plaintext1 = new byte[HEADER_LENGTH + 1234];
|
||||
HeaderEncoder.encodeHeader(plaintext1, 1L, 1234, 0, true);
|
||||
frameIvEncoder.updateIv(iv, 1L);
|
||||
ivSpec = new IvParameterSpec(iv);
|
||||
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
|
||||
byte[] ciphertext1 = frameCipher.doFinal(plaintext1);
|
||||
// Concatenate the ciphertexts
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(tag);
|
||||
out.write(ciphertext);
|
||||
out.write(ciphertext1);
|
||||
byte[] expected = out.toByteArray();
|
||||
// Use the encryption layer to encrypt the plaintext
|
||||
out.reset();
|
||||
FrameWriter encrypter = new OutgoingEncryptionLayer(out, Long.MAX_VALUE,
|
||||
tagCipher, frameCipher, frameIvEncoder, tagKey, frameKey);
|
||||
encrypter.writeFrame(plaintext);
|
||||
encrypter.writeFrame(plaintext1);
|
||||
byte[] actual = out.toByteArray();
|
||||
// Check that the actual ciphertext matches the expected ciphertext
|
||||
assertArrayEquals(expected, actual);
|
||||
assertEquals(Long.MAX_VALUE - actual.length,
|
||||
encrypter.getRemainingCapacity());
|
||||
}
|
||||
public void testNothing() {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user