mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
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.
117 lines
3.4 KiB
Java
117 lines
3.4 KiB
Java
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;
|
|
}
|
|
} |