mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 11:19:04 +01:00
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:
@@ -13,11 +13,11 @@ import java.security.Signature;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyAgreement;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
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.api.crypto.KeyParser;
|
||||
import net.sf.briar.api.crypto.MessageDigest;
|
||||
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 SECRET_KEY_ALGO = "AES";
|
||||
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 int KEY_DERIVATION_IV_BYTES = 16; // 128 bits
|
||||
private static final String DIGEST_ALGO = "SHA-384";
|
||||
private static final String SIGNATURE_KEY_PAIR_ALGO = "ECDSA";
|
||||
private static final int SIGNATURE_KEY_PAIR_BITS = 384;
|
||||
private static final String SIGNATURE_ALGO = "ECDSA";
|
||||
private static final String TAG_CIPHER_ALGO = "AES/ECB/NoPadding";
|
||||
private static final String FRAME_CIPHER_ALGO = "AES/CTR/NoPadding";
|
||||
private static final String MAC_ALGO = "HMacSHA384";
|
||||
private static final String FRAME_CIPHER_ALGO = "AES/GCM/NoPadding";
|
||||
private static final String FRAME_PEEKING_CIPHER_ALGO = "AES/CTR/NoPadding";
|
||||
|
||||
// Labels for key derivation
|
||||
private static final byte[] TAG = { 'T', 'A', 'G' };
|
||||
private static final byte[] FRAME = { 'F', 'R', 'A', 'M', 'E' };
|
||||
private static final byte[] MAC = { 'M', 'A', 'C' };
|
||||
// Labels for secret derivation
|
||||
private static final byte[] FIRST = { 'F', 'I', 'R', 'S', 'T' };
|
||||
private static final byte[] NEXT = { 'N', 'E', 'X', 'T' };
|
||||
@@ -96,11 +95,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
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) {
|
||||
byte[] key = counterModeKdf(secret, label, context);
|
||||
return new ErasableKeyImpl(key, SECRET_KEY_ALGO);
|
||||
@@ -289,11 +283,19 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public Mac getMac() {
|
||||
public Cipher getFramePeekingCipher() {
|
||||
try {
|
||||
return Mac.getInstance(MAC_ALGO, PROVIDER);
|
||||
return Cipher.getInstance(FRAME_PEEKING_CIPHER_ALGO, PROVIDER);
|
||||
} catch(GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public IvEncoder getFrameIvEncoder() {
|
||||
return new FrameIvEncoder();
|
||||
}
|
||||
|
||||
public IvEncoder getFramePeekingIvEncoder() {
|
||||
return new FramePeekingIvEncoder();
|
||||
}
|
||||
}
|
||||
|
||||
26
components/net/sf/briar/crypto/FrameIvEncoder.java
Normal file
26
components/net/sf/briar/crypto/FrameIvEncoder.java
Normal 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);
|
||||
}
|
||||
}
|
||||
20
components/net/sf/briar/crypto/FramePeekingIvEncoder.java
Normal file
20
components/net/sf/briar/crypto/FramePeekingIvEncoder.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,10 @@ package net.sf.briar.transport;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.Mac;
|
||||
|
||||
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.api.transport.ConnectionReader;
|
||||
import net.sf.briar.api.transport.ConnectionReaderFactory;
|
||||
import net.sf.briar.util.ByteUtils;
|
||||
@@ -27,18 +27,16 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
|
||||
// Derive the keys and erase the secret
|
||||
ErasableKey tagKey = crypto.deriveTagKey(secret, initiator);
|
||||
ErasableKey frameKey = crypto.deriveFrameKey(secret, initiator);
|
||||
ErasableKey macKey = crypto.deriveMacKey(secret, initiator);
|
||||
ByteUtils.erase(secret);
|
||||
// Encryption
|
||||
// Create the reader
|
||||
Cipher tagCipher = crypto.getTagCipher();
|
||||
Cipher frameCipher = crypto.getFrameCipher();
|
||||
Cipher framePeekingCipher = crypto.getFramePeekingCipher();
|
||||
IvEncoder frameIvEncoder = crypto.getFrameIvEncoder();
|
||||
IvEncoder framePeekingIvEncoder = crypto.getFramePeekingIvEncoder();
|
||||
FrameReader encryption = new IncomingEncryptionLayerImpl(in, tagCipher,
|
||||
frameCipher, tagKey, frameKey, !initiator);
|
||||
// Authentication
|
||||
Mac mac = crypto.getMac();
|
||||
FrameReader authentication = new IncomingAuthenticationLayerImpl(
|
||||
encryption, mac, macKey);
|
||||
// Create the reader
|
||||
return new ConnectionReaderImpl(authentication);
|
||||
frameCipher, framePeekingCipher, frameIvEncoder,
|
||||
framePeekingIvEncoder, tagKey, frameKey, !initiator);
|
||||
return new ConnectionReaderImpl(encryption);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,14 +53,20 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
|
||||
|
||||
private boolean readFrame() throws IOException {
|
||||
assert length == 0;
|
||||
if(HeaderEncoder.isLastFrame(frame.getBuffer())) {
|
||||
byte[] buf = frame.getBuffer();
|
||||
if(HeaderEncoder.isLastFrame(buf)) {
|
||||
length = -1;
|
||||
return false;
|
||||
}
|
||||
frame.reset();
|
||||
if(!in.readFrame(frame)) throw new FormatException();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package net.sf.briar.transport;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.Mac;
|
||||
|
||||
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.api.transport.ConnectionWriter;
|
||||
import net.sf.briar.api.transport.ConnectionWriterFactory;
|
||||
import net.sf.briar.util.ByteUtils;
|
||||
@@ -27,18 +27,14 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
|
||||
// Derive the keys and erase the secret
|
||||
ErasableKey tagKey = crypto.deriveTagKey(secret, initiator);
|
||||
ErasableKey frameKey = crypto.deriveFrameKey(secret, initiator);
|
||||
ErasableKey macKey = crypto.deriveMacKey(secret, initiator);
|
||||
ByteUtils.erase(secret);
|
||||
// Encryption
|
||||
// Create the writer
|
||||
Cipher tagCipher = crypto.getTagCipher();
|
||||
Cipher frameCipher = crypto.getFrameCipher();
|
||||
IvEncoder frameIvEncoder = crypto.getFrameIvEncoder();
|
||||
FrameWriter encryption = new OutgoingEncryptionLayerImpl(
|
||||
out, capacity, tagCipher, frameCipher, tagKey, frameKey);
|
||||
// Authentication
|
||||
Mac mac = crypto.getMac();
|
||||
FrameWriter authentication =
|
||||
new OutgoingAuthenticationLayerImpl(encryption, mac, macKey);
|
||||
// Create the writer
|
||||
return new ConnectionWriterImpl(authentication);
|
||||
out, capacity, tagCipher, frameCipher, frameIvEncoder, tagKey,
|
||||
frameKey);
|
||||
return new ConnectionWriterImpl(encryption);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ class ConnectionWriterImpl extends OutputStream implements ConnectionWriter {
|
||||
assert payload >= 0;
|
||||
HeaderEncoder.encodeHeader(frame.getBuffer(), frameNumber, payload, 0,
|
||||
lastFrame);
|
||||
frame.setLength(offset + MAC_LENGTH);
|
||||
frame.setLength(offset);
|
||||
out.writeFrame(frame);
|
||||
frame.reset();
|
||||
offset = FRAME_HEADER_LENGTH;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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;
|
||||
|
||||
class Frame {
|
||||
@@ -24,7 +23,7 @@ class Frame {
|
||||
}
|
||||
|
||||
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();
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -15,31 +15,38 @@ import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
import net.sf.briar.api.FormatException;
|
||||
import net.sf.briar.api.crypto.ErasableKey;
|
||||
import net.sf.briar.api.crypto.IvEncoder;
|
||||
|
||||
class IncomingEncryptionLayerImpl implements FrameReader {
|
||||
|
||||
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 int blockSize;
|
||||
private final byte[] iv, ciphertext;
|
||||
private final byte[] frameIv, framePeekingIv, ciphertext;
|
||||
|
||||
private boolean readTag;
|
||||
private long frameNumber;
|
||||
|
||||
IncomingEncryptionLayerImpl(InputStream in, Cipher tagCipher,
|
||||
Cipher frameCipher, ErasableKey tagKey, ErasableKey frameKey,
|
||||
boolean readTag) {
|
||||
Cipher frameCipher, Cipher framePeekingCipher,
|
||||
IvEncoder frameIvEncoder, IvEncoder framePeekingIvEncoder,
|
||||
ErasableKey tagKey, ErasableKey frameKey, boolean readTag) {
|
||||
this.in = in;
|
||||
this.tagCipher = tagCipher;
|
||||
this.frameCipher = frameCipher;
|
||||
this.framePeekingCipher = framePeekingCipher;
|
||||
this.frameIvEncoder = frameIvEncoder;
|
||||
this.framePeekingIvEncoder = framePeekingIvEncoder;
|
||||
this.tagKey = tagKey;
|
||||
this.frameKey = frameKey;
|
||||
this.readTag = readTag;
|
||||
blockSize = frameCipher.getBlockSize();
|
||||
if(blockSize < FRAME_HEADER_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
iv = IvEncoder.encodeIv(0L, blockSize);
|
||||
frameIv = frameIvEncoder.encodeIv(0L);
|
||||
framePeekingIv = framePeekingIvEncoder.encodeIv(0L);
|
||||
ciphertext = new byte[MAX_FRAME_LENGTH];
|
||||
frameNumber = 0L;
|
||||
}
|
||||
@@ -65,21 +72,18 @@ class IncomingEncryptionLayerImpl implements FrameReader {
|
||||
int offset = 0;
|
||||
while(offset < blockSize) {
|
||||
int read = in.read(ciphertext, offset, blockSize - offset);
|
||||
if(read == -1) {
|
||||
if(offset == 0 && !readTag) return false;
|
||||
throw new EOFException();
|
||||
}
|
||||
if(read == -1) throw new EOFException();
|
||||
offset += read;
|
||||
}
|
||||
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();
|
||||
try {
|
||||
IvEncoder.updateIv(iv, frameNumber);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
frameCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec);
|
||||
int decrypted = frameCipher.update(ciphertext, 0, blockSize,
|
||||
plaintext);
|
||||
framePeekingCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec);
|
||||
int decrypted = framePeekingCipher.update(ciphertext, 0,
|
||||
blockSize, plaintext);
|
||||
if(decrypted != blockSize) throw new RuntimeException();
|
||||
} catch(GeneralSecurityException badCipher) {
|
||||
throw new RuntimeException(badCipher);
|
||||
@@ -95,16 +99,19 @@ class IncomingEncryptionLayerImpl implements FrameReader {
|
||||
if(read == -1) throw new EOFException();
|
||||
offset += read;
|
||||
}
|
||||
// Decrypt the remainder of the frame
|
||||
// Decrypt and authenticate the entire frame
|
||||
frameIvEncoder.updateIv(frameIv, frameNumber);
|
||||
ivSpec = new IvParameterSpec(frameIv);
|
||||
try {
|
||||
int decrypted = frameCipher.doFinal(ciphertext, blockSize,
|
||||
length - blockSize, plaintext, blockSize);
|
||||
if(decrypted != length - blockSize)
|
||||
frameCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec);
|
||||
int decrypted = frameCipher.doFinal(ciphertext, 0, length,
|
||||
plaintext);
|
||||
if(decrypted != length - MAC_LENGTH)
|
||||
throw new RuntimeException();
|
||||
} catch(GeneralSecurityException badCipher) {
|
||||
throw new RuntimeException(badCipher);
|
||||
}
|
||||
f.setLength(length);
|
||||
f.setLength(length - MAC_LENGTH);
|
||||
frameNumber++;
|
||||
return true;
|
||||
} catch(IOException e) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
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.TAG_LENGTH;
|
||||
|
||||
@@ -11,56 +12,58 @@ import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
import net.sf.briar.api.crypto.ErasableKey;
|
||||
import net.sf.briar.api.crypto.IvEncoder;
|
||||
|
||||
class OutgoingEncryptionLayerImpl implements FrameWriter {
|
||||
|
||||
private final OutputStream out;
|
||||
private final Cipher tagCipher, frameCipher;
|
||||
private final IvEncoder frameIvEncoder;
|
||||
private final ErasableKey tagKey, frameKey;
|
||||
private final byte[] iv, ciphertext;
|
||||
private final byte[] frameIv, ciphertext;
|
||||
|
||||
private long capacity, frameNumber;
|
||||
|
||||
OutgoingEncryptionLayerImpl(OutputStream out, long capacity,
|
||||
Cipher tagCipher, Cipher frameCipher, ErasableKey tagKey,
|
||||
ErasableKey frameKey) {
|
||||
Cipher tagCipher, Cipher frameCipher, IvEncoder frameIvEncoder,
|
||||
ErasableKey tagKey, ErasableKey frameKey) {
|
||||
this.out = out;
|
||||
this.capacity = capacity;
|
||||
this.tagCipher = tagCipher;
|
||||
this.frameCipher = frameCipher;
|
||||
this.frameIvEncoder = frameIvEncoder;
|
||||
this.tagKey = tagKey;
|
||||
this.frameKey = frameKey;
|
||||
iv = IvEncoder.encodeIv(0L, frameCipher.getBlockSize());
|
||||
frameIv = frameIvEncoder.encodeIv(0L);
|
||||
ciphertext = new byte[TAG_LENGTH + MAX_FRAME_LENGTH];
|
||||
frameNumber = 0L;
|
||||
}
|
||||
|
||||
public void writeFrame(Frame f) throws IOException {
|
||||
byte[] plaintext = f.getBuffer();
|
||||
int length = f.getLength();
|
||||
int offset = 0;
|
||||
int offset = 0, length = f.getLength();
|
||||
if(frameNumber == 0) {
|
||||
TagEncoder.encodeTag(ciphertext, tagCipher, tagKey);
|
||||
offset = TAG_LENGTH;
|
||||
}
|
||||
IvEncoder.updateIv(iv, frameNumber);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
frameIvEncoder.updateIv(frameIv, frameNumber);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(frameIv);
|
||||
try {
|
||||
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
|
||||
int encrypted = frameCipher.doFinal(plaintext, 0, length,
|
||||
ciphertext, offset);
|
||||
if(encrypted != length) throw new RuntimeException();
|
||||
if(encrypted != length + MAC_LENGTH) throw new RuntimeException();
|
||||
} catch(GeneralSecurityException badCipher) {
|
||||
throw new RuntimeException(badCipher);
|
||||
}
|
||||
try {
|
||||
out.write(ciphertext, 0, offset + length);
|
||||
out.write(ciphertext, 0, offset + length + MAC_LENGTH);
|
||||
} catch(IOException e) {
|
||||
frameKey.erase();
|
||||
tagKey.erase();
|
||||
throw e;
|
||||
}
|
||||
capacity -= offset + length;
|
||||
capacity -= offset + length + MAC_LENGTH;
|
||||
frameNumber++;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user