From 7ed747b2a310b1d3e8ef89f70ad749fa7887b227 Mon Sep 17 00:00:00 2001 From: akwizgran <akwizgran@users.sourceforge.net> Date: Wed, 7 Sep 2011 17:21:52 +0100 Subject: [PATCH] Added an initiator flag (bit 31) to the IV. The flag is used to distinguish between the initiator and responder directions of a stream-mode connection, allowing them to use the same connection number without risking IV reuse. The flag is also raised for batch-mode connections, which only have one direction. --- .../transport/ConnectionReaderFactory.java | 4 +-- .../transport/ConnectionWriterFactory.java | 4 +-- .../transport/ConnectionDecrypterImpl.java | 15 ++++------ .../transport/ConnectionEncrypterImpl.java | 29 ++++++++----------- .../ConnectionReaderFactoryImpl.java | 5 ++-- .../transport/ConnectionRecogniserImpl.java | 2 +- .../ConnectionWriterFactoryImpl.java | 7 +++-- .../net/sf/briar/transport/IvEncoder.java | 18 ++++-------- test/net/sf/briar/FileReadWriteTest.java | 4 +-- .../ConnectionDecrypterImplTest.java | 26 ++++++++++------- .../ConnectionEncrypterImplTest.java | 26 ++++++++++++----- .../ConnectionRecogniserImplTest.java | 2 +- .../briar/transport/FrameReadWriteTest.java | 19 ++++++++---- 13 files changed, 85 insertions(+), 76 deletions(-) diff --git a/api/net/sf/briar/api/transport/ConnectionReaderFactory.java b/api/net/sf/briar/api/transport/ConnectionReaderFactory.java index 536123f48e..01498fb9c1 100644 --- a/api/net/sf/briar/api/transport/ConnectionReaderFactory.java +++ b/api/net/sf/briar/api/transport/ConnectionReaderFactory.java @@ -4,6 +4,6 @@ import java.io.InputStream; public interface ConnectionReaderFactory { - ConnectionReader createConnectionReader(InputStream in, int transportId, - long connection, byte[] secret); + ConnectionReader createConnectionReader(InputStream in, boolean initiator, + int transportId, long connection, byte[] secret); } diff --git a/api/net/sf/briar/api/transport/ConnectionWriterFactory.java b/api/net/sf/briar/api/transport/ConnectionWriterFactory.java index 737096bfe8..696616dfb3 100644 --- a/api/net/sf/briar/api/transport/ConnectionWriterFactory.java +++ b/api/net/sf/briar/api/transport/ConnectionWriterFactory.java @@ -4,6 +4,6 @@ import java.io.OutputStream; public interface ConnectionWriterFactory { - ConnectionWriter createConnectionWriter(OutputStream out, int transportId, - long connection, byte[] secret); + ConnectionWriter createConnectionWriter(OutputStream out, boolean initiator, + int transportId, long connection, byte[] secret); } diff --git a/components/net/sf/briar/transport/ConnectionDecrypterImpl.java b/components/net/sf/briar/transport/ConnectionDecrypterImpl.java index 9e74fe792b..77f62df6de 100644 --- a/components/net/sf/briar/transport/ConnectionDecrypterImpl.java +++ b/components/net/sf/briar/transport/ConnectionDecrypterImpl.java @@ -20,8 +20,6 @@ import javax.crypto.spec.IvParameterSpec; class ConnectionDecrypterImpl extends FilterInputStream implements ConnectionDecrypter { - private final int transportId; - private final long connection; private final Cipher frameCipher; private final SecretKey frameKey; private final byte[] buf, iv; @@ -30,15 +28,13 @@ implements ConnectionDecrypter { private long frame = 0L; private boolean betweenFrames = true; - ConnectionDecrypterImpl(InputStream in, int transportId, long connection, - Cipher frameCipher, SecretKey frameKey) { + ConnectionDecrypterImpl(InputStream in, boolean initiator, int transportId, + long connection, Cipher frameCipher, SecretKey frameKey) { super(in); - this.transportId = transportId; - this.connection = connection; this.frameCipher = frameCipher; this.frameKey = frameKey; buf = new byte[IV_LENGTH]; - iv = new byte[IV_LENGTH]; + iv = IvEncoder.encodeIv(initiator, transportId, connection); } public InputStream getInputStream() { @@ -132,8 +128,7 @@ implements ConnectionDecrypter { private void initialiseCipher() { assert betweenFrames; if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException(); - IvEncoder.encodeIv(iv, transportId, connection, frame); - // Use the plaintext IV to initialise the frame cipher + IvEncoder.updateIv(iv, frame); IvParameterSpec ivSpec = new IvParameterSpec(iv); try { frameCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec); @@ -145,4 +140,4 @@ implements ConnectionDecrypter { frame++; betweenFrames = false; } -} +} \ No newline at end of file diff --git a/components/net/sf/briar/transport/ConnectionEncrypterImpl.java b/components/net/sf/briar/transport/ConnectionEncrypterImpl.java index ad1d2d7285..17454fc98a 100644 --- a/components/net/sf/briar/transport/ConnectionEncrypterImpl.java +++ b/components/net/sf/briar/transport/ConnectionEncrypterImpl.java @@ -18,25 +18,21 @@ import javax.crypto.spec.IvParameterSpec; class ConnectionEncrypterImpl extends FilterOutputStream implements ConnectionEncrypter { - private final int transportId; - private final long connection; private final Cipher ivCipher, frameCipher; private final SecretKey frameKey; private final byte[] iv; private long frame = 0L; - private boolean started = false, betweenFrames = false; + private boolean ivWritten = false, betweenFrames = false; - ConnectionEncrypterImpl(OutputStream out, int transportId, - long connection, Cipher ivCipher, Cipher frameCipher, - SecretKey ivKey, SecretKey frameKey) { + ConnectionEncrypterImpl(OutputStream out, boolean initiator, + int transportId, long connection, Cipher ivCipher, + Cipher frameCipher, SecretKey ivKey, SecretKey frameKey) { super(out); - this.transportId = transportId; - this.connection = connection; this.ivCipher = ivCipher; this.frameCipher = frameCipher; this.frameKey = frameKey; - iv = new byte[IV_LENGTH]; + iv = IvEncoder.encodeIv(initiator, transportId, connection); try { ivCipher.init(Cipher.ENCRYPT_MODE, ivKey); } catch(InvalidKeyException badKey) { @@ -51,7 +47,7 @@ implements ConnectionEncrypter { } public void writeMac(byte[] mac) throws IOException { - if(!started || betweenFrames) throw new IllegalStateException(); + if(!ivWritten || betweenFrames) throw new IllegalStateException(); try { out.write(frameCipher.doFinal(mac)); } catch(BadPaddingException badCipher) { @@ -64,7 +60,7 @@ implements ConnectionEncrypter { @Override public void write(int b) throws IOException { - if(!started) writeIv(); + if(!ivWritten) writeIv(); if(betweenFrames) initialiseCipher(); byte[] ciphertext = frameCipher.update(new byte[] {(byte) b}); if(ciphertext != null) out.write(ciphertext); @@ -77,16 +73,15 @@ implements ConnectionEncrypter { @Override public void write(byte[] b, int off, int len) throws IOException { - if(!started) writeIv(); + if(!ivWritten) writeIv(); if(betweenFrames) initialiseCipher(); byte[] ciphertext = frameCipher.update(b, off, len); if(ciphertext != null) out.write(ciphertext); } private void writeIv() throws IOException { - assert !started; + assert !ivWritten; assert !betweenFrames; - IvEncoder.encodeIv(iv, transportId, connection, 0L); try { out.write(ivCipher.doFinal(iv)); } catch(BadPaddingException badCipher) { @@ -94,15 +89,15 @@ implements ConnectionEncrypter { } catch(IllegalBlockSizeException badCipher) { throw new RuntimeException(badCipher); } - started = true; + ivWritten = true; betweenFrames = true; } private void initialiseCipher() { - assert started; + assert ivWritten; assert betweenFrames; if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException(); - IvEncoder.encodeIv(iv, transportId, connection, frame); + IvEncoder.updateIv(iv, frame); IvParameterSpec ivSpec = new IvParameterSpec(iv); try { frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec); diff --git a/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java b/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java index 016e4fb43b..7629e02562 100644 --- a/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java +++ b/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java @@ -23,7 +23,8 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory { } public ConnectionReader createConnectionReader(InputStream in, - int transportId, long connection, byte[] secret) { + boolean initiator, int transportId, long connection, + byte[] secret) { SecretKey macKey = crypto.deriveIncomingMacKey(secret); SecretKey frameKey = crypto.deriveIncomingFrameKey(secret); Cipher frameCipher = crypto.getFrameCipher(); @@ -34,7 +35,7 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory { throw new IllegalArgumentException(e); } ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in, - transportId, connection, frameCipher, frameKey); + initiator, transportId, connection, frameCipher, frameKey); return new ConnectionReaderImpl(decrypter, mac); } } diff --git a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java index 7915ecf718..12664ef7af 100644 --- a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java +++ b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java @@ -81,7 +81,7 @@ DatabaseListener { } private synchronized byte[] encryptIv(ContactId c, long connection) { - byte[] iv = IvEncoder.encodeIv(transportId, connection); + byte[] iv = IvEncoder.encodeIv(true, transportId, connection); Cipher cipher = contactToCipher.get(c); assert cipher != null; try { diff --git a/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java b/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java index 9d0768c03c..42ed959005 100644 --- a/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java +++ b/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java @@ -23,7 +23,8 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory { } public ConnectionWriter createConnectionWriter(OutputStream out, - int transportId, long connection, byte[] secret) { + boolean initiator, int transportId, long connection, + byte[] secret) { SecretKey macKey = crypto.deriveOutgoingMacKey(secret); SecretKey ivKey = crypto.deriveOutgoingIvKey(secret); SecretKey frameKey = crypto.deriveOutgoingFrameKey(secret); @@ -36,8 +37,8 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory { throw new IllegalArgumentException(badKey); } ConnectionEncrypter encrypter = new ConnectionEncrypterImpl(out, - transportId, connection, ivCipher, frameCipher, ivKey, - frameKey); + initiator, transportId, connection, ivCipher, frameCipher, + ivKey, frameKey); return new ConnectionWriterImpl(encrypter, mac); } } diff --git a/components/net/sf/briar/transport/IvEncoder.java b/components/net/sf/briar/transport/IvEncoder.java index 481f0304a5..6744b04495 100644 --- a/components/net/sf/briar/transport/IvEncoder.java +++ b/components/net/sf/briar/transport/IvEncoder.java @@ -5,8 +5,11 @@ import net.sf.briar.util.ByteUtils; class IvEncoder { - static byte[] encodeIv(int transportId, long connection) { + static byte[] encodeIv(boolean initiator, int transportId, + long connection) { byte[] iv = new byte[IV_LENGTH]; + // Bit 31 is the initiator flag + if(initiator) iv[3] = 1; // Encode the transport identifier as an unsigned 16-bit integer ByteUtils.writeUint16(transportId, iv, 4); // Encode the connection number as an unsigned 32-bit integer @@ -14,20 +17,9 @@ class IvEncoder { return iv; } - static void encodeIv(byte[] iv, int transportId, long connection, - long frame) { + static void updateIv(byte[] iv, long frame) { if(iv.length != IV_LENGTH) throw new IllegalArgumentException(); - // The first 16 bits of the IV must be zero (reserved) - iv[0] = 0; - iv[1] = 0; - // Encode the transport identifier as an unsigned 16-bit integer - ByteUtils.writeUint16(transportId, iv, 4); - // Encode the connection number as an unsigned 32-bit integer - ByteUtils.writeUint32(connection, iv, 6); // Encode the frame number as an unsigned 32-bit integer ByteUtils.writeUint32(frame, iv, 10); - // The last 16 bits of the IV must be zero (block number) - iv[14] = 0; - iv[15] = 0; } } diff --git a/test/net/sf/briar/FileReadWriteTest.java b/test/net/sf/briar/FileReadWriteTest.java index 5c0ca77c2f..fa54fd4a3a 100644 --- a/test/net/sf/briar/FileReadWriteTest.java +++ b/test/net/sf/briar/FileReadWriteTest.java @@ -135,7 +135,7 @@ public class FileReadWriteTest extends TestCase { OutputStream out = new FileOutputStream(file); // Use Alice's secret for writing ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out, - transportId, connection, aliceSecret); + true, transportId, connection, aliceSecret); out = w.getOutputStream(); AckWriter a = protocolWriterFactory.createAckWriter(out); @@ -194,7 +194,7 @@ public class FileReadWriteTest extends TestCase { assertEquals(16, offset); // Use Bob's secret for reading ConnectionReader r = connectionReaderFactory.createConnectionReader(in, - transportId, connection, bobSecret); + true, transportId, connection, bobSecret); in = r.getInputStream(); ProtocolReader protocolReader = protocolReaderFactory.createProtocolReader(in); diff --git a/test/net/sf/briar/transport/ConnectionDecrypterImplTest.java b/test/net/sf/briar/transport/ConnectionDecrypterImplTest.java index 1787033468..f785d68122 100644 --- a/test/net/sf/briar/transport/ConnectionDecrypterImplTest.java +++ b/test/net/sf/briar/transport/ConnectionDecrypterImplTest.java @@ -1,7 +1,5 @@ package net.sf.briar.transport; -import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH; - import java.io.ByteArrayInputStream; import java.util.Arrays; @@ -43,25 +41,33 @@ public class ConnectionDecrypterImplTest extends TestCase { byte[] ciphertext = new byte[1 + MAC_LENGTH]; ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); // Check that one byte plus a MAC can be read - ConnectionDecrypter d = new ConnectionDecrypterImpl(in, transportId, - connection, frameCipher, frameKey); + ConnectionDecrypter d = new ConnectionDecrypterImpl(in, true, + transportId, connection, frameCipher, frameKey); assertFalse(d.getInputStream().read() == -1); d.readMac(new byte[MAC_LENGTH]); assertTrue(d.getInputStream().read() == -1); } @Test - public void testDecryption() throws Exception { + public void testInitiatorDecryption() throws Exception { + testDecryption(true); + } + + @Test + public void testResponderDecryption() throws Exception { + testDecryption(false); + } + + private void testDecryption(boolean initiator) throws Exception { // Calculate the expected plaintext for the first frame byte[] ciphertext = new byte[123]; - byte[] iv = new byte[IV_LENGTH]; - IvEncoder.encodeIv(iv, transportId, connection, 0L); + byte[] iv = IvEncoder.encodeIv(initiator, transportId, connection); IvParameterSpec ivSpec = new IvParameterSpec(iv); frameCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec); byte[] plaintext = frameCipher.doFinal(ciphertext); // Calculate the expected plaintext for the second frame byte[] ciphertext1 = new byte[1234]; - IvEncoder.encodeIv(iv, transportId, connection, 1L); + IvEncoder.updateIv(iv, 1L); ivSpec = new IvParameterSpec(iv); frameCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec); byte[] plaintext1 = frameCipher.doFinal(ciphertext1); @@ -72,8 +78,8 @@ public class ConnectionDecrypterImplTest extends TestCase { out.write(ciphertext1); ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); // Use a ConnectionDecrypter to decrypt the ciphertext - ConnectionDecrypter d = new ConnectionDecrypterImpl(in, transportId, - connection, frameCipher, frameKey); + ConnectionDecrypter d = new ConnectionDecrypterImpl(in, initiator, + transportId, connection, frameCipher, frameKey); // First frame byte[] decrypted = new byte[plaintext.length - MAC_LENGTH]; TestUtils.readFully(d.getInputStream(), decrypted); diff --git a/test/net/sf/briar/transport/ConnectionEncrypterImplTest.java b/test/net/sf/briar/transport/ConnectionEncrypterImplTest.java index 59624159dd..9121b6b381 100644 --- a/test/net/sf/briar/transport/ConnectionEncrypterImplTest.java +++ b/test/net/sf/briar/transport/ConnectionEncrypterImplTest.java @@ -40,17 +40,27 @@ public class ConnectionEncrypterImplTest extends TestCase { @Test public void testSingleByteFrame() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); - ConnectionEncrypter e = new ConnectionEncrypterImpl(out, transportId, - connection, ivCipher, frameCipher, ivKey, frameKey); + ConnectionEncrypter e = new ConnectionEncrypterImpl(out, true, + transportId, connection, ivCipher, frameCipher, ivKey, + frameKey); e.getOutputStream().write((byte) 0); e.writeMac(new byte[MAC_LENGTH]); assertEquals(IV_LENGTH + 1 + MAC_LENGTH, out.toByteArray().length); } @Test - public void testEncryption() throws Exception { + public void testInitiatorEncryption() throws Exception { + testEncryption(true); + } + + @Test + public void testResponderEncryption() throws Exception { + testEncryption(false); + } + + private void testEncryption(boolean initiator) throws Exception { // Calculate the expected ciphertext for the IV - byte[] iv = IvEncoder.encodeIv(transportId, connection); + byte[] iv = IvEncoder.encodeIv(initiator, transportId, connection); assertEquals(IV_LENGTH, iv.length); ivCipher.init(Cipher.ENCRYPT_MODE, ivKey); byte[] encryptedIv = ivCipher.doFinal(iv); @@ -58,7 +68,6 @@ public class ConnectionEncrypterImplTest extends TestCase { // Calculate the expected ciphertext for the first frame byte[] plaintext = new byte[123]; byte[] plaintextMac = new byte[MAC_LENGTH]; - IvEncoder.encodeIv(iv, transportId, connection, 0L); IvParameterSpec ivSpec = new IvParameterSpec(iv); frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec); byte[] ciphertext = new byte[plaintext.length + plaintextMac.length]; @@ -68,7 +77,7 @@ public class ConnectionEncrypterImplTest extends TestCase { offset); // Calculate the expected ciphertext for the second frame byte[] plaintext1 = new byte[1234]; - IvEncoder.encodeIv(iv, transportId, connection, 1L); + IvEncoder.updateIv(iv, 1L); ivSpec = new IvParameterSpec(iv); frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec); byte[] ciphertext1 = new byte[plaintext1.length + plaintextMac.length]; @@ -84,8 +93,9 @@ public class ConnectionEncrypterImplTest extends TestCase { byte[] expected = out.toByteArray(); // Use a ConnectionEncrypter to encrypt the plaintext out.reset(); - ConnectionEncrypter e = new ConnectionEncrypterImpl(out, transportId, - connection, ivCipher, frameCipher, ivKey, frameKey); + ConnectionEncrypter e = new ConnectionEncrypterImpl(out, initiator, + transportId, connection, ivCipher, frameCipher, ivKey, + frameKey); e.getOutputStream().write(plaintext); e.writeMac(plaintextMac); e.getOutputStream().write(plaintext1); diff --git a/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java b/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java index 4d8af92f52..de66b5438a 100644 --- a/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java +++ b/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java @@ -65,7 +65,7 @@ public class ConnectionRecogniserImplTest extends TestCase { SecretKey ivKey = crypto.deriveIncomingIvKey(secret); Cipher ivCipher = crypto.getIvCipher(); ivCipher.init(Cipher.ENCRYPT_MODE, ivKey); - byte[] iv = IvEncoder.encodeIv(transportId, 3L); + byte[] iv = IvEncoder.encodeIv(true, transportId, 3L); byte[] encryptedIv = ivCipher.doFinal(iv); Mockery context = new Mockery(); diff --git a/test/net/sf/briar/transport/FrameReadWriteTest.java b/test/net/sf/briar/transport/FrameReadWriteTest.java index d7c98f09a7..f676f838cc 100644 --- a/test/net/sf/briar/transport/FrameReadWriteTest.java +++ b/test/net/sf/briar/transport/FrameReadWriteTest.java @@ -50,9 +50,18 @@ public class FrameReadWriteTest extends TestCase { } @Test - public void testWriteAndRead() throws Exception { + public void testInitiatorWriteAndRead() throws Exception { + testWriteAndRead(true); + } + + @Test + public void testResponderWriteAndRead() throws Exception { + testWriteAndRead(false); + } + + private void testWriteAndRead(boolean initiator) throws Exception { // Calculate the expected ciphertext for the IV - byte[] iv = IvEncoder.encodeIv(transportId, connection); + byte[] iv = IvEncoder.encodeIv(initiator, transportId, connection); assertEquals(IV_LENGTH, iv.length); ivCipher.init(Cipher.ENCRYPT_MODE, ivKey); byte[] encryptedIv = ivCipher.doFinal(iv); @@ -65,8 +74,8 @@ public class FrameReadWriteTest extends TestCase { // Write the frames ByteArrayOutputStream out = new ByteArrayOutputStream(); ConnectionEncrypter encrypter = new ConnectionEncrypterImpl(out, - transportId, connection, ivCipher, frameCipher, ivKey, - frameKey); + initiator, transportId, connection, ivCipher, frameCipher, + ivKey, frameKey); mac.init(macKey); ConnectionWriter writer = new ConnectionWriterImpl(encrypter, mac); OutputStream out1 = writer.getOutputStream(); @@ -80,7 +89,7 @@ public class FrameReadWriteTest extends TestCase { assertEquals(IV_LENGTH, in.read(recoveredIv)); assertTrue(Arrays.equals(encryptedIv, recoveredIv)); ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in, - transportId, connection, frameCipher, frameKey); + initiator, transportId, connection, frameCipher, frameKey); ConnectionReader reader = new ConnectionReaderImpl(decrypter, mac); InputStream in1 = reader.getInputStream(); byte[] recovered = new byte[frame.length]; -- GitLab