mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-22 23:59:54 +01:00
Forward secrecy.
Each connection's keys are derived from a secret that is erased after deriving the keys and the secret for the next connection.
This commit is contained in:
@@ -52,7 +52,8 @@ public class ConnectionDecrypterImplTest extends TestCase {
|
||||
|
||||
private void testDecryption(boolean initiator) throws Exception {
|
||||
// Calculate the plaintext and ciphertext for the IV
|
||||
byte[] iv = IvEncoder.encodeIv(initiator, transportIndex, connection);
|
||||
byte[] iv = IvEncoder.encodeIv(initiator, transportIndex.getInt(),
|
||||
connection);
|
||||
ivCipher.init(Cipher.ENCRYPT_MODE, ivKey);
|
||||
byte[] encryptedIv = ivCipher.doFinal(iv);
|
||||
assertEquals(IV_LENGTH, encryptedIv.length);
|
||||
@@ -85,8 +86,8 @@ public class ConnectionDecrypterImplTest extends TestCase {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
// Use a ConnectionDecrypter to decrypt the ciphertext
|
||||
ConnectionDecrypter d = new ConnectionDecrypterImpl(in,
|
||||
IvEncoder.encodeIv(initiator, transportIndex, connection),
|
||||
frameCipher, frameKey);
|
||||
IvEncoder.encodeIv(initiator, transportIndex.getInt(),
|
||||
connection), frameCipher, frameKey);
|
||||
// First frame
|
||||
byte[] decrypted = new byte[ciphertext.length];
|
||||
TestUtils.readFully(d.getInputStream(), decrypted);
|
||||
|
||||
@@ -50,7 +50,8 @@ public class ConnectionEncrypterImplTest extends TestCase {
|
||||
|
||||
private void testEncryption(boolean initiator) throws Exception {
|
||||
// Calculate the expected ciphertext for the IV
|
||||
byte[] iv = IvEncoder.encodeIv(initiator, transportIndex, connection);
|
||||
byte[] iv = IvEncoder.encodeIv(initiator, transportIndex.getInt(),
|
||||
connection);
|
||||
ivCipher.init(Cipher.ENCRYPT_MODE, ivKey);
|
||||
byte[] encryptedIv = ivCipher.doFinal(iv);
|
||||
assertEquals(IV_LENGTH, encryptedIv.length);
|
||||
@@ -82,7 +83,7 @@ public class ConnectionEncrypterImplTest extends TestCase {
|
||||
byte[] expected = out.toByteArray();
|
||||
// Use a ConnectionEncrypter to encrypt the plaintext
|
||||
out.reset();
|
||||
iv = IvEncoder.encodeIv(initiator, transportIndex, connection);
|
||||
iv = IvEncoder.encodeIv(initiator, transportIndex.getInt(), connection);
|
||||
ConnectionEncrypter e = new ConnectionEncrypterImpl(out, Long.MAX_VALUE,
|
||||
iv, ivCipher, frameCipher, ivKey, frameKey);
|
||||
e.getOutputStream().write(plaintext);
|
||||
|
||||
@@ -4,6 +4,7 @@ import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
@@ -51,7 +52,8 @@ public class ConnectionRecogniserImplTest extends TestCase {
|
||||
Transport transport = new Transport(transportId, localIndex,
|
||||
Collections.singletonMap("foo", "bar"));
|
||||
transports = Collections.singletonList(transport);
|
||||
connectionWindow = new ConnectionWindowImpl();
|
||||
connectionWindow = new ConnectionWindowImpl(crypto, remoteIndex,
|
||||
inSecret);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -65,8 +67,6 @@ public class ConnectionRecogniserImplTest extends TestCase {
|
||||
will(returnValue(transports));
|
||||
oneOf(db).getContacts();
|
||||
will(returnValue(Collections.singletonList(contactId)));
|
||||
oneOf(db).getSharedSecret(contactId, true);
|
||||
will(returnValue(inSecret));
|
||||
oneOf(db).getRemoteIndex(contactId, transportId);
|
||||
will(returnValue(remoteIndex));
|
||||
oneOf(db).getConnectionWindow(contactId, remoteIndex);
|
||||
@@ -80,11 +80,16 @@ public class ConnectionRecogniserImplTest extends TestCase {
|
||||
|
||||
@Test
|
||||
public void testExpectedIv() throws Exception {
|
||||
// Calculate the shared secret for connection number 3
|
||||
byte[] secret = inSecret;
|
||||
for(int i = 0; i < 4; i++) {
|
||||
secret = crypto.deriveNextSecret(secret, remoteIndex.getInt(), i);
|
||||
}
|
||||
// Calculate the expected IV for connection number 3
|
||||
ErasableKey ivKey = crypto.deriveIvKey(inSecret, true);
|
||||
ErasableKey ivKey = crypto.deriveIvKey(secret, true);
|
||||
Cipher ivCipher = crypto.getIvCipher();
|
||||
ivCipher.init(Cipher.ENCRYPT_MODE, ivKey);
|
||||
byte[] iv = IvEncoder.encodeIv(true, remoteIndex, 3L);
|
||||
byte[] iv = IvEncoder.encodeIv(true, remoteIndex.getInt(), 3);
|
||||
byte[] encryptedIv = ivCipher.doFinal(iv);
|
||||
|
||||
Mockery context = new Mockery();
|
||||
@@ -96,8 +101,6 @@ public class ConnectionRecogniserImplTest extends TestCase {
|
||||
will(returnValue(transports));
|
||||
oneOf(db).getContacts();
|
||||
will(returnValue(Collections.singletonList(contactId)));
|
||||
oneOf(db).getSharedSecret(contactId, true);
|
||||
will(returnValue(inSecret));
|
||||
oneOf(db).getRemoteIndex(contactId, transportId);
|
||||
will(returnValue(remoteIndex));
|
||||
oneOf(db).getConnectionWindow(contactId, remoteIndex);
|
||||
@@ -107,8 +110,6 @@ public class ConnectionRecogniserImplTest extends TestCase {
|
||||
will(returnValue(connectionWindow));
|
||||
oneOf(db).setConnectionWindow(contactId, remoteIndex,
|
||||
connectionWindow);
|
||||
oneOf(db).getSharedSecret(contactId, true);
|
||||
will(returnValue(inSecret));
|
||||
}});
|
||||
final ConnectionRecogniserImpl c =
|
||||
new ConnectionRecogniserImpl(crypto, db);
|
||||
@@ -121,11 +122,11 @@ public class ConnectionRecogniserImplTest extends TestCase {
|
||||
// Second time - the IV should no longer be expected
|
||||
assertNull(c.acceptConnection(encryptedIv));
|
||||
// The window should have advanced
|
||||
Collection<Long> unseen = connectionWindow.getUnseen();
|
||||
Map<Long, byte[]> unseen = connectionWindow.getUnseen();
|
||||
assertEquals(19, unseen.size());
|
||||
for(int i = 0; i < 19; i++) {
|
||||
if(i == 3) continue;
|
||||
assertTrue(unseen.contains(Long.valueOf(i)));
|
||||
assertTrue(unseen.containsKey(Long.valueOf(i)));
|
||||
}
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@@ -1,18 +1,39 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.protocol.TransportIndex;
|
||||
import net.sf.briar.api.transport.ConnectionWindow;
|
||||
import net.sf.briar.crypto.CryptoModule;
|
||||
import net.sf.briar.util.ByteUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
public class ConnectionWindowImplTest extends TestCase {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final byte[] secret;
|
||||
private final TransportIndex transportIndex = new TransportIndex(13);
|
||||
|
||||
public ConnectionWindowImplTest(String name) {
|
||||
super(name);
|
||||
Injector i = Guice.createInjector(new CryptoModule());
|
||||
crypto = i.getInstance(CryptoComponent.class);
|
||||
secret = new byte[32];
|
||||
new Random().nextBytes(secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWindowSliding() {
|
||||
ConnectionWindowImpl w = new ConnectionWindowImpl();
|
||||
ConnectionWindow w = new ConnectionWindowImpl(crypto,
|
||||
transportIndex, secret);
|
||||
for(int i = 0; i < 100; i++) {
|
||||
assertFalse(w.isSeen(i));
|
||||
w.setSeen(i);
|
||||
@@ -22,7 +43,8 @@ public class ConnectionWindowImplTest extends TestCase {
|
||||
|
||||
@Test
|
||||
public void testWindowJumping() {
|
||||
ConnectionWindowImpl w = new ConnectionWindowImpl();
|
||||
ConnectionWindow w = new ConnectionWindowImpl(crypto, transportIndex,
|
||||
secret);
|
||||
for(int i = 0; i < 100; i += 13) {
|
||||
assertFalse(w.isSeen(i));
|
||||
w.setSeen(i);
|
||||
@@ -32,7 +54,8 @@ public class ConnectionWindowImplTest extends TestCase {
|
||||
|
||||
@Test
|
||||
public void testWindowUpperLimit() {
|
||||
ConnectionWindowImpl w = new ConnectionWindowImpl();
|
||||
ConnectionWindow w = new ConnectionWindowImpl(crypto, transportIndex,
|
||||
secret);
|
||||
// Centre is 0, highest value in window is 15
|
||||
w.setSeen(15);
|
||||
// Centre is 16, highest value in window is 31
|
||||
@@ -43,11 +66,11 @@ public class ConnectionWindowImplTest extends TestCase {
|
||||
fail();
|
||||
} catch(IllegalArgumentException expected) {}
|
||||
// Values greater than 2^32 - 1 should never be allowed
|
||||
Collection<Long> unseen = new ArrayList<Long>();
|
||||
Map<Long, byte[]> unseen = new HashMap<Long, byte[]>();
|
||||
for(int i = 0; i < 32; i++) {
|
||||
unseen.add(ByteUtils.MAX_32_BIT_UNSIGNED - i);
|
||||
unseen.put(ByteUtils.MAX_32_BIT_UNSIGNED - i, secret);
|
||||
}
|
||||
w = new ConnectionWindowImpl(unseen);
|
||||
w = new ConnectionWindowImpl(crypto, transportIndex, unseen);
|
||||
w.setSeen(ByteUtils.MAX_32_BIT_UNSIGNED);
|
||||
try {
|
||||
w.setSeen(ByteUtils.MAX_32_BIT_UNSIGNED + 1);
|
||||
@@ -57,7 +80,8 @@ public class ConnectionWindowImplTest extends TestCase {
|
||||
|
||||
@Test
|
||||
public void testWindowLowerLimit() {
|
||||
ConnectionWindowImpl w = new ConnectionWindowImpl();
|
||||
ConnectionWindow w = new ConnectionWindowImpl(crypto, transportIndex,
|
||||
secret);
|
||||
// Centre is 0, negative values should never be allowed
|
||||
try {
|
||||
w.setSeen(-1);
|
||||
@@ -87,7 +111,8 @@ public class ConnectionWindowImplTest extends TestCase {
|
||||
|
||||
@Test
|
||||
public void testCannotSetSeenTwice() {
|
||||
ConnectionWindowImpl w = new ConnectionWindowImpl();
|
||||
ConnectionWindow w = new ConnectionWindowImpl(crypto, transportIndex,
|
||||
secret);
|
||||
w.setSeen(15);
|
||||
try {
|
||||
w.setSeen(15);
|
||||
@@ -97,12 +122,13 @@ public class ConnectionWindowImplTest extends TestCase {
|
||||
|
||||
@Test
|
||||
public void testGetUnseenConnectionNumbers() {
|
||||
ConnectionWindowImpl w = new ConnectionWindowImpl();
|
||||
ConnectionWindow w = new ConnectionWindowImpl(crypto, transportIndex,
|
||||
secret);
|
||||
// Centre is 0; window should cover 0 to 15, inclusive, with none seen
|
||||
Collection<Long> unseen = w.getUnseen();
|
||||
Map<Long, byte[]> unseen = w.getUnseen();
|
||||
assertEquals(16, unseen.size());
|
||||
for(int i = 0; i < 16; i++) {
|
||||
assertTrue(unseen.contains(Long.valueOf(i)));
|
||||
assertTrue(unseen.containsKey(Long.valueOf(i)));
|
||||
assertFalse(w.isSeen(i));
|
||||
}
|
||||
w.setSeen(3);
|
||||
@@ -112,10 +138,10 @@ public class ConnectionWindowImplTest extends TestCase {
|
||||
assertEquals(19, unseen.size());
|
||||
for(int i = 0; i < 21; i++) {
|
||||
if(i == 3 || i == 4) {
|
||||
assertFalse(unseen.contains(Long.valueOf(i)));
|
||||
assertFalse(unseen.containsKey(Long.valueOf(i)));
|
||||
assertTrue(w.isSeen(i));
|
||||
} else {
|
||||
assertTrue(unseen.contains(Long.valueOf(i)));
|
||||
assertTrue(unseen.containsKey(Long.valueOf(i)));
|
||||
assertFalse(w.isSeen(i));
|
||||
}
|
||||
}
|
||||
@@ -125,10 +151,10 @@ public class ConnectionWindowImplTest extends TestCase {
|
||||
assertEquals(30, unseen.size());
|
||||
for(int i = 4; i < 36; i++) {
|
||||
if(i == 4 || i == 19) {
|
||||
assertFalse(unseen.contains(Long.valueOf(i)));
|
||||
assertFalse(unseen.containsKey(Long.valueOf(i)));
|
||||
assertTrue(w.isSeen(i));
|
||||
} else {
|
||||
assertTrue(unseen.contains(Long.valueOf(i)));
|
||||
assertTrue(unseen.containsKey(Long.valueOf(i)));
|
||||
assertFalse(w.isSeen(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@ import java.util.Random;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import net.sf.briar.TestDatabaseModule;
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.protocol.TransportIndex;
|
||||
import net.sf.briar.api.transport.ConnectionContext;
|
||||
import net.sf.briar.api.transport.ConnectionContextFactory;
|
||||
import net.sf.briar.api.transport.ConnectionWriter;
|
||||
import net.sf.briar.api.transport.ConnectionWriterFactory;
|
||||
import net.sf.briar.crypto.CryptoModule;
|
||||
@@ -26,8 +29,10 @@ import com.google.inject.Injector;
|
||||
|
||||
public class ConnectionWriterTest extends TestCase {
|
||||
|
||||
private final ConnectionContextFactory connectionContextFactory;
|
||||
private final ConnectionWriterFactory connectionWriterFactory;
|
||||
private final byte[] outSecret;
|
||||
private final byte[] secret;
|
||||
private final ContactId contactId = new ContactId(13);
|
||||
private final TransportIndex transportIndex = new TransportIndex(13);
|
||||
private final long connection = 12345L;
|
||||
|
||||
@@ -38,17 +43,22 @@ public class ConnectionWriterTest extends TestCase {
|
||||
new ProtocolWritersModule(), new SerialModule(),
|
||||
new TestDatabaseModule(), new TransportBatchModule(),
|
||||
new TransportModule(), new TransportStreamModule());
|
||||
connectionContextFactory =
|
||||
i.getInstance(ConnectionContextFactory.class);
|
||||
connectionWriterFactory = i.getInstance(ConnectionWriterFactory.class);
|
||||
outSecret = new byte[32];
|
||||
new Random().nextBytes(outSecret);
|
||||
secret = new byte[32];
|
||||
new Random().nextBytes(secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverhead() throws Exception {
|
||||
ByteArrayOutputStream out =
|
||||
new ByteArrayOutputStream(MIN_CONNECTION_LENGTH);
|
||||
ConnectionContext ctx =
|
||||
connectionContextFactory.createConnectionContext(contactId,
|
||||
transportIndex, connection, secret);
|
||||
ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out,
|
||||
MIN_CONNECTION_LENGTH, transportIndex, connection, outSecret);
|
||||
MIN_CONNECTION_LENGTH, ctx);
|
||||
// Check that the connection writer thinks there's room for a packet
|
||||
long capacity = w.getRemainingCapacity();
|
||||
assertTrue(capacity >= MAX_PACKET_LENGTH);
|
||||
|
||||
@@ -64,7 +64,8 @@ public class FrameReadWriteTest extends TestCase {
|
||||
|
||||
private void testWriteAndRead(boolean initiator) throws Exception {
|
||||
// Create and encrypt the IV
|
||||
byte[] iv = IvEncoder.encodeIv(initiator, transportIndex, connection);
|
||||
byte[] iv = IvEncoder.encodeIv(initiator, transportIndex.getInt(),
|
||||
connection);
|
||||
ivCipher.init(Cipher.ENCRYPT_MODE, ivKey);
|
||||
byte[] encryptedIv = ivCipher.doFinal(iv);
|
||||
assertEquals(IV_LENGTH, encryptedIv.length);
|
||||
@@ -92,7 +93,7 @@ public class FrameReadWriteTest extends TestCase {
|
||||
// Decrypt the IV
|
||||
ivCipher.init(Cipher.DECRYPT_MODE, ivKey);
|
||||
byte[] recoveredIv = ivCipher.doFinal(recoveredEncryptedIv);
|
||||
iv = IvEncoder.encodeIv(initiator, transportIndex, connection);
|
||||
iv = IvEncoder.encodeIv(initiator, transportIndex.getInt(), connection);
|
||||
assertArrayEquals(iv, recoveredIv);
|
||||
// Read the frames back
|
||||
ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in, iv,
|
||||
|
||||
@@ -119,7 +119,7 @@ public class BatchConnectionReadWriteTest extends TestCase {
|
||||
alice.getInstance(ProtocolWriterFactory.class);
|
||||
BatchTransportWriter writer = new TestBatchTransportWriter(out);
|
||||
OutgoingBatchConnection batchOut = new OutgoingBatchConnection(
|
||||
connFactory, db, protoFactory, transportIndex, contactId,
|
||||
connFactory, db, protoFactory, contactId, transportIndex,
|
||||
writer);
|
||||
// Write whatever needs to be written
|
||||
batchOut.write();
|
||||
@@ -170,8 +170,7 @@ public class BatchConnectionReadWriteTest extends TestCase {
|
||||
bob.getInstance(ProtocolReaderFactory.class);
|
||||
BatchTransportReader reader = new TestBatchTransportReader(in);
|
||||
IncomingBatchConnection batchIn = new IncomingBatchConnection(
|
||||
connFactory, db, protoFactory, transportIndex, contactId,
|
||||
reader, encryptedIv);
|
||||
connFactory, db, protoFactory, ctx, reader, encryptedIv);
|
||||
// No messages should have been added yet
|
||||
assertFalse(listener.messagesAdded);
|
||||
// Read whatever needs to be read
|
||||
|
||||
Reference in New Issue
Block a user