diff --git a/api/net/sf/briar/api/transport/ConnectionReaderFactory.java b/api/net/sf/briar/api/transport/ConnectionReaderFactory.java index fc6f9418ba45cd5c35441a9659dbde79a4565790..a886826af4bc076371b24004d50c93160702f391 100644 --- a/api/net/sf/briar/api/transport/ConnectionReaderFactory.java +++ b/api/net/sf/briar/api/transport/ConnectionReaderFactory.java @@ -5,15 +5,15 @@ import java.io.InputStream; public interface ConnectionReaderFactory { /** - * Creates a connection reader for a batch-mode connection or the - * initiator's side of a stream-mode connection. The secret is erased - * before this method returns. + * Creates a connection reader for a simplex connection or the initiator's + * side of a duplex connection. The secret is erased before this method + * returns. */ ConnectionReader createConnectionReader(InputStream in, byte[] secret, byte[] tag); /** - * Creates a connection reader for the responder's side of a stream-mode + * Creates a connection reader for the responder's side of a duplex * connection. The secret is erased before this method returns. */ ConnectionReader createConnectionReader(InputStream in, byte[] secret); diff --git a/api/net/sf/briar/api/transport/ConnectionWriterFactory.java b/api/net/sf/briar/api/transport/ConnectionWriterFactory.java index 13b03fa38714530786c0d6dd897b0d5fade14963..13fbbb23039827f2780b4e808e11c857d2464502 100644 --- a/api/net/sf/briar/api/transport/ConnectionWriterFactory.java +++ b/api/net/sf/briar/api/transport/ConnectionWriterFactory.java @@ -5,15 +5,15 @@ import java.io.OutputStream; public interface ConnectionWriterFactory { /** - * Creates a connection writer for a batch-mode connection or the - * initiator's side of a stream-mode connection. The secret is erased - * before this method returns. + * Creates a connection writer for a simplex connection or the initiator's + * side of a duplex connection. The secret is erased before this method + * returns. */ ConnectionWriter createConnectionWriter(OutputStream out, long capacity, byte[] secret); /** - * Creates a connection writer for the responder's side of a stream-mode + * Creates a connection writer for the responder's side of a duplex * connection. The secret is erased before this method returns. */ ConnectionWriter createConnectionWriter(OutputStream out, long capacity, diff --git a/components/net/sf/briar/transport/ConnectionEncrypter.java b/components/net/sf/briar/transport/ConnectionEncrypter.java index 01fcee7708d5513035b261d2f776f4f56941e2d9..82bbf357af1b8adc0ecb632bd7927f21d1f0c6e7 100644 --- a/components/net/sf/briar/transport/ConnectionEncrypter.java +++ b/components/net/sf/briar/transport/ConnectionEncrypter.java @@ -2,7 +2,6 @@ package net.sf.briar.transport; import java.io.IOException; - /** Encrypts authenticated data to be sent over a connection. */ interface ConnectionEncrypter extends FrameSink { diff --git a/components/net/sf/briar/transport/ConnectionEncrypterImpl.java b/components/net/sf/briar/transport/ConnectionEncrypterImpl.java index 0e83690360ef9da0c4b72d0b84a491c9b32ec337..3eeba89d406f9207efa71e344c5290f73d825535 100644 --- a/components/net/sf/briar/transport/ConnectionEncrypterImpl.java +++ b/components/net/sf/briar/transport/ConnectionEncrypterImpl.java @@ -1,5 +1,6 @@ package net.sf.briar.transport; +import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED; import java.io.IOException; @@ -14,54 +15,52 @@ import net.sf.briar.api.crypto.ErasableKey; class ConnectionEncrypterImpl implements ConnectionEncrypter { private final OutputStream out; - private final Cipher frameCipher; - private final ErasableKey frameKey; + private final Cipher tagCipher, frameCipher; + private final ErasableKey tagKey, frameKey; + private final boolean tagEverySegment; private final byte[] iv, tag; - private long capacity, frame = 0L; - private boolean tagWritten = false; + private long capacity, frame = 0; ConnectionEncrypterImpl(OutputStream out, long capacity, Cipher tagCipher, - Cipher frameCipher, ErasableKey tagKey, ErasableKey frameKey) { + Cipher frameCipher, ErasableKey tagKey, ErasableKey frameKey, + boolean tagEverySegment) { this.out = out; this.capacity = capacity; + this.tagCipher = tagCipher; this.frameCipher = frameCipher; + this.tagKey = tagKey; this.frameKey = frameKey; + this.tagEverySegment = tagEverySegment; iv = IvEncoder.encodeIv(0, frameCipher.getBlockSize()); - // Encrypt the tag - tag = TagEncoder.encodeTag(0, tagCipher, tagKey); - tagKey.erase(); + tag = new byte[TAG_LENGTH]; } public void writeFrame(byte[] b, int len) throws IOException { if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException(); - if(!tagWritten) { - try { + try { + if(tagEverySegment || frame == 0) { + TagEncoder.encodeTag(tag, frame, tagCipher, tagKey); out.write(tag); - } catch(IOException e) { - frameKey.erase(); - throw e; + capacity -= tag.length; + } + IvEncoder.updateIv(iv, frame); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + try { + frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec); + int encrypted = frameCipher.doFinal(b, 0, len, b); + if(encrypted != len) throw new RuntimeException(); + } catch(GeneralSecurityException badCipher) { + throw new RuntimeException(badCipher); } - capacity -= tag.length; - tagWritten = true; - } - IvEncoder.updateIv(iv, frame); - IvParameterSpec ivSpec = new IvParameterSpec(iv); - try { - frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec); - int encrypted = frameCipher.doFinal(b, 0, len, b); - if(encrypted != len) throw new RuntimeException(); - } catch(GeneralSecurityException badCipher) { - throw new RuntimeException(badCipher); - } - try { out.write(b, 0, len); + capacity -= len; + frame++; } catch(IOException e) { frameKey.erase(); + tagKey.erase(); throw e; } - capacity -= len; - frame++; } public void flush() throws IOException { diff --git a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java index 1118ebe76966425987142b836bd8ee40b33b29f5..d00a4efd1de1b187f0dda9dd150278efea0d3fdb 100644 --- a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java +++ b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java @@ -33,8 +33,8 @@ import net.sf.briar.api.protocol.TransportId; import net.sf.briar.api.protocol.TransportIndex; import net.sf.briar.api.transport.ConnectionContext; import net.sf.briar.api.transport.ConnectionRecogniser; -import net.sf.briar.api.transport.IncomingConnectionExecutor; import net.sf.briar.api.transport.ConnectionWindow; +import net.sf.briar.api.transport.IncomingConnectionExecutor; import net.sf.briar.util.ByteUtils; import com.google.inject.Inject; @@ -102,7 +102,8 @@ DatabaseListener { // Locking: this private Bytes calculateTag(Context ctx, byte[] secret) { ErasableKey tagKey = crypto.deriveTagKey(secret, true); - byte[] tag = TagEncoder.encodeTag(0, tagCipher, tagKey); + byte[] tag = new byte[TAG_LENGTH]; + TagEncoder.encodeTag(tag, 0, tagCipher, tagKey); tagKey.erase(); return new Bytes(tag); } diff --git a/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java b/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java index e0b5288c678e2117bdddc998c4a77e35b47b011b..e368d1f39bbd740a29c5e7b115dc99a1100195c2 100644 --- a/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java +++ b/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java @@ -49,7 +49,7 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory { Cipher tagCipher = crypto.getTagCipher(); Cipher frameCipher = crypto.getFrameCipher(); ConnectionEncrypter encrypter = new ConnectionEncrypterImpl(out, - capacity, tagCipher, frameCipher, tagKey, frameKey); + capacity, tagCipher, frameCipher, tagKey, frameKey, false); // Create the writer Mac mac = crypto.getMac(); return new ConnectionWriterImpl(encrypter, mac, macKey); diff --git a/components/net/sf/briar/transport/SegmentedConnectionEncrypter.java b/components/net/sf/briar/transport/SegmentedConnectionEncrypter.java index 60f905120c0d2d9774aab30ca1bf19cb9ae961a9..f757425aacbbc55dce244645403351cc428e2179 100644 --- a/components/net/sf/briar/transport/SegmentedConnectionEncrypter.java +++ b/components/net/sf/briar/transport/SegmentedConnectionEncrypter.java @@ -1,7 +1,8 @@ package net.sf.briar.transport; -import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED; import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH; +import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; +import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED; import java.io.IOException; import java.security.GeneralSecurityException; @@ -16,38 +17,37 @@ import net.sf.briar.api.plugins.SegmentSink; class SegmentedConnectionEncrypter implements ConnectionEncrypter { private final SegmentSink out; - private final Cipher frameCipher; - private final ErasableKey frameKey; - private final byte[] iv, tag; + private final Cipher tagCipher, frameCipher; + private final ErasableKey tagKey, frameKey; + private final boolean tagEverySegment; + private final byte[] iv; private final Segment segment; - private long capacity, frame = 0L; - private boolean tagWritten = false; + private long capacity, frame = 0; SegmentedConnectionEncrypter(SegmentSink out, long capacity, Cipher tagCipher, Cipher frameCipher, ErasableKey tagKey, - ErasableKey frameKey) { + ErasableKey frameKey, boolean tagEverySegment) { this.out = out; this.capacity = capacity; + this.tagCipher = tagCipher; this.frameCipher = frameCipher; + this.tagKey = tagKey; this.frameKey = frameKey; + this.tagEverySegment = tagEverySegment; iv = IvEncoder.encodeIv(0, frameCipher.getBlockSize()); - // Encrypt the tag - tag = TagEncoder.encodeTag(0, tagCipher, tagKey); - tagKey.erase(); segment = new SegmentImpl(); } public void writeFrame(byte[] b, int len) throws IOException { if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException(); int offset = 0; - if(!tagWritten) { - if(tag.length + len > MAX_FRAME_LENGTH) + if(tagEverySegment || frame == 0) { + if(len + TAG_LENGTH > MAX_FRAME_LENGTH) throw new IllegalArgumentException(); - System.arraycopy(tag, 0, segment.getBuffer(), 0, tag.length); - capacity -= tag.length; - tagWritten = true; - offset = tag.length; + TagEncoder.encodeTag(segment.getBuffer(), frame, tagCipher, tagKey); + offset = TAG_LENGTH; + capacity -= TAG_LENGTH; } IvEncoder.updateIv(iv, frame); IvParameterSpec ivSpec = new IvParameterSpec(iv); @@ -64,6 +64,7 @@ class SegmentedConnectionEncrypter implements ConnectionEncrypter { out.writeSegment(segment); } catch(IOException e) { frameKey.erase(); + tagKey.erase(); throw e; } capacity -= len; diff --git a/components/net/sf/briar/transport/TagEncoder.java b/components/net/sf/briar/transport/TagEncoder.java index f442c96844c47cc215d8c00a800a0c48831ad234..df83c7d8fdd4ddf87050d903a7c08c625ee10ca8 100644 --- a/components/net/sf/briar/transport/TagEncoder.java +++ b/components/net/sf/briar/transport/TagEncoder.java @@ -1,29 +1,34 @@ package net.sf.briar.transport; +import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; +import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED; + import java.security.GeneralSecurityException; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import net.sf.briar.api.crypto.ErasableKey; -import net.sf.briar.api.transport.TransportConstants; import net.sf.briar.util.ByteUtils; class TagEncoder { - static byte[] encodeTag(long frame, Cipher tagCipher, ErasableKey tagKey) { - if(frame < 0 || frame > ByteUtils.MAX_32_BIT_UNSIGNED) + private static final byte[] BLANK = new byte[TAG_LENGTH]; + + static void encodeTag(byte[] tag, long frame, Cipher tagCipher, + ErasableKey tagKey) { + if(tag.length < TAG_LENGTH) throw new IllegalArgumentException(); + if(frame < 0 || frame > MAX_32_BIT_UNSIGNED) throw new IllegalArgumentException(); - // The plaintext is blank - byte[] plaintext = new byte[TransportConstants.TAG_LENGTH]; // Encode the frame number as a uint32 at the end of the IV byte[] iv = new byte[tagCipher.getBlockSize()]; - if(iv.length != plaintext.length) throw new IllegalArgumentException(); + if(iv.length != TAG_LENGTH) throw new IllegalArgumentException(); ByteUtils.writeUint32(frame, iv, iv.length - 4); IvParameterSpec ivSpec = new IvParameterSpec(iv); try { tagCipher.init(Cipher.ENCRYPT_MODE, tagKey, ivSpec); - return tagCipher.doFinal(plaintext); + int encrypted = tagCipher.doFinal(BLANK, 0, TAG_LENGTH, tag); + if(encrypted != TAG_LENGTH) throw new IllegalArgumentException(); } catch(GeneralSecurityException e) { // Unsuitable cipher or key throw new IllegalArgumentException(e); @@ -32,9 +37,9 @@ class TagEncoder { static boolean validateTag(byte[] tag, long frame, Cipher tagCipher, ErasableKey tagKey) { - if(frame < 0 || frame > ByteUtils.MAX_32_BIT_UNSIGNED) + if(frame < 0 || frame > MAX_32_BIT_UNSIGNED) throw new IllegalArgumentException(); - if(tag.length != TransportConstants.TAG_LENGTH) return false; + if(tag.length != TAG_LENGTH) return false; // Encode the frame number as a uint32 at the end of the IV byte[] iv = new byte[tagCipher.getBlockSize()]; if(iv.length != tag.length) throw new IllegalArgumentException(); diff --git a/test/net/sf/briar/transport/ConnectionEncrypterImplTest.java b/test/net/sf/briar/transport/ConnectionEncrypterImplTest.java index 17016265e33992e07afc056e83677c7a18f7cebc..eb46a182d1fc4bce87ea3bf9e445b1c680220761 100644 --- a/test/net/sf/briar/transport/ConnectionEncrypterImplTest.java +++ b/test/net/sf/briar/transport/ConnectionEncrypterImplTest.java @@ -1,5 +1,6 @@ package net.sf.briar.transport; +import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; import static org.junit.Assert.assertArrayEquals; import java.io.ByteArrayOutputStream; @@ -37,7 +38,8 @@ public class ConnectionEncrypterImplTest extends BriarTestCase { @Test public void testEncryption() throws Exception { // Calculate the expected tag - byte[] tag = TagEncoder.encodeTag(0, tagCipher, tagKey); + byte[] tag = new byte[TAG_LENGTH]; + TagEncoder.encodeTag(tag, 0, tagCipher, tagKey); // Calculate the expected ciphertext for the first frame byte[] iv = new byte[frameCipher.getBlockSize()]; byte[] plaintext = new byte[123 + MAC_LENGTH]; @@ -59,7 +61,7 @@ public class ConnectionEncrypterImplTest extends BriarTestCase { // Use a ConnectionEncrypter to encrypt the plaintext out.reset(); ConnectionEncrypter e = new ConnectionEncrypterImpl(out, Long.MAX_VALUE, - tagCipher, frameCipher, tagKey, frameKey); + tagCipher, frameCipher, tagKey, frameKey, false); e.writeFrame(plaintext, plaintext.length); e.writeFrame(plaintext1, plaintext1.length); byte[] actual = out.toByteArray(); diff --git a/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java b/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java index db8cc11ba3e5141075ecd7eb6051f6955a085cd0..a35279e54393359cb03e5085256811ec7033d561 100644 --- a/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java +++ b/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java @@ -617,6 +617,8 @@ public class ConnectionRecogniserImplTest extends BriarTestCase { // Calculate the expected tag for connection number 3 ErasableKey tagKey = crypto.deriveTagKey(secret, true); Cipher tagCipher = crypto.getTagCipher(); - return TagEncoder.encodeTag(0, tagCipher, tagKey); + byte[] tag = new byte[TAG_LENGTH]; + TagEncoder.encodeTag(tag, 0, tagCipher, tagKey); + return tag; } } diff --git a/test/net/sf/briar/transport/FrameReadWriteTest.java b/test/net/sf/briar/transport/FrameReadWriteTest.java index b763682a4b4e31fd83e6b35f508c550bd5a9f21c..bfa229e6fe588e060c0c3fae510c2c1bf99b275d 100644 --- a/test/net/sf/briar/transport/FrameReadWriteTest.java +++ b/test/net/sf/briar/transport/FrameReadWriteTest.java @@ -61,7 +61,8 @@ public class FrameReadWriteTest extends BriarTestCase { private void testWriteAndRead(boolean initiator) throws Exception { // Encode the tag - byte[] tag = TagEncoder.encodeTag(0, tagCipher, tagKey); + byte[] tag = new byte[TAG_LENGTH]; + TagEncoder.encodeTag(tag, 0, tagCipher, tagKey); // Generate two random frames byte[] frame = new byte[12345]; random.nextBytes(frame); @@ -74,7 +75,8 @@ public class FrameReadWriteTest extends BriarTestCase { // Write the frames ByteArrayOutputStream out = new ByteArrayOutputStream(); ConnectionEncrypter encrypter = new ConnectionEncrypterImpl(out, - Long.MAX_VALUE, tagCipher, frameCipher, tagCopy, frameCopy); + Long.MAX_VALUE, tagCipher, frameCipher, tagCopy, frameCopy, + false); ConnectionWriter writer = new ConnectionWriterImpl(encrypter, mac, macCopy); OutputStream out1 = writer.getOutputStream(); diff --git a/test/net/sf/briar/transport/SegmentedConnectionEncrypterTest.java b/test/net/sf/briar/transport/SegmentedConnectionEncrypterTest.java index 638d9ac31d2c72895f3675817b987dc3864c61a5..8cce6fa072430f334591f5320ae9c8b4941ef8d9 100644 --- a/test/net/sf/briar/transport/SegmentedConnectionEncrypterTest.java +++ b/test/net/sf/briar/transport/SegmentedConnectionEncrypterTest.java @@ -1,5 +1,6 @@ package net.sf.briar.transport; +import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; import static org.junit.Assert.assertArrayEquals; import java.io.ByteArrayOutputStream; @@ -40,7 +41,8 @@ public class SegmentedConnectionEncrypterTest extends BriarTestCase { @Test public void testEncryption() throws Exception { // Calculate the expected tag - byte[] tag = TagEncoder.encodeTag(0, tagCipher, tagKey); + byte[] tag = new byte[TAG_LENGTH]; + TagEncoder.encodeTag(tag, 0, tagCipher, tagKey); // Calculate the expected ciphertext for the first frame byte[] iv = new byte[frameCipher.getBlockSize()]; byte[] plaintext = new byte[123 + MAC_LENGTH]; @@ -62,7 +64,8 @@ public class SegmentedConnectionEncrypterTest extends BriarTestCase { // Use a connection encrypter to encrypt the plaintext ByteArraySegmentSink sink = new ByteArraySegmentSink(); ConnectionEncrypter e = new SegmentedConnectionEncrypter(sink, - Long.MAX_VALUE, tagCipher, frameCipher, tagKey, frameKey); + Long.MAX_VALUE, tagCipher, frameCipher, tagKey, frameKey, + false); // The first frame's buffer must have enough space for the tag e.writeFrame(plaintext, plaintext.length); e.writeFrame(plaintext1, plaintext1.length);