mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 03:09:04 +01:00
This ensures the frame number is covered by the MAC, cleanly separating encryption from authentication (previously we depended on the encryption layer to garble frames if they were reordered).
130 lines
3.8 KiB
Java
130 lines
3.8 KiB
Java
package net.sf.briar.transport;
|
|
|
|
import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
|
|
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.security.InvalidKeyException;
|
|
import java.util.Arrays;
|
|
|
|
import javax.crypto.Mac;
|
|
|
|
import net.sf.briar.api.FormatException;
|
|
import net.sf.briar.api.crypto.ErasableKey;
|
|
import net.sf.briar.api.transport.ConnectionReader;
|
|
|
|
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,
|
|
ErasableKey macKey) {
|
|
super(decrypter.getInputStream());
|
|
this.decrypter = decrypter;
|
|
this.mac = mac;
|
|
// Initialise the MAC
|
|
try {
|
|
mac.init(macKey);
|
|
} catch(InvalidKeyException e) {
|
|
throw new IllegalArgumentException(e);
|
|
}
|
|
macKey.erase();
|
|
maxPayloadLength =
|
|
MAX_FRAME_LENGTH - FRAME_HEADER_LENGTH - mac.getMacLength();
|
|
header = new byte[FRAME_HEADER_LENGTH];
|
|
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;
|
|
// Don't allow more than 2^32 frames to be read
|
|
if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException();
|
|
// Read the header
|
|
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
|
|
// Check that the frame number is correct and the length is legal
|
|
if(!HeaderEncoder.validateHeader(header, frame, maxPayloadLength))
|
|
throw new FormatException();
|
|
payloadLen = HeaderEncoder.getPayloadLength(header);
|
|
int paddingLen = HeaderEncoder.getPaddingLength(header);
|
|
mac.update(header);
|
|
// 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 padding
|
|
while(offset < payloadLen + paddingLen) {
|
|
int read = in.read(payload, offset,
|
|
payloadLen + paddingLen - offset);
|
|
if(read == -1) throw new EOFException(); // Unexpected EOF
|
|
mac.update(payload, offset, read);
|
|
offset += read;
|
|
}
|
|
// Check that the padding is all zeroes
|
|
for(int i = payloadLen; i < payloadLen + paddingLen; i++) {
|
|
if(payload[i] != 0) throw new FormatException();
|
|
}
|
|
// Read the MAC
|
|
byte[] expectedMac = mac.doFinal();
|
|
decrypter.readMac(footer);
|
|
if(!Arrays.equals(expectedMac, footer)) throw new FormatException();
|
|
betweenFrames = false;
|
|
frame++;
|
|
return true;
|
|
}
|
|
}
|