Store the incoming and outgoing secrets separately.

This commit is contained in:
akwizgran
2011-11-15 16:07:14 +00:00
parent f41d48eb9f
commit 6a15c03e81
26 changed files with 200 additions and 336 deletions

View File

@@ -53,17 +53,22 @@ class CryptoComponentImpl implements CryptoComponent {
}
}
public ErasableKey deriveIncomingFrameKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveFrameKey(s, !s.getAlice());
public ErasableKey deriveFrameKey(byte[] source, boolean initiator) {
if(initiator) return deriveKey("FRAME_I", source);
else return deriveKey("FRAME_R", source);
}
private ErasableKey deriveFrameKey(SharedSecret s, boolean alice) {
if(alice) return deriveKey("F_A", s.getSecret());
else return deriveKey("F_B", s.getSecret());
public ErasableKey deriveIvKey(byte[] source, boolean initiator) {
if(initiator) return deriveKey("IV_I", source);
else return deriveKey("IV_R", source);
}
private ErasableKey deriveKey(String name, byte[] secret) {
public ErasableKey deriveMacKey(byte[] source, boolean initiator) {
if(initiator) return deriveKey("MAC_I", source);
else return deriveKey("MAC_R", source);
}
private ErasableKey deriveKey(String name, byte[] source) {
MessageDigest digest = getMessageDigest();
assert digest.getDigestLength() == SECRET_KEY_BYTES;
try {
@@ -71,49 +76,20 @@ class CryptoComponentImpl implements CryptoComponent {
} catch(UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
digest.update(secret);
digest.update(source);
return new ErasableKeyImpl(digest.digest(), SECRET_KEY_ALGO);
}
public ErasableKey deriveIncomingIvKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveIvKey(s, !s.getAlice());
}
private ErasableKey deriveIvKey(SharedSecret s, boolean alice) {
if(alice) return deriveKey("I_A", s.getSecret());
else return deriveKey("I_B", s.getSecret());
}
public ErasableKey deriveIncomingMacKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveMacKey(s, !s.getAlice());
}
private ErasableKey deriveMacKey(SharedSecret s, boolean alice) {
if(alice) return deriveKey("M_A", s.getSecret());
else return deriveKey("M_B", s.getSecret());
}
public ErasableKey deriveOutgoingFrameKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveFrameKey(s, s.getAlice());
}
public ErasableKey deriveOutgoingIvKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveIvKey(s, s.getAlice());
}
public ErasableKey deriveOutgoingMacKey(byte[] secret) {
SharedSecret s = new SharedSecret(secret);
return deriveMacKey(s, s.getAlice());
}
public KeyPair generateKeyPair() {
return keyPairGenerator.generateKeyPair();
}
public ErasableKey generateTestKey() {
byte[] b = new byte[SECRET_KEY_BYTES];
getSecureRandom().nextBytes(b);
return new ErasableKeyImpl(b, SECRET_KEY_ALGO);
}
public Cipher getFrameCipher() {
try {
return Cipher.getInstance(FRAME_CIPHER_ALGO, PROVIDER);
@@ -177,10 +153,4 @@ class CryptoComponentImpl implements CryptoComponent {
throw new RuntimeException(e);
}
}
public ErasableKey generateTestKey() {
byte[] b = new byte[SECRET_KEY_BYTES];
getSecureRandom().nextBytes(b);
return new ErasableKeyImpl(b, SECRET_KEY_ALGO);
}
}

View File

