mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Frame the encrypted data independently of inter-packet boundaries and
authenticate each frame before parsing its contents. Each connection starts with a tag, followed by any number of frames, each starting with the frame number (32 bits) and payload length (16 bits), and ending with a MAC (256 bits). Tags have the following format: 32 bits reserved, 16 bits for the transport ID, 32 bits for the connection number, 32 bits (set to zero in the tag) for the frame number, and 16 bits (set to zero in the tag) for the block number. The tag is encrypted with the tag key in ECB mode. Frame numbers for each connection must start from zero and must be contiguous and strictly increasing. Each frame is encrypted with the frame key in CTR mode, using the plaintext tag with the appropriate frame number to initialise the counter. The maximum frame size is 64 KiB, including header and footer. The maximum amount of data that can be sent over a connection is 2^32 frames - roughly 2^48 bytes, or 8 terabytes, with the maximum frame size of 64 KiB. If that isn't sufficient we can add another 16 bits to the frame counter.
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
14
components/net/sf/briar/transport/ConnectionDecrypter.java
Normal file
14
components/net/sf/briar/transport/ConnectionDecrypter.java
Normal file
@@ -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;
|
||||
}
|
||||
148
components/net/sf/briar/transport/ConnectionDecrypterImpl.java
Normal file
148
components/net/sf/briar/transport/ConnectionDecrypterImpl.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
14
components/net/sf/briar/transport/ConnectionEncrypter.java
Normal file
14
components/net/sf/briar/transport/ConnectionEncrypter.java
Normal file
@@ -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;
|
||||
}
|
||||
117
components/net/sf/briar/transport/ConnectionEncrypterImpl.java
Normal file
117
components/net/sf/briar/transport/ConnectionEncrypterImpl.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
107
components/net/sf/briar/transport/ConnectionReaderImpl.java
Normal file
107
components/net/sf/briar/transport/ConnectionReaderImpl.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
84
components/net/sf/briar/transport/ConnectionWriterImpl.java
Normal file
84
components/net/sf/briar/transport/ConnectionWriterImpl.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user