diff --git a/components/net/sf/briar/crypto/CryptoModule.java b/components/net/sf/briar/crypto/CryptoModule.java index 1f6546a8cb2b91e72b899814875b2098a4f6a905..64efb11c12bb1062621dde37cd4d238f79db353d 100644 --- a/components/net/sf/briar/crypto/CryptoModule.java +++ b/components/net/sf/briar/crypto/CryptoModule.java @@ -1,6 +1,10 @@ package net.sf.briar.crypto; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + import net.sf.briar.api.crypto.CryptoComponent; +import net.sf.briar.api.crypto.SecretStorageKey; import com.google.inject.AbstractModule; import com.google.inject.Singleton; @@ -9,6 +13,10 @@ public class CryptoModule extends AbstractModule { @Override protected void configure() { - bind(CryptoComponent.class).to(CryptoComponentImpl.class).in(Singleton.class); + bind(CryptoComponent.class).to( + CryptoComponentImpl.class).in(Singleton.class); + bind(SecretKey.class).annotatedWith(SecretStorageKey.class).toInstance( + new SecretKeySpec(new byte[32], "AES")); // FIXME + } } diff --git a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..6779c6ad720da280cd15720db6dee7d55b172280 --- /dev/null +++ b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java @@ -0,0 +1,133 @@ +package net.sf.briar.transport; + +import java.security.InvalidKeyException; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.SecretKey; + +import net.sf.briar.api.Bytes; +import net.sf.briar.api.ContactId; +import net.sf.briar.api.crypto.CryptoComponent; +import net.sf.briar.api.db.DatabaseComponent; +import net.sf.briar.api.db.DatabaseListener; +import net.sf.briar.api.db.DbException; +import net.sf.briar.api.db.NoSuchContactException; +import net.sf.briar.api.transport.ConnectionRecogniser; +import net.sf.briar.api.transport.ConnectionWindow; + +class ConnectionRecogniserImpl implements ConnectionRecogniser, +DatabaseListener { + + private final int transportId; + private final CryptoComponent crypto; + private final DatabaseComponent db; + private final Map<Bytes, ContactId> tagToContact; + private final Map<Bytes, Long> tagToConnectionNumber; + private final Map<ContactId, Map<Long, Bytes>> contactToTags; + private final Map<ContactId, Cipher> contactToCipher; + private final Map<ContactId, ConnectionWindow> contactToWindow; + private boolean initialised = false; + + ConnectionRecogniserImpl(int transportId, CryptoComponent crypto, + DatabaseComponent db) { + this.transportId = transportId; + this.crypto = crypto; + this.db = db; + // FIXME: There's probably a tidier way of maintaining all this state + tagToContact = new HashMap<Bytes, ContactId>(); + tagToConnectionNumber = new HashMap<Bytes, Long>(); + contactToTags = new HashMap<ContactId, Map<Long, Bytes>>(); + contactToCipher = new HashMap<ContactId, Cipher>(); + contactToWindow = new HashMap<ContactId, ConnectionWindow>(); + db.addListener(this); + } + + private synchronized void initialise() throws DbException { + for(ContactId c : db.getContacts()) { + try { + // Initialise and store the contact's tag cipher + SecretKey tagKey = crypto.deriveTagKey(db.getSharedSecret(c)); + Cipher cipher = crypto.getTagCipher(); + try { + cipher.init(Cipher.ENCRYPT_MODE, tagKey); + } catch(InvalidKeyException badKey) { + throw new RuntimeException(badKey); + } + contactToCipher.put(c, cipher); + // Calculate the tags for the contact's connection window + ConnectionWindow w = db.getConnectionWindow(c, transportId); + Map<Long, Bytes> tags = new HashMap<Long, Bytes>(); + for(Long unseen : w.getUnseenConnectionNumbers()) { + Bytes expectedTag = new Bytes(calculateTag(c, unseen)); + tagToContact.put(expectedTag, c); + tagToConnectionNumber.put(expectedTag, unseen); + tags.put(unseen, expectedTag); + } + contactToTags.put(c, tags); + contactToWindow.put(c, w); + } catch(NoSuchContactException e) { + continue; + } + } + initialised = true; + } + + private synchronized byte[] calculateTag(ContactId c, + long connectionNumber) { + byte[] tag = TagEncoder.encodeTag(transportId, connectionNumber, 0L); + Cipher cipher = contactToCipher.get(c); + assert cipher != null; + try { + return cipher.doFinal(tag); + } catch(BadPaddingException e) { + throw new RuntimeException(e); + } catch(IllegalBlockSizeException e) { + throw new RuntimeException(e); + } + } + + public synchronized ContactId acceptConnection(byte[] tag) + throws DbException { + if(tag.length != 16) throw new IllegalArgumentException(); + if(!initialised) initialise(); + Bytes b = new Bytes(tag); + ContactId contactId = tagToContact.remove(b); + Long connectionNumber = tagToConnectionNumber.remove(b); + assert (contactId == null) == (connectionNumber == null); + if(contactId == null) return null; + // The tag was expected - update and save the connection window + ConnectionWindow w = contactToWindow.get(contactId); + assert w != null; + w.setSeen(connectionNumber); + db.setConnectionWindow(contactId, transportId, w); + // Update the set of expected tags + Map<Long, Bytes> oldTags = contactToTags.remove(contactId); + assert oldTags != null; + assert oldTags.containsKey(connectionNumber); + Map<Long, Bytes> newTags = new HashMap<Long, Bytes>(); + for(Long unseen : w.getUnseenConnectionNumbers()) { + Bytes expectedTag = oldTags.get(unseen); + if(expectedTag == null) { + expectedTag = new Bytes(calculateTag(contactId, unseen)); + tagToContact.put(expectedTag, contactId); + tagToConnectionNumber.put(expectedTag, connectionNumber); + } + newTags.put(unseen, expectedTag); + } + contactToTags.put(contactId, newTags); + return contactId; + } + + public void eventOccurred(Event e) { + // When the set of contacts changes we need to re-initialise everything + if(e == Event.CONTACTS_UPDATED) { + synchronized(this) { + initialised = false; + } + } + } +} diff --git a/components/net/sf/briar/transport/ConnectionWindowImpl.java b/components/net/sf/briar/transport/ConnectionWindowImpl.java index 956a5c722a072235edb610757dc22b0f0be81a48..af2c294870529366b19e3e41a1f0a761668b2090 100644 --- a/components/net/sf/briar/transport/ConnectionWindowImpl.java +++ b/components/net/sf/briar/transport/ConnectionWindowImpl.java @@ -7,8 +7,6 @@ import net.sf.briar.api.transport.ConnectionWindow; class ConnectionWindowImpl implements ConnectionWindow { - private static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1 - private long centre; private int bitmap; @@ -33,7 +31,7 @@ class ConnectionWindowImpl implements ConnectionWindow { private int getOffset(long connectionNumber) { if(connectionNumber < 0L) throw new IllegalArgumentException(); - if(connectionNumber > MAX_32_BIT_UNSIGNED) + if(connectionNumber > Constants.MAX_32_BIT_UNSIGNED) throw new IllegalArgumentException(); int offset = (int) (connectionNumber - centre) + 16; if(offset < 0 || offset > 31) throw new IllegalArgumentException(); @@ -58,10 +56,11 @@ class ConnectionWindowImpl implements ConnectionWindow { int mask = 0x80000000 >>> i; if((bitmap & mask) == 0) { long c = centre - 16 + i; - if(c >= 0L && c <= MAX_32_BIT_UNSIGNED) unseen.add(c); + if(c >= 0L && c <= Constants.MAX_32_BIT_UNSIGNED) unseen.add(c); } } - assert unseen.contains(centre) || centre == MAX_32_BIT_UNSIGNED + 1; + assert unseen.contains(centre) + || centre == Constants.MAX_32_BIT_UNSIGNED + 1; return unseen; } } diff --git a/components/net/sf/briar/transport/Constants.java b/components/net/sf/briar/transport/Constants.java new file mode 100644 index 0000000000000000000000000000000000000000..07c96eaa065c3032cb01f007fb2a4d0d882acf80 --- /dev/null +++ b/components/net/sf/briar/transport/Constants.java @@ -0,0 +1,8 @@ +package net.sf.briar.transport; + +interface Constants { + + static final int MAX_16_BIT_UNSIGNED = 65535; // 2^16 - 1 + static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1 + +} diff --git a/components/net/sf/briar/transport/PacketWriterImpl.java b/components/net/sf/briar/transport/PacketWriterImpl.java index c0f66d40e675e524a4c18d9d822394add76505cd..97c9626aa704a72c90abb3bc6cb6e699dfea696c 100644 --- a/components/net/sf/briar/transport/PacketWriterImpl.java +++ b/components/net/sf/briar/transport/PacketWriterImpl.java @@ -10,9 +10,6 @@ import net.sf.briar.api.transport.PacketWriter; class PacketWriterImpl extends FilterOutputStream implements PacketWriter { - private static final int MAX_16_BIT_UNSIGNED = 65535; // 2^16 - 1 - private static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1 - private final PacketEncrypter encrypter; private final Mac mac; private final int transportIdentifier; @@ -27,11 +24,11 @@ class PacketWriterImpl extends FilterOutputStream implements PacketWriter { this.encrypter = encrypter; this.mac = mac; if(transportIdentifier < 0) throw new IllegalArgumentException(); - if(transportIdentifier > MAX_16_BIT_UNSIGNED) + if(transportIdentifier > Constants.MAX_16_BIT_UNSIGNED) throw new IllegalArgumentException(); this.transportIdentifier = transportIdentifier; if(connectionNumber < 0L) throw new IllegalArgumentException(); - if(connectionNumber > MAX_32_BIT_UNSIGNED) + if(connectionNumber > Constants.MAX_32_BIT_UNSIGNED) throw new IllegalArgumentException(); this.connectionNumber = connectionNumber; } @@ -72,39 +69,14 @@ class PacketWriterImpl extends FilterOutputStream implements PacketWriter { } private void writeTag() throws IOException { - if(packetNumber > MAX_32_BIT_UNSIGNED) + if(packetNumber > Constants.MAX_32_BIT_UNSIGNED) throw new IllegalStateException(); - byte[] tag = new byte[16]; - // Encode the transport identifier as an unsigned 16-bit integer - writeUint16(transportIdentifier, tag, 2); - // Encode the connection number as an unsigned 32-bit integer - writeUint32(connectionNumber, tag, 4); - // Encode the packet number as an unsigned 32-bit integer - writeUint32(packetNumber, tag, 8); + byte[] tag = TagEncoder.encodeTag(transportIdentifier, connectionNumber, + packetNumber); // Write the tag to the encrypter and start calculating the MAC encrypter.writeTag(tag); mac.update(tag); packetNumber++; betweenPackets = false; } - - // Package access for testing - static void writeUint16(int i, byte[] b, int offset) { - assert i >= 0; - assert i <= MAX_16_BIT_UNSIGNED; - assert b.length >= offset + 2; - b[offset] = (byte) (i >> 8); - b[offset + 1] = (byte) (i & 0xFF); - } - - // Package access for testing - static void writeUint32(long i, byte[] b, int offset) { - assert i >= 0L; - assert i <= MAX_32_BIT_UNSIGNED; - assert b.length >= offset + 4; - b[offset] = (byte) (i >> 24); - b[offset + 1] = (byte) (i >> 16 & 0xFF); - b[offset + 2] = (byte) (i >> 8 & 0xFF); - b[offset + 3] = (byte) (i & 0xFF); - } } diff --git a/components/net/sf/briar/transport/TagEncoder.java b/components/net/sf/briar/transport/TagEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..aead6debbf2de9d251b8091ba03d4822e9c3f1f1 --- /dev/null +++ b/components/net/sf/briar/transport/TagEncoder.java @@ -0,0 +1,36 @@ +package net.sf.briar.transport; + +public class TagEncoder { + + static byte[] encodeTag(int transportIdentifier, long connectionNumber, + long packetNumber) { + byte[] tag = new byte[16]; + // Encode the transport identifier as an unsigned 16-bit integer + writeUint16(transportIdentifier, tag, 2); + // Encode the connection number as an unsigned 32-bit integer + writeUint32(connectionNumber, tag, 4); + // Encode the packet number as an unsigned 32-bit integer + writeUint32(packetNumber, tag, 8); + return tag; + } + + // Package access for testing + static void writeUint16(int i, byte[] b, int offset) { + assert i >= 0; + assert i <= Constants.MAX_16_BIT_UNSIGNED; + assert b.length >= offset + 2; + b[offset] = (byte) (i >> 8); + b[offset + 1] = (byte) (i & 0xFF); + } + + // Package access for testing + static void writeUint32(long i, byte[] b, int offset) { + assert i >= 0L; + assert i <= Constants.MAX_32_BIT_UNSIGNED; + assert b.length >= offset + 4; + b[offset] = (byte) (i >> 24); + b[offset + 1] = (byte) (i >> 16 & 0xFF); + b[offset + 2] = (byte) (i >> 8 & 0xFF); + b[offset + 3] = (byte) (i & 0xFF); + } +} diff --git a/test/build.xml b/test/build.xml index f034b76f42367c161cb89c61bd5cf945672b6a6c..2791cc2a029c6e1bb278c112185650a450cd6bae 100644 --- a/test/build.xml +++ b/test/build.xml @@ -35,6 +35,7 @@ <test name='net.sf.briar.transport.ConnectionWindowImplTest'/> <test name='net.sf.briar.transport.PacketEncrypterImplTest'/> <test name='net.sf.briar.transport.PacketWriterImplTest'/> + <test name='net.sf.briar.transport.TagEncoderTest'/> <test name='net.sf.briar.util.FileUtilsTest'/> <test name='net.sf.briar.util.StringUtilsTest'/> <test name='net.sf.briar.util.ZipUtilsTest'/> diff --git a/test/net/sf/briar/transport/PacketWriterImplTest.java b/test/net/sf/briar/transport/PacketWriterImplTest.java index 9bf4ee1166479c76067aa9a0fc35e8f5894a15a7..94783c7a28cd87bb7ebe2c2ef5b5ab038f568c68 100644 --- a/test/net/sf/briar/transport/PacketWriterImplTest.java +++ b/test/net/sf/briar/transport/PacketWriterImplTest.java @@ -140,28 +140,6 @@ public class PacketWriterImplTest extends TestCase { assertTrue(Arrays.equals(expectedMac1, actualMac1)); } - @Test - public void testWriteUint16() throws Exception { - byte[] b = new byte[3]; - PacketWriterImpl.writeUint16(0, b, 1); - assertEquals("000000", StringUtils.toHexString(b)); - PacketWriterImpl.writeUint16(1, b, 1); - assertEquals("000001", StringUtils.toHexString(b)); - PacketWriterImpl.writeUint16(65535, b, 1); - assertEquals("00FFFF", StringUtils.toHexString(b)); - } - - @Test - public void testWriteUint32() throws Exception { - byte[] b = new byte[5]; - PacketWriterImpl.writeUint32(0L, b, 1); - assertEquals("0000000000", StringUtils.toHexString(b)); - PacketWriterImpl.writeUint32(1L, b, 1); - assertEquals("0000000001", StringUtils.toHexString(b)); - PacketWriterImpl.writeUint32(4294967295L, b, 1); - assertEquals("00FFFFFFFF", StringUtils.toHexString(b)); - } - private static class NullPacketEncrypter implements PacketEncrypter { private final OutputStream out; diff --git a/test/net/sf/briar/transport/TagEncoderTest.java b/test/net/sf/briar/transport/TagEncoderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0f81d84a1a4d87b02a4047193b0920d445f76a15 --- /dev/null +++ b/test/net/sf/briar/transport/TagEncoderTest.java @@ -0,0 +1,32 @@ +package net.sf.briar.transport; + +import net.sf.briar.util.StringUtils; + +import org.junit.Test; + +import junit.framework.TestCase; + +public class TagEncoderTest extends TestCase { + + @Test + public void testWriteUint16() throws Exception { + byte[] b = new byte[3]; + TagEncoder.writeUint16(0, b, 1); + assertEquals("000000", StringUtils.toHexString(b)); + TagEncoder.writeUint16(1, b, 1); + assertEquals("000001", StringUtils.toHexString(b)); + TagEncoder.writeUint16(65535, b, 1); + assertEquals("00FFFF", StringUtils.toHexString(b)); + } + + @Test + public void testWriteUint32() throws Exception { + byte[] b = new byte[5]; + TagEncoder.writeUint32(0L, b, 1); + assertEquals("0000000000", StringUtils.toHexString(b)); + TagEncoder.writeUint32(1L, b, 1); + assertEquals("0000000001", StringUtils.toHexString(b)); + TagEncoder.writeUint32(4294967295L, b, 1); + assertEquals("00FFFFFFFF", StringUtils.toHexString(b)); + } +}