@@ -1,58 +0,0 @@
package net.sf.briar.crypto;
/**
* A shared secret from which authentication and encryption keys can be derived.
* The secret carries a flag indicating whether Alice's keys or Bob's keys
* should be derived from the secret. When two parties agree on a shared secret,
* they must decide which of them will derive Alice's keys and which Bob's.
*/
class SharedSecret {
private final boolean alice;
private final byte[] secret;
SharedSecret(byte[] b) {
if(b.length < 2) throw new IllegalArgumentException();
switch(b[0]) {
case 0:
alice = false;
break;
case 1:
alice = true;
break;
default:
throw new IllegalArgumentException();
}
secret = new byte[b.length - 1];
System.arraycopy(b, 1, secret, 0, secret.length);
}
SharedSecret(boolean alice, byte[] secret) {
this.alice = alice;
this.secret = secret;
}
/**
* Returns true if we should play the role of Alice in connections using
* this secret, or false if we should play the role of Bob.
*/
boolean getAlice() {
return alice;
}
/** Returns the shared secret. */
byte[] getSecret() {
return secret;
}
/**
* Returns a raw representation of this object, suitable for storing in the
* database.
*/
byte[] getBytes() {
byte[] b = new byte[1 + secret.length];
if(alice) b[0] = (byte) 1;
System.arraycopy(secret, 0, b, 1, secret.length);
return b;
}
}

View File

