Use AES/GCM instead of AES/CTR and HMAC.

This makes us Suite B compliant and saves 32 bytes per frame. The
AES/GCM implementation refuses to decrypt the frame header before
checking the MAC, so we have to use AES/CTR to peek at the header. The
header is still covered by the MAC, and we still check it after peeking!
This commit is contained in:
akwizgran
2012-05-24 18:38:19 +01:00
parent d074652f43
commit d6b260ed61
27 changed files with 252 additions and 362 deletions

View File

@@ -6,7 +6,6 @@ import java.security.SecureRandom;
import java.security.Signature; import java.security.Signature;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.Mac;
public interface CryptoComponent { public interface CryptoComponent {
@@ -14,8 +13,6 @@ public interface CryptoComponent {
ErasableKey deriveFrameKey(byte[] secret, boolean initiator); ErasableKey deriveFrameKey(byte[] secret, boolean initiator);
ErasableKey deriveMacKey(byte[] secret, boolean initiator);
byte[][] deriveInitialSecrets(byte[] ourPublicKey, byte[] theirPublicKey, byte[][] deriveInitialSecrets(byte[] ourPublicKey, byte[] theirPublicKey,
PrivateKey ourPrivateKey, int invitationCode, boolean initiator); PrivateKey ourPrivateKey, int invitationCode, boolean initiator);
@@ -41,7 +38,11 @@ public interface CryptoComponent {
Cipher getFrameCipher(); Cipher getFrameCipher();
Signature getSignature(); Cipher getFramePeekingCipher();
Mac getMac(); IvEncoder getFrameIvEncoder();
IvEncoder getFramePeekingIvEncoder();
Signature getSignature();
} }

View File

@@ -0,0 +1,8 @@
package net.sf.briar.api.crypto;
public interface IvEncoder {
byte[] encodeIv(long frameNumber);
void updateIv(byte[] iv, long frameNumber);
}

View File

@@ -12,7 +12,7 @@ public interface TransportConstants {
static final int FRAME_HEADER_LENGTH = 9; static final int FRAME_HEADER_LENGTH = 9;
/** The length of the MAC in bytes. */ /** The length of the MAC in bytes. */
static final int MAC_LENGTH = 48; static final int MAC_LENGTH = 16;
/** /**
* The minimum connection length in bytes that all transport plugins must * The minimum connection length in bytes that all transport plugins must

View File

@@ -13,11 +13,11 @@ import java.security.Signature;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.KeyAgreement; import javax.crypto.KeyAgreement;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey; import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.IvEncoder;
import net.sf.briar.api.crypto.KeyParser; import net.sf.briar.api.crypto.KeyParser;
import net.sf.briar.api.crypto.MessageDigest; import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.crypto.PseudoRandom; import net.sf.briar.api.crypto.PseudoRandom;
@@ -35,20 +35,19 @@ class CryptoComponentImpl implements CryptoComponent {
private static final String AGREEMENT_ALGO = "ECDHC"; private static final String AGREEMENT_ALGO = "ECDHC";
private static final String SECRET_KEY_ALGO = "AES"; private static final String SECRET_KEY_ALGO = "AES";
private static final int SECRET_KEY_BYTES = 32; // 256 bits private static final int SECRET_KEY_BYTES = 32; // 256 bits
private static final int KEY_DERIVATION_IV_BYTES = 16; // 128 bits
private static final String KEY_DERIVATION_ALGO = "AES/CTR/NoPadding"; private static final String KEY_DERIVATION_ALGO = "AES/CTR/NoPadding";
private static final int KEY_DERIVATION_IV_BYTES = 16; // 128 bits
private static final String DIGEST_ALGO = "SHA-384"; private static final String DIGEST_ALGO = "SHA-384";
private static final String SIGNATURE_KEY_PAIR_ALGO = "ECDSA"; private static final String SIGNATURE_KEY_PAIR_ALGO = "ECDSA";
private static final int SIGNATURE_KEY_PAIR_BITS = 384; private static final int SIGNATURE_KEY_PAIR_BITS = 384;
private static final String SIGNATURE_ALGO = "ECDSA"; private static final String SIGNATURE_ALGO = "ECDSA";
private static final String TAG_CIPHER_ALGO = "AES/ECB/NoPadding"; private static final String TAG_CIPHER_ALGO = "AES/ECB/NoPadding";
private static final String FRAME_CIPHER_ALGO = "AES/CTR/NoPadding"; private static final String FRAME_CIPHER_ALGO = "AES/GCM/NoPadding";
private static final String MAC_ALGO = "HMacSHA384"; private static final String FRAME_PEEKING_CIPHER_ALGO = "AES/CTR/NoPadding";
// Labels for key derivation // Labels for key derivation
private static final byte[] TAG = { 'T', 'A', 'G' }; private static final byte[] TAG = { 'T', 'A', 'G' };
private static final byte[] FRAME = { 'F', 'R', 'A', 'M', 'E' }; private static final byte[] FRAME = { 'F', 'R', 'A', 'M', 'E' };
private static final byte[] MAC = { 'M', 'A', 'C' };
// Labels for secret derivation // Labels for secret derivation
private static final byte[] FIRST = { 'F', 'I', 'R', 'S', 'T' }; private static final byte[] FIRST = { 'F', 'I', 'R', 'S', 'T' };
private static final byte[] NEXT = { 'N', 'E', 'X', 'T' }; private static final byte[] NEXT = { 'N', 'E', 'X', 'T' };
@@ -96,11 +95,6 @@ class CryptoComponentImpl implements CryptoComponent {
else return deriveKey(secret, FRAME, RESPONDER); else return deriveKey(secret, FRAME, RESPONDER);
} }
public ErasableKey deriveMacKey(byte[] secret, boolean initiator) {
if(initiator) return deriveKey(secret, MAC, INITIATOR);
else return deriveKey(secret, MAC, RESPONDER);
}
private ErasableKey deriveKey(byte[] secret, byte[] label, byte[] context) { private ErasableKey deriveKey(byte[] secret, byte[] label, byte[] context) {
byte[] key = counterModeKdf(secret, label, context); byte[] key = counterModeKdf(secret, label, context);
return new ErasableKeyImpl(key, SECRET_KEY_ALGO); return new ErasableKeyImpl(key, SECRET_KEY_ALGO);
@@ -289,11 +283,19 @@ class CryptoComponentImpl implements CryptoComponent {
} }
} }
public Mac getMac() { public Cipher getFramePeekingCipher() {
try { try {
return Mac.getInstance(MAC_ALGO, PROVIDER); return Cipher.getInstance(FRAME_PEEKING_CIPHER_ALGO, PROVIDER);
} catch(GeneralSecurityException e) { } catch(GeneralSecurityException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
public IvEncoder getFrameIvEncoder() {
return new FrameIvEncoder();
}
public IvEncoder getFramePeekingIvEncoder() {
return new FramePeekingIvEncoder();
}
} }

View File

@@ -0,0 +1,26 @@
package net.sf.briar.crypto;
import net.sf.briar.api.crypto.IvEncoder;
import net.sf.briar.util.ByteUtils;
class FrameIvEncoder implements IvEncoder {
// AES-GCM uses a 96-bit IV; the bytes 0x00, 0x00, 0x00, 0x02 are
// appended internally (see NIST SP 800-38D, section 7.1)
private static final int IV_LENGTH = 12;
public byte[] encodeIv(long frame) {
if(frame < 0 || frame > ByteUtils.MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
byte[] iv = new byte[IV_LENGTH];
updateIv(iv, frame);
return iv;
}
public void updateIv(byte[] iv, long frame) {
if(frame < 0 || frame > ByteUtils.MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
// Encode the frame number as a uint32
ByteUtils.writeUint32(frame, iv, 0);
}
}

View File

@@ -0,0 +1,20 @@
package net.sf.briar.crypto;
import net.sf.briar.util.ByteUtils;
class FramePeekingIvEncoder extends FrameIvEncoder {
// AES/CTR uses a 128-bit IV; to match the AES/GCM IV we have to append
// the bytes 0x00, 0x00, 0x00, 0x02 (see NIST SP 800-38D, section 7.1)
private static final int IV_LENGTH = 16;
@Override
public byte[] encodeIv(long frame) {
if(frame < 0 || frame > ByteUtils.MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
byte[] iv = new byte[IV_LENGTH];
iv[IV_LENGTH - 1] = 2;
updateIv(iv, frame);
return iv;
}
}

View File

@@ -3,10 +3,10 @@ package net.sf.briar.transport;
import java.io.InputStream; import java.io.InputStream;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.Mac;
import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey; import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.IvEncoder;
import net.sf.briar.api.transport.ConnectionReader; import net.sf.briar.api.transport.ConnectionReader;
import net.sf.briar.api.transport.ConnectionReaderFactory; import net.sf.briar.api.transport.ConnectionReaderFactory;
import net.sf.briar.util.ByteUtils; import net.sf.briar.util.ByteUtils;
@@ -27,18 +27,16 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
// Derive the keys and erase the secret // Derive the keys and erase the secret
ErasableKey tagKey = crypto.deriveTagKey(secret, initiator); ErasableKey tagKey = crypto.deriveTagKey(secret, initiator);
ErasableKey frameKey = crypto.deriveFrameKey(secret, initiator); ErasableKey frameKey = crypto.deriveFrameKey(secret, initiator);
ErasableKey macKey = crypto.deriveMacKey(secret, initiator);
ByteUtils.erase(secret); ByteUtils.erase(secret);
// Encryption // Create the reader
Cipher tagCipher = crypto.getTagCipher(); Cipher tagCipher = crypto.getTagCipher();
Cipher frameCipher = crypto.getFrameCipher(); Cipher frameCipher = crypto.getFrameCipher();
Cipher framePeekingCipher = crypto.getFramePeekingCipher();
IvEncoder frameIvEncoder = crypto.getFrameIvEncoder();
IvEncoder framePeekingIvEncoder = crypto.getFramePeekingIvEncoder();
FrameReader encryption = new IncomingEncryptionLayerImpl(in, tagCipher, FrameReader encryption = new IncomingEncryptionLayerImpl(in, tagCipher,
frameCipher, tagKey, frameKey, !initiator); frameCipher, framePeekingCipher, frameIvEncoder,
// Authentication framePeekingIvEncoder, tagKey, frameKey, !initiator);
Mac mac = crypto.getMac(); return new ConnectionReaderImpl(encryption);
FrameReader authentication = new IncomingAuthenticationLayerImpl(
encryption, mac, macKey);
// Create the reader
return new ConnectionReaderImpl(authentication);
} }
} }

View File

@@ -53,14 +53,20 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
private boolean readFrame() throws IOException { private boolean readFrame() throws IOException {
assert length == 0; assert length == 0;
if(HeaderEncoder.isLastFrame(frame.getBuffer())) { byte[] buf = frame.getBuffer();
if(HeaderEncoder.isLastFrame(buf)) {
length = -1; length = -1;
return false; return false;
} }
frame.reset(); frame.reset();
if(!in.readFrame(frame)) throw new FormatException(); if(!in.readFrame(frame)) throw new FormatException();
offset = FRAME_HEADER_LENGTH; offset = FRAME_HEADER_LENGTH;
length = HeaderEncoder.getPayloadLength(frame.getBuffer()); length = HeaderEncoder.getPayloadLength(buf);
// The padding must be all zeroes
int padding = HeaderEncoder.getPaddingLength(buf);
for(int i = offset + length; i < offset + length + padding; i++) {
if(buf[i] != 0) throw new FormatException();
}
return true; return true;
} }
} }

View File

@@ -3,10 +3,10 @@ package net.sf.briar.transport;
import java.io.OutputStream; import java.io.OutputStream;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.Mac;
import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey; import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.IvEncoder;
import net.sf.briar.api.transport.ConnectionWriter; import net.sf.briar.api.transport.ConnectionWriter;
import net.sf.briar.api.transport.ConnectionWriterFactory; import net.sf.briar.api.transport.ConnectionWriterFactory;
import net.sf.briar.util.ByteUtils; import net.sf.briar.util.ByteUtils;
@@ -27,18 +27,14 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
// Derive the keys and erase the secret // Derive the keys and erase the secret
ErasableKey tagKey = crypto.deriveTagKey(secret, initiator); ErasableKey tagKey = crypto.deriveTagKey(secret, initiator);
ErasableKey frameKey = crypto.deriveFrameKey(secret, initiator); ErasableKey frameKey = crypto.deriveFrameKey(secret, initiator);
ErasableKey macKey = crypto.deriveMacKey(secret, initiator);
ByteUtils.erase(secret); ByteUtils.erase(secret);
// Encryption // Create the writer
Cipher tagCipher = crypto.getTagCipher(); Cipher tagCipher = crypto.getTagCipher();
Cipher frameCipher = crypto.getFrameCipher(); Cipher frameCipher = crypto.getFrameCipher();
IvEncoder frameIvEncoder = crypto.getFrameIvEncoder();
FrameWriter encryption = new OutgoingEncryptionLayerImpl( FrameWriter encryption = new OutgoingEncryptionLayerImpl(
out, capacity, tagCipher, frameCipher, tagKey, frameKey); out, capacity, tagCipher, frameCipher, frameIvEncoder, tagKey,
// Authentication frameKey);
Mac mac = crypto.getMac(); return new ConnectionWriterImpl(encryption);
FrameWriter authentication =
new OutgoingAuthenticationLayerImpl(encryption, mac, macKey);
// Create the writer
return new ConnectionWriterImpl(authentication);
} }
} }

View File

@@ -91,7 +91,7 @@ class ConnectionWriterImpl extends OutputStream implements ConnectionWriter {
assert payload >= 0; assert payload >= 0;
HeaderEncoder.encodeHeader(frame.getBuffer(), frameNumber, payload, 0, HeaderEncoder.encodeHeader(frame.getBuffer(), frameNumber, payload, 0,
lastFrame); lastFrame);
frame.setLength(offset + MAC_LENGTH); frame.setLength(offset);
out.writeFrame(frame); out.writeFrame(frame);
frame.reset(); frame.reset();
offset = FRAME_HEADER_LENGTH; offset = FRAME_HEADER_LENGTH;

View File

@@ -1,7 +1,6 @@
package net.sf.briar.transport; package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH; import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH; import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
class Frame { class Frame {
@@ -24,7 +23,7 @@ class Frame {
} }
public void setLength(int length) { public void setLength(int length) {
if(length < FRAME_HEADER_LENGTH + MAC_LENGTH || length > buf.length) if(length < FRAME_HEADER_LENGTH || length > buf.length)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
this.length = length; this.length = length;
} }

View File

@@ -1,61 +0,0 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import java.io.IOException;
import java.security.InvalidKeyException;
import javax.crypto.Mac;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.crypto.ErasableKey;
class IncomingAuthenticationLayerImpl implements FrameReader {
private final FrameReader in;
private final Mac mac;
IncomingAuthenticationLayerImpl(FrameReader in, Mac mac,
ErasableKey macKey) {
this.in = in;
this.mac = mac;
try {
mac.init(macKey);
} catch(InvalidKeyException e) {
throw new IllegalArgumentException(e);
}
macKey.erase();
if(mac.getMacLength() != MAC_LENGTH)
throw new IllegalArgumentException();
}
public boolean readFrame(Frame f) throws IOException {
// Read a frame
if(!in.readFrame(f)) return false;
// Check that the length is legal
int length = f.getLength();
if(length < FRAME_HEADER_LENGTH + MAC_LENGTH)
throw new FormatException();
if(length > MAX_FRAME_LENGTH) throw new FormatException();
// Check that the header fields are legal and match the length
byte[] buf = f.getBuffer();
if(!HeaderEncoder.checkHeader(buf, length)) throw new FormatException();
// Check that the padding is all zeroes
int payload = HeaderEncoder.getPayloadLength(buf);
int padding = HeaderEncoder.getPaddingLength(buf);
int paddingStart = FRAME_HEADER_LENGTH + payload;
for(int i = paddingStart; i < paddingStart + padding; i++) {
if(buf[i] != 0) throw new FormatException();
}
// Verify the MAC
int macStart = FRAME_HEADER_LENGTH + payload + padding;
mac.update(buf, 0, macStart);
byte[] expectedMac = mac.doFinal();
for(int i = 0; i < expectedMac.length; i++) {
if(expectedMac[i] != buf[macStart + i]) throw new FormatException();
}
return true;
}
}

View File

@@ -15,31 +15,38 @@ import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.api.FormatException; import net.sf.briar.api.FormatException;
import net.sf.briar.api.crypto.ErasableKey; import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.IvEncoder;
class IncomingEncryptionLayerImpl implements FrameReader { class IncomingEncryptionLayerImpl implements FrameReader {
private final InputStream in; private final InputStream in;
private final Cipher tagCipher, frameCipher; private final Cipher tagCipher, frameCipher, framePeekingCipher;
private final IvEncoder frameIvEncoder, framePeekingIvEncoder;
private final ErasableKey tagKey, frameKey; private final ErasableKey tagKey, frameKey;
private final int blockSize; private final int blockSize;
private final byte[] iv, ciphertext; private final byte[] frameIv, framePeekingIv, ciphertext;
private boolean readTag; private boolean readTag;
private long frameNumber; private long frameNumber;
IncomingEncryptionLayerImpl(InputStream in, Cipher tagCipher, IncomingEncryptionLayerImpl(InputStream in, Cipher tagCipher,
Cipher frameCipher, ErasableKey tagKey, ErasableKey frameKey, Cipher frameCipher, Cipher framePeekingCipher,
boolean readTag) { IvEncoder frameIvEncoder, IvEncoder framePeekingIvEncoder,
ErasableKey tagKey, ErasableKey frameKey, boolean readTag) {
this.in = in; this.in = in;
this.tagCipher = tagCipher; this.tagCipher = tagCipher;
this.frameCipher = frameCipher; this.frameCipher = frameCipher;
this.framePeekingCipher = framePeekingCipher;
this.frameIvEncoder = frameIvEncoder;
this.framePeekingIvEncoder = framePeekingIvEncoder;
this.tagKey = tagKey; this.tagKey = tagKey;
this.frameKey = frameKey; this.frameKey = frameKey;
this.readTag = readTag; this.readTag = readTag;
blockSize = frameCipher.getBlockSize(); blockSize = frameCipher.getBlockSize();
if(blockSize < FRAME_HEADER_LENGTH) if(blockSize < FRAME_HEADER_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
iv = IvEncoder.encodeIv(0L, blockSize); frameIv = frameIvEncoder.encodeIv(0L);
framePeekingIv = framePeekingIvEncoder.encodeIv(0L);
ciphertext = new byte[MAX_FRAME_LENGTH]; ciphertext = new byte[MAX_FRAME_LENGTH];
frameNumber = 0L; frameNumber = 0L;
} }
@@ -65,21 +72,18 @@ class IncomingEncryptionLayerImpl implements FrameReader {
int offset = 0; int offset = 0;
while(offset < blockSize) { while(offset < blockSize) {
int read = in.read(ciphertext, offset, blockSize - offset); int read = in.read(ciphertext, offset, blockSize - offset);
if(read == -1) { if(read == -1) throw new EOFException();
if(offset == 0 && !readTag) return false;
throw new EOFException();
}
offset += read; offset += read;
} }
readTag = false; readTag = false;
// Decrypt the first block of the frame // Decrypt the first block of the frame to peek at the header
framePeekingIvEncoder.updateIv(framePeekingIv, frameNumber);
IvParameterSpec ivSpec = new IvParameterSpec(framePeekingIv);
byte[] plaintext = f.getBuffer(); byte[] plaintext = f.getBuffer();
try { try {
IvEncoder.updateIv(iv, frameNumber); framePeekingCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec);
IvParameterSpec ivSpec = new IvParameterSpec(iv); int decrypted = framePeekingCipher.update(ciphertext, 0,
frameCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec); blockSize, plaintext);
int decrypted = frameCipher.update(ciphertext, 0, blockSize,
plaintext);
if(decrypted != blockSize) throw new RuntimeException(); if(decrypted != blockSize) throw new RuntimeException();
} catch(GeneralSecurityException badCipher) { } catch(GeneralSecurityException badCipher) {
throw new RuntimeException(badCipher); throw new RuntimeException(badCipher);
@@ -95,16 +99,19 @@ class IncomingEncryptionLayerImpl implements FrameReader {
if(read == -1) throw new EOFException(); if(read == -1) throw new EOFException();
offset += read; offset += read;
} }
// Decrypt the remainder of the frame // Decrypt and authenticate the entire frame
frameIvEncoder.updateIv(frameIv, frameNumber);
ivSpec = new IvParameterSpec(frameIv);
try { try {
int decrypted = frameCipher.doFinal(ciphertext, blockSize, frameCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec);
length - blockSize, plaintext, blockSize); int decrypted = frameCipher.doFinal(ciphertext, 0, length,
if(decrypted != length - blockSize) plaintext);
if(decrypted != length - MAC_LENGTH)
throw new RuntimeException(); throw new RuntimeException();
} catch(GeneralSecurityException badCipher) { } catch(GeneralSecurityException badCipher) {
throw new RuntimeException(badCipher); throw new RuntimeException(badCipher);
} }
f.setLength(length); f.setLength(length - MAC_LENGTH);
frameNumber++; frameNumber++;
return true; return true;
} catch(IOException e) { } catch(IOException e) {

View File

@@ -1,19 +0,0 @@
package net.sf.briar.transport;
import net.sf.briar.util.ByteUtils;
class IvEncoder {
static byte[] encodeIv(long frame, int blockSize) {
if(frame < 0 || frame > ByteUtils.MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
byte[] iv = new byte[blockSize];
updateIv(iv, frame);
return iv;
}
static void updateIv(byte[] iv, long frame) {
// Encode the frame number as a uint32
ByteUtils.writeUint32(frame, iv, 0);
}
}

View File

@@ -1,51 +0,0 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import java.io.IOException;
import java.security.InvalidKeyException;
import javax.crypto.Mac;
import javax.crypto.ShortBufferException;
import net.sf.briar.api.crypto.ErasableKey;
class OutgoingAuthenticationLayerImpl implements FrameWriter {
private final FrameWriter out;
private final Mac mac;
OutgoingAuthenticationLayerImpl(FrameWriter out, Mac mac,
ErasableKey macKey) {
this.out = out;
this.mac = mac;
try {
mac.init(macKey);
} catch(InvalidKeyException badKey) {
throw new IllegalArgumentException(badKey);
}
macKey.erase();
if(mac.getMacLength() != MAC_LENGTH)
throw new IllegalArgumentException();
}
public void writeFrame(Frame f) throws IOException {
byte[] buf = f.getBuffer();
int length = f.getLength() - MAC_LENGTH;
mac.update(buf, 0, length);
try {
mac.doFinal(buf, length);
} catch(ShortBufferException badMac) {
throw new RuntimeException(badMac);
}
out.writeFrame(f);
}
public void flush() throws IOException {
out.flush();
}
public long getRemainingCapacity() {
return out.getRemainingCapacity();
}
}

View File

@@ -1,5 +1,6 @@
package net.sf.briar.transport; package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH; import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
@@ -11,56 +12,58 @@ import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.api.crypto.ErasableKey; import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.IvEncoder;
class OutgoingEncryptionLayerImpl implements FrameWriter { class OutgoingEncryptionLayerImpl implements FrameWriter {
private final OutputStream out; private final OutputStream out;
private final Cipher tagCipher, frameCipher; private final Cipher tagCipher, frameCipher;
private final IvEncoder frameIvEncoder;
private final ErasableKey tagKey, frameKey; private final ErasableKey tagKey, frameKey;
private final byte[] iv, ciphertext; private final byte[] frameIv, ciphertext;
private long capacity, frameNumber; private long capacity, frameNumber;
OutgoingEncryptionLayerImpl(OutputStream out, long capacity, OutgoingEncryptionLayerImpl(OutputStream out, long capacity,
Cipher tagCipher, Cipher frameCipher, ErasableKey tagKey, Cipher tagCipher, Cipher frameCipher, IvEncoder frameIvEncoder,
ErasableKey frameKey) { ErasableKey tagKey, ErasableKey frameKey) {
this.out = out; this.out = out;
this.capacity = capacity; this.capacity = capacity;
this.tagCipher = tagCipher; this.tagCipher = tagCipher;
this.frameCipher = frameCipher; this.frameCipher = frameCipher;
this.frameIvEncoder = frameIvEncoder;
this.tagKey = tagKey; this.tagKey = tagKey;
this.frameKey = frameKey; this.frameKey = frameKey;
iv = IvEncoder.encodeIv(0L, frameCipher.getBlockSize()); frameIv = frameIvEncoder.encodeIv(0L);
ciphertext = new byte[TAG_LENGTH + MAX_FRAME_LENGTH]; ciphertext = new byte[TAG_LENGTH + MAX_FRAME_LENGTH];
frameNumber = 0L; frameNumber = 0L;
} }
public void writeFrame(Frame f) throws IOException { public void writeFrame(Frame f) throws IOException {
byte[] plaintext = f.getBuffer(); byte[] plaintext = f.getBuffer();
int length = f.getLength(); int offset = 0, length = f.getLength();
int offset = 0;
if(frameNumber == 0) { if(frameNumber == 0) {
TagEncoder.encodeTag(ciphertext, tagCipher, tagKey); TagEncoder.encodeTag(ciphertext, tagCipher, tagKey);
offset = TAG_LENGTH; offset = TAG_LENGTH;
} }
IvEncoder.updateIv(iv, frameNumber); frameIvEncoder.updateIv(frameIv, frameNumber);
IvParameterSpec ivSpec = new IvParameterSpec(iv); IvParameterSpec ivSpec = new IvParameterSpec(frameIv);
try { try {
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec); frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
int encrypted = frameCipher.doFinal(plaintext, 0, length, int encrypted = frameCipher.doFinal(plaintext, 0, length,
ciphertext, offset); ciphertext, offset);
if(encrypted != length) throw new RuntimeException(); if(encrypted != length + MAC_LENGTH) throw new RuntimeException();
} catch(GeneralSecurityException badCipher) { } catch(GeneralSecurityException badCipher) {
throw new RuntimeException(badCipher); throw new RuntimeException(badCipher);
} }
try { try {
out.write(ciphertext, 0, offset + length); out.write(ciphertext, 0, offset + length + MAC_LENGTH);
} catch(IOException e) { } catch(IOException e) {
frameKey.erase(); frameKey.erase();
tagKey.erase(); tagKey.erase();
throw e; throw e;
} }
capacity -= offset + length; capacity -= offset + length + MAC_LENGTH;
frameNumber++; frameNumber++;
} }

View File

@@ -18,6 +18,7 @@
<test name='net.sf.briar.ProtocolIntegrationTest'/> <test name='net.sf.briar.ProtocolIntegrationTest'/>
<test name='net.sf.briar.crypto.CounterModeTest'/> <test name='net.sf.briar.crypto.CounterModeTest'/>
<test name='net.sf.briar.crypto.ErasableKeyTest'/> <test name='net.sf.briar.crypto.ErasableKeyTest'/>
<test name='net.sf.briar.crypto.FramePeekingTest'/>
<test name='net.sf.briar.crypto.KeyDerivationTest'/> <test name='net.sf.briar.crypto.KeyDerivationTest'/>
<test name='net.sf.briar.db.BasicH2Test'/> <test name='net.sf.briar.db.BasicH2Test'/>
<test name='net.sf.briar.db.DatabaseCleanerImplTest'/> <test name='net.sf.briar.db.DatabaseCleanerImplTest'/>

View File

@@ -0,0 +1,44 @@
package net.sf.briar.crypto;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.IvEncoder;
import net.sf.briar.util.ByteUtils;
import org.junit.Test;
public class FramePeekingTest extends BriarTestCase {
@Test
public void testFramePeeking() throws Exception {
CryptoComponent crypto = new CryptoComponentImpl();
ErasableKey key = crypto.generateTestKey();
Cipher frameCipher = crypto.getFrameCipher();
IvEncoder frameIvEncoder = crypto.getFrameIvEncoder();
byte[] iv = frameIvEncoder.encodeIv(ByteUtils.MAX_32_BIT_UNSIGNED);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
Cipher framePeekingCipher = crypto.getFramePeekingCipher();
IvEncoder framePeekingIvEncoder = crypto.getFramePeekingIvEncoder();
iv = framePeekingIvEncoder.encodeIv(ByteUtils.MAX_32_BIT_UNSIGNED);
ivSpec = new IvParameterSpec(iv);
framePeekingCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
// The ciphers should produce the same ciphertext, apart from the MAC
byte[] plaintext = new byte[123];
byte[] ciphertext = frameCipher.doFinal(plaintext);
byte[] peekingCiphertext = framePeekingCipher.doFinal(plaintext);
assertEquals(ciphertext.length, peekingCiphertext.length + MAC_LENGTH);
for(int i = 0; i < peekingCiphertext.length; i++) {
assertEquals(ciphertext[i], peekingCiphertext[i]);
}
}
}

View File

@@ -25,17 +25,15 @@ public class KeyDerivationTest extends BriarTestCase {
} }
@Test @Test
public void testSixKeysAreDistinct() { public void testKeysAreDistinct() {
List<ErasableKey> keys = new ArrayList<ErasableKey>(); List<ErasableKey> keys = new ArrayList<ErasableKey>();
keys.add(crypto.deriveFrameKey(secret, true)); keys.add(crypto.deriveFrameKey(secret, true));
keys.add(crypto.deriveFrameKey(secret, false)); keys.add(crypto.deriveFrameKey(secret, false));
keys.add(crypto.deriveTagKey(secret, true)); keys.add(crypto.deriveTagKey(secret, true));
keys.add(crypto.deriveTagKey(secret, false)); keys.add(crypto.deriveTagKey(secret, false));
keys.add(crypto.deriveMacKey(secret, true)); for(int i = 0; i < 4; i++) {
keys.add(crypto.deriveMacKey(secret, false));
for(int i = 0; i < 6; i++) {
byte[] keyI = keys.get(i).getEncoded(); byte[] keyI = keys.get(i).getEncoded();
for(int j = 0; j < 6; j++) { for(int j = 0; j < 4; j++) {
byte[] keyJ = keys.get(j).getEncoded(); byte[] keyJ = keys.get(j).getEncoded();
assertEquals(i == j, Arrays.equals(keyI, keyJ)); assertEquals(i == j, Arrays.equals(keyI, keyJ));
} }

View File

@@ -23,14 +23,8 @@ public class ConnectionReaderImplTest extends TransportTest {
@Test @Test
public void testLengthZero() throws Exception { public void testLengthZero() throws Exception {
int payloadLength = 0; byte[] frame = new byte[FRAME_HEADER_LENGTH + MAC_LENGTH];
byte[] frame = new byte[FRAME_HEADER_LENGTH + payloadLength HeaderEncoder.encodeHeader(frame, 0, 0, 0, true);
+ MAC_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, payloadLength, 0, true);
// Calculate the MAC
mac.init(macKey);
mac.update(frame, 0, FRAME_HEADER_LENGTH + payloadLength);
mac.doFinal(frame, FRAME_HEADER_LENGTH + payloadLength);
// Read the frame // Read the frame
ByteArrayInputStream in = new ByteArrayInputStream(frame); ByteArrayInputStream in = new ByteArrayInputStream(frame);
ConnectionReader r = createConnectionReader(in); ConnectionReader r = createConnectionReader(in);
@@ -40,14 +34,8 @@ public class ConnectionReaderImplTest extends TransportTest {
@Test @Test
public void testLengthOne() throws Exception { public void testLengthOne() throws Exception {
int payloadLength = 1; byte[] frame = new byte[FRAME_HEADER_LENGTH + 1 + MAC_LENGTH];
byte[] frame = new byte[FRAME_HEADER_LENGTH + payloadLength HeaderEncoder.encodeHeader(frame, 0, 1, 0, true);
+ MAC_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, payloadLength, 0, true);
// Calculate the MAC
mac.init(macKey);
mac.update(frame, 0, FRAME_HEADER_LENGTH + payloadLength);
mac.doFinal(frame, FRAME_HEADER_LENGTH + payloadLength);
// Read the frame // Read the frame
ByteArrayInputStream in = new ByteArrayInputStream(frame); ByteArrayInputStream in = new ByteArrayInputStream(frame);
ConnectionReader r = createConnectionReader(in); ConnectionReader r = createConnectionReader(in);
@@ -61,14 +49,9 @@ public class ConnectionReaderImplTest extends TransportTest {
// First frame: max payload length // First frame: max payload length
byte[] frame = new byte[MAX_FRAME_LENGTH]; byte[] frame = new byte[MAX_FRAME_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, MAX_PAYLOAD_LENGTH, 0, false); HeaderEncoder.encodeHeader(frame, 0, MAX_PAYLOAD_LENGTH, 0, false);
mac.init(macKey);
mac.update(frame, 0, FRAME_HEADER_LENGTH + MAX_PAYLOAD_LENGTH);
mac.doFinal(frame, FRAME_HEADER_LENGTH + MAX_PAYLOAD_LENGTH);
// Second frame: max payload length plus one // Second frame: max payload length plus one
byte[] frame1 = new byte[MAX_FRAME_LENGTH + 1]; byte[] frame1 = new byte[MAX_FRAME_LENGTH + 1];
HeaderEncoder.encodeHeader(frame1, 1, MAX_PAYLOAD_LENGTH + 1, 0, false); HeaderEncoder.encodeHeader(frame1, 1, MAX_PAYLOAD_LENGTH + 1, 0, false);
mac.update(frame1, 0, FRAME_HEADER_LENGTH + MAX_PAYLOAD_LENGTH + 1);
mac.doFinal(frame1, FRAME_HEADER_LENGTH + MAX_PAYLOAD_LENGTH + 1);
// Concatenate the frames // Concatenate the frames
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(frame); out.write(frame);
@@ -93,15 +76,10 @@ public class ConnectionReaderImplTest extends TransportTest {
byte[] frame = new byte[MAX_FRAME_LENGTH]; byte[] frame = new byte[MAX_FRAME_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, MAX_PAYLOAD_LENGTH - paddingLength, HeaderEncoder.encodeHeader(frame, 0, MAX_PAYLOAD_LENGTH - paddingLength,
paddingLength, false); paddingLength, false);
mac.init(macKey);
mac.update(frame, 0, FRAME_HEADER_LENGTH + MAX_PAYLOAD_LENGTH);
mac.doFinal(frame, FRAME_HEADER_LENGTH + MAX_PAYLOAD_LENGTH);
// Second frame: max payload length plus one, including padding // Second frame: max payload length plus one, including padding
byte[] frame1 = new byte[MAX_FRAME_LENGTH + 1]; byte[] frame1 = new byte[MAX_FRAME_LENGTH + 1];
HeaderEncoder.encodeHeader(frame1, 1, HeaderEncoder.encodeHeader(frame1, 1,
MAX_PAYLOAD_LENGTH + 1 - paddingLength, paddingLength, false); MAX_PAYLOAD_LENGTH + 1 - paddingLength, paddingLength, false);
mac.update(frame1, 0, FRAME_HEADER_LENGTH + MAX_PAYLOAD_LENGTH + 1);
mac.doFinal(frame1, FRAME_HEADER_LENGTH + MAX_PAYLOAD_LENGTH + 1);
// Concatenate the frames // Concatenate the frames
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(frame); out.write(frame);
@@ -128,10 +106,6 @@ public class ConnectionReaderImplTest extends TransportTest {
false); false);
// Set a byte of the padding to a non-zero value // Set a byte of the padding to a non-zero value
frame[FRAME_HEADER_LENGTH + payloadLength] = 1; frame[FRAME_HEADER_LENGTH + payloadLength] = 1;
mac.init(macKey);
mac.update(frame, 0, FRAME_HEADER_LENGTH + payloadLength
+ paddingLength);
mac.doFinal(frame, FRAME_HEADER_LENGTH + payloadLength + paddingLength);
// Read the frame // Read the frame
ByteArrayInputStream in = new ByteArrayInputStream(frame); ByteArrayInputStream in = new ByteArrayInputStream(frame);
ConnectionReader r = createConnectionReader(in); ConnectionReader r = createConnectionReader(in);
@@ -149,16 +123,11 @@ public class ConnectionReaderImplTest extends TransportTest {
byte[] frame = new byte[FRAME_HEADER_LENGTH + payloadLength byte[] frame = new byte[FRAME_HEADER_LENGTH + payloadLength
+ MAC_LENGTH]; + MAC_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, payloadLength, 0, false); HeaderEncoder.encodeHeader(frame, 0, payloadLength, 0, false);
mac.init(macKey);
mac.update(frame, 0, FRAME_HEADER_LENGTH + payloadLength);
mac.doFinal(frame, FRAME_HEADER_LENGTH + payloadLength);
// Second frame: 1234-byte payload // Second frame: 1234-byte payload
int payloadLength1 = 1234; int payloadLength1 = 1234;
byte[] frame1 = new byte[FRAME_HEADER_LENGTH + payloadLength1 byte[] frame1 = new byte[FRAME_HEADER_LENGTH + payloadLength1
+ MAC_LENGTH]; + MAC_LENGTH];
HeaderEncoder.encodeHeader(frame1, 1, payloadLength1, 0, true); HeaderEncoder.encodeHeader(frame1, 1, payloadLength1, 0, true);
mac.update(frame1, 0, FRAME_HEADER_LENGTH + payloadLength1);
mac.doFinal(frame1, FRAME_HEADER_LENGTH + payloadLength1);
// Concatenate the frames // Concatenate the frames
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(frame); out.write(frame);
@@ -182,16 +151,11 @@ public class ConnectionReaderImplTest extends TransportTest {
byte[] frame = new byte[FRAME_HEADER_LENGTH + payloadLength byte[] frame = new byte[FRAME_HEADER_LENGTH + payloadLength
+ MAC_LENGTH]; + MAC_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, payloadLength, 0, false); HeaderEncoder.encodeHeader(frame, 0, payloadLength, 0, false);
mac.init(macKey);
mac.update(frame, 0, FRAME_HEADER_LENGTH + payloadLength);
mac.doFinal(frame, FRAME_HEADER_LENGTH + payloadLength);
// Second frame: 1234-byte payload // Second frame: 1234-byte payload
int payloadLength1 = 1234; int payloadLength1 = 1234;
byte[] frame1 = new byte[FRAME_HEADER_LENGTH + payloadLength1 byte[] frame1 = new byte[FRAME_HEADER_LENGTH + payloadLength1
+ MAC_LENGTH]; + MAC_LENGTH];
HeaderEncoder.encodeHeader(frame1, 1, payloadLength1, 0, false); HeaderEncoder.encodeHeader(frame1, 1, payloadLength1, 0, false);
mac.update(frame1, 0, FRAME_HEADER_LENGTH + payloadLength1);
mac.doFinal(frame1, FRAME_HEADER_LENGTH + payloadLength1);
// Concatenate the frames // Concatenate the frames
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(frame); out.write(frame);
@@ -211,52 +175,8 @@ public class ConnectionReaderImplTest extends TransportTest {
} catch(FormatException expected) {} } catch(FormatException expected) {}
} }
@Test
public void testCorruptPayload() throws Exception {
int payloadLength = 8;
byte[] frame = new byte[FRAME_HEADER_LENGTH + payloadLength
+ MAC_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, payloadLength, 0, false);
// Calculate the MAC
mac.init(macKey);
mac.update(frame, 0, FRAME_HEADER_LENGTH + payloadLength);
mac.doFinal(frame, FRAME_HEADER_LENGTH + payloadLength);
// Modify the payload
frame[12] ^= 1;
// Try to read the frame - not a single byte should be read
ByteArrayInputStream in = new ByteArrayInputStream(frame);
ConnectionReader r = createConnectionReader(in);
try {
r.getInputStream().read();
fail();
} catch(FormatException expected) {}
}
@Test
public void testCorruptMac() throws Exception {
int payloadLength = 8;
byte[] frame = new byte[FRAME_HEADER_LENGTH + payloadLength
+ MAC_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, payloadLength, 0, false);
// Calculate the MAC
mac.init(macKey);
mac.update(frame, 0, FRAME_HEADER_LENGTH + payloadLength);
mac.doFinal(frame, FRAME_HEADER_LENGTH + payloadLength);
// Modify the MAC
frame[17] ^= 1;
// Try to read the frame - not a single byte should be read
ByteArrayInputStream in = new ByteArrayInputStream(frame);
ConnectionReader r = createConnectionReader(in);
try {
r.getInputStream().read();
fail();
} catch(FormatException expected) {}
}
private ConnectionReader createConnectionReader(InputStream in) { private ConnectionReader createConnectionReader(InputStream in) {
FrameReader encryption = new NullIncomingEncryptionLayer(in); FrameReader encryption = new NullIncomingEncryptionLayer(in);
FrameReader authentication = new IncomingAuthenticationLayerImpl( return new ConnectionReaderImpl(encryption);
encryption, mac, macKey);
return new ConnectionReaderImpl(authentication);
} }
} }

View File

@@ -30,14 +30,9 @@ public class ConnectionWriterImplTest extends TransportTest {
@Test @Test
public void testSingleByteFrame() throws Exception { public void testSingleByteFrame() throws Exception {
int payloadLength = 1; // Create a single-byte frame
byte[] frame = new byte[FRAME_HEADER_LENGTH + payloadLength byte[] frame = new byte[FRAME_HEADER_LENGTH + 1 + MAC_LENGTH];
+ MAC_LENGTH]; HeaderEncoder.encodeHeader(frame, 0, 1, 0, false);
HeaderEncoder.encodeHeader(frame, 0, payloadLength, 0, false);
// Calculate the MAC
mac.init(macKey);
mac.update(frame, 0, FRAME_HEADER_LENGTH + payloadLength);
mac.doFinal(frame, FRAME_HEADER_LENGTH + payloadLength);
// Check that the ConnectionWriter gets the same results // Check that the ConnectionWriter gets the same results
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
ConnectionWriter w = createConnectionWriter(out); ConnectionWriter w = createConnectionWriter(out);
@@ -75,20 +70,11 @@ public class ConnectionWriterImplTest extends TransportTest {
@Test @Test
public void testMultipleFrames() throws Exception { public void testMultipleFrames() throws Exception {
// First frame: 123-byte payload // First frame: 123-byte payload
int payloadLength = 123; byte[] frame = new byte[FRAME_HEADER_LENGTH + 123 + MAC_LENGTH];
byte[] frame = new byte[FRAME_HEADER_LENGTH + payloadLength HeaderEncoder.encodeHeader(frame, 0, 123, 0, false);
+ MAC_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, payloadLength, 0, false);
mac.init(macKey);
mac.update(frame, 0, FRAME_HEADER_LENGTH + payloadLength);
mac.doFinal(frame, FRAME_HEADER_LENGTH + payloadLength);
// Second frame: 1234-byte payload // Second frame: 1234-byte payload
int payloadLength1 = 1234; byte[] frame1 = new byte[FRAME_HEADER_LENGTH + 1234 + MAC_LENGTH];
byte[] frame1 = new byte[FRAME_HEADER_LENGTH + payloadLength1 HeaderEncoder.encodeHeader(frame1, 1, 1234, 0, false);
+ MAC_LENGTH];
HeaderEncoder.encodeHeader(frame1, 1, payloadLength1, 0, false);
mac.update(frame1, 0, FRAME_HEADER_LENGTH + 1234);
mac.doFinal(frame1, FRAME_HEADER_LENGTH + 1234);
// Concatenate the frames // Concatenate the frames
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(frame); out.write(frame);
@@ -107,8 +93,6 @@ public class ConnectionWriterImplTest extends TransportTest {
private ConnectionWriter createConnectionWriter(OutputStream out) { private ConnectionWriter createConnectionWriter(OutputStream out) {
FrameWriter encryption = new NullOutgoingEncryptionLayer(out); FrameWriter encryption = new NullOutgoingEncryptionLayer(out);
FrameWriter authentication = return new ConnectionWriterImpl(encryption);
new OutgoingAuthenticationLayerImpl(encryption, mac, macKey);
return new ConnectionWriterImpl(authentication);
} }
} }

View File

@@ -10,11 +10,11 @@ import java.io.OutputStream;
import java.util.Random; import java.util.Random;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.Mac;
import net.sf.briar.BriarTestCase; import net.sf.briar.BriarTestCase;
import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey; import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.IvEncoder;
import net.sf.briar.api.transport.ConnectionReader; import net.sf.briar.api.transport.ConnectionReader;
import net.sf.briar.api.transport.ConnectionWriter; import net.sf.briar.api.transport.ConnectionWriter;
import net.sf.briar.crypto.CryptoModule; import net.sf.briar.crypto.CryptoModule;
@@ -27,11 +27,11 @@ import com.google.inject.Injector;
public class FrameReadWriteTest extends BriarTestCase { public class FrameReadWriteTest extends BriarTestCase {
private final CryptoComponent crypto; private final CryptoComponent crypto;
private final Cipher tagCipher, frameCipher; private final Cipher tagCipher, frameCipher, framePeekingCipher;
private final Mac mac; private final IvEncoder frameIvEncoder, framePeekingIvEncoder;
private final Random random; private final Random random;
private final byte[] outSecret; private final byte[] outSecret;
private final ErasableKey tagKey, frameKey, macKey; private final ErasableKey tagKey, frameKey;
public FrameReadWriteTest() { public FrameReadWriteTest() {
super(); super();
@@ -39,14 +39,15 @@ public class FrameReadWriteTest extends BriarTestCase {
crypto = i.getInstance(CryptoComponent.class); crypto = i.getInstance(CryptoComponent.class);
tagCipher = crypto.getTagCipher(); tagCipher = crypto.getTagCipher();
frameCipher = crypto.getFrameCipher(); frameCipher = crypto.getFrameCipher();
mac = crypto.getMac(); framePeekingCipher = crypto.getFramePeekingCipher();
frameIvEncoder = crypto.getFrameIvEncoder();
framePeekingIvEncoder = crypto.getFramePeekingIvEncoder();
random = new Random(); random = new Random();
// Since we're sending frames to ourselves, we only need outgoing keys // Since we're sending frames to ourselves, we only need outgoing keys
outSecret = new byte[32]; outSecret = new byte[32];
random.nextBytes(outSecret); random.nextBytes(outSecret);
tagKey = crypto.deriveTagKey(outSecret, true); tagKey = crypto.deriveTagKey(outSecret, true);
frameKey = crypto.deriveFrameKey(outSecret, true); frameKey = crypto.deriveFrameKey(outSecret, true);
macKey = crypto.deriveMacKey(outSecret, true);
} }
@Test @Test
@@ -71,14 +72,12 @@ public class FrameReadWriteTest extends BriarTestCase {
// Copy the keys - the copies will be erased // Copy the keys - the copies will be erased
ErasableKey tagCopy = tagKey.copy(); ErasableKey tagCopy = tagKey.copy();
ErasableKey frameCopy = frameKey.copy(); ErasableKey frameCopy = frameKey.copy();
ErasableKey macCopy = macKey.copy();
// Write the frames // Write the frames
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
FrameWriter encryptionOut = new OutgoingEncryptionLayerImpl(out, FrameWriter encryptionOut = new OutgoingEncryptionLayerImpl(out,
Long.MAX_VALUE, tagCipher, frameCipher, tagCopy, frameCopy); Long.MAX_VALUE, tagCipher, frameCipher, frameIvEncoder, tagCopy,
FrameWriter authenticationOut = new OutgoingAuthenticationLayerImpl( frameCopy);
encryptionOut, mac, macCopy); ConnectionWriter writer = new ConnectionWriterImpl(encryptionOut);
ConnectionWriter writer = new ConnectionWriterImpl(authenticationOut);
OutputStream out1 = writer.getOutputStream(); OutputStream out1 = writer.getOutputStream();
out1.write(frame); out1.write(frame);
out1.flush(); out1.flush();
@@ -92,10 +91,9 @@ public class FrameReadWriteTest extends BriarTestCase {
assertTrue(TagEncoder.decodeTag(recoveredTag, tagCipher, tagKey)); assertTrue(TagEncoder.decodeTag(recoveredTag, tagCipher, tagKey));
// Read the frames back // Read the frames back
FrameReader encryptionIn = new IncomingEncryptionLayerImpl(in, FrameReader encryptionIn = new IncomingEncryptionLayerImpl(in,
tagCipher, frameCipher, tagKey, frameKey, false); tagCipher, frameCipher, framePeekingCipher, frameIvEncoder,
FrameReader authenticationIn = new IncomingAuthenticationLayerImpl( framePeekingIvEncoder, tagKey, frameKey, false);
encryptionIn, mac, macKey); ConnectionReader reader = new ConnectionReaderImpl(encryptionIn);
ConnectionReader reader = new ConnectionReaderImpl(authenticationIn);
InputStream in1 = reader.getInputStream(); InputStream in1 = reader.getInputStream();
byte[] recovered = new byte[frame.length]; byte[] recovered = new byte[frame.length];
int offset = 0; int offset = 0;

View File

@@ -1,7 +1,6 @@
package net.sf.briar.transport; package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH; import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH; import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@@ -12,6 +11,7 @@ import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.BriarTestCase; import net.sf.briar.BriarTestCase;
import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey; import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.IvEncoder;
import net.sf.briar.crypto.CryptoModule; import net.sf.briar.crypto.CryptoModule;
import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.commons.io.output.ByteArrayOutputStream;
@@ -22,7 +22,8 @@ import com.google.inject.Injector;
public class IncomingEncryptionLayerImplTest extends BriarTestCase { public class IncomingEncryptionLayerImplTest extends BriarTestCase {
private final Cipher tagCipher, frameCipher; private final Cipher tagCipher, frameCipher, framePeekingCipher;
private final IvEncoder frameIvEncoder, framePeekingIvEncoder;
private final ErasableKey tagKey, frameKey; private final ErasableKey tagKey, frameKey;
public IncomingEncryptionLayerImplTest() { public IncomingEncryptionLayerImplTest() {
@@ -31,6 +32,9 @@ public class IncomingEncryptionLayerImplTest extends BriarTestCase {
CryptoComponent crypto = i.getInstance(CryptoComponent.class); CryptoComponent crypto = i.getInstance(CryptoComponent.class);
tagCipher = crypto.getTagCipher(); tagCipher = crypto.getTagCipher();
frameCipher = crypto.getFrameCipher(); frameCipher = crypto.getFrameCipher();
framePeekingCipher = crypto.getFramePeekingCipher();
frameIvEncoder = crypto.getFrameIvEncoder();
framePeekingIvEncoder = crypto.getFramePeekingIvEncoder();
tagKey = crypto.generateTestKey(); tagKey = crypto.generateTestKey();
frameKey = crypto.generateTestKey(); frameKey = crypto.generateTestKey();
} }
@@ -41,16 +45,16 @@ public class IncomingEncryptionLayerImplTest extends BriarTestCase {
byte[] tag = new byte[TAG_LENGTH]; byte[] tag = new byte[TAG_LENGTH];
TagEncoder.encodeTag(tag, tagCipher, tagKey); TagEncoder.encodeTag(tag, tagCipher, tagKey);
// Calculate the ciphertext for the first frame // Calculate the ciphertext for the first frame
byte[] plaintext = new byte[FRAME_HEADER_LENGTH + 123 + MAC_LENGTH]; byte[] plaintext = new byte[FRAME_HEADER_LENGTH + 123];
HeaderEncoder.encodeHeader(plaintext, 0L, 123, 0, false); HeaderEncoder.encodeHeader(plaintext, 0L, 123, 0, false);
byte[] iv = IvEncoder.encodeIv(0L, frameCipher.getBlockSize()); byte[] iv = frameIvEncoder.encodeIv(0L);
IvParameterSpec ivSpec = new IvParameterSpec(iv); IvParameterSpec ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec); frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
byte[] ciphertext = frameCipher.doFinal(plaintext, 0, plaintext.length); byte[] ciphertext = frameCipher.doFinal(plaintext);
// Calculate the ciphertext for the second frame // Calculate the ciphertext for the second frame
byte[] plaintext1 = new byte[FRAME_HEADER_LENGTH + 1234 + MAC_LENGTH]; byte[] plaintext1 = new byte[FRAME_HEADER_LENGTH + 1234];
HeaderEncoder.encodeHeader(plaintext1, 1L, 1234, 0, false); HeaderEncoder.encodeHeader(plaintext1, 1L, 1234, 0, false);
IvEncoder.updateIv(iv, 1L); frameIvEncoder.updateIv(iv, 1L);
ivSpec = new IvParameterSpec(iv); ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec); frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
byte[] ciphertext1 = frameCipher.doFinal(plaintext1, 0, byte[] ciphertext1 = frameCipher.doFinal(plaintext1, 0,
@@ -63,7 +67,8 @@ public class IncomingEncryptionLayerImplTest extends BriarTestCase {
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
// Use the encryption layer to decrypt the ciphertext // Use the encryption layer to decrypt the ciphertext
FrameReader decrypter = new IncomingEncryptionLayerImpl(in, tagCipher, FrameReader decrypter = new IncomingEncryptionLayerImpl(in, tagCipher,
frameCipher, tagKey, frameKey, true); frameCipher, framePeekingCipher, frameIvEncoder,
framePeekingIvEncoder, tagKey, frameKey, true);
// First frame // First frame
Frame f = new Frame(); Frame f = new Frame();
assertTrue(decrypter.readFrame(f)); assertTrue(decrypter.readFrame(f));
@@ -86,16 +91,16 @@ public class IncomingEncryptionLayerImplTest extends BriarTestCase {
@Test @Test
public void testDecryptionWithoutTag() throws Exception { public void testDecryptionWithoutTag() throws Exception {
// Calculate the ciphertext for the first frame // Calculate the ciphertext for the first frame
byte[] plaintext = new byte[FRAME_HEADER_LENGTH + 123 + MAC_LENGTH]; byte[] plaintext = new byte[FRAME_HEADER_LENGTH + 123];
HeaderEncoder.encodeHeader(plaintext, 0L, 123, 0, false); HeaderEncoder.encodeHeader(plaintext, 0L, 123, 0, false);
byte[] iv = IvEncoder.encodeIv(0L, frameCipher.getBlockSize()); byte[] iv = frameIvEncoder.encodeIv(0L);
IvParameterSpec ivSpec = new IvParameterSpec(iv); IvParameterSpec ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec); frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
byte[] ciphertext = frameCipher.doFinal(plaintext, 0, plaintext.length); byte[] ciphertext = frameCipher.doFinal(plaintext);
// Calculate the ciphertext for the second frame // Calculate the ciphertext for the second frame
byte[] plaintext1 = new byte[FRAME_HEADER_LENGTH + 1234 + MAC_LENGTH]; byte[] plaintext1 = new byte[FRAME_HEADER_LENGTH + 1234];
HeaderEncoder.encodeHeader(plaintext1, 1L, 1234, 0, false); HeaderEncoder.encodeHeader(plaintext1, 1L, 1234, 0, false);
IvEncoder.updateIv(iv, 1L); frameIvEncoder.updateIv(iv, 1L);
ivSpec = new IvParameterSpec(iv); ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec); frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
byte[] ciphertext1 = frameCipher.doFinal(plaintext1, 0, byte[] ciphertext1 = frameCipher.doFinal(plaintext1, 0,
@@ -107,7 +112,8 @@ public class IncomingEncryptionLayerImplTest extends BriarTestCase {
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
// Use the encryption layer to decrypt the ciphertext // Use the encryption layer to decrypt the ciphertext
FrameReader decrypter = new IncomingEncryptionLayerImpl(in, tagCipher, FrameReader decrypter = new IncomingEncryptionLayerImpl(in, tagCipher,
frameCipher, tagKey, frameKey, false); frameCipher, framePeekingCipher, frameIvEncoder,
framePeekingIvEncoder, tagKey, frameKey, false);
// First frame // First frame
Frame f = new Frame(); Frame f = new Frame();
assertTrue(decrypter.readFrame(f)); assertTrue(decrypter.readFrame(f));

View File

@@ -42,7 +42,7 @@ class NullIncomingEncryptionLayer implements FrameReader {
if(read == -1) throw new EOFException(); if(read == -1) throw new EOFException();
offset += read; offset += read;
} }
f.setLength(length); f.setLength(length - MAC_LENGTH);
return true; return true;
} }
} }

View File

@@ -1,5 +1,7 @@
package net.sf.briar.transport; package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@@ -21,7 +23,7 @@ class NullOutgoingEncryptionLayer implements FrameWriter {
} }
public void writeFrame(Frame f) throws IOException { public void writeFrame(Frame f) throws IOException {
out.write(f.getBuffer(), 0, f.getLength()); out.write(f.getBuffer(), 0, f.getLength() + MAC_LENGTH);
capacity -= f.getLength(); capacity -= f.getLength();
} }

View File

@@ -11,6 +11,7 @@ import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.BriarTestCase; import net.sf.briar.BriarTestCase;
import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey; import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.IvEncoder;
import net.sf.briar.crypto.CryptoModule; import net.sf.briar.crypto.CryptoModule;
import org.junit.Test; import org.junit.Test;
@@ -20,9 +21,8 @@ import com.google.inject.Injector;
public class OutgoingEncryptionLayerImplTest extends BriarTestCase { public class OutgoingEncryptionLayerImplTest extends BriarTestCase {
private static final int MAC_LENGTH = 32;
private final Cipher tagCipher, frameCipher; private final Cipher tagCipher, frameCipher;
private final IvEncoder frameIvEncoder;
private final ErasableKey tagKey, frameKey; private final ErasableKey tagKey, frameKey;
public OutgoingEncryptionLayerImplTest() { public OutgoingEncryptionLayerImplTest() {
@@ -31,6 +31,7 @@ public class OutgoingEncryptionLayerImplTest extends BriarTestCase {
CryptoComponent crypto = i.getInstance(CryptoComponent.class); CryptoComponent crypto = i.getInstance(CryptoComponent.class);
tagCipher = crypto.getTagCipher(); tagCipher = crypto.getTagCipher();
frameCipher = crypto.getFrameCipher(); frameCipher = crypto.getFrameCipher();
frameIvEncoder = crypto.getFrameIvEncoder();
tagKey = crypto.generateTestKey(); tagKey = crypto.generateTestKey();
frameKey = crypto.generateTestKey(); frameKey = crypto.generateTestKey();
} }
@@ -41,14 +42,14 @@ public class OutgoingEncryptionLayerImplTest extends BriarTestCase {
byte[] tag = new byte[TAG_LENGTH]; byte[] tag = new byte[TAG_LENGTH];
TagEncoder.encodeTag(tag, tagCipher, tagKey); TagEncoder.encodeTag(tag, tagCipher, tagKey);
// Calculate the expected ciphertext for the first frame // Calculate the expected ciphertext for the first frame
byte[] iv = new byte[frameCipher.getBlockSize()]; byte[] iv = frameIvEncoder.encodeIv(0L);
byte[] plaintext = new byte[123 + MAC_LENGTH]; byte[] plaintext = new byte[123];
IvParameterSpec ivSpec = new IvParameterSpec(iv); IvParameterSpec ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec); frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
byte[] ciphertext = frameCipher.doFinal(plaintext); byte[] ciphertext = frameCipher.doFinal(plaintext);
// Calculate the expected ciphertext for the second frame // Calculate the expected ciphertext for the second frame
byte[] plaintext1 = new byte[1234 + MAC_LENGTH]; byte[] plaintext1 = new byte[1234];
IvEncoder.updateIv(iv, 1L); frameIvEncoder.updateIv(iv, 1L);
ivSpec = new IvParameterSpec(iv); ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec); frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
byte[] ciphertext1 = frameCipher.doFinal(plaintext1); byte[] ciphertext1 = frameCipher.doFinal(plaintext1);
@@ -61,7 +62,8 @@ public class OutgoingEncryptionLayerImplTest extends BriarTestCase {
// Use the encryption layer to encrypt the plaintext // Use the encryption layer to encrypt the plaintext
out.reset(); out.reset();
FrameWriter encrypter = new OutgoingEncryptionLayerImpl(out, FrameWriter encrypter = new OutgoingEncryptionLayerImpl(out,
Long.MAX_VALUE, tagCipher, frameCipher, tagKey, frameKey); Long.MAX_VALUE, tagCipher, frameCipher, frameIvEncoder, tagKey,
frameKey);
Frame f = new Frame(); Frame f = new Frame();
System.arraycopy(plaintext, 0, f.getBuffer(), 0, plaintext.length); System.arraycopy(plaintext, 0, f.getBuffer(), 0, plaintext.length);
f.setLength(plaintext.length); f.setLength(plaintext.length);

View File

@@ -4,7 +4,7 @@ import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH; import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH; import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import javax.crypto.Mac; import javax.crypto.Cipher;
import net.sf.briar.BriarTestCase; import net.sf.briar.BriarTestCase;
import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.crypto.CryptoComponent;
@@ -19,14 +19,14 @@ public abstract class TransportTest extends BriarTestCase {
static final int MAX_PAYLOAD_LENGTH = static final int MAX_PAYLOAD_LENGTH =
MAX_FRAME_LENGTH - FRAME_HEADER_LENGTH - MAC_LENGTH; MAX_FRAME_LENGTH - FRAME_HEADER_LENGTH - MAC_LENGTH;
protected final Mac mac; protected final Cipher frameCipher;
protected final ErasableKey macKey; protected final ErasableKey frameKey;
public TransportTest() throws Exception { public TransportTest() throws Exception {
super(); super();
Injector i = Guice.createInjector(new CryptoModule()); Injector i = Guice.createInjector(new CryptoModule());
CryptoComponent crypto = i.getInstance(CryptoComponent.class); CryptoComponent crypto = i.getInstance(CryptoComponent.class);
mac = crypto.getMac(); frameCipher = crypto.getFrameCipher();
macKey = crypto.generateTestKey(); frameKey = crypto.generateTestKey();
} }
} }