diff --git a/api/net/sf/briar/api/serial/FormatException.java b/api/net/sf/briar/api/FormatException.java similarity index 82% rename from api/net/sf/briar/api/serial/FormatException.java rename to api/net/sf/briar/api/FormatException.java index 784f44e30685166f4a7202e17fb4be39419c9fc9..b48f8ba51f08f7b38f0d229dbf01c16e4635db9d 100644 --- a/api/net/sf/briar/api/serial/FormatException.java +++ b/api/net/sf/briar/api/FormatException.java @@ -1,4 +1,4 @@ -package net.sf.briar.api.serial; +package net.sf.briar.api; import java.io.IOException; diff --git a/api/net/sf/briar/api/crypto/CryptoComponent.java b/api/net/sf/briar/api/crypto/CryptoComponent.java index 373daea801c4430184d185c2c74a47dfd45e4048..504ed97055b130202be2d8a65c781512757276ad 100644 --- a/api/net/sf/briar/api/crypto/CryptoComponent.java +++ b/api/net/sf/briar/api/crypto/CryptoComponent.java @@ -12,13 +12,13 @@ public interface CryptoComponent { SecretKey deriveIncomingMacKey(byte[] secret); - SecretKey deriveIncomingPacketKey(byte[] secret); + SecretKey deriveIncomingFrameKey(byte[] secret); SecretKey deriveIncomingTagKey(byte[] secret); SecretKey deriveOutgoingMacKey(byte[] secret); - SecretKey deriveOutgoingPacketKey(byte[] secret); + SecretKey deriveOutgoingFrameKey(byte[] secret); SecretKey deriveOutgoingTagKey(byte[] secret); @@ -32,7 +32,7 @@ public interface CryptoComponent { MessageDigest getMessageDigest(); - Cipher getPacketCipher(); + Cipher getFrameCipher(); Signature getSignature(); diff --git a/api/net/sf/briar/api/transport/ConnectionReader.java b/api/net/sf/briar/api/transport/ConnectionReader.java new file mode 100644 index 0000000000000000000000000000000000000000..cac3fbe40c825bd368c1b2aa1ea7c63fcab029c4 --- /dev/null +++ b/api/net/sf/briar/api/transport/ConnectionReader.java @@ -0,0 +1,13 @@ +package net.sf.briar.api.transport; + +import java.io.InputStream; + +/** Decrypts and authenticates data received over a connection. */ +public interface ConnectionReader { + + /** + * Returns an input stream from which the decrypted, authenticated data can + * be read. + */ + InputStream getInputStream(); +} diff --git a/api/net/sf/briar/api/transport/ConnectionReaderFactory.java b/api/net/sf/briar/api/transport/ConnectionReaderFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..536123f48e6cf08ab3efdd89b428dc420f85ac00 --- /dev/null +++ b/api/net/sf/briar/api/transport/ConnectionReaderFactory.java @@ -0,0 +1,9 @@ +package net.sf.briar.api.transport; + +import java.io.InputStream; + +public interface ConnectionReaderFactory { + + ConnectionReader createConnectionReader(InputStream in, int transportId, + long connection, byte[] secret); +} diff --git a/api/net/sf/briar/api/transport/ConnectionWriter.java b/api/net/sf/briar/api/transport/ConnectionWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..c7ed2038acf5e73f3c12356f6e72bcb89aa1e79d --- /dev/null +++ b/api/net/sf/briar/api/transport/ConnectionWriter.java @@ -0,0 +1,13 @@ +package net.sf.briar.api.transport; + +import java.io.OutputStream; + +/** Encrypts and authenticates data to be sent over a connection. */ +public interface ConnectionWriter { + + /** + * Returns an output stream to which unencrypted, unauthenticated data can + * be written. + */ + OutputStream getOutputStream(); +} diff --git a/api/net/sf/briar/api/transport/ConnectionWriterFactory.java b/api/net/sf/briar/api/transport/ConnectionWriterFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..737096bfe88ad4f629d9d9e5e704402f75926f5c --- /dev/null +++ b/api/net/sf/briar/api/transport/ConnectionWriterFactory.java @@ -0,0 +1,9 @@ +package net.sf.briar.api.transport; + +import java.io.OutputStream; + +public interface ConnectionWriterFactory { + + ConnectionWriter createConnectionWriter(OutputStream out, int transportId, + long connection, byte[] secret); +} diff --git a/api/net/sf/briar/api/transport/PacketReader.java b/api/net/sf/briar/api/transport/PacketReader.java deleted file mode 100644 index 683f3cea087276516954fb4782ee2b555c056119..0000000000000000000000000000000000000000 --- a/api/net/sf/briar/api/transport/PacketReader.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.sf.briar.api.transport; - -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; - -/** - * Reads encrypted packets from an underlying input stream, decrypts and - * authenticates them. - */ -public interface PacketReader { - - /** - * Returns the input stream from which packets should be read. (Note that - * this is not the underlying input stream.) - */ - InputStream getInputStream(); - - /** - * Finishes reading the current packet (if any), authenticates the packet - * and prepares to read the next packet. If this method is called twice in - * succession without any intervening reads, the underlying input stream - * will be unaffected. - */ - void finishPacket() throws IOException, GeneralSecurityException; -} diff --git a/api/net/sf/briar/api/transport/PacketReaderFactory.java b/api/net/sf/briar/api/transport/PacketReaderFactory.java deleted file mode 100644 index 8e2ec5b3b651c03ac30de1c1c1ae2cbd3216de91..0000000000000000000000000000000000000000 --- a/api/net/sf/briar/api/transport/PacketReaderFactory.java +++ /dev/null @@ -1,9 +0,0 @@ -package net.sf.briar.api.transport; - -import java.io.InputStream; - -public interface PacketReaderFactory { - - PacketReader createPacketReader(byte[] firstTag, InputStream in, - int transportId, long connection, byte[] secret); -} diff --git a/api/net/sf/briar/api/transport/PacketWriter.java b/api/net/sf/briar/api/transport/PacketWriter.java deleted file mode 100644 index bc4d92a0a682bdb1a0e1677fd5c9de9916ace839..0000000000000000000000000000000000000000 --- a/api/net/sf/briar/api/transport/PacketWriter.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.sf.briar.api.transport; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * A filter that adds tags and MACs to outgoing packets, encrypts them and - * writes them to 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 finishPacket() throws IOException; -} diff --git a/api/net/sf/briar/api/transport/PacketWriterFactory.java b/api/net/sf/briar/api/transport/PacketWriterFactory.java deleted file mode 100644 index c93e41e7567068d258d4a7b7ea0e97d6dafeea1c..0000000000000000000000000000000000000000 --- a/api/net/sf/briar/api/transport/PacketWriterFactory.java +++ /dev/null @@ -1,9 +0,0 @@ -package net.sf.briar.api.transport; - -import java.io.OutputStream; - -public interface PacketWriterFactory { - - PacketWriter createPacketWriter(OutputStream out, int transportId, - long connection, byte[] secret); -} diff --git a/api/net/sf/briar/api/transport/TransportConstants.java b/api/net/sf/briar/api/transport/TransportConstants.java index 3b8503f5663d63d6721ce3a381e79451db5b28ae..2746a8c0dfd9e6e1f8cea917928eebf8a8239da5 100644 --- a/api/net/sf/briar/api/transport/TransportConstants.java +++ b/api/net/sf/briar/api/transport/TransportConstants.java @@ -9,14 +9,4 @@ public interface TransportConstants { /** The length in bytes of the tag that uniquely identifies a connection. */ static final int TAG_LENGTH = 16; - - /** - * The maximum value that can be represented as an unsigned 16-bit integer. - */ - static final int MAX_16_BIT_UNSIGNED = 65535; // 2^16 - 1 - - /** - * The maximum value that can be represented as an unsigned 32-bit integer. - */ - static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1 } diff --git a/api/net/sf/briar/api/transport/batch/BatchTransportPlugin.java b/api/net/sf/briar/api/transport/batch/BatchTransportPlugin.java index db2ff72c0bd8167790d7be9ac95386273cbafa91..bd810f8e61110414a38bc406a0aba4f1a06f7d24 100644 --- a/api/net/sf/briar/api/transport/batch/BatchTransportPlugin.java +++ b/api/net/sf/briar/api/transport/batch/BatchTransportPlugin.java @@ -22,8 +22,8 @@ public interface BatchTransportPlugin { * Starts the plugin. Any connections that are later initiated by contacts * or established through polling will be passed to the given callback. */ - void start(Map<String, String> localTransports, - Map<ContactId, Map<String, String>> remoteTransports, + void start(Map<String, String> localProperties, + Map<ContactId, Map<String, String>> remoteProperties, Map<String, String> config, BatchTransportCallback c) throws InvalidTransportException, InvalidConfigException; @@ -34,11 +34,11 @@ public interface BatchTransportPlugin { void stop(); /** Updates the plugin's local transport properties. */ - void setLocalTransports(Map<String, String> transports) + void setLocalProperties(Map<String, String> properties) throws InvalidTransportException; /** Updates the plugin's transport properties for the given contact. */ - void setRemoteTransports(ContactId c, Map<String, String> transports) + void setRemoteProperties(ContactId c, Map<String, String> properties) throws InvalidTransportException; /** Updates the plugin's configuration properties. */ diff --git a/api/net/sf/briar/api/transport/stream/StreamTransportPlugin.java b/api/net/sf/briar/api/transport/stream/StreamTransportPlugin.java index 67e04a204dff6d31a7619d3e3d285acac79ac930..30c35047820f4cfc25d66ad2203907838cce967d 100644 --- a/api/net/sf/briar/api/transport/stream/StreamTransportPlugin.java +++ b/api/net/sf/briar/api/transport/stream/StreamTransportPlugin.java @@ -22,8 +22,8 @@ public interface StreamTransportPlugin { * Starts the plugin. Any connections that are later initiated by contacts * or established through polling will be passed to the given callback. */ - void start(Map<String, String> localTransports, - Map<ContactId, Map<String, String>> remoteTransports, + void start(Map<String, String> localProperties, + Map<ContactId, Map<String, String>> remoteProperties, Map<String, String> config, StreamTransportCallback c) throws InvalidTransportException, InvalidConfigException; @@ -34,11 +34,11 @@ public interface StreamTransportPlugin { void stop(); /** Updates the plugin's local transport properties. */ - void setLocalTransports(Map<String, String> transports) + void setLocalProperties(Map<String, String> properties) throws InvalidTransportException; /** Updates the plugin's transport properties for the given contact. */ - void setRemoteTransports(ContactId c, Map<String, String> transports) + void setRemoteProperties(ContactId c, Map<String, String> properties) throws InvalidTransportException; /** Updates the plugin's configuration properties. */ diff --git a/components/net/sf/briar/crypto/CryptoComponentImpl.java b/components/net/sf/briar/crypto/CryptoComponentImpl.java index 366c37fd54949a7f8220f5a2545030f9c9c4e682..f8816a1859a95bf1f4ffc977fa44b2ea9992d176 100644 --- a/components/net/sf/briar/crypto/CryptoComponentImpl.java +++ b/components/net/sf/briar/crypto/CryptoComponentImpl.java @@ -114,12 +114,12 @@ class CryptoComponentImpl implements CryptoComponent { } } - public SecretKey deriveIncomingPacketKey(byte[] secret) { + public SecretKey deriveIncomingFrameKey(byte[] secret) { SharedSecret s = new SharedSecret(secret); - return derivePacketKey(s, !s.getAlice()); + return deriveFrameKey(s, !s.getAlice()); } - private SecretKey derivePacketKey(SharedSecret s, boolean alice) { + private SecretKey deriveFrameKey(SharedSecret s, boolean alice) { if(alice) return deriveKey("PKTA", s.getIv(), s.getCiphertext()); else return deriveKey("PKTB", s.getIv(), s.getCiphertext()); } @@ -139,9 +139,9 @@ class CryptoComponentImpl implements CryptoComponent { return deriveMacKey(s, s.getAlice()); } - public SecretKey deriveOutgoingPacketKey(byte[] secret) { + public SecretKey deriveOutgoingFrameKey(byte[] secret) { SharedSecret s = new SharedSecret(secret); - return derivePacketKey(s, s.getAlice()); + return deriveFrameKey(s, s.getAlice()); } public SecretKey deriveOutgoingTagKey(byte[] secret) { @@ -181,7 +181,7 @@ class CryptoComponentImpl implements CryptoComponent { } } - public Cipher getPacketCipher() { + public Cipher getFrameCipher() { try { return Cipher.getInstance(PACKET_CIPHER_ALGO, PROVIDER); } catch(NoSuchAlgorithmException e) { diff --git a/components/net/sf/briar/protocol/BatchIdReader.java b/components/net/sf/briar/protocol/BatchIdReader.java index 4542a0f5ecf4d7d033f235119e53df9f05d840c9..21807eb475c6a12ce34af6619aa60f7093a39e0d 100644 --- a/components/net/sf/briar/protocol/BatchIdReader.java +++ b/components/net/sf/briar/protocol/BatchIdReader.java @@ -2,10 +2,10 @@ package net.sf.briar.protocol; import java.io.IOException; +import net.sf.briar.api.FormatException; import net.sf.briar.api.protocol.BatchId; import net.sf.briar.api.protocol.Tags; import net.sf.briar.api.protocol.UniqueId; -import net.sf.briar.api.serial.FormatException; import net.sf.briar.api.serial.ObjectReader; import net.sf.briar.api.serial.Reader; diff --git a/components/net/sf/briar/protocol/CountingConsumer.java b/components/net/sf/briar/protocol/CountingConsumer.java index c882ab0b2b87174781eb44e7f74d2f5d97bbd00c..bb032341e06040e2bbb7996f5d060671cf0a41d5 100644 --- a/components/net/sf/briar/protocol/CountingConsumer.java +++ b/components/net/sf/briar/protocol/CountingConsumer.java @@ -2,8 +2,8 @@ package net.sf.briar.protocol; import java.io.IOException; +import net.sf.briar.api.FormatException; import net.sf.briar.api.serial.Consumer; -import net.sf.briar.api.serial.FormatException; /** * A consumer that counts the number of bytes consumed and throws a diff --git a/components/net/sf/briar/protocol/GroupIdReader.java b/components/net/sf/briar/protocol/GroupIdReader.java index 07bf2233f138ea11768d0016c3a79eede87dfac8..bf9fd81b1eb1f43e0aff54d0b6c6e6ec9c608298 100644 --- a/components/net/sf/briar/protocol/GroupIdReader.java +++ b/components/net/sf/briar/protocol/GroupIdReader.java @@ -2,10 +2,10 @@ package net.sf.briar.protocol; import java.io.IOException; +import net.sf.briar.api.FormatException; import net.sf.briar.api.protocol.GroupId; import net.sf.briar.api.protocol.Tags; import net.sf.briar.api.protocol.UniqueId; -import net.sf.briar.api.serial.FormatException; import net.sf.briar.api.serial.ObjectReader; import net.sf.briar.api.serial.Reader; diff --git a/components/net/sf/briar/protocol/MessageEncoderImpl.java b/components/net/sf/briar/protocol/MessageEncoderImpl.java index 5c2149ad5c349172b33bd171fe38fb2382dcd0b2..8e430f1a57ff71c01eb3c25b219eb53314df9d67 100644 --- a/components/net/sf/briar/protocol/MessageEncoderImpl.java +++ b/components/net/sf/briar/protocol/MessageEncoderImpl.java @@ -58,6 +58,8 @@ class MessageEncoderImpl implements MessageEncoder { throw new IllegalArgumentException(); if((group.getPublicKey() == null) != (groupKey == null)) throw new IllegalArgumentException(); + if(body.length > Message.MAX_BODY_LENGTH) + throw new IllegalArgumentException(); long timestamp = System.currentTimeMillis(); ByteArrayOutputStream out = new ByteArrayOutputStream(); diff --git a/components/net/sf/briar/protocol/MessageIdReader.java b/components/net/sf/briar/protocol/MessageIdReader.java index eecf32649fa0f5469768bc34e814c4bce4e4a5c2..32e4e905ace77b21041917ea35d1db9a37985731 100644 --- a/components/net/sf/briar/protocol/MessageIdReader.java +++ b/components/net/sf/briar/protocol/MessageIdReader.java @@ -2,10 +2,10 @@ package net.sf.briar.protocol; import java.io.IOException; +import net.sf.briar.api.FormatException; import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.Tags; import net.sf.briar.api.protocol.UniqueId; -import net.sf.briar.api.serial.FormatException; import net.sf.briar.api.serial.ObjectReader; import net.sf.briar.api.serial.Reader; diff --git a/components/net/sf/briar/protocol/MessageReader.java b/components/net/sf/briar/protocol/MessageReader.java index e4c24078a67392612966d0d7d0530a55cf20a370..1ee31dd152282f26dad75d10b1c81f770125a22a 100644 --- a/components/net/sf/briar/protocol/MessageReader.java +++ b/components/net/sf/briar/protocol/MessageReader.java @@ -6,6 +6,7 @@ import java.security.MessageDigest; import java.security.PublicKey; import java.security.Signature; +import net.sf.briar.api.FormatException; import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.crypto.KeyParser; import net.sf.briar.api.protocol.Author; @@ -14,7 +15,6 @@ import net.sf.briar.api.protocol.Group; import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.Tags; -import net.sf.briar.api.serial.FormatException; import net.sf.briar.api.serial.ObjectReader; import net.sf.briar.api.serial.Reader; diff --git a/components/net/sf/briar/protocol/OfferIdReader.java b/components/net/sf/briar/protocol/OfferIdReader.java index 52485760cfbb327948b0a6057ed824b15f6f1054..3c1fe80aad2d418ab8aedc4ba406f19e3f6f51e4 100644 --- a/components/net/sf/briar/protocol/OfferIdReader.java +++ b/components/net/sf/briar/protocol/OfferIdReader.java @@ -2,10 +2,10 @@ package net.sf.briar.protocol; import java.io.IOException; +import net.sf.briar.api.FormatException; import net.sf.briar.api.protocol.OfferId; import net.sf.briar.api.protocol.Tags; import net.sf.briar.api.protocol.UniqueId; -import net.sf.briar.api.serial.FormatException; import net.sf.briar.api.serial.ObjectReader; import net.sf.briar.api.serial.Reader; diff --git a/components/net/sf/briar/serial/ReaderImpl.java b/components/net/sf/briar/serial/ReaderImpl.java index 63b9e4ef0c9796ae8567edd859a596e03d5dd081..ffc7af901e4a706f1ef0d593d0ea2186db7cce7e 100644 --- a/components/net/sf/briar/serial/ReaderImpl.java +++ b/components/net/sf/briar/serial/ReaderImpl.java @@ -9,8 +9,8 @@ import java.util.List; import java.util.Map; import net.sf.briar.api.Bytes; +import net.sf.briar.api.FormatException; import net.sf.briar.api.serial.Consumer; -import net.sf.briar.api.serial.FormatException; import net.sf.briar.api.serial.ObjectReader; import net.sf.briar.api.serial.Reader; import net.sf.briar.api.serial.Tag; diff --git a/components/net/sf/briar/transport/ConnectionDecrypter.java b/components/net/sf/briar/transport/ConnectionDecrypter.java new file mode 100644 index 0000000000000000000000000000000000000000..80bd798eeebe57fa3d0084634758b3ff2e277d8c --- /dev/null +++ b/components/net/sf/briar/transport/ConnectionDecrypter.java @@ -0,0 +1,14 @@ +package net.sf.briar.transport; + +import java.io.IOException; +import java.io.InputStream; + +/** Decrypts unauthenticated data received over a connection. */ +interface ConnectionDecrypter { + + /** Returns an input stream from which decrypted data can be read. */ + InputStream getInputStream(); + + /** Reads and decrypts the MAC for the current frame. */ + void readMac(byte[] mac) throws IOException; +} diff --git a/components/net/sf/briar/transport/ConnectionDecrypterImpl.java b/components/net/sf/briar/transport/ConnectionDecrypterImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..4ab3b618d76caf95b496475cad15fc084d119025 --- /dev/null +++ b/components/net/sf/briar/transport/ConnectionDecrypterImpl.java @@ -0,0 +1,148 @@ +package net.sf.briar.transport; + +import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; +import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED; + +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +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.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; + +class ConnectionDecrypterImpl extends FilterInputStream +implements ConnectionDecrypter { + + private final int transportId; + private final long connection; + private final Cipher frameCipher; + private final SecretKey frameKey; + private final byte[] buf, tag; + + private int bufOff = 0, bufLen = 0; + private long frame = 0L; + private boolean betweenFrames = true; + + ConnectionDecrypterImpl(InputStream in, int transportId, long connection, + Cipher frameCipher, SecretKey frameKey) { + super(in); + this.transportId = transportId; + this.connection = connection; + this.frameCipher = frameCipher; + this.frameKey = frameKey; + buf = new byte[TAG_LENGTH]; + tag = new byte[TAG_LENGTH]; + } + + public InputStream getInputStream() { + return this; + } + + public void readMac(byte[] mac) throws IOException { + if(betweenFrames) throw new IllegalStateException(); + // If we have any plaintext in the buffer, copy it into the MAC + System.arraycopy(buf, bufOff, mac, 0, bufLen); + // Read the remainder of the MAC + int offset = bufLen; + while(offset < mac.length) { + int read = in.read(mac, offset, mac.length - offset); + if(read == -1) break; + offset += read; + } + if(offset < mac.length) throw new EOFException(); // Unexpected EOF + // Decrypt the remainder of the MAC + try { + int length = mac.length - bufLen; + int i = frameCipher.doFinal(mac, bufLen, length, mac, bufLen); + if(i < length) throw new RuntimeException(); + } catch(BadPaddingException badCipher) { + throw new RuntimeException(badCipher); + } catch(IllegalBlockSizeException badCipher) { + throw new RuntimeException(badCipher); + } catch(ShortBufferException badCipher) { + throw new RuntimeException(badCipher); + } + bufOff = bufLen = 0; + betweenFrames = true; + } + + @Override + public int read() throws IOException { + if(betweenFrames) initialiseCipher(); + if(bufLen == 0) { + if(!readBlock()) return -1; + bufOff = 0; + bufLen = buf.length; + } + int i = buf[bufOff]; + bufOff++; + bufLen--; + return i < 0 ? i + 256 : i; + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if(betweenFrames) initialiseCipher(); + if(bufLen == 0) { + if(!readBlock()) return -1; + bufOff = 0; + bufLen = buf.length; + } + int length = Math.min(len, bufLen); + System.arraycopy(buf, bufOff, b, off, length); + bufOff += length; + bufLen -= length; + return length; + } + + // Although we're using CTR mode, which doesn't require full blocks of + // ciphertext, the cipher still tries to operate a block at a time + private boolean readBlock() throws IOException { + // Try to read a block of ciphertext + int offset = 0; + while(offset < buf.length) { + int read = in.read(buf, offset, buf.length - offset); + if(read == -1) break; + offset += read; + } + if(offset == 0) return false; + if(offset < buf.length) throw new EOFException(); // Unexpected EOF + // Decrypt the block + try { + int i = frameCipher.update(buf, 0, offset, buf); + if(i < offset) throw new RuntimeException(); + } catch(ShortBufferException badCipher) { + throw new RuntimeException(badCipher); + } + return true; + } + + private void initialiseCipher() { + assert betweenFrames; + if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException(); + TagEncoder.encodeTag(tag, transportId, connection, frame); + // Use the plaintext tag to initialise the packet cipher + IvParameterSpec iv = new IvParameterSpec(tag); + try { + frameCipher.init(Cipher.DECRYPT_MODE, frameKey, iv); + } catch(InvalidAlgorithmParameterException badIv) { + throw new RuntimeException(badIv); + } catch(InvalidKeyException badKey) { + throw new RuntimeException(badKey); + } + frame++; + betweenFrames = false; + } +} diff --git a/components/net/sf/briar/transport/ConnectionEncrypter.java b/components/net/sf/briar/transport/ConnectionEncrypter.java new file mode 100644 index 0000000000000000000000000000000000000000..595307547b9a4c8be74e3a81d8b508829fba5dd2 --- /dev/null +++ b/components/net/sf/briar/transport/ConnectionEncrypter.java @@ -0,0 +1,14 @@ +package net.sf.briar.transport; + +import java.io.IOException; +import java.io.OutputStream; + +/** Encrypts authenticated data to be sent over a connection. */ +interface ConnectionEncrypter { + + /** Returns an output stream to which unencrypted data can be written. */ + OutputStream getOutputStream(); + + /** Encrypts and writes the MAC for the current frame. */ + void writeMac(byte[] mac) throws IOException; +} diff --git a/components/net/sf/briar/transport/ConnectionEncrypterImpl.java b/components/net/sf/briar/transport/ConnectionEncrypterImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..0fccc905f9cf23abd14f8c980078a31cd4d5eef8 --- /dev/null +++ b/components/net/sf/briar/transport/ConnectionEncrypterImpl.java @@ -0,0 +1,117 @@ +package net.sf.briar.transport; + +import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; +import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED; + +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 ConnectionEncrypterImpl extends FilterOutputStream +implements ConnectionEncrypter { + + private final int transportId; + private final long connection; + private final Cipher tagCipher, frameCipher; + private final SecretKey frameKey; + private final byte[] tag; + + private long frame = 0L; + private boolean started = false, betweenFrames = false; + + ConnectionEncrypterImpl(OutputStream out, int transportId, + long connection, Cipher tagCipher, Cipher frameCipher, + SecretKey tagKey, SecretKey frameKey) { + super(out); + this.transportId = transportId; + this.connection = connection; + this.tagCipher = tagCipher; + this.frameCipher = frameCipher; + this.frameKey = frameKey; + tag = new byte[TAG_LENGTH]; + try { + tagCipher.init(Cipher.ENCRYPT_MODE, tagKey); + } catch(InvalidKeyException badKey) { + throw new IllegalArgumentException(badKey); + } + if(tagCipher.getOutputSize(TAG_LENGTH) != TAG_LENGTH) + throw new IllegalArgumentException(); + } + + public OutputStream getOutputStream() { + return this; + } + + public void writeMac(byte[] mac) throws IOException { + if(!started || betweenFrames) throw new IllegalStateException(); + try { + out.write(frameCipher.doFinal(mac)); + } catch(BadPaddingException badCipher) { + throw new IOException(badCipher); + } catch(IllegalBlockSizeException badCipher) { + throw new RuntimeException(badCipher); + } + betweenFrames = true; + } + + @Override + public void write(int b) throws IOException { + if(!started) writeTag(); + if(betweenFrames) initialiseCipher(); + byte[] ciphertext = frameCipher.update(new byte[] {(byte) b}); + if(ciphertext != null) out.write(ciphertext); + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if(!started) writeTag(); + if(betweenFrames) initialiseCipher(); + byte[] ciphertext = frameCipher.update(b, off, len); + if(ciphertext != null) out.write(ciphertext); + } + + private void writeTag() throws IOException { + assert !started; + assert !betweenFrames; + TagEncoder.encodeTag(tag, transportId, connection, 0L); + try { + out.write(tagCipher.doFinal(tag)); + } catch(BadPaddingException badCipher) { + throw new IOException(badCipher); + } catch(IllegalBlockSizeException badCipher) { + throw new RuntimeException(badCipher); + } + started = true; + betweenFrames = true; + } + + private void initialiseCipher() { + assert started; + assert betweenFrames; + if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException(); + TagEncoder.encodeTag(tag, transportId, connection, frame); + IvParameterSpec iv = new IvParameterSpec(tag); + try { + frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, iv); + } catch(InvalidAlgorithmParameterException badIv) { + throw new RuntimeException(badIv); + } catch(InvalidKeyException badKey) { + throw new RuntimeException(badKey); + } + frame++; + betweenFrames = false; + } +} \ No newline at end of file diff --git a/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java b/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..016e4fb43b15091f2333664610d66cf67ca736d0 --- /dev/null +++ b/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java @@ -0,0 +1,40 @@ +package net.sf.briar.transport; + +import java.io.InputStream; +import java.security.InvalidKeyException; + +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.SecretKey; + +import net.sf.briar.api.crypto.CryptoComponent; +import net.sf.briar.api.transport.ConnectionReader; +import net.sf.briar.api.transport.ConnectionReaderFactory; + +import com.google.inject.Inject; + +class ConnectionReaderFactoryImpl implements ConnectionReaderFactory { + + private final CryptoComponent crypto; + + @Inject + ConnectionReaderFactoryImpl(CryptoComponent crypto) { + this.crypto = crypto; + } + + public ConnectionReader createConnectionReader(InputStream in, + int transportId, long connection, byte[] secret) { + SecretKey macKey = crypto.deriveIncomingMacKey(secret); + SecretKey frameKey = crypto.deriveIncomingFrameKey(secret); + Cipher frameCipher = crypto.getFrameCipher(); + Mac mac = crypto.getMac(); + try { + mac.init(macKey); + } catch(InvalidKeyException e) { + throw new IllegalArgumentException(e); + } + ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in, + transportId, connection, frameCipher, frameKey); + return new ConnectionReaderImpl(decrypter, mac); + } +} diff --git a/components/net/sf/briar/transport/ConnectionReaderImpl.java b/components/net/sf/briar/transport/ConnectionReaderImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..f3d54a9a15a680d85e46bf3fbef4ab2b316a3027 --- /dev/null +++ b/components/net/sf/briar/transport/ConnectionReaderImpl.java @@ -0,0 +1,107 @@ +package net.sf.briar.transport; + +import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH; +import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED; + +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import javax.crypto.Mac; + +import net.sf.briar.api.FormatException; +import net.sf.briar.api.transport.ConnectionReader; +import net.sf.briar.util.ByteUtils; + +class ConnectionReaderImpl extends FilterInputStream +implements ConnectionReader { + + private final ConnectionDecrypter decrypter; + private final Mac mac; + private final int maxPayloadLength; + private final byte[] header, payload, footer; + + private long frame = 0L; + private int payloadOff = 0, payloadLen = 0; + private boolean betweenFrames = true; + + ConnectionReaderImpl(ConnectionDecrypter decrypter, Mac mac) { + super(decrypter.getInputStream()); + this.decrypter = decrypter; + this.mac = mac; + maxPayloadLength = MAX_FRAME_LENGTH - 6 - mac.getMacLength(); + header = new byte[6]; + payload = new byte[maxPayloadLength]; + footer = new byte[mac.getMacLength()]; + } + + public InputStream getInputStream() { + return this; + } + + @Override + public int read() throws IOException { + if(betweenFrames && !readFrame()) return -1; + int i = payload[payloadOff]; + payloadOff++; + payloadLen--; + if(payloadLen == 0) betweenFrames = true; + return i; + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if(betweenFrames && !readFrame()) return -1; + len = Math.min(len, payloadLen); + System.arraycopy(payload, payloadOff, b, off, len); + payloadOff += len; + payloadLen -= len; + if(payloadLen == 0) betweenFrames = true; + return len; + } + + private boolean readFrame() throws IOException { + assert betweenFrames; + // Read the header + if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException(); + int offset = 0; + while(offset < header.length) { + int read = in.read(header, offset, header.length - offset); + if(read == -1) break; + offset += read; + } + if(offset == 0) return false; // EOF between frames + if(offset < header.length) throw new EOFException(); // Unexpected EOF + mac.update(header); + // Check that the frame has the expected frame number + if(ByteUtils.readUint32(header, 0) != frame) + throw new FormatException(); + // Check that the payload length is legal + payloadLen = ByteUtils.readUint16(header, 4); + if(payloadLen == 0 || payloadLen > maxPayloadLength) + throw new FormatException(); + frame++; + // Read the payload + offset = 0; + while(offset < payloadLen) { + int read = in.read(payload, offset, payloadLen - offset); + if(read == -1) throw new EOFException(); // Unexpected EOF + mac.update(payload, offset, read); + offset += read; + } + payloadOff = 0; + // Read the MAC + byte[] expectedMac = mac.doFinal(); + decrypter.readMac(footer); + if(!Arrays.equals(expectedMac, footer)) throw new FormatException(); + betweenFrames = false; + return true; + } +} diff --git a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java index f9f0f9a63654a101254d8ec7c78d9ec1aeeb99e0..5f662503c0b5c4f55360cc1828c56b24b075f35d 100644 --- a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java +++ b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java @@ -73,6 +73,7 @@ DatabaseListener { contactToTags.put(c, tags); contactToWindow.put(c, w); } catch(NoSuchContactException e) { + // The contact was removed after the call to getContacts() continue; } } @@ -80,15 +81,15 @@ DatabaseListener { } private synchronized byte[] calculateTag(ContactId c, long connection) { - byte[] tag = TagEncoder.encodeTag(transportId, connection, 0L); + byte[] tag = TagEncoder.encodeTag(transportId, connection); 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); + } catch(BadPaddingException badCipher) { + throw new RuntimeException(badCipher); + } catch(IllegalBlockSizeException badCipher) { + throw new RuntimeException(badCipher); } } diff --git a/components/net/sf/briar/transport/ConnectionWindowImpl.java b/components/net/sf/briar/transport/ConnectionWindowImpl.java index 5ce228e7d3faaac267e8b64c4681b7d78222b258..f0485cb1acafd2cdc0581c315f6f276d509fff00 100644 --- a/components/net/sf/briar/transport/ConnectionWindowImpl.java +++ b/components/net/sf/briar/transport/ConnectionWindowImpl.java @@ -1,6 +1,6 @@ package net.sf.briar.transport; -import static net.sf.briar.api.transport.TransportConstants.MAX_32_BIT_UNSIGNED; +import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED; import java.util.ArrayList; import java.util.Collection; diff --git a/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java b/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..ae12ef4b1c4384ce4fb3fd6440f8a4e2dc3cc835 --- /dev/null +++ b/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java @@ -0,0 +1,43 @@ +package net.sf.briar.transport; + +import java.io.OutputStream; +import java.security.InvalidKeyException; + +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.SecretKey; + +import net.sf.briar.api.crypto.CryptoComponent; +import net.sf.briar.api.transport.ConnectionWriter; +import net.sf.briar.api.transport.ConnectionWriterFactory; + +import com.google.inject.Inject; + +class ConnectionWriterFactoryImpl implements ConnectionWriterFactory { + + private final CryptoComponent crypto; + + @Inject + public ConnectionWriterFactoryImpl(CryptoComponent crypto) { + this.crypto = crypto; + } + + public ConnectionWriter createConnectionWriter(OutputStream out, + int transportId, long connection, byte[] secret) { + SecretKey macKey = crypto.deriveOutgoingMacKey(secret); + SecretKey tagKey = crypto.deriveOutgoingTagKey(secret); + SecretKey frameKey = crypto.deriveOutgoingFrameKey(secret); + Cipher tagCipher = crypto.getTagCipher(); + Cipher frameCipher = crypto.getFrameCipher(); + Mac mac = crypto.getMac(); + try { + mac.init(macKey); + } catch(InvalidKeyException badKey) { + throw new IllegalArgumentException(badKey); + } + ConnectionEncrypter encrypter = new ConnectionEncrypterImpl(out, + transportId, connection, tagCipher, frameCipher, tagKey, + frameKey); + return new ConnectionWriterImpl(encrypter, mac); + } +} diff --git a/components/net/sf/briar/transport/ConnectionWriterImpl.java b/components/net/sf/briar/transport/ConnectionWriterImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..882176a11d7f488e9caef83283eaeffb487f9463 --- /dev/null +++ b/components/net/sf/briar/transport/ConnectionWriterImpl.java @@ -0,0 +1,84 @@ +package net.sf.briar.transport; + +import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH; +import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED; + +import java.io.ByteArrayOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import javax.crypto.Mac; + +import net.sf.briar.api.transport.ConnectionWriter; +import net.sf.briar.util.ByteUtils; + +class ConnectionWriterImpl extends FilterOutputStream +implements ConnectionWriter { + + private final ConnectionEncrypter encrypter; + private final Mac mac; + private final int maxPayloadLength; + private final ByteArrayOutputStream buf; + private final byte[] header; + + private long frame = 0L; + + ConnectionWriterImpl(ConnectionEncrypter encrypter, Mac mac) { + super(encrypter.getOutputStream()); + this.encrypter = encrypter; + this.mac = mac; + maxPayloadLength = MAX_FRAME_LENGTH - 6 - mac.getMacLength(); + buf = new ByteArrayOutputStream(maxPayloadLength); + header = new byte[6]; + } + + public OutputStream getOutputStream() { + return this; + } + + @Override + public void flush() throws IOException { + if(buf.size() > 0) writeFrame(); + out.flush(); + } + + @Override + public void write(int b) throws IOException { + if(buf.size() == maxPayloadLength) writeFrame(); + buf.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + int available = maxPayloadLength - buf.size(); + while(available < len) { + buf.write(b, off, available); + writeFrame(); + off += available; + len -= available; + available = maxPayloadLength; + } + buf.write(b, off, len); + } + + private void writeFrame() throws IOException { + if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException(); + byte[] payload = buf.toByteArray(); + if(payload.length > maxPayloadLength) throw new IllegalStateException(); + ByteUtils.writeUint32(frame, header, 0); + ByteUtils.writeUint16(payload.length, header, 4); + out.write(header); + mac.update(header); + out.write(payload); + mac.update(payload); + encrypter.writeMac(mac.doFinal()); + frame++; + buf.reset(); + } +} diff --git a/components/net/sf/briar/transport/PacketDecrypter.java b/components/net/sf/briar/transport/PacketDecrypter.java deleted file mode 100644 index 239426021613a7aa842bf46876642d05f9236f8b..0000000000000000000000000000000000000000 --- a/components/net/sf/briar/transport/PacketDecrypter.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.sf.briar.transport; - -import java.io.IOException; -import java.io.InputStream; - -interface PacketDecrypter { - - /** Returns the input stream from which packets should be read. */ - InputStream getInputStream(); - - /** - * Reads, decrypts and returns a tag from the underlying input stream. - * Returns null if the end of the input stream is reached before any bytes - * are read. - */ - byte[] readTag() throws IOException; -} diff --git a/components/net/sf/briar/transport/PacketDecrypterImpl.java b/components/net/sf/briar/transport/PacketDecrypterImpl.java deleted file mode 100644 index b1ed14790443856e867258a073334082dc6daec8..0000000000000000000000000000000000000000 --- a/components/net/sf/briar/transport/PacketDecrypterImpl.java +++ /dev/null @@ -1,166 +0,0 @@ -package net.sf.briar.transport; - -import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; - -import java.io.EOFException; -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.util.Arrays; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.SecretKey; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.IvParameterSpec; - -class PacketDecrypterImpl extends FilterInputStream implements PacketDecrypter { - - private final Cipher tagCipher, packetCipher; - private final SecretKey packetKey; - - private byte[] cipherBuf, plainBuf; - private int bufOff = 0, bufLen = TAG_LENGTH; - private boolean betweenPackets = true; - - PacketDecrypterImpl(byte[] firstTag, InputStream in, Cipher tagCipher, - Cipher packetCipher, SecretKey tagKey, SecretKey packetKey) { - super(in); - if(firstTag.length != TAG_LENGTH) - throw new IllegalArgumentException(); - cipherBuf = Arrays.copyOf(firstTag, firstTag.length); - plainBuf = new byte[TAG_LENGTH]; - this.tagCipher = tagCipher; - this.packetCipher = packetCipher; - this.packetKey = packetKey; - try { - tagCipher.init(Cipher.DECRYPT_MODE, tagKey); - } catch(InvalidKeyException e) { - throw new IllegalArgumentException(e); - } - if(tagCipher.getOutputSize(TAG_LENGTH) != TAG_LENGTH) - throw new IllegalArgumentException(); - } - - public InputStream getInputStream() { - return this; - } - - public byte[] readTag() throws IOException { - byte[] tag = new byte[TAG_LENGTH]; - System.arraycopy(cipherBuf, bufOff, tag, 0, bufLen); - int offset = bufLen; - bufOff = bufLen = 0; - while(offset < tag.length) { - int read = in.read(tag, offset, tag.length - offset); - if(read == -1) break; - offset += read; - } - if(offset == 0) return null; // EOF between packets is acceptable - if(offset < tag.length) throw new EOFException(); - betweenPackets = false; - try { - byte[] decryptedTag = tagCipher.doFinal(tag); - IvParameterSpec iv = new IvParameterSpec(decryptedTag); - packetCipher.init(Cipher.DECRYPT_MODE, packetKey, iv); - return decryptedTag; - } catch(BadPaddingException badCipher) { - throw new RuntimeException(badCipher); - } catch(IllegalBlockSizeException badCipher) { - throw new RuntimeException(badCipher); - } catch(InvalidAlgorithmParameterException badIv) { - throw new RuntimeException(badIv); - } catch(InvalidKeyException badKey) { - throw new RuntimeException(badKey); - } - } - - @Override - public int read() throws IOException { - if(betweenPackets) throw new IllegalStateException(); - if(bufLen == 0) { - int read = readBlock(); - if(read == 0) return -1; - bufOff = 0; - bufLen = read; - } - int i = plainBuf[bufOff]; - bufOff++; - bufLen--; - return i < 0 ? i + 256 : i; - } - - // Although we're using CTR mode, which doesn't require full blocks of - // ciphertext, the cipher still tries to operate a block at a time. We must - // either call update() with a full block or doFinal() with the last - // (possibly partial) block. - private int readBlock() throws IOException { - // Try to read a block of ciphertext - int off = 0; - while(off < cipherBuf.length) { - int read = in.read(cipherBuf, off, cipherBuf.length - off); - if(read == -1) break; - off += read; - } - if(off == 0) return 0; - // Did we get a whole block? If not we must be at EOF - if(off < cipherBuf.length) { - // We're at EOF so we can call doFinal() to force decryption - try { - int i = packetCipher.doFinal(cipherBuf, 0, off, plainBuf); - if(i < off) throw new RuntimeException(); - betweenPackets = true; - } catch(BadPaddingException badCipher) { - throw new RuntimeException(badCipher); - } catch(IllegalBlockSizeException badCipher) { - throw new RuntimeException(badCipher); - } catch(ShortBufferException badCipher) { - throw new RuntimeException(badCipher); - } - } else { - // We're not at EOF but we have a whole block to decrypt - try { - int i = packetCipher.update(cipherBuf, 0, off, plainBuf); - if(i < off) throw new RuntimeException(); - } catch(ShortBufferException badCipher) { - throw new RuntimeException(badCipher); - } - } - return off; - } - - @Override - public int read(byte[] b) throws IOException { - if(betweenPackets) throw new IllegalStateException(); - if(bufLen == 0) { - int read = readBlock(); - if(read == 0) return -1; - bufOff = 0; - bufLen = read; - } - int length = Math.min(b.length, bufLen); - System.arraycopy(plainBuf, bufOff, b, 0, length); - bufOff += length; - bufLen -= length; - return length; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - if(betweenPackets) throw new IllegalStateException(); - if(bufLen == 0) { - int read = readBlock(); - if(read == 0) return -1; - bufOff = 0; - bufLen = read; - } - int length = Math.min(len, bufLen); - System.arraycopy(plainBuf, bufOff, b, off, length); - bufOff += length; - bufLen -= length; - return length; - } -} diff --git a/components/net/sf/briar/transport/PacketEncrypter.java b/components/net/sf/briar/transport/PacketEncrypter.java deleted file mode 100644 index 7652753b2611fbe74e58ba66ac96fbb95b3085af..0000000000000000000000000000000000000000 --- a/components/net/sf/briar/transport/PacketEncrypter.java +++ /dev/null @@ -1,16 +0,0 @@ -package net.sf.briar.transport; - -import java.io.IOException; -import java.io.OutputStream; - -interface PacketEncrypter { - - /** Returns the output stream to which packets should be written. */ - OutputStream getOutputStream(); - - /** Encrypts the given tag and writes it to the underlying output stream. */ - void writeTag(byte[] tag) throws IOException; - - /** Finishes writing the current packet. */ - void finishPacket() throws IOException; -} diff --git a/components/net/sf/briar/transport/PacketEncrypterImpl.java b/components/net/sf/briar/transport/PacketEncrypterImpl.java deleted file mode 100644 index bd9b5e5a766165e6330e4eb45fd476de7486ae99..0000000000000000000000000000000000000000 --- a/components/net/sf/briar/transport/PacketEncrypterImpl.java +++ /dev/null @@ -1,86 +0,0 @@ -package net.sf.briar.transport; - -import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; - -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 Cipher tagCipher, packetCipher; - private final SecretKey packetKey; - - PacketEncrypterImpl(OutputStream out, Cipher tagCipher, - Cipher packetCipher, SecretKey tagKey, SecretKey packetKey) { - super(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(TAG_LENGTH) != TAG_LENGTH) - throw new IllegalArgumentException(); - } - - public OutputStream getOutputStream() { - return this; - } - - public void writeTag(byte[] tag) throws IOException { - if(tag.length != TAG_LENGTH) 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); - } -} diff --git a/components/net/sf/briar/transport/PacketReaderFactoryImpl.java b/components/net/sf/briar/transport/PacketReaderFactoryImpl.java deleted file mode 100644 index 3d8ccbdafe380b8ec5de4ae6163abcb048a4bc36..0000000000000000000000000000000000000000 --- a/components/net/sf/briar/transport/PacketReaderFactoryImpl.java +++ /dev/null @@ -1,42 +0,0 @@ -package net.sf.briar.transport; - -import java.io.InputStream; -import java.security.InvalidKeyException; - -import javax.crypto.Cipher; -import javax.crypto.Mac; -import javax.crypto.SecretKey; - -import net.sf.briar.api.crypto.CryptoComponent; -import net.sf.briar.api.transport.PacketReader; -import net.sf.briar.api.transport.PacketReaderFactory; - -import com.google.inject.Inject; - -class PacketReaderFactoryImpl implements PacketReaderFactory { - - private final CryptoComponent crypto; - - @Inject - PacketReaderFactoryImpl(CryptoComponent crypto) { - this.crypto = crypto; - } - - public PacketReader createPacketReader(byte[] firstTag, InputStream in, - int transportId, long connection, byte[] secret) { - SecretKey macKey = crypto.deriveIncomingMacKey(secret); - SecretKey tagKey = crypto.deriveIncomingTagKey(secret); - SecretKey packetKey = crypto.deriveIncomingPacketKey(secret); - Cipher tagCipher = crypto.getTagCipher(); - Cipher packetCipher = crypto.getPacketCipher(); - Mac mac = crypto.getMac(); - try { - mac.init(macKey); - } catch(InvalidKeyException e) { - throw new IllegalArgumentException(e); - } - PacketDecrypter decrypter = new PacketDecrypterImpl(firstTag, in, - tagCipher, packetCipher, tagKey, packetKey); - return new PacketReaderImpl(decrypter, mac, transportId, connection); - } -} diff --git a/components/net/sf/briar/transport/PacketReaderImpl.java b/components/net/sf/briar/transport/PacketReaderImpl.java deleted file mode 100644 index 2db8bafefe53750316b113e56d058971a72eab8c..0000000000000000000000000000000000000000 --- a/components/net/sf/briar/transport/PacketReaderImpl.java +++ /dev/null @@ -1,92 +0,0 @@ -package net.sf.briar.transport; - -import static net.sf.briar.api.transport.TransportConstants.MAX_32_BIT_UNSIGNED; - -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; -import java.util.Arrays; - -import javax.crypto.Mac; - -import net.sf.briar.api.serial.FormatException; -import net.sf.briar.api.transport.PacketReader; - -class PacketReaderImpl extends FilterInputStream implements PacketReader { - - private final PacketDecrypter decrypter; - private final Mac mac; - private final int macLength, transportId; - private final long connection; - - private long packet = 0L; - private boolean betweenPackets = true; - - PacketReaderImpl(PacketDecrypter decrypter, Mac mac, int transportId, - long connection) { - super(decrypter.getInputStream()); - this.decrypter = decrypter; - this.mac = mac; - macLength = mac.getMacLength(); - this.transportId = transportId; - this.connection = connection; - } - - public InputStream getInputStream() { - return this; - } - - public void finishPacket() throws IOException, GeneralSecurityException { - if(!betweenPackets) readMac(); - } - - @Override - public int read() throws IOException { - if(betweenPackets) readTag(); - int i = in.read(); - if(i != -1) mac.update((byte) i); - return i; - } - - @Override - public int read(byte[] b) throws IOException { - return read(b, 0, b.length); - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - if(betweenPackets) readTag(); - int i = in.read(b, off, len); - if(i != -1) mac.update(b, off, i); - return i; - } - - private void readMac() throws IOException, GeneralSecurityException { - byte[] expectedMac = mac.doFinal(); - byte[] actualMac = new byte[macLength]; - InputStream in = decrypter.getInputStream(); - int offset = 0; - while(offset < macLength) { - int read = in.read(actualMac, offset, actualMac.length - offset); - if(read == -1) break; - offset += read; - } - if(offset < macLength) throw new GeneralSecurityException(); - if(!Arrays.equals(expectedMac, actualMac)) - throw new GeneralSecurityException(); - betweenPackets = true; - } - - private void readTag() throws IOException { - assert betweenPackets; - if(packet > MAX_32_BIT_UNSIGNED) throw new IllegalStateException(); - byte[] tag = decrypter.readTag(); - if(tag == null) return; // EOF - if(!TagDecoder.decodeTag(tag, transportId, connection, packet)) - throw new FormatException(); - mac.update(tag); - packet++; - betweenPackets = false; - } -} diff --git a/components/net/sf/briar/transport/PacketWriterFactoryImpl.java b/components/net/sf/briar/transport/PacketWriterFactoryImpl.java deleted file mode 100644 index a59d201c4c27ae2ffc88b4101872fb38eed11c2a..0000000000000000000000000000000000000000 --- a/components/net/sf/briar/transport/PacketWriterFactoryImpl.java +++ /dev/null @@ -1,42 +0,0 @@ -package net.sf.briar.transport; - -import java.io.OutputStream; -import java.security.InvalidKeyException; - -import javax.crypto.Cipher; -import javax.crypto.Mac; -import javax.crypto.SecretKey; - -import net.sf.briar.api.crypto.CryptoComponent; -import net.sf.briar.api.transport.PacketWriter; -import net.sf.briar.api.transport.PacketWriterFactory; - -import com.google.inject.Inject; - -class PacketWriterFactoryImpl implements PacketWriterFactory { - - private final CryptoComponent crypto; - - @Inject - public PacketWriterFactoryImpl(CryptoComponent crypto) { - this.crypto = crypto; - } - - public PacketWriter createPacketWriter(OutputStream out, int transportId, - long connection, byte[] secret) { - SecretKey macKey = crypto.deriveOutgoingMacKey(secret); - SecretKey tagKey = crypto.deriveOutgoingTagKey(secret); - SecretKey packetKey = crypto.deriveOutgoingPacketKey(secret); - Cipher tagCipher = crypto.getTagCipher(); - Cipher packetCipher = crypto.getPacketCipher(); - Mac mac = crypto.getMac(); - try { - mac.init(macKey); - } catch(InvalidKeyException e) { - throw new IllegalArgumentException(e); - } - PacketEncrypter encrypter = new PacketEncrypterImpl(out, tagCipher, - packetCipher, tagKey, packetKey); - return new PacketWriterImpl(encrypter, mac, transportId, connection); - } -} diff --git a/components/net/sf/briar/transport/PacketWriterImpl.java b/components/net/sf/briar/transport/PacketWriterImpl.java deleted file mode 100644 index 7ca9f9289d223c8a6c94c1704d4fdddfffc87058..0000000000000000000000000000000000000000 --- a/components/net/sf/briar/transport/PacketWriterImpl.java +++ /dev/null @@ -1,83 +0,0 @@ -package net.sf.briar.transport; - -import static net.sf.briar.api.transport.TransportConstants.MAX_16_BIT_UNSIGNED; -import static net.sf.briar.api.transport.TransportConstants.MAX_32_BIT_UNSIGNED; - -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 final PacketEncrypter encrypter; - private final Mac mac; - private final int transportId; - private final long connection; - - private long packet = 0L; - private boolean betweenPackets = true; - - PacketWriterImpl(PacketEncrypter encrypter, Mac mac, int transportId, - long connection) { - super(encrypter.getOutputStream()); - this.encrypter = encrypter; - this.mac = mac; - if(transportId < 0) throw new IllegalArgumentException(); - if(transportId > MAX_16_BIT_UNSIGNED) - throw new IllegalArgumentException(); - this.transportId = transportId; - if(connection < 0L) throw new IllegalArgumentException(); - if(connection > MAX_32_BIT_UNSIGNED) - throw new IllegalArgumentException(); - this.connection = connection; - } - - public OutputStream getOutputStream() { - return this; - } - - public void finishPacket() 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 { - write(b, 0, b.length); - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - if(betweenPackets) writeTag(); - out.write(b, off, len); - mac.update(b, off, len); - } - - private void writeMac() throws IOException { - out.write(mac.doFinal()); - encrypter.finishPacket(); - betweenPackets = true; - } - - private void writeTag() throws IOException { - assert betweenPackets; - if(packet > MAX_32_BIT_UNSIGNED) throw new IllegalStateException(); - byte[] tag = TagEncoder.encodeTag(transportId, connection, - packet); - // Write the tag to the encrypter and start calculating the MAC - encrypter.writeTag(tag); - mac.update(tag); - packet++; - betweenPackets = false; - } -} diff --git a/components/net/sf/briar/transport/TagDecoder.java b/components/net/sf/briar/transport/TagDecoder.java index 169f1ad15175372aaff44c011454dddd54b2ba8b..a83be3d847f5b68e77c387d44b7c840c0c4727d0 100644 --- a/components/net/sf/briar/transport/TagDecoder.java +++ b/components/net/sf/briar/transport/TagDecoder.java @@ -1,35 +1,20 @@ package net.sf.briar.transport; import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; +import net.sf.briar.util.ByteUtils; class TagDecoder { - static boolean decodeTag(byte[] tag, int transportId, long connection, - long packet) { + static boolean decodeTag(byte[] tag, int transportId, long connection) { if(tag.length != TAG_LENGTH) return false; - // First 16 bits must be zero - if(readUint16(tag, 0) != 0) return false; + // First 32 bits must be zero (reserved) + for(int i = 0; i < 4; i++) if(tag[i] != 0) return false; // Transport identifier is encoded as an unsigned 16-bit integer - if(readUint16(tag, 2) != transportId) return false; + if(ByteUtils.readUint16(tag, 4) != transportId) return false; // Connection number is encoded as an unsigned 32-bit integer - if(readUint32(tag, 4) != connection) return false; - // Packet number is encoded as an unsigned 32-bit integer - if(readUint32(tag, 8) != packet) return false; - // Last 32 bits must be zero - if(readUint32(tag, 12) != 0L) return false; + if(ByteUtils.readUint32(tag, 6) != connection) return false; + // Last 48 bits must be zero (frame number and block number) + for(int i = 10; i < 16; i++) if(tag[i] != 0) return false; return true; } - - // Package access for testing - static int readUint16(byte[] b, int offset) { - assert b.length >= offset + 2; - return ((b[offset] & 0xFF) << 8) | (b[offset + 1] & 0xFF); - } - - // Package access for testing - static long readUint32(byte[] b, int offset) { - assert b.length >= offset + 4; - return ((b[offset] & 0xFFL) << 24) | ((b[offset + 1] & 0xFFL) << 16) - | ((b[offset + 2] & 0xFFL) << 8) | (b[offset + 3] & 0xFFL); - } } diff --git a/components/net/sf/briar/transport/TagEncoder.java b/components/net/sf/briar/transport/TagEncoder.java index 1660c437120e5f110943e27f809b693a9e9dbbfd..dbde2fa50538021ad218f4ff86d2b653a55ad209 100644 --- a/components/net/sf/briar/transport/TagEncoder.java +++ b/components/net/sf/briar/transport/TagEncoder.java @@ -1,40 +1,31 @@ package net.sf.briar.transport; import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; -import static net.sf.briar.api.transport.TransportConstants.MAX_16_BIT_UNSIGNED; -import static net.sf.briar.api.transport.TransportConstants.MAX_32_BIT_UNSIGNED; +import net.sf.briar.util.ByteUtils; class TagEncoder { - static byte[] encodeTag(int transportId, long connection, - long packet) { + static byte[] encodeTag(int transportId, long connection) { byte[] tag = new byte[TAG_LENGTH]; // Encode the transport identifier as an unsigned 16-bit integer - writeUint16(transportId, tag, 2); + ByteUtils.writeUint16(transportId, tag, 4); // Encode the connection number as an unsigned 32-bit integer - writeUint32(connection, tag, 4); - // Encode the packet number as an unsigned 32-bit integer - writeUint32(packet, tag, 8); + ByteUtils.writeUint32(connection, tag, 6); return tag; } - // 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); + static void encodeTag(byte[] tag, int transportId, long connection, + long frame) { + if(tag.length != TAG_LENGTH) throw new IllegalArgumentException(); + // The first 16 bits of the tag must be zero (reserved) + ByteUtils.writeUint16(0, tag, 0); + // Encode the transport identifier as an unsigned 16-bit integer + ByteUtils.writeUint16(transportId, tag, 4); + // Encode the connection number as an unsigned 32-bit integer + ByteUtils.writeUint32(connection, tag, 6); + // Encode the frame number as an unsigned 32-bit integer + ByteUtils.writeUint32(frame, tag, 10); + // The last 16 bits of the tag must be zero (block number) + ByteUtils.writeUint16(0, tag, 14); } } diff --git a/components/net/sf/briar/transport/TransportModule.java b/components/net/sf/briar/transport/TransportModule.java index 1fefd05eafab81a84782e5887fbb02fa9592cf03..d952d534ec6223b77b09f7a97b0e2c2770e4ea60 100644 --- a/components/net/sf/briar/transport/TransportModule.java +++ b/components/net/sf/briar/transport/TransportModule.java @@ -1,8 +1,8 @@ package net.sf.briar.transport; +import net.sf.briar.api.transport.ConnectionReaderFactory; import net.sf.briar.api.transport.ConnectionWindowFactory; -import net.sf.briar.api.transport.PacketReaderFactory; -import net.sf.briar.api.transport.PacketWriterFactory; +import net.sf.briar.api.transport.ConnectionWriterFactory; import com.google.inject.AbstractModule; @@ -10,9 +10,11 @@ public class TransportModule extends AbstractModule { @Override protected void configure() { + bind(ConnectionReaderFactory.class).to( + ConnectionReaderFactoryImpl.class); bind(ConnectionWindowFactory.class).to( ConnectionWindowFactoryImpl.class); - bind(PacketReaderFactory.class).to(PacketReaderFactoryImpl.class); - bind(PacketWriterFactory.class).to(PacketWriterFactoryImpl.class); + bind(ConnectionWriterFactory.class).to( + ConnectionWriterFactoryImpl.class); } } diff --git a/test/build.xml b/test/build.xml index f63488bbfffcdf781d493a939c4e0da5bd8d5b76..f6bf3ddb1025aec127b87dd6c206901086de5f4a 100644 --- a/test/build.xml +++ b/test/build.xml @@ -36,15 +36,14 @@ <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.ConnectionDecrypterImplTest'/> + <test name='net.sf.briar.transport.ConnectionEncrypterImplTest'/> + <test name='net.sf.briar.transport.ConnectionReaderImplTest'/> <test name='net.sf.briar.transport.ConnectionRecogniserImplTest'/> <test name='net.sf.briar.transport.ConnectionWindowImplTest'/> - <test name='net.sf.briar.transport.PacketDecrypterImplTest'/> - <test name='net.sf.briar.transport.PacketEncrypterImplTest'/> - <test name='net.sf.briar.transport.PacketReaderImplTest'/> - <test name='net.sf.briar.transport.PacketReadWriteTest'/> - <test name='net.sf.briar.transport.PacketWriterImplTest'/> - <test name='net.sf.briar.transport.TagDecoderTest'/> - <test name='net.sf.briar.transport.TagEncoderTest'/> + <test name='net.sf.briar.transport.ConnectionWriterImplTest'/> + <test name='net.sf.briar.transport.FrameReadWriteTest'/> + <test name='net.sf.briar.util.ByteUtilsTest'/> <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/FileReadWriteTest.java b/test/net/sf/briar/FileReadWriteTest.java index 14ac71a9c4e7e1b5bef0ca021994ce66e750e56a..e7a6c86c9b5cd2b56808ef875d4f0243c8b8617a 100644 --- a/test/net/sf/briar/FileReadWriteTest.java +++ b/test/net/sf/briar/FileReadWriteTest.java @@ -41,10 +41,10 @@ import net.sf.briar.api.protocol.writers.ProtocolWriterFactory; import net.sf.briar.api.protocol.writers.RequestWriter; import net.sf.briar.api.protocol.writers.SubscriptionWriter; import net.sf.briar.api.protocol.writers.TransportWriter; -import net.sf.briar.api.transport.PacketReader; -import net.sf.briar.api.transport.PacketReaderFactory; -import net.sf.briar.api.transport.PacketWriter; -import net.sf.briar.api.transport.PacketWriterFactory; +import net.sf.briar.api.transport.ConnectionReader; +import net.sf.briar.api.transport.ConnectionReaderFactory; +import net.sf.briar.api.transport.ConnectionWriter; +import net.sf.briar.api.transport.ConnectionWriterFactory; import net.sf.briar.crypto.CryptoModule; import net.sf.briar.protocol.ProtocolModule; import net.sf.briar.protocol.writers.WritersModule; @@ -66,8 +66,8 @@ public class FileReadWriteTest extends TestCase { private final BatchId ack = new BatchId(TestUtils.getRandomId()); private final long timestamp = System.currentTimeMillis(); - private final PacketReaderFactory packetReaderFactory; - private final PacketWriterFactory packetWriterFactory; + private final ConnectionReaderFactory connectionReaderFactory; + private final ConnectionWriterFactory connectionWriterFactory; private final ProtocolReaderFactory protocolReaderFactory; private final ProtocolWriterFactory protocolWriterFactory; private final CryptoComponent crypto; @@ -87,8 +87,8 @@ public class FileReadWriteTest extends TestCase { Injector i = Guice.createInjector(new CryptoModule(), new ProtocolModule(), new SerialModule(), new TransportModule(), new WritersModule()); - packetReaderFactory = i.getInstance(PacketReaderFactory.class); - packetWriterFactory = i.getInstance(PacketWriterFactory.class); + connectionReaderFactory = i.getInstance(ConnectionReaderFactory.class); + connectionWriterFactory = i.getInstance(ConnectionWriterFactory.class); protocolReaderFactory = i.getInstance(ProtocolReaderFactory.class); protocolWriterFactory = i.getInstance(ProtocolWriterFactory.class); crypto = i.getInstance(CryptoComponent.class); @@ -134,14 +134,13 @@ public class FileReadWriteTest extends TestCase { public void testWriteFile() throws Exception { OutputStream out = new FileOutputStream(file); // Use Alice's secret for writing - PacketWriter packetWriter = packetWriterFactory.createPacketWriter(out, + ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out, transportId, connection, aliceSecret); - out = packetWriter.getOutputStream(); + out = w.getOutputStream(); AckWriter a = protocolWriterFactory.createAckWriter(out); assertTrue(a.writeBatchId(ack)); a.finish(); - packetWriter.finishPacket(); BatchWriter b = protocolWriterFactory.createBatchWriter(out); assertTrue(b.writeMessage(message.getBytes())); @@ -149,7 +148,6 @@ public class FileReadWriteTest extends TestCase { assertTrue(b.writeMessage(message2.getBytes())); assertTrue(b.writeMessage(message3.getBytes())); b.finish(); - packetWriter.finishPacket(); OfferWriter o = protocolWriterFactory.createOfferWriter(out); assertTrue(o.writeMessageId(message.getId())); @@ -157,14 +155,12 @@ public class FileReadWriteTest extends TestCase { assertTrue(o.writeMessageId(message2.getId())); assertTrue(o.writeMessageId(message3.getId())); o.finish(); - packetWriter.finishPacket(); RequestWriter r = protocolWriterFactory.createRequestWriter(out); BitSet requested = new BitSet(4); requested.set(1); requested.set(3); r.writeRequest(offerId, requested, 4); - packetWriter.finishPacket(); SubscriptionWriter s = protocolWriterFactory.createSubscriptionWriter(out); @@ -173,14 +169,12 @@ public class FileReadWriteTest extends TestCase { subs.put(group, 0L); subs.put(group1, 0L); s.writeSubscriptions(subs, timestamp); - packetWriter.finishPacket(); TransportWriter t = protocolWriterFactory.createTransportWriter(out); t.writeTransports(transports, timestamp); - packetWriter.finishPacket(); - out.flush(); - out.close(); + w.getOutputStream().flush(); + w.getOutputStream().close(); assertTrue(file.exists()); assertTrue(file.length() > message.getSize()); } @@ -200,22 +194,20 @@ public class FileReadWriteTest extends TestCase { } assertEquals(16, offset); // Use Bob's secret for reading - PacketReader packetReader = packetReaderFactory.createPacketReader( - firstTag, in, transportId, connection, bobSecret); - in = packetReader.getInputStream(); + ConnectionReader r = connectionReaderFactory.createConnectionReader(in, + transportId, connection, bobSecret); + in = r.getInputStream(); ProtocolReader protocolReader = protocolReaderFactory.createProtocolReader(in); // Read the ack assertTrue(protocolReader.hasAck()); Ack a = protocolReader.readAck(); - packetReader.finishPacket(); assertEquals(Collections.singletonList(ack), a.getBatchIds()); // Read the batch assertTrue(protocolReader.hasBatch()); Batch b = protocolReader.readBatch(); - packetReader.finishPacket(); Collection<Message> messages = b.getMessages(); assertEquals(4, messages.size()); Iterator<Message> it = messages.iterator(); @@ -227,7 +219,6 @@ public class FileReadWriteTest extends TestCase { // Read the offer assertTrue(protocolReader.hasOffer()); Offer o = protocolReader.readOffer(); - packetReader.finishPacket(); Collection<MessageId> offered = o.getMessageIds(); assertEquals(4, offered.size()); Iterator<MessageId> it1 = offered.iterator(); @@ -238,10 +229,9 @@ public class FileReadWriteTest extends TestCase { // Read the request assertTrue(protocolReader.hasRequest()); - Request r = protocolReader.readRequest(); - packetReader.finishPacket(); - assertEquals(offerId, r.getOfferId()); - BitSet requested = r.getBitmap(); + Request req = protocolReader.readRequest(); + assertEquals(offerId, req.getOfferId()); + BitSet requested = req.getBitmap(); assertFalse(requested.get(0)); assertTrue(requested.get(1)); assertFalse(requested.get(2)); @@ -252,7 +242,6 @@ public class FileReadWriteTest extends TestCase { // Read the subscription update assertTrue(protocolReader.hasSubscriptionUpdate()); SubscriptionUpdate s = protocolReader.readSubscriptionUpdate(); - packetReader.finishPacket(); Map<Group, Long> subs = s.getSubscriptions(); assertEquals(2, subs.size()); assertEquals(Long.valueOf(0L), subs.get(group)); @@ -262,7 +251,6 @@ public class FileReadWriteTest extends TestCase { // Read the transport update assertTrue(protocolReader.hasTransportUpdate()); TransportUpdate t = protocolReader.readTransportUpdate(); - packetReader.finishPacket(); assertEquals(transports, t.getTransports()); assertTrue(t.getTimestamp() == timestamp); diff --git a/test/net/sf/briar/TestUtils.java b/test/net/sf/briar/TestUtils.java index d8c25231b78bca3463c96c424ac795da3a7c0f59..6ecb32a4c68cf92c2b4f0b2741d8b29d96070c0f 100644 --- a/test/net/sf/briar/TestUtils.java +++ b/test/net/sf/briar/TestUtils.java @@ -3,10 +3,12 @@ package net.sf.briar; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.PrintStream; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; +import junit.framework.TestCase; import net.sf.briar.api.protocol.UniqueId; public class TestUtils { @@ -60,4 +62,14 @@ public class TestUtils { random.nextBytes(b); return b; } + + public static void readFully(InputStream in, byte[] b) throws IOException { + int offset = 0; + while(offset < b.length) { + int read = in.read(b, offset, b.length - offset); + if(read == -1) break; + offset += read; + } + TestCase.assertEquals(b.length, offset); + } } diff --git a/test/net/sf/briar/crypto/CounterModeTest.java b/test/net/sf/briar/crypto/CounterModeTest.java index 7ce6dbcc29515fb98e09f59a458c2bad2ba50385..ff664429db30fbc03253ad8f65dc0ff34654cba1 100644 --- a/test/net/sf/briar/crypto/CounterModeTest.java +++ b/test/net/sf/briar/crypto/CounterModeTest.java @@ -87,31 +87,28 @@ public class CounterModeTest extends TestCase { @Test public void testLeastSignificantBitsUsedAsCounter() throws GeneralSecurityException { - // Initialise the least significant 32 bits of the IV to zero and + // Initialise the least significant 16 bits of the IV to zero and // encrypt ten blocks of zeroes byte[] plaintext = new byte[BLOCK_SIZE_BYTES * 10]; byte[] ivBytes = new byte[BLOCK_SIZE_BYTES]; random.nextBytes(ivBytes); - for(int i = BLOCK_SIZE_BYTES - 4; i < BLOCK_SIZE_BYTES; i++) { - ivBytes[i] = 0; - } + ivBytes[BLOCK_SIZE_BYTES - 2] = 0; + ivBytes[BLOCK_SIZE_BYTES - 1] = 0; IvParameterSpec iv = new IvParameterSpec(ivBytes); Cipher cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER); cipher.init(Cipher.ENCRYPT_MODE, key, iv); - byte[] ciphertext = - new byte[cipher.getOutputSize(plaintext.length)]; + byte[] ciphertext = new byte[cipher.getOutputSize(plaintext.length)]; cipher.doFinal(plaintext, 0, plaintext.length, ciphertext, 0); - // Initialise the least significant 32 bits of the IV to one and + // Make sure the IV array hasn't been modified + assertEquals(0, ivBytes[BLOCK_SIZE_BYTES - 2]); + assertEquals(0, ivBytes[BLOCK_SIZE_BYTES - 1]); + // Initialise the least significant 16 bits of the IV to one and // encrypt another ten blocks of zeroes - for(int i = BLOCK_SIZE_BYTES - 4; i < BLOCK_SIZE_BYTES; i++) { - assertEquals(0, ivBytes[i]); - } ivBytes[BLOCK_SIZE_BYTES - 1] = 1; iv = new IvParameterSpec(ivBytes); cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER); cipher.init(Cipher.ENCRYPT_MODE, key, iv); - byte[] ciphertext1 = - new byte[cipher.getOutputSize(plaintext.length)]; + byte[] ciphertext1 = new byte[cipher.getOutputSize(plaintext.length)]; cipher.doFinal(plaintext, 0, plaintext.length, ciphertext1, 0); // The last nine blocks of the first ciphertext should be identical to // the first nine blocks of the second ciphertext @@ -121,38 +118,34 @@ public class CounterModeTest extends TestCase { } @Test - public void testCounterUsesMoreThan32Bits() + public void testCounterUsesMoreThan16Bits() throws GeneralSecurityException { - // Initialise the least significant bits of the IV to 2^32-1 and + // Initialise the least significant bits of the IV to 2^16-1 and // encrypt ten blocks of zeroes byte[] plaintext = new byte[BLOCK_SIZE_BYTES * 10]; byte[] ivBytes = new byte[BLOCK_SIZE_BYTES]; random.nextBytes(ivBytes); - ivBytes[BLOCK_SIZE_BYTES - 5] = 0; - for(int i = BLOCK_SIZE_BYTES - 4; i < BLOCK_SIZE_BYTES; i++) { - ivBytes[i] = (byte) 255; - } + ivBytes[BLOCK_SIZE_BYTES - 3] = 0; + ivBytes[BLOCK_SIZE_BYTES - 2] = (byte) 255; + ivBytes[BLOCK_SIZE_BYTES - 1] = (byte) 255; IvParameterSpec iv = new IvParameterSpec(ivBytes); Cipher cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER); cipher.init(Cipher.ENCRYPT_MODE, key, iv); - byte[] ciphertext = - new byte[cipher.getOutputSize(plaintext.length)]; + byte[] ciphertext = new byte[cipher.getOutputSize(plaintext.length)]; cipher.doFinal(plaintext, 0, plaintext.length, ciphertext, 0); - // Initialise the least significant bits of the IV to 2^32 and + // Make sure the IV array hasn't been modified + assertEquals(0, ivBytes[BLOCK_SIZE_BYTES - 3]); + assertEquals((byte) 255, ivBytes[BLOCK_SIZE_BYTES - 2]); + assertEquals((byte) 255, ivBytes[BLOCK_SIZE_BYTES - 1]); + // Initialise the least significant bits of the IV to 2^16 and // encrypt another ten blocks of zeroes - assertEquals(0, ivBytes[BLOCK_SIZE_BYTES - 5]); - for(int i = BLOCK_SIZE_BYTES - 4; i < BLOCK_SIZE_BYTES; i++) { - assertEquals((byte) 255, ivBytes[i]); - } - ivBytes[BLOCK_SIZE_BYTES - 5] = 1; - for(int i = BLOCK_SIZE_BYTES - 4; i < BLOCK_SIZE_BYTES; i++) { - ivBytes[i] = 0; - } + ivBytes[BLOCK_SIZE_BYTES - 3] = 1; + ivBytes[BLOCK_SIZE_BYTES - 2] = 0; + ivBytes[BLOCK_SIZE_BYTES - 1] = 0; iv = new IvParameterSpec(ivBytes); cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER); cipher.init(Cipher.ENCRYPT_MODE, key, iv); - byte[] ciphertext1 = - new byte[cipher.getOutputSize(plaintext.length)]; + byte[] ciphertext1 = new byte[cipher.getOutputSize(plaintext.length)]; cipher.doFinal(plaintext, 0, plaintext.length, ciphertext1, 0); // The last nine blocks of the first ciphertext should be identical to // the first nine blocks of the second ciphertext diff --git a/test/net/sf/briar/crypto/CryptoComponentTest.java b/test/net/sf/briar/crypto/CryptoComponentTest.java index d7dad93ed9b8933ad64725111c383580401161f9..b8ea8ade6a8a36e136374a323035a58bb791c7e8 100644 --- a/test/net/sf/briar/crypto/CryptoComponentTest.java +++ b/test/net/sf/briar/crypto/CryptoComponentTest.java @@ -27,22 +27,22 @@ public class CryptoComponentTest extends TestCase { // Check that Alice's incoming keys match Bob's outgoing keys assertEquals(crypto.deriveIncomingMacKey(aliceSecret), crypto.deriveOutgoingMacKey(bobSecret)); - assertEquals(crypto.deriveIncomingPacketKey(aliceSecret), - crypto.deriveOutgoingPacketKey(bobSecret)); + assertEquals(crypto.deriveIncomingFrameKey(aliceSecret), + crypto.deriveOutgoingFrameKey(bobSecret)); assertEquals(crypto.deriveIncomingTagKey(aliceSecret), crypto.deriveOutgoingTagKey(bobSecret)); // Check that Alice's outgoing keys match Bob's incoming keys assertEquals(crypto.deriveOutgoingMacKey(aliceSecret), crypto.deriveIncomingMacKey(bobSecret)); - assertEquals(crypto.deriveOutgoingPacketKey(aliceSecret), - crypto.deriveIncomingPacketKey(bobSecret)); + assertEquals(crypto.deriveOutgoingFrameKey(aliceSecret), + crypto.deriveIncomingFrameKey(bobSecret)); assertEquals(crypto.deriveOutgoingTagKey(aliceSecret), crypto.deriveIncomingTagKey(bobSecret)); // Check that Alice's incoming and outgoing keys are different assertFalse(crypto.deriveIncomingMacKey(aliceSecret).equals( crypto.deriveOutgoingMacKey(aliceSecret))); - assertFalse(crypto.deriveIncomingPacketKey(aliceSecret).equals( - crypto.deriveOutgoingPacketKey(aliceSecret))); + assertFalse(crypto.deriveIncomingFrameKey(aliceSecret).equals( + crypto.deriveOutgoingFrameKey(aliceSecret))); assertFalse(crypto.deriveIncomingTagKey(aliceSecret).equals( crypto.deriveOutgoingTagKey(aliceSecret))); } diff --git a/test/net/sf/briar/protocol/AckReaderTest.java b/test/net/sf/briar/protocol/AckReaderTest.java index 67f08091e979d5cd2902b3fd22165816eaaf8a4d..42dffb2d0afce319d9299b99bad30433b7ef6728 100644 --- a/test/net/sf/briar/protocol/AckReaderTest.java +++ b/test/net/sf/briar/protocol/AckReaderTest.java @@ -7,12 +7,12 @@ import java.util.Collections; import java.util.Random; import junit.framework.TestCase; +import net.sf.briar.api.FormatException; import net.sf.briar.api.protocol.Ack; import net.sf.briar.api.protocol.BatchId; import net.sf.briar.api.protocol.ProtocolConstants; import net.sf.briar.api.protocol.Tags; import net.sf.briar.api.protocol.UniqueId; -import net.sf.briar.api.serial.FormatException; import net.sf.briar.api.serial.Reader; import net.sf.briar.api.serial.ReaderFactory; import net.sf.briar.api.serial.Writer; diff --git a/test/net/sf/briar/protocol/BatchReaderTest.java b/test/net/sf/briar/protocol/BatchReaderTest.java index 9c7407dbbaebdae5f17f4f0b8af1a9f8beac2792..8306b683eb9138dacfd6ac976e6766400d610099 100644 --- a/test/net/sf/briar/protocol/BatchReaderTest.java +++ b/test/net/sf/briar/protocol/BatchReaderTest.java @@ -7,13 +7,13 @@ import java.security.MessageDigest; import java.util.Collections; import junit.framework.TestCase; +import net.sf.briar.api.FormatException; import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.protocol.Batch; import net.sf.briar.api.protocol.BatchId; import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.ProtocolConstants; import net.sf.briar.api.protocol.Tags; -import net.sf.briar.api.serial.FormatException; import net.sf.briar.api.serial.ObjectReader; import net.sf.briar.api.serial.Reader; import net.sf.briar.api.serial.ReaderFactory; diff --git a/test/net/sf/briar/protocol/ConsumersTest.java b/test/net/sf/briar/protocol/ConsumersTest.java index fbd839d9b43a0ae117e56e785f2581f960b5a03f..cb03476b616886a23c6e0924eb978d6089b8efab 100644 --- a/test/net/sf/briar/protocol/ConsumersTest.java +++ b/test/net/sf/briar/protocol/ConsumersTest.java @@ -7,8 +7,8 @@ import java.util.Arrays; import java.util.Random; import junit.framework.TestCase; +import net.sf.briar.api.FormatException; import net.sf.briar.api.crypto.CryptoComponent; -import net.sf.briar.api.serial.FormatException; import net.sf.briar.crypto.CryptoModule; import org.junit.Before; diff --git a/test/net/sf/briar/protocol/RequestReaderTest.java b/test/net/sf/briar/protocol/RequestReaderTest.java index 5b10e496488f01a7796080b43d607494bd4ac442..8be4c17049b808a2f7357cfd73c359796cfdb379 100644 --- a/test/net/sf/briar/protocol/RequestReaderTest.java +++ b/test/net/sf/briar/protocol/RequestReaderTest.java @@ -5,12 +5,12 @@ import java.io.ByteArrayOutputStream; import java.util.BitSet; import junit.framework.TestCase; +import net.sf.briar.api.FormatException; import net.sf.briar.api.protocol.OfferId; import net.sf.briar.api.protocol.ProtocolConstants; import net.sf.briar.api.protocol.Request; import net.sf.briar.api.protocol.Tags; import net.sf.briar.api.protocol.UniqueId; -import net.sf.briar.api.serial.FormatException; import net.sf.briar.api.serial.Reader; import net.sf.briar.api.serial.ReaderFactory; import net.sf.briar.api.serial.Writer; diff --git a/test/net/sf/briar/serial/ReaderImplTest.java b/test/net/sf/briar/serial/ReaderImplTest.java index 28670b8b91751fb013d46a5a7da4ce5166757488..ddd310accb9d726971b643952e501fc2d5a8dbba 100644 --- a/test/net/sf/briar/serial/ReaderImplTest.java +++ b/test/net/sf/briar/serial/ReaderImplTest.java @@ -11,8 +11,8 @@ import java.util.Map.Entry; import junit.framework.TestCase; import net.sf.briar.api.Bytes; +import net.sf.briar.api.FormatException; import net.sf.briar.api.serial.Consumer; -import net.sf.briar.api.serial.FormatException; import net.sf.briar.api.serial.ObjectReader; import net.sf.briar.api.serial.Reader; import net.sf.briar.util.StringUtils; diff --git a/test/net/sf/briar/transport/ConnectionDecrypterImplTest.java b/test/net/sf/briar/transport/ConnectionDecrypterImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9bf306fd844dc7470d0a80e28c5b1fc75e876e87 --- /dev/null +++ b/test/net/sf/briar/transport/ConnectionDecrypterImplTest.java @@ -0,0 +1,100 @@ +package net.sf.briar.transport; + +import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; + +import java.io.ByteArrayInputStream; +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.TestUtils; +import net.sf.briar.api.crypto.CryptoComponent; +import net.sf.briar.crypto.CryptoModule; + +import org.apache.commons.io.output.ByteArrayOutputStream; +import org.junit.Test; + +import com.google.inject.Guice; +import com.google.inject.Injector; + +public class ConnectionDecrypterImplTest extends TestCase { + + private static final int MAC_LENGTH = 32; + + private final Cipher frameCipher; + private final SecretKey frameKey; + private final int transportId = 1234; + private final long connection = 12345L; + + public ConnectionDecrypterImplTest() { + super(); + Injector i = Guice.createInjector(new CryptoModule()); + CryptoComponent crypto = i.getInstance(CryptoComponent.class); + frameCipher = crypto.getFrameCipher(); + frameKey = crypto.generateSecretKey(); + } + + @Test + public void testSingleByteFrame() throws Exception { + // Create a fake ciphertext frame: one byte plus a MAC + byte[] ciphertext = new byte[1 + MAC_LENGTH]; + ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); + // Check that one byte plus a MAC can be read + ConnectionDecrypter d = new ConnectionDecrypterImpl(in, transportId, + connection, frameCipher, frameKey); + assertFalse(d.getInputStream().read() == -1); + d.readMac(new byte[MAC_LENGTH]); + assertTrue(d.getInputStream().read() == -1); + } + + @Test + public void testDecryption() throws Exception { + // Calculate the expected plaintext for the first frame + byte[] ciphertext = new byte[123]; + byte[] ivBytes = new byte[TAG_LENGTH]; + TagEncoder.encodeTag(ivBytes, transportId, connection, 0L); + IvParameterSpec iv = new IvParameterSpec(ivBytes); + frameCipher.init(Cipher.DECRYPT_MODE, frameKey, iv); + byte[] plaintext = frameCipher.doFinal(ciphertext); + // Calculate the expected plaintext for the second frame + byte[] ciphertext1 = new byte[1234]; + TagEncoder.encodeTag(ivBytes, transportId, connection, 1L); + iv = new IvParameterSpec(ivBytes); + frameCipher.init(Cipher.DECRYPT_MODE, frameKey, iv); + byte[] plaintext1 = frameCipher.doFinal(ciphertext1); + assertEquals(ciphertext1.length, plaintext1.length); + // Concatenate the ciphertexts + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write(ciphertext); + out.write(ciphertext1); + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + // Use a ConnectionDecrypter to decrypt the ciphertext + ConnectionDecrypter d = new ConnectionDecrypterImpl(in, transportId, + connection, frameCipher, frameKey); + // First frame + byte[] decrypted = new byte[plaintext.length - MAC_LENGTH]; + TestUtils.readFully(d.getInputStream(), decrypted); + byte[] decryptedMac = new byte[MAC_LENGTH]; + d.readMac(decryptedMac); + // Second frame + byte[] decrypted1 = new byte[plaintext1.length - MAC_LENGTH]; + TestUtils.readFully(d.getInputStream(), decrypted1); + byte[] decryptedMac1 = new byte[MAC_LENGTH]; + d.readMac(decryptedMac1); + // Check that the actual plaintext matches the expected plaintext + out.reset(); + out.write(plaintext); + out.write(plaintext1); + byte[] expected = out.toByteArray(); + out.reset(); + out.write(decrypted); + out.write(decryptedMac); + out.write(decrypted1); + out.write(decryptedMac1); + byte[] actual = out.toByteArray(); + assertTrue(Arrays.equals(expected, actual)); + } +} diff --git a/test/net/sf/briar/transport/ConnectionEncrypterImplTest.java b/test/net/sf/briar/transport/ConnectionEncrypterImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3b3a9145d2d3811e0fb18ab6eec23dc68d3acb64 --- /dev/null +++ b/test/net/sf/briar/transport/ConnectionEncrypterImplTest.java @@ -0,0 +1,98 @@ +package net.sf.briar.transport; + +import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; + +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 ConnectionEncrypterImplTest extends TestCase { + + private static final int MAC_LENGTH = 32; + + private final Cipher tagCipher, frameCipher; + private final SecretKey tagKey, frameKey; + private final int transportId = 1234; + private final long connection = 12345L; + + public ConnectionEncrypterImplTest() { + super(); + Injector i = Guice.createInjector(new CryptoModule()); + CryptoComponent crypto = i.getInstance(CryptoComponent.class); + tagCipher = crypto.getTagCipher(); + frameCipher = crypto.getFrameCipher(); + tagKey = crypto.generateSecretKey(); + frameKey = crypto.generateSecretKey(); + } + + @Test + public void testSingleByteFrame() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ConnectionEncrypter e = new ConnectionEncrypterImpl(out, transportId, + connection, tagCipher, frameCipher, tagKey, frameKey); + e.getOutputStream().write((byte) 0); + e.writeMac(new byte[MAC_LENGTH]); + assertEquals(TAG_LENGTH + 1 + MAC_LENGTH, out.toByteArray().length); + } + + @Test + public void testEncryption() throws Exception { + // Calculate the expected ciphertext for the tag + byte[] plaintextTag = TagEncoder.encodeTag(transportId, connection); + assertEquals(TAG_LENGTH, plaintextTag.length); + tagCipher.init(Cipher.ENCRYPT_MODE, tagKey); + byte[] tag = tagCipher.doFinal(plaintextTag); + assertEquals(TAG_LENGTH, tag.length); + // Calculate the expected ciphertext for the first frame + byte[] plaintext = new byte[123]; + byte[] plaintextMac = new byte[MAC_LENGTH]; + byte[] ivBytes = new byte[TAG_LENGTH]; + TagEncoder.encodeTag(ivBytes, transportId, connection, 0L); + IvParameterSpec iv = new IvParameterSpec(ivBytes); + frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, iv); + byte[] ciphertext = new byte[plaintext.length + plaintextMac.length]; + int offset = frameCipher.update(plaintext, 0, plaintext.length, + ciphertext); + frameCipher.doFinal(plaintextMac, 0, plaintextMac.length, ciphertext, + offset); + // Calculate the expected ciphertext for the second frame + byte[] plaintext1 = new byte[1234]; + TagEncoder.encodeTag(ivBytes, transportId, connection, 1L); + iv = new IvParameterSpec(ivBytes); + frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, iv); + byte[] ciphertext1 = new byte[plaintext1.length + plaintextMac.length]; + offset = frameCipher.update(plaintext1, 0, plaintext1.length, + ciphertext1); + frameCipher.doFinal(plaintextMac, 0, plaintextMac.length, ciphertext1, + offset); + // Concatenate the ciphertexts + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write(tag); + out.write(ciphertext); + out.write(ciphertext1); + byte[] expected = out.toByteArray(); + // Use a ConnectionEncrypter to encrypt the plaintext + out.reset(); + ConnectionEncrypter e = new ConnectionEncrypterImpl(out, transportId, + connection, tagCipher, frameCipher, tagKey, frameKey); + e.getOutputStream().write(plaintext); + e.writeMac(plaintextMac); + e.getOutputStream().write(plaintext1); + e.writeMac(plaintextMac); + byte[] actual = out.toByteArray(); + // Check that the actual ciphertext matches the expected ciphertext + assertTrue(Arrays.equals(expected, actual)); + } +} diff --git a/test/net/sf/briar/transport/ConnectionReaderImplTest.java b/test/net/sf/briar/transport/ConnectionReaderImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d73e43676e82a382dbfdd90fa3886447da9cf719 --- /dev/null +++ b/test/net/sf/briar/transport/ConnectionReaderImplTest.java @@ -0,0 +1,106 @@ +package net.sf.briar.transport; + +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import javax.crypto.Mac; + +import junit.framework.TestCase; +import net.sf.briar.TestUtils; +import net.sf.briar.api.crypto.CryptoComponent; +import net.sf.briar.api.transport.ConnectionReader; +import net.sf.briar.crypto.CryptoModule; +import net.sf.briar.util.ByteUtils; + +import org.apache.commons.io.output.ByteArrayOutputStream; +import org.junit.Test; + +import com.google.inject.Guice; +import com.google.inject.Injector; + +public class ConnectionReaderImplTest extends TestCase { + + private final Mac mac; + + public ConnectionReaderImplTest() 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 testSingleByteFrame() throws Exception { + // Six bytes for the header, one for the payload + byte[] frame = new byte[6 + 1 + mac.getMacLength()]; + ByteUtils.writeUint16(1, frame, 4); // Payload length = 1 + // Calculate the MAC + mac.update(frame, 0, 6 + 1); + mac.doFinal(frame, 6 + 1); + // Read the frame + ByteArrayInputStream in = new ByteArrayInputStream(frame); + ConnectionDecrypter d = new NullConnectionDecrypter(in); + ConnectionReader r = new ConnectionReaderImpl(d, mac); + // There should be one byte available before EOF + assertEquals(0, r.getInputStream().read()); + assertEquals(-1, r.getInputStream().read()); + } + + @Test + public void testMultipleFrames() throws Exception { + // First frame: 123-byte payload + byte[] frame = new byte[6 + 123 + mac.getMacLength()]; + ByteUtils.writeUint16(123, frame, 4); + mac.update(frame, 0, 6 + 123); + mac.doFinal(frame, 6 + 123); + // Second frame: 1234-byte payload + byte[] frame1 = new byte[6 + 1234 + mac.getMacLength()]; + ByteUtils.writeUint32(1, frame1, 0); + ByteUtils.writeUint16(1234, frame1, 4); + mac.update(frame1, 0, 6 + 1234); + mac.doFinal(frame1, 6 + 1234); + // Concatenate the frames + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write(frame); + out.write(frame1); + // Read the frames + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + ConnectionDecrypter d = new NullConnectionDecrypter(in); + ConnectionReader r = new ConnectionReaderImpl(d, mac); + byte[] read = new byte[123]; + TestUtils.readFully(r.getInputStream(), read); + assertTrue(Arrays.equals(new byte[123], read)); + byte[] read1 = new byte[1234]; + TestUtils.readFully(r.getInputStream(), read1); + assertTrue(Arrays.equals(new byte[1234], read1)); + } + + /** A ConnectionDecrypter that performs no decryption. */ + private static class NullConnectionDecrypter + implements ConnectionDecrypter { + + private final InputStream in; + + private NullConnectionDecrypter(InputStream in) { + this.in = in; + } + + public InputStream getInputStream() { + return in; + } + + public void readMac(byte[] mac) throws IOException { + int offset = 0; + while(offset < mac.length) { + int read = in.read(mac, offset, mac.length - offset); + if(read == -1) break; + offset += read; + } + if(offset < mac.length) throw new EOFException(); + } + } +} diff --git a/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java b/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java index 52ed9148b5b6db6760ba0976404e9c390c77fd65..c94b242a8a57c9538276596b77517afc7604316b 100644 --- a/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java +++ b/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java @@ -65,7 +65,7 @@ public class ConnectionRecogniserImplTest extends TestCase { SecretKey tagKey = crypto.deriveIncomingTagKey(secret); Cipher tagCipher = crypto.getTagCipher(); tagCipher.init(Cipher.ENCRYPT_MODE, tagKey); - byte[] tag = TagEncoder.encodeTag(transportId, 3L, 0); + byte[] tag = TagEncoder.encodeTag(transportId, 3L); byte[] encryptedTag = tagCipher.doFinal(tag); Mockery context = new Mockery(); diff --git a/test/net/sf/briar/transport/ConnectionWriterImplTest.java b/test/net/sf/briar/transport/ConnectionWriterImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4892d58c9724296d8e9c08ecf37b14534d3c9f50 --- /dev/null +++ b/test/net/sf/briar/transport/ConnectionWriterImplTest.java @@ -0,0 +1,98 @@ +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; + +import junit.framework.TestCase; +import net.sf.briar.api.crypto.CryptoComponent; +import net.sf.briar.api.transport.ConnectionWriter; +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 ConnectionWriterImplTest extends TestCase { + + private final Mac mac; + + public ConnectionWriterImplTest() 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 testSingleByteFrame() throws Exception { + // Six bytes for the header, one for the payload + byte[] frame = new byte[6 + 1 + mac.getMacLength()]; + ByteUtils.writeUint16(1, frame, 4); // Payload length = 1 + // Calculate the MAC + mac.update(frame, 0, 6 + 1); + mac.doFinal(frame, 6 + 1); + // Check that the ConnectionWriter gets the same results + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ConnectionEncrypter e = new NullConnectionEncrypter(out); + ConnectionWriter w = new ConnectionWriterImpl(e, mac); + w.getOutputStream().write(0); + w.getOutputStream().flush(); + assertTrue(Arrays.equals(frame, out.toByteArray())); + } + + @Test + public void testMultipleFrames() throws Exception { + // First frame: 123-byte payload + byte[] frame = new byte[6 + 123 + mac.getMacLength()]; + ByteUtils.writeUint16(123, frame, 4); + mac.update(frame, 0, 6 + 123); + mac.doFinal(frame, 6 + 123); + // Second frame: 1234-byte payload + byte[] frame1 = new byte[6 + 1234 + mac.getMacLength()]; + ByteUtils.writeUint32(1, frame1, 0); + ByteUtils.writeUint16(1234, frame1, 4); + mac.update(frame1, 0, 6 + 1234); + mac.doFinal(frame1, 6 + 1234); + // Concatenate the frames + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write(frame); + out.write(frame1); + byte[] expected = out.toByteArray(); + // Check that the ConnectionWriter gets the same results + out.reset(); + ConnectionEncrypter e = new NullConnectionEncrypter(out); + ConnectionWriter w = new ConnectionWriterImpl(e, mac); + w.getOutputStream().write(new byte[123]); + w.getOutputStream().flush(); + w.getOutputStream().write(new byte[1234]); + w.getOutputStream().flush(); + byte[] actual = out.toByteArray(); + assertTrue(Arrays.equals(expected, actual)); + } + + /** A ConnectionEncrypter that performs no encryption. */ + private static class NullConnectionEncrypter + implements ConnectionEncrypter { + + private final OutputStream out; + + private NullConnectionEncrypter(OutputStream out) { + this.out = out; + } + + public OutputStream getOutputStream() { + return out; + } + + public void writeMac(byte[] mac) throws IOException { + out.write(mac); + } + } +} diff --git a/test/net/sf/briar/transport/PacketReadWriteTest.java b/test/net/sf/briar/transport/FrameReadWriteTest.java similarity index 53% rename from test/net/sf/briar/transport/PacketReadWriteTest.java rename to test/net/sf/briar/transport/FrameReadWriteTest.java index 27b1f35491103804c773a42c5237103f566a7347..7fb8ef717728d91e14b14aa7a2e4bceb4fbe4187 100644 --- a/test/net/sf/briar/transport/PacketReadWriteTest.java +++ b/test/net/sf/briar/transport/FrameReadWriteTest.java @@ -15,8 +15,8 @@ import javax.crypto.SecretKey; import junit.framework.TestCase; import net.sf.briar.api.crypto.CryptoComponent; -import net.sf.briar.api.transport.PacketReader; -import net.sf.briar.api.transport.PacketWriter; +import net.sf.briar.api.transport.ConnectionReader; +import net.sf.briar.api.transport.ConnectionWriter; import net.sf.briar.crypto.CryptoModule; import org.junit.Test; @@ -24,60 +24,66 @@ import org.junit.Test; import com.google.inject.Guice; import com.google.inject.Injector; -public class PacketReadWriteTest extends TestCase { +public class FrameReadWriteTest extends TestCase { private final CryptoComponent crypto; - private final Cipher tagCipher, packetCipher; - private final SecretKey macKey, tagKey, packetKey; + private final Cipher tagCipher, frameCipher; + private final SecretKey macKey, tagKey, frameKey; private final Mac mac; private final Random random; private final byte[] secret = new byte[100]; private final int transportId = 999; private final long connection = 1234L; - public PacketReadWriteTest() { + public FrameReadWriteTest() { super(); Injector i = Guice.createInjector(new CryptoModule()); crypto = i.getInstance(CryptoComponent.class); tagCipher = crypto.getTagCipher(); - packetCipher = crypto.getPacketCipher(); + frameCipher = crypto.getFrameCipher(); // Since we're sending packets to ourselves, we only need outgoing keys macKey = crypto.deriveOutgoingMacKey(secret); tagKey = crypto.deriveOutgoingTagKey(secret); - packetKey = crypto.deriveOutgoingPacketKey(secret); + frameKey = crypto.deriveOutgoingFrameKey(secret); mac = crypto.getMac(); random = new Random(); } @Test public void testWriteAndRead() throws Exception { - // Generate two random packets - byte[] packet = new byte[12345]; - random.nextBytes(packet); - byte[] packet1 = new byte[321]; - random.nextBytes(packet1); - // Write the packets + // Calculate the expected ciphertext for the tag + byte[] plaintextTag = TagEncoder.encodeTag(transportId, connection); + assertEquals(TAG_LENGTH, plaintextTag.length); + tagCipher.init(Cipher.ENCRYPT_MODE, tagKey); + byte[] tag = tagCipher.doFinal(plaintextTag); + assertEquals(TAG_LENGTH, tag.length); + // Generate two random frames + byte[] frame = new byte[12345]; + random.nextBytes(frame); + byte[] frame1 = new byte[321]; + random.nextBytes(frame1); + // Write the frames ByteArrayOutputStream out = new ByteArrayOutputStream(); - PacketEncrypter encrypter = new PacketEncrypterImpl(out, tagCipher, - packetCipher, tagKey, packetKey); + ConnectionEncrypter encrypter = new ConnectionEncrypterImpl(out, + transportId, connection, tagCipher, frameCipher, tagKey, + frameKey); mac.init(macKey); - PacketWriter writer = new PacketWriterImpl(encrypter, mac, transportId, - connection); + ConnectionWriter writer = new ConnectionWriterImpl(encrypter, mac); OutputStream out1 = writer.getOutputStream(); - out1.write(packet); - writer.finishPacket(); - out1.write(packet1); - writer.finishPacket(); - // Read the packets back + out1.write(frame); + out1.flush(); + out1.write(frame1); + out1.flush(); + // Read the frames back ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - byte[] firstTag = new byte[TAG_LENGTH]; - assertEquals(TAG_LENGTH, in.read(firstTag)); - PacketDecrypter decrypter = new PacketDecrypterImpl(firstTag, in, - tagCipher, packetCipher, tagKey, packetKey); - PacketReader reader = new PacketReaderImpl(decrypter, mac, transportId, - connection); + byte[] recoveredTag = new byte[TAG_LENGTH]; + assertEquals(TAG_LENGTH, in.read(recoveredTag)); + assertTrue(Arrays.equals(tag, recoveredTag)); + ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in, + transportId, connection, frameCipher, frameKey); + ConnectionReader reader = new ConnectionReaderImpl(decrypter, mac); InputStream in1 = reader.getInputStream(); - byte[] recovered = new byte[packet.length]; + byte[] recovered = new byte[frame.length]; int offset = 0; while(offset < recovered.length) { int read = in1.read(recovered, offset, recovered.length - offset); @@ -85,9 +91,8 @@ public class PacketReadWriteTest extends TestCase { offset += read; } assertEquals(recovered.length, offset); - reader.finishPacket(); - assertTrue(Arrays.equals(packet, recovered)); - byte[] recovered1 = new byte[packet1.length]; + assertTrue(Arrays.equals(frame, recovered)); + byte[] recovered1 = new byte[frame1.length]; offset = 0; while(offset < recovered1.length) { int read = in1.read(recovered1, offset, recovered1.length - offset); @@ -95,7 +100,6 @@ public class PacketReadWriteTest extends TestCase { offset += read; } assertEquals(recovered1.length, offset); - reader.finishPacket(); - assertTrue(Arrays.equals(packet1, recovered1)); + assertTrue(Arrays.equals(frame1, recovered1)); } } diff --git a/test/net/sf/briar/transport/PacketDecrypterImplTest.java b/test/net/sf/briar/transport/PacketDecrypterImplTest.java deleted file mode 100644 index c001a20aaf6b99168b276795d11e8d86ce4f043e..0000000000000000000000000000000000000000 --- a/test/net/sf/briar/transport/PacketDecrypterImplTest.java +++ /dev/null @@ -1,115 +0,0 @@ -package net.sf.briar.transport; - -import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; - -import java.io.ByteArrayInputStream; -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 PacketDecrypterImplTest extends TestCase { - - private final Cipher tagCipher, packetCipher; - private final SecretKey tagKey, packetKey; - - public PacketDecrypterImplTest() { - 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 testSingleBytePackets() throws Exception { - byte[] ciphertext = new byte[(TAG_LENGTH + 1) * 2]; - ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); - byte[] firstTag = new byte[TAG_LENGTH]; - assertEquals(TAG_LENGTH, in.read(firstTag)); - PacketDecrypter p = new PacketDecrypterImpl(firstTag, in, tagCipher, - packetCipher, tagKey, packetKey); - byte[] decryptedTag = p.readTag(); - assertEquals(TAG_LENGTH, decryptedTag.length); - assertTrue(p.getInputStream().read() > -1); - byte[] decryptedTag1 = p.readTag(); - assertEquals(TAG_LENGTH, decryptedTag1.length); - assertTrue(p.getInputStream().read() > -1); - } - - @Test - public void testDecryption() throws Exception { - byte[] tag = new byte[TAG_LENGTH]; - byte[] packet = new byte[123]; - byte[] tag1 = new byte[TAG_LENGTH]; - byte[] packet1 = new byte[234]; - // Calculate the first expected decrypted tag - tagCipher.init(Cipher.DECRYPT_MODE, tagKey); - byte[] expectedTag = tagCipher.doFinal(tag); - assertEquals(tag.length, expectedTag.length); - // Calculate the first expected decrypted packet - IvParameterSpec iv = new IvParameterSpec(expectedTag); - packetCipher.init(Cipher.DECRYPT_MODE, packetKey, iv); - byte[] expectedPacket = packetCipher.doFinal(packet); - assertEquals(packet.length, expectedPacket.length); - // Calculate the second expected decrypted tag - tagCipher.init(Cipher.DECRYPT_MODE, tagKey); - byte[] expectedTag1 = tagCipher.doFinal(tag1); - assertEquals(tag1.length, expectedTag1.length); - // Calculate the second expected decrypted packet - IvParameterSpec iv1 = new IvParameterSpec(expectedTag1); - packetCipher.init(Cipher.DECRYPT_MODE, packetKey, iv1); - byte[] expectedPacket1 = packetCipher.doFinal(packet1); - assertEquals(packet1.length, expectedPacket1.length); - // Check that the PacketDecrypter gets the same results - byte[] ciphertext = new byte[tag.length + packet.length - + tag1.length + packet1.length]; - System.arraycopy(tag, 0, ciphertext, 0, tag.length); - System.arraycopy(packet, 0, ciphertext, tag.length, packet.length); - System.arraycopy(tag1, 0, ciphertext, tag.length + packet.length, - tag1.length); - System.arraycopy(packet1, 0, ciphertext, - tag.length + packet.length + tag1.length, packet1.length); - ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); - PacketDecrypter p = new PacketDecrypterImpl(tag, in, tagCipher, - packetCipher, tagKey, packetKey); - // First tag - assertTrue(Arrays.equals(expectedTag, p.readTag())); - // First packet - byte[] actualPacket = new byte[packet.length]; - int offset = 0; - while(offset < actualPacket.length) { - int read = p.getInputStream().read(actualPacket, offset, - actualPacket.length - offset); - if(read == -1) break; - offset += read; - } - assertEquals(actualPacket.length, offset); - assertTrue(Arrays.equals(expectedPacket, actualPacket)); - // Second tag - assertTrue(Arrays.equals(expectedTag1, p.readTag())); - // Second packet - byte[] actualPacket1 = new byte[packet1.length]; - offset = 0; - while(offset < actualPacket1.length) { - int read = p.getInputStream().read(actualPacket1, offset, - actualPacket1.length - offset); - if(read == -1) break; - offset += read; - } - assertEquals(actualPacket1.length, offset); - assertTrue(Arrays.equals(expectedPacket1, actualPacket1)); - } -} diff --git a/test/net/sf/briar/transport/PacketEncrypterImplTest.java b/test/net/sf/briar/transport/PacketEncrypterImplTest.java deleted file mode 100644 index 06ea1b66a910fc7e4f2a63cf2b1895b1a35abbf4..0000000000000000000000000000000000000000 --- a/test/net/sf/briar/transport/PacketEncrypterImplTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package net.sf.briar.transport; - -import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; - -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[TAG_LENGTH]); - p.getOutputStream().write((byte) 0); - p.finishPacket(); - assertEquals(TAG_LENGTH + 1, out.toByteArray().length); - } - - @Test - public void testEncryption() throws Exception { - byte[] tag = new byte[TAG_LENGTH]; - 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(TAG_LENGTH + packet.length, ciphertext.length); - // Check the tag - byte[] actualTag = new byte[TAG_LENGTH]; - System.arraycopy(ciphertext, 0, actualTag, 0, TAG_LENGTH); - assertTrue(Arrays.equals(expectedTag, actualTag)); - // Check the packet - byte[] actualPacket = new byte[packet.length]; - System.arraycopy(ciphertext, TAG_LENGTH, actualPacket, 0, - actualPacket.length); - assertTrue(Arrays.equals(expectedPacket, actualPacket)); - } -} diff --git a/test/net/sf/briar/transport/PacketReaderImplTest.java b/test/net/sf/briar/transport/PacketReaderImplTest.java deleted file mode 100644 index d5e34c89c9c4e69dc96a210bc1997c5813cfe8e1..0000000000000000000000000000000000000000 --- a/test/net/sf/briar/transport/PacketReaderImplTest.java +++ /dev/null @@ -1,189 +0,0 @@ -package net.sf.briar.transport; - -import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; - -import java.io.ByteArrayInputStream; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; -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.PacketReader; -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 PacketReaderImplTest extends TestCase { - - private final Mac mac; - - public PacketReaderImplTest() 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 testFirstReadTriggersTag() throws Exception { - // TAG_BYTES for the tag, 1 byte for the packet - byte[] b = new byte[TAG_LENGTH + 1]; - ByteArrayInputStream in = new ByteArrayInputStream(b); - PacketDecrypter d = new NullPacketDecrypter(in); - PacketReader p = new PacketReaderImpl(d, mac, 0, 0L); - // There should be one byte available before EOF - assertEquals(0, p.getInputStream().read()); - assertEquals(-1, p.getInputStream().read()); - } - - @Test - public void testFinishPacketAfterReadTriggersMac() throws Exception { - // TAG_BYTES for the tag, 1 byte for the packet - byte[] b = new byte[TAG_LENGTH + 1]; - // Calculate the MAC and append it to the packet - mac.update(b); - byte[] macBytes = mac.doFinal(); - byte[] b1 = Arrays.copyOf(b, b.length + macBytes.length); - System.arraycopy(macBytes, 0, b1, b.length, macBytes.length); - // Check that the PacketReader reads and verifies the MAC - ByteArrayInputStream in = new ByteArrayInputStream(b1); - PacketDecrypter d = new NullPacketDecrypter(in); - PacketReader p = new PacketReaderImpl(d, mac, 0, 0L); - assertEquals(0, p.getInputStream().read()); - p.finishPacket(); - // Reading the MAC should take us to EOF - assertEquals(-1, p.getInputStream().read()); - } - - @Test - public void testModifyingPacketInvalidatesMac() throws Exception { - // TAG_BYTES for the tag, 1 byte for the packet - byte[] b = new byte[TAG_LENGTH + 1]; - // Calculate the MAC and append it to the packet - mac.update(b); - byte[] macBytes = mac.doFinal(); - byte[] b1 = Arrays.copyOf(b, b.length + macBytes.length); - System.arraycopy(macBytes, 0, b1, b.length, macBytes.length); - // Modify the packet - b1[TAG_LENGTH] = (byte) 1; - // Check that the PacketReader reads and fails to verify the MAC - ByteArrayInputStream in = new ByteArrayInputStream(b1); - PacketDecrypter d = new NullPacketDecrypter(in); - PacketReader p = new PacketReaderImpl(d, mac, 0, 0L); - assertEquals(1, p.getInputStream().read()); - try { - p.finishPacket(); - fail(); - } catch(GeneralSecurityException expected) {} - } - - @Test - public void testExtraCallsToFinishPacketDoNothing() throws Exception { - // TAG_BYTES for the tag, 1 byte for the packet - byte[] b = new byte[TAG_LENGTH + 1]; - // Calculate the MAC and append it to the packet - mac.update(b); - byte[] macBytes = mac.doFinal(); - byte[] b1 = Arrays.copyOf(b, b.length + macBytes.length); - System.arraycopy(macBytes, 0, b1, b.length, macBytes.length); - // Check that the PacketReader reads and verifies the MAC - ByteArrayInputStream in = new ByteArrayInputStream(b1); - PacketDecrypter d = new NullPacketDecrypter(in); - PacketReader p = new PacketReaderImpl(d, mac, 0, 0L); - // Initial calls to finishPacket() should have no effect - p.finishPacket(); - p.finishPacket(); - p.finishPacket(); - assertEquals(0, p.getInputStream().read()); - p.finishPacket(); - // Extra calls to finishPacket() should have no effect - p.finishPacket(); - p.finishPacket(); - p.finishPacket(); - // Reading the MAC should take us to EOF - assertEquals(-1, p.getInputStream().read()); - } - - @Test - public void testPacketNumberIsIncremented() throws Exception { - byte[] tag = 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 - ); - assertEquals(TAG_LENGTH, tag.length); - byte[] tag1 = 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 - ); - assertEquals(TAG_LENGTH, tag1.length); - // Calculate the MAC on the first packet and append it to the packet - mac.update(tag); - mac.update((byte) 0); - byte[] macBytes = mac.doFinal(); - byte[] b = Arrays.copyOf(tag, tag.length + 1 + macBytes.length); - System.arraycopy(macBytes, 0, b, tag.length + 1, macBytes.length); - // Calculate the MAC on the second packet and append it to the packet - mac.update(tag1); - mac.update((byte) 0); - byte[] macBytes1 = mac.doFinal(); - byte[] b1 = Arrays.copyOf(tag1, tag1.length + 1 + macBytes1.length); - System.arraycopy(macBytes1, 0, b1, tag.length + 1, macBytes1.length); - // Check that the PacketReader accepts the correct tags and MACs - byte[] b2 = Arrays.copyOf(b, b.length + b1.length); - System.arraycopy(b1, 0, b2, b.length, b1.length); - ByteArrayInputStream in = new ByteArrayInputStream(b2); - PacketDecrypter d = new NullPacketDecrypter(in); - PacketReader p = new PacketReaderImpl(d, mac, 0xF00D, 0xDEADBEEFL); - // Packet one - assertEquals(0, p.getInputStream().read()); - p.finishPacket(); - // Packet two - assertEquals(0, p.getInputStream().read()); - p.finishPacket(); - // We should be at EOF - assertEquals(-1, p.getInputStream().read()); - } - - /** A PacketDecrypter that performs no decryption. */ - private static class NullPacketDecrypter implements PacketDecrypter { - - private final InputStream in; - - private NullPacketDecrypter(InputStream in) { - this.in = in; - } - - public InputStream getInputStream() { - return in; - } - - public byte[] readTag() throws IOException { - byte[] tag = new byte[TAG_LENGTH]; - int offset = 0; - while(offset < tag.length) { - int read = in.read(tag, offset, tag.length - offset); - if(read == -1) break; - offset += read; - } - if(offset == 0) return null; // EOF between packets is acceptable - if(offset < tag.length) throw new EOFException(); - return tag; - } - } -} diff --git a/test/net/sf/briar/transport/PacketWriterImplTest.java b/test/net/sf/briar/transport/PacketWriterImplTest.java deleted file mode 100644 index f99c51e5be46bd487775720bee594667d54c9295..0000000000000000000000000000000000000000 --- a/test/net/sf/briar/transport/PacketWriterImplTest.java +++ /dev/null @@ -1,174 +0,0 @@ -package net.sf.briar.transport; - -import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -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(); - PacketEncrypter e = new NullPacketEncrypter(out); - PacketWriter p = new PacketWriterImpl(e, mac, 0, 0L); - p.getOutputStream().write(0); - // There should be TAG_BYTES bytes for the tag, 1 byte for the packet - assertTrue(Arrays.equals(new byte[TAG_LENGTH + 1], - out.toByteArray())); - } - - @Test - public void testFinishPacketAfterWriteTriggersMac() throws Exception { - // Calculate what the MAC should be - mac.update(new byte[TAG_LENGTH + 1]); - byte[] expectedMac = mac.doFinal(); - // Check that the PacketWriter calculates and writes the correct MAC - ByteArrayOutputStream out = new ByteArrayOutputStream(); - PacketEncrypter e = new NullPacketEncrypter(out); - PacketWriter p = new PacketWriterImpl(e, mac, 0, 0L); - p.getOutputStream().write(0); - p.finishPacket(); - byte[] written = out.toByteArray(); - assertEquals(TAG_LENGTH + 1 + expectedMac.length, - written.length); - byte[] actualMac = new byte[expectedMac.length]; - System.arraycopy(written, TAG_LENGTH + 1, actualMac, 0, - actualMac.length); - assertTrue(Arrays.equals(expectedMac, actualMac)); - } - - @Test - public void testExtraCallsToFinishPacketDoNothing() throws Exception { - // Calculate what the MAC should be - mac.update(new byte[TAG_LENGTH + 1]); - byte[] expectedMac = mac.doFinal(); - // Check that the PacketWriter calculates and writes the correct MAC - ByteArrayOutputStream out = new ByteArrayOutputStream(); - PacketEncrypter e = new NullPacketEncrypter(out); - PacketWriter p = new PacketWriterImpl(e, mac, 0, 0L); - // Initial calls to finishPacket() should have no effect - p.finishPacket(); - p.finishPacket(); - p.finishPacket(); - p.getOutputStream().write(0); - p.finishPacket(); - // Extra calls to finishPacket() should have no effect - p.finishPacket(); - p.finishPacket(); - p.finishPacket(); - byte[] written = out.toByteArray(); - assertEquals(TAG_LENGTH + 1 + expectedMac.length, - written.length); - byte[] actualMac = new byte[expectedMac.length]; - System.arraycopy(written, TAG_LENGTH + 1, 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 - ); - assertEquals(TAG_LENGTH, expectedTag.length); - 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 - ); - assertEquals(TAG_LENGTH, expectedTag1.length); - // 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(); - PacketEncrypter e = new NullPacketEncrypter(out); - PacketWriter p = new PacketWriterImpl(e, mac, 0xF00D, 0xDEADBEEFL); - // Packet one - p.getOutputStream().write(0); - p.finishPacket(); - // Packet two - p.getOutputStream().write(0); - p.finishPacket(); - byte[] written = out.toByteArray(); - assertEquals(TAG_LENGTH + 1 + expectedMac.length - + TAG_LENGTH + 1 + expectedMac1.length, - written.length); - // Check the first packet's tag - byte[] actualTag = new byte[TAG_LENGTH]; - System.arraycopy(written, 0, actualTag, 0, TAG_LENGTH); - assertTrue(Arrays.equals(expectedTag, actualTag)); - // Check the first packet's MAC - byte[] actualMac = new byte[expectedMac.length]; - System.arraycopy(written, TAG_LENGTH + 1, actualMac, 0, - actualMac.length); - assertTrue(Arrays.equals(expectedMac, actualMac)); - // Check the second packet's tag - byte[] actualTag1 = new byte[TAG_LENGTH]; - System.arraycopy(written, TAG_LENGTH + 1 + expectedMac.length, - actualTag1, 0, TAG_LENGTH); - assertTrue(Arrays.equals(expectedTag1, actualTag1)); - // Check the second packet's MAC - byte[] actualMac1 = new byte[expectedMac1.length]; - System.arraycopy(written, TAG_LENGTH + 1 + expectedMac.length - + TAG_LENGTH + 1, actualMac1, 0, actualMac1.length); - assertTrue(Arrays.equals(expectedMac1, actualMac1)); - } - - /** A PacketEncrypter that performs no encryption. */ - 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() {} - } -} diff --git a/test/net/sf/briar/transport/TagDecoderTest.java b/test/net/sf/briar/transport/TagDecoderTest.java deleted file mode 100644 index d99e0d480cc6f8c93e31f80eb18b0b6880b3fec0..0000000000000000000000000000000000000000 --- a/test/net/sf/briar/transport/TagDecoderTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.sf.briar.transport; - -import junit.framework.TestCase; - -import net.sf.briar.util.StringUtils; - -import org.junit.Test; - -public class TagDecoderTest extends TestCase { - - @Test - public void testReadUint16() { - byte[] b = StringUtils.fromHexString("000000"); - assertEquals(0, TagDecoder.readUint16(b, 1)); - b = StringUtils.fromHexString("000001"); - assertEquals(1, TagDecoder.readUint16(b, 1)); - b = StringUtils.fromHexString("00FFFF"); - assertEquals(65535, TagDecoder.readUint16(b, 1)); - } - - @Test - public void testReadUint32() { - byte[] b = StringUtils.fromHexString("0000000000"); - assertEquals(0L, TagDecoder.readUint32(b, 1)); - b = StringUtils.fromHexString("0000000001"); - assertEquals(1L, TagDecoder.readUint32(b, 1)); - b = StringUtils.fromHexString("00FFFFFFFF"); - assertEquals(4294967295L, TagDecoder.readUint32(b, 1)); - } -} diff --git a/test/net/sf/briar/transport/TagEncoderTest.java b/test/net/sf/briar/transport/TagEncoderTest.java deleted file mode 100644 index c521b052367e862350468e3a4b769034f6588b05..0000000000000000000000000000000000000000 --- a/test/net/sf/briar/transport/TagEncoderTest.java +++ /dev/null @@ -1,32 +0,0 @@ -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() { - 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() { - 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)); - } -} diff --git a/test/net/sf/briar/util/ByteUtilsTest.java b/test/net/sf/briar/util/ByteUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3b850e8b197199b490810b75efbd7216be6348dc --- /dev/null +++ b/test/net/sf/briar/util/ByteUtilsTest.java @@ -0,0 +1,51 @@ +package net.sf.briar.util; + +import junit.framework.TestCase; + +import org.junit.Test; + +public class ByteUtilsTest extends TestCase { + + @Test + public void testReadUint16() { + byte[] b = StringUtils.fromHexString("000000"); + assertEquals(0, ByteUtils.readUint16(b, 1)); + b = StringUtils.fromHexString("000001"); + assertEquals(1, ByteUtils.readUint16(b, 1)); + b = StringUtils.fromHexString("00FFFF"); + assertEquals(65535, ByteUtils.readUint16(b, 1)); + } + + @Test + public void testReadUint32() { + byte[] b = StringUtils.fromHexString("0000000000"); + assertEquals(0L, ByteUtils.readUint32(b, 1)); + b = StringUtils.fromHexString("0000000001"); + assertEquals(1L, ByteUtils.readUint32(b, 1)); + b = StringUtils.fromHexString("00FFFFFFFF"); + assertEquals(4294967295L, ByteUtils.readUint32(b, 1)); + } + + + @Test + public void testWriteUint16() { + byte[] b = new byte[3]; + ByteUtils.writeUint16(0, b, 1); + assertEquals("000000", StringUtils.toHexString(b)); + ByteUtils.writeUint16(1, b, 1); + assertEquals("000001", StringUtils.toHexString(b)); + ByteUtils.writeUint16(65535, b, 1); + assertEquals("00FFFF", StringUtils.toHexString(b)); + } + + @Test + public void testWriteUint32() { + byte[] b = new byte[5]; + ByteUtils.writeUint32(0L, b, 1); + assertEquals("0000000000", StringUtils.toHexString(b)); + ByteUtils.writeUint32(1L, b, 1); + assertEquals("0000000001", StringUtils.toHexString(b)); + ByteUtils.writeUint32(4294967295L, b, 1); + assertEquals("00FFFFFFFF", StringUtils.toHexString(b)); + } +} diff --git a/util/net/sf/briar/util/ByteUtils.java b/util/net/sf/briar/util/ByteUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..2066e820a19e6ebe8a789767e9d24fb6a8464505 --- /dev/null +++ b/util/net/sf/briar/util/ByteUtils.java @@ -0,0 +1,43 @@ +package net.sf.briar.util; + +public class ByteUtils { + + /** + * The maximum value that can be represented as an unsigned 16-bit integer. + */ + public static final int MAX_16_BIT_UNSIGNED = 65535; // 2^16 - 1 + + /** + * The maximum value that can be represented as an unsigned 32-bit integer. + */ + public static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1 + + public static void writeUint16(int i, byte[] b, int offset) { + if(i < 0) throw new IllegalArgumentException(); + if(i > MAX_16_BIT_UNSIGNED) throw new IllegalArgumentException(); + assert b.length >= offset + 2; + b[offset] = (byte) (i >> 8); + b[offset + 1] = (byte) (i & 0xFF); + } + + public static void writeUint32(long i, byte[] b, int offset) { + if(i < 0L) throw new IllegalArgumentException(); + if(i > MAX_32_BIT_UNSIGNED) throw new IllegalArgumentException(); + 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); + } + + public static int readUint16(byte[] b, int offset) { + if(b.length < offset + 2) throw new IllegalArgumentException(); + return ((b[offset] & 0xFF) << 8) | (b[offset + 1] & 0xFF); + } + + public static long readUint32(byte[] b, int offset) { + if(b.length < offset + 4) throw new IllegalArgumentException(); + return ((b[offset] & 0xFFL) << 24) | ((b[offset + 1] & 0xFFL) << 16) + | ((b[offset + 2] & 0xFFL) << 8) | (b[offset + 3] & 0xFFL); + } +}