@@ -80,12 +80,13 @@ interface Database<T> {
void addBatchToAck(T txn, ContactId c, BatchId b) throws DbException;
/**
* Adds a new contact to the database with the given secret and returns an
* Adds a new contact to the database with the given secrets and returns an
* ID for the contact.
* <p>
* Locking: contact write.
*/
ContactId addContact(T txn, byte[] secret) throws DbException;
ContactId addContact(T txn, byte[] incomingSecret, byte[] outgoingSecret)
throws DbException;
/**
* Returns false if the given message is already in the database. Otherwise
@@ -376,7 +377,8 @@ interface Database<T> {
* <p>
* Locking: contact read.
*/
byte[] getSharedSecret(T txn, ContactId c) throws DbException;
byte[] getSharedSecret(T txn, ContactId c, boolean incoming)
throws DbException;
/**
* Returns true if the given message has been starred.

View File

@@ -135,14 +135,15 @@ DatabaseCleaner.Callback {
}
}
public ContactId addContact(byte[] secret) throws DbException {
public ContactId addContact(byte[] incomingSecret, byte[] outgoingSecret)
throws DbException {
if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact");
ContactId c;
contactLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
c = db.addContact(txn, secret);
c = db.addContact(txn, incomingSecret, outgoingSecret);
db.commitTransaction(txn);
if(LOG.isLoggable(Level.FINE)) LOG.fine("Added contact " + c);
} catch(DbException e) {
@@ -905,13 +906,14 @@ DatabaseCleaner.Callback {
}
}
public byte[] getSharedSecret(ContactId c) throws DbException {
public byte[] getSharedSecret(ContactId c, boolean incoming)
throws DbException {
contactLock.readLock().lock();
try {
if(!containsContact(c)) throw new NoSuchContactException();
T txn = db.startTransaction();
try {
byte[] secret = db.getSharedSecret(txn, c);
byte[] secret = db.getSharedSecret(txn, c, incoming);
db.commitTransaction(txn);
return secret;
} catch(DbException e) {

View File

@@ -56,7 +56,8 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_CONTACTS =
"CREATE TABLE contacts"
+ " (contactId COUNTER,"
+ " secret BINARY NOT NULL,"
+ " incomingSecret BINARY NOT NULL,"
+ " outgoingSecret BINARY NOT NULL,"
+ " PRIMARY KEY (contactId))";
private static final String CREATE_MESSAGES =
@@ -509,15 +510,17 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public ContactId addContact(Connection txn, byte[] secret)
throws DbException {
public ContactId addContact(Connection txn, byte[] incomingSecret,
byte[] outgoingSecret) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
// Create a new contact row
String sql = "INSERT INTO contacts (secret) VALUES (?)";
String sql = "INSERT INTO contacts (incomingSecret, outgoingSecret)"
+ " VALUES (?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, secret);
ps.setBytes(1, incomingSecret);
ps.setBytes(2, outgoingSecret);
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
@@ -1643,12 +1646,13 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public byte[] getSharedSecret(Connection txn, ContactId c)
public byte[] getSharedSecret(Connection txn, ContactId c, boolean incoming)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT secret FROM contacts WHERE contactId = ?";
String col = incoming ? "incomingSecret" : "outgoingSecret";
String sql = "SELECT " + col + " FROM contacts WHERE contactId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
rs = ps.executeQuery();

View File

@@ -29,7 +29,7 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
TransportIndex i, byte[] encryptedIv, byte[] secret) {
// Decrypt the IV
Cipher ivCipher = crypto.getIvCipher();
ErasableKey ivKey = crypto.deriveIncomingIvKey(secret);
ErasableKey ivKey = crypto.deriveIvKey(secret, true);
byte[] iv;
try {
ivCipher.init(Cipher.DECRYPT_MODE, ivKey);
@@ -57,15 +57,17 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
private ConnectionReader createConnectionReader(InputStream in,
boolean initiator, TransportIndex i, long connection,
byte[] secret) {
byte[] iv = IvEncoder.encodeIv(initiator, i, connection);
// Derive the keys and erase the secret
ErasableKey frameKey = crypto.deriveFrameKey(secret, initiator);
ErasableKey macKey = crypto.deriveMacKey(secret, initiator);
for(int j = 0; j < secret.length; j++) secret[j] = 0;
// Create the decrypter
byte[] iv = IvEncoder.encodeIv(initiator, i, connection);
Cipher frameCipher = crypto.getFrameCipher();
ErasableKey frameKey = crypto.deriveIncomingFrameKey(secret);
ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in, iv,
frameCipher, frameKey);
// Create the reader
Mac mac = crypto.getMac();
ErasableKey macKey = crypto.deriveIncomingMacKey(secret);
return new ConnectionReaderImpl(decrypter, mac, macKey);
}
}

View File

@@ -75,7 +75,9 @@ DatabaseListener {
}
private synchronized void calculateIvs(ContactId c) throws DbException {
ErasableKey ivKey = crypto.deriveIncomingIvKey(db.getSharedSecret(c));
byte[] secret = db.getSharedSecret(c, true);
ErasableKey ivKey = crypto.deriveIvKey(secret, true);
for(int i = 0; i < secret.length; i++) secret[i] = 0;
for(TransportId t : localTransportIds) {
TransportIndex i = db.getRemoteIndex(c, t);
if(i != null) {
@@ -131,7 +133,9 @@ DatabaseListener {
TransportIndex i1 = ctx1.getTransportIndex();
if(c1.equals(c) && i1.equals(i)) it.remove();
}
ErasableKey ivKey = crypto.deriveIncomingIvKey(db.getSharedSecret(c));
byte[] secret = db.getSharedSecret(c, true);
ErasableKey ivKey = crypto.deriveIvKey(secret, true);
for(int j = 0; j < secret.length; j++) secret[j] = 0;
calculateIvs(c, ctx.getTransportId(), i, ivKey, w);
} catch(NoSuchContactException e) {
// The contact was removed - clean up when we get the event
@@ -181,8 +185,9 @@ DatabaseListener {
private synchronized void calculateIvs(TransportId t) throws DbException {
for(ContactId c : db.getContacts()) {
try {
byte[] secret = db.getSharedSecret(c);
ErasableKey ivKey = crypto.deriveIncomingIvKey(secret);
byte[] secret = db.getSharedSecret(c, true);
ErasableKey ivKey = crypto.deriveIvKey(secret, true);
for(int i = 0; i < secret.length; i++) secret[i] = 0;
TransportIndex i = db.getRemoteIndex(c, t);
if(i != null) {
ConnectionWindow w = db.getConnectionWindow(c, i);

View File

@@ -36,7 +36,7 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
byte[] secret) {
// Decrypt the IV
Cipher ivCipher = crypto.getIvCipher();
ErasableKey ivKey = crypto.deriveIncomingIvKey(secret);
ErasableKey ivKey = crypto.deriveIvKey(secret, true);
byte[] iv;
try {
ivCipher.init(Cipher.DECRYPT_MODE, ivKey);
@@ -60,17 +60,19 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
private ConnectionWriter createConnectionWriter(OutputStream out,
long capacity, boolean initiator, TransportIndex i, long connection,
byte[] secret) {
// Derive the keys and erase the secret
ErasableKey ivKey = crypto.deriveIvKey(secret, initiator);
ErasableKey frameKey = crypto.deriveFrameKey(secret, initiator);
ErasableKey macKey = crypto.deriveMacKey(secret, initiator);
for(int j = 0; j < secret.length; j++) secret[j] = 0;
// Create the encrypter
Cipher ivCipher = crypto.getIvCipher();
Cipher frameCipher = crypto.getFrameCipher();
ErasableKey ivKey = crypto.deriveOutgoingIvKey(secret);
ErasableKey frameKey = crypto.deriveOutgoingFrameKey(secret);
byte[] iv = IvEncoder.encodeIv(initiator, i, connection);
ConnectionEncrypter encrypter = new ConnectionEncrypterImpl(out,
capacity, iv, ivCipher, frameCipher, ivKey, frameKey);
// Create the writer
Mac mac = crypto.getMac();
ErasableKey macKey = crypto.deriveOutgoingMacKey(secret);
return new ConnectionWriterImpl(encrypter, mac, macKey);
}
}

View File

@@ -47,7 +47,7 @@ class IncomingBatchConnection {
void read() {
try {
byte[] secret = db.getSharedSecret(contactId);
byte[] secret = db.getSharedSecret(contactId, true);
ConnectionReader conn = connFactory.createConnectionReader(
reader.getInputStream(), transportIndex, encryptedIv,
secret);

View File

@@ -46,7 +46,7 @@ class OutgoingBatchConnection {
void write() {
try {
byte[] secret = db.getSharedSecret(contactId);
byte[] secret = db.getSharedSecret(contactId, false);
long connection = db.getConnectionNumber(contactId, transportIndex);
ConnectionWriter conn = connFactory.createConnectionWriter(
writer.getOutputStream(), writer.getCapacity(),

View File

@@ -33,7 +33,7 @@ public class IncomingStreamConnection extends StreamConnection {
@Override
protected ConnectionReader createConnectionReader() throws DbException,
IOException {
byte[] secret = db.getSharedSecret(contactId);
byte[] secret = db.getSharedSecret(contactId, true);
return connReaderFactory.createConnectionReader(
connection.getInputStream(), transportIndex, encryptedIv,
secret);
@@ -42,7 +42,7 @@ public class IncomingStreamConnection extends StreamConnection {
@Override
protected ConnectionWriter createConnectionWriter() throws DbException,
IOException {
byte[] secret = db.getSharedSecret(contactId);
byte[] secret = db.getSharedSecret(contactId, false);
return connWriterFactory.createConnectionWriter(
connection.getOutputStream(), Long.MAX_VALUE, transportIndex,
encryptedIv, secret);

View File

@@ -37,7 +37,7 @@ public class OutgoingStreamConnection extends StreamConnection {
transportIndex);
}
}
byte[] secret = db.getSharedSecret(contactId);
byte[] secret = db.getSharedSecret(contactId, true);
return connReaderFactory.createConnectionReader(
connection.getInputStream(), transportIndex, connectionNum,
secret);
@@ -52,7 +52,7 @@ public class OutgoingStreamConnection extends StreamConnection {
transportIndex);
}
}
byte[] secret = db.getSharedSecret(contactId);
byte[] secret = db.getSharedSecret(contactId, false);
return connWriterFactory.createConnectionWriter(
connection.getOutputStream(), Long.MAX_VALUE, transportIndex,
connectionNum, secret);