The word "tag" was overloaded, so from now on use "tag" for the

predefined tags in the protocol and serial components, and "IV" for
the encrypted IVs used to identify connections in the transport
component.
This commit is contained in:
akwizgran
2011-08-19 11:15:35 +02:00
parent 2411e2008b
commit 9dea4d0299
19 changed files with 239 additions and 250 deletions

View File

@@ -10,31 +10,31 @@ import javax.crypto.SecretKey;
public interface CryptoComponent {
SecretKey deriveIncomingMacKey(byte[] secret);
SecretKey deriveIncomingFrameKey(byte[] secret);
SecretKey deriveIncomingTagKey(byte[] secret);
SecretKey deriveIncomingIvKey(byte[] secret);
SecretKey deriveOutgoingMacKey(byte[] secret);
SecretKey deriveIncomingMacKey(byte[] secret);
SecretKey deriveOutgoingFrameKey(byte[] secret);
SecretKey deriveOutgoingTagKey(byte[] secret);
SecretKey deriveOutgoingIvKey(byte[] secret);
SecretKey deriveOutgoingMacKey(byte[] secret);
KeyPair generateKeyPair();
SecretKey generateSecretKey();
Cipher getFrameCipher();
Cipher getIvCipher();
KeyParser getKeyParser();
Mac getMac();
MessageDigest getMessageDigest();
Cipher getFrameCipher();
Signature getSignature();
Cipher getTagCipher();
}

View File

@@ -10,8 +10,9 @@ import net.sf.briar.api.db.DbException;
public interface ConnectionRecogniser {
/**
* Returns the ID of the contact who created the tag if the connection
* should be accepted, or null if the connection should be rejected.
* Returns the ID of the contact who created the encrypted IV if the
* connection should be accepted, or null if the connection should be
* rejected.
*/
ContactId acceptConnection(byte[] tag) throws DbException;
ContactId acceptConnection(byte[] encryptedIv) throws DbException;
}

View File

@@ -7,6 +7,9 @@ public interface TransportConstants {
*/
static final int MAX_FRAME_LENGTH = 65536; // 2^16
/** The length in bytes of the tag that uniquely identifies a connection. */
static final int TAG_LENGTH = 16;
/**
* The length in bytes of the encrypted IV that uniquely identifies a
* connection.
*/
static final int IV_LENGTH = 16;
}

View File

