mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-20 06:39:54 +01:00
Separated tag encoding from PacketWriterImpl, since it's also needed
by the code that recognises tags. Implemented ConnectionRecogniser (untested).
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
package net.sf.briar.crypto;
|
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.CryptoComponent;
|
||||||
|
import net.sf.briar.api.crypto.SecretStorageKey;
|
||||||
|
|
||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
@@ -9,6 +13,10 @@ public class CryptoModule extends AbstractModule {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
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
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
133
components/net/sf/briar/transport/ConnectionRecogniserImpl.java
Normal file
133
components/net/sf/briar/transport/ConnectionRecogniserImpl.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,8 +7,6 @@ import net.sf.briar.api.transport.ConnectionWindow;
|
|||||||
|
|
||||||
class ConnectionWindowImpl implements ConnectionWindow {
|
class ConnectionWindowImpl implements ConnectionWindow {
|
||||||
|
|
||||||
private static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1
|
|
||||||
|
|
||||||
private long centre;
|
private long centre;
|
||||||
private int bitmap;
|
private int bitmap;
|
||||||
|
|
||||||
@@ -33,7 +31,7 @@ class ConnectionWindowImpl implements ConnectionWindow {
|
|||||||
|
|
||||||
private int getOffset(long connectionNumber) {
|
private int getOffset(long connectionNumber) {
|
||||||
if(connectionNumber < 0L) throw new IllegalArgumentException();
|
if(connectionNumber < 0L) throw new IllegalArgumentException();
|
||||||
if(connectionNumber > MAX_32_BIT_UNSIGNED)
|
if(connectionNumber > Constants.MAX_32_BIT_UNSIGNED)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
int offset = (int) (connectionNumber - centre) + 16;
|
int offset = (int) (connectionNumber - centre) + 16;
|
||||||
if(offset < 0 || offset > 31) throw new IllegalArgumentException();
|
if(offset < 0 || offset > 31) throw new IllegalArgumentException();
|
||||||
@@ -58,10 +56,11 @@ class ConnectionWindowImpl implements ConnectionWindow {
|
|||||||
int mask = 0x80000000 >>> i;
|
int mask = 0x80000000 >>> i;
|
||||||
if((bitmap & mask) == 0) {
|
if((bitmap & mask) == 0) {
|
||||||
long c = centre - 16 + i;
|
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;
|
return unseen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
components/net/sf/briar/transport/Constants.java
Normal file
8
components/net/sf/briar/transport/Constants.java
Normal file
@@ -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
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,9 +10,6 @@ import net.sf.briar.api.transport.PacketWriter;
|
|||||||
|
|
||||||
class PacketWriterImpl extends FilterOutputStream implements 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 PacketEncrypter encrypter;
|
||||||
private final Mac mac;
|
private final Mac mac;
|
||||||
private final int transportIdentifier;
|
private final int transportIdentifier;
|
||||||
@@ -27,11 +24,11 @@ class PacketWriterImpl extends FilterOutputStream implements PacketWriter {
|
|||||||
this.encrypter = encrypter;
|
this.encrypter = encrypter;
|
||||||
this.mac = mac;
|
this.mac = mac;
|
||||||
if(transportIdentifier < 0) throw new IllegalArgumentException();
|
if(transportIdentifier < 0) throw new IllegalArgumentException();
|
||||||
if(transportIdentifier > MAX_16_BIT_UNSIGNED)
|
if(transportIdentifier > Constants.MAX_16_BIT_UNSIGNED)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
this.transportIdentifier = transportIdentifier;
|
this.transportIdentifier = transportIdentifier;
|
||||||
if(connectionNumber < 0L) throw new IllegalArgumentException();
|
if(connectionNumber < 0L) throw new IllegalArgumentException();
|
||||||
if(connectionNumber > MAX_32_BIT_UNSIGNED)
|
if(connectionNumber > Constants.MAX_32_BIT_UNSIGNED)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
this.connectionNumber = connectionNumber;
|
this.connectionNumber = connectionNumber;
|
||||||
}
|
}
|
||||||
@@ -72,39 +69,14 @@ class PacketWriterImpl extends FilterOutputStream implements PacketWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void writeTag() throws IOException {
|
private void writeTag() throws IOException {
|
||||||
if(packetNumber > MAX_32_BIT_UNSIGNED)
|
if(packetNumber > Constants.MAX_32_BIT_UNSIGNED)
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
byte[] tag = new byte[16];
|
byte[] tag = TagEncoder.encodeTag(transportIdentifier, connectionNumber,
|
||||||
// Encode the transport identifier as an unsigned 16-bit integer
|
packetNumber);
|
||||||
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);
|
|
||||||
// Write the tag to the encrypter and start calculating the MAC
|
// Write the tag to the encrypter and start calculating the MAC
|
||||||
encrypter.writeTag(tag);
|
encrypter.writeTag(tag);
|
||||||
mac.update(tag);
|
mac.update(tag);
|
||||||
packetNumber++;
|
packetNumber++;
|
||||||
betweenPackets = false;
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
36
components/net/sf/briar/transport/TagEncoder.java
Normal file
36
components/net/sf/briar/transport/TagEncoder.java
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
<test name='net.sf.briar.transport.ConnectionWindowImplTest'/>
|
<test name='net.sf.briar.transport.ConnectionWindowImplTest'/>
|
||||||
<test name='net.sf.briar.transport.PacketEncrypterImplTest'/>
|
<test name='net.sf.briar.transport.PacketEncrypterImplTest'/>
|
||||||
<test name='net.sf.briar.transport.PacketWriterImplTest'/>
|
<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.FileUtilsTest'/>
|
||||||
<test name='net.sf.briar.util.StringUtilsTest'/>
|
<test name='net.sf.briar.util.StringUtilsTest'/>
|
||||||
<test name='net.sf.briar.util.ZipUtilsTest'/>
|
<test name='net.sf.briar.util.ZipUtilsTest'/>
|
||||||
|
|||||||
@@ -140,28 +140,6 @@ public class PacketWriterImplTest extends TestCase {
|
|||||||
assertTrue(Arrays.equals(expectedMac1, actualMac1));
|
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 static class NullPacketEncrypter implements PacketEncrypter {
|
||||||
|
|
||||||
private final OutputStream out;
|
private final OutputStream out;
|
||||||
|
|||||||
32
test/net/sf/briar/transport/TagEncoderTest.java
Normal file
32
test/net/sf/briar/transport/TagEncoderTest.java
Normal file
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user