mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-17 21:29:54 +01:00
Simpler key rotation: rotation period R = C + L, retention period = 3R.
This commit is contained in:
@@ -5,8 +5,8 @@ import net.sf.briar.api.TransportId;
|
|||||||
|
|
||||||
public class Endpoint {
|
public class Endpoint {
|
||||||
|
|
||||||
private final ContactId contactId;
|
protected final ContactId contactId;
|
||||||
private final TransportId transportId;
|
protected final TransportId transportId;
|
||||||
private final long epoch;
|
private final long epoch;
|
||||||
private final boolean alice;
|
private final boolean alice;
|
||||||
|
|
||||||
|
|||||||
@@ -53,4 +53,20 @@ public class TemporarySecret extends Endpoint {
|
|||||||
public byte[] getWindowBitmap() {
|
public byte[] getWindowBitmap() {
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int periodHashCode = (int) (period ^ (period >>> 32));
|
||||||
|
return contactId.hashCode() ^ transportId.hashCode() ^ periodHashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if(o instanceof TemporarySecret) {
|
||||||
|
TemporarySecret s = (TemporarySecret) o;
|
||||||
|
return contactId.equals(s.contactId) &&
|
||||||
|
transportId.equals(s.transportId) && period == s.period;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -848,7 +848,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
for(TemporarySecret s : secrets) {
|
for(TemporarySecret s : secrets) {
|
||||||
ps.setInt(1, s.getContactId().getInt());
|
ps.setInt(1, s.getContactId().getInt());
|
||||||
ps.setBytes(2, s.getTransportId().getBytes());
|
ps.setBytes(2, s.getTransportId().getBytes());
|
||||||
ps.setLong(3, s.getPeriod() - 1);
|
ps.setLong(3, s.getPeriod() - 2);
|
||||||
ps.addBatch();
|
ps.addBatch();
|
||||||
}
|
}
|
||||||
batchAffected = ps.executeBatch();
|
batchAffected = ps.executeBatch();
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
|
|||||||
|
|
||||||
// All of the following are locking: this
|
// All of the following are locking: this
|
||||||
private final Map<TransportId, Long> maxLatencies;
|
private final Map<TransportId, Long> maxLatencies;
|
||||||
private final Map<EndpointKey, TemporarySecret> outgoing;
|
private final Map<EndpointKey, TemporarySecret> oldSecrets;
|
||||||
private final Map<EndpointKey, TemporarySecret> incomingOld;
|
private final Map<EndpointKey, TemporarySecret> currentSecrets;
|
||||||
private final Map<EndpointKey, TemporarySecret> incomingNew;
|
private final Map<EndpointKey, TemporarySecret> newSecrets;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
KeyManagerImpl(CryptoComponent crypto, DatabaseComponent db,
|
KeyManagerImpl(CryptoComponent crypto, DatabaseComponent db,
|
||||||
@@ -63,9 +63,9 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
|
|||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.timer = timer;
|
this.timer = timer;
|
||||||
maxLatencies = new HashMap<TransportId, Long>();
|
maxLatencies = new HashMap<TransportId, Long>();
|
||||||
outgoing = new HashMap<EndpointKey, TemporarySecret>();
|
oldSecrets = new HashMap<EndpointKey, TemporarySecret>();
|
||||||
incomingOld = new HashMap<EndpointKey, TemporarySecret>();
|
currentSecrets = new HashMap<EndpointKey, TemporarySecret>();
|
||||||
incomingNew = new HashMap<EndpointKey, TemporarySecret>();
|
newSecrets = new HashMap<EndpointKey, TemporarySecret>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean start() {
|
public synchronized boolean start() {
|
||||||
@@ -85,7 +85,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
|
|||||||
// Replace any dead secrets
|
// Replace any dead secrets
|
||||||
Collection<TemporarySecret> created = replaceDeadSecrets(now, dead);
|
Collection<TemporarySecret> created = replaceDeadSecrets(now, dead);
|
||||||
if(!created.isEmpty()) {
|
if(!created.isEmpty()) {
|
||||||
// Store any secrets that have been created
|
// Store any secrets that have been created, removing any dead ones
|
||||||
try {
|
try {
|
||||||
db.addSecrets(created);
|
db.addSecrets(created);
|
||||||
} catch(DbException e) {
|
} catch(DbException e) {
|
||||||
@@ -93,10 +93,12 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Pass the current incoming secrets to the recogniser
|
// Pass the old, current and new secrets to the recogniser
|
||||||
for(TemporarySecret s : incomingOld.values())
|
for(TemporarySecret s : oldSecrets.values())
|
||||||
connectionRecogniser.addSecret(s);
|
connectionRecogniser.addSecret(s);
|
||||||
for(TemporarySecret s : incomingNew.values())
|
for(TemporarySecret s : currentSecrets.values())
|
||||||
|
connectionRecogniser.addSecret(s);
|
||||||
|
for(TemporarySecret s : newSecrets.values())
|
||||||
connectionRecogniser.addSecret(s);
|
connectionRecogniser.addSecret(s);
|
||||||
// Schedule periodic key rotation
|
// Schedule periodic key rotation
|
||||||
timer.scheduleAtFixedRate(this, MS_BETWEEN_CHECKS, MS_BETWEEN_CHECKS);
|
timer.scheduleAtFixedRate(this, MS_BETWEEN_CHECKS, MS_BETWEEN_CHECKS);
|
||||||
@@ -110,29 +112,24 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
|
|||||||
Collection<TemporarySecret> dead = new ArrayList<TemporarySecret>();
|
Collection<TemporarySecret> dead = new ArrayList<TemporarySecret>();
|
||||||
for(TemporarySecret s : secrets) {
|
for(TemporarySecret s : secrets) {
|
||||||
// Discard the secret if the transport has been removed
|
// Discard the secret if the transport has been removed
|
||||||
if(!maxLatencies.containsKey(s.getTransportId())) {
|
Long maxLatency = maxLatencies.get(s.getTransportId());
|
||||||
|
if(maxLatency == null) {
|
||||||
ByteUtils.erase(s.getSecret());
|
ByteUtils.erase(s.getSecret());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
EndpointKey k = new EndpointKey(s);
|
long rotation = maxLatency + MAX_CLOCK_DIFFERENCE;
|
||||||
long rotationPeriod = getRotationPeriod(s);
|
long creationTime = s.getEpoch() + rotation * (s.getPeriod() - 2);
|
||||||
long creationTime = getCreationTime(s);
|
long activationTime = creationTime + rotation;
|
||||||
long activationTime = creationTime + MAX_CLOCK_DIFFERENCE;
|
long deactivationTime = activationTime + rotation;
|
||||||
long successorCreationTime = creationTime + rotationPeriod;
|
long destructionTime = deactivationTime + rotation;
|
||||||
long deactivationTime = activationTime + rotationPeriod;
|
|
||||||
long destructionTime = successorCreationTime + rotationPeriod;
|
|
||||||
if(now >= destructionTime) {
|
if(now >= destructionTime) {
|
||||||
dead.add(s);
|
dead.add(s);
|
||||||
} else if(now >= deactivationTime) {
|
} else if(now >= deactivationTime) {
|
||||||
incomingOld.put(k, s);
|
oldSecrets.put(new EndpointKey(s), s);
|
||||||
} else if(now >= successorCreationTime) {
|
|
||||||
incomingOld.put(k, s);
|
|
||||||
outgoing.put(k, s);
|
|
||||||
} else if(now >= activationTime) {
|
} else if(now >= activationTime) {
|
||||||
incomingNew.put(k, s);
|
currentSecrets.put(new EndpointKey(s), s);
|
||||||
outgoing.put(k, s);
|
|
||||||
} else if(now >= creationTime) {
|
} else if(now >= creationTime) {
|
||||||
incomingNew.put(k, s);
|
newSecrets.put(new EndpointKey(s), s);
|
||||||
} else {
|
} else {
|
||||||
// FIXME: Work out what to do here
|
// FIXME: Work out what to do here
|
||||||
throw new Error("Clock has moved backwards");
|
throw new Error("Clock has moved backwards");
|
||||||
@@ -147,62 +144,50 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
|
|||||||
Collection<TemporarySecret> dead) {
|
Collection<TemporarySecret> dead) {
|
||||||
Collection<TemporarySecret> created = new ArrayList<TemporarySecret>();
|
Collection<TemporarySecret> created = new ArrayList<TemporarySecret>();
|
||||||
for(TemporarySecret s : dead) {
|
for(TemporarySecret s : dead) {
|
||||||
|
Long maxLatency = maxLatencies.get(s.getTransportId());
|
||||||
|
if(maxLatency == null) throw new IllegalStateException();
|
||||||
// Work out which rotation period we're in
|
// Work out which rotation period we're in
|
||||||
long rotationPeriod = getRotationPeriod(s);
|
|
||||||
long elapsed = now - s.getEpoch();
|
long elapsed = now - s.getEpoch();
|
||||||
long period = (elapsed / rotationPeriod) + 1;
|
long rotation = maxLatency + MAX_CLOCK_DIFFERENCE;
|
||||||
if(period <= s.getPeriod()) throw new IllegalStateException();
|
long currentPeriod = (elapsed / rotation) + 1;
|
||||||
// Derive the two current incoming secrets
|
if(currentPeriod < 1) throw new IllegalStateException();
|
||||||
byte[] secret1 = s.getSecret();
|
if(currentPeriod - s.getPeriod() < 2)
|
||||||
for(long p = s.getPeriod(); p < period; p++) {
|
throw new IllegalStateException();
|
||||||
byte[] temp = crypto.deriveNextSecret(secret1, p);
|
// Derive the old, current and new secrets
|
||||||
ByteUtils.erase(secret1);
|
byte[] b1 = s.getSecret();
|
||||||
secret1 = temp;
|
for(long p = s.getPeriod() + 1; p < currentPeriod; p++) {
|
||||||
|
byte[] temp = crypto.deriveNextSecret(b1, p);
|
||||||
|
ByteUtils.erase(b1);
|
||||||
|
b1 = temp;
|
||||||
}
|
}
|
||||||
byte[] secret2 = crypto.deriveNextSecret(secret1, period);
|
byte[] b2 = crypto.deriveNextSecret(b1, currentPeriod);
|
||||||
// Add the incoming secrets to their respective maps - the older
|
byte[] b3 = crypto.deriveNextSecret(b2, currentPeriod + 1);
|
||||||
// may already exist if the dead secret has a living successor
|
TemporarySecret s1 = new TemporarySecret(s, currentPeriod - 1, b1);
|
||||||
|
TemporarySecret s2 = new TemporarySecret(s, currentPeriod, b2);
|
||||||
|
TemporarySecret s3 = new TemporarySecret(s, currentPeriod + 1, b3);
|
||||||
|
// Add the secrets to their respective maps - the old and current
|
||||||
|
// secrets may already exist, in which case erase the duplicates
|
||||||
EndpointKey k = new EndpointKey(s);
|
EndpointKey k = new EndpointKey(s);
|
||||||
TemporarySecret s1 = new TemporarySecret(s, period - 1, secret1);
|
TemporarySecret exists = oldSecrets.put(k, s1);
|
||||||
created.add(s1);
|
if(exists == null) created.add(s1);
|
||||||
TemporarySecret exists = incomingOld.put(k, s1);
|
else ByteUtils.erase(exists.getSecret());
|
||||||
if(exists != null) ByteUtils.erase(exists.getSecret());
|
exists = currentSecrets.put(k, s2);
|
||||||
TemporarySecret s2 = new TemporarySecret(s, period, secret2);
|
if(exists == null) created.add(s2);
|
||||||
created.add(s2);
|
else ByteUtils.erase(exists.getSecret());
|
||||||
incomingNew.put(k, s2);
|
newSecrets.put(k, s3);
|
||||||
// One of the incoming secrets is the current outgoing secret
|
created.add(s3);
|
||||||
if(elapsed % rotationPeriod < MAX_CLOCK_DIFFERENCE) {
|
|
||||||
// The outgoing secret is the older incoming secret
|
|
||||||
outgoing.put(k, s1);
|
|
||||||
} else {
|
|
||||||
// The outgoing secret is the newer incoming secret
|
|
||||||
outgoing.put(k, s2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return created;
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locking: this
|
|
||||||
private long getRotationPeriod(Endpoint ep) {
|
|
||||||
Long maxLatency = maxLatencies.get(ep.getTransportId());
|
|
||||||
if(maxLatency == null) throw new IllegalStateException();
|
|
||||||
return 2 * MAX_CLOCK_DIFFERENCE + maxLatency;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Locking: this
|
|
||||||
private long getCreationTime(TemporarySecret s) {
|
|
||||||
long rotationPeriod = getRotationPeriod(s);
|
|
||||||
return s.getEpoch() + rotationPeriod * s.getPeriod();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void stop() {
|
public synchronized void stop() {
|
||||||
db.removeListener(this);
|
db.removeListener(this);
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
connectionRecogniser.removeSecrets();
|
connectionRecogniser.removeSecrets();
|
||||||
maxLatencies.clear();
|
maxLatencies.clear();
|
||||||
removeAndEraseSecrets(outgoing);
|
removeAndEraseSecrets(oldSecrets);
|
||||||
removeAndEraseSecrets(incomingOld);
|
removeAndEraseSecrets(currentSecrets);
|
||||||
removeAndEraseSecrets(incomingNew);
|
removeAndEraseSecrets(newSecrets);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locking: this
|
// Locking: this
|
||||||
@@ -213,7 +198,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
|
|||||||
|
|
||||||
public synchronized ConnectionContext getConnectionContext(ContactId c,
|
public synchronized ConnectionContext getConnectionContext(ContactId c,
|
||||||
TransportId t) {
|
TransportId t) {
|
||||||
TemporarySecret s = outgoing.get(new EndpointKey(c, t));
|
TemporarySecret s = currentSecrets.get(new EndpointKey(c, t));
|
||||||
if(s == null) return null;
|
if(s == null) return null;
|
||||||
long connection;
|
long connection;
|
||||||
try {
|
try {
|
||||||
@@ -227,41 +212,36 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void endpointAdded(Endpoint ep, byte[] initialSecret) {
|
public synchronized void endpointAdded(Endpoint ep, byte[] initialSecret) {
|
||||||
if(!maxLatencies.containsKey(ep.getTransportId())) {
|
Long maxLatency = maxLatencies.get(ep.getTransportId());
|
||||||
|
if(maxLatency == null) {
|
||||||
if(LOG.isLoggable(WARNING)) LOG.warning("No such transport");
|
if(LOG.isLoggable(WARNING)) LOG.warning("No such transport");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Work out which rotation period we're in
|
// Work out which rotation period we're in
|
||||||
long now = clock.currentTimeMillis();
|
long elapsed = clock.currentTimeMillis() - ep.getEpoch();
|
||||||
long rotationPeriod = getRotationPeriod(ep);
|
long rotation = maxLatency + MAX_CLOCK_DIFFERENCE;
|
||||||
long elapsed = now - ep.getEpoch();
|
long currentPeriod = (elapsed / rotation) + 1;
|
||||||
long period = (elapsed / rotationPeriod) + 1;
|
if(currentPeriod < 1) throw new IllegalStateException();
|
||||||
if(period < 1) throw new IllegalStateException();
|
// Derive the old, current and new secrets
|
||||||
// Derive the two current incoming secrets
|
byte[] b1 = initialSecret;
|
||||||
byte[] secret1 = initialSecret;
|
for(long p = 0; p < currentPeriod; p++) {
|
||||||
for(long p = 0; p < period; p++) {
|
byte[] temp = crypto.deriveNextSecret(b1, p);
|
||||||
byte[] temp = crypto.deriveNextSecret(secret1, p);
|
ByteUtils.erase(b1);
|
||||||
ByteUtils.erase(secret1);
|
b1 = temp;
|
||||||
secret1 = temp;
|
|
||||||
}
|
}
|
||||||
byte[] secret2 = crypto.deriveNextSecret(secret1, period);
|
byte[] b2 = crypto.deriveNextSecret(b1, currentPeriod);
|
||||||
|
byte[] b3 = crypto.deriveNextSecret(b2, currentPeriod + 1);
|
||||||
|
TemporarySecret s1 = new TemporarySecret(ep, currentPeriod - 1, b1);
|
||||||
|
TemporarySecret s2 = new TemporarySecret(ep, currentPeriod, b2);
|
||||||
|
TemporarySecret s3 = new TemporarySecret(ep, currentPeriod + 1, b3);
|
||||||
// Add the incoming secrets to their respective maps
|
// Add the incoming secrets to their respective maps
|
||||||
EndpointKey k = new EndpointKey(ep);
|
EndpointKey k = new EndpointKey(ep);
|
||||||
TemporarySecret s1 = new TemporarySecret(ep, period - 1, secret1);
|
oldSecrets.put(k, s1);
|
||||||
incomingOld.put(k, s1);
|
currentSecrets.put(k, s2);
|
||||||
TemporarySecret s2 = new TemporarySecret(ep, period, secret2);
|
newSecrets.put(k, s3);
|
||||||
incomingNew.put(k, s2);
|
|
||||||
// One of the incoming secrets is the current outgoing secret
|
|
||||||
if(elapsed % rotationPeriod < MAX_CLOCK_DIFFERENCE) {
|
|
||||||
// The outgoing secret is the older incoming secret
|
|
||||||
outgoing.put(k, s1);
|
|
||||||
} else {
|
|
||||||
// The outgoing secret is the newer incoming secret
|
|
||||||
outgoing.put(k, s2);
|
|
||||||
}
|
|
||||||
// Store the new secrets
|
// Store the new secrets
|
||||||
try {
|
try {
|
||||||
db.addSecrets(Arrays.asList(s1, s2));
|
db.addSecrets(Arrays.asList(s1, s2, s3));
|
||||||
} catch(DbException e) {
|
} catch(DbException e) {
|
||||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
return;
|
return;
|
||||||
@@ -269,17 +249,18 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
|
|||||||
// Pass the new secrets to the recogniser
|
// Pass the new secrets to the recogniser
|
||||||
connectionRecogniser.addSecret(s1);
|
connectionRecogniser.addSecret(s1);
|
||||||
connectionRecogniser.addSecret(s2);
|
connectionRecogniser.addSecret(s2);
|
||||||
|
connectionRecogniser.addSecret(s3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void run() {
|
public synchronized void run() {
|
||||||
// Rebuild the maps because we may be running a whole period late
|
// Rebuild the maps because we may be running a whole period late
|
||||||
Collection<TemporarySecret> secrets = new ArrayList<TemporarySecret>();
|
Collection<TemporarySecret> secrets = new ArrayList<TemporarySecret>();
|
||||||
secrets.addAll(incomingOld.values());
|
secrets.addAll(oldSecrets.values());
|
||||||
secrets.addAll(incomingNew.values());
|
secrets.addAll(currentSecrets.values());
|
||||||
outgoing.clear();
|
secrets.addAll(newSecrets.values());
|
||||||
incomingOld.clear();
|
oldSecrets.clear();
|
||||||
incomingNew.clear();
|
currentSecrets.clear();
|
||||||
|
newSecrets.clear();
|
||||||
// Work out what phase of its lifecycle each secret is in
|
// Work out what phase of its lifecycle each secret is in
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
Collection<TemporarySecret> dead = assignSecretsToMaps(now, secrets);
|
Collection<TemporarySecret> dead = assignSecretsToMaps(now, secrets);
|
||||||
@@ -309,9 +290,9 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
|
|||||||
ContactId c = ((ContactRemovedEvent) e).getContactId();
|
ContactId c = ((ContactRemovedEvent) e).getContactId();
|
||||||
connectionRecogniser.removeSecrets(c);
|
connectionRecogniser.removeSecrets(c);
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
removeAndEraseSecrets(c, outgoing);
|
removeAndEraseSecrets(c, oldSecrets);
|
||||||
removeAndEraseSecrets(c, incomingOld);
|
removeAndEraseSecrets(c, currentSecrets);
|
||||||
removeAndEraseSecrets(c, incomingNew);
|
removeAndEraseSecrets(c, newSecrets);
|
||||||
}
|
}
|
||||||
} else if(e instanceof TransportAddedEvent) {
|
} else if(e instanceof TransportAddedEvent) {
|
||||||
TransportAddedEvent t = (TransportAddedEvent) e;
|
TransportAddedEvent t = (TransportAddedEvent) e;
|
||||||
@@ -323,9 +304,9 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
|
|||||||
connectionRecogniser.removeSecrets(t);
|
connectionRecogniser.removeSecrets(t);
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
maxLatencies.remove(t);
|
maxLatencies.remove(t);
|
||||||
removeAndEraseSecrets(t, outgoing);
|
removeAndEraseSecrets(t, oldSecrets);
|
||||||
removeAndEraseSecrets(t, incomingOld);
|
removeAndEraseSecrets(t, currentSecrets);
|
||||||
removeAndEraseSecrets(t, incomingNew);
|
removeAndEraseSecrets(t, newSecrets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,7 @@
|
|||||||
<test name='net.sf.briar.transport.ConnectionWindowTest'/>
|
<test name='net.sf.briar.transport.ConnectionWindowTest'/>
|
||||||
<test name='net.sf.briar.transport.ConnectionWriterImplTest'/>
|
<test name='net.sf.briar.transport.ConnectionWriterImplTest'/>
|
||||||
<test name='net.sf.briar.transport.IncomingEncryptionLayerTest'/>
|
<test name='net.sf.briar.transport.IncomingEncryptionLayerTest'/>
|
||||||
|
<test name='net.sf.briar.transport.KeyManagerImplTest'/>
|
||||||
<test name='net.sf.briar.transport.OutgoingEncryptionLayerTest'/>
|
<test name='net.sf.briar.transport.OutgoingEncryptionLayerTest'/>
|
||||||
<test name='net.sf.briar.transport.TransportIntegrationTest'/>
|
<test name='net.sf.briar.transport.TransportIntegrationTest'/>
|
||||||
<test name='net.sf.briar.transport.TransportConnectionRecogniserTest'/>
|
<test name='net.sf.briar.transport.TransportConnectionRecogniserTest'/>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package net.sf.briar;
|
package net.sf.briar;
|
||||||
|
|
||||||
|
|
||||||
import java.lang.Thread.UncaughtExceptionHandler;
|
import java.lang.Thread.UncaughtExceptionHandler;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|||||||
@@ -1511,12 +1511,13 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTemporarySecrets() throws Exception {
|
public void testTemporarySecrets() throws Exception {
|
||||||
// Create an endpoint and three consecutive temporary secrets
|
// Create an endpoint and four consecutive temporary secrets
|
||||||
long epoch = 123, latency = 234;
|
long epoch = 123, latency = 234;
|
||||||
boolean alice = false;
|
boolean alice = false;
|
||||||
long outgoing1 = 345, centre1 = 456;
|
long outgoing1 = 345, centre1 = 456;
|
||||||
long outgoing2 = 567, centre2 = 678;
|
long outgoing2 = 567, centre2 = 678;
|
||||||
long outgoing3 = 789, centre3 = 890;
|
long outgoing3 = 789, centre3 = 890;
|
||||||
|
long outgoing4 = 901, centre4 = 123;
|
||||||
Endpoint ep = new Endpoint(contactId, transportId, epoch, alice);
|
Endpoint ep = new Endpoint(contactId, transportId, epoch, alice);
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
byte[] secret1 = new byte[32], bitmap1 = new byte[4];
|
byte[] secret1 = new byte[32], bitmap1 = new byte[4];
|
||||||
@@ -1534,6 +1535,11 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
random.nextBytes(bitmap3);
|
random.nextBytes(bitmap3);
|
||||||
TemporarySecret s3 = new TemporarySecret(contactId, transportId, epoch,
|
TemporarySecret s3 = new TemporarySecret(contactId, transportId, epoch,
|
||||||
alice, 2, secret3, outgoing3, centre3, bitmap3);
|
alice, 2, secret3, outgoing3, centre3, bitmap3);
|
||||||
|
byte[] secret4 = new byte[32], bitmap4 = new byte[4];
|
||||||
|
random.nextBytes(secret4);
|
||||||
|
random.nextBytes(bitmap4);
|
||||||
|
TemporarySecret s4 = new TemporarySecret(contactId, transportId, epoch,
|
||||||
|
alice, 3, secret4, outgoing4, centre4, bitmap4);
|
||||||
|
|
||||||
Database<Connection> db = open(false);
|
Database<Connection> db = open(false);
|
||||||
Connection txn = db.startTransaction();
|
Connection txn = db.startTransaction();
|
||||||
@@ -1541,18 +1547,18 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
// Initially there should be no secrets in the database
|
// Initially there should be no secrets in the database
|
||||||
assertEquals(Collections.emptyList(), db.getSecrets(txn));
|
assertEquals(Collections.emptyList(), db.getSecrets(txn));
|
||||||
|
|
||||||
// Add the contact, the transport, the endpoint and the first two
|
// Add the contact, the transport, the endpoint and the first three
|
||||||
// secrets (periods 0 and 1)
|
// secrets (periods 0, 1 and 2)
|
||||||
db.addLocalAuthor(txn, localAuthor);
|
db.addLocalAuthor(txn, localAuthor);
|
||||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||||
db.addTransport(txn, transportId, latency);
|
db.addTransport(txn, transportId, latency);
|
||||||
db.addEndpoint(txn, ep);
|
db.addEndpoint(txn, ep);
|
||||||
db.addSecrets(txn, Arrays.asList(s1, s2));
|
db.addSecrets(txn, Arrays.asList(s1, s2, s3));
|
||||||
|
|
||||||
// Retrieve the first two secrets
|
// Retrieve the first three secrets
|
||||||
Collection<TemporarySecret> secrets = db.getSecrets(txn);
|
Collection<TemporarySecret> secrets = db.getSecrets(txn);
|
||||||
assertEquals(2, secrets.size());
|
assertEquals(3, secrets.size());
|
||||||
boolean foundFirst = false, foundSecond = false;
|
boolean foundFirst = false, foundSecond = false, foundThird = false;
|
||||||
for(TemporarySecret s : secrets) {
|
for(TemporarySecret s : secrets) {
|
||||||
assertEquals(contactId, s.getContactId());
|
assertEquals(contactId, s.getContactId());
|
||||||
assertEquals(transportId, s.getTransportId());
|
assertEquals(transportId, s.getTransportId());
|
||||||
@@ -1570,19 +1576,26 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
assertEquals(centre2, s.getWindowCentre());
|
assertEquals(centre2, s.getWindowCentre());
|
||||||
assertArrayEquals(bitmap2, s.getWindowBitmap());
|
assertArrayEquals(bitmap2, s.getWindowBitmap());
|
||||||
foundSecond = true;
|
foundSecond = true;
|
||||||
|
} else if(s.getPeriod() == 2) {
|
||||||
|
assertArrayEquals(secret3, s.getSecret());
|
||||||
|
assertEquals(outgoing3, s.getOutgoingConnectionCounter());
|
||||||
|
assertEquals(centre3, s.getWindowCentre());
|
||||||
|
assertArrayEquals(bitmap3, s.getWindowBitmap());
|
||||||
|
foundThird = true;
|
||||||
} else {
|
} else {
|
||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertTrue(foundFirst);
|
assertTrue(foundFirst);
|
||||||
assertTrue(foundSecond);
|
assertTrue(foundSecond);
|
||||||
|
assertTrue(foundThird);
|
||||||
|
|
||||||
// Adding the third secret (period 2) should delete the first (period 0)
|
// Adding the fourth secret (period 3) should delete the first
|
||||||
db.addSecrets(txn, Arrays.asList(s3));
|
db.addSecrets(txn, Arrays.asList(s4));
|
||||||
secrets = db.getSecrets(txn);
|
secrets = db.getSecrets(txn);
|
||||||
assertEquals(2, secrets.size());
|
assertEquals(3, secrets.size());
|
||||||
foundSecond = false;
|
foundSecond = foundThird = false;
|
||||||
boolean foundThird = false;
|
boolean foundFourth = false;
|
||||||
for(TemporarySecret s : secrets) {
|
for(TemporarySecret s : secrets) {
|
||||||
assertEquals(contactId, s.getContactId());
|
assertEquals(contactId, s.getContactId());
|
||||||
assertEquals(transportId, s.getTransportId());
|
assertEquals(transportId, s.getTransportId());
|
||||||
@@ -1600,12 +1613,19 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
assertEquals(centre3, s.getWindowCentre());
|
assertEquals(centre3, s.getWindowCentre());
|
||||||
assertArrayEquals(bitmap3, s.getWindowBitmap());
|
assertArrayEquals(bitmap3, s.getWindowBitmap());
|
||||||
foundThird = true;
|
foundThird = true;
|
||||||
|
} else if(s.getPeriod() == 3) {
|
||||||
|
assertArrayEquals(secret4, s.getSecret());
|
||||||
|
assertEquals(outgoing4, s.getOutgoingConnectionCounter());
|
||||||
|
assertEquals(centre4, s.getWindowCentre());
|
||||||
|
assertArrayEquals(bitmap4, s.getWindowBitmap());
|
||||||
|
foundFourth = true;
|
||||||
} else {
|
} else {
|
||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertTrue(foundSecond);
|
assertTrue(foundSecond);
|
||||||
assertTrue(foundThird);
|
assertTrue(foundThird);
|
||||||
|
assertTrue(foundFourth);
|
||||||
|
|
||||||
// Removing the contact should remove the secrets
|
// Removing the contact should remove the secrets
|
||||||
db.removeContact(txn, contactId);
|
db.removeContact(txn, contactId);
|
||||||
|
|||||||
193
briar-tests/src/net/sf/briar/transport/KeyManagerImplTest.java
Normal file
193
briar-tests/src/net/sf/briar/transport/KeyManagerImplTest.java
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
package net.sf.briar.transport;
|
||||||
|
|
||||||
|
import static net.sf.briar.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
import net.sf.briar.BriarTestCase;
|
||||||
|
import net.sf.briar.TestUtils;
|
||||||
|
import net.sf.briar.api.ContactId;
|
||||||
|
import net.sf.briar.api.TransportId;
|
||||||
|
import net.sf.briar.api.clock.Clock;
|
||||||
|
import net.sf.briar.api.clock.Timer;
|
||||||
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
|
import net.sf.briar.api.db.DatabaseComponent;
|
||||||
|
import net.sf.briar.api.db.event.DatabaseListener;
|
||||||
|
import net.sf.briar.api.transport.ConnectionRecogniser;
|
||||||
|
import net.sf.briar.api.transport.Endpoint;
|
||||||
|
import net.sf.briar.api.transport.TemporarySecret;
|
||||||
|
|
||||||
|
import org.jmock.Expectations;
|
||||||
|
import org.jmock.Mockery;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class KeyManagerImplTest extends BriarTestCase {
|
||||||
|
|
||||||
|
private final Random random = new Random();
|
||||||
|
private final ContactId contactId;
|
||||||
|
private final TransportId transportId;
|
||||||
|
private final long maxLatency;
|
||||||
|
private final long rotationPeriodLength;
|
||||||
|
private final byte[] secret0, secret1, secret2, secret3;
|
||||||
|
private final long epoch = 1000L * 1000L * 1000L * 1000L;
|
||||||
|
|
||||||
|
public KeyManagerImplTest() {
|
||||||
|
contactId = new ContactId(234);
|
||||||
|
transportId = new TransportId(TestUtils.getRandomId());
|
||||||
|
maxLatency = 2 * 60 * 1000; // 2 minutes
|
||||||
|
rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
|
||||||
|
secret0 = new byte[32];
|
||||||
|
secret1 = new byte[32];
|
||||||
|
secret2 = new byte[32];
|
||||||
|
secret3 = new byte[32];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
random.nextBytes(secret0);
|
||||||
|
random.nextBytes(secret1);
|
||||||
|
random.nextBytes(secret2);
|
||||||
|
random.nextBytes(secret3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStartAndStop() throws Exception {
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||||
|
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
|
final ConnectionRecogniser connectionRecogniser =
|
||||||
|
context.mock(ConnectionRecogniser.class);
|
||||||
|
final Clock clock = context.mock(Clock.class);
|
||||||
|
final Timer timer = context.mock(Timer.class);
|
||||||
|
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||||
|
connectionRecogniser, clock, timer);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
// start()
|
||||||
|
oneOf(db).addListener(with(any(DatabaseListener.class)));
|
||||||
|
oneOf(db).getSecrets();
|
||||||
|
will(returnValue(Collections.emptyList()));
|
||||||
|
oneOf(db).getTransportLatencies();
|
||||||
|
will(returnValue(Collections.emptyMap()));
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(epoch));
|
||||||
|
oneOf(timer).scheduleAtFixedRate(with(any(TimerTask.class)),
|
||||||
|
with(any(long.class)), with(any(long.class)));
|
||||||
|
// stop()
|
||||||
|
oneOf(db).removeListener(with(any(DatabaseListener.class)));
|
||||||
|
oneOf(timer).cancel();
|
||||||
|
oneOf(connectionRecogniser).removeSecrets();
|
||||||
|
}});
|
||||||
|
|
||||||
|
assertTrue(keyManager.start());
|
||||||
|
keyManager.stop();
|
||||||
|
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadSecretsAtEpoch() throws Exception {
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||||
|
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
|
final ConnectionRecogniser connectionRecogniser =
|
||||||
|
context.mock(ConnectionRecogniser.class);
|
||||||
|
final Clock clock = context.mock(Clock.class);
|
||||||
|
final Timer timer = context.mock(Timer.class);
|
||||||
|
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||||
|
connectionRecogniser, clock, timer);
|
||||||
|
// The DB contains secrets for periods 0 - 2
|
||||||
|
Endpoint ep = new Endpoint(contactId, transportId, epoch, true);
|
||||||
|
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0);
|
||||||
|
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1);
|
||||||
|
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
// start()
|
||||||
|
oneOf(db).addListener(with(any(DatabaseListener.class)));
|
||||||
|
oneOf(db).getSecrets();
|
||||||
|
will(returnValue(Arrays.asList(s0, s1, s2)));
|
||||||
|
oneOf(db).getTransportLatencies();
|
||||||
|
will(returnValue(Collections.singletonMap(transportId,
|
||||||
|
maxLatency)));
|
||||||
|
// The current time is the second secret's activation time
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(epoch));
|
||||||
|
// The secrets for periods 0 - 2 should be added to the recogniser
|
||||||
|
oneOf(connectionRecogniser).addSecret(s0);
|
||||||
|
oneOf(connectionRecogniser).addSecret(s1);
|
||||||
|
oneOf(connectionRecogniser).addSecret(s2);
|
||||||
|
oneOf(timer).scheduleAtFixedRate(with(any(TimerTask.class)),
|
||||||
|
with(any(long.class)), with(any(long.class)));
|
||||||
|
// stop()
|
||||||
|
oneOf(db).removeListener(with(any(DatabaseListener.class)));
|
||||||
|
oneOf(timer).cancel();
|
||||||
|
oneOf(connectionRecogniser).removeSecrets();
|
||||||
|
}});
|
||||||
|
|
||||||
|
assertTrue(keyManager.start());
|
||||||
|
keyManager.stop();
|
||||||
|
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadSecretsAtNewActivationTime() throws Exception {
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||||
|
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
|
final ConnectionRecogniser connectionRecogniser =
|
||||||
|
context.mock(ConnectionRecogniser.class);
|
||||||
|
final Clock clock = context.mock(Clock.class);
|
||||||
|
final Timer timer = context.mock(Timer.class);
|
||||||
|
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||||
|
connectionRecogniser, clock, timer);
|
||||||
|
// The DB contains secrets for periods 0 - 2
|
||||||
|
Endpoint ep = new Endpoint(contactId, transportId, epoch, true);
|
||||||
|
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0);
|
||||||
|
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1);
|
||||||
|
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2);
|
||||||
|
// A fourth secret should be derived and stored
|
||||||
|
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
// start()
|
||||||
|
oneOf(db).addListener(with(any(DatabaseListener.class)));
|
||||||
|
oneOf(db).getSecrets();
|
||||||
|
will(returnValue(Arrays.asList(s0, s1, s2)));
|
||||||
|
oneOf(db).getTransportLatencies();
|
||||||
|
will(returnValue(Collections.singletonMap(transportId,
|
||||||
|
maxLatency)));
|
||||||
|
// The current time is the third secret's activation time
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(epoch + rotationPeriodLength));
|
||||||
|
// A fourth secret should be derived and stored
|
||||||
|
oneOf(crypto).deriveNextSecret(secret0, 1);
|
||||||
|
will(returnValue(secret1.clone()));
|
||||||
|
oneOf(crypto).deriveNextSecret(secret1, 2);
|
||||||
|
will(returnValue(secret2.clone()));
|
||||||
|
oneOf(crypto).deriveNextSecret(secret2, 3);
|
||||||
|
will(returnValue(secret3));
|
||||||
|
oneOf(db).addSecrets(Arrays.asList(s3));
|
||||||
|
// The secrets for periods 1 - 3 should be added to the recogniser
|
||||||
|
oneOf(connectionRecogniser).addSecret(s1);
|
||||||
|
oneOf(connectionRecogniser).addSecret(s2);
|
||||||
|
oneOf(connectionRecogniser).addSecret(s3);
|
||||||
|
oneOf(timer).scheduleAtFixedRate(with(any(TimerTask.class)),
|
||||||
|
with(any(long.class)), with(any(long.class)));
|
||||||
|
// stop()
|
||||||
|
oneOf(db).removeListener(with(any(DatabaseListener.class)));
|
||||||
|
oneOf(timer).cancel();
|
||||||
|
oneOf(connectionRecogniser).removeSecrets();
|
||||||
|
}});
|
||||||
|
|
||||||
|
assertTrue(keyManager.start());
|
||||||
|
// The dead secret should have been erased
|
||||||
|
assertArrayEquals(new byte[32], secret0);
|
||||||
|
keyManager.stop();
|
||||||
|
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user