@@ -37,12 +37,12 @@ class CryptoComponentImpl implements CryptoComponent {
private static final String KEY_PAIR_ALGO = "ECDSA";
private static final int KEY_PAIR_BITS = 256;
private static final String SECRET_STORAGE_ALGO = "AES/CTR/NoPadding";
private static final String MAC_ALGO = "HMacSHA256";
private static final String PACKET_CIPHER_ALGO = "AES/CTR/NoPadding";
private static final String FRAME_CIPHER_ALGO = "AES/CTR/NoPadding";
private static final String SECRET_KEY_ALGO = "AES";
private static final int SECRET_KEY_BITS = 256;
private static final String IV_CIPHER_ALGO = "AES/ECB/NoPadding";
private static final String MAC_ALGO = "HMacSHA256";
private static final String SIGNATURE_ALGO = "ECDSA";
private static final String TAG_CIPHER_ALGO = "AES/ECB/NoPadding";
private final SecretKey secretStorageKey;
private final KeyParser keyParser;
@@ -68,14 +68,14 @@ class CryptoComponentImpl implements CryptoComponent {
}
}
public SecretKey deriveIncomingMacKey(byte[] secret) {
public SecretKey deriveIncomingFrameKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveMacKey(s, !s.getAlice());
return deriveFrameKey(s, !s.getAlice());
}
private SecretKey deriveMacKey(SharedSecret s, boolean alice) {
if(alice) return deriveKey("MACA", s.getIv(), s.getCiphertext());
else return deriveKey("MACB", s.getIv(), s.getCiphertext());
private SecretKey deriveFrameKey(SharedSecret s, boolean alice) {
if(alice) return deriveKey("F_A", s.getIv(), s.getCiphertext());
else return deriveKey("F_B", s.getIv(), s.getCiphertext());
}
private SecretKey deriveKey(String name, IvParameterSpec iv,
@@ -114,29 +114,24 @@ class CryptoComponentImpl implements CryptoComponent {
}
}
public SecretKey deriveIncomingFrameKey(byte[] secret) {
public SecretKey deriveIncomingIvKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveFrameKey(s, !s.getAlice());
return deriveIvKey(s, !s.getAlice());
}
private SecretKey deriveFrameKey(SharedSecret s, boolean alice) {
if(alice) return deriveKey("PKTA", s.getIv(), s.getCiphertext());
else return deriveKey("PKTB", s.getIv(), s.getCiphertext());
private SecretKey deriveIvKey(SharedSecret s, boolean alice) {
if(alice) return deriveKey("I_A", s.getIv(), s.getCiphertext());
else return deriveKey("I_B", s.getIv(), s.getCiphertext());
}
public SecretKey deriveIncomingTagKey(byte[] secret) {
public SecretKey deriveIncomingMacKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveTagKey(s, !s.getAlice());
return deriveMacKey(s, !s.getAlice());
}
private SecretKey deriveTagKey(SharedSecret s, boolean alice) {
if(alice) return deriveKey("TAGA", s.getIv(), s.getCiphertext());
else return deriveKey("TAGB", s.getIv(), s.getCiphertext());
}
public SecretKey deriveOutgoingMacKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveMacKey(s, s.getAlice());
private SecretKey deriveMacKey(SharedSecret s, boolean alice) {
if(alice) return deriveKey("M_A", s.getIv(), s.getCiphertext());
else return deriveKey("M_B", s.getIv(), s.getCiphertext());
}
public SecretKey deriveOutgoingFrameKey(byte[] secret) {
@@ -144,9 +139,14 @@ class CryptoComponentImpl implements CryptoComponent {
return deriveFrameKey(s, s.getAlice());
}
public SecretKey deriveOutgoingTagKey(byte[] secret) {
public SecretKey deriveOutgoingIvKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveTagKey(s, s.getAlice());
return deriveIvKey(s, s.getAlice());
}
public SecretKey deriveOutgoingMacKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveMacKey(s, s.getAlice());
}
public KeyPair generateKeyPair() {
@@ -157,6 +157,30 @@ class CryptoComponentImpl implements CryptoComponent {
return keyGenerator.generateKey();
}
public Cipher getFrameCipher() {
try {
return Cipher.getInstance(FRAME_CIPHER_ALGO, PROVIDER);
} catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch(NoSuchPaddingException e) {
throw new RuntimeException(e);
} catch(NoSuchProviderException e) {
throw new RuntimeException(e);
}
}
public Cipher getIvCipher() {
try {
return Cipher.getInstance(IV_CIPHER_ALGO, PROVIDER);
} catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch(NoSuchPaddingException e) {
throw new RuntimeException(e);
} catch(NoSuchProviderException e) {
throw new RuntimeException(e);
}
}
public KeyParser getKeyParser() {
return keyParser;
}
@@ -181,18 +205,6 @@ class CryptoComponentImpl implements CryptoComponent {
}
}
public Cipher getFrameCipher() {
try {
return Cipher.getInstance(PACKET_CIPHER_ALGO, PROVIDER);
} catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch(NoSuchPaddingException e) {
throw new RuntimeException(e);
} catch(NoSuchProviderException e) {
throw new RuntimeException(e);
}
}
public Signature getSignature() {
try {
return Signature.getInstance(SIGNATURE_ALGO, PROVIDER);
@@ -202,16 +214,4 @@ class CryptoComponentImpl implements CryptoComponent {
throw new RuntimeException(e);
}
}
public Cipher getTagCipher() {
try {
return Cipher.getInstance(TAG_CIPHER_ALGO, PROVIDER);
} catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch(NoSuchPaddingException e) {
throw new RuntimeException(e);
} catch(NoSuchProviderException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -1,6 +1,6 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH;
import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import java.io.EOFException;
@@ -24,7 +24,7 @@ implements ConnectionDecrypter {
private final long connection;
private final Cipher frameCipher;
private final SecretKey frameKey;
private final byte[] buf, tag;
private final byte[] buf, iv;
private int bufOff = 0, bufLen = 0;
private long frame = 0L;
@@ -37,8 +37,8 @@ implements ConnectionDecrypter {
this.connection = connection;
this.frameCipher = frameCipher;
this.frameKey = frameKey;
buf = new byte[TAG_LENGTH];
tag = new byte[TAG_LENGTH];
buf = new byte[IV_LENGTH];
iv = new byte[IV_LENGTH];
}
public InputStream getInputStream() {
@@ -132,11 +132,11 @@ implements ConnectionDecrypter {
private void initialiseCipher() {
assert betweenFrames;
if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException();
TagEncoder.encodeTag(tag, transportId, connection, frame);
// Use the plaintext tag to initialise the packet cipher
IvParameterSpec iv = new IvParameterSpec(tag);
IvEncoder.encodeIv(iv, transportId, connection, frame);
// Use the plaintext IV to initialise the frame cipher
IvParameterSpec ivSpec = new IvParameterSpec(iv);
try {
frameCipher.init(Cipher.DECRYPT_MODE, frameKey, iv);
frameCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec);
} catch(InvalidAlgorithmParameterException badIv) {
throw new RuntimeException(badIv);
} catch(InvalidKeyException badKey) {

View File

@@ -1,6 +1,6 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH;
import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import java.io.FilterOutputStream;
@@ -20,29 +20,29 @@ implements ConnectionEncrypter {
private final int transportId;
private final long connection;
private final Cipher tagCipher, frameCipher;
private final Cipher ivCipher, frameCipher;
private final SecretKey frameKey;
private final byte[] tag;
private final byte[] iv;
private long frame = 0L;
private boolean started = false, betweenFrames = false;
ConnectionEncrypterImpl(OutputStream out, int transportId,
long connection, Cipher tagCipher, Cipher frameCipher,
SecretKey tagKey, SecretKey frameKey) {
long connection, Cipher ivCipher, Cipher frameCipher,
SecretKey ivKey, SecretKey frameKey) {
super(out);
this.transportId = transportId;
this.connection = connection;
this.tagCipher = tagCipher;
this.ivCipher = ivCipher;
this.frameCipher = frameCipher;
this.frameKey = frameKey;
tag = new byte[TAG_LENGTH];
iv = new byte[IV_LENGTH];
try {
tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
ivCipher.init(Cipher.ENCRYPT_MODE, ivKey);
} catch(InvalidKeyException badKey) {
throw new IllegalArgumentException(badKey);
}
if(tagCipher.getOutputSize(TAG_LENGTH) != TAG_LENGTH)
if(ivCipher.getOutputSize(IV_LENGTH) != IV_LENGTH)
throw new IllegalArgumentException();
}
@@ -64,7 +64,7 @@ implements ConnectionEncrypter {
@Override
public void write(int b) throws IOException {
if(!started) writeTag();
if(!started) writeIv();
if(betweenFrames) initialiseCipher();
byte[] ciphertext = frameCipher.update(new byte[] {(byte) b});
if(ciphertext != null) out.write(ciphertext);
@@ -77,18 +77,18 @@ implements ConnectionEncrypter {
@Override
public void write(byte[] b, int off, int len) throws IOException {
if(!started) writeTag();
if(!started) writeIv();
if(betweenFrames) initialiseCipher();
byte[] ciphertext = frameCipher.update(b, off, len);
if(ciphertext != null) out.write(ciphertext);
}
private void writeTag() throws IOException {
private void writeIv() throws IOException {
assert !started;
assert !betweenFrames;
TagEncoder.encodeTag(tag, transportId, connection, 0L);
IvEncoder.encodeIv(iv, transportId, connection, 0L);
try {
out.write(tagCipher.doFinal(tag));
out.write(ivCipher.doFinal(iv));
} catch(BadPaddingException badCipher) {
throw new IOException(badCipher);
} catch(IllegalBlockSizeException badCipher) {
@@ -102,10 +102,10 @@ implements ConnectionEncrypter {
assert started;
assert betweenFrames;
if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException();
TagEncoder.encodeTag(tag, transportId, connection, frame);
IvParameterSpec iv = new IvParameterSpec(tag);
IvEncoder.encodeIv(iv, transportId, connection, frame);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
try {
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, iv);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
} catch(InvalidAlgorithmParameterException badIv) {
throw new RuntimeException(badIv);
} catch(InvalidKeyException badKey) {

View File

@@ -1,6 +1,6 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH;
import java.security.InvalidKeyException;
import java.util.HashMap;
@@ -27,9 +27,9 @@ DatabaseListener {
private final int transportId;
private final CryptoComponent crypto;
private final DatabaseComponent db;
private final Map<Bytes, ContactId> tagToContact;
private final Map<Bytes, Long> tagToConnectionNumber;
private final Map<ContactId, Map<Long, Bytes>> contactToTags;
private final Map<Bytes, ContactId> ivToContact;
private final Map<Bytes, Long> ivToConnectionNumber;
private final Map<ContactId, Map<Long, Bytes>> contactToIvs;
private final Map<ContactId, Cipher> contactToCipher;
private final Map<ContactId, ConnectionWindow> contactToWindow;
private boolean initialised = false;
@@ -40,9 +40,9 @@ DatabaseListener {
this.crypto = crypto;
this.db = db;
// FIXME: There's probably a tidier way of maintaining all this state
tagToContact = new HashMap<Bytes, ContactId>();
tagToConnectionNumber = new HashMap<Bytes, Long>();
contactToTags = new HashMap<ContactId, Map<Long, Bytes>>();
ivToContact = new HashMap<Bytes, ContactId>();
ivToConnectionNumber = new HashMap<Bytes, Long>();
contactToIvs = new HashMap<ContactId, Map<Long, Bytes>>();
contactToCipher = new HashMap<ContactId, Cipher>();
contactToWindow = new HashMap<ContactId, ConnectionWindow>();
db.addListener(this);
@@ -51,26 +51,26 @@ DatabaseListener {
private synchronized void initialise() throws DbException {
for(ContactId c : db.getContacts()) {
try {
// Initialise and store the contact's tag cipher
// Initialise and store the contact's IV cipher
byte[] secret = db.getSharedSecret(c);
SecretKey tagKey = crypto.deriveIncomingTagKey(secret);
Cipher cipher = crypto.getTagCipher();
SecretKey ivKey = crypto.deriveIncomingIvKey(secret);
Cipher cipher = crypto.getIvCipher();
try {
cipher.init(Cipher.ENCRYPT_MODE, tagKey);
cipher.init(Cipher.ENCRYPT_MODE, ivKey);
} catch(InvalidKeyException badKey) {
throw new RuntimeException(badKey);
}
contactToCipher.put(c, cipher);
// Calculate the tags for the contact's connection window
// Calculate the IVs for the contact's connection window
ConnectionWindow w = db.getConnectionWindow(c, transportId);
Map<Long, Bytes> tags = new HashMap<Long, Bytes>();
Map<Long, Bytes> ivs = new HashMap<Long, Bytes>();
for(Long unseen : w.getUnseenConnectionNumbers()) {
Bytes expectedTag = new Bytes(calculateTag(c, unseen));
tagToContact.put(expectedTag, c);
tagToConnectionNumber.put(expectedTag, unseen);
tags.put(unseen, expectedTag);
Bytes expectedIv = new Bytes(encryptIv(c, unseen));
ivToContact.put(expectedIv, c);
ivToConnectionNumber.put(expectedIv, unseen);
ivs.put(unseen, expectedIv);
}
contactToTags.put(c, tags);
contactToIvs.put(c, ivs);
contactToWindow.put(c, w);
} catch(NoSuchContactException e) {
// The contact was removed after the call to getContacts()
@@ -80,12 +80,12 @@ DatabaseListener {
initialised = true;
}
private synchronized byte[] calculateTag(ContactId c, long connection) {
byte[] tag = TagEncoder.encodeTag(transportId, connection);
private synchronized byte[] encryptIv(ContactId c, long connection) {
byte[] iv = IvEncoder.encodeIv(transportId, connection);
Cipher cipher = contactToCipher.get(c);
assert cipher != null;
try {
return cipher.doFinal(tag);
return cipher.doFinal(iv);
} catch(BadPaddingException badCipher) {
throw new RuntimeException(badCipher);
} catch(IllegalBlockSizeException badCipher) {
@@ -93,36 +93,36 @@ DatabaseListener {
}
}
public synchronized ContactId acceptConnection(byte[] tag)
public synchronized ContactId acceptConnection(byte[] encryptedIv)
throws DbException {
if(tag.length != TAG_LENGTH)
if(encryptedIv.length != IV_LENGTH)
throw new IllegalArgumentException();
if(!initialised) initialise();
Bytes b = new Bytes(tag);
ContactId contactId = tagToContact.remove(b);
Long connection = tagToConnectionNumber.remove(b);
Bytes b = new Bytes(encryptedIv);
ContactId contactId = ivToContact.remove(b);
Long connection = ivToConnectionNumber.remove(b);
assert (contactId == null) == (connection == null);
if(contactId == null) return null;
// The tag was expected - update and save the connection window
// The IV was expected - update and save the connection window
ConnectionWindow w = contactToWindow.get(contactId);
assert w != null;
w.setSeen(connection);
db.setConnectionWindow(contactId, transportId, w);
// Update the set of expected tags
Map<Long, Bytes> oldTags = contactToTags.remove(contactId);
assert oldTags != null;
assert oldTags.containsKey(connection);
Map<Long, Bytes> newTags = new HashMap<Long, Bytes>();
// Update the set of expected IVs
Map<Long, Bytes> oldIvs = contactToIvs.remove(contactId);
assert oldIvs != null;
assert oldIvs.containsKey(connection);
Map<Long, Bytes> newIvs = new HashMap<Long, Bytes>();
for(Long unseen : w.getUnseenConnectionNumbers()) {
Bytes expectedTag = oldTags.get(unseen);
if(expectedTag == null) {
expectedTag = new Bytes(calculateTag(contactId, unseen));
tagToContact.put(expectedTag, contactId);
tagToConnectionNumber.put(expectedTag, connection);
Bytes expectedIv = oldIvs.get(unseen);
if(expectedIv == null) {
expectedIv = new Bytes(encryptIv(contactId, unseen));
ivToContact.put(expectedIv, contactId);
ivToConnectionNumber.put(expectedIv, connection);
}
newTags.put(unseen, expectedTag);
newIvs.put(unseen, expectedIv);
}
contactToTags.put(contactId, newTags);
contactToIvs.put(contactId, newIvs);
return contactId;
}

View File

@@ -25,9 +25,9 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
public ConnectionWriter createConnectionWriter(OutputStream out,
int transportId, long connection, byte[] secret) {
SecretKey macKey = crypto.deriveOutgoingMacKey(secret);
SecretKey tagKey = crypto.deriveOutgoingTagKey(secret);
SecretKey ivKey = crypto.deriveOutgoingIvKey(secret);
SecretKey frameKey = crypto.deriveOutgoingFrameKey(secret);
Cipher tagCipher = crypto.getTagCipher();
Cipher ivCipher = crypto.getIvCipher();
Cipher frameCipher = crypto.getFrameCipher();
Mac mac = crypto.getMac();
try {
@@ -36,7 +36,7 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
throw new IllegalArgumentException(badKey);
}
ConnectionEncrypter encrypter = new ConnectionEncrypterImpl(out,
transportId, connection, tagCipher, frameCipher, tagKey,
transportId, connection, ivCipher, frameCipher, ivKey,
frameKey);
return new ConnectionWriterImpl(encrypter, mac);
}

View File

@@ -0,0 +1,33 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH;
import net.sf.briar.util.ByteUtils;
class IvEncoder {
static byte[] encodeIv(int transportId, long connection) {
byte[] iv = new byte[IV_LENGTH];
// Encode the transport identifier as an unsigned 16-bit integer
ByteUtils.writeUint16(transportId, iv, 4);
// Encode the connection number as an unsigned 32-bit integer
ByteUtils.writeUint32(connection, iv, 6);
return iv;
}
static void encodeIv(byte[] iv, int transportId, long connection,
long frame) {
if(iv.length != IV_LENGTH) throw new IllegalArgumentException();
// The first 16 bits of the IV must be zero (reserved)
iv[0] = 0;
iv[1] = 0;
// Encode the transport identifier as an unsigned 16-bit integer
ByteUtils.writeUint16(transportId, iv, 4);
// Encode the connection number as an unsigned 32-bit integer
ByteUtils.writeUint32(connection, iv, 6);
// Encode the frame number as an unsigned 32-bit integer
ByteUtils.writeUint32(frame, iv, 10);
// The last 16 bits of the IV must be zero (block number)
iv[14] = 0;
iv[15] = 0;
}
}

View File

@@ -1,20 +0,0 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import net.sf.briar.util.ByteUtils;
class TagDecoder {
static boolean decodeTag(byte[] tag, int transportId, long connection) {
if(tag.length != TAG_LENGTH) return false;
// First 32 bits must be zero (reserved)
for(int i = 0; i < 4; i++) if(tag[i] != 0) return false;
// Transport identifier is encoded as an unsigned 16-bit integer
if(ByteUtils.readUint16(tag, 4) != transportId) return false;
// Connection number is encoded as an unsigned 32-bit integer
if(ByteUtils.readUint32(tag, 6) != connection) return false;
// Last 48 bits must be zero (frame number and block number)
for(int i = 10; i < 16; i++) if(tag[i] != 0) return false;
return true;
}
}

View File

@@ -1,31 +0,0 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import net.sf.briar.util.ByteUtils;
class TagEncoder {
static byte[] encodeTag(int transportId, long connection) {
byte[] tag = new byte[TAG_LENGTH];
// Encode the transport identifier as an unsigned 16-bit integer
ByteUtils.writeUint16(transportId, tag, 4);
// Encode the connection number as an unsigned 32-bit integer
ByteUtils.writeUint32(connection, tag, 6);
return tag;
}
static void encodeTag(byte[] tag, int transportId, long connection,
long frame) {
if(tag.length != TAG_LENGTH) throw new IllegalArgumentException();
// The first 16 bits of the tag must be zero (reserved)
ByteUtils.writeUint16(0, tag, 0);
// Encode the transport identifier as an unsigned 16-bit integer
ByteUtils.writeUint16(transportId, tag, 4);
// Encode the connection number as an unsigned 32-bit integer
ByteUtils.writeUint32(connection, tag, 6);
// Encode the frame number as an unsigned 32-bit integer
ByteUtils.writeUint32(frame, tag, 10);
// The last 16 bits of the tag must be zero (block number)
ByteUtils.writeUint16(0, tag, 14);
}
}

View File

@@ -185,10 +185,10 @@ public class FileReadWriteTest extends TestCase {
testWriteFile();
InputStream in = new FileInputStream(file);
byte[] firstTag = new byte[16];
byte[] iv = new byte[16];
int offset = 0;
while(offset < 16) {
int read = in.read(firstTag, offset, firstTag.length - offset);
int read = in.read(iv, offset, iv.length - offset);
if(read == -1) break;
offset += read;
}

View File

@@ -29,21 +29,21 @@ public class CryptoComponentTest extends TestCase {
crypto.deriveOutgoingMacKey(bobSecret));
assertEquals(crypto.deriveIncomingFrameKey(aliceSecret),
crypto.deriveOutgoingFrameKey(bobSecret));
assertEquals(crypto.deriveIncomingTagKey(aliceSecret),
crypto.deriveOutgoingTagKey(bobSecret));
assertEquals(crypto.deriveIncomingIvKey(aliceSecret),
crypto.deriveOutgoingIvKey(bobSecret));
// Check that Alice's outgoing keys match Bob's incoming keys
assertEquals(crypto.deriveOutgoingMacKey(aliceSecret),
crypto.deriveIncomingMacKey(bobSecret));
assertEquals(crypto.deriveOutgoingFrameKey(aliceSecret),
crypto.deriveIncomingFrameKey(bobSecret));
assertEquals(crypto.deriveOutgoingTagKey(aliceSecret),
crypto.deriveIncomingTagKey(bobSecret));
assertEquals(crypto.deriveOutgoingIvKey(aliceSecret),
crypto.deriveIncomingIvKey(bobSecret));
// Check that Alice's incoming and outgoing keys are different
assertFalse(crypto.deriveIncomingMacKey(aliceSecret).equals(
crypto.deriveOutgoingMacKey(aliceSecret)));
assertFalse(crypto.deriveIncomingFrameKey(aliceSecret).equals(
crypto.deriveOutgoingFrameKey(aliceSecret)));
assertFalse(crypto.deriveIncomingTagKey(aliceSecret).equals(
crypto.deriveOutgoingTagKey(aliceSecret)));
assertFalse(crypto.deriveIncomingIvKey(aliceSecret).equals(
crypto.deriveOutgoingIvKey(aliceSecret)));
}
}

View File

@@ -1,6 +1,6 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH;
import java.io.ByteArrayInputStream;
import java.util.Arrays;
@@ -54,16 +54,16 @@ public class ConnectionDecrypterImplTest extends TestCase {
public void testDecryption() throws Exception {
// Calculate the expected plaintext for the first frame
byte[] ciphertext = new byte[123];
byte[] ivBytes = new byte[TAG_LENGTH];
TagEncoder.encodeTag(ivBytes, transportId, connection, 0L);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
frameCipher.init(Cipher.DECRYPT_MODE, frameKey, iv);
byte[] iv = new byte[IV_LENGTH];
IvEncoder.encodeIv(iv, transportId, connection, 0L);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec);
byte[] plaintext = frameCipher.doFinal(ciphertext);
// Calculate the expected plaintext for the second frame
byte[] ciphertext1 = new byte[1234];
TagEncoder.encodeTag(ivBytes, transportId, connection, 1L);
iv = new IvParameterSpec(ivBytes);
frameCipher.init(Cipher.DECRYPT_MODE, frameKey, iv);
IvEncoder.encodeIv(iv, transportId, connection, 1L);
ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec);
byte[] plaintext1 = frameCipher.doFinal(ciphertext1);
assertEquals(ciphertext1.length, plaintext1.length);
// Concatenate the ciphertexts

View File

@@ -1,6 +1,6 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
@@ -22,8 +22,8 @@ public class ConnectionEncrypterImplTest extends TestCase {
private static final int MAC_LENGTH = 32;
private final Cipher tagCipher, frameCipher;
private final SecretKey tagKey, frameKey;
private final Cipher ivCipher, frameCipher;
private final SecretKey ivKey, frameKey;
private final int transportId = 1234;
private final long connection = 12345L;
@@ -31,9 +31,9 @@ public class ConnectionEncrypterImplTest extends TestCase {
super();
Injector i = Guice.createInjector(new CryptoModule());
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
tagCipher = crypto.getTagCipher();
ivCipher = crypto.getIvCipher();
frameCipher = crypto.getFrameCipher();
tagKey = crypto.generateSecretKey();
ivKey = crypto.generateSecretKey();
frameKey = crypto.generateSecretKey();
}
@@ -41,27 +41,26 @@ public class ConnectionEncrypterImplTest extends TestCase {
public void testSingleByteFrame() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ConnectionEncrypter e = new ConnectionEncrypterImpl(out, transportId,
connection, tagCipher, frameCipher, tagKey, frameKey);
connection, ivCipher, frameCipher, ivKey, frameKey);
e.getOutputStream().write((byte) 0);
e.writeMac(new byte[MAC_LENGTH]);
assertEquals(TAG_LENGTH + 1 + MAC_LENGTH, out.toByteArray().length);
assertEquals(IV_LENGTH + 1 + MAC_LENGTH, out.toByteArray().length);
}
@Test
public void testEncryption() throws Exception {
// Calculate the expected ciphertext for the tag
byte[] plaintextTag = TagEncoder.encodeTag(transportId, connection);
assertEquals(TAG_LENGTH, plaintextTag.length);
tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
byte[] tag = tagCipher.doFinal(plaintextTag);
assertEquals(TAG_LENGTH, tag.length);
// Calculate the expected ciphertext for the IV
byte[] iv = IvEncoder.encodeIv(transportId, connection);
assertEquals(IV_LENGTH, iv.length);
ivCipher.init(Cipher.ENCRYPT_MODE, ivKey);
byte[] encryptedIv = ivCipher.doFinal(iv);
assertEquals(IV_LENGTH, encryptedIv.length);
// Calculate the expected ciphertext for the first frame
byte[] plaintext = new byte[123];
byte[] plaintextMac = new byte[MAC_LENGTH];
byte[] ivBytes = new byte[TAG_LENGTH];
TagEncoder.encodeTag(ivBytes, transportId, connection, 0L);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, iv);
IvEncoder.encodeIv(iv, transportId, connection, 0L);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
byte[] ciphertext = new byte[plaintext.length + plaintextMac.length];
int offset = frameCipher.update(plaintext, 0, plaintext.length,
ciphertext);
@@ -69,9 +68,9 @@ public class ConnectionEncrypterImplTest extends TestCase {
offset);
// Calculate the expected ciphertext for the second frame
byte[] plaintext1 = new byte[1234];
TagEncoder.encodeTag(ivBytes, transportId, connection, 1L);
iv = new IvParameterSpec(ivBytes);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, iv);
IvEncoder.encodeIv(iv, transportId, connection, 1L);
ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
byte[] ciphertext1 = new byte[plaintext1.length + plaintextMac.length];
offset = frameCipher.update(plaintext1, 0, plaintext1.length,
ciphertext1);
@@ -79,14 +78,14 @@ public class ConnectionEncrypterImplTest extends TestCase {
offset);
// Concatenate the ciphertexts
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(tag);
out.write(encryptedIv);
out.write(ciphertext);
out.write(ciphertext1);
byte[] expected = out.toByteArray();
// Use a ConnectionEncrypter to encrypt the plaintext
out.reset();
ConnectionEncrypter e = new ConnectionEncrypterImpl(out, transportId,
connection, tagCipher, frameCipher, tagKey, frameKey);
connection, ivCipher, frameCipher, ivKey, frameKey);
e.getOutputStream().write(plaintext);
e.writeMac(plaintextMac);
e.getOutputStream().write(plaintext1);

View File

@@ -33,6 +33,8 @@ public class ConnectionReaderImplTest extends TestCase {
mac.init(crypto.generateSecretKey());
}
// FIXME: Test corner cases and corrupt frames
@Test
public void testSingleByteFrame() throws Exception {
// Six bytes for the header, one for the payload

View File

@@ -1,6 +1,6 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH;
import java.util.Collection;
import java.util.Collections;
@@ -41,7 +41,7 @@ public class ConnectionRecogniserImplTest extends TestCase {
}
@Test
public void testUnexpectedTag() throws Exception {
public void testUnexpectedIv() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{
@@ -55,18 +55,18 @@ public class ConnectionRecogniserImplTest extends TestCase {
}});
final ConnectionRecogniserImpl c =
new ConnectionRecogniserImpl(transportId, crypto, db);
assertNull(c.acceptConnection(new byte[TAG_LENGTH]));
assertNull(c.acceptConnection(new byte[IV_LENGTH]));
context.assertIsSatisfied();
}
@Test
public void testExpectedTag() throws Exception {
// Calculate the expected tag for connection number 3
SecretKey tagKey = crypto.deriveIncomingTagKey(secret);
Cipher tagCipher = crypto.getTagCipher();
tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
byte[] tag = TagEncoder.encodeTag(transportId, 3L);
byte[] encryptedTag = tagCipher.doFinal(tag);
public void testExpectedIv() throws Exception {
// Calculate the expected IV for connection number 3
SecretKey ivKey = crypto.deriveIncomingIvKey(secret);
Cipher ivCipher = crypto.getIvCipher();
ivCipher.init(Cipher.ENCRYPT_MODE, ivKey);
byte[] iv = IvEncoder.encodeIv(transportId, 3L);
byte[] encryptedIv = ivCipher.doFinal(iv);
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
@@ -83,10 +83,10 @@ public class ConnectionRecogniserImplTest extends TestCase {
}});
final ConnectionRecogniserImpl c =
new ConnectionRecogniserImpl(transportId, crypto, db);
// First time - the tag should be expected
assertEquals(contactId, c.acceptConnection(encryptedTag));
// Second time - the tag should no longer be expected
assertNull(c.acceptConnection(encryptedTag));
// First time - the IV should be expected
assertEquals(contactId, c.acceptConnection(encryptedIv));
// Second time - the IV should no longer be expected
assertNull(c.acceptConnection(encryptedIv));
// The window should have advanced
assertEquals(4L, connectionWindow.getCentre());
Collection<Long> unseen = connectionWindow.getUnseenConnectionNumbers();

View File

@@ -30,6 +30,8 @@ public class ConnectionWriterImplTest extends TestCase {
mac.init(crypto.generateSecretKey());
}
// FIXME: Test corner cases
@Test
public void testSingleByteFrame() throws Exception {
// Six bytes for the header, one for the payload

View File

@@ -1,6 +1,6 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -27,8 +27,8 @@ import com.google.inject.Injector;
public class FrameReadWriteTest extends TestCase {
private final CryptoComponent crypto;
private final Cipher tagCipher, frameCipher;
private final SecretKey macKey, tagKey, frameKey;
private final Cipher ivCipher, frameCipher;
private final SecretKey ivKey, frameKey, macKey;
private final Mac mac;
private final Random random;
private final byte[] secret = new byte[100];
@@ -39,24 +39,24 @@ public class FrameReadWriteTest extends TestCase {
super();
Injector i = Guice.createInjector(new CryptoModule());
crypto = i.getInstance(CryptoComponent.class);
tagCipher = crypto.getTagCipher();
ivCipher = crypto.getIvCipher();
frameCipher = crypto.getFrameCipher();
// Since we're sending packets to ourselves, we only need outgoing keys
macKey = crypto.deriveOutgoingMacKey(secret);
tagKey = crypto.deriveOutgoingTagKey(secret);
// Since we're sending frames to ourselves, we only need outgoing keys
ivKey = crypto.deriveOutgoingIvKey(secret);
frameKey = crypto.deriveOutgoingFrameKey(secret);
macKey = crypto.deriveOutgoingMacKey(secret);
mac = crypto.getMac();
random = new Random();
}
@Test
public void testWriteAndRead() throws Exception {
// Calculate the expected ciphertext for the tag
byte[] plaintextTag = TagEncoder.encodeTag(transportId, connection);
assertEquals(TAG_LENGTH, plaintextTag.length);
tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
byte[] tag = tagCipher.doFinal(plaintextTag);
assertEquals(TAG_LENGTH, tag.length);
// Calculate the expected ciphertext for the IV
byte[] iv = IvEncoder.encodeIv(transportId, connection);
assertEquals(IV_LENGTH, iv.length);
ivCipher.init(Cipher.ENCRYPT_MODE, ivKey);
byte[] encryptedIv = ivCipher.doFinal(iv);
assertEquals(IV_LENGTH, encryptedIv.length);
// Generate two random frames
byte[] frame = new byte[12345];
random.nextBytes(frame);
@@ -65,7 +65,7 @@ public class FrameReadWriteTest extends TestCase {
// Write the frames
ByteArrayOutputStream out = new ByteArrayOutputStream();
ConnectionEncrypter encrypter = new ConnectionEncrypterImpl(out,
transportId, connection, tagCipher, frameCipher, tagKey,
transportId, connection, ivCipher, frameCipher, ivKey,
frameKey);
mac.init(macKey);
ConnectionWriter writer = new ConnectionWriterImpl(encrypter, mac);
@@ -76,9 +76,9 @@ public class FrameReadWriteTest extends TestCase {
out1.flush();
// Read the frames back
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
byte[] recoveredTag = new byte[TAG_LENGTH];
assertEquals(TAG_LENGTH, in.read(recoveredTag));
assertTrue(Arrays.equals(tag, recoveredTag));
byte[] recoveredIv = new byte[IV_LENGTH];
assertEquals(IV_LENGTH, in.read(recoveredIv));
assertTrue(Arrays.equals(encryptedIv, recoveredIv));
ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in,
transportId, connection, frameCipher, frameKey);
ConnectionReader reader = new ConnectionReaderImpl(decrypter, mac);