mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
PacketWriter is implemented by two classes: PacketWriterImpl and
PacketEncrypter. The separation allows authentication and encryption to be tested separately.
This commit is contained in:
@@ -4,6 +4,7 @@ import java.security.KeyPair;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.Signature;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
@@ -19,5 +20,9 @@ public interface CryptoComponent {
|
||||
|
||||
MessageDigest getMessageDigest();
|
||||
|
||||
Cipher getPacketCipher();
|
||||
|
||||
Signature getSignature();
|
||||
|
||||
Cipher getTagCipher();
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ 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.
|
||||
* A filter that adds tags and MACs to outgoing packets, encrypts them and
|
||||
* writes them to the underlying output stream.
|
||||
*/
|
||||
public interface PacketWriter {
|
||||
|
||||
|
||||
@@ -8,8 +8,10 @@ import java.security.NoSuchProviderException;
|
||||
import java.security.Security;
|
||||
import java.security.Signature;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
@@ -24,9 +26,11 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
private static final String KEY_PAIR_ALGO = "ECDSA";
|
||||
private static final int KEY_PAIR_KEYSIZE = 256; // Bits
|
||||
private static final String MAC_ALGO = "HMacSHA256";
|
||||
private static final String PACKET_CIPHER_ALGO = "AES/CTR/NoPadding";
|
||||
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 TAG_CIPHER_ALGO = "AES/ECB/NoPadding";
|
||||
|
||||
private final KeyParser keyParser;
|
||||
private final KeyPairGenerator keyPairGenerator;
|
||||
@@ -81,6 +85,18 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public Cipher getPacketCipher() {
|
||||
try {
|
||||
return Cipher.getInstance(PACKET_CIPHER_ALGO, PROVIDER);
|
||||
} catch(NoSuchAlgorithmException impossible) {
|
||||
throw new RuntimeException(impossible);
|
||||
} catch(NoSuchPaddingException impossible) {
|
||||
throw new RuntimeException(impossible);
|
||||
} catch(NoSuchProviderException impossible) {
|
||||
throw new RuntimeException(impossible);
|
||||
}
|
||||
}
|
||||
|
||||
public Signature getSignature() {
|
||||
try {
|
||||
return Signature.getInstance(SIGNATURE_ALGO, PROVIDER);
|
||||
@@ -90,4 +106,16 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
throw new RuntimeException(impossible);
|
||||
}
|
||||
}
|
||||
|
||||
public Cipher getTagCipher() {
|
||||
try {
|
||||
return Cipher.getInstance(TAG_CIPHER_ALGO, PROVIDER);
|
||||
} catch(NoSuchAlgorithmException impossible) {
|
||||
throw new RuntimeException(impossible);
|
||||
} catch(NoSuchPaddingException impossible) {
|
||||
throw new RuntimeException(impossible);
|
||||
} catch(NoSuchProviderException impossible) {
|
||||
throw new RuntimeException(impossible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
components/net/sf/briar/transport/PacketEncrypter.java
Normal file
13
components/net/sf/briar/transport/PacketEncrypter.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
interface PacketEncrypter {
|
||||
|
||||
OutputStream getOutputStream();
|
||||
|
||||
void writeTag(byte[] tag) throws IOException;
|
||||
|
||||
void finishPacket() throws IOException;
|
||||
}
|
||||
86
components/net/sf/briar/transport/PacketEncrypterImpl.java
Normal file
86
components/net/sf/briar/transport/PacketEncrypterImpl.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
class PacketEncrypterImpl extends FilterOutputStream
|
||||
implements PacketEncrypter {
|
||||
|
||||
private final OutputStream out;
|
||||
private final Cipher tagCipher, packetCipher;
|
||||
private final SecretKey packetKey;
|
||||
|
||||
PacketEncrypterImpl(OutputStream out, Cipher tagCipher,
|
||||
Cipher packetCipher, SecretKey tagKey, SecretKey packetKey) {
|
||||
super(out);
|
||||
this.out = out;
|
||||
this.tagCipher = tagCipher;
|
||||
this.packetCipher = packetCipher;
|
||||
this.packetKey = packetKey;
|
||||
try {
|
||||
tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
|
||||
} catch(InvalidKeyException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
if(tagCipher.getOutputSize(16) != 16)
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void writeTag(byte[] tag) throws IOException {
|
||||
if(tag.length != 16) throw new IllegalArgumentException();
|
||||
IvParameterSpec iv = new IvParameterSpec(tag);
|
||||
try {
|
||||
out.write(tagCipher.doFinal(tag));
|
||||
packetCipher.init(Cipher.ENCRYPT_MODE, packetKey, iv);
|
||||
} catch(BadPaddingException badCipher) {
|
||||
throw new IOException(badCipher);
|
||||
} catch(IllegalBlockSizeException badCipher) {
|
||||
throw new RuntimeException(badCipher);
|
||||
} catch(InvalidAlgorithmParameterException badIv) {
|
||||
throw new RuntimeException(badIv);
|
||||
} catch(InvalidKeyException badKey) {
|
||||
throw new RuntimeException(badKey);
|
||||
}
|
||||
}
|
||||
|
||||
public void finishPacket() throws IOException {
|
||||
try {
|
||||
out.write(packetCipher.doFinal());
|
||||
} catch(BadPaddingException badCipher) {
|
||||
throw new IOException(badCipher);
|
||||
} catch(IllegalBlockSizeException badCipher) {
|
||||
throw new RuntimeException(badCipher);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
byte[] ciphertext = packetCipher.update(new byte[] {(byte) b});
|
||||
if(ciphertext != null) out.write(ciphertext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b) throws IOException {
|
||||
byte[] ciphertext = packetCipher.update(b);
|
||||
if(ciphertext != null) out.write(ciphertext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
byte[] ciphertext = packetCipher.update(b, off, len);
|
||||
if(ciphertext != null) out.write(ciphertext);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ 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;
|
||||
private final long connectionNumber;
|
||||
@@ -20,9 +21,10 @@ class PacketWriterImpl extends FilterOutputStream implements PacketWriter {
|
||||
private long packetNumber = 0L;
|
||||
private boolean betweenPackets = true;
|
||||
|
||||
PacketWriterImpl(OutputStream out, Mac mac, int transportIdentifier,
|
||||
long connectionNumber) {
|
||||
super(out);
|
||||
PacketWriterImpl(PacketEncrypter encrypter, Mac mac,
|
||||
int transportIdentifier, long connectionNumber) {
|
||||
super(encrypter.getOutputStream());
|
||||
this.encrypter = encrypter;
|
||||
this.mac = mac;
|
||||
if(transportIdentifier < 0) throw new IllegalArgumentException();
|
||||
if(transportIdentifier > MAX_16_BIT_UNSIGNED)
|
||||
@@ -65,6 +67,7 @@ class PacketWriterImpl extends FilterOutputStream implements PacketWriter {
|
||||
|
||||
private void writeMac() throws IOException {
|
||||
out.write(mac.doFinal());
|
||||
encrypter.finishPacket();
|
||||
betweenPackets = true;
|
||||
}
|
||||
|
||||
@@ -78,14 +81,15 @@ class PacketWriterImpl extends FilterOutputStream implements PacketWriter {
|
||||
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);
|
||||
// Write the tag to the encrypter and start calculating the MAC
|
||||
encrypter.writeTag(tag);
|
||||
mac.update(tag);
|
||||
packetNumber++;
|
||||
betweenPackets = false;
|
||||
}
|
||||
|
||||
private void writeUint16(int i, byte[] b, int offset) {
|
||||
// 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;
|
||||
@@ -93,7 +97,8 @@ class PacketWriterImpl extends FilterOutputStream implements PacketWriter {
|
||||
b[offset + 1] = (byte) (i & 0xFF);
|
||||
}
|
||||
|
||||
private void writeUint32(long i, byte[] b, int offset) {
|
||||
// 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;
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<test name='net.sf.briar.serial.ReaderImplTest'/>
|
||||
<test name='net.sf.briar.serial.WriterImplTest'/>
|
||||
<test name='net.sf.briar.setup.SetupWorkerTest'/>
|
||||
<test name='net.sf.briar.transport.PacketEncrypterImplTest'/>
|
||||
<test name='net.sf.briar.transport.PacketWriterImplTest'/>
|
||||
<test name='net.sf.briar.util.FileUtilsTest'/>
|
||||
<test name='net.sf.briar.util.StringUtilsTest'/>
|
||||
|
||||
76
test/net/sf/briar/transport/PacketEncrypterImplTest.java
Normal file
76
test/net/sf/briar/transport/PacketEncrypterImplTest.java
Normal file
@@ -0,0 +1,76 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.crypto.CryptoModule;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
public class PacketEncrypterImplTest extends TestCase {
|
||||
|
||||
private final Cipher tagCipher, packetCipher;
|
||||
private final SecretKey tagKey, packetKey;
|
||||
|
||||
public PacketEncrypterImplTest() {
|
||||
super();
|
||||
Injector i = Guice.createInjector(new CryptoModule());
|
||||
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
|
||||
tagCipher = crypto.getTagCipher();
|
||||
packetCipher = crypto.getPacketCipher();
|
||||
tagKey = crypto.generateSecretKey();
|
||||
packetKey = crypto.generateSecretKey();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleBytePacket() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
PacketEncrypter p = new PacketEncrypterImpl(out, tagCipher,
|
||||
packetCipher, tagKey, packetKey);
|
||||
p.writeTag(new byte[16]);
|
||||
p.getOutputStream().write((byte) 0);
|
||||
p.finishPacket();
|
||||
assertEquals(17, out.toByteArray().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryption() throws Exception {
|
||||
byte[] tag = new byte[16];
|
||||
byte[] packet = new byte[123];
|
||||
// Calculate the expected encrypted tag
|
||||
tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
|
||||
byte[] expectedTag = tagCipher.doFinal(tag);
|
||||
assertEquals(tag.length, expectedTag.length);
|
||||
// Calculate the expected encrypted packet
|
||||
IvParameterSpec iv = new IvParameterSpec(tag);
|
||||
packetCipher.init(Cipher.ENCRYPT_MODE, packetKey, iv);
|
||||
byte[] expectedPacket = packetCipher.doFinal(packet);
|
||||
assertEquals(packet.length, expectedPacket.length);
|
||||
// Check that the PacketEncrypter gets the same results
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
PacketEncrypter p = new PacketEncrypterImpl(out, tagCipher,
|
||||
packetCipher, tagKey, packetKey);
|
||||
p.writeTag(tag);
|
||||
p.getOutputStream().write(packet);
|
||||
p.finishPacket();
|
||||
byte[] ciphertext = out.toByteArray();
|
||||
assertEquals(16 + packet.length, ciphertext.length);
|
||||
// Check the tag
|
||||
byte[] actualTag = new byte[16];
|
||||
System.arraycopy(ciphertext, 0, actualTag, 0, 16);
|
||||
assertTrue(Arrays.equals(expectedTag, actualTag));
|
||||
// Check the packet
|
||||
byte[] actualPacket = new byte[packet.length];
|
||||
System.arraycopy(ciphertext, 16, actualPacket, 0, actualPacket.length);
|
||||
assertTrue(Arrays.equals(expectedPacket, actualPacket));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package net.sf.briar.transport;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
@@ -31,7 +33,8 @@ public class PacketWriterImplTest extends TestCase {
|
||||
@Test
|
||||
public void testFirstWriteTriggersTag() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
PacketWriter p = new PacketWriterImpl(out, mac, 0, 0L);
|
||||
PacketEncrypter e = new NullPacketEncrypter(out);
|
||||
PacketWriter p = new PacketWriterImpl(e, 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()));
|
||||
@@ -44,7 +47,8 @@ public class PacketWriterImplTest extends TestCase {
|
||||
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);
|
||||
PacketEncrypter e = new NullPacketEncrypter(out);
|
||||
PacketWriter p = new PacketWriterImpl(e, mac, 0, 0L);
|
||||
p.getOutputStream().write(0);
|
||||
p.nextPacket();
|
||||
byte[] written = out.toByteArray();
|
||||
@@ -61,7 +65,8 @@ public class PacketWriterImplTest extends TestCase {
|
||||
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);
|
||||
PacketEncrypter e = new NullPacketEncrypter(out);
|
||||
PacketWriter p = new PacketWriterImpl(e, mac, 0, 0L);
|
||||
// Initial calls to nextPacket() should have no effect
|
||||
p.nextPacket();
|
||||
p.nextPacket();
|
||||
@@ -105,7 +110,8 @@ public class PacketWriterImplTest extends TestCase {
|
||||
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);
|
||||
PacketEncrypter e = new NullPacketEncrypter(out);
|
||||
PacketWriter p = new PacketWriterImpl(e, mac, 0xF00D, 0xDEADBEEFL);
|
||||
// Packet one
|
||||
p.getOutputStream().write(0);
|
||||
p.nextPacket();
|
||||
@@ -133,4 +139,45 @@ public class PacketWriterImplTest extends TestCase {
|
||||
actualMac1.length);
|
||||
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;
|
||||
|
||||
private NullPacketEncrypter(OutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() {
|
||||
return out;
|
||||
}
|
||||
|
||||
public void writeTag(byte[] tag) throws IOException {
|
||||
out.write(tag);
|
||||
}
|
||||
|
||||
public void finishPacket() {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user