diff --git a/api/net/sf/briar/api/transport/ConnectionReaderFactory.java b/api/net/sf/briar/api/transport/ConnectionReaderFactory.java index ea079fb52f088c576a0ccaa83d5f1dbde63f17c4..4620c5296e0981099f687b6ae9a846ae995ed9ad 100644 --- a/api/net/sf/briar/api/transport/ConnectionReaderFactory.java +++ b/api/net/sf/briar/api/transport/ConnectionReaderFactory.java @@ -12,7 +12,7 @@ public interface ConnectionReaderFactory { * returns. */ ConnectionReader createConnectionReader(InputStream in, byte[] secret, - byte[] tag); + byte[] bufferedTag); /** * Creates a connection reader for a simplex connection or the initiator's @@ -20,7 +20,7 @@ public interface ConnectionReaderFactory { * returns. */ ConnectionReader createConnectionReader(SegmentSource in, byte[] secret, - Segment buffered); + Segment bufferedSegment); /** * Creates a connection reader for the responder's side of a duplex diff --git a/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java b/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java index 7ef9d7adff038cdfe0224c9cb35d5c1a976c92b0..f587991f2de10328fe4c8e110ae6e8e4b7d2fdd9 100644 --- a/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java +++ b/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java @@ -25,8 +25,8 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory { } public ConnectionReader createConnectionReader(InputStream in, - byte[] secret, byte[] tag) { - return createConnectionReader(in, secret, tag, true); + byte[] secret, byte[] bufferedTag) { + return createConnectionReader(in, secret, bufferedTag, true); } public ConnectionReader createConnectionReader(InputStream in, @@ -35,7 +35,7 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory { } private ConnectionReader createConnectionReader(InputStream in, - byte[] secret, byte[] tag, boolean initiator) { + byte[] secret, byte[] bufferedTag, boolean initiator) { // Derive the keys and erase the secret ErasableKey tagKey = crypto.deriveTagKey(secret, initiator); ErasableKey segKey = crypto.deriveSegmentKey(secret, initiator); @@ -45,18 +45,24 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory { Cipher tagCipher = crypto.getTagCipher(); Cipher segCipher = crypto.getSegmentCipher(); IncomingEncryptionLayer decrypter = new IncomingEncryptionLayerImpl(in, - tagCipher, segCipher, tagKey, segKey, false, tag); + tagCipher, segCipher, tagKey, segKey, false, bufferedTag); // No error correction IncomingErrorCorrectionLayer correcter = new NullIncomingErrorCorrectionLayer(decrypter); - // Create the reader - don't tolerate errors + // Create the authenticator Mac mac = crypto.getMac(); - return new ConnectionReaderImpl(correcter, mac, macKey, false); + IncomingAuthenticationLayer authenticator = + new IncomingAuthenticationLayerImpl(correcter, mac, macKey); + // No reordering or retransmission + IncomingReliabilityLayer reliability = + new NullIncomingReliabilityLayer(authenticator); + // Create the reader - don't tolerate errors + return new ConnectionReaderImpl(reliability, false); } public ConnectionReader createConnectionReader(SegmentSource in, - byte[] secret, Segment buffered) { - return createConnectionReader(in, secret, buffered, true); + byte[] secret, Segment bufferedSegment) { + return createConnectionReader(in, secret, bufferedSegment, true); } public ConnectionReader createConnectionReader(SegmentSource in, @@ -65,7 +71,7 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory { } private ConnectionReader createConnectionReader(SegmentSource in, - byte[] secret, Segment buffered, boolean initiator) { + byte[] secret, Segment bufferedSegment, boolean initiator) { // Derive the keys and erase the secret ErasableKey tagKey = crypto.deriveTagKey(secret, initiator); ErasableKey segKey = crypto.deriveSegmentKey(secret, initiator); @@ -76,12 +82,18 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory { Cipher segCipher = crypto.getSegmentCipher(); IncomingEncryptionLayer decrypter = new IncomingSegmentedEncryptionLayer(in, tagCipher, segCipher, - tagKey, segKey, false, buffered); + tagKey, segKey, false, bufferedSegment); // No error correction IncomingErrorCorrectionLayer correcter = new NullIncomingErrorCorrectionLayer(decrypter); - // Create the reader - don't tolerate errors + // Create the authenticator Mac mac = crypto.getMac(); - return new ConnectionReaderImpl(correcter, mac, macKey, false); + IncomingAuthenticationLayer authenticator = + new IncomingAuthenticationLayerImpl(correcter, mac, macKey); + // No reordering or retransmission + IncomingReliabilityLayer reliability = + new NullIncomingReliabilityLayer(authenticator); + // Create the reader - don't tolerate errors + return new ConnectionReaderImpl(reliability, false); } } diff --git a/components/net/sf/briar/transport/ConnectionReaderImpl.java b/components/net/sf/briar/transport/ConnectionReaderImpl.java index e02905813cca46b9161fbb292940ca430ba1f252..581c026ce5875f396811515a0c4a25aa4b3286c1 100644 --- a/components/net/sf/briar/transport/ConnectionReaderImpl.java +++ b/components/net/sf/briar/transport/ConnectionReaderImpl.java @@ -1,45 +1,24 @@ package net.sf.briar.transport; import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH; -import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH; -import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED; import java.io.IOException; import java.io.InputStream; -import java.security.InvalidKeyException; -import java.util.Collection; -import java.util.Collections; - -import javax.crypto.Mac; import net.sf.briar.api.FormatException; -import net.sf.briar.api.crypto.ErasableKey; import net.sf.briar.api.transport.ConnectionReader; class ConnectionReaderImpl extends InputStream implements ConnectionReader { - private final IncomingErrorCorrectionLayer in; - private final Mac mac; + private final IncomingReliabilityLayer in; private final boolean tolerateErrors; private final Frame frame; - private long frameNumber = 0L; private int offset = 0, length = 0; - ConnectionReaderImpl(IncomingErrorCorrectionLayer in, Mac mac, - ErasableKey macKey, boolean tolerateErrors) { + ConnectionReaderImpl(IncomingReliabilityLayer in, boolean tolerateErrors) { this.in = in; - this.mac = mac; this.tolerateErrors = tolerateErrors; - // Initialise the MAC - try { - mac.init(macKey); - } catch(InvalidKeyException e) { - throw new IllegalArgumentException(e); - } - macKey.erase(); - if(mac.getMacLength() != MAC_LENGTH) - throw new IllegalArgumentException(); frame = new Frame(); } @@ -72,49 +51,17 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader { } private boolean readValidFrame() throws IOException { + assert length == 0; while(true) { try { - return readFrame(); + if(!in.readFrame(frame)) return false; + offset = FRAME_HEADER_LENGTH; + length = HeaderEncoder.getPayloadLength(frame.getBuffer()); + return true; } catch(InvalidDataException e) { if(tolerateErrors) continue; throw new FormatException(); } } } - - private boolean readFrame() throws IOException, InvalidDataException { - assert length == 0; - // Don't allow more than 2^32 frames to be read - if(frameNumber > MAX_32_BIT_UNSIGNED) - throw new IllegalStateException(); - // Read a frame - Collection<Long> window = Collections.singleton(frameNumber); - if(!in.readFrame(frame, window)) return false; - // Check that the frame number is correct and the length is legal - byte[] buf = frame.getBuffer(); - if(!HeaderEncoder.validateHeader(buf, frameNumber)) - throw new InvalidDataException(); - // Check that the payload and padding lengths are correct - int payload = HeaderEncoder.getPayloadLength(buf); - int padding = HeaderEncoder.getPaddingLength(buf); - if(frame.getLength() != FRAME_HEADER_LENGTH + payload + padding - + MAC_LENGTH) throw new InvalidDataException(); - // Check that the padding is all zeroes - int paddingStart = FRAME_HEADER_LENGTH + payload; - for(int i = paddingStart; i < paddingStart + padding; i++) { - if(buf[i] != 0) throw new InvalidDataException(); - } - // Check the MAC - int macStart = FRAME_HEADER_LENGTH + payload + padding; - mac.update(buf, 0, macStart); - byte[] expectedMac = mac.doFinal(); - for(int i = 0; i < expectedMac.length; i++) { - if(expectedMac[i] != buf[macStart + i]) - throw new InvalidDataException(); - } - offset = FRAME_HEADER_LENGTH; - length = payload; - frameNumber++; - return true; - } } diff --git a/components/net/sf/briar/transport/IncomingAuthenticationLayer.java b/components/net/sf/briar/transport/IncomingAuthenticationLayer.java new file mode 100644 index 0000000000000000000000000000000000000000..c9559537fb9ef780be1e5e937898c643bb37f369 --- /dev/null +++ b/components/net/sf/briar/transport/IncomingAuthenticationLayer.java @@ -0,0 +1,19 @@ +package net.sf.briar.transport; + +import java.io.IOException; +import java.util.Collection; + +interface IncomingAuthenticationLayer { + + /** + * Reads a frame into the given buffer. The frame number must be contained + * in the given window. Returns false if no more frames can be read from + * the connection. + * @throws IOException if an unrecoverable error occurs and the connection + * must be closed. + * @throws InvalidDataException if a recoverable error occurs. The caller + * may choose whether to retry the read or close the connection. + */ + boolean readFrame(Frame f, Collection<Long> window) throws IOException, + InvalidDataException; +} diff --git a/components/net/sf/briar/transport/IncomingAuthenticationLayerImpl.java b/components/net/sf/briar/transport/IncomingAuthenticationLayerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..14320757c345e9c09ada122b0dc3d125604cd6f4 --- /dev/null +++ b/components/net/sf/briar/transport/IncomingAuthenticationLayerImpl.java @@ -0,0 +1,64 @@ +package net.sf.briar.transport; + +import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH; +import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.util.Collection; + +import javax.crypto.Mac; + +import net.sf.briar.api.crypto.ErasableKey; + +class IncomingAuthenticationLayerImpl implements IncomingAuthenticationLayer { + + private final IncomingErrorCorrectionLayer in; + private final Mac mac; + + IncomingAuthenticationLayerImpl(IncomingErrorCorrectionLayer in, Mac mac, + ErasableKey macKey) { + this.in = in; + this.mac = mac; + // Initialise the MAC + try { + mac.init(macKey); + } catch(InvalidKeyException e) { + throw new IllegalArgumentException(e); + } + macKey.erase(); + if(mac.getMacLength() != MAC_LENGTH) + throw new IllegalArgumentException(); + } + + public boolean readFrame(Frame f, Collection<Long> window) + throws IOException, InvalidDataException { + // Read a frame + if(!in.readFrame(f, window)) return false; + // Check that the length is legal + byte[] buf = f.getBuffer(); + long frameNumber = HeaderEncoder.getFrameNumber(buf); + if(!HeaderEncoder.validateHeader(buf, frameNumber)) + throw new InvalidDataException(); + // Check that the payload and padding lengths are correct + int payload = HeaderEncoder.getPayloadLength(buf); + int padding = HeaderEncoder.getPaddingLength(buf); + if(f.getLength() != FRAME_HEADER_LENGTH + payload + padding + + MAC_LENGTH) throw new InvalidDataException(); + // Check that the padding is all zeroes + int paddingStart = FRAME_HEADER_LENGTH + payload; + for(int i = paddingStart; i < paddingStart + padding; i++) { + if(buf[i] != 0) throw new InvalidDataException(); + } + // Check the MAC + int macStart = FRAME_HEADER_LENGTH + payload + padding; + mac.update(buf, 0, macStart); + byte[] expectedMac = mac.doFinal(); + for(int i = 0; i < expectedMac.length; i++) { + if(expectedMac[i] != buf[macStart + i]) + throw new InvalidDataException(); + } + frameNumber++; + return true; + } +} diff --git a/components/net/sf/briar/transport/IncomingReliabilityLayer.java b/components/net/sf/briar/transport/IncomingReliabilityLayer.java new file mode 100644 index 0000000000000000000000000000000000000000..417a2525f199a747278252ddcd0f32ebb8a7b299 --- /dev/null +++ b/components/net/sf/briar/transport/IncomingReliabilityLayer.java @@ -0,0 +1,16 @@ +package net.sf.briar.transport; + +import java.io.IOException; + +interface IncomingReliabilityLayer { + + /** + * Reads a frame into the given buffer. Returns false if no more frames + * can be read from the connection. + * @throws IOException if an unrecoverable error occurs and the connection + * must be closed. + * @throws InvalidDataException if a recoverable error occurs. The caller + * may choose whether to retry the read or close the connection. + */ + boolean readFrame(Frame f) throws IOException, InvalidDataException; +} diff --git a/components/net/sf/briar/transport/NullIncomingReliabilityLayer.java b/components/net/sf/briar/transport/NullIncomingReliabilityLayer.java new file mode 100644 index 0000000000000000000000000000000000000000..b8b965fc17bac7a238239337b3cbcb8e1141ef66 --- /dev/null +++ b/components/net/sf/briar/transport/NullIncomingReliabilityLayer.java @@ -0,0 +1,24 @@ +package net.sf.briar.transport; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; + +class NullIncomingReliabilityLayer implements IncomingReliabilityLayer { + + private final IncomingAuthenticationLayer in; + + private long frameNumber = 0L; + + NullIncomingReliabilityLayer(IncomingAuthenticationLayer in) { + this.in = in; + } + + public boolean readFrame(Frame f) throws IOException, InvalidDataException { + // Frames must be read in order + Collection<Long> window = Collections.singleton(frameNumber); + if(!in.readFrame(f, window)) return false; + frameNumber++; + return true; + } +} diff --git a/test/net/sf/briar/transport/ConnectionReaderImplTest.java b/test/net/sf/briar/transport/ConnectionReaderImplTest.java index 33358ba418960d35a2165a1f008244138fa3e037..d03330fa914593f8e1f49d9651f5f1faf7df3cfe 100644 --- a/test/net/sf/briar/transport/ConnectionReaderImplTest.java +++ b/test/net/sf/briar/transport/ConnectionReaderImplTest.java @@ -6,6 +6,7 @@ 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 net.sf.briar.TestUtils; import net.sf.briar.api.FormatException; @@ -14,6 +15,7 @@ import net.sf.briar.api.transport.ConnectionReader; import org.apache.commons.io.output.ByteArrayOutputStream; import org.junit.Test; +// FIXME: This test covers too many classes public class ConnectionReaderImplTest extends TransportTest { public ConnectionReaderImplTest() throws Exception { @@ -32,11 +34,7 @@ public class ConnectionReaderImplTest extends TransportTest { mac.doFinal(frame, FRAME_HEADER_LENGTH + payloadLength); // Read the frame ByteArrayInputStream in = new ByteArrayInputStream(frame); - IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in); - IncomingErrorCorrectionLayer correcter = - new NullIncomingErrorCorrectionLayer(decrypter); - ConnectionReader r = new ConnectionReaderImpl(correcter, mac, macKey, - false); + ConnectionReader r = createConnectionReader(in); // There should be no bytes available before EOF assertEquals(-1, r.getInputStream().read()); } @@ -53,11 +51,7 @@ public class ConnectionReaderImplTest extends TransportTest { mac.doFinal(frame, FRAME_HEADER_LENGTH + payloadLength); // Read the frame ByteArrayInputStream in = new ByteArrayInputStream(frame); - IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in); - IncomingErrorCorrectionLayer correcter = - new NullIncomingErrorCorrectionLayer(decrypter); - ConnectionReader r = new ConnectionReaderImpl(correcter, mac, macKey, - false); + ConnectionReader r = createConnectionReader(in); // There should be one byte available before EOF assertEquals(0, r.getInputStream().read()); assertEquals(-1, r.getInputStream().read()); @@ -82,11 +76,7 @@ public class ConnectionReaderImplTest extends TransportTest { out.write(frame1); // Read the first frame ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in); - IncomingErrorCorrectionLayer correcter = - new NullIncomingErrorCorrectionLayer(decrypter); - ConnectionReader r = new ConnectionReaderImpl(correcter, mac, macKey, - false); + ConnectionReader r = createConnectionReader(in); byte[] read = new byte[MAX_PAYLOAD_LENGTH]; TestUtils.readFully(r.getInputStream(), read); // Try to read the second frame @@ -119,11 +109,7 @@ public class ConnectionReaderImplTest extends TransportTest { out.write(frame1); // Read the first frame ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in); - IncomingErrorCorrectionLayer correcter = - new NullIncomingErrorCorrectionLayer(decrypter); - ConnectionReader r = new ConnectionReaderImpl(correcter, mac, macKey, - false); + ConnectionReader r = createConnectionReader(in); byte[] read = new byte[MAX_PAYLOAD_LENGTH - paddingLength]; TestUtils.readFully(r.getInputStream(), read); // Try to read the second frame @@ -148,11 +134,7 @@ public class ConnectionReaderImplTest extends TransportTest { mac.doFinal(frame, FRAME_HEADER_LENGTH + payloadLength + paddingLength); // Read the frame ByteArrayInputStream in = new ByteArrayInputStream(frame); - IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in); - IncomingErrorCorrectionLayer correcter = - new NullIncomingErrorCorrectionLayer(decrypter); - ConnectionReader r = new ConnectionReaderImpl(correcter, mac, macKey, - false); + ConnectionReader r = createConnectionReader(in); // The non-zero padding should be rejected try { r.getInputStream().read(); @@ -183,11 +165,7 @@ public class ConnectionReaderImplTest extends TransportTest { out.write(frame1); // Read the frames ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in); - IncomingErrorCorrectionLayer correcter = - new NullIncomingErrorCorrectionLayer(decrypter); - ConnectionReader r = new ConnectionReaderImpl(correcter, mac, macKey, - false); + ConnectionReader r = createConnectionReader(in); byte[] read = new byte[payloadLength]; TestUtils.readFully(r.getInputStream(), read); assertArrayEquals(new byte[payloadLength], read); @@ -210,11 +188,7 @@ public class ConnectionReaderImplTest extends TransportTest { frame[12] ^= 1; // Try to read the frame - not a single byte should be read ByteArrayInputStream in = new ByteArrayInputStream(frame); - IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in); - IncomingErrorCorrectionLayer correcter = - new NullIncomingErrorCorrectionLayer(decrypter); - ConnectionReader r = new ConnectionReaderImpl(correcter, mac, macKey, - false); + ConnectionReader r = createConnectionReader(in); try { r.getInputStream().read(); fail(); @@ -235,14 +209,21 @@ public class ConnectionReaderImplTest extends TransportTest { frame[17] ^= 1; // Try to read the frame - not a single byte should be read ByteArrayInputStream in = new ByteArrayInputStream(frame); - IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in); - IncomingErrorCorrectionLayer correcter = - new NullIncomingErrorCorrectionLayer(decrypter); - ConnectionReader r = new ConnectionReaderImpl(correcter, mac, macKey, - false); + ConnectionReader r = createConnectionReader(in); try { r.getInputStream().read(); fail(); } catch(FormatException expected) {} } + + private ConnectionReader createConnectionReader(InputStream in) { + IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in); + IncomingErrorCorrectionLayer correcter = + new NullIncomingErrorCorrectionLayer(decrypter); + IncomingAuthenticationLayer authenticator = + new IncomingAuthenticationLayerImpl(correcter, mac, macKey); + IncomingReliabilityLayer reliability = + new NullIncomingReliabilityLayer(authenticator); + return new ConnectionReaderImpl(reliability, false); + } } diff --git a/test/net/sf/briar/transport/FrameReadWriteTest.java b/test/net/sf/briar/transport/FrameReadWriteTest.java index ad25f196bb497fcfa4d21164faf069b3e30f4d43..ef0403f3a0658a74c5031ab1277d3a3f1aa41cfd 100644 --- a/test/net/sf/briar/transport/FrameReadWriteTest.java +++ b/test/net/sf/briar/transport/FrameReadWriteTest.java @@ -97,8 +97,11 @@ public class FrameReadWriteTest extends BriarTestCase { tagCipher, segCipher, tagKey, segKey, false, recoveredTag); IncomingErrorCorrectionLayer correcter1 = new NullIncomingErrorCorrectionLayer(decrypter); - ConnectionReader reader = new ConnectionReaderImpl(correcter1, mac, - macKey, false); + IncomingAuthenticationLayer authenticator = + new IncomingAuthenticationLayerImpl(correcter1, mac, macKey); + IncomingReliabilityLayer reliability = + new NullIncomingReliabilityLayer(authenticator); + ConnectionReader reader = new ConnectionReaderImpl(reliability, false); InputStream in1 = reader.getInputStream(); byte[] recovered = new byte[frame.length]; int offset = 0;