mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-14 19:59:05 +01:00
Implement stream header for BTPv2. #111
This commit is contained in:
@@ -1,27 +1,28 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.FRAME_IV_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
class FrameEncoder {
|
||||
|
||||
static void encodeIv(byte[] iv, long frameNumber, boolean header) {
|
||||
if (iv.length < IV_LENGTH) throw new IllegalArgumentException();
|
||||
if (iv.length < FRAME_IV_LENGTH) throw new IllegalArgumentException();
|
||||
if (frameNumber < 0 || frameNumber > MAX_32_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
ByteUtils.writeUint32(frameNumber, iv, 0);
|
||||
if (header) iv[4] = 1;
|
||||
else iv[4] = 0;
|
||||
for (int i = 5; i < IV_LENGTH; i++) iv[i] = 0;
|
||||
for (int i = 5; i < FRAME_IV_LENGTH; i++) iv[i] = 0;
|
||||
}
|
||||
|
||||
static void encodeHeader(byte[] header, boolean finalFrame,
|
||||
int payloadLength, int paddingLength) {
|
||||
if (header.length < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
if (header.length < FRAME_HEADER_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
if (payloadLength < 0) throw new IllegalArgumentException();
|
||||
if (paddingLength < 0) throw new IllegalArgumentException();
|
||||
if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
|
||||
@@ -32,17 +33,20 @@ class FrameEncoder {
|
||||
}
|
||||
|
||||
static boolean isFinalFrame(byte[] header) {
|
||||
if (header.length < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
if (header.length < FRAME_HEADER_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
return (header[0] & 0x80) == 0x80;
|
||||
}
|
||||
|
||||
static int getPayloadLength(byte[] header) {
|
||||
if (header.length < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
if (header.length < FRAME_HEADER_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
return ByteUtils.readUint16(header, 0) & 0x7FFF;
|
||||
}
|
||||
|
||||
static int getPaddingLength(byte[] header) {
|
||||
if (header.length < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
if (header.length < FRAME_HEADER_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
return ByteUtils.readUint16(header, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,31 +9,34 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.FRAME_IV_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
|
||||
|
||||
// FIXME: Implementation is incomplete, doesn't read the stream header
|
||||
class StreamDecrypterImpl implements StreamDecrypter {
|
||||
|
||||
private final InputStream in;
|
||||
private final AuthenticatedCipher frameCipher;
|
||||
private final SecretKey frameKey;
|
||||
private final byte[] iv, frameHeader, frameCiphertext;
|
||||
private final AuthenticatedCipher cipher;
|
||||
private final SecretKey streamHeaderKey;
|
||||
private final byte[] frameIv, frameHeader, frameCiphertext;
|
||||
|
||||
private SecretKey frameKey;
|
||||
private long frameNumber;
|
||||
private boolean finalFrame;
|
||||
|
||||
StreamDecrypterImpl(InputStream in, AuthenticatedCipher frameCipher,
|
||||
SecretKey headerKey) {
|
||||
StreamDecrypterImpl(InputStream in, AuthenticatedCipher cipher,
|
||||
SecretKey streamHeaderKey) {
|
||||
this.in = in;
|
||||
this.frameCipher = frameCipher;
|
||||
this.frameKey = headerKey; // FIXME
|
||||
iv = new byte[IV_LENGTH];
|
||||
frameHeader = new byte[HEADER_LENGTH];
|
||||
this.cipher = cipher;
|
||||
this.streamHeaderKey = streamHeaderKey;
|
||||
frameIv = new byte[FRAME_IV_LENGTH];
|
||||
frameHeader = new byte[FRAME_HEADER_LENGTH];
|
||||
frameCiphertext = new byte[MAX_FRAME_LENGTH];
|
||||
frameKey = null;
|
||||
frameNumber = 0;
|
||||
finalFrame = false;
|
||||
}
|
||||
@@ -43,20 +46,23 @@ class StreamDecrypterImpl implements StreamDecrypter {
|
||||
if (payload.length < MAX_PAYLOAD_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
if (finalFrame) return -1;
|
||||
// Read the stream header if required
|
||||
if (frameKey == null) readStreamHeader();
|
||||
// Read the frame header
|
||||
int offset = 0;
|
||||
while (offset < HEADER_LENGTH) {
|
||||
int read = in.read(frameCiphertext, offset, HEADER_LENGTH - offset);
|
||||
while (offset < FRAME_HEADER_LENGTH) {
|
||||
int read = in.read(frameCiphertext, offset,
|
||||
FRAME_HEADER_LENGTH - offset);
|
||||
if (read == -1) throw new EOFException();
|
||||
offset += read;
|
||||
}
|
||||
// Decrypt and authenticate the frame header
|
||||
FrameEncoder.encodeIv(iv, frameNumber, true);
|
||||
FrameEncoder.encodeIv(frameIv, frameNumber, true);
|
||||
try {
|
||||
frameCipher.init(false, frameKey, iv);
|
||||
int decrypted = frameCipher.process(frameCiphertext, 0,
|
||||
HEADER_LENGTH, frameHeader, 0);
|
||||
if (decrypted != HEADER_LENGTH - MAC_LENGTH)
|
||||
cipher.init(false, frameKey, frameIv);
|
||||
int decrypted = cipher.process(frameCiphertext, 0,
|
||||
FRAME_HEADER_LENGTH, frameHeader, 0);
|
||||
if (decrypted != FRAME_HEADER_LENGTH - MAC_LENGTH)
|
||||
throw new RuntimeException();
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new FormatException();
|
||||
@@ -68,7 +74,7 @@ class StreamDecrypterImpl implements StreamDecrypter {
|
||||
if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
|
||||
throw new FormatException();
|
||||
// Read the payload and padding
|
||||
int frameLength = HEADER_LENGTH + payloadLength + paddingLength
|
||||
int frameLength = FRAME_HEADER_LENGTH + payloadLength + paddingLength
|
||||
+ MAC_LENGTH;
|
||||
while (offset < frameLength) {
|
||||
int read = in.read(frameCiphertext, offset, frameLength - offset);
|
||||
@@ -76,10 +82,10 @@ class StreamDecrypterImpl implements StreamDecrypter {
|
||||
offset += read;
|
||||
}
|
||||
// Decrypt and authenticate the payload and padding
|
||||
FrameEncoder.encodeIv(iv, frameNumber, false);
|
||||
FrameEncoder.encodeIv(frameIv, frameNumber, false);
|
||||
try {
|
||||
frameCipher.init(false, frameKey, iv);
|
||||
int decrypted = frameCipher.process(frameCiphertext, HEADER_LENGTH,
|
||||
cipher.init(false, frameKey, frameIv);
|
||||
int decrypted = cipher.process(frameCiphertext, FRAME_HEADER_LENGTH,
|
||||
payloadLength + paddingLength + MAC_LENGTH, payload, 0);
|
||||
if (decrypted != payloadLength + paddingLength)
|
||||
throw new RuntimeException();
|
||||
@@ -92,4 +98,31 @@ class StreamDecrypterImpl implements StreamDecrypter {
|
||||
frameNumber++;
|
||||
return payloadLength;
|
||||
}
|
||||
|
||||
private void readStreamHeader() throws IOException {
|
||||
byte[] streamHeaderIv = new byte[STREAM_HEADER_IV_LENGTH];
|
||||
byte[] streamHeaderCiphertext = new byte[STREAM_HEADER_LENGTH];
|
||||
byte[] streamHeaderPlaintext = new byte[SecretKey.LENGTH];
|
||||
// Read the stream header
|
||||
int offset = 0;
|
||||
while (offset < STREAM_HEADER_LENGTH) {
|
||||
int read = in.read(streamHeaderCiphertext, offset,
|
||||
STREAM_HEADER_LENGTH - offset);
|
||||
if (read == -1) throw new EOFException();
|
||||
offset += read;
|
||||
}
|
||||
// Decrypt and authenticate the stream header
|
||||
System.arraycopy(streamHeaderCiphertext, 0, streamHeaderIv, 0,
|
||||
STREAM_HEADER_IV_LENGTH);
|
||||
try {
|
||||
cipher.init(false, streamHeaderKey, streamHeaderIv);
|
||||
int decrypted = cipher.process(streamHeaderCiphertext,
|
||||
STREAM_HEADER_IV_LENGTH, SecretKey.LENGTH + MAC_LENGTH,
|
||||
streamHeaderPlaintext, 0);
|
||||
if (decrypted != SecretKey.LENGTH) throw new RuntimeException();
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new FormatException();
|
||||
}
|
||||
frameKey = new SecretKey(streamHeaderPlaintext);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,19 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.api.crypto.StreamEncrypter;
|
||||
import org.briarproject.api.crypto.StreamEncrypterFactory;
|
||||
import org.briarproject.api.transport.StreamContext;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
||||
|
||||
class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
@@ -27,15 +28,23 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
||||
|
||||
public StreamEncrypter createStreamEncrypter(OutputStream out,
|
||||
StreamContext ctx) {
|
||||
AuthenticatedCipher cipher = cipherProvider.get();
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
crypto.encodeTag(tag, ctx.getTagKey(), ctx.getStreamNumber());
|
||||
AuthenticatedCipher cipher = cipherProvider.get();
|
||||
return new StreamEncrypterImpl(out, cipher, ctx.getHeaderKey(), tag);
|
||||
byte[] streamHeaderIv = new byte[STREAM_HEADER_IV_LENGTH];
|
||||
crypto.getSecureRandom().nextBytes(streamHeaderIv);
|
||||
SecretKey frameKey = crypto.generateSecretKey();
|
||||
return new StreamEncrypterImpl(out, cipher, tag, streamHeaderIv,
|
||||
ctx.getHeaderKey(), frameKey);
|
||||
}
|
||||
|
||||
public StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
|
||||
SecretKey headerKey) {
|
||||
AuthenticatedCipher cipher = cipherProvider.get();
|
||||
return new StreamEncrypterImpl(out, cipher, headerKey, null);
|
||||
byte[] streamHeaderIv = new byte[STREAM_HEADER_IV_LENGTH];
|
||||
crypto.getSecureRandom().nextBytes(streamHeaderIv);
|
||||
SecretKey frameKey = crypto.generateSecretKey();
|
||||
return new StreamEncrypterImpl(out, cipher, null, streamHeaderIv,
|
||||
headerKey, frameKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,35 +7,41 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.FRAME_IV_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
|
||||
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||
|
||||
// FIXME: Implementation is incomplete, doesn't write the stream header
|
||||
class StreamEncrypterImpl implements StreamEncrypter {
|
||||
|
||||
private final OutputStream out;
|
||||
private final AuthenticatedCipher frameCipher;
|
||||
private final SecretKey frameKey;
|
||||
private final byte[] tag, iv, framePlaintext, frameCiphertext;
|
||||
private final AuthenticatedCipher cipher;
|
||||
private final SecretKey streamHeaderKey, frameKey;
|
||||
private final byte[] tag, streamHeaderIv;
|
||||
private final byte[] frameIv, framePlaintext, frameCiphertext;
|
||||
|
||||
private long frameNumber;
|
||||
private boolean writeTag;
|
||||
private boolean writeTag, writeStreamHeader;
|
||||
|
||||
StreamEncrypterImpl(OutputStream out, AuthenticatedCipher frameCipher,
|
||||
SecretKey headerKey, byte[] tag) {
|
||||
StreamEncrypterImpl(OutputStream out, AuthenticatedCipher cipher,
|
||||
byte[] tag, byte[] streamHeaderIv, SecretKey streamHeaderKey,
|
||||
SecretKey frameKey) {
|
||||
this.out = out;
|
||||
this.frameCipher = frameCipher;
|
||||
this.frameKey = headerKey; // FIXME
|
||||
this.cipher = cipher;
|
||||
this.tag = tag;
|
||||
iv = new byte[IV_LENGTH];
|
||||
framePlaintext = new byte[HEADER_LENGTH + MAX_PAYLOAD_LENGTH];
|
||||
this.streamHeaderIv = streamHeaderIv;
|
||||
this.streamHeaderKey = streamHeaderKey;
|
||||
this.frameKey = frameKey;
|
||||
frameIv = new byte[FRAME_IV_LENGTH];
|
||||
framePlaintext = new byte[FRAME_HEADER_LENGTH + MAX_PAYLOAD_LENGTH];
|
||||
frameCiphertext = new byte[MAX_FRAME_LENGTH];
|
||||
frameNumber = 0;
|
||||
writeTag = (tag != null);
|
||||
writeStreamHeader = true;
|
||||
}
|
||||
|
||||
public void writeFrame(byte[] payload, int payloadLength,
|
||||
@@ -45,52 +51,75 @@ class StreamEncrypterImpl implements StreamEncrypter {
|
||||
// Don't allow the frame counter to wrap
|
||||
if (frameNumber > MAX_32_BIT_UNSIGNED) throw new IOException();
|
||||
// Write the tag if required
|
||||
if (writeTag) {
|
||||
out.write(tag, 0, tag.length);
|
||||
writeTag = false;
|
||||
}
|
||||
if (writeTag) writeTag();
|
||||
// Write the stream header if required
|
||||
if (writeStreamHeader) writeStreamHeader();
|
||||
// Encode the frame header
|
||||
FrameEncoder.encodeHeader(framePlaintext, finalFrame, payloadLength,
|
||||
paddingLength);
|
||||
// Encrypt and authenticate the frame header
|
||||
FrameEncoder.encodeIv(iv, frameNumber, true);
|
||||
FrameEncoder.encodeIv(frameIv, frameNumber, true);
|
||||
try {
|
||||
frameCipher.init(true, frameKey, iv);
|
||||
int encrypted = frameCipher.process(framePlaintext, 0,
|
||||
HEADER_LENGTH - MAC_LENGTH, frameCiphertext, 0);
|
||||
if (encrypted != HEADER_LENGTH) throw new RuntimeException();
|
||||
cipher.init(true, frameKey, frameIv);
|
||||
int encrypted = cipher.process(framePlaintext, 0,
|
||||
FRAME_HEADER_LENGTH - MAC_LENGTH, frameCiphertext, 0);
|
||||
if (encrypted != FRAME_HEADER_LENGTH) throw new RuntimeException();
|
||||
} catch (GeneralSecurityException badCipher) {
|
||||
throw new RuntimeException(badCipher);
|
||||
}
|
||||
// Combine the payload and padding
|
||||
System.arraycopy(payload, 0, framePlaintext, HEADER_LENGTH,
|
||||
System.arraycopy(payload, 0, framePlaintext, FRAME_HEADER_LENGTH,
|
||||
payloadLength);
|
||||
for (int i = 0; i < paddingLength; i++)
|
||||
framePlaintext[HEADER_LENGTH + payloadLength + i] = 0;
|
||||
framePlaintext[FRAME_HEADER_LENGTH + payloadLength + i] = 0;
|
||||
// Encrypt and authenticate the payload and padding
|
||||
FrameEncoder.encodeIv(iv, frameNumber, false);
|
||||
FrameEncoder.encodeIv(frameIv, frameNumber, false);
|
||||
try {
|
||||
frameCipher.init(true, frameKey, iv);
|
||||
int encrypted = frameCipher.process(framePlaintext, HEADER_LENGTH,
|
||||
cipher.init(true, frameKey, frameIv);
|
||||
int encrypted = cipher.process(framePlaintext, FRAME_HEADER_LENGTH,
|
||||
payloadLength + paddingLength, frameCiphertext,
|
||||
HEADER_LENGTH);
|
||||
FRAME_HEADER_LENGTH);
|
||||
if (encrypted != payloadLength + paddingLength + MAC_LENGTH)
|
||||
throw new RuntimeException();
|
||||
} catch (GeneralSecurityException badCipher) {
|
||||
throw new RuntimeException(badCipher);
|
||||
}
|
||||
// Write the frame
|
||||
out.write(frameCiphertext, 0, HEADER_LENGTH + payloadLength
|
||||
out.write(frameCiphertext, 0, FRAME_HEADER_LENGTH + payloadLength
|
||||
+ paddingLength + MAC_LENGTH);
|
||||
frameNumber++;
|
||||
}
|
||||
|
||||
private void writeTag() throws IOException {
|
||||
out.write(tag, 0, tag.length);
|
||||
writeTag = false;
|
||||
}
|
||||
|
||||
private void writeStreamHeader() throws IOException {
|
||||
byte[] streamHeaderPlaintext = frameKey.getBytes();
|
||||
byte[] streamHeaderCiphertext = new byte[STREAM_HEADER_LENGTH];
|
||||
System.arraycopy(streamHeaderIv, 0, streamHeaderCiphertext, 0,
|
||||
STREAM_HEADER_IV_LENGTH);
|
||||
// Encrypt and authenticate the frame key
|
||||
try {
|
||||
cipher.init(true, streamHeaderKey, streamHeaderIv);
|
||||
int encrypted = cipher.process(streamHeaderPlaintext, 0,
|
||||
SecretKey.LENGTH, streamHeaderCiphertext,
|
||||
STREAM_HEADER_IV_LENGTH);
|
||||
if (encrypted != SecretKey.LENGTH + MAC_LENGTH)
|
||||
throw new RuntimeException();
|
||||
} catch (GeneralSecurityException badCipher) {
|
||||
throw new RuntimeException(badCipher);
|
||||
}
|
||||
out.write(streamHeaderCiphertext);
|
||||
writeStreamHeader = false;
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
// Write the tag if required
|
||||
if (writeTag) {
|
||||
out.write(tag, 0, tag.length);
|
||||
writeTag = false;
|
||||
}
|
||||
if (writeTag) writeTag();
|
||||
// Write the stream header if required
|
||||
if (writeStreamHeader) writeStreamHeader();
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user