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

@@ -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);
}
}