mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-22 07:39:53 +01:00
Added interfaces for reading and writing packets and recognising which
contact originated an incoming connection, and an implementation of the PacketWriter interface.
This commit is contained in:
@@ -4,13 +4,19 @@ import java.security.KeyPair;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
public interface CryptoComponent {
|
public interface CryptoComponent {
|
||||||
|
|
||||||
KeyPair generateKeyPair();
|
KeyPair generateKeyPair();
|
||||||
|
|
||||||
|
SecretKey generateSecretKey();
|
||||||
|
|
||||||
KeyParser getKeyParser();
|
KeyParser getKeyParser();
|
||||||
|
|
||||||
|
Mac getMac();
|
||||||
|
|
||||||
MessageDigest getMessageDigest();
|
MessageDigest getMessageDigest();
|
||||||
|
|
||||||
Signature getSignature();
|
Signature getSignature();
|
||||||
|
|||||||
16
api/net/sf/briar/api/transport/ConnectionRecogniser.java
Normal file
16
api/net/sf/briar/api/transport/ConnectionRecogniser.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package net.sf.briar.api.transport;
|
||||||
|
|
||||||
|
import net.sf.briar.api.ContactId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maintains a transport plugin's connection reordering window and decides
|
||||||
|
* whether incoming connections should be accepted or rejected.
|
||||||
|
*/
|
||||||
|
public interface ConnectionRecogniser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of the contact who created the tag if the connection
|
||||||
|
* should be accepted, or null if the connection should be rejected.
|
||||||
|
*/
|
||||||
|
ContactId acceptConnection(byte[] tag);
|
||||||
|
}
|
||||||
37
api/net/sf/briar/api/transport/PacketReader.java
Normal file
37
api/net/sf/briar/api/transport/PacketReader.java
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package net.sf.briar.api.transport;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import net.sf.briar.api.protocol.Ack;
|
||||||
|
import net.sf.briar.api.protocol.Batch;
|
||||||
|
import net.sf.briar.api.protocol.Offer;
|
||||||
|
import net.sf.briar.api.protocol.Request;
|
||||||
|
import net.sf.briar.api.protocol.SubscriptionUpdate;
|
||||||
|
import net.sf.briar.api.protocol.TransportUpdate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads unencrypted packets from an underlying input stream and authenticates
|
||||||
|
* them.
|
||||||
|
*/
|
||||||
|
public interface PacketReader {
|
||||||
|
|
||||||
|
boolean eof() throws IOException;
|
||||||
|
|
||||||
|
boolean hasAck() throws IOException;
|
||||||
|
Ack readAck() throws IOException;
|
||||||
|
|
||||||
|
boolean hasBatch() throws IOException;
|
||||||
|
Batch readBatch() throws IOException;
|
||||||
|
|
||||||
|
boolean hasOffer() throws IOException;
|
||||||
|
Offer readOffer() throws IOException;
|
||||||
|
|
||||||
|
boolean hasRequest() throws IOException;
|
||||||
|
Request readRequest() throws IOException;
|
||||||
|
|
||||||
|
boolean hasSubscriptionUpdate() throws IOException;
|
||||||
|
SubscriptionUpdate readSubscriptionUpdate() throws IOException;
|
||||||
|
|
||||||
|
boolean hasTransportUpdate() throws IOException;
|
||||||
|
TransportUpdate readTransportUpdate() throws IOException;
|
||||||
|
}
|
||||||
24
api/net/sf/briar/api/transport/PacketWriter.java
Normal file
24
api/net/sf/briar/api/transport/PacketWriter.java
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package net.sf.briar.api.transport;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter that adds tags and MACs to outgoing packets. Encryption is handled
|
||||||
|
* by the underlying output stream.
|
||||||
|
*/
|
||||||
|
public interface PacketWriter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the output stream to which packets should be written. (Note that
|
||||||
|
* this is not the underlying output stream.)
|
||||||
|
*/
|
||||||
|
OutputStream getOutputStream();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finishes writing the current packet (if any) and prepares to write the
|
||||||
|
* next packet. If this method is called twice in succession without any
|
||||||
|
* intervening writes, the underlying output stream will be unaffected.
|
||||||
|
*/
|
||||||
|
void nextPacket() throws IOException;
|
||||||
|
}
|
||||||
@@ -8,6 +8,10 @@ import java.security.NoSuchProviderException;
|
|||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
|
|
||||||
|
import javax.crypto.KeyGenerator;
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
import net.sf.briar.api.crypto.CryptoComponent;
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
import net.sf.briar.api.crypto.KeyParser;
|
import net.sf.briar.api.crypto.KeyParser;
|
||||||
|
|
||||||
@@ -18,11 +22,15 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
private static final String PROVIDER = "BC";
|
private static final String PROVIDER = "BC";
|
||||||
private static final String DIGEST_ALGO = "SHA-256";
|
private static final String DIGEST_ALGO = "SHA-256";
|
||||||
private static final String KEY_PAIR_ALGO = "ECDSA";
|
private static final String KEY_PAIR_ALGO = "ECDSA";
|
||||||
private static final int KEY_PAIR_KEYSIZE = 256;
|
private static final int KEY_PAIR_KEYSIZE = 256; // Bits
|
||||||
|
private static final String MAC_ALGO = "HMacSHA256";
|
||||||
|
private static final String SECRET_KEY_ALGO = "AES";
|
||||||
|
private static final int SECRET_KEY_KEYSIZE = 256; // Bits
|
||||||
private static final String SIGNATURE_ALGO = "ECDSA";
|
private static final String SIGNATURE_ALGO = "ECDSA";
|
||||||
|
|
||||||
private final KeyParser keyParser;
|
private final KeyParser keyParser;
|
||||||
private final KeyPairGenerator keyPairGenerator;
|
private final KeyPairGenerator keyPairGenerator;
|
||||||
|
private final KeyGenerator keyGenerator;
|
||||||
|
|
||||||
CryptoComponentImpl() {
|
CryptoComponentImpl() {
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
@@ -31,6 +39,9 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGO,
|
keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGO,
|
||||||
PROVIDER);
|
PROVIDER);
|
||||||
keyPairGenerator.initialize(KEY_PAIR_KEYSIZE);
|
keyPairGenerator.initialize(KEY_PAIR_KEYSIZE);
|
||||||
|
keyGenerator = KeyGenerator.getInstance(SECRET_KEY_ALGO,
|
||||||
|
PROVIDER);
|
||||||
|
keyGenerator.init(SECRET_KEY_KEYSIZE);
|
||||||
} catch(NoSuchAlgorithmException impossible) {
|
} catch(NoSuchAlgorithmException impossible) {
|
||||||
throw new RuntimeException(impossible);
|
throw new RuntimeException(impossible);
|
||||||
} catch(NoSuchProviderException impossible) {
|
} catch(NoSuchProviderException impossible) {
|
||||||
@@ -42,10 +53,24 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
return keyPairGenerator.generateKeyPair();
|
return keyPairGenerator.generateKeyPair();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SecretKey generateSecretKey() {
|
||||||
|
return keyGenerator.generateKey();
|
||||||
|
}
|
||||||
|
|
||||||
public KeyParser getKeyParser() {
|
public KeyParser getKeyParser() {
|
||||||
return keyParser;
|
return keyParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Mac getMac() {
|
||||||
|
try {
|
||||||
|
return Mac.getInstance(MAC_ALGO, PROVIDER);
|
||||||
|
} catch(NoSuchAlgorithmException impossible) {
|
||||||
|
throw new RuntimeException(impossible);
|
||||||
|
} catch(NoSuchProviderException impossible) {
|
||||||
|
throw new RuntimeException(impossible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public MessageDigest getMessageDigest() {
|
public MessageDigest getMessageDigest() {
|
||||||
try {
|
try {
|
||||||
return MessageDigest.getInstance(DIGEST_ALGO, PROVIDER);
|
return MessageDigest.getInstance(DIGEST_ALGO, PROVIDER);
|
||||||
|
|||||||
105
components/net/sf/briar/transport/PacketWriterImpl.java
Normal file
105
components/net/sf/briar/transport/PacketWriterImpl.java
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package net.sf.briar.transport;
|
||||||
|
|
||||||
|
import java.io.FilterOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
|
||||||
|
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 Mac mac;
|
||||||
|
private final int transportIdentifier;
|
||||||
|
private final long connectionNumber;
|
||||||
|
|
||||||
|
private long packetNumber = 0L;
|
||||||
|
private boolean betweenPackets = true;
|
||||||
|
|
||||||
|
PacketWriterImpl(OutputStream out, Mac mac, int transportIdentifier,
|
||||||
|
long connectionNumber) {
|
||||||
|
super(out);
|
||||||
|
this.mac = mac;
|
||||||
|
if(transportIdentifier < 0) throw new IllegalArgumentException();
|
||||||
|
if(transportIdentifier > MAX_16_BIT_UNSIGNED)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
this.transportIdentifier = transportIdentifier;
|
||||||
|
if(connectionNumber < 0L) throw new IllegalArgumentException();
|
||||||
|
if(connectionNumber > MAX_32_BIT_UNSIGNED)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
this.connectionNumber = connectionNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputStream getOutputStream() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void nextPacket() throws IOException {
|
||||||
|
if(!betweenPackets) writeMac();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
if(betweenPackets) writeTag();
|
||||||
|
out.write(b);
|
||||||
|
mac.update((byte) b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b) throws IOException {
|
||||||
|
if(betweenPackets) writeTag();
|
||||||
|
out.write(b);
|
||||||
|
mac.update(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int len, int off) throws IOException {
|
||||||
|
if(betweenPackets) writeTag();
|
||||||
|
out.write(b, len, off);
|
||||||
|
mac.update(b, len, off);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeMac() throws IOException {
|
||||||
|
out.write(mac.doFinal());
|
||||||
|
betweenPackets = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeTag() throws IOException {
|
||||||
|
if(packetNumber > 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);
|
||||||
|
// Write the tag to the underlying output stream and the MAC
|
||||||
|
out.write(tag);
|
||||||
|
mac.update(tag);
|
||||||
|
packetNumber++;
|
||||||
|
betweenPackets = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
<test name='net.sf.briar.serial.ReaderImplTest'/>
|
<test name='net.sf.briar.serial.ReaderImplTest'/>
|
||||||
<test name='net.sf.briar.serial.WriterImplTest'/>
|
<test name='net.sf.briar.serial.WriterImplTest'/>
|
||||||
<test name='net.sf.briar.setup.SetupWorkerTest'/>
|
<test name='net.sf.briar.setup.SetupWorkerTest'/>
|
||||||
|
<test name='net.sf.briar.transport.PacketWriterImplTest'/>
|
||||||
<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'/>
|
||||||
|
|||||||
136
test/net/sf/briar/transport/PacketWriterImplTest.java
Normal file
136
test/net/sf/briar/transport/PacketWriterImplTest.java
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
package net.sf.briar.transport;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
|
import net.sf.briar.api.transport.PacketWriter;
|
||||||
|
import net.sf.briar.crypto.CryptoModule;
|
||||||
|
import net.sf.briar.util.StringUtils;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.google.inject.Guice;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
|
||||||
|
public class PacketWriterImplTest extends TestCase {
|
||||||
|
|
||||||
|
private final Mac mac;
|
||||||
|
|
||||||
|
public PacketWriterImplTest() throws Exception {
|
||||||
|
super();
|
||||||
|
Injector i = Guice.createInjector(new CryptoModule());
|
||||||
|
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
|
||||||
|
mac = crypto.getMac();
|
||||||
|
mac.init(crypto.generateSecretKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFirstWriteTriggersTag() throws Exception {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
PacketWriter p = new PacketWriterImpl(out, mac, 0, 0L);
|
||||||
|
p.getOutputStream().write(0);
|
||||||
|
// There should be 16 zero bytes for the tag, 1 for the byte written
|
||||||
|
assertTrue(Arrays.equals(new byte[17], out.toByteArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNextPacketAfterWriteTriggersMac() throws Exception {
|
||||||
|
// Calculate what the MAC should be
|
||||||
|
mac.update(new byte[17]);
|
||||||
|
byte[] expectedMac = mac.doFinal();
|
||||||
|
// Check that the PacketWriter calculates and writes the correct MAC
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
PacketWriter p = new PacketWriterImpl(out, mac, 0, 0L);
|
||||||
|
p.getOutputStream().write(0);
|
||||||
|
p.nextPacket();
|
||||||
|
byte[] written = out.toByteArray();
|
||||||
|
assertEquals(17 + expectedMac.length, written.length);
|
||||||
|
byte[] actualMac = new byte[expectedMac.length];
|
||||||
|
System.arraycopy(written, 17, actualMac, 0, actualMac.length);
|
||||||
|
assertTrue(Arrays.equals(expectedMac, actualMac));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtraCallsToNextPacketDoNothing() throws Exception {
|
||||||
|
// Calculate what the MAC should be
|
||||||
|
mac.update(new byte[17]);
|
||||||
|
byte[] expectedMac = mac.doFinal();
|
||||||
|
// Check that the PacketWriter calculates and writes the correct MAC
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
PacketWriter p = new PacketWriterImpl(out, mac, 0, 0L);
|
||||||
|
// Initial calls to nextPacket() should have no effect
|
||||||
|
p.nextPacket();
|
||||||
|
p.nextPacket();
|
||||||
|
p.nextPacket();
|
||||||
|
p.getOutputStream().write(0);
|
||||||
|
p.nextPacket();
|
||||||
|
// Extra calls to nextPacket() should have no effect
|
||||||
|
p.nextPacket();
|
||||||
|
p.nextPacket();
|
||||||
|
p.nextPacket();
|
||||||
|
byte[] written = out.toByteArray();
|
||||||
|
assertEquals(17 + expectedMac.length, written.length);
|
||||||
|
byte[] actualMac = new byte[expectedMac.length];
|
||||||
|
System.arraycopy(written, 17, actualMac, 0, actualMac.length);
|
||||||
|
assertTrue(Arrays.equals(expectedMac, actualMac));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPacketNumberIsIncremented() throws Exception {
|
||||||
|
byte[] expectedTag = StringUtils.fromHexString(
|
||||||
|
"0000" // 16 bits reserved
|
||||||
|
+ "F00D" // 16 bits for the transport ID
|
||||||
|
+ "DEADBEEF" // 32 bits for the connection number
|
||||||
|
+ "00000000" // 32 bits for the packet number
|
||||||
|
+ "00000000" // 32 bits for the block number
|
||||||
|
);
|
||||||
|
byte[] expectedTag1 = StringUtils.fromHexString(
|
||||||
|
"0000" // 16 bits reserved
|
||||||
|
+ "F00D" // 16 bits for the transport ID
|
||||||
|
+ "DEADBEEF" // 32 bits for the connection number
|
||||||
|
+ "00000001" // 32 bits for the packet number
|
||||||
|
+ "00000000" // 32 bits for the block number
|
||||||
|
);
|
||||||
|
// Calculate what the MAC on the first packet should be
|
||||||
|
mac.update(expectedTag);
|
||||||
|
mac.update((byte) 0);
|
||||||
|
byte[] expectedMac = mac.doFinal();
|
||||||
|
// Calculate what the MAC on the second packet should be
|
||||||
|
mac.update(expectedTag1);
|
||||||
|
mac.update((byte) 0);
|
||||||
|
byte[] expectedMac1 = mac.doFinal();
|
||||||
|
// Check that the PacketWriter writes the correct tags and MACs
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
PacketWriter p = new PacketWriterImpl(out, mac, 0xF00D, 0xDEADBEEFL);
|
||||||
|
// Packet one
|
||||||
|
p.getOutputStream().write(0);
|
||||||
|
p.nextPacket();
|
||||||
|
// Packet two
|
||||||
|
p.getOutputStream().write(0);
|
||||||
|
p.nextPacket();
|
||||||
|
byte[] written = out.toByteArray();
|
||||||
|
assertEquals(17 + expectedMac.length + 17 + expectedMac1.length,
|
||||||
|
written.length);
|
||||||
|
// Check the first packet's tag
|
||||||
|
byte[] actualTag = new byte[16];
|
||||||
|
System.arraycopy(written, 0, actualTag, 0, 16);
|
||||||
|
assertTrue(Arrays.equals(expectedTag, actualTag));
|
||||||
|
// Check the first packet's MAC
|
||||||
|
byte[] actualMac = new byte[expectedMac.length];
|
||||||
|
System.arraycopy(written, 17, actualMac, 0, actualMac.length);
|
||||||
|
assertTrue(Arrays.equals(expectedMac, actualMac));
|
||||||
|
// Check the second packet's tag
|
||||||
|
byte[] actualTag1 = new byte[16];
|
||||||
|
System.arraycopy(written, 17 + expectedMac.length, actualTag1, 0, 16);
|
||||||
|
assertTrue(Arrays.equals(expectedTag1, actualTag1));
|
||||||
|
// Check the second packet's MAC
|
||||||
|
byte[] actualMac1 = new byte[expectedMac1.length];
|
||||||
|
System.arraycopy(written, 17 + expectedMac.length + 17, actualMac1, 0,
|
||||||
|
actualMac1.length);
|
||||||
|
assertTrue(Arrays.equals(expectedMac1, actualMac1));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user