mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-20 06:39:54 +01:00
Merge branch '55-key-manager-refactoring' into 'master'
Refactor KeyManager and TagRecogniser, implement new key rotation logic. This patch implements the new key rotation logic for BTP version 2, the new transport protocol (#111). KeyManager and TagRecogniser have been merged (#55). They no longer need to hold locks while calling each other's methods (#3, #4). TransportKeyManager holds a lock while calling CryptoComponent methods, but those methods don't block or acquire any locks. The maximum clock difference has been increased from one hour to 24 hours because some people adjust the time rather than the timezone when travelling (#18). This will cause keys to be rotated less frequently. For the same reason, the key manager no longer throws an Error when the clock moves backwards - keys that belong to future rotation periods are kept until they become current, then rotation resumes (#19). The new KeyManagerImpl and TransportKeyManager need unit tests. I'm putting this up for review while I write the tests. TransportKeyManager needs further work before #55 is complete - when a stream context is created, TransportKeyManager should wait for the database write to complete before returning the context, to avoid the risk of key reuse if the write fails. I'll make a separate patch for that as this one's big enough already. Enjoy! See merge request !13
This commit is contained in:
@@ -32,7 +32,6 @@ import org.briarproject.api.event.Event;
|
|||||||
import org.briarproject.api.event.EventBus;
|
import org.briarproject.api.event.EventBus;
|
||||||
import org.briarproject.api.event.EventListener;
|
import org.briarproject.api.event.EventListener;
|
||||||
import org.briarproject.api.event.SettingsUpdatedEvent;
|
import org.briarproject.api.event.SettingsUpdatedEvent;
|
||||||
import org.briarproject.api.lifecycle.Service;
|
|
||||||
import org.briarproject.api.messaging.GroupId;
|
import org.briarproject.api.messaging.GroupId;
|
||||||
import org.briarproject.util.StringUtils;
|
import org.briarproject.util.StringUtils;
|
||||||
|
|
||||||
@@ -45,7 +44,7 @@ import android.support.v4.app.NotificationCompat;
|
|||||||
import android.support.v4.app.TaskStackBuilder;
|
import android.support.v4.app.TaskStackBuilder;
|
||||||
|
|
||||||
class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||||
Service, EventListener {
|
EventListener {
|
||||||
|
|
||||||
private static final int PRIVATE_MESSAGE_NOTIFICATION_ID = 3;
|
private static final int PRIVATE_MESSAGE_NOTIFICATION_ID = 3;
|
||||||
private static final int GROUP_POST_NOTIFICATION_ID = 4;
|
private static final int GROUP_POST_NOTIFICATION_ID = 4;
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package org.briarproject.api.android;
|
package org.briarproject.api.android;
|
||||||
|
|
||||||
import org.briarproject.api.ContactId;
|
import org.briarproject.api.ContactId;
|
||||||
|
import org.briarproject.api.lifecycle.Service;
|
||||||
import org.briarproject.api.messaging.GroupId;
|
import org.briarproject.api.messaging.GroupId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages notifications for private messages and group posts. All methods must
|
* Manages notifications for private messages and group posts. All methods must
|
||||||
* be called from the Android UI thread.
|
* be called from the Android UI thread.
|
||||||
*/
|
*/
|
||||||
public interface AndroidNotificationManager {
|
public interface AndroidNotificationManager extends Service {
|
||||||
|
|
||||||
public void showPrivateMessageNotification(ContactId c);
|
public void showPrivateMessageNotification(ContactId c);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package org.briarproject.api.crypto;
|
package org.briarproject.api.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.api.TransportId;
|
||||||
|
import org.briarproject.api.transport.TransportKeys;
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
@@ -26,56 +29,50 @@ public interface CryptoComponent {
|
|||||||
/** Generates a random invitation code. */
|
/** Generates a random invitation code. */
|
||||||
int generateInvitationCode();
|
int generateInvitationCode();
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives two confirmation codes from the given master secret. The first
|
|
||||||
* code is for Alice to give to Bob; the second is for Bob to give to
|
|
||||||
* Alice.
|
|
||||||
*/
|
|
||||||
int[] deriveConfirmationCodes(byte[] secret);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives two nonces from the given master secret. The first nonce is for
|
|
||||||
* Alice to sign; the second is for Bob to sign.
|
|
||||||
*/
|
|
||||||
byte[][] deriveInvitationNonces(byte[] secret);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives a shared master secret from two public keys and one of the
|
* Derives a shared master secret from two public keys and one of the
|
||||||
* corresponding private keys.
|
* corresponding private keys.
|
||||||
* @param alice indicates whether the private key belongs to Alice or Bob.
|
* @param alice whether the private key belongs to Alice or Bob.
|
||||||
*/
|
*/
|
||||||
byte[] deriveMasterSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
|
SecretKey deriveMasterSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
|
||||||
boolean alice) throws GeneralSecurityException;
|
boolean alice) throws GeneralSecurityException;
|
||||||
|
|
||||||
/** Derives a group salt from the given master secret. */
|
/**
|
||||||
byte[] deriveGroupSalt(byte[] secret);
|
* Derives a confirmation code from the given master secret.
|
||||||
|
* @param alice whether the code is for use by Alice or Bob.
|
||||||
|
*/
|
||||||
|
int deriveConfirmationCode(SecretKey master, boolean alice);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives an initial secret for the given transport from the given master
|
* Derives a header key for an invitation stream from the given master
|
||||||
* secret.
|
* secret.
|
||||||
|
* @param alice whether the key is for use by Alice or Bob.
|
||||||
*/
|
*/
|
||||||
byte[] deriveInitialSecret(byte[] secret, int transportIndex);
|
SecretKey deriveInvitationKey(SecretKey master, boolean alice);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives a temporary secret for the given period from the given secret,
|
* Derives a nonce from the given master secret for one of the parties to
|
||||||
* which is either the initial shared secret or the previous period's
|
* sign.
|
||||||
* temporary secret.
|
* @param alice whether the nonce is for use by Alice or Bob.
|
||||||
*/
|
*/
|
||||||
byte[] deriveNextSecret(byte[] secret, long period);
|
byte[] deriveSignatureNonce(SecretKey master, boolean alice);
|
||||||
|
|
||||||
|
/** Derives a group salt from the given master secret. */
|
||||||
|
byte[] deriveGroupSalt(SecretKey master);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives a tag key from the given temporary secret.
|
* Derives initial transport keys for the given transport in the given
|
||||||
* @param alice indicates whether the key is for streams initiated by
|
* rotation period from the given master secret.
|
||||||
* Alice or Bob.
|
* @param alice whether the keys are for use by Alice or Bob.
|
||||||
*/
|
*/
|
||||||
SecretKey deriveTagKey(byte[] secret, boolean alice);
|
TransportKeys deriveTransportKeys(TransportId t, SecretKey master,
|
||||||
|
long rotationPeriod, boolean alice);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives a frame key from the given temporary secret and stream number.
|
* Rotates the given transport keys to the given rotation period. If the
|
||||||
* @param alice indicates whether the key is for a stream initiated by
|
* keys are for a future rotation period they are not rotated.
|
||||||
* Alice or Bob.
|
|
||||||
*/
|
*/
|
||||||
SecretKey deriveFrameKey(byte[] secret, long streamNumber, boolean alice);
|
TransportKeys rotateTransportKeys(TransportKeys k, long rotationPeriod);
|
||||||
|
|
||||||
/** Encodes the pseudo-random tag that is used to recognise a stream. */
|
/** Encodes the pseudo-random tag that is used to recognise a stream. */
|
||||||
void encodeTag(byte[] tag, SecretKey tagKey, long streamNumber);
|
void encodeTag(byte[] tag, SecretKey tagKey, long streamNumber);
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package org.briarproject.api.crypto;
|
|
||||||
|
|
||||||
import org.briarproject.api.ContactId;
|
|
||||||
import org.briarproject.api.TransportId;
|
|
||||||
import org.briarproject.api.lifecycle.Service;
|
|
||||||
import org.briarproject.api.transport.Endpoint;
|
|
||||||
import org.briarproject.api.transport.StreamContext;
|
|
||||||
|
|
||||||
public interface KeyManager extends Service {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@link org.briarproject.api.transport.StreamContext
|
|
||||||
* StreamContext} for sending data to the given contact over the given
|
|
||||||
* transport, or null if an error occurs or the contact does not support
|
|
||||||
* the transport.
|
|
||||||
*/
|
|
||||||
StreamContext getStreamContext(ContactId c, TransportId t);
|
|
||||||
|
|
||||||
/** Called whenever an endpoint has been added. */
|
|
||||||
void endpointAdded(Endpoint ep, int maxLatency, byte[] initialSecret);
|
|
||||||
}
|
|
||||||
@@ -13,5 +13,5 @@ public interface StreamDecrypterFactory {
|
|||||||
* Creates a {@link StreamDecrypter} for decrypting an invitation stream.
|
* Creates a {@link StreamDecrypter} for decrypting an invitation stream.
|
||||||
*/
|
*/
|
||||||
StreamDecrypter createInvitationStreamDecrypter(InputStream in,
|
StreamDecrypter createInvitationStreamDecrypter(InputStream in,
|
||||||
byte[] secret, boolean alice);
|
SecretKey headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,5 @@ public interface StreamEncrypterFactory {
|
|||||||
* Creates a {@link StreamEncrypter} for encrypting an invitation stream.
|
* Creates a {@link StreamEncrypter} for encrypting an invitation stream.
|
||||||
*/
|
*/
|
||||||
StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
|
StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
|
||||||
byte[] secret, boolean alice);
|
SecretKey headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
package org.briarproject.api.db;
|
package org.briarproject.api.db;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.briarproject.api.Author;
|
import org.briarproject.api.Author;
|
||||||
import org.briarproject.api.AuthorId;
|
import org.briarproject.api.AuthorId;
|
||||||
import org.briarproject.api.Contact;
|
import org.briarproject.api.Contact;
|
||||||
@@ -26,8 +22,11 @@ import org.briarproject.api.messaging.SubscriptionAck;
|
|||||||
import org.briarproject.api.messaging.SubscriptionUpdate;
|
import org.briarproject.api.messaging.SubscriptionUpdate;
|
||||||
import org.briarproject.api.messaging.TransportAck;
|
import org.briarproject.api.messaging.TransportAck;
|
||||||
import org.briarproject.api.messaging.TransportUpdate;
|
import org.briarproject.api.messaging.TransportUpdate;
|
||||||
import org.briarproject.api.transport.Endpoint;
|
import org.briarproject.api.transport.TransportKeys;
|
||||||
import org.briarproject.api.transport.TemporarySecret;
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates the database implementation and exposes high-level operations
|
* Encapsulates the database implementation and exposes high-level operations
|
||||||
@@ -47,9 +46,6 @@ public interface DatabaseComponent {
|
|||||||
*/
|
*/
|
||||||
ContactId addContact(Author remote, AuthorId local) throws DbException;
|
ContactId addContact(Author remote, AuthorId local) throws DbException;
|
||||||
|
|
||||||
/** Stores an endpoint. */
|
|
||||||
void addEndpoint(Endpoint ep) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribes to a group, or returns false if the user already has the
|
* Subscribes to a group, or returns false if the user already has the
|
||||||
* maximum number of public subscriptions.
|
* maximum number of public subscriptions.
|
||||||
@@ -62,18 +58,17 @@ public interface DatabaseComponent {
|
|||||||
/** Stores a local message. */
|
/** Stores a local message. */
|
||||||
void addLocalMessage(Message m) throws DbException;
|
void addLocalMessage(Message m) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores the given temporary secrets and deletes any secrets that have
|
|
||||||
* been made obsolete.
|
|
||||||
*/
|
|
||||||
void addSecrets(Collection<TemporarySecret> secrets) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a transport and returns true if the transport was not previously
|
* Stores a transport and returns true if the transport was not previously
|
||||||
* in the database.
|
* in the database.
|
||||||
*/
|
*/
|
||||||
boolean addTransport(TransportId t, int maxLatency) throws DbException;
|
boolean addTransport(TransportId t, int maxLatency) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the given transport keys for a newly added contact.
|
||||||
|
*/
|
||||||
|
void addTransportKeys(ContactId c, TransportKeys k) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an acknowledgement for the given contact, or null if there are
|
* Returns an acknowledgement for the given contact, or null if there are
|
||||||
* no messages to acknowledge.
|
* no messages to acknowledge.
|
||||||
@@ -214,16 +209,17 @@ public interface DatabaseComponent {
|
|||||||
Map<ContactId, TransportProperties> getRemoteProperties(TransportId t)
|
Map<ContactId, TransportProperties> getRemoteProperties(TransportId t)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/** Returns all temporary secrets. */
|
|
||||||
Collection<TemporarySecret> getSecrets() throws DbException;
|
|
||||||
|
|
||||||
/** Returns all settings. */
|
/** Returns all settings. */
|
||||||
Settings getSettings() throws DbException;
|
Settings getSettings() throws DbException;
|
||||||
|
|
||||||
/** Returns all contacts who subscribe to the given group. */
|
/** Returns all contacts who subscribe to the given group. */
|
||||||
Collection<Contact> getSubscribers(GroupId g) throws DbException;
|
Collection<Contact> getSubscribers(GroupId g) throws DbException;
|
||||||
|
|
||||||
/** Returns the maximum latencies of all supported transports. */
|
/** Returns all transport keys for the given transport. */
|
||||||
|
Map<ContactId, TransportKeys> getTransportKeys(TransportId t)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
|
/** Returns the maximum latencies in milliseconds of all transports. */
|
||||||
Map<TransportId, Integer> getTransportLatencies() throws DbException;
|
Map<TransportId, Integer> getTransportLatencies() throws DbException;
|
||||||
|
|
||||||
/** Returns the number of unread messages in each subscribed group. */
|
/** Returns the number of unread messages in each subscribed group. */
|
||||||
@@ -233,11 +229,10 @@ public interface DatabaseComponent {
|
|||||||
Collection<ContactId> getVisibility(GroupId g) throws DbException;
|
Collection<ContactId> getVisibility(GroupId g) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments the outgoing stream counter for the given endpoint in the
|
* Increments the outgoing stream counter for the given contact and
|
||||||
* given rotation period and returns the old value, or -1 if the counter
|
* transport in the given rotation period .
|
||||||
* does not exist.
|
|
||||||
*/
|
*/
|
||||||
long incrementStreamCounter(ContactId c, TransportId t, long period)
|
void incrementStreamCounter(ContactId c, TransportId t, long rotationPeriod)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -310,18 +305,11 @@ public interface DatabaseComponent {
|
|||||||
*/
|
*/
|
||||||
void removeTransport(TransportId t) throws DbException;
|
void removeTransport(TransportId t) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the reordering window for the given endpoint in the given rotation
|
|
||||||
* period.
|
|
||||||
*/
|
|
||||||
void setReorderingWindow(ContactId c, TransportId t, long period,
|
|
||||||
long centre, byte[] bitmap) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a group visible to the given contact, adds it to the contact's
|
* Makes a group visible to the given contact, adds it to the contact's
|
||||||
* subscriptions, and sets it as the inbox group for the contact.
|
* subscriptions, and sets it as the inbox group for the contact.
|
||||||
*/
|
*/
|
||||||
public void setInboxGroup(ContactId c, Group g) throws DbException;
|
void setInboxGroup(ContactId c, Group g) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks a message as read or unread.
|
* Marks a message as read or unread.
|
||||||
@@ -335,6 +323,13 @@ public interface DatabaseComponent {
|
|||||||
void setRemoteProperties(ContactId c,
|
void setRemoteProperties(ContactId c,
|
||||||
Map<TransportId, TransportProperties> p) throws DbException;
|
Map<TransportId, TransportProperties> p) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the reordering window for the given contact and transport in the
|
||||||
|
* given rotation period.
|
||||||
|
*/
|
||||||
|
void setReorderingWindow(ContactId c, TransportId t, long rotationPeriod,
|
||||||
|
long base, byte[] bitmap) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a group visible to the given set of contacts and invisible to any
|
* Makes a group visible to the given set of contacts and invisible to any
|
||||||
* other current or future contacts.
|
* other current or future contacts.
|
||||||
@@ -347,4 +342,10 @@ public interface DatabaseComponent {
|
|||||||
* to future contacts.
|
* to future contacts.
|
||||||
*/
|
*/
|
||||||
void setVisibleToAll(GroupId g, boolean all) throws DbException;
|
void setVisibleToAll(GroupId g, boolean all) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the given transport keys, deleting any keys they have replaced.
|
||||||
|
*/
|
||||||
|
void updateTransportKeys(Map<ContactId, TransportKeys> keys)
|
||||||
|
throws DbException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public interface Message {
|
|||||||
/** Returns the message's content type. */
|
/** Returns the message's content type. */
|
||||||
String getContentType();
|
String getContentType();
|
||||||
|
|
||||||
/** Returns the message's timestamp. */
|
/** Returns the message's timestamp in milliseconds since the Unix epoch. */
|
||||||
long getTimestamp();
|
long getTimestamp();
|
||||||
|
|
||||||
/** Returns the serialised message. */
|
/** Returns the serialised message. */
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
package org.briarproject.api.transport;
|
|
||||||
|
|
||||||
import org.briarproject.api.ContactId;
|
|
||||||
import org.briarproject.api.TransportId;
|
|
||||||
|
|
||||||
public class Endpoint {
|
|
||||||
|
|
||||||
protected final ContactId contactId;
|
|
||||||
protected final TransportId transportId;
|
|
||||||
private final long epoch;
|
|
||||||
private final boolean alice;
|
|
||||||
|
|
||||||
public Endpoint(ContactId contactId, TransportId transportId, long epoch,
|
|
||||||
boolean alice) {
|
|
||||||
this.contactId = contactId;
|
|
||||||
this.transportId = transportId;
|
|
||||||
this.epoch = epoch;
|
|
||||||
this.alice = alice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContactId getContactId() {
|
|
||||||
return contactId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransportId getTransportId() {
|
|
||||||
return transportId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getEpoch() {
|
|
||||||
return epoch;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getAlice() {
|
|
||||||
return alice;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package org.briarproject.api.transport;
|
||||||
|
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
|
||||||
|
import static org.briarproject.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains transport keys for receiving streams from a given contact over a
|
||||||
|
* given transport in a given rotation period.
|
||||||
|
*/
|
||||||
|
public class IncomingKeys {
|
||||||
|
|
||||||
|
private final SecretKey tagKey, headerKey;
|
||||||
|
private final long rotationPeriod, windowBase;
|
||||||
|
private final byte[] windowBitmap;
|
||||||
|
|
||||||
|
public IncomingKeys(SecretKey tagKey, SecretKey headerKey,
|
||||||
|
long rotationPeriod) {
|
||||||
|
this(tagKey, headerKey, rotationPeriod, 0,
|
||||||
|
new byte[REORDERING_WINDOW_SIZE / 8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IncomingKeys(SecretKey tagKey, SecretKey headerKey,
|
||||||
|
long rotationPeriod, long windowBase, byte[] windowBitmap) {
|
||||||
|
this.tagKey = tagKey;
|
||||||
|
this.headerKey = headerKey;
|
||||||
|
this.rotationPeriod = rotationPeriod;
|
||||||
|
this.windowBase = windowBase;
|
||||||
|
this.windowBitmap = windowBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SecretKey getTagKey() {
|
||||||
|
return tagKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SecretKey getHeaderKey() {
|
||||||
|
return headerKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRotationPeriod() {
|
||||||
|
return rotationPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getWindowBase() {
|
||||||
|
return windowBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getWindowBitmap() {
|
||||||
|
return windowBitmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
briar-api/src/org/briarproject/api/transport/KeyManager.java
Normal file
36
briar-api/src/org/briarproject/api/transport/KeyManager.java
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package org.briarproject.api.transport;
|
||||||
|
|
||||||
|
import org.briarproject.api.ContactId;
|
||||||
|
import org.briarproject.api.TransportId;
|
||||||
|
import org.briarproject.api.db.DbException;
|
||||||
|
import org.briarproject.api.lifecycle.Service;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for managing transport keys and recognising the pseudo-random
|
||||||
|
* tags of incoming streams.
|
||||||
|
*/
|
||||||
|
public interface KeyManager extends Service {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs the key manager that a new contact has been added.
|
||||||
|
* {@link StreamContext StreamContexts} for the contact can be created
|
||||||
|
* after this method has returned.
|
||||||
|
*/
|
||||||
|
void contactAdded(ContactId c, Collection<TransportKeys> keys);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link StreamContext} for sending a stream to the given
|
||||||
|
* contact over the given transport, or null if an error occurs or the
|
||||||
|
* contact does not support the transport.
|
||||||
|
*/
|
||||||
|
StreamContext getStreamContext(ContactId c, TransportId t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up the given tag and returns a {@link StreamContext} for reading
|
||||||
|
* from the corresponding stream if the tag was expected, or null if the
|
||||||
|
* tag was unexpected.
|
||||||
|
*/
|
||||||
|
StreamContext recogniseTag(TransportId t, byte[] tag) throws DbException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package org.briarproject.api.transport;
|
||||||
|
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains transport keys for sending streams to a given contact over a given
|
||||||
|
* transport in a given rotation period.
|
||||||
|
*/
|
||||||
|
public class OutgoingKeys {
|
||||||
|
|
||||||
|
private final SecretKey tagKey, headerKey;
|
||||||
|
private final long rotationPeriod, streamCounter;
|
||||||
|
|
||||||
|
public OutgoingKeys(SecretKey tagKey, SecretKey headerKey,
|
||||||
|
long rotationPeriod) {
|
||||||
|
this(tagKey, headerKey, rotationPeriod, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutgoingKeys(SecretKey tagKey, SecretKey headerKey,
|
||||||
|
long rotationPeriod, long streamCounter) {
|
||||||
|
this.tagKey = tagKey;
|
||||||
|
this.headerKey = headerKey;
|
||||||
|
this.rotationPeriod = rotationPeriod;
|
||||||
|
this.streamCounter = streamCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SecretKey getTagKey() {
|
||||||
|
return tagKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SecretKey getHeaderKey() {
|
||||||
|
return headerKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRotationPeriod() {
|
||||||
|
return rotationPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStreamCounter() {
|
||||||
|
return streamCounter;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,22 +2,22 @@ package org.briarproject.api.transport;
|
|||||||
|
|
||||||
import org.briarproject.api.ContactId;
|
import org.briarproject.api.ContactId;
|
||||||
import org.briarproject.api.TransportId;
|
import org.briarproject.api.TransportId;
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
|
||||||
public class StreamContext {
|
public class StreamContext {
|
||||||
|
|
||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final TransportId transportId;
|
private final TransportId transportId;
|
||||||
private final byte[] secret;
|
private final SecretKey tagKey, headerKey;
|
||||||
private final long streamNumber;
|
private final long streamNumber;
|
||||||
private final boolean alice;
|
|
||||||
|
|
||||||
public StreamContext(ContactId contactId, TransportId transportId,
|
public StreamContext(ContactId contactId, TransportId transportId,
|
||||||
byte[] secret, long streamNumber, boolean alice) {
|
SecretKey tagKey, SecretKey headerKey, long streamNumber) {
|
||||||
this.contactId = contactId;
|
this.contactId = contactId;
|
||||||
this.transportId = transportId;
|
this.transportId = transportId;
|
||||||
this.secret = secret;
|
this.tagKey = tagKey;
|
||||||
|
this.headerKey = headerKey;
|
||||||
this.streamNumber = streamNumber;
|
this.streamNumber = streamNumber;
|
||||||
this.alice = alice;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContactId getContactId() {
|
public ContactId getContactId() {
|
||||||
@@ -28,15 +28,15 @@ public class StreamContext {
|
|||||||
return transportId;
|
return transportId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getSecret() {
|
public SecretKey getTagKey() {
|
||||||
return secret;
|
return tagKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SecretKey getHeaderKey() {
|
||||||
|
return headerKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getStreamNumber() {
|
public long getStreamNumber() {
|
||||||
return streamNumber;
|
return streamNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getAlice() {
|
|
||||||
return alice;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package org.briarproject.api.transport;
|
|||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
|
||||||
public interface StreamReaderFactory {
|
public interface StreamReaderFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,5 +17,5 @@ public interface StreamReaderFactory {
|
|||||||
* invitation stream.
|
* invitation stream.
|
||||||
*/
|
*/
|
||||||
InputStream createInvitationStreamReader(InputStream in,
|
InputStream createInvitationStreamReader(InputStream in,
|
||||||
byte[] secret, boolean alice);
|
SecretKey headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package org.briarproject.api.transport;
|
|||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
|
||||||
public interface StreamWriterFactory {
|
public interface StreamWriterFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,5 +17,5 @@ public interface StreamWriterFactory {
|
|||||||
* invitation stream.
|
* invitation stream.
|
||||||
*/
|
*/
|
||||||
OutputStream createInvitationStreamWriter(OutputStream out,
|
OutputStream createInvitationStreamWriter(OutputStream out,
|
||||||
byte[] secret, boolean alice);
|
SecretKey headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
package org.briarproject.api.transport;
|
|
||||||
|
|
||||||
import org.briarproject.api.ContactId;
|
|
||||||
import org.briarproject.api.TransportId;
|
|
||||||
import org.briarproject.api.db.DbException;
|
|
||||||
|
|
||||||
/** Keeps track of expected tags and uses them to recognise incoming streams. */
|
|
||||||
public interface TagRecogniser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks up the given tag and returns a {@link StreamContext} for reading
|
|
||||||
* from the stream if the tag was expected, or null if the tag was
|
|
||||||
* unexpected.
|
|
||||||
*/
|
|
||||||
StreamContext recogniseTag(TransportId t, byte[] tag) throws DbException;
|
|
||||||
|
|
||||||
void addSecret(TemporarySecret s);
|
|
||||||
|
|
||||||
void removeSecret(ContactId c, TransportId t, long period);
|
|
||||||
|
|
||||||
void removeSecrets(ContactId c);
|
|
||||||
|
|
||||||
void removeSecrets(TransportId t);
|
|
||||||
|
|
||||||
void removeSecrets();
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package org.briarproject.api.transport;
|
|
||||||
|
|
||||||
import static org.briarproject.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
|
|
||||||
|
|
||||||
import org.briarproject.api.ContactId;
|
|
||||||
import org.briarproject.api.TransportId;
|
|
||||||
|
|
||||||
public class TemporarySecret extends Endpoint {
|
|
||||||
|
|
||||||
private final long period, outgoing, centre;
|
|
||||||
private final byte[] secret, bitmap;
|
|
||||||
|
|
||||||
/** Creates a temporary secret with the given reordering window. */
|
|
||||||
public TemporarySecret(ContactId contactId, TransportId transportId,
|
|
||||||
long epoch, boolean alice, long period, byte[] secret,
|
|
||||||
long outgoing, long centre, byte[] bitmap) {
|
|
||||||
super(contactId, transportId, epoch, alice);
|
|
||||||
this.period = period;
|
|
||||||
this.secret = secret;
|
|
||||||
this.outgoing = outgoing;
|
|
||||||
this.centre = centre;
|
|
||||||
this.bitmap = bitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Creates a temporary secret with a new reordering window. */
|
|
||||||
public TemporarySecret(ContactId contactId, TransportId transportId,
|
|
||||||
long epoch, boolean alice, long period, byte[] secret) {
|
|
||||||
this(contactId, transportId, epoch, alice, period, secret, 0, 0,
|
|
||||||
new byte[REORDERING_WINDOW_SIZE / 8]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Creates a temporary secret derived from the given endpoint. */
|
|
||||||
public TemporarySecret(Endpoint ep, long period, byte[] secret) {
|
|
||||||
this(ep.getContactId(), ep.getTransportId(), ep.getEpoch(),
|
|
||||||
ep.getAlice(), period, secret);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getPeriod() {
|
|
||||||
return period;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getSecret() {
|
|
||||||
return secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getOutgoingStreamCounter() {
|
|
||||||
return outgoing;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getWindowCentre() {
|
|
||||||
return centre;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getWindowBitmap() {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,8 +28,8 @@ public interface TransportConstants {
|
|||||||
*/
|
*/
|
||||||
int MIN_STREAM_LENGTH = 64 * 1024; // 64 KiB
|
int MIN_STREAM_LENGTH = 64 * 1024; // 64 KiB
|
||||||
|
|
||||||
/** The maximum difference between two communicating devices' clocks. */
|
/** The maximum difference in milliseconds between two peers' clocks. */
|
||||||
int MAX_CLOCK_DIFFERENCE = 60 * 60 * 1000; // 1 hour
|
int MAX_CLOCK_DIFFERENCE = 24 * 60 * 60 * 1000; // 24 hours
|
||||||
|
|
||||||
/** The size of the reordering window. */
|
/** The size of the reordering window. */
|
||||||
int REORDERING_WINDOW_SIZE = 32;
|
int REORDERING_WINDOW_SIZE = 32;
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.briarproject.api.transport;
|
||||||
|
|
||||||
|
import org.briarproject.api.TransportId;
|
||||||
|
|
||||||
|
/** Keys for communicating with a given contact over a given transport. */
|
||||||
|
public class TransportKeys {
|
||||||
|
|
||||||
|
private final TransportId transportId;
|
||||||
|
private final IncomingKeys inPrev, inCurr, inNext;
|
||||||
|
private final OutgoingKeys outCurr;
|
||||||
|
|
||||||
|
public TransportKeys(TransportId transportId, IncomingKeys inPrev,
|
||||||
|
IncomingKeys inCurr, IncomingKeys inNext, OutgoingKeys outCurr) {
|
||||||
|
this.transportId = transportId;
|
||||||
|
this.inPrev = inPrev;
|
||||||
|
this.inCurr = inCurr;
|
||||||
|
this.inNext = inNext;
|
||||||
|
this.outCurr = outCurr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransportId getTransportId() {
|
||||||
|
return transportId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IncomingKeys getPreviousIncomingKeys() {
|
||||||
|
return inPrev;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IncomingKeys getCurrentIncomingKeys() {
|
||||||
|
return inCurr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IncomingKeys getNextIncomingKeys() {
|
||||||
|
return inNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutgoingKeys getCurrentOutgoingKeys() {
|
||||||
|
return outCurr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRotationPeriod() {
|
||||||
|
return outCurr.getRotationPeriod();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,6 @@
|
|||||||
package org.briarproject.crypto;
|
package org.briarproject.crypto;
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import org.briarproject.api.TransportId;
|
||||||
import static org.briarproject.api.invitation.InvitationConstants.CODE_BITS;
|
|
||||||
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
|
||||||
import static org.briarproject.crypto.EllipticCurveConstants.PARAMETERS;
|
|
||||||
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.api.crypto.KeyPair;
|
import org.briarproject.api.crypto.KeyPair;
|
||||||
import org.briarproject.api.crypto.KeyParser;
|
import org.briarproject.api.crypto.KeyParser;
|
||||||
@@ -25,6 +11,9 @@ import org.briarproject.api.crypto.PublicKey;
|
|||||||
import org.briarproject.api.crypto.SecretKey;
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
import org.briarproject.api.crypto.Signature;
|
import org.briarproject.api.crypto.Signature;
|
||||||
import org.briarproject.api.system.SeedProvider;
|
import org.briarproject.api.system.SeedProvider;
|
||||||
|
import org.briarproject.api.transport.IncomingKeys;
|
||||||
|
import org.briarproject.api.transport.OutgoingKeys;
|
||||||
|
import org.briarproject.api.transport.TransportKeys;
|
||||||
import org.briarproject.util.ByteUtils;
|
import org.briarproject.util.ByteUtils;
|
||||||
import org.briarproject.util.StringUtils;
|
import org.briarproject.util.StringUtils;
|
||||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
@@ -43,6 +32,21 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
|||||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||||
import org.spongycastle.crypto.params.KeyParameter;
|
import org.spongycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static org.briarproject.api.invitation.InvitationConstants.CODE_BITS;
|
||||||
|
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
||||||
|
import static org.briarproject.crypto.EllipticCurveConstants.PARAMETERS;
|
||||||
|
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||||
|
|
||||||
class CryptoComponentImpl implements CryptoComponent {
|
class CryptoComponentImpl implements CryptoComponent {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
@@ -55,22 +59,33 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
private static final int PBKDF_TARGET_MILLIS = 500;
|
private static final int PBKDF_TARGET_MILLIS = 500;
|
||||||
private static final int PBKDF_SAMPLES = 30;
|
private static final int PBKDF_SAMPLES = 30;
|
||||||
|
|
||||||
// Labels for secret derivation
|
// KDF label for master key derivation
|
||||||
private static final byte[] MASTER = { 'M', 'A', 'S', 'T', 'E', 'R', '\0' };
|
private static final byte[] MASTER = { 'M', 'A', 'S', 'T', 'E', 'R' };
|
||||||
private static final byte[] SALT = { 'S', 'A', 'L', 'T', '\0' };
|
// KDF labels for confirmation code derivation
|
||||||
private static final byte[] FIRST = { 'F', 'I', 'R', 'S', 'T', '\0' };
|
private static final byte[] A_CONFIRM =
|
||||||
private static final byte[] ROTATE = { 'R', 'O', 'T', 'A', 'T', 'E', '\0' };
|
{ 'A', '_', 'C', 'O', 'N', 'F', 'I', 'R', 'M' };
|
||||||
// Label for confirmation code derivation
|
private static final byte[] B_CONFIRM =
|
||||||
private static final byte[] CODE = { 'C', 'O', 'D', 'E', '\0' };
|
{ 'B', '_', 'C', 'O', 'N', 'F', 'I', 'R', 'M' };
|
||||||
// Label for invitation nonce derivation
|
// KDF labels for invitation stream header key derivation
|
||||||
private static final byte[] NONCE = { 'N', 'O', 'N', 'C', 'E', '\0' };
|
private static final byte[] A_INVITE =
|
||||||
// Labels for key derivation
|
{ 'A', '_', 'I', 'N', 'V', 'I', 'T', 'E' };
|
||||||
private static final byte[] A_TAG = { 'A', '_', 'T', 'A', 'G', '\0' };
|
private static final byte[] B_INVITE =
|
||||||
private static final byte[] B_TAG = { 'B', '_', 'T', 'A', 'G', '\0' };
|
{ 'B', '_', 'I', 'N', 'V', 'I', 'T', 'E' };
|
||||||
private static final byte[] A_FRAME =
|
// KDF labels for signature nonce derivation
|
||||||
{ 'A', '_', 'F', 'R', 'A', 'M', 'E', '\0' };
|
private static final byte[] A_NONCE = { 'A', '_', 'N', 'O', 'N', 'C', 'E' };
|
||||||
private static final byte[] B_FRAME =
|
private static final byte[] B_NONCE = { 'B', '_', 'N', 'O', 'N', 'C', 'E' };
|
||||||
{ 'B', '_', 'F', 'R', 'A', 'M', 'E', '\0' };
|
// KDF label for group salt derivation
|
||||||
|
private static final byte[] SALT = { 'S', 'A', 'L', 'T' };
|
||||||
|
// KDF labels for tag key derivation
|
||||||
|
private static final byte[] A_TAG = { 'A', '_', 'T', 'A', 'G' };
|
||||||
|
private static final byte[] B_TAG = { 'B', '_', 'T', 'A', 'G' };
|
||||||
|
// KDF labels for header key derivation
|
||||||
|
private static final byte[] A_HEADER =
|
||||||
|
{ 'A', '_', 'H', 'E', 'A', 'D', 'E', 'R' };
|
||||||
|
private static final byte[] B_HEADER =
|
||||||
|
{ 'B', '_', 'H', 'E', 'A', 'D', 'E', 'R' };
|
||||||
|
// KDF label for key rotation
|
||||||
|
private static final byte[] ROTATE = { 'R', 'O', 'T', 'A', 'T', 'E' };
|
||||||
|
|
||||||
private final SecureRandom secureRandom;
|
private final SecureRandom secureRandom;
|
||||||
private final ECKeyPairGenerator agreementKeyPairGenerator;
|
private final ECKeyPairGenerator agreementKeyPairGenerator;
|
||||||
@@ -167,26 +182,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
return ByteUtils.readUint(random, CODE_BITS);
|
return ByteUtils.readUint(random, CODE_BITS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int[] deriveConfirmationCodes(byte[] secret) {
|
public SecretKey deriveMasterSecret(byte[] theirPublicKey,
|
||||||
if (secret.length != SecretKey.LENGTH)
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
byte[] alice = counterModeKdf(secret, CODE, 0);
|
|
||||||
byte[] bob = counterModeKdf(secret, CODE, 1);
|
|
||||||
int[] codes = new int[2];
|
|
||||||
codes[0] = ByteUtils.readUint(alice, CODE_BITS);
|
|
||||||
codes[1] = ByteUtils.readUint(bob, CODE_BITS);
|
|
||||||
return codes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[][] deriveInvitationNonces(byte[] secret) {
|
|
||||||
if (secret.length != SecretKey.LENGTH)
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
byte[] alice = counterModeKdf(secret, NONCE, 0);
|
|
||||||
byte[] bob = counterModeKdf(secret, NONCE, 1);
|
|
||||||
return new byte[][] { alice, bob };
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] deriveMasterSecret(byte[] theirPublicKey,
|
|
||||||
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
|
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
|
||||||
MessageDigest messageDigest = getMessageDigest();
|
MessageDigest messageDigest = getMessageDigest();
|
||||||
byte[] ourPublicKey = ourKeyPair.getPublic().getEncoded();
|
byte[] ourPublicKey = ourKeyPair.getPublic().getEncoded();
|
||||||
@@ -204,9 +200,8 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
PublicKey theirPub = agreementKeyParser.parsePublicKey(theirPublicKey);
|
PublicKey theirPub = agreementKeyParser.parsePublicKey(theirPublicKey);
|
||||||
// The raw secret comes from the key agreement algorithm
|
// The raw secret comes from the key agreement algorithm
|
||||||
byte[] raw = deriveSharedSecret(ourPriv, theirPub);
|
byte[] raw = deriveSharedSecret(ourPriv, theirPub);
|
||||||
// Derive the cooked secret from the raw secret using the
|
// Derive the master secret from the raw secret using the hash KDF
|
||||||
// concatenation KDF
|
return new SecretKey(hashKdf(raw, MASTER, aliceInfo, bobInfo));
|
||||||
return concatenationKdf(raw, MASTER, aliceInfo, bobInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Package access for testing
|
// Package access for testing
|
||||||
@@ -228,46 +223,90 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] deriveGroupSalt(byte[] secret) {
|
public int deriveConfirmationCode(SecretKey master, boolean alice) {
|
||||||
if (secret.length != SecretKey.LENGTH)
|
byte[] b = macKdf(master, alice ? A_CONFIRM : B_CONFIRM);
|
||||||
throw new IllegalArgumentException();
|
return ByteUtils.readUint(b, CODE_BITS);
|
||||||
return counterModeKdf(secret, SALT, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] deriveInitialSecret(byte[] secret, int transportIndex) {
|
public SecretKey deriveInvitationKey(SecretKey master, boolean alice) {
|
||||||
if (secret.length != SecretKey.LENGTH)
|
return new SecretKey(macKdf(master, alice ? A_INVITE : B_INVITE));
|
||||||
throw new IllegalArgumentException();
|
|
||||||
if (transportIndex < 0) throw new IllegalArgumentException();
|
|
||||||
return counterModeKdf(secret, FIRST, transportIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] deriveNextSecret(byte[] secret, long period) {
|
public byte[] deriveSignatureNonce(SecretKey master, boolean alice) {
|
||||||
if (secret.length != SecretKey.LENGTH)
|
return macKdf(master, alice ? A_NONCE : B_NONCE);
|
||||||
throw new IllegalArgumentException();
|
|
||||||
if (period < 0 || period > MAX_32_BIT_UNSIGNED)
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
return counterModeKdf(secret, ROTATE, period);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SecretKey deriveTagKey(byte[] secret, boolean alice) {
|
public byte[] deriveGroupSalt(SecretKey master) {
|
||||||
if (secret.length != SecretKey.LENGTH)
|
return macKdf(master, SALT);
|
||||||
throw new IllegalArgumentException();
|
|
||||||
if (alice) return deriveKey(secret, A_TAG, 0);
|
|
||||||
else return deriveKey(secret, B_TAG, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SecretKey deriveFrameKey(byte[] secret, long streamNumber,
|
public TransportKeys deriveTransportKeys(TransportId t,
|
||||||
|
SecretKey master, long rotationPeriod, boolean alice) {
|
||||||
|
// Keys for the previous period are derived from the master secret
|
||||||
|
SecretKey inTagPrev = deriveTagKey(master, t, !alice);
|
||||||
|
SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice);
|
||||||
|
SecretKey outTagPrev = deriveTagKey(master, t, alice);
|
||||||
|
SecretKey outHeaderPrev = deriveHeaderKey(master, t, alice);
|
||||||
|
// Derive the keys for the current and next periods
|
||||||
|
SecretKey inTagCurr = rotateKey(inTagPrev, rotationPeriod);
|
||||||
|
SecretKey inHeaderCurr = rotateKey(inHeaderPrev, rotationPeriod);
|
||||||
|
SecretKey inTagNext = rotateKey(inTagCurr, rotationPeriod + 1);
|
||||||
|
SecretKey inHeaderNext = rotateKey(inHeaderCurr, rotationPeriod + 1);
|
||||||
|
SecretKey outTagCurr = rotateKey(outTagPrev, rotationPeriod);
|
||||||
|
SecretKey outHeaderCurr = rotateKey(outHeaderPrev, rotationPeriod);
|
||||||
|
// Initialise the reordering windows and stream counters
|
||||||
|
IncomingKeys inPrev = new IncomingKeys(inTagPrev, inHeaderPrev,
|
||||||
|
rotationPeriod - 1);
|
||||||
|
IncomingKeys inCurr = new IncomingKeys(inTagCurr, inHeaderCurr,
|
||||||
|
rotationPeriod);
|
||||||
|
IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext,
|
||||||
|
rotationPeriod + 1);
|
||||||
|
OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr,
|
||||||
|
rotationPeriod);
|
||||||
|
// Collect and return the keys
|
||||||
|
return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransportKeys rotateTransportKeys(TransportKeys k,
|
||||||
|
long rotationPeriod) {
|
||||||
|
if (k.getRotationPeriod() >= rotationPeriod) return k;
|
||||||
|
IncomingKeys inPrev = k.getPreviousIncomingKeys();
|
||||||
|
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
||||||
|
IncomingKeys inNext = k.getNextIncomingKeys();
|
||||||
|
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||||
|
long startPeriod = outCurr.getRotationPeriod();
|
||||||
|
// Rotate the keys
|
||||||
|
for (long p = startPeriod + 1; p <= rotationPeriod; p++) {
|
||||||
|
inPrev = inCurr;
|
||||||
|
inCurr = inNext;
|
||||||
|
SecretKey inNextTag = rotateKey(inNext.getTagKey(), p + 1);
|
||||||
|
SecretKey inNextHeader = rotateKey(inNext.getHeaderKey(), p + 1);
|
||||||
|
inNext = new IncomingKeys(inNextTag, inNextHeader, p);
|
||||||
|
SecretKey outCurrTag = rotateKey(outCurr.getTagKey(), p);
|
||||||
|
SecretKey outCurrHeader = rotateKey(outCurr.getHeaderKey(), p);
|
||||||
|
outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p);
|
||||||
|
}
|
||||||
|
// Collect and return the keys
|
||||||
|
return new TransportKeys(k.getTransportId(), inPrev, inCurr, inNext,
|
||||||
|
outCurr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecretKey rotateKey(SecretKey k, long rotationPeriod) {
|
||||||
|
byte[] period = new byte[4];
|
||||||
|
ByteUtils.writeUint32(rotationPeriod, period, 0);
|
||||||
|
return new SecretKey(macKdf(k, ROTATE, period));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecretKey deriveTagKey(SecretKey master, TransportId t,
|
||||||
boolean alice) {
|
boolean alice) {
|
||||||
if (secret.length != SecretKey.LENGTH)
|
byte[] id = StringUtils.toUtf8(t.getString());
|
||||||
throw new IllegalArgumentException();
|
return new SecretKey(macKdf(master, alice ? A_TAG : B_TAG, id));
|
||||||
if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
if (alice) return deriveKey(secret, A_FRAME, streamNumber);
|
|
||||||
else return deriveKey(secret, B_FRAME, streamNumber);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private SecretKey deriveKey(byte[] secret, byte[] label, long context) {
|
private SecretKey deriveHeaderKey(SecretKey master, TransportId t,
|
||||||
return new SecretKey(counterModeKdf(secret, label, context));
|
boolean alice) {
|
||||||
|
byte[] id = StringUtils.toUtf8(t.getString());
|
||||||
|
return new SecretKey(macKdf(master, alice ? A_HEADER : B_HEADER, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void encodeTag(byte[] tag, SecretKey tagKey, long streamNumber) {
|
public void encodeTag(byte[] tag, SecretKey tagKey, long streamNumber) {
|
||||||
@@ -277,7 +316,8 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
for (int i = 0; i < TAG_LENGTH; i++) tag[i] = 0;
|
for (int i = 0; i < TAG_LENGTH; i++) tag[i] = 0;
|
||||||
ByteUtils.writeUint32(streamNumber, tag, 0);
|
ByteUtils.writeUint32(streamNumber, tag, 0);
|
||||||
BlockCipher cipher = new AESLightEngine();
|
BlockCipher cipher = new AESLightEngine();
|
||||||
assert cipher.getBlockSize() == TAG_LENGTH;
|
if (cipher.getBlockSize() != TAG_LENGTH)
|
||||||
|
throw new IllegalStateException();
|
||||||
KeyParameter k = new KeyParameter(tagKey.getBytes());
|
KeyParameter k = new KeyParameter(tagKey.getBytes());
|
||||||
cipher.init(true, k);
|
cipher.init(true, k);
|
||||||
cipher.processBlock(tag, 0, tag, 0);
|
cipher.processBlock(tag, 0, tag, 0);
|
||||||
@@ -348,16 +388,16 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
|
|
||||||
// Key derivation function based on a hash function - see NIST SP 800-56A,
|
// Key derivation function based on a hash function - see NIST SP 800-56A,
|
||||||
// section 5.8
|
// section 5.8
|
||||||
private byte[] concatenationKdf(byte[]... inputs) {
|
private byte[] hashKdf(byte[]... inputs) {
|
||||||
// The output of the hash function must be long enough to use as a key
|
// The output of the hash function must be long enough to use as a key
|
||||||
MessageDigest messageDigest = getMessageDigest();
|
MessageDigest messageDigest = getMessageDigest();
|
||||||
if (messageDigest.getDigestLength() < SecretKey.LENGTH)
|
if (messageDigest.getDigestLength() < SecretKey.LENGTH)
|
||||||
throw new RuntimeException();
|
throw new IllegalStateException();
|
||||||
// Each input is length-prefixed - the length must fit in an
|
// Calculate the hash over the concatenated length-prefixed inputs
|
||||||
// unsigned 8-bit integer
|
byte[] length = new byte[4];
|
||||||
for (byte[] input : inputs) {
|
for (byte[] input : inputs) {
|
||||||
if (input.length > 255) throw new IllegalArgumentException();
|
ByteUtils.writeUint32(input.length, length, 0);
|
||||||
messageDigest.update((byte) input.length);
|
messageDigest.update(length);
|
||||||
messageDigest.update(input);
|
messageDigest.update(input);
|
||||||
}
|
}
|
||||||
byte[] hash = messageDigest.digest();
|
byte[] hash = messageDigest.digest();
|
||||||
@@ -368,28 +408,24 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
return truncated;
|
return truncated;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key derivation function based on a PRF in counter mode - see
|
// Key derivation function based on a pseudo-random function - see
|
||||||
// NIST SP 800-108, section 5.1
|
// NIST SP 800-108, section 5.1
|
||||||
private byte[] counterModeKdf(byte[] secret, byte[] label, long context) {
|
private byte[] macKdf(SecretKey key, byte[]... inputs) {
|
||||||
if (secret.length != SecretKey.LENGTH)
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
// The label must be null-terminated
|
|
||||||
if (label[label.length - 1] != '\0')
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
// Initialise the PRF
|
// Initialise the PRF
|
||||||
Mac prf = new HMac(new SHA256Digest());
|
Mac prf = new HMac(new SHA256Digest());
|
||||||
KeyParameter k = new KeyParameter(secret);
|
prf.init(new KeyParameter(key.getBytes()));
|
||||||
prf.init(k);
|
|
||||||
int macLength = prf.getMacSize();
|
|
||||||
// The output of the PRF must be long enough to use as a key
|
// The output of the PRF must be long enough to use as a key
|
||||||
if (macLength < SecretKey.LENGTH) throw new RuntimeException();
|
int macLength = prf.getMacSize();
|
||||||
|
if (macLength < SecretKey.LENGTH)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
// Calculate the PRF over the concatenated length-prefixed inputs
|
||||||
|
byte[] length = new byte[4];
|
||||||
|
for (byte[] input : inputs) {
|
||||||
|
ByteUtils.writeUint32(input.length, length, 0);
|
||||||
|
prf.update(length, 0, length.length);
|
||||||
|
prf.update(input, 0, input.length);
|
||||||
|
}
|
||||||
byte[] mac = new byte[macLength];
|
byte[] mac = new byte[macLength];
|
||||||
prf.update((byte) 0); // Counter
|
|
||||||
prf.update(label, 0, label.length); // Null-terminated
|
|
||||||
byte[] contextBytes = new byte[4];
|
|
||||||
ByteUtils.writeUint32(context, contextBytes, 0);
|
|
||||||
prf.update(contextBytes, 0, contextBytes.length);
|
|
||||||
prf.update((byte) SecretKey.LENGTH); // Output length
|
|
||||||
prf.doFinal(mac, 0);
|
prf.doFinal(mac, 0);
|
||||||
// The output is the first SecretKey.LENGTH bytes of the MAC
|
// The output is the first SecretKey.LENGTH bytes of the MAC
|
||||||
if (mac.length == SecretKey.LENGTH) return mac;
|
if (mac.length == SecretKey.LENGTH) return mac;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.io.InputStream;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
|
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.api.crypto.SecretKey;
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
import org.briarproject.api.crypto.StreamDecrypter;
|
import org.briarproject.api.crypto.StreamDecrypter;
|
||||||
import org.briarproject.api.crypto.StreamDecrypterFactory;
|
import org.briarproject.api.crypto.StreamDecrypterFactory;
|
||||||
@@ -13,34 +12,21 @@ import org.briarproject.api.transport.StreamContext;
|
|||||||
|
|
||||||
class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
|
class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
|
||||||
private final Provider<AuthenticatedCipher> cipherProvider;
|
private final Provider<AuthenticatedCipher> cipherProvider;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
StreamDecrypterFactoryImpl(CryptoComponent crypto,
|
StreamDecrypterFactoryImpl(Provider<AuthenticatedCipher> cipherProvider) {
|
||||||
Provider<AuthenticatedCipher> cipherProvider) {
|
|
||||||
this.crypto = crypto;
|
|
||||||
this.cipherProvider = cipherProvider;
|
this.cipherProvider = cipherProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamDecrypter createStreamDecrypter(InputStream in,
|
public StreamDecrypter createStreamDecrypter(InputStream in,
|
||||||
StreamContext ctx) {
|
StreamContext ctx) {
|
||||||
// Derive the frame key
|
|
||||||
byte[] secret = ctx.getSecret();
|
|
||||||
long streamNumber = ctx.getStreamNumber();
|
|
||||||
boolean alice = !ctx.getAlice();
|
|
||||||
SecretKey frameKey = crypto.deriveFrameKey(secret, streamNumber, alice);
|
|
||||||
// Create the decrypter
|
|
||||||
AuthenticatedCipher cipher = cipherProvider.get();
|
AuthenticatedCipher cipher = cipherProvider.get();
|
||||||
return new StreamDecrypterImpl(in, cipher, frameKey);
|
return new StreamDecrypterImpl(in, cipher, ctx.getHeaderKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamDecrypter createInvitationStreamDecrypter(InputStream in,
|
public StreamDecrypter createInvitationStreamDecrypter(InputStream in,
|
||||||
byte[] secret, boolean alice) {
|
SecretKey headerKey) {
|
||||||
// Derive the frame key
|
return new StreamDecrypterImpl(in, cipherProvider.get(), headerKey);
|
||||||
SecretKey frameKey = crypto.deriveFrameKey(secret, 0, alice);
|
|
||||||
// Create the decrypter
|
|
||||||
AuthenticatedCipher cipher = cipherProvider.get();
|
|
||||||
return new StreamDecrypterImpl(in, cipher, frameKey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,75 +1,77 @@
|
|||||||
package org.briarproject.crypto;
|
package org.briarproject.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.api.crypto.StreamDecrypter;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
|
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
|
||||||
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
|
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
|
||||||
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
|
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
|
||||||
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||||
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||||
|
|
||||||
import java.io.EOFException;
|
// FIXME: Implementation is incomplete, doesn't read the stream header
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
|
|
||||||
import org.briarproject.api.FormatException;
|
|
||||||
import org.briarproject.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.api.crypto.StreamDecrypter;
|
|
||||||
|
|
||||||
class StreamDecrypterImpl implements StreamDecrypter {
|
class StreamDecrypterImpl implements StreamDecrypter {
|
||||||
|
|
||||||
private final InputStream in;
|
private final InputStream in;
|
||||||
private final AuthenticatedCipher frameCipher;
|
private final AuthenticatedCipher frameCipher;
|
||||||
private final SecretKey frameKey;
|
private final SecretKey frameKey;
|
||||||
private final byte[] iv, header, ciphertext;
|
private final byte[] iv, frameHeader, frameCiphertext;
|
||||||
|
|
||||||
private long frameNumber;
|
private long frameNumber;
|
||||||
private boolean finalFrame;
|
private boolean finalFrame;
|
||||||
|
|
||||||
StreamDecrypterImpl(InputStream in, AuthenticatedCipher frameCipher,
|
StreamDecrypterImpl(InputStream in, AuthenticatedCipher frameCipher,
|
||||||
SecretKey frameKey) {
|
SecretKey headerKey) {
|
||||||
this.in = in;
|
this.in = in;
|
||||||
this.frameCipher = frameCipher;
|
this.frameCipher = frameCipher;
|
||||||
this.frameKey = frameKey;
|
this.frameKey = headerKey; // FIXME
|
||||||
iv = new byte[IV_LENGTH];
|
iv = new byte[IV_LENGTH];
|
||||||
header = new byte[HEADER_LENGTH];
|
frameHeader = new byte[HEADER_LENGTH];
|
||||||
ciphertext = new byte[MAX_FRAME_LENGTH];
|
frameCiphertext = new byte[MAX_FRAME_LENGTH];
|
||||||
frameNumber = 0;
|
frameNumber = 0;
|
||||||
finalFrame = false;
|
finalFrame = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int readFrame(byte[] payload) throws IOException {
|
public int readFrame(byte[] payload) throws IOException {
|
||||||
|
// The buffer must be big enough for a full-size frame
|
||||||
if (payload.length < MAX_PAYLOAD_LENGTH)
|
if (payload.length < MAX_PAYLOAD_LENGTH)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
if (finalFrame) return -1;
|
if (finalFrame) return -1;
|
||||||
// Read the header
|
// Read the frame header
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
while (offset < HEADER_LENGTH) {
|
while (offset < HEADER_LENGTH) {
|
||||||
int read = in.read(ciphertext, offset, HEADER_LENGTH - offset);
|
int read = in.read(frameCiphertext, offset, HEADER_LENGTH - offset);
|
||||||
if (read == -1) throw new EOFException();
|
if (read == -1) throw new EOFException();
|
||||||
offset += read;
|
offset += read;
|
||||||
}
|
}
|
||||||
// Decrypt and authenticate the header
|
// Decrypt and authenticate the frame header
|
||||||
FrameEncoder.encodeIv(iv, frameNumber, true);
|
FrameEncoder.encodeIv(iv, frameNumber, true);
|
||||||
try {
|
try {
|
||||||
frameCipher.init(false, frameKey, iv);
|
frameCipher.init(false, frameKey, iv);
|
||||||
int decrypted = frameCipher.process(ciphertext, 0, HEADER_LENGTH,
|
int decrypted = frameCipher.process(frameCiphertext, 0,
|
||||||
header, 0);
|
HEADER_LENGTH, frameHeader, 0);
|
||||||
if (decrypted != HEADER_LENGTH - MAC_LENGTH)
|
if (decrypted != HEADER_LENGTH - MAC_LENGTH)
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
throw new FormatException();
|
throw new FormatException();
|
||||||
}
|
}
|
||||||
// Decode and validate the header
|
// Decode and validate the frame header
|
||||||
finalFrame = FrameEncoder.isFinalFrame(header);
|
finalFrame = FrameEncoder.isFinalFrame(frameHeader);
|
||||||
int payloadLength = FrameEncoder.getPayloadLength(header);
|
int payloadLength = FrameEncoder.getPayloadLength(frameHeader);
|
||||||
int paddingLength = FrameEncoder.getPaddingLength(header);
|
int paddingLength = FrameEncoder.getPaddingLength(frameHeader);
|
||||||
if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
|
if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
|
||||||
throw new FormatException();
|
throw new FormatException();
|
||||||
// Read the payload and padding
|
// Read the payload and padding
|
||||||
int frameLength = HEADER_LENGTH + payloadLength + paddingLength
|
int frameLength = HEADER_LENGTH + payloadLength + paddingLength
|
||||||
+ MAC_LENGTH;
|
+ MAC_LENGTH;
|
||||||
while (offset < frameLength) {
|
while (offset < frameLength) {
|
||||||
int read = in.read(ciphertext, offset, frameLength - offset);
|
int read = in.read(frameCiphertext, offset, frameLength - offset);
|
||||||
if (read == -1) throw new EOFException();
|
if (read == -1) throw new EOFException();
|
||||||
offset += read;
|
offset += read;
|
||||||
}
|
}
|
||||||
@@ -77,7 +79,7 @@ class StreamDecrypterImpl implements StreamDecrypter {
|
|||||||
FrameEncoder.encodeIv(iv, frameNumber, false);
|
FrameEncoder.encodeIv(iv, frameNumber, false);
|
||||||
try {
|
try {
|
||||||
frameCipher.init(false, frameKey, iv);
|
frameCipher.init(false, frameKey, iv);
|
||||||
int decrypted = frameCipher.process(ciphertext, HEADER_LENGTH,
|
int decrypted = frameCipher.process(frameCiphertext, HEADER_LENGTH,
|
||||||
payloadLength + paddingLength + MAC_LENGTH, payload, 0);
|
payloadLength + paddingLength + MAC_LENGTH, payload, 0);
|
||||||
if (decrypted != payloadLength + paddingLength)
|
if (decrypted != payloadLength + paddingLength)
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
|
|||||||
@@ -27,26 +27,15 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
|||||||
|
|
||||||
public StreamEncrypter createStreamEncrypter(OutputStream out,
|
public StreamEncrypter createStreamEncrypter(OutputStream out,
|
||||||
StreamContext ctx) {
|
StreamContext ctx) {
|
||||||
byte[] secret = ctx.getSecret();
|
|
||||||
long streamNumber = ctx.getStreamNumber();
|
|
||||||
boolean alice = ctx.getAlice();
|
|
||||||
// Encode the tag
|
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
SecretKey tagKey = crypto.deriveTagKey(secret, alice);
|
crypto.encodeTag(tag, ctx.getTagKey(), ctx.getStreamNumber());
|
||||||
crypto.encodeTag(tag, tagKey, streamNumber);
|
|
||||||
// Derive the frame key
|
|
||||||
SecretKey frameKey = crypto.deriveFrameKey(secret, streamNumber, alice);
|
|
||||||
// Create the encrypter
|
|
||||||
AuthenticatedCipher cipher = cipherProvider.get();
|
AuthenticatedCipher cipher = cipherProvider.get();
|
||||||
return new StreamEncrypterImpl(out, cipher, frameKey, tag);
|
return new StreamEncrypterImpl(out, cipher, ctx.getHeaderKey(), tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
|
public StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
|
||||||
byte[] secret, boolean alice) {
|
SecretKey headerKey) {
|
||||||
// Derive the frame key
|
|
||||||
SecretKey frameKey = crypto.deriveFrameKey(secret, 0, alice);
|
|
||||||
// Create the encrypter
|
|
||||||
AuthenticatedCipher cipher = cipherProvider.get();
|
AuthenticatedCipher cipher = cipherProvider.get();
|
||||||
return new StreamEncrypterImpl(out, cipher, frameKey, null);
|
return new StreamEncrypterImpl(out, cipher, headerKey, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
package org.briarproject.crypto;
|
package org.briarproject.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.api.crypto.StreamEncrypter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
|
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
|
||||||
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
|
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
|
||||||
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
|
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
|
||||||
@@ -7,32 +14,26 @@ import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH
|
|||||||
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||||
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||||
|
|
||||||
import java.io.IOException;
|
// FIXME: Implementation is incomplete, doesn't write the stream header
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
|
|
||||||
import org.briarproject.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.api.crypto.StreamEncrypter;
|
|
||||||
|
|
||||||
class StreamEncrypterImpl implements StreamEncrypter {
|
class StreamEncrypterImpl implements StreamEncrypter {
|
||||||
|
|
||||||
private final OutputStream out;
|
private final OutputStream out;
|
||||||
private final AuthenticatedCipher frameCipher;
|
private final AuthenticatedCipher frameCipher;
|
||||||
private final SecretKey frameKey;
|
private final SecretKey frameKey;
|
||||||
private final byte[] tag, iv, plaintext, ciphertext;
|
private final byte[] tag, iv, framePlaintext, frameCiphertext;
|
||||||
|
|
||||||
private long frameNumber;
|
private long frameNumber;
|
||||||
private boolean writeTag;
|
private boolean writeTag;
|
||||||
|
|
||||||
StreamEncrypterImpl(OutputStream out, AuthenticatedCipher frameCipher,
|
StreamEncrypterImpl(OutputStream out, AuthenticatedCipher frameCipher,
|
||||||
SecretKey frameKey, byte[] tag) {
|
SecretKey headerKey, byte[] tag) {
|
||||||
this.out = out;
|
this.out = out;
|
||||||
this.frameCipher = frameCipher;
|
this.frameCipher = frameCipher;
|
||||||
this.frameKey = frameKey;
|
this.frameKey = headerKey; // FIXME
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
iv = new byte[IV_LENGTH];
|
iv = new byte[IV_LENGTH];
|
||||||
plaintext = new byte[HEADER_LENGTH + MAX_PAYLOAD_LENGTH];
|
framePlaintext = new byte[HEADER_LENGTH + MAX_PAYLOAD_LENGTH];
|
||||||
ciphertext = new byte[MAX_FRAME_LENGTH];
|
frameCiphertext = new byte[MAX_FRAME_LENGTH];
|
||||||
frameNumber = 0;
|
frameNumber = 0;
|
||||||
writeTag = (tag != null);
|
writeTag = (tag != null);
|
||||||
}
|
}
|
||||||
@@ -48,37 +49,39 @@ class StreamEncrypterImpl implements StreamEncrypter {
|
|||||||
out.write(tag, 0, tag.length);
|
out.write(tag, 0, tag.length);
|
||||||
writeTag = false;
|
writeTag = false;
|
||||||
}
|
}
|
||||||
// Encode the header
|
// Encode the frame header
|
||||||
FrameEncoder.encodeHeader(plaintext, finalFrame, payloadLength,
|
FrameEncoder.encodeHeader(framePlaintext, finalFrame, payloadLength,
|
||||||
paddingLength);
|
paddingLength);
|
||||||
// Encrypt and authenticate the header
|
// Encrypt and authenticate the frame header
|
||||||
FrameEncoder.encodeIv(iv, frameNumber, true);
|
FrameEncoder.encodeIv(iv, frameNumber, true);
|
||||||
try {
|
try {
|
||||||
frameCipher.init(true, frameKey, iv);
|
frameCipher.init(true, frameKey, iv);
|
||||||
int encrypted = frameCipher.process(plaintext, 0,
|
int encrypted = frameCipher.process(framePlaintext, 0,
|
||||||
HEADER_LENGTH - MAC_LENGTH, ciphertext, 0);
|
HEADER_LENGTH - MAC_LENGTH, frameCiphertext, 0);
|
||||||
if (encrypted != HEADER_LENGTH) throw new RuntimeException();
|
if (encrypted != HEADER_LENGTH) throw new RuntimeException();
|
||||||
} catch (GeneralSecurityException badCipher) {
|
} catch (GeneralSecurityException badCipher) {
|
||||||
throw new RuntimeException(badCipher);
|
throw new RuntimeException(badCipher);
|
||||||
}
|
}
|
||||||
// Combine the payload and padding
|
// Combine the payload and padding
|
||||||
System.arraycopy(payload, 0, plaintext, HEADER_LENGTH, payloadLength);
|
System.arraycopy(payload, 0, framePlaintext, HEADER_LENGTH,
|
||||||
|
payloadLength);
|
||||||
for (int i = 0; i < paddingLength; i++)
|
for (int i = 0; i < paddingLength; i++)
|
||||||
plaintext[HEADER_LENGTH + payloadLength + i] = 0;
|
framePlaintext[HEADER_LENGTH + payloadLength + i] = 0;
|
||||||
// Encrypt and authenticate the payload and padding
|
// Encrypt and authenticate the payload and padding
|
||||||
FrameEncoder.encodeIv(iv, frameNumber, false);
|
FrameEncoder.encodeIv(iv, frameNumber, false);
|
||||||
try {
|
try {
|
||||||
frameCipher.init(true, frameKey, iv);
|
frameCipher.init(true, frameKey, iv);
|
||||||
int encrypted = frameCipher.process(plaintext, HEADER_LENGTH,
|
int encrypted = frameCipher.process(framePlaintext, HEADER_LENGTH,
|
||||||
payloadLength + paddingLength, ciphertext, HEADER_LENGTH);
|
payloadLength + paddingLength, frameCiphertext,
|
||||||
|
HEADER_LENGTH);
|
||||||
if (encrypted != payloadLength + paddingLength + MAC_LENGTH)
|
if (encrypted != payloadLength + paddingLength + MAC_LENGTH)
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
} catch (GeneralSecurityException badCipher) {
|
} catch (GeneralSecurityException badCipher) {
|
||||||
throw new RuntimeException(badCipher);
|
throw new RuntimeException(badCipher);
|
||||||
}
|
}
|
||||||
// Write the frame
|
// Write the frame
|
||||||
out.write(ciphertext, 0, HEADER_LENGTH + payloadLength + paddingLength
|
out.write(frameCiphertext, 0, HEADER_LENGTH + payloadLength
|
||||||
+ MAC_LENGTH);
|
+ paddingLength + MAC_LENGTH);
|
||||||
frameNumber++;
|
frameNumber++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
package org.briarproject.db;
|
package org.briarproject.db;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.briarproject.api.Author;
|
import org.briarproject.api.Author;
|
||||||
import org.briarproject.api.AuthorId;
|
import org.briarproject.api.AuthorId;
|
||||||
import org.briarproject.api.Contact;
|
import org.briarproject.api.Contact;
|
||||||
@@ -25,8 +21,11 @@ import org.briarproject.api.messaging.SubscriptionAck;
|
|||||||
import org.briarproject.api.messaging.SubscriptionUpdate;
|
import org.briarproject.api.messaging.SubscriptionUpdate;
|
||||||
import org.briarproject.api.messaging.TransportAck;
|
import org.briarproject.api.messaging.TransportAck;
|
||||||
import org.briarproject.api.messaging.TransportUpdate;
|
import org.briarproject.api.messaging.TransportUpdate;
|
||||||
import org.briarproject.api.transport.Endpoint;
|
import org.briarproject.api.transport.TransportKeys;
|
||||||
import org.briarproject.api.transport.TemporarySecret;
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
// FIXME: Document the preconditions for calling each method
|
// FIXME: Document the preconditions for calling each method
|
||||||
|
|
||||||
@@ -89,13 +88,6 @@ interface Database<T> {
|
|||||||
ContactId addContact(T txn, Author remote, AuthorId local)
|
ContactId addContact(T txn, Author remote, AuthorId local)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores an endpoint.
|
|
||||||
* <p>
|
|
||||||
* Locking: write.
|
|
||||||
*/
|
|
||||||
void addEndpoint(T txn, Endpoint ep) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribes to a group, or returns false if the user already has the
|
* Subscribes to a group, or returns false if the user already has the
|
||||||
* maximum number of subscriptions.
|
* maximum number of subscriptions.
|
||||||
@@ -125,15 +117,6 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException;
|
void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores the given temporary secrets and deletes any secrets that have
|
|
||||||
* been made obsolete.
|
|
||||||
* <p>
|
|
||||||
* Locking: write.
|
|
||||||
*/
|
|
||||||
void addSecrets(T txn, Collection<TemporarySecret> secrets)
|
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialises the status of the given message with respect to the given
|
* Initialises the status of the given message with respect to the given
|
||||||
* contact.
|
* contact.
|
||||||
@@ -154,6 +137,13 @@ interface Database<T> {
|
|||||||
boolean addTransport(T txn, TransportId t, int maxLatency)
|
boolean addTransport(T txn, TransportId t, int maxLatency)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the given transport keys for a newly added contact.
|
||||||
|
* <p>
|
||||||
|
* Locking: write.
|
||||||
|
*/
|
||||||
|
void addTransportKeys(T txn, ContactId c, TransportKeys k) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a group visible to the given contact.
|
* Makes a group visible to the given contact.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -270,13 +260,6 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
Collection<ContactId> getContacts(T txn, AuthorId a) throws DbException;
|
Collection<ContactId> getContacts(T txn, AuthorId a) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all endpoints.
|
|
||||||
* <p>
|
|
||||||
* Locking: read.
|
|
||||||
*/
|
|
||||||
Collection<Endpoint> getEndpoints(T txn) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the amount of free storage space available to the database, in
|
* Returns the amount of free storage space available to the database, in
|
||||||
* bytes. This is based on the minimum of the space available on the device
|
* bytes. This is based on the minimum of the space available on the device
|
||||||
@@ -461,13 +444,6 @@ interface Database<T> {
|
|||||||
RetentionUpdate getRetentionUpdate(T txn, ContactId c, int maxLatency)
|
RetentionUpdate getRetentionUpdate(T txn, ContactId c, int maxLatency)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all temporary secrets.
|
|
||||||
* <p>
|
|
||||||
* Locking: read.
|
|
||||||
*/
|
|
||||||
Collection<TemporarySecret> getSecrets(T txn) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all settings.
|
* Returns all settings.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -509,7 +485,15 @@ interface Database<T> {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the maximum latencies of all supported transports.
|
* Returns all transport keys for the given transport.
|
||||||
|
* <p>
|
||||||
|
* Locking: read.
|
||||||
|
*/
|
||||||
|
Map<ContactId, TransportKeys> getTransportKeys(T txn, TransportId t)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum latencies in milliseconds of all transports.
|
||||||
* <p>
|
* <p>
|
||||||
* Locking: read.
|
* Locking: read.
|
||||||
*/
|
*/
|
||||||
@@ -540,14 +524,13 @@ interface Database<T> {
|
|||||||
Collection<ContactId> getVisibility(T txn, GroupId g) throws DbException;
|
Collection<ContactId> getVisibility(T txn, GroupId g) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments the outgoing stream counter for the given endpoint in the
|
* Increments the outgoing stream counter for the given contact and
|
||||||
* given rotation period and returns the old value, or -1 if the counter
|
* transport in the given rotation period.
|
||||||
* does not exist.
|
|
||||||
* <p>
|
* <p>
|
||||||
* Locking: write.
|
* Locking: write.
|
||||||
*/
|
*/
|
||||||
long incrementStreamCounter(T txn, ContactId c, TransportId t, long period)
|
void incrementStreamCounter(T txn, ContactId c, TransportId t,
|
||||||
throws DbException;
|
long rotationPeriod) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments the retention time versions for all contacts to indicate that
|
* Increments the retention time versions for all contacts to indicate that
|
||||||
@@ -692,13 +675,13 @@ interface Database<T> {
|
|||||||
void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
|
void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the reordering window for the given endpoint in the given rotation
|
* Sets the reordering window for the given contact and transport in the
|
||||||
* period.
|
* given rotation period.
|
||||||
* <p>
|
* <p>
|
||||||
* Locking: write.
|
* Locking: write.
|
||||||
*/
|
*/
|
||||||
void setReorderingWindow(T txn, ContactId c, TransportId t, long period,
|
void setReorderingWindow(T txn, ContactId c, TransportId t,
|
||||||
long centre, byte[] bitmap) throws DbException;
|
long rotationPeriod, long base, byte[] bitmap) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the groups to which the given contact subscribes and returns
|
* Updates the groups to which the given contact subscribes and returns
|
||||||
@@ -716,7 +699,7 @@ interface Database<T> {
|
|||||||
* <p>
|
* <p>
|
||||||
* Locking: write.
|
* Locking: write.
|
||||||
*/
|
*/
|
||||||
public void setInboxGroup(T txn, ContactId c, Group g) throws DbException;
|
void setInboxGroup(T txn, ContactId c, Group g) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks a message as read or unread.
|
* Marks a message as read or unread.
|
||||||
@@ -798,4 +781,12 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void updateExpiryTime(T txn, ContactId c, MessageId m, int maxLatency)
|
void updateExpiryTime(T txn, ContactId c, MessageId m, int maxLatency)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the given transport keys, deleting any keys they have replaced.
|
||||||
|
* <p>
|
||||||
|
* Locking: write.
|
||||||
|
*/
|
||||||
|
void updateTransportKeys(T txn, Map<ContactId, TransportKeys> keys)
|
||||||
|
throws DbException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,5 @@
|
|||||||
package org.briarproject.db;
|
package org.briarproject.db;
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static org.briarproject.db.DatabaseConstants.BYTES_PER_SWEEP;
|
|
||||||
import static org.briarproject.db.DatabaseConstants.CRITICAL_FREE_SPACE;
|
|
||||||
import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
|
|
||||||
import static org.briarproject.db.DatabaseConstants.MAX_TRANSACTIONS_BETWEEN_SPACE_CHECKS;
|
|
||||||
import static org.briarproject.db.DatabaseConstants.MIN_FREE_SPACE;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import org.briarproject.api.Author;
|
import org.briarproject.api.Author;
|
||||||
import org.briarproject.api.AuthorId;
|
import org.briarproject.api.AuthorId;
|
||||||
import org.briarproject.api.Contact;
|
import org.briarproject.api.Contact;
|
||||||
@@ -75,8 +55,29 @@ import org.briarproject.api.messaging.SubscriptionAck;
|
|||||||
import org.briarproject.api.messaging.SubscriptionUpdate;
|
import org.briarproject.api.messaging.SubscriptionUpdate;
|
||||||
import org.briarproject.api.messaging.TransportAck;
|
import org.briarproject.api.messaging.TransportAck;
|
||||||
import org.briarproject.api.messaging.TransportUpdate;
|
import org.briarproject.api.messaging.TransportUpdate;
|
||||||
import org.briarproject.api.transport.Endpoint;
|
import org.briarproject.api.transport.TransportKeys;
|
||||||
import org.briarproject.api.transport.TemporarySecret;
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.db.DatabaseConstants.BYTES_PER_SWEEP;
|
||||||
|
import static org.briarproject.db.DatabaseConstants.CRITICAL_FREE_SPACE;
|
||||||
|
import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
|
||||||
|
import static org.briarproject.db.DatabaseConstants.MAX_TRANSACTIONS_BETWEEN_SPACE_CHECKS;
|
||||||
|
import static org.briarproject.db.DatabaseConstants.MIN_FREE_SPACE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of DatabaseComponent using reentrant read-write locks.
|
* An implementation of DatabaseComponent using reentrant read-write locks.
|
||||||
@@ -85,7 +86,7 @@ import org.briarproject.api.transport.TemporarySecret;
|
|||||||
* implementation is safe on a given JVM.
|
* implementation is safe on a given JVM.
|
||||||
*/
|
*/
|
||||||
class DatabaseComponentImpl<T> implements DatabaseComponent,
|
class DatabaseComponentImpl<T> implements DatabaseComponent,
|
||||||
DatabaseCleaner.Callback {
|
DatabaseCleaner.Callback {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(DatabaseComponentImpl.class.getName());
|
Logger.getLogger(DatabaseComponentImpl.class.getName());
|
||||||
@@ -180,26 +181,6 @@ DatabaseCleaner.Callback {
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addEndpoint(Endpoint ep) throws DbException {
|
|
||||||
lock.writeLock().lock();
|
|
||||||
try {
|
|
||||||
T txn = db.startTransaction();
|
|
||||||
try {
|
|
||||||
if (!db.containsContact(txn, ep.getContactId()))
|
|
||||||
throw new NoSuchContactException();
|
|
||||||
if (!db.containsTransport(txn, ep.getTransportId()))
|
|
||||||
throw new NoSuchTransportException();
|
|
||||||
db.addEndpoint(txn, ep);
|
|
||||||
db.commitTransaction(txn);
|
|
||||||
} catch (DbException e) {
|
|
||||||
db.abortTransaction(txn);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
lock.writeLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean addGroup(Group g) throws DbException {
|
public boolean addGroup(Group g) throws DbException {
|
||||||
boolean added = false;
|
boolean added = false;
|
||||||
lock.writeLock().lock();
|
lock.writeLock().lock();
|
||||||
@@ -290,30 +271,6 @@ DatabaseCleaner.Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addSecrets(Collection<TemporarySecret> secrets)
|
|
||||||
throws DbException {
|
|
||||||
lock.writeLock().lock();
|
|
||||||
try {
|
|
||||||
T txn = db.startTransaction();
|
|
||||||
try {
|
|
||||||
Collection<TemporarySecret> relevant =
|
|
||||||
new ArrayList<TemporarySecret>();
|
|
||||||
for (TemporarySecret s : secrets) {
|
|
||||||
if (db.containsContact(txn, s.getContactId()))
|
|
||||||
if (db.containsTransport(txn, s.getTransportId()))
|
|
||||||
relevant.add(s);
|
|
||||||
}
|
|
||||||
if (!secrets.isEmpty()) db.addSecrets(txn, relevant);
|
|
||||||
db.commitTransaction(txn);
|
|
||||||
} catch (DbException e) {
|
|
||||||
db.abortTransaction(txn);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
lock.writeLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean addTransport(TransportId t, int maxLatency)
|
public boolean addTransport(TransportId t, int maxLatency)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
boolean added;
|
boolean added;
|
||||||
@@ -334,6 +291,27 @@ DatabaseCleaner.Callback {
|
|||||||
return added;
|
return added;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addTransportKeys(ContactId c, TransportKeys k)
|
||||||
|
throws DbException {
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
T txn = db.startTransaction();
|
||||||
|
try {
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
if (!db.containsTransport(txn, k.getTransportId()))
|
||||||
|
throw new NoSuchTransportException();
|
||||||
|
db.addTransportKeys(txn, c, k);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
} catch (DbException e) {
|
||||||
|
db.abortTransaction(txn);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Ack generateAck(ContactId c, int maxMessages) throws DbException {
|
public Ack generateAck(ContactId c, int maxMessages) throws DbException {
|
||||||
Collection<MessageId> ids;
|
Collection<MessageId> ids;
|
||||||
lock.writeLock().lock();
|
lock.writeLock().lock();
|
||||||
@@ -883,23 +861,6 @@ DatabaseCleaner.Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<TemporarySecret> getSecrets() throws DbException {
|
|
||||||
lock.readLock().lock();
|
|
||||||
try {
|
|
||||||
T txn = db.startTransaction();
|
|
||||||
try {
|
|
||||||
Collection<TemporarySecret> secrets = db.getSecrets(txn);
|
|
||||||
db.commitTransaction(txn);
|
|
||||||
return secrets;
|
|
||||||
} catch (DbException e) {
|
|
||||||
db.abortTransaction(txn);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
lock.readLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Settings getSettings() throws DbException {
|
public Settings getSettings() throws DbException {
|
||||||
lock.readLock().lock();
|
lock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
@@ -934,6 +895,27 @@ DatabaseCleaner.Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<ContactId, TransportKeys> getTransportKeys(TransportId t)
|
||||||
|
throws DbException {
|
||||||
|
lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
T txn = db.startTransaction();
|
||||||
|
try {
|
||||||
|
if (!db.containsTransport(txn, t))
|
||||||
|
throw new NoSuchTransportException();
|
||||||
|
Map<ContactId, TransportKeys> keys =
|
||||||
|
db.getTransportKeys(txn, t);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
return keys;
|
||||||
|
} catch (DbException e) {
|
||||||
|
db.abortTransaction(txn);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Map<TransportId, Integer> getTransportLatencies()
|
public Map<TransportId, Integer> getTransportLatencies()
|
||||||
throws DbException {
|
throws DbException {
|
||||||
lock.readLock().lock();
|
lock.readLock().lock();
|
||||||
@@ -989,8 +971,8 @@ DatabaseCleaner.Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long incrementStreamCounter(ContactId c, TransportId t,
|
public void incrementStreamCounter(ContactId c, TransportId t,
|
||||||
long period) throws DbException {
|
long rotationPeriod) throws DbException {
|
||||||
lock.writeLock().lock();
|
lock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
T txn = db.startTransaction();
|
T txn = db.startTransaction();
|
||||||
@@ -999,9 +981,8 @@ DatabaseCleaner.Callback {
|
|||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
if (!db.containsTransport(txn, t))
|
if (!db.containsTransport(txn, t))
|
||||||
throw new NoSuchTransportException();
|
throw new NoSuchTransportException();
|
||||||
long counter = db.incrementStreamCounter(txn, c, t, period);
|
db.incrementStreamCounter(txn, c, t, rotationPeriod);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
return counter;
|
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
db.abortTransaction(txn);
|
db.abortTransaction(txn);
|
||||||
throw e;
|
throw e;
|
||||||
@@ -1404,27 +1385,6 @@ DatabaseCleaner.Callback {
|
|||||||
eventBus.broadcast(new TransportRemovedEvent(t));
|
eventBus.broadcast(new TransportRemovedEvent(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setReorderingWindow(ContactId c, TransportId t, long period,
|
|
||||||
long centre, byte[] bitmap) throws DbException {
|
|
||||||
lock.writeLock().lock();
|
|
||||||
try {
|
|
||||||
T txn = db.startTransaction();
|
|
||||||
try {
|
|
||||||
if (!db.containsContact(txn, c))
|
|
||||||
throw new NoSuchContactException();
|
|
||||||
if (!db.containsTransport(txn, t))
|
|
||||||
throw new NoSuchTransportException();
|
|
||||||
db.setReorderingWindow(txn, c, t, period, centre, bitmap);
|
|
||||||
db.commitTransaction(txn);
|
|
||||||
} catch (DbException e) {
|
|
||||||
db.abortTransaction(txn);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
lock.writeLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInboxGroup(ContactId c, Group g) throws DbException {
|
public void setInboxGroup(ContactId c, Group g) throws DbException {
|
||||||
lock.writeLock().lock();
|
lock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
@@ -1480,6 +1440,27 @@ DatabaseCleaner.Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setReorderingWindow(ContactId c, TransportId t,
|
||||||
|
long rotationPeriod, long base, byte[] bitmap) throws DbException {
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
T txn = db.startTransaction();
|
||||||
|
try {
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
if (!db.containsTransport(txn, t))
|
||||||
|
throw new NoSuchTransportException();
|
||||||
|
db.setReorderingWindow(txn, c, t, rotationPeriod, base, bitmap);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
} catch (DbException e) {
|
||||||
|
db.abortTransaction(txn);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setVisibility(GroupId g, Collection<ContactId> visible)
|
public void setVisibility(GroupId g, Collection<ContactId> visible)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Collection<ContactId> affected = new ArrayList<ContactId>();
|
Collection<ContactId> affected = new ArrayList<ContactId>();
|
||||||
@@ -1552,6 +1533,33 @@ DatabaseCleaner.Callback {
|
|||||||
eventBus.broadcast(new LocalSubscriptionsUpdatedEvent(affected));
|
eventBus.broadcast(new LocalSubscriptionsUpdatedEvent(affected));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateTransportKeys(Map<ContactId, TransportKeys> keys)
|
||||||
|
throws DbException {
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
T txn = db.startTransaction();
|
||||||
|
try {
|
||||||
|
Map<ContactId, TransportKeys> filtered =
|
||||||
|
new HashMap<ContactId, TransportKeys>();
|
||||||
|
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
||||||
|
ContactId c = e.getKey();
|
||||||
|
TransportKeys k = e.getValue();
|
||||||
|
if (db.containsContact(txn, c)
|
||||||
|
&& db.containsTransport(txn, k.getTransportId())) {
|
||||||
|
filtered.put(c, k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.updateTransportKeys(txn, filtered);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
} catch (DbException e) {
|
||||||
|
db.abortTransaction(txn);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void checkFreeSpaceAndClean() throws DbException {
|
public void checkFreeSpaceAndClean() throws DbException {
|
||||||
long freeSpace = db.getFreeSpace();
|
long freeSpace = db.getFreeSpace();
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(freeSpace + " bytes free space");
|
if (LOG.isLoggable(INFO)) LOG.info(freeSpace + " bytes free space");
|
||||||
|
|||||||
@@ -1,14 +1,33 @@
|
|||||||
package org.briarproject.db;
|
package org.briarproject.db;
|
||||||
|
|
||||||
import static java.sql.Types.BINARY;
|
import org.briarproject.api.Author;
|
||||||
import static java.sql.Types.VARCHAR;
|
import org.briarproject.api.AuthorId;
|
||||||
import static java.util.logging.Level.WARNING;
|
import org.briarproject.api.Contact;
|
||||||
import static org.briarproject.api.Author.Status.ANONYMOUS;
|
import org.briarproject.api.ContactId;
|
||||||
import static org.briarproject.api.Author.Status.UNKNOWN;
|
import org.briarproject.api.LocalAuthor;
|
||||||
import static org.briarproject.api.Author.Status.VERIFIED;
|
import org.briarproject.api.Settings;
|
||||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
|
import org.briarproject.api.TransportConfig;
|
||||||
import static org.briarproject.api.messaging.MessagingConstants.RETENTION_GRANULARITY;
|
import org.briarproject.api.TransportId;
|
||||||
import static org.briarproject.db.ExponentialBackoff.calculateExpiry;
|
import org.briarproject.api.TransportProperties;
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.api.db.DbClosedException;
|
||||||
|
import org.briarproject.api.db.DbException;
|
||||||
|
import org.briarproject.api.db.MessageHeader;
|
||||||
|
import org.briarproject.api.db.MessageHeader.State;
|
||||||
|
import org.briarproject.api.messaging.Group;
|
||||||
|
import org.briarproject.api.messaging.GroupId;
|
||||||
|
import org.briarproject.api.messaging.Message;
|
||||||
|
import org.briarproject.api.messaging.MessageId;
|
||||||
|
import org.briarproject.api.messaging.RetentionAck;
|
||||||
|
import org.briarproject.api.messaging.RetentionUpdate;
|
||||||
|
import org.briarproject.api.messaging.SubscriptionAck;
|
||||||
|
import org.briarproject.api.messaging.SubscriptionUpdate;
|
||||||
|
import org.briarproject.api.messaging.TransportAck;
|
||||||
|
import org.briarproject.api.messaging.TransportUpdate;
|
||||||
|
import org.briarproject.api.system.Clock;
|
||||||
|
import org.briarproject.api.transport.IncomingKeys;
|
||||||
|
import org.briarproject.api.transport.OutgoingKeys;
|
||||||
|
import org.briarproject.api.transport.TransportKeys;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
@@ -32,32 +51,15 @@ import java.util.concurrent.locks.Lock;
|
|||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.briarproject.api.Author;
|
import static java.sql.Types.BINARY;
|
||||||
import org.briarproject.api.AuthorId;
|
import static java.sql.Types.VARCHAR;
|
||||||
import org.briarproject.api.Contact;
|
import static java.util.logging.Level.WARNING;
|
||||||
import org.briarproject.api.ContactId;
|
import static org.briarproject.api.Author.Status.ANONYMOUS;
|
||||||
import org.briarproject.api.LocalAuthor;
|
import static org.briarproject.api.Author.Status.UNKNOWN;
|
||||||
import org.briarproject.api.Settings;
|
import static org.briarproject.api.Author.Status.VERIFIED;
|
||||||
import org.briarproject.api.TransportConfig;
|
import static org.briarproject.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
|
||||||
import org.briarproject.api.TransportId;
|
import static org.briarproject.api.messaging.MessagingConstants.RETENTION_GRANULARITY;
|
||||||
import org.briarproject.api.TransportProperties;
|
import static org.briarproject.db.ExponentialBackoff.calculateExpiry;
|
||||||
import org.briarproject.api.db.DbClosedException;
|
|
||||||
import org.briarproject.api.db.DbException;
|
|
||||||
import org.briarproject.api.db.MessageHeader;
|
|
||||||
import org.briarproject.api.db.MessageHeader.State;
|
|
||||||
import org.briarproject.api.messaging.Group;
|
|
||||||
import org.briarproject.api.messaging.GroupId;
|
|
||||||
import org.briarproject.api.messaging.Message;
|
|
||||||
import org.briarproject.api.messaging.MessageId;
|
|
||||||
import org.briarproject.api.messaging.RetentionAck;
|
|
||||||
import org.briarproject.api.messaging.RetentionUpdate;
|
|
||||||
import org.briarproject.api.messaging.SubscriptionAck;
|
|
||||||
import org.briarproject.api.messaging.SubscriptionUpdate;
|
|
||||||
import org.briarproject.api.messaging.TransportAck;
|
|
||||||
import org.briarproject.api.messaging.TransportUpdate;
|
|
||||||
import org.briarproject.api.system.Clock;
|
|
||||||
import org.briarproject.api.transport.Endpoint;
|
|
||||||
import org.briarproject.api.transport.TemporarySecret;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic database implementation that can be used with any JDBC-compatible
|
* A generic database implementation that can be used with any JDBC-compatible
|
||||||
@@ -65,8 +67,8 @@ import org.briarproject.api.transport.TemporarySecret;
|
|||||||
*/
|
*/
|
||||||
abstract class JdbcDatabase implements Database<Connection> {
|
abstract class JdbcDatabase implements Database<Connection> {
|
||||||
|
|
||||||
private static final int SCHEMA_VERSION = 9;
|
private static final int SCHEMA_VERSION = 10;
|
||||||
private static final int MIN_SCHEMA_VERSION = 9;
|
private static final int MIN_SCHEMA_VERSION = 10;
|
||||||
|
|
||||||
private static final String CREATE_SETTINGS =
|
private static final String CREATE_SETTINGS =
|
||||||
"CREATE TABLE settings"
|
"CREATE TABLE settings"
|
||||||
@@ -277,13 +279,16 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " REFERENCES contacts (contactId)"
|
+ " REFERENCES contacts (contactId)"
|
||||||
+ " ON DELETE CASCADE)";
|
+ " ON DELETE CASCADE)";
|
||||||
|
|
||||||
private static final String CREATE_ENDPOINTS =
|
private static final String CREATE_INCOMING_KEYS =
|
||||||
"CREATE TABLE endpoints"
|
"CREATE TABLE incomingKeys"
|
||||||
+ " (contactId INT NOT NULL,"
|
+ " (contactId INT NOT NULL,"
|
||||||
+ " transportId VARCHAR NOT NULL,"
|
+ " transportId VARCHAR NOT NULL,"
|
||||||
+ " epoch BIGINT NOT NULL,"
|
+ " period BIGINT NOT NULL,"
|
||||||
+ " alice BOOLEAN NOT NULL,"
|
+ " tagKey SECRET NOT NULL,"
|
||||||
+ " PRIMARY KEY (contactId, transportId),"
|
+ " headerKey SECRET NOT NULL,"
|
||||||
|
+ " base BIGINT NOT NULL,"
|
||||||
|
+ " bitmap BINARY NOT NULL,"
|
||||||
|
+ " PRIMARY KEY (contactId, transportId, period),"
|
||||||
+ " FOREIGN KEY (contactId)"
|
+ " FOREIGN KEY (contactId)"
|
||||||
+ " REFERENCES contacts (contactId)"
|
+ " REFERENCES contacts (contactId)"
|
||||||
+ " ON DELETE CASCADE,"
|
+ " ON DELETE CASCADE,"
|
||||||
@@ -291,16 +296,15 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " REFERENCES transports (transportId)"
|
+ " REFERENCES transports (transportId)"
|
||||||
+ " ON DELETE CASCADE)";
|
+ " ON DELETE CASCADE)";
|
||||||
|
|
||||||
private static final String CREATE_SECRETS =
|
private static final String CREATE_OUTGOING_KEYS =
|
||||||
"CREATE TABLE secrets"
|
"CREATE TABLE outgoingKeys"
|
||||||
+ " (contactId INT NOT NULL,"
|
+ " (contactId INT NOT NULL,"
|
||||||
+ " transportId VARCHAR NOT NULL,"
|
+ " transportId VARCHAR NOT NULL,"
|
||||||
+ " period BIGINT NOT NULL,"
|
+ " period BIGINT NOT NULL,"
|
||||||
+ " secret SECRET NOT NULL,"
|
+ " tagKey SECRET NOT NULL,"
|
||||||
+ " outgoing BIGINT NOT NULL,"
|
+ " headerKey SECRET NOT NULL,"
|
||||||
+ " centre BIGINT NOT NULL,"
|
+ " stream BIGINT NOT NULL,"
|
||||||
+ " bitmap BINARY NOT NULL,"
|
+ " PRIMARY KEY (contactId, transportId),"
|
||||||
+ " PRIMARY KEY (contactId, transportId, period),"
|
|
||||||
+ " FOREIGN KEY (contactId)"
|
+ " FOREIGN KEY (contactId)"
|
||||||
+ " REFERENCES contacts (contactId)"
|
+ " REFERENCES contacts (contactId)"
|
||||||
+ " ON DELETE CASCADE,"
|
+ " ON DELETE CASCADE,"
|
||||||
@@ -324,6 +328,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
private boolean closed = false; // Locking: connectionsLock
|
private boolean closed = false; // Locking: connectionsLock
|
||||||
|
|
||||||
protected abstract Connection createConnection() throws SQLException;
|
protected abstract Connection createConnection() throws SQLException;
|
||||||
|
|
||||||
protected abstract void flushBuffersToDisk(Statement s) throws SQLException;
|
protected abstract void flushBuffersToDisk(Statement s) throws SQLException;
|
||||||
|
|
||||||
private final Lock connectionsLock = new ReentrantLock();
|
private final Lock connectionsLock = new ReentrantLock();
|
||||||
@@ -339,7 +344,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void open(String driverClass, boolean reopen) throws DbException,
|
protected void open(String driverClass, boolean reopen) throws DbException,
|
||||||
IOException {
|
IOException {
|
||||||
// Load the JDBC driver
|
// Load the JDBC driver
|
||||||
try {
|
try {
|
||||||
Class.forName(driverClass);
|
Class.forName(driverClass);
|
||||||
@@ -382,7 +387,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
if (rs != null) rs.close();
|
if (rs != null) rs.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
if (LOG.isLoggable(WARNING))LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,7 +395,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
if (s != null) s.close();
|
if (s != null) s.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
if (LOG.isLoggable(WARNING))LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,8 +423,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
s.executeUpdate(insertTypeNames(CREATE_TRANSPORT_VERSIONS));
|
s.executeUpdate(insertTypeNames(CREATE_TRANSPORT_VERSIONS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORT_PROPS));
|
s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORT_PROPS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORT_VERSIONS));
|
s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORT_VERSIONS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_ENDPOINTS));
|
s.executeUpdate(insertTypeNames(CREATE_INCOMING_KEYS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_SECRETS));
|
s.executeUpdate(insertTypeNames(CREATE_OUTGOING_KEYS));
|
||||||
s.close();
|
s.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(s);
|
tryToClose(s);
|
||||||
@@ -480,7 +485,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
txn.close();
|
txn.close();
|
||||||
} catch (SQLException e1) {
|
} catch (SQLException e1) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e1.toString(), e1);
|
if (LOG.isLoggable(WARNING))
|
||||||
|
LOG.log(WARNING, e1.toString(), e1);
|
||||||
}
|
}
|
||||||
// Whatever happens, allow the database to close
|
// Whatever happens, allow the database to close
|
||||||
connectionsLock.lock();
|
connectionsLock.lock();
|
||||||
@@ -679,26 +685,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addEndpoint(Connection txn, Endpoint ep) throws DbException {
|
|
||||||
PreparedStatement ps = null;
|
|
||||||
try {
|
|
||||||
String sql = "INSERT INTO endpoints"
|
|
||||||
+ " (contactId, transportId, epoch, alice)"
|
|
||||||
+ " VALUES (?, ?, ?, ?)";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, ep.getContactId().getInt());
|
|
||||||
ps.setString(2, ep.getTransportId().getString());
|
|
||||||
ps.setLong(3, ep.getEpoch());
|
|
||||||
ps.setBoolean(4, ep.getAlice());
|
|
||||||
int affected = ps.executeUpdate();
|
|
||||||
if (affected != 1) throw new DbStateException();
|
|
||||||
ps.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
tryToClose(ps);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean addGroup(Connection txn, Group g) throws DbException {
|
public boolean addGroup(Connection txn, Group g) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
@@ -824,52 +810,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addSecrets(Connection txn, Collection<TemporarySecret> secrets)
|
|
||||||
throws DbException {
|
|
||||||
PreparedStatement ps = null;
|
|
||||||
try {
|
|
||||||
// Store the new secrets
|
|
||||||
String sql = "INSERT INTO secrets (contactId, transportId, period,"
|
|
||||||
+ " secret, outgoing, centre, bitmap)"
|
|
||||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
for (TemporarySecret s : secrets) {
|
|
||||||
ps.setInt(1, s.getContactId().getInt());
|
|
||||||
ps.setString(2, s.getTransportId().getString());
|
|
||||||
ps.setLong(3, s.getPeriod());
|
|
||||||
ps.setBytes(4, s.getSecret());
|
|
||||||
ps.setLong(5, s.getOutgoingStreamCounter());
|
|
||||||
ps.setLong(6, s.getWindowCentre());
|
|
||||||
ps.setBytes(7, s.getWindowBitmap());
|
|
||||||
ps.addBatch();
|
|
||||||
}
|
|
||||||
int[] batchAffected = ps.executeBatch();
|
|
||||||
if (batchAffected.length != secrets.size())
|
|
||||||
throw new DbStateException();
|
|
||||||
for (int i = 0; i < batchAffected.length; i++) {
|
|
||||||
if (batchAffected[i] != 1) throw new DbStateException();
|
|
||||||
}
|
|
||||||
ps.close();
|
|
||||||
// Delete any obsolete secrets
|
|
||||||
sql = "DELETE FROM secrets"
|
|
||||||
+ " WHERE contactId = ? AND transportId = ? AND period < ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
for (TemporarySecret s : secrets) {
|
|
||||||
ps.setInt(1, s.getContactId().getInt());
|
|
||||||
ps.setString(2, s.getTransportId().getString());
|
|
||||||
ps.setLong(3, s.getPeriod() - 2);
|
|
||||||
ps.addBatch();
|
|
||||||
}
|
|
||||||
batchAffected = ps.executeBatch();
|
|
||||||
if (batchAffected.length != secrets.size())
|
|
||||||
throw new DbStateException();
|
|
||||||
ps.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
tryToClose(ps);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addStatus(Connection txn, ContactId c, MessageId m, boolean ack,
|
public void addStatus(Connection txn, ContactId c, MessageId m, boolean ack,
|
||||||
boolean seen) throws DbException {
|
boolean seen) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
@@ -947,6 +887,68 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addTransportKeys(Connection txn, ContactId c, TransportKeys k)
|
||||||
|
throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
try {
|
||||||
|
// Store the incoming keys
|
||||||
|
String sql = "INSERT INTO incomingKeys (contactId, transportId,"
|
||||||
|
+ " period, tagKey, headerKey, base, bitmap)"
|
||||||
|
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setString(2, k.getTransportId().getString());
|
||||||
|
// Previous rotation period
|
||||||
|
IncomingKeys inPrev = k.getPreviousIncomingKeys();
|
||||||
|
ps.setLong(3, inPrev.getRotationPeriod());
|
||||||
|
ps.setBytes(4, inPrev.getTagKey().getBytes());
|
||||||
|
ps.setBytes(5, inPrev.getHeaderKey().getBytes());
|
||||||
|
ps.setLong(6, inPrev.getWindowBase());
|
||||||
|
ps.setBytes(7, inPrev.getWindowBitmap());
|
||||||
|
ps.addBatch();
|
||||||
|
// Current rotation period
|
||||||
|
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
||||||
|
ps.setLong(3, inCurr.getRotationPeriod());
|
||||||
|
ps.setBytes(4, inCurr.getTagKey().getBytes());
|
||||||
|
ps.setBytes(5, inCurr.getHeaderKey().getBytes());
|
||||||
|
ps.setLong(6, inCurr.getWindowBase());
|
||||||
|
ps.setBytes(7, inCurr.getWindowBitmap());
|
||||||
|
ps.addBatch();
|
||||||
|
// Next rotation period
|
||||||
|
IncomingKeys inNext = k.getNextIncomingKeys();
|
||||||
|
ps.setLong(3, inNext.getRotationPeriod());
|
||||||
|
ps.setBytes(4, inNext.getTagKey().getBytes());
|
||||||
|
ps.setBytes(5, inNext.getHeaderKey().getBytes());
|
||||||
|
ps.setLong(6, inNext.getWindowBase());
|
||||||
|
ps.setBytes(7, inNext.getWindowBitmap());
|
||||||
|
ps.addBatch();
|
||||||
|
int[] batchAffected = ps.executeBatch();
|
||||||
|
if (batchAffected.length != 3) throw new DbStateException();
|
||||||
|
for (int i = 0; i < batchAffected.length; i++) {
|
||||||
|
if (batchAffected[i] != 1) throw new DbStateException();
|
||||||
|
}
|
||||||
|
ps.close();
|
||||||
|
// Store the outgoing keys
|
||||||
|
sql = "INSERT INTO outgoingKeys (contactId, transportId, period,"
|
||||||
|
+ " tagKey, headerKey, stream)"
|
||||||
|
+ " VALUES (?, ?, ?, ?, ?, ?)";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setString(2, k.getTransportId().getString());
|
||||||
|
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||||
|
ps.setLong(3, outCurr.getRotationPeriod());
|
||||||
|
ps.setBytes(4, outCurr.getTagKey().getBytes());
|
||||||
|
ps.setBytes(5, outCurr.getHeaderKey().getBytes());
|
||||||
|
ps.setLong(6, outCurr.getStreamCounter());
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
|
if (affected != 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(ps);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void addVisibility(Connection txn, ContactId c, GroupId g)
|
public void addVisibility(Connection txn, ContactId c, GroupId g)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
@@ -1326,32 +1328,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<Endpoint> getEndpoints(Connection txn)
|
|
||||||
throws DbException {
|
|
||||||
PreparedStatement ps = null;
|
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
|
||||||
String sql = "SELECT contactId, transportId, epoch, alice"
|
|
||||||
+ " FROM endpoints";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
rs = ps.executeQuery();
|
|
||||||
List<Endpoint> endpoints = new ArrayList<Endpoint>();
|
|
||||||
while (rs.next()) {
|
|
||||||
ContactId contactId = new ContactId(rs.getInt(1));
|
|
||||||
TransportId transportId = new TransportId(rs.getString(2));
|
|
||||||
long epoch = rs.getLong(3);
|
|
||||||
boolean alice = rs.getBoolean(4);
|
|
||||||
endpoints.add(new Endpoint(contactId, transportId, epoch,
|
|
||||||
alice));
|
|
||||||
}
|
|
||||||
return Collections.unmodifiableList(endpoints);
|
|
||||||
} catch (SQLException e) {
|
|
||||||
tryToClose(rs);
|
|
||||||
tryToClose(ps);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Group getGroup(Connection txn, GroupId g) throws DbException {
|
public Group getGroup(Connection txn, GroupId g) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
@@ -2098,43 +2074,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<TemporarySecret> getSecrets(Connection txn)
|
|
||||||
throws DbException {
|
|
||||||
PreparedStatement ps = null;
|
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
|
||||||
String sql = "SELECT e.contactId, e.transportId, epoch, alice,"
|
|
||||||
+ " period, secret, outgoing, centre, bitmap"
|
|
||||||
+ " FROM endpoints AS e"
|
|
||||||
+ " JOIN secrets AS s"
|
|
||||||
+ " ON e.contactId = s.contactId"
|
|
||||||
+ " AND e.transportId = s.transportId";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
rs = ps.executeQuery();
|
|
||||||
List<TemporarySecret> secrets = new ArrayList<TemporarySecret>();
|
|
||||||
while (rs.next()) {
|
|
||||||
ContactId contactId = new ContactId(rs.getInt(1));
|
|
||||||
TransportId transportId = new TransportId(rs.getString(2));
|
|
||||||
long epoch = rs.getLong(3);
|
|
||||||
boolean alice = rs.getBoolean(4);
|
|
||||||
long period = rs.getLong(5);
|
|
||||||
byte[] secret = rs.getBytes(6);
|
|
||||||
long outgoing = rs.getLong(7);
|
|
||||||
long centre = rs.getLong(8);
|
|
||||||
byte[] bitmap = rs.getBytes(9);
|
|
||||||
secrets.add(new TemporarySecret(contactId, transportId, epoch,
|
|
||||||
alice, period, secret, outgoing, centre, bitmap));
|
|
||||||
}
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return Collections.unmodifiableList(secrets);
|
|
||||||
} catch (SQLException e) {
|
|
||||||
tryToClose(rs);
|
|
||||||
tryToClose(ps);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Settings getSettings(Connection txn) throws DbException {
|
public Settings getSettings(Connection txn) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
@@ -2317,6 +2256,67 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<ContactId, TransportKeys> getTransportKeys(Connection txn,
|
||||||
|
TransportId t) throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
// Retrieve the incoming keys
|
||||||
|
String sql = "SELECT period, tagKey, headerKey, base, bitmap"
|
||||||
|
+ " FROM incomingKeys"
|
||||||
|
+ " WHERE transportId = ?"
|
||||||
|
+ " ORDER BY contactId, period";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setString(1, t.getString());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
List<IncomingKeys> inKeys = new ArrayList<IncomingKeys>();
|
||||||
|
while (rs.next()) {
|
||||||
|
long rotationPeriod = rs.getLong(1);
|
||||||
|
SecretKey tagKey = new SecretKey(rs.getBytes(2));
|
||||||
|
SecretKey headerKey = new SecretKey(rs.getBytes(3));
|
||||||
|
long windowBase = rs.getLong(4);
|
||||||
|
byte[] windowBitmap = rs.getBytes(5);
|
||||||
|
inKeys.add(new IncomingKeys(tagKey, headerKey, rotationPeriod,
|
||||||
|
windowBase, windowBitmap));
|
||||||
|
}
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
// Retrieve the outgoing keys in the same order
|
||||||
|
sql = "SELECT contactId, period, tagKey, headerKey, stream"
|
||||||
|
+ " FROM outgoingKeys"
|
||||||
|
+ " WHERE transportId = ?"
|
||||||
|
+ " ORDER BY contactId, period";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setString(1, t.getString());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
Map<ContactId, TransportKeys> keys =
|
||||||
|
new HashMap<ContactId, TransportKeys>();
|
||||||
|
for (int i = 0; rs.next(); i++) {
|
||||||
|
// There should be three times as many incoming keys
|
||||||
|
if (inKeys.size() < (i + 1) * 3) throw new DbStateException();
|
||||||
|
ContactId contactId = new ContactId(rs.getInt(1));
|
||||||
|
long rotationPeriod = rs.getLong(2);
|
||||||
|
SecretKey tagKey = new SecretKey(rs.getBytes(3));
|
||||||
|
SecretKey headerKey = new SecretKey(rs.getBytes(4));
|
||||||
|
long streamCounter = rs.getLong(5);
|
||||||
|
OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey,
|
||||||
|
rotationPeriod, streamCounter);
|
||||||
|
IncomingKeys inPrev = inKeys.get(i * 3);
|
||||||
|
IncomingKeys inCurr = inKeys.get(i * 3 + 1);
|
||||||
|
IncomingKeys inNext = inKeys.get(i * 3 + 2);
|
||||||
|
keys.put(contactId, new TransportKeys(t, inPrev, inCurr,
|
||||||
|
inNext, outCurr));
|
||||||
|
}
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return Collections.unmodifiableMap(keys);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(rs);
|
||||||
|
tryToClose(ps);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Map<TransportId, Integer> getTransportLatencies(Connection txn)
|
public Map<TransportId, Integer> getTransportLatencies(Connection txn)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
@@ -2327,7 +2327,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
Map<TransportId, Integer> latencies =
|
Map<TransportId, Integer> latencies =
|
||||||
new HashMap<TransportId, Integer>();
|
new HashMap<TransportId, Integer>();
|
||||||
while (rs.next()){
|
while (rs.next()) {
|
||||||
TransportId id = new TransportId(rs.getString(1));
|
TransportId id = new TransportId(rs.getString(1));
|
||||||
latencies.put(id, rs.getInt(2));
|
latencies.put(id, rs.getInt(2));
|
||||||
}
|
}
|
||||||
@@ -2392,7 +2392,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.setString(3, u.getId().getString());
|
ps.setString(3, u.getId().getString());
|
||||||
ps.addBatch();
|
ps.addBatch();
|
||||||
}
|
}
|
||||||
int [] batchAffected = ps.executeBatch();
|
int[] batchAffected = ps.executeBatch();
|
||||||
if (batchAffected.length != updates.size())
|
if (batchAffected.length != updates.size())
|
||||||
throw new DbStateException();
|
throw new DbStateException();
|
||||||
for (i = 0; i < batchAffected.length; i++) {
|
for (i = 0; i < batchAffected.length; i++) {
|
||||||
@@ -2455,42 +2455,21 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long incrementStreamCounter(Connection txn, ContactId c,
|
public void incrementStreamCounter(Connection txn, ContactId c,
|
||||||
TransportId t, long period) throws DbException {
|
TransportId t, long rotationPeriod) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
try {
|
||||||
// Get the current stream counter
|
String sql = "UPDATE outgoingKeys SET stream = stream + 1"
|
||||||
String sql = "SELECT outgoing FROM secrets"
|
|
||||||
+ " WHERE contactId = ? AND transportId = ? AND period = ?";
|
+ " WHERE contactId = ? AND transportId = ? AND period = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
ps.setString(2, t.getString());
|
ps.setString(2, t.getString());
|
||||||
ps.setLong(3, period);
|
ps.setLong(3, rotationPeriod);
|
||||||
rs = ps.executeQuery();
|
|
||||||
if (!rs.next()) {
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
long streamNumber = rs.getLong(1);
|
|
||||||
if (rs.next()) throw new DbStateException();
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
// Increment the stream counter
|
|
||||||
sql = "UPDATE secrets SET outgoing = outgoing + 1"
|
|
||||||
+ " WHERE contactId = ? AND transportId = ? AND period = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, c.getInt());
|
|
||||||
ps.setString(2, t.getString());
|
|
||||||
ps.setLong(3, period);
|
|
||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
return streamNumber;
|
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps);
|
||||||
tryToClose(rs);
|
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2929,18 +2908,19 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setReorderingWindow(Connection txn, ContactId c, TransportId t,
|
public void setReorderingWindow(Connection txn, ContactId c, TransportId t,
|
||||||
long period, long centre, byte[] bitmap) throws DbException {
|
long rotationPeriod, long base, byte[] bitmap) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
String sql = "UPDATE secrets SET centre = ?, bitmap = ?"
|
String sql = "UPDATE incomingKeys SET base = ?, bitmap = ?"
|
||||||
+ " WHERE contactId = ? AND transportId = ? AND period = ?";
|
+ " WHERE contactId = ? AND transportId = ? AND period = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setLong(1, centre);
|
ps.setLong(1, base);
|
||||||
ps.setBytes(2, bitmap);
|
ps.setBytes(2, bitmap);
|
||||||
ps.setInt(3, c.getInt());
|
ps.setInt(3, c.getInt());
|
||||||
ps.setString(4, t.getString());
|
ps.setString(4, t.getString());
|
||||||
ps.setLong(5, period);
|
ps.setLong(5, rotationPeriod);
|
||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -3139,7 +3119,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
public boolean setRemoteProperties(Connection txn, ContactId c,
|
public boolean setRemoteProperties(Connection txn, ContactId c,
|
||||||
TransportId t, TransportProperties p, long version)
|
TransportId t, TransportProperties p, long version)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
@@ -3354,4 +3334,46 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateTransportKeys(Connection txn,
|
||||||
|
Map<ContactId, TransportKeys> keys) throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
try {
|
||||||
|
// Delete any existing incoming keys
|
||||||
|
String sql = "DELETE FROM incomingKeys"
|
||||||
|
+ " WHERE contactId = ?"
|
||||||
|
+ " AND transportId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
||||||
|
ps.setInt(1, e.getKey().getInt());
|
||||||
|
ps.setString(2, e.getValue().getTransportId().getString());
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
int[] batchAffected = ps.executeBatch();
|
||||||
|
if (batchAffected.length != keys.size())
|
||||||
|
throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
// Delete any existing outgoing keys
|
||||||
|
sql = "DELETE FROM outgoingKeys"
|
||||||
|
+ " WHERE contactId = ?"
|
||||||
|
+ " AND transportId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
||||||
|
ps.setInt(1, e.getKey().getInt());
|
||||||
|
ps.setString(2, e.getValue().getTransportId().getString());
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
batchAffected = ps.executeBatch();
|
||||||
|
if (batchAffected.length != keys.size())
|
||||||
|
throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(ps);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
// Store the new keys
|
||||||
|
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
||||||
|
addTransportKeys(txn, e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,13 @@
|
|||||||
package org.briarproject.invitation;
|
package org.briarproject.invitation;
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import org.briarproject.api.Author;
|
import org.briarproject.api.Author;
|
||||||
import org.briarproject.api.AuthorFactory;
|
import org.briarproject.api.AuthorFactory;
|
||||||
import org.briarproject.api.LocalAuthor;
|
import org.briarproject.api.LocalAuthor;
|
||||||
import org.briarproject.api.TransportId;
|
import org.briarproject.api.TransportId;
|
||||||
import org.briarproject.api.TransportProperties;
|
import org.briarproject.api.TransportProperties;
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.api.crypto.KeyManager;
|
|
||||||
import org.briarproject.api.crypto.PseudoRandom;
|
import org.briarproject.api.crypto.PseudoRandom;
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
import org.briarproject.api.data.Reader;
|
import org.briarproject.api.data.Reader;
|
||||||
import org.briarproject.api.data.ReaderFactory;
|
import org.briarproject.api.data.ReaderFactory;
|
||||||
import org.briarproject.api.data.Writer;
|
import org.briarproject.api.data.Writer;
|
||||||
@@ -29,9 +19,20 @@ import org.briarproject.api.plugins.ConnectionManager;
|
|||||||
import org.briarproject.api.plugins.duplex.DuplexPlugin;
|
import org.briarproject.api.plugins.duplex.DuplexPlugin;
|
||||||
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.api.system.Clock;
|
import org.briarproject.api.system.Clock;
|
||||||
|
import org.briarproject.api.transport.KeyManager;
|
||||||
import org.briarproject.api.transport.StreamReaderFactory;
|
import org.briarproject.api.transport.StreamReaderFactory;
|
||||||
import org.briarproject.api.transport.StreamWriterFactory;
|
import org.briarproject.api.transport.StreamWriterFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
/** A connection thread for the peer being Alice in the invitation protocol. */
|
/** A connection thread for the peer being Alice in the invitation protocol. */
|
||||||
class AliceConnector extends Connector {
|
class AliceConnector extends Connector {
|
||||||
|
|
||||||
@@ -49,9 +50,9 @@ class AliceConnector extends Connector {
|
|||||||
Map<TransportId, TransportProperties> localProps,
|
Map<TransportId, TransportProperties> localProps,
|
||||||
PseudoRandom random) {
|
PseudoRandom random) {
|
||||||
super(crypto, db, readerFactory, writerFactory, streamReaderFactory,
|
super(crypto, db, readerFactory, writerFactory, streamReaderFactory,
|
||||||
streamWriterFactory, authorFactory, groupFactory, keyManager,
|
streamWriterFactory, authorFactory, groupFactory,
|
||||||
connectionManager, clock, reuseConnection, group, plugin,
|
keyManager, connectionManager, clock, reuseConnection, group,
|
||||||
localAuthor, localProps, random);
|
plugin, localAuthor, localProps, random);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -71,7 +72,7 @@ class AliceConnector extends Connector {
|
|||||||
OutputStream out;
|
OutputStream out;
|
||||||
Reader r;
|
Reader r;
|
||||||
Writer w;
|
Writer w;
|
||||||
byte[] secret;
|
SecretKey master;
|
||||||
try {
|
try {
|
||||||
in = conn.getReader().getInputStream();
|
in = conn.getReader().getInputStream();
|
||||||
out = conn.getWriter().getOutputStream();
|
out = conn.getWriter().getOutputStream();
|
||||||
@@ -82,7 +83,7 @@ class AliceConnector extends Connector {
|
|||||||
byte[] hash = receivePublicKeyHash(r);
|
byte[] hash = receivePublicKeyHash(r);
|
||||||
sendPublicKey(w);
|
sendPublicKey(w);
|
||||||
byte[] key = receivePublicKey(r);
|
byte[] key = receivePublicKey(r);
|
||||||
secret = deriveMasterSecret(hash, key, true);
|
master = deriveMasterSecret(hash, key, true);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
group.keyAgreementFailed();
|
group.keyAgreementFailed();
|
||||||
@@ -96,8 +97,8 @@ class AliceConnector extends Connector {
|
|||||||
}
|
}
|
||||||
// The key agreement succeeded - derive the confirmation codes
|
// The key agreement succeeded - derive the confirmation codes
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
|
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
|
||||||
int[] codes = crypto.deriveConfirmationCodes(secret);
|
int aliceCode = crypto.deriveConfirmationCode(master, true);
|
||||||
int aliceCode = codes[0], bobCode = codes[1];
|
int bobCode = crypto.deriveConfirmationCode(master, false);
|
||||||
group.keyAgreementSucceeded(aliceCode, bobCode);
|
group.keyAgreementSucceeded(aliceCode, bobCode);
|
||||||
// Exchange confirmation results
|
// Exchange confirmation results
|
||||||
boolean localMatched, remoteMatched;
|
boolean localMatched, remoteMatched;
|
||||||
@@ -130,19 +131,22 @@ class AliceConnector extends Connector {
|
|||||||
// Confirmation succeeded - upgrade to a secure connection
|
// Confirmation succeeded - upgrade to a secure connection
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info(pluginName + " confirmation succeeded");
|
LOG.info(pluginName + " confirmation succeeded");
|
||||||
|
// Derive the header keys
|
||||||
|
SecretKey aliceHeaderKey = crypto.deriveInvitationKey(master, true);
|
||||||
|
SecretKey bobHeaderKey = crypto.deriveInvitationKey(master, false);
|
||||||
// Create the readers
|
// Create the readers
|
||||||
InputStream streamReader =
|
InputStream streamReader =
|
||||||
streamReaderFactory.createInvitationStreamReader(in,
|
streamReaderFactory.createInvitationStreamReader(in,
|
||||||
secret, false); // Bob's stream
|
bobHeaderKey);
|
||||||
r = readerFactory.createReader(streamReader);
|
r = readerFactory.createReader(streamReader);
|
||||||
// Create the writers
|
// Create the writers
|
||||||
OutputStream streamWriter =
|
OutputStream streamWriter =
|
||||||
streamWriterFactory.createInvitationStreamWriter(out,
|
streamWriterFactory.createInvitationStreamWriter(out,
|
||||||
secret, true); // Alice's stream
|
aliceHeaderKey);
|
||||||
w = writerFactory.createWriter(streamWriter);
|
w = writerFactory.createWriter(streamWriter);
|
||||||
// Derive the invitation nonces
|
// Derive the invitation nonces
|
||||||
byte[][] nonces = crypto.deriveInvitationNonces(secret);
|
byte[] aliceNonce = crypto.deriveSignatureNonce(master, true);
|
||||||
byte[] aliceNonce = nonces[0], bobNonce = nonces[1];
|
byte[] bobNonce = crypto.deriveSignatureNonce(master, false);
|
||||||
// Exchange pseudonyms, signed nonces, timestamps and transports
|
// Exchange pseudonyms, signed nonces, timestamps and transports
|
||||||
Author remoteAuthor;
|
Author remoteAuthor;
|
||||||
long remoteTimestamp;
|
long remoteTimestamp;
|
||||||
@@ -171,11 +175,11 @@ class AliceConnector extends Connector {
|
|||||||
tryToClose(conn, true);
|
tryToClose(conn, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// The epoch is the minimum of the peers' timestamps
|
// The agreed timestamp is the minimum of the peers' timestamps
|
||||||
long epoch = Math.min(localTimestamp, remoteTimestamp);
|
long timestamp = Math.min(localTimestamp, remoteTimestamp);
|
||||||
// Add the contact and store the transports
|
// Add the contact and store the transports
|
||||||
try {
|
try {
|
||||||
addContact(remoteAuthor, remoteProps, secret, epoch, true);
|
addContact(remoteAuthor, remoteProps, master, timestamp, true);
|
||||||
} 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);
|
||||||
tryToClose(conn, true);
|
tryToClose(conn, true);
|
||||||
|
|||||||
@@ -1,23 +1,13 @@
|
|||||||
package org.briarproject.invitation;
|
package org.briarproject.invitation;
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import org.briarproject.api.Author;
|
import org.briarproject.api.Author;
|
||||||
import org.briarproject.api.AuthorFactory;
|
import org.briarproject.api.AuthorFactory;
|
||||||
import org.briarproject.api.LocalAuthor;
|
import org.briarproject.api.LocalAuthor;
|
||||||
import org.briarproject.api.TransportId;
|
import org.briarproject.api.TransportId;
|
||||||
import org.briarproject.api.TransportProperties;
|
import org.briarproject.api.TransportProperties;
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.api.crypto.KeyManager;
|
|
||||||
import org.briarproject.api.crypto.PseudoRandom;
|
import org.briarproject.api.crypto.PseudoRandom;
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
import org.briarproject.api.data.Reader;
|
import org.briarproject.api.data.Reader;
|
||||||
import org.briarproject.api.data.ReaderFactory;
|
import org.briarproject.api.data.ReaderFactory;
|
||||||
import org.briarproject.api.data.Writer;
|
import org.briarproject.api.data.Writer;
|
||||||
@@ -29,9 +19,20 @@ import org.briarproject.api.plugins.ConnectionManager;
|
|||||||
import org.briarproject.api.plugins.duplex.DuplexPlugin;
|
import org.briarproject.api.plugins.duplex.DuplexPlugin;
|
||||||
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.api.system.Clock;
|
import org.briarproject.api.system.Clock;
|
||||||
|
import org.briarproject.api.transport.KeyManager;
|
||||||
import org.briarproject.api.transport.StreamReaderFactory;
|
import org.briarproject.api.transport.StreamReaderFactory;
|
||||||
import org.briarproject.api.transport.StreamWriterFactory;
|
import org.briarproject.api.transport.StreamWriterFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
/** A connection thread for the peer being Bob in the invitation protocol. */
|
/** A connection thread for the peer being Bob in the invitation protocol. */
|
||||||
class BobConnector extends Connector {
|
class BobConnector extends Connector {
|
||||||
|
|
||||||
@@ -49,9 +50,9 @@ class BobConnector extends Connector {
|
|||||||
Map<TransportId, TransportProperties> localProps,
|
Map<TransportId, TransportProperties> localProps,
|
||||||
PseudoRandom random) {
|
PseudoRandom random) {
|
||||||
super(crypto, db, readerFactory, writerFactory, streamReaderFactory,
|
super(crypto, db, readerFactory, writerFactory, streamReaderFactory,
|
||||||
streamWriterFactory, authorFactory, groupFactory, keyManager,
|
streamWriterFactory, authorFactory, groupFactory,
|
||||||
connectionManager, clock, reuseConnection, group, plugin,
|
keyManager, connectionManager, clock, reuseConnection, group,
|
||||||
localAuthor, localProps, random);
|
plugin, localAuthor, localProps, random);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -65,7 +66,7 @@ class BobConnector extends Connector {
|
|||||||
OutputStream out;
|
OutputStream out;
|
||||||
Reader r;
|
Reader r;
|
||||||
Writer w;
|
Writer w;
|
||||||
byte[] secret;
|
SecretKey master;
|
||||||
try {
|
try {
|
||||||
in = conn.getReader().getInputStream();
|
in = conn.getReader().getInputStream();
|
||||||
out = conn.getWriter().getOutputStream();
|
out = conn.getWriter().getOutputStream();
|
||||||
@@ -82,7 +83,7 @@ class BobConnector extends Connector {
|
|||||||
sendPublicKeyHash(w);
|
sendPublicKeyHash(w);
|
||||||
byte[] key = receivePublicKey(r);
|
byte[] key = receivePublicKey(r);
|
||||||
sendPublicKey(w);
|
sendPublicKey(w);
|
||||||
secret = deriveMasterSecret(hash, key, false);
|
master = deriveMasterSecret(hash, key, false);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
group.keyAgreementFailed();
|
group.keyAgreementFailed();
|
||||||
@@ -96,8 +97,8 @@ class BobConnector extends Connector {
|
|||||||
}
|
}
|
||||||
// The key agreement succeeded - derive the confirmation codes
|
// The key agreement succeeded - derive the confirmation codes
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
|
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
|
||||||
int[] codes = crypto.deriveConfirmationCodes(secret);
|
int aliceCode = crypto.deriveConfirmationCode(master, true);
|
||||||
int aliceCode = codes[0], bobCode = codes[1];
|
int bobCode = crypto.deriveConfirmationCode(master, false);
|
||||||
group.keyAgreementSucceeded(bobCode, aliceCode);
|
group.keyAgreementSucceeded(bobCode, aliceCode);
|
||||||
// Exchange confirmation results
|
// Exchange confirmation results
|
||||||
boolean localMatched, remoteMatched;
|
boolean localMatched, remoteMatched;
|
||||||
@@ -130,19 +131,22 @@ class BobConnector extends Connector {
|
|||||||
// Confirmation succeeded - upgrade to a secure connection
|
// Confirmation succeeded - upgrade to a secure connection
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info(pluginName + " confirmation succeeded");
|
LOG.info(pluginName + " confirmation succeeded");
|
||||||
|
// Derive the header keys
|
||||||
|
SecretKey aliceHeaderKey = crypto.deriveInvitationKey(master, true);
|
||||||
|
SecretKey bobHeaderKey = crypto.deriveInvitationKey(master, false);
|
||||||
// Create the readers
|
// Create the readers
|
||||||
InputStream streamReader =
|
InputStream streamReader =
|
||||||
streamReaderFactory.createInvitationStreamReader(in,
|
streamReaderFactory.createInvitationStreamReader(in,
|
||||||
secret, true); // Alice's stream
|
aliceHeaderKey);
|
||||||
r = readerFactory.createReader(streamReader);
|
r = readerFactory.createReader(streamReader);
|
||||||
// Create the writers
|
// Create the writers
|
||||||
OutputStream streamWriter =
|
OutputStream streamWriter =
|
||||||
streamWriterFactory.createInvitationStreamWriter(out,
|
streamWriterFactory.createInvitationStreamWriter(out,
|
||||||
secret, false); // Bob's stream
|
bobHeaderKey);
|
||||||
w = writerFactory.createWriter(streamWriter);
|
w = writerFactory.createWriter(streamWriter);
|
||||||
// Derive the nonces
|
// Derive the nonces
|
||||||
byte[][] nonces = crypto.deriveInvitationNonces(secret);
|
byte[] aliceNonce = crypto.deriveSignatureNonce(master, true);
|
||||||
byte[] aliceNonce = nonces[0], bobNonce = nonces[1];
|
byte[] bobNonce = crypto.deriveSignatureNonce(master, false);
|
||||||
// Exchange pseudonyms, signed nonces, timestamps and transports
|
// Exchange pseudonyms, signed nonces, timestamps and transports
|
||||||
Author remoteAuthor;
|
Author remoteAuthor;
|
||||||
long remoteTimestamp;
|
long remoteTimestamp;
|
||||||
@@ -171,11 +175,11 @@ class BobConnector extends Connector {
|
|||||||
tryToClose(conn, true);
|
tryToClose(conn, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// The epoch is the minimum of the peers' timestamps
|
// The agreed timestamp is the minimum of the peers' timestamps
|
||||||
long epoch = Math.min(localTimestamp, remoteTimestamp);
|
long timestamp = Math.min(localTimestamp, remoteTimestamp);
|
||||||
// Add the contact and store the transports
|
// Add the contact and store the transports
|
||||||
try {
|
try {
|
||||||
addContact(remoteAuthor, remoteProps, secret, epoch, false);
|
addContact(remoteAuthor, remoteProps, master, timestamp, false);
|
||||||
} 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);
|
||||||
tryToClose(conn, true);
|
tryToClose(conn, true);
|
||||||
|
|||||||
@@ -1,5 +1,46 @@
|
|||||||
package org.briarproject.invitation;
|
package org.briarproject.invitation;
|
||||||
|
|
||||||
|
import org.briarproject.api.Author;
|
||||||
|
import org.briarproject.api.AuthorFactory;
|
||||||
|
import org.briarproject.api.ContactId;
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.api.LocalAuthor;
|
||||||
|
import org.briarproject.api.TransportId;
|
||||||
|
import org.briarproject.api.TransportProperties;
|
||||||
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.api.crypto.KeyPair;
|
||||||
|
import org.briarproject.api.crypto.KeyParser;
|
||||||
|
import org.briarproject.api.crypto.MessageDigest;
|
||||||
|
import org.briarproject.api.crypto.PseudoRandom;
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.api.crypto.Signature;
|
||||||
|
import org.briarproject.api.data.Reader;
|
||||||
|
import org.briarproject.api.data.ReaderFactory;
|
||||||
|
import org.briarproject.api.data.Writer;
|
||||||
|
import org.briarproject.api.data.WriterFactory;
|
||||||
|
import org.briarproject.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.api.db.DbException;
|
||||||
|
import org.briarproject.api.messaging.Group;
|
||||||
|
import org.briarproject.api.messaging.GroupFactory;
|
||||||
|
import org.briarproject.api.plugins.ConnectionManager;
|
||||||
|
import org.briarproject.api.plugins.duplex.DuplexPlugin;
|
||||||
|
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
||||||
|
import org.briarproject.api.system.Clock;
|
||||||
|
import org.briarproject.api.transport.KeyManager;
|
||||||
|
import org.briarproject.api.transport.StreamReaderFactory;
|
||||||
|
import org.briarproject.api.transport.StreamWriterFactory;
|
||||||
|
import org.briarproject.api.transport.TransportKeys;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.api.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.api.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
@@ -9,50 +50,9 @@ import static org.briarproject.api.TransportPropertyConstants.MAX_PROPERTIES_PER
|
|||||||
import static org.briarproject.api.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
import static org.briarproject.api.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||||
import static org.briarproject.api.TransportPropertyConstants.MAX_TRANSPORT_ID_LENGTH;
|
import static org.briarproject.api.TransportPropertyConstants.MAX_TRANSPORT_ID_LENGTH;
|
||||||
import static org.briarproject.api.invitation.InvitationConstants.CONNECTION_TIMEOUT;
|
import static org.briarproject.api.invitation.InvitationConstants.CONNECTION_TIMEOUT;
|
||||||
|
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||||
|
|
||||||
import java.io.IOException;
|
// FIXME: This class has way too many dependencies
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import org.briarproject.api.Author;
|
|
||||||
import org.briarproject.api.AuthorFactory;
|
|
||||||
import org.briarproject.api.ContactId;
|
|
||||||
import org.briarproject.api.FormatException;
|
|
||||||
import org.briarproject.api.LocalAuthor;
|
|
||||||
import org.briarproject.api.TransportId;
|
|
||||||
import org.briarproject.api.TransportProperties;
|
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.api.crypto.KeyManager;
|
|
||||||
import org.briarproject.api.crypto.KeyPair;
|
|
||||||
import org.briarproject.api.crypto.KeyParser;
|
|
||||||
import org.briarproject.api.crypto.MessageDigest;
|
|
||||||
import org.briarproject.api.crypto.PseudoRandom;
|
|
||||||
import org.briarproject.api.crypto.Signature;
|
|
||||||
import org.briarproject.api.data.Reader;
|
|
||||||
import org.briarproject.api.data.ReaderFactory;
|
|
||||||
import org.briarproject.api.data.Writer;
|
|
||||||
import org.briarproject.api.data.WriterFactory;
|
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
|
||||||
import org.briarproject.api.db.DbException;
|
|
||||||
import org.briarproject.api.db.NoSuchTransportException;
|
|
||||||
import org.briarproject.api.messaging.Group;
|
|
||||||
import org.briarproject.api.messaging.GroupFactory;
|
|
||||||
import org.briarproject.api.plugins.ConnectionManager;
|
|
||||||
import org.briarproject.api.plugins.duplex.DuplexPlugin;
|
|
||||||
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
|
||||||
import org.briarproject.api.system.Clock;
|
|
||||||
import org.briarproject.api.transport.Endpoint;
|
|
||||||
import org.briarproject.api.transport.StreamReaderFactory;
|
|
||||||
import org.briarproject.api.transport.StreamWriterFactory;
|
|
||||||
|
|
||||||
abstract class Connector extends Thread {
|
abstract class Connector extends Thread {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
@@ -152,8 +152,8 @@ abstract class Connector extends Thread {
|
|||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected byte[] deriveMasterSecret(byte[] hash, byte[] key, boolean alice)
|
protected SecretKey deriveMasterSecret(byte[] hash, byte[] key,
|
||||||
throws GeneralSecurityException {
|
boolean alice) throws GeneralSecurityException {
|
||||||
// Check that the hash matches the key
|
// Check that the hash matches the key
|
||||||
if (!Arrays.equals(hash, messageDigest.digest(key))) {
|
if (!Arrays.equals(hash, messageDigest.digest(key))) {
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
@@ -271,39 +271,34 @@ abstract class Connector extends Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void addContact(Author remoteAuthor,
|
protected void addContact(Author remoteAuthor,
|
||||||
Map<TransportId, TransportProperties> remoteProps, byte[] secret,
|
Map<TransportId, TransportProperties> remoteProps, SecretKey master,
|
||||||
long epoch, boolean alice) throws DbException {
|
long timestamp, boolean alice) throws DbException {
|
||||||
// Add the contact to the database
|
// Add the contact to the database
|
||||||
contactId = db.addContact(remoteAuthor, localAuthor.getId());
|
contactId = db.addContact(remoteAuthor, localAuthor.getId());
|
||||||
// Create and store the inbox group
|
// Create and store the inbox group
|
||||||
byte[] salt = crypto.deriveGroupSalt(secret);
|
byte[] salt = crypto.deriveGroupSalt(master);
|
||||||
Group inbox = groupFactory.createGroup("Inbox", salt);
|
Group inbox = groupFactory.createGroup("Inbox", salt);
|
||||||
db.addGroup(inbox);
|
db.addGroup(inbox);
|
||||||
db.setInboxGroup(contactId, inbox);
|
db.setInboxGroup(contactId, inbox);
|
||||||
// Store the remote transport properties
|
// Store the remote transport properties
|
||||||
db.setRemoteProperties(contactId, remoteProps);
|
db.setRemoteProperties(contactId, remoteProps);
|
||||||
// Create an endpoint for each transport shared with the contact
|
// Derive transport keys for each transport shared with the contact
|
||||||
List<TransportId> ids = new ArrayList<TransportId>();
|
|
||||||
Map<TransportId, Integer> latencies = db.getTransportLatencies();
|
Map<TransportId, Integer> latencies = db.getTransportLatencies();
|
||||||
for (TransportId id : localProps.keySet()) {
|
List<TransportKeys> keys = new ArrayList<TransportKeys>();
|
||||||
if (latencies.containsKey(id) && remoteProps.containsKey(id))
|
for (TransportId t : localProps.keySet()) {
|
||||||
ids.add(id);
|
if (remoteProps.containsKey(t) && latencies.containsKey(t)) {
|
||||||
}
|
// Work out what rotation period the timestamp belongs to
|
||||||
// Assign indices to the transports deterministically and derive keys
|
long latency = latencies.get(t);
|
||||||
Collections.sort(ids, TransportIdComparator.INSTANCE);
|
long rotationPeriodLength = latency + MAX_CLOCK_DIFFERENCE;
|
||||||
int size = ids.size();
|
long rotationPeriod = timestamp / rotationPeriodLength;
|
||||||
for (int i = 0; i < size; i++) {
|
// Derive the transport keys
|
||||||
TransportId id = ids.get(i);
|
TransportKeys k = crypto.deriveTransportKeys(t, master,
|
||||||
Endpoint ep = new Endpoint(contactId, id, epoch, alice);
|
rotationPeriod, alice);
|
||||||
int maxLatency = latencies.get(id);
|
db.addTransportKeys(contactId, k);
|
||||||
try {
|
keys.add(k);
|
||||||
db.addEndpoint(ep);
|
|
||||||
} catch (NoSuchTransportException e) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
byte[] initialSecret = crypto.deriveInitialSecret(secret, i);
|
|
||||||
keyManager.endpointAdded(ep, maxLatency, initialSecret);
|
|
||||||
}
|
}
|
||||||
|
keyManager.contactAdded(contactId, keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void tryToClose(DuplexTransportConnection conn,
|
protected void tryToClose(DuplexTransportConnection conn,
|
||||||
@@ -322,16 +317,4 @@ abstract class Connector extends Thread {
|
|||||||
TransportId t = plugin.getId();
|
TransportId t = plugin.getId();
|
||||||
connectionManager.manageOutgoingConnection(contactId, t, conn);
|
connectionManager.manageOutgoingConnection(contactId, t, conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class TransportIdComparator
|
|
||||||
implements Comparator<TransportId> {
|
|
||||||
|
|
||||||
private static final TransportIdComparator INSTANCE =
|
|
||||||
new TransportIdComparator();
|
|
||||||
|
|
||||||
public int compare(TransportId t1, TransportId t2) {
|
|
||||||
String s1 = t1.getString(), s2 = t2.getString();
|
|
||||||
return String.CASE_INSENSITIVE_ORDER.compare(s1, s2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,5 @@
|
|||||||
package org.briarproject.invitation;
|
package org.briarproject.invitation;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static org.briarproject.api.invitation.InvitationConstants.CONFIRMATION_TIMEOUT;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import org.briarproject.api.Author;
|
import org.briarproject.api.Author;
|
||||||
import org.briarproject.api.AuthorFactory;
|
import org.briarproject.api.AuthorFactory;
|
||||||
import org.briarproject.api.AuthorId;
|
import org.briarproject.api.AuthorId;
|
||||||
@@ -21,7 +7,6 @@ import org.briarproject.api.LocalAuthor;
|
|||||||
import org.briarproject.api.TransportId;
|
import org.briarproject.api.TransportId;
|
||||||
import org.briarproject.api.TransportProperties;
|
import org.briarproject.api.TransportProperties;
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.api.crypto.KeyManager;
|
|
||||||
import org.briarproject.api.crypto.PseudoRandom;
|
import org.briarproject.api.crypto.PseudoRandom;
|
||||||
import org.briarproject.api.data.ReaderFactory;
|
import org.briarproject.api.data.ReaderFactory;
|
||||||
import org.briarproject.api.data.WriterFactory;
|
import org.briarproject.api.data.WriterFactory;
|
||||||
@@ -35,9 +20,24 @@ import org.briarproject.api.plugins.ConnectionManager;
|
|||||||
import org.briarproject.api.plugins.PluginManager;
|
import org.briarproject.api.plugins.PluginManager;
|
||||||
import org.briarproject.api.plugins.duplex.DuplexPlugin;
|
import org.briarproject.api.plugins.duplex.DuplexPlugin;
|
||||||
import org.briarproject.api.system.Clock;
|
import org.briarproject.api.system.Clock;
|
||||||
|
import org.briarproject.api.transport.KeyManager;
|
||||||
import org.briarproject.api.transport.StreamReaderFactory;
|
import org.briarproject.api.transport.StreamReaderFactory;
|
||||||
import org.briarproject.api.transport.StreamWriterFactory;
|
import org.briarproject.api.transport.StreamWriterFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.api.invitation.InvitationConstants.CONFIRMATION_TIMEOUT;
|
||||||
|
|
||||||
/** A task consisting of one or more parallel connection attempts. */
|
/** A task consisting of one or more parallel connection attempts. */
|
||||||
class ConnectorGroup extends Thread implements InvitationTask {
|
class ConnectorGroup extends Thread implements InvitationTask {
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import javax.inject.Inject;
|
|||||||
import org.briarproject.api.AuthorFactory;
|
import org.briarproject.api.AuthorFactory;
|
||||||
import org.briarproject.api.AuthorId;
|
import org.briarproject.api.AuthorId;
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.api.crypto.KeyManager;
|
|
||||||
import org.briarproject.api.data.ReaderFactory;
|
import org.briarproject.api.data.ReaderFactory;
|
||||||
import org.briarproject.api.data.WriterFactory;
|
import org.briarproject.api.data.WriterFactory;
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
import org.briarproject.api.db.DatabaseComponent;
|
||||||
@@ -15,6 +14,7 @@ import org.briarproject.api.messaging.GroupFactory;
|
|||||||
import org.briarproject.api.plugins.ConnectionManager;
|
import org.briarproject.api.plugins.ConnectionManager;
|
||||||
import org.briarproject.api.plugins.PluginManager;
|
import org.briarproject.api.plugins.PluginManager;
|
||||||
import org.briarproject.api.system.Clock;
|
import org.briarproject.api.system.Clock;
|
||||||
|
import org.briarproject.api.transport.KeyManager;
|
||||||
import org.briarproject.api.transport.StreamReaderFactory;
|
import org.briarproject.api.transport.StreamReaderFactory;
|
||||||
import org.briarproject.api.transport.StreamWriterFactory;
|
import org.briarproject.api.transport.StreamWriterFactory;
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import org.briarproject.api.ContactId;
|
import org.briarproject.api.ContactId;
|
||||||
import org.briarproject.api.TransportId;
|
import org.briarproject.api.TransportId;
|
||||||
import org.briarproject.api.crypto.KeyManager;
|
|
||||||
import org.briarproject.api.db.DbException;
|
import org.briarproject.api.db.DbException;
|
||||||
import org.briarproject.api.lifecycle.IoExecutor;
|
import org.briarproject.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.api.messaging.MessagingSession;
|
import org.briarproject.api.messaging.MessagingSession;
|
||||||
@@ -24,10 +23,10 @@ import org.briarproject.api.plugins.ConnectionRegistry;
|
|||||||
import org.briarproject.api.plugins.TransportConnectionReader;
|
import org.briarproject.api.plugins.TransportConnectionReader;
|
||||||
import org.briarproject.api.plugins.TransportConnectionWriter;
|
import org.briarproject.api.plugins.TransportConnectionWriter;
|
||||||
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
||||||
|
import org.briarproject.api.transport.KeyManager;
|
||||||
import org.briarproject.api.transport.StreamContext;
|
import org.briarproject.api.transport.StreamContext;
|
||||||
import org.briarproject.api.transport.StreamReaderFactory;
|
import org.briarproject.api.transport.StreamReaderFactory;
|
||||||
import org.briarproject.api.transport.StreamWriterFactory;
|
import org.briarproject.api.transport.StreamWriterFactory;
|
||||||
import org.briarproject.api.transport.TagRecogniser;
|
|
||||||
|
|
||||||
class ConnectionManagerImpl implements ConnectionManager {
|
class ConnectionManagerImpl implements ConnectionManager {
|
||||||
|
|
||||||
@@ -36,7 +35,6 @@ class ConnectionManagerImpl implements ConnectionManager {
|
|||||||
|
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
private final KeyManager keyManager;
|
private final KeyManager keyManager;
|
||||||
private final TagRecogniser tagRecogniser;
|
|
||||||
private final StreamReaderFactory streamReaderFactory;
|
private final StreamReaderFactory streamReaderFactory;
|
||||||
private final StreamWriterFactory streamWriterFactory;
|
private final StreamWriterFactory streamWriterFactory;
|
||||||
private final MessagingSessionFactory messagingSessionFactory;
|
private final MessagingSessionFactory messagingSessionFactory;
|
||||||
@@ -44,14 +42,12 @@ class ConnectionManagerImpl implements ConnectionManager {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ConnectionManagerImpl(@IoExecutor Executor ioExecutor,
|
ConnectionManagerImpl(@IoExecutor Executor ioExecutor,
|
||||||
KeyManager keyManager, TagRecogniser tagRecogniser,
|
KeyManager keyManager, StreamReaderFactory streamReaderFactory,
|
||||||
StreamReaderFactory streamReaderFactory,
|
|
||||||
StreamWriterFactory streamWriterFactory,
|
StreamWriterFactory streamWriterFactory,
|
||||||
MessagingSessionFactory messagingSessionFactory,
|
MessagingSessionFactory messagingSessionFactory,
|
||||||
ConnectionRegistry connectionRegistry) {
|
ConnectionRegistry connectionRegistry) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
this.keyManager = keyManager;
|
this.keyManager = keyManager;
|
||||||
this.tagRecogniser = tagRecogniser;
|
|
||||||
this.streamReaderFactory = streamReaderFactory;
|
this.streamReaderFactory = streamReaderFactory;
|
||||||
this.streamWriterFactory = streamWriterFactory;
|
this.streamWriterFactory = streamWriterFactory;
|
||||||
this.messagingSessionFactory = messagingSessionFactory;
|
this.messagingSessionFactory = messagingSessionFactory;
|
||||||
@@ -134,7 +130,7 @@ class ConnectionManagerImpl implements ConnectionManager {
|
|||||||
StreamContext ctx;
|
StreamContext ctx;
|
||||||
try {
|
try {
|
||||||
byte[] tag = readTag(transportId, reader);
|
byte[] tag = readTag(transportId, reader);
|
||||||
ctx = tagRecogniser.recogniseTag(transportId, tag);
|
ctx = keyManager.recogniseTag(transportId, tag);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
disposeReader(true, false);
|
disposeReader(true, false);
|
||||||
@@ -238,7 +234,7 @@ class ConnectionManagerImpl implements ConnectionManager {
|
|||||||
StreamContext ctx;
|
StreamContext ctx;
|
||||||
try {
|
try {
|
||||||
byte[] tag = readTag(transportId, reader);
|
byte[] tag = readTag(transportId, reader);
|
||||||
ctx = tagRecogniser.recogniseTag(transportId, tag);
|
ctx = keyManager.recogniseTag(transportId, tag);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
disposeReader(true, false);
|
disposeReader(true, false);
|
||||||
@@ -367,7 +363,7 @@ class ConnectionManagerImpl implements ConnectionManager {
|
|||||||
StreamContext ctx;
|
StreamContext ctx;
|
||||||
try {
|
try {
|
||||||
byte[] tag = readTag(transportId, reader);
|
byte[] tag = readTag(transportId, reader);
|
||||||
ctx = tagRecogniser.recogniseTag(transportId, tag);
|
ctx = keyManager.recogniseTag(transportId, tag);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
disposeReader(true, true);
|
disposeReader(true, true);
|
||||||
|
|||||||
@@ -1,22 +1,5 @@
|
|||||||
package org.briarproject.plugins;
|
package org.briarproject.plugins;
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import org.briarproject.api.ContactId;
|
import org.briarproject.api.ContactId;
|
||||||
import org.briarproject.api.TransportConfig;
|
import org.briarproject.api.TransportConfig;
|
||||||
import org.briarproject.api.TransportId;
|
import org.briarproject.api.TransportId;
|
||||||
@@ -42,6 +25,23 @@ import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
|
|||||||
import org.briarproject.api.system.Clock;
|
import org.briarproject.api.system.Clock;
|
||||||
import org.briarproject.api.ui.UiCallback;
|
import org.briarproject.api.ui.UiCallback;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
class PluginManagerImpl implements PluginManager {
|
class PluginManagerImpl implements PluginManager {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
@@ -367,7 +367,7 @@ class PluginManagerImpl implements PluginManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class SimplexCallback extends PluginCallbackImpl
|
private class SimplexCallback extends PluginCallbackImpl
|
||||||
implements SimplexPluginCallback {
|
implements SimplexPluginCallback {
|
||||||
|
|
||||||
private SimplexCallback(TransportId id) {
|
private SimplexCallback(TransportId id) {
|
||||||
super(id);
|
super(id);
|
||||||
@@ -383,7 +383,7 @@ class PluginManagerImpl implements PluginManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class DuplexCallback extends PluginCallbackImpl
|
private class DuplexCallback extends PluginCallbackImpl
|
||||||
implements DuplexPluginCallback {
|
implements DuplexPluginCallback {
|
||||||
|
|
||||||
private DuplexCallback(TransportId id) {
|
private DuplexCallback(TransportId id) {
|
||||||
super(id);
|
super(id);
|
||||||
|
|||||||
@@ -1,27 +1,10 @@
|
|||||||
package org.briarproject.transport;
|
package org.briarproject.transport;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.TimerTask;
|
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import org.briarproject.api.ContactId;
|
import org.briarproject.api.ContactId;
|
||||||
import org.briarproject.api.TransportId;
|
import org.briarproject.api.TransportId;
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.api.crypto.KeyManager;
|
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
import org.briarproject.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.api.db.DbException;
|
import org.briarproject.api.db.DbException;
|
||||||
import org.briarproject.api.event.ContactRemovedEvent;
|
import org.briarproject.api.event.ContactRemovedEvent;
|
||||||
import org.briarproject.api.event.Event;
|
import org.briarproject.api.event.Event;
|
||||||
@@ -31,429 +14,115 @@ import org.briarproject.api.event.TransportAddedEvent;
|
|||||||
import org.briarproject.api.event.TransportRemovedEvent;
|
import org.briarproject.api.event.TransportRemovedEvent;
|
||||||
import org.briarproject.api.system.Clock;
|
import org.briarproject.api.system.Clock;
|
||||||
import org.briarproject.api.system.Timer;
|
import org.briarproject.api.system.Timer;
|
||||||
import org.briarproject.api.transport.Endpoint;
|
import org.briarproject.api.transport.KeyManager;
|
||||||
import org.briarproject.api.transport.StreamContext;
|
import org.briarproject.api.transport.StreamContext;
|
||||||
import org.briarproject.api.transport.TagRecogniser;
|
import org.briarproject.api.transport.TransportKeys;
|
||||||
import org.briarproject.api.transport.TemporarySecret;
|
|
||||||
|
|
||||||
// FIXME: Don't make alien calls with a lock held
|
import java.util.Collection;
|
||||||
class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
private static final int MS_BETWEEN_CHECKS = 60 * 1000;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
|
class KeyManagerImpl implements KeyManager, EventListener {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(KeyManagerImpl.class.getName());
|
Logger.getLogger(KeyManagerImpl.class.getName());
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
private final Executor dbExecutor;
|
||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
private final TagRecogniser tagRecogniser;
|
|
||||||
private final Clock clock;
|
|
||||||
private final Timer timer;
|
private final Timer timer;
|
||||||
private final Lock lock = new ReentrantLock();
|
private final Clock clock;
|
||||||
|
private final ConcurrentHashMap<TransportId, TransportKeyManager> managers;
|
||||||
// The following are locking: lock
|
|
||||||
private final Map<TransportId, Integer> maxLatencies;
|
|
||||||
private final Map<EndpointKey, TemporarySecret> oldSecrets;
|
|
||||||
private final Map<EndpointKey, TemporarySecret> currentSecrets;
|
|
||||||
private final Map<EndpointKey, TemporarySecret> newSecrets;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
KeyManagerImpl(CryptoComponent crypto, DatabaseComponent db,
|
KeyManagerImpl(DatabaseComponent db, CryptoComponent crypto,
|
||||||
EventBus eventBus, TagRecogniser tagRecogniser, Clock clock,
|
@DatabaseExecutor Executor dbExecutor, EventBus eventBus,
|
||||||
Timer timer) {
|
Timer timer, Clock clock) {
|
||||||
this.crypto = crypto;
|
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
this.crypto = crypto;
|
||||||
|
this.dbExecutor = dbExecutor;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.tagRecogniser = tagRecogniser;
|
|
||||||
this.clock = clock;
|
|
||||||
this.timer = timer;
|
this.timer = timer;
|
||||||
maxLatencies = new HashMap<TransportId, Integer>();
|
this.clock = clock;
|
||||||
oldSecrets = new HashMap<EndpointKey, TemporarySecret>();
|
managers = new ConcurrentHashMap<TransportId, TransportKeyManager>();
|
||||||
currentSecrets = new HashMap<EndpointKey, TemporarySecret>();
|
|
||||||
newSecrets = new HashMap<EndpointKey, TemporarySecret>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean start() {
|
public boolean start() {
|
||||||
lock.lock();
|
eventBus.addListener(this);
|
||||||
try {
|
try {
|
||||||
eventBus.addListener(this);
|
Map<TransportId, Integer> latencies = db.getTransportLatencies();
|
||||||
// Load the temporary secrets and transport latencies from the DB
|
for (Entry<TransportId, Integer> e : latencies.entrySet())
|
||||||
Collection<TemporarySecret> secrets;
|
addTransport(e.getKey(), e.getValue());
|
||||||
try {
|
} catch (DbException e) {
|
||||||
secrets = db.getSecrets();
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
maxLatencies.putAll(db.getTransportLatencies());
|
return false;
|
||||||
} catch (DbException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Work out what phase of its lifecycle each secret is in
|
|
||||||
long now = clock.currentTimeMillis();
|
|
||||||
Collection<TemporarySecret> dead =
|
|
||||||
assignSecretsToMaps(now, secrets);
|
|
||||||
// Replace any dead secrets
|
|
||||||
Collection<TemporarySecret> created = replaceDeadSecrets(now, dead);
|
|
||||||
if (!created.isEmpty()) {
|
|
||||||
// Store any secrets that have been created,
|
|
||||||
// removing any dead ones
|
|
||||||
try {
|
|
||||||
db.addSecrets(created);
|
|
||||||
} catch (DbException e) {
|
|
||||||
if (LOG.isLoggable(WARNING))
|
|
||||||
LOG.log(WARNING, e.toString(), e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Pass the old, current and new secrets to the recogniser
|
|
||||||
for (TemporarySecret s : oldSecrets.values())
|
|
||||||
tagRecogniser.addSecret(s);
|
|
||||||
for (TemporarySecret s : currentSecrets.values())
|
|
||||||
tagRecogniser.addSecret(s);
|
|
||||||
for (TemporarySecret s : newSecrets.values())
|
|
||||||
tagRecogniser.addSecret(s);
|
|
||||||
// Schedule periodic key rotation
|
|
||||||
timer.scheduleAtFixedRate(this, MS_BETWEEN_CHECKS,
|
|
||||||
MS_BETWEEN_CHECKS);
|
|
||||||
return true;
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
}
|
||||||
}
|
return true;
|
||||||
|
|
||||||
// Assigns secrets to the appropriate maps and returns any dead secrets
|
|
||||||
// Locking: lock
|
|
||||||
private Collection<TemporarySecret> assignSecretsToMaps(long now,
|
|
||||||
Collection<TemporarySecret> secrets) {
|
|
||||||
Collection<TemporarySecret> dead = new ArrayList<TemporarySecret>();
|
|
||||||
for (TemporarySecret s : secrets) {
|
|
||||||
// Discard the secret if the transport has been removed
|
|
||||||
Integer maxLatency = maxLatencies.get(s.getTransportId());
|
|
||||||
if (maxLatency == null) {
|
|
||||||
LOG.info("Discarding obsolete secret");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
long rotation = maxLatency + MAX_CLOCK_DIFFERENCE;
|
|
||||||
long creationTime = s.getEpoch() + rotation * (s.getPeriod() - 2);
|
|
||||||
long activationTime = creationTime + rotation;
|
|
||||||
long deactivationTime = activationTime + rotation;
|
|
||||||
long destructionTime = deactivationTime + rotation;
|
|
||||||
if (now >= destructionTime) {
|
|
||||||
dead.add(s);
|
|
||||||
} else if (now >= deactivationTime) {
|
|
||||||
oldSecrets.put(new EndpointKey(s), s);
|
|
||||||
} else if (now >= activationTime) {
|
|
||||||
currentSecrets.put(new EndpointKey(s), s);
|
|
||||||
} else if (now >= creationTime) {
|
|
||||||
newSecrets.put(new EndpointKey(s), s);
|
|
||||||
} else {
|
|
||||||
// FIXME: Work out what to do here
|
|
||||||
throw new Error("Clock has moved backwards");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dead;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replaces the given secrets and returns any secrets created
|
|
||||||
// Locking: lock
|
|
||||||
private Collection<TemporarySecret> replaceDeadSecrets(long now,
|
|
||||||
Collection<TemporarySecret> dead) {
|
|
||||||
// If there are several dead secrets for an endpoint, use the newest
|
|
||||||
Map<EndpointKey, TemporarySecret> newest =
|
|
||||||
new HashMap<EndpointKey, TemporarySecret>();
|
|
||||||
for (TemporarySecret s : dead) {
|
|
||||||
EndpointKey k = new EndpointKey(s);
|
|
||||||
TemporarySecret exists = newest.get(k);
|
|
||||||
if (exists == null) {
|
|
||||||
// There's no other secret for this endpoint
|
|
||||||
newest.put(k, s);
|
|
||||||
} else if (exists.getPeriod() < s.getPeriod()) {
|
|
||||||
// There's an older secret - use this one instead
|
|
||||||
newest.put(k, s);
|
|
||||||
} else {
|
|
||||||
// There's a newer secret - keep using it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Collection<TemporarySecret> created = new ArrayList<TemporarySecret>();
|
|
||||||
for (Entry<EndpointKey, TemporarySecret> e : newest.entrySet()) {
|
|
||||||
TemporarySecret s = e.getValue();
|
|
||||||
Integer maxLatency = maxLatencies.get(s.getTransportId());
|
|
||||||
if (maxLatency == null) throw new IllegalStateException();
|
|
||||||
// Work out which rotation period we're in
|
|
||||||
long elapsed = now - s.getEpoch();
|
|
||||||
long rotation = maxLatency + MAX_CLOCK_DIFFERENCE;
|
|
||||||
long period = (elapsed / rotation) + 1;
|
|
||||||
if (period < 1) throw new IllegalStateException();
|
|
||||||
if (period - s.getPeriod() < 2)
|
|
||||||
throw new IllegalStateException();
|
|
||||||
// Derive the old, current and new secrets
|
|
||||||
byte[] b1 = s.getSecret();
|
|
||||||
for (long p = s.getPeriod() + 1; p < period; p++)
|
|
||||||
b1 = crypto.deriveNextSecret(b1, p);
|
|
||||||
byte[] b2 = crypto.deriveNextSecret(b1, period);
|
|
||||||
byte[] b3 = crypto.deriveNextSecret(b2, period + 1);
|
|
||||||
// Add the secrets to their respective maps if not already present
|
|
||||||
EndpointKey k = e.getKey();
|
|
||||||
if (!oldSecrets.containsKey(k)) {
|
|
||||||
TemporarySecret s1 = new TemporarySecret(s, period - 1, b1);
|
|
||||||
oldSecrets.put(k, s1);
|
|
||||||
created.add(s1);
|
|
||||||
}
|
|
||||||
if (!currentSecrets.containsKey(k)) {
|
|
||||||
TemporarySecret s2 = new TemporarySecret(s, period, b2);
|
|
||||||
currentSecrets.put(k, s2);
|
|
||||||
created.add(s2);
|
|
||||||
}
|
|
||||||
if (!newSecrets.containsKey(k)) {
|
|
||||||
TemporarySecret s3 = new TemporarySecret(s, period + 1, b3);
|
|
||||||
newSecrets.put(k, s3);
|
|
||||||
created.add(s3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return created;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean stop() {
|
public boolean stop() {
|
||||||
lock.lock();
|
eventBus.removeListener(this);
|
||||||
try {
|
return true;
|
||||||
eventBus.removeListener(this);
|
}
|
||||||
timer.cancel();
|
|
||||||
tagRecogniser.removeSecrets();
|
public void contactAdded(ContactId c, Collection<TransportKeys> keys) {
|
||||||
maxLatencies.clear();
|
for (TransportKeys k : keys) {
|
||||||
oldSecrets.clear();
|
TransportKeyManager m = managers.get(k.getTransportId());
|
||||||
currentSecrets.clear();
|
if (m != null) m.addContact(c, k);
|
||||||
newSecrets.clear();
|
|
||||||
return true;
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamContext getStreamContext(ContactId c,
|
public StreamContext getStreamContext(ContactId c, TransportId t) {
|
||||||
TransportId t) {
|
TransportKeyManager m = managers.get(t);
|
||||||
lock.lock();
|
return m == null ? null : m.getStreamContext(c);
|
||||||
try {
|
|
||||||
TemporarySecret s = currentSecrets.get(new EndpointKey(c, t));
|
|
||||||
if (s == null) {
|
|
||||||
LOG.info("No secret for endpoint");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
long streamNumber;
|
|
||||||
try {
|
|
||||||
streamNumber = db.incrementStreamCounter(c, t, s.getPeriod());
|
|
||||||
if (streamNumber == -1) {
|
|
||||||
LOG.info("No counter for period");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (DbException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
byte[] secret = s.getSecret();
|
|
||||||
return new StreamContext(c, t, secret, streamNumber, s.getAlice());
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void endpointAdded(Endpoint ep, int maxLatency,
|
public StreamContext recogniseTag(TransportId t, byte[] tag)
|
||||||
byte[] initialSecret) {
|
throws DbException {
|
||||||
lock.lock();
|
TransportKeyManager m = managers.get(t);
|
||||||
try {
|
return m == null ? null : m.recogniseTag(tag);
|
||||||
maxLatencies.put(ep.getTransportId(), maxLatency);
|
|
||||||
// Work out which rotation period we're in
|
|
||||||
long elapsed = clock.currentTimeMillis() - ep.getEpoch();
|
|
||||||
long rotation = maxLatency + MAX_CLOCK_DIFFERENCE;
|
|
||||||
long period = (elapsed / rotation) + 1;
|
|
||||||
if (period < 1) throw new IllegalStateException();
|
|
||||||
// Derive the old, current and new secrets
|
|
||||||
byte[] b1 = initialSecret;
|
|
||||||
for (long p = 0; p < period; p++)
|
|
||||||
b1 = crypto.deriveNextSecret(b1, p);
|
|
||||||
byte[] b2 = crypto.deriveNextSecret(b1, period);
|
|
||||||
byte[] b3 = crypto.deriveNextSecret(b2, period + 1);
|
|
||||||
TemporarySecret s1 = new TemporarySecret(ep, period - 1, b1);
|
|
||||||
TemporarySecret s2 = new TemporarySecret(ep, period, b2);
|
|
||||||
TemporarySecret s3 = new TemporarySecret(ep, period + 1, b3);
|
|
||||||
// Add the incoming secrets to their respective maps
|
|
||||||
EndpointKey k = new EndpointKey(ep);
|
|
||||||
oldSecrets.put(k, s1);
|
|
||||||
currentSecrets.put(k, s2);
|
|
||||||
newSecrets.put(k, s3);
|
|
||||||
// Store the new secrets
|
|
||||||
try {
|
|
||||||
db.addSecrets(Arrays.asList(s1, s2, s3));
|
|
||||||
} catch (DbException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Pass the new secrets to the recogniser
|
|
||||||
tagRecogniser.addSecret(s1);
|
|
||||||
tagRecogniser.addSecret(s2);
|
|
||||||
tagRecogniser.addSecret(s3);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
// Rebuild the maps because we may be running a whole period late
|
|
||||||
Collection<TemporarySecret> secrets = new ArrayList<TemporarySecret>();
|
|
||||||
secrets.addAll(oldSecrets.values());
|
|
||||||
secrets.addAll(currentSecrets.values());
|
|
||||||
secrets.addAll(newSecrets.values());
|
|
||||||
oldSecrets.clear();
|
|
||||||
currentSecrets.clear();
|
|
||||||
newSecrets.clear();
|
|
||||||
// Work out what phase of its lifecycle each secret is in
|
|
||||||
long now = clock.currentTimeMillis();
|
|
||||||
Collection<TemporarySecret> dead = assignSecretsToMaps(now, secrets);
|
|
||||||
// Remove any dead secrets from the recogniser
|
|
||||||
for (TemporarySecret s : dead) {
|
|
||||||
ContactId c = s.getContactId();
|
|
||||||
TransportId t = s.getTransportId();
|
|
||||||
long period = s.getPeriod();
|
|
||||||
tagRecogniser.removeSecret(c, t, period);
|
|
||||||
}
|
|
||||||
// Replace any dead secrets
|
|
||||||
Collection<TemporarySecret> created = replaceDeadSecrets(now, dead);
|
|
||||||
if (!created.isEmpty()) {
|
|
||||||
// Store any secrets that have been created
|
|
||||||
try {
|
|
||||||
db.addSecrets(created);
|
|
||||||
} catch (DbException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
// Pass any secrets that have been created to the recogniser
|
|
||||||
for (TemporarySecret s : created) tagRecogniser.addSecret(s);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void eventOccurred(Event e) {
|
public void eventOccurred(Event e) {
|
||||||
if (e instanceof ContactRemovedEvent) {
|
if (e instanceof TransportAddedEvent) {
|
||||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
|
||||||
timer.schedule(new ContactRemovedTask(c), 0);
|
|
||||||
} else if (e instanceof TransportAddedEvent) {
|
|
||||||
TransportAddedEvent t = (TransportAddedEvent) e;
|
TransportAddedEvent t = (TransportAddedEvent) e;
|
||||||
timer.schedule(new TransportAddedTask(t), 0);
|
addTransport(t.getTransportId(), t.getMaxLatency());
|
||||||
} else if (e instanceof TransportRemovedEvent) {
|
} else if (e instanceof TransportRemovedEvent) {
|
||||||
TransportRemovedEvent t = (TransportRemovedEvent) e;
|
removeTransport(((TransportRemovedEvent) e).getTransportId());
|
||||||
timer.schedule(new TransportRemovedTask(t), 0);
|
} else if (e instanceof ContactRemovedEvent) {
|
||||||
|
removeContact(((ContactRemovedEvent) e).getContactId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locking: lock
|
private void addTransport(final TransportId t, final int maxLatency) {
|
||||||
private void removeSecrets(ContactId c, Map<?, TemporarySecret> m) {
|
dbExecutor.execute(new Runnable() {
|
||||||
Iterator<TemporarySecret> it = m.values().iterator();
|
public void run() {
|
||||||
while (it.hasNext())
|
TransportKeyManager m = new TransportKeyManager(db, crypto,
|
||||||
if (it.next().getContactId().equals(c)) it.remove();
|
dbExecutor, timer, clock, t, maxLatency);
|
||||||
}
|
// Don't add transport twice if event is received during startup
|
||||||
|
if (managers.putIfAbsent(t, m) == null) m.start();
|
||||||
// Locking: lock
|
|
||||||
private void removeSecrets(TransportId t, Map<?, TemporarySecret> m) {
|
|
||||||
Iterator<TemporarySecret> it = m.values().iterator();
|
|
||||||
while (it.hasNext())
|
|
||||||
if (it.next().getTransportId().equals(t)) it.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class EndpointKey {
|
|
||||||
|
|
||||||
private final ContactId contactId;
|
|
||||||
private final TransportId transportId;
|
|
||||||
|
|
||||||
private EndpointKey(ContactId contactId, TransportId transportId) {
|
|
||||||
this.contactId = contactId;
|
|
||||||
this.transportId = transportId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private EndpointKey(Endpoint ep) {
|
|
||||||
this(ep.getContactId(), ep.getTransportId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return contactId.hashCode() ^ transportId.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (o instanceof EndpointKey) {
|
|
||||||
EndpointKey k = (EndpointKey) o;
|
|
||||||
return contactId.equals(k.contactId) &&
|
|
||||||
transportId.equals(k.transportId);
|
|
||||||
}
|
}
|
||||||
return false;
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ContactRemovedTask extends TimerTask {
|
private void removeTransport(TransportId t) {
|
||||||
|
managers.remove(t);
|
||||||
private final ContactRemovedEvent event;
|
|
||||||
|
|
||||||
private ContactRemovedTask(ContactRemovedEvent event) {
|
|
||||||
this.event = event;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
ContactId c = event.getContactId();
|
|
||||||
tagRecogniser.removeSecrets(c);
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
removeSecrets(c, oldSecrets);
|
|
||||||
removeSecrets(c, currentSecrets);
|
|
||||||
removeSecrets(c, newSecrets);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TransportAddedTask extends TimerTask {
|
private void removeContact(final ContactId c) {
|
||||||
|
dbExecutor.execute(new Runnable() {
|
||||||
private final TransportAddedEvent event;
|
public void run() {
|
||||||
|
for (TransportKeyManager m : managers.values())
|
||||||
private TransportAddedTask(TransportAddedEvent event) {
|
m.removeContact(c);
|
||||||
this.event = event;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
maxLatencies.put(event.getTransportId(), event.getMaxLatency());
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
private class TransportRemovedTask extends TimerTask {
|
|
||||||
|
|
||||||
private TransportRemovedEvent event;
|
|
||||||
|
|
||||||
private TransportRemovedTask(TransportRemovedEvent event) {
|
|
||||||
this.event = event;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
TransportId t = event.getTransportId();
|
|
||||||
tagRecogniser.removeSecrets(t);
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
maxLatencies.remove(t);
|
|
||||||
removeSecrets(t, oldSecrets);
|
|
||||||
removeSecrets(t, currentSecrets);
|
|
||||||
removeSecrets(t, newSecrets);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package org.briarproject.transport;
|
||||||
|
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.api.transport.IncomingKeys;
|
||||||
|
|
||||||
|
// This class is not thread-safe
|
||||||
|
class MutableIncomingKeys {
|
||||||
|
|
||||||
|
private final SecretKey tagKey, headerKey;
|
||||||
|
private final long rotationPeriod;
|
||||||
|
private final ReorderingWindow window;
|
||||||
|
|
||||||
|
MutableIncomingKeys(IncomingKeys in) {
|
||||||
|
tagKey = in.getTagKey();
|
||||||
|
headerKey = in.getHeaderKey();
|
||||||
|
rotationPeriod = in.getRotationPeriod();
|
||||||
|
window = new ReorderingWindow(in.getWindowBase(), in.getWindowBitmap());
|
||||||
|
}
|
||||||
|
|
||||||
|
IncomingKeys snapshot() {
|
||||||
|
return new IncomingKeys(tagKey, headerKey, rotationPeriod,
|
||||||
|
window.getBase(), window.getBitmap());
|
||||||
|
}
|
||||||
|
|
||||||
|
SecretKey getTagKey() {
|
||||||
|
return tagKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
SecretKey getHeaderKey() {
|
||||||
|
return headerKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getRotationPeriod() {
|
||||||
|
return rotationPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReorderingWindow getWindow() {
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.briarproject.transport;
|
||||||
|
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.api.transport.OutgoingKeys;
|
||||||
|
|
||||||
|
// This class is not thread-safe
|
||||||
|
class MutableOutgoingKeys {
|
||||||
|
|
||||||
|
private final SecretKey tagKey, headerKey;
|
||||||
|
private final long rotationPeriod;
|
||||||
|
private long streamCounter;
|
||||||
|
|
||||||
|
MutableOutgoingKeys(OutgoingKeys out) {
|
||||||
|
tagKey = out.getTagKey();
|
||||||
|
headerKey = out.getHeaderKey();
|
||||||
|
rotationPeriod = out.getRotationPeriod();
|
||||||
|
streamCounter = out.getStreamCounter();
|
||||||
|
}
|
||||||
|
|
||||||
|
OutgoingKeys snapshot() {
|
||||||
|
return new OutgoingKeys(tagKey, headerKey, rotationPeriod,
|
||||||
|
streamCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
SecretKey getTagKey() {
|
||||||
|
return tagKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
SecretKey getHeaderKey() {
|
||||||
|
return headerKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getRotationPeriod() {
|
||||||
|
return rotationPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getStreamCounter() {
|
||||||
|
return streamCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
void incrementStreamCounter() {
|
||||||
|
streamCounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.briarproject.transport;
|
||||||
|
|
||||||
|
import org.briarproject.api.TransportId;
|
||||||
|
import org.briarproject.api.transport.TransportKeys;
|
||||||
|
|
||||||
|
class MutableTransportKeys {
|
||||||
|
|
||||||
|
private final TransportId transportId;
|
||||||
|
private final MutableIncomingKeys inPrev, inCurr, inNext;
|
||||||
|
private final MutableOutgoingKeys outCurr;
|
||||||
|
|
||||||
|
MutableTransportKeys(TransportKeys k) {
|
||||||
|
transportId = k.getTransportId();
|
||||||
|
inPrev = new MutableIncomingKeys(k.getPreviousIncomingKeys());
|
||||||
|
inCurr = new MutableIncomingKeys(k.getCurrentIncomingKeys());
|
||||||
|
inNext = new MutableIncomingKeys(k.getNextIncomingKeys());
|
||||||
|
outCurr = new MutableOutgoingKeys(k.getCurrentOutgoingKeys());
|
||||||
|
}
|
||||||
|
|
||||||
|
TransportKeys snapshot() {
|
||||||
|
return new TransportKeys(transportId, inPrev.snapshot(),
|
||||||
|
inCurr.snapshot(), inNext.snapshot(), outCurr.snapshot());
|
||||||
|
}
|
||||||
|
|
||||||
|
TransportId getTransportId() {
|
||||||
|
return transportId;
|
||||||
|
}
|
||||||
|
|
||||||
|
MutableIncomingKeys getPreviousIncomingKeys() {
|
||||||
|
return inPrev;
|
||||||
|
}
|
||||||
|
|
||||||
|
MutableIncomingKeys getCurrentIncomingKeys() {
|
||||||
|
return inCurr;
|
||||||
|
}
|
||||||
|
|
||||||
|
MutableIncomingKeys getNextIncomingKeys() {
|
||||||
|
return inNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
MutableOutgoingKeys getCurrentOutgoingKeys() {
|
||||||
|
return outCurr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,102 +1,98 @@
|
|||||||
package org.briarproject.transport;
|
package org.briarproject.transport;
|
||||||
|
|
||||||
import static org.briarproject.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
|
|
||||||
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||||
|
|
||||||
// This class is not thread-safe
|
// This class is not thread-safe
|
||||||
class ReorderingWindow {
|
class ReorderingWindow {
|
||||||
|
|
||||||
private final Set<Long> unseen;
|
private long base;
|
||||||
|
private boolean[] seen;
|
||||||
|
|
||||||
private long centre;
|
ReorderingWindow(long base, byte[] bitmap) {
|
||||||
|
if (base < 0) throw new IllegalArgumentException();
|
||||||
ReorderingWindow() {
|
if (base > MAX_32_BIT_UNSIGNED + 1)
|
||||||
unseen = new HashSet<Long>();
|
|
||||||
for (long l = 0; l < REORDERING_WINDOW_SIZE / 2; l++) unseen.add(l);
|
|
||||||
centre = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReorderingWindow(long centre, byte[] bitmap) {
|
|
||||||
if (centre < 0 || centre > MAX_32_BIT_UNSIGNED + 1)
|
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
if (bitmap.length != REORDERING_WINDOW_SIZE / 8)
|
this.base = base;
|
||||||
throw new IllegalArgumentException();
|
seen = new boolean[bitmap.length * 8];
|
||||||
this.centre = centre;
|
for (int i = 0; i < bitmap.length; i++) {
|
||||||
unseen = new HashSet<Long>();
|
for (int j = 0; j < 8; j++) {
|
||||||
long bitmapBottom = centre - REORDERING_WINDOW_SIZE / 2;
|
if ((bitmap[i] & (128 >> j)) != 0) seen[i * 8 + j] = true;
|
||||||
for (int bytes = 0; bytes < bitmap.length; bytes++) {
|
|
||||||
for (int bits = 0; bits < 8; bits++) {
|
|
||||||
long streamNumber = bitmapBottom + bytes * 8 + bits;
|
|
||||||
if (streamNumber >= 0 && streamNumber <= MAX_32_BIT_UNSIGNED) {
|
|
||||||
if ((bitmap[bytes] & (128 >> bits)) == 0)
|
|
||||||
unseen.add(streamNumber);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isSeen(long streamNumber) {
|
long getBase() {
|
||||||
return !unseen.contains(streamNumber);
|
return base;
|
||||||
}
|
|
||||||
|
|
||||||
Collection<Long> setSeen(long streamNumber) {
|
|
||||||
long bottom = getBottom(centre);
|
|
||||||
long top = getTop(centre);
|
|
||||||
if (streamNumber < bottom || streamNumber > top)
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
if (!unseen.remove(streamNumber))
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
Collection<Long> changed = new ArrayList<Long>();
|
|
||||||
if (streamNumber >= centre) {
|
|
||||||
centre = streamNumber + 1;
|
|
||||||
long newBottom = getBottom(centre);
|
|
||||||
long newTop = getTop(centre);
|
|
||||||
for (long l = bottom; l < newBottom; l++) {
|
|
||||||
if (unseen.remove(l)) changed.add(l);
|
|
||||||
}
|
|
||||||
for (long l = top + 1; l <= newTop; l++) {
|
|
||||||
if (unseen.add(l)) changed.add(l);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
long getCentre() {
|
|
||||||
return centre;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] getBitmap() {
|
byte[] getBitmap() {
|
||||||
byte[] bitmap = new byte[REORDERING_WINDOW_SIZE / 8];
|
byte[] bitmap = new byte[seen.length / 8];
|
||||||
long bitmapBottom = centre - REORDERING_WINDOW_SIZE / 2;
|
for (int i = 0; i < bitmap.length; i++) {
|
||||||
for (int bytes = 0; bytes < bitmap.length; bytes++) {
|
for (int j = 0; j < 8; j++) {
|
||||||
for (int bits = 0; bits < 8; bits++) {
|
if (seen[i * 8 + j]) bitmap[i] |= 128 >> j;
|
||||||
long streamNumber = bitmapBottom + bytes * 8 + bits;
|
|
||||||
if (streamNumber >= 0 && streamNumber <= MAX_32_BIT_UNSIGNED) {
|
|
||||||
if (!unseen.contains(streamNumber))
|
|
||||||
bitmap[bytes] |= 128 >> bits;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the lowest value contained in a window with the given centre
|
List<Long> getUnseen() {
|
||||||
private static long getBottom(long centre) {
|
List<Long> unseen = new ArrayList<Long>(seen.length);
|
||||||
return Math.max(0, centre - REORDERING_WINDOW_SIZE / 2);
|
for (int i = 0; i < seen.length; i++)
|
||||||
}
|
if (!seen[i]) unseen.add(base + i);
|
||||||
|
|
||||||
// Returns the highest value contained in a window with the given centre
|
|
||||||
private static long getTop(long centre) {
|
|
||||||
return Math.min(MAX_32_BIT_UNSIGNED,
|
|
||||||
centre + REORDERING_WINDOW_SIZE / 2 - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<Long> getUnseen() {
|
|
||||||
return unseen;
|
return unseen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Change setSeen(long index) {
|
||||||
|
if (index < base) throw new IllegalArgumentException();
|
||||||
|
if (index >= base + seen.length) throw new IllegalArgumentException();
|
||||||
|
if (index > MAX_32_BIT_UNSIGNED) throw new IllegalArgumentException();
|
||||||
|
int offset = (int) (index - base);
|
||||||
|
if (seen[offset]) throw new IllegalArgumentException();
|
||||||
|
seen[offset] = true;
|
||||||
|
// Rule 1: Slide until all elements above the midpoint are unseen
|
||||||
|
int slide = Math.max(0, offset + 1 - seen.length / 2);
|
||||||
|
// Rule 2: Slide until the lowest element is unseen
|
||||||
|
while (seen[slide]) slide++;
|
||||||
|
// If the window doesn't need to slide, return
|
||||||
|
if (slide == 0) {
|
||||||
|
List<Long> added = Collections.emptyList();
|
||||||
|
List<Long> removed = Collections.singletonList(index);
|
||||||
|
return new Change(added, removed);
|
||||||
|
}
|
||||||
|
// Record the elements that will be added and removed
|
||||||
|
List<Long> added = new ArrayList<Long>(slide);
|
||||||
|
List<Long> removed = new ArrayList<Long>(slide);
|
||||||
|
for (int i = 0; i < slide; i++) {
|
||||||
|
if (!seen[i]) removed.add(base + i);
|
||||||
|
added.add(base + seen.length + i);
|
||||||
|
}
|
||||||
|
removed.add(index);
|
||||||
|
// Update the window
|
||||||
|
base += slide;
|
||||||
|
for (int i = 0; i + slide < seen.length; i++) seen[i] = seen[i + slide];
|
||||||
|
for (int i = seen.length - slide; i < seen.length; i++) seen[i] = false;
|
||||||
|
return new Change(added, removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Change {
|
||||||
|
|
||||||
|
private final List<Long> added, removed;
|
||||||
|
|
||||||
|
Change(List<Long> added, List<Long> removed) {
|
||||||
|
this.added = added;
|
||||||
|
this.removed = removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Long> getAdded() {
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Long> getRemoved() {
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.io.InputStream;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
import org.briarproject.api.crypto.StreamDecrypterFactory;
|
import org.briarproject.api.crypto.StreamDecrypterFactory;
|
||||||
import org.briarproject.api.transport.StreamContext;
|
import org.briarproject.api.transport.StreamContext;
|
||||||
import org.briarproject.api.transport.StreamReaderFactory;
|
import org.briarproject.api.transport.StreamReaderFactory;
|
||||||
@@ -23,9 +24,9 @@ class StreamReaderFactoryImpl implements StreamReaderFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public InputStream createInvitationStreamReader(InputStream in,
|
public InputStream createInvitationStreamReader(InputStream in,
|
||||||
byte[] secret, boolean alice) {
|
SecretKey headerKey) {
|
||||||
return new StreamReaderImpl(
|
return new StreamReaderImpl(
|
||||||
streamDecrypterFactory.createInvitationStreamDecrypter(in,
|
streamDecrypterFactory.createInvitationStreamDecrypter(in,
|
||||||
secret, alice));
|
headerKey));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ import java.io.InputStream;
|
|||||||
|
|
||||||
import org.briarproject.api.crypto.StreamDecrypter;
|
import org.briarproject.api.crypto.StreamDecrypter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link java.io.InputStream InputStream} that unpacks payload data from
|
||||||
|
* transport frames.
|
||||||
|
* <p>
|
||||||
|
* This class is not thread-safe.
|
||||||
|
*/
|
||||||
class StreamReaderImpl extends InputStream {
|
class StreamReaderImpl extends InputStream {
|
||||||
|
|
||||||
private final StreamDecrypter decrypter;
|
private final StreamDecrypter decrypter;
|
||||||
@@ -50,7 +56,7 @@ class StreamReaderImpl extends InputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void readFrame() throws IOException {
|
private void readFrame() throws IOException {
|
||||||
assert length == 0;
|
if (length != 0) throw new IllegalStateException();
|
||||||
offset = 0;
|
offset = 0;
|
||||||
length = decrypter.readFrame(payload);
|
length = decrypter.readFrame(payload);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.io.OutputStream;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
import org.briarproject.api.crypto.StreamEncrypterFactory;
|
import org.briarproject.api.crypto.StreamEncrypterFactory;
|
||||||
import org.briarproject.api.transport.StreamContext;
|
import org.briarproject.api.transport.StreamContext;
|
||||||
import org.briarproject.api.transport.StreamWriterFactory;
|
import org.briarproject.api.transport.StreamWriterFactory;
|
||||||
@@ -24,9 +25,9 @@ class StreamWriterFactoryImpl implements StreamWriterFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public OutputStream createInvitationStreamWriter(OutputStream out,
|
public OutputStream createInvitationStreamWriter(OutputStream out,
|
||||||
byte[] secret, boolean alice) {
|
SecretKey headerKey) {
|
||||||
return new StreamWriterImpl(
|
return new StreamWriterImpl(
|
||||||
streamEncrypterFactory.createInvitationStreamEncrypter(out,
|
streamEncrypterFactory.createInvitationStreamEncrypter(out,
|
||||||
secret, alice));
|
headerKey));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,9 +8,9 @@ import java.io.OutputStream;
|
|||||||
import org.briarproject.api.crypto.StreamEncrypter;
|
import org.briarproject.api.crypto.StreamEncrypter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link org.briarproject.api.transport.StreamWriter StreamWriter} that
|
* An {@link java.io.OutputStream OutputStream} that packs data into transport
|
||||||
* buffers its input and writes a frame whenever there is a full frame to write
|
* frames, writing a frame whenever there is a full frame to write or the
|
||||||
* or the {@link #flush()} method is called.
|
* {@link #flush()} method is called.
|
||||||
* <p>
|
* <p>
|
||||||
* This class is not thread-safe.
|
* This class is not thread-safe.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
package org.briarproject.transport;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import org.briarproject.api.ContactId;
|
|
||||||
import org.briarproject.api.TransportId;
|
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
|
||||||
import org.briarproject.api.db.DbException;
|
|
||||||
import org.briarproject.api.transport.StreamContext;
|
|
||||||
import org.briarproject.api.transport.TagRecogniser;
|
|
||||||
import org.briarproject.api.transport.TemporarySecret;
|
|
||||||
|
|
||||||
class TagRecogniserImpl implements TagRecogniser {
|
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
|
||||||
private final DatabaseComponent db;
|
|
||||||
private final Lock lock = new ReentrantLock();
|
|
||||||
|
|
||||||
// Locking: lock
|
|
||||||
private final Map<TransportId, TransportTagRecogniser> recognisers;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
TagRecogniserImpl(CryptoComponent crypto, DatabaseComponent db) {
|
|
||||||
this.crypto = crypto;
|
|
||||||
this.db = db;
|
|
||||||
recognisers = new HashMap<TransportId, TransportTagRecogniser>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public StreamContext recogniseTag(TransportId t, byte[] tag)
|
|
||||||
throws DbException {
|
|
||||||
TransportTagRecogniser r;
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
r = recognisers.get(t);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
if (r == null) return null;
|
|
||||||
return r.recogniseTag(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addSecret(TemporarySecret s) {
|
|
||||||
TransportId t = s.getTransportId();
|
|
||||||
TransportTagRecogniser r;
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
r = recognisers.get(t);
|
|
||||||
if (r == null) {
|
|
||||||
r = new TransportTagRecogniser(crypto, db, t);
|
|
||||||
recognisers.put(t, r);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
r.addSecret(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeSecret(ContactId c, TransportId t, long period) {
|
|
||||||
TransportTagRecogniser r;
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
r = recognisers.get(t);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
if (r != null) r.removeSecret(c, period);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeSecrets(ContactId c) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
for (TransportTagRecogniser r : recognisers.values())
|
|
||||||
r.removeSecrets(c);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeSecrets(TransportId t) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
recognisers.remove(t);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeSecrets() {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
for (TransportTagRecogniser r : recognisers.values())
|
|
||||||
r.removeSecrets();
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
package org.briarproject.transport;
|
||||||
|
|
||||||
|
import org.briarproject.api.Bytes;
|
||||||
|
import org.briarproject.api.ContactId;
|
||||||
|
import org.briarproject.api.TransportId;
|
||||||
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.api.db.DbException;
|
||||||
|
import org.briarproject.api.system.Clock;
|
||||||
|
import org.briarproject.api.system.Timer;
|
||||||
|
import org.briarproject.api.transport.StreamContext;
|
||||||
|
import org.briarproject.api.transport.TransportKeys;
|
||||||
|
import org.briarproject.transport.ReorderingWindow.Change;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||||
|
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
||||||
|
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||||
|
|
||||||
|
class TransportKeyManager extends TimerTask {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
Logger.getLogger(TransportKeyManager.class.getName());
|
||||||
|
|
||||||
|
private final DatabaseComponent db;
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
private final Executor dbExecutor;
|
||||||
|
private final Timer timer;
|
||||||
|
private final Clock clock;
|
||||||
|
private final TransportId transportId;
|
||||||
|
private final long rotationPeriodLength;
|
||||||
|
private final ReentrantLock lock;
|
||||||
|
|
||||||
|
// The following are locking: lock
|
||||||
|
private final Map<Bytes, TagContext> inContexts;
|
||||||
|
private final Map<ContactId, MutableOutgoingKeys> outContexts;
|
||||||
|
private final Map<ContactId, MutableTransportKeys> keys;
|
||||||
|
|
||||||
|
TransportKeyManager(DatabaseComponent db, CryptoComponent crypto,
|
||||||
|
Executor dbExecutor, Timer timer, Clock clock,
|
||||||
|
TransportId transportId, long maxLatency) {
|
||||||
|
this.db = db;
|
||||||
|
this.crypto = crypto;
|
||||||
|
this.dbExecutor = dbExecutor;
|
||||||
|
this.timer = timer;
|
||||||
|
this.clock = clock;
|
||||||
|
this.transportId = transportId;
|
||||||
|
rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
|
||||||
|
lock = new ReentrantLock();
|
||||||
|
inContexts = new HashMap<Bytes, TagContext>();
|
||||||
|
outContexts = new HashMap<ContactId, MutableOutgoingKeys>();
|
||||||
|
keys = new HashMap<ContactId, MutableTransportKeys>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
// Load the transport keys from the DB
|
||||||
|
Map<ContactId, TransportKeys> loaded;
|
||||||
|
try {
|
||||||
|
loaded = db.getTransportKeys(transportId);
|
||||||
|
} catch (DbException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Rotate the keys to the current rotation period
|
||||||
|
Map<ContactId, TransportKeys> rotated =
|
||||||
|
new HashMap<ContactId, TransportKeys>();
|
||||||
|
Map<ContactId, TransportKeys> current =
|
||||||
|
new HashMap<ContactId, TransportKeys>();
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
long rotationPeriod = now / rotationPeriodLength;
|
||||||
|
for (Entry<ContactId, TransportKeys> e : loaded.entrySet()) {
|
||||||
|
ContactId c = e.getKey();
|
||||||
|
TransportKeys k = e.getValue();
|
||||||
|
TransportKeys k1 = crypto.rotateTransportKeys(k, rotationPeriod);
|
||||||
|
if (k1.getRotationPeriod() > k.getRotationPeriod())
|
||||||
|
rotated.put(c, k1);
|
||||||
|
current.put(c, k1);
|
||||||
|
}
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
// Initialise mutable state for all contacts
|
||||||
|
for (Entry<ContactId, TransportKeys> e : current.entrySet())
|
||||||
|
addKeys(e.getKey(), new MutableTransportKeys(e.getValue()));
|
||||||
|
// Write any rotated keys back to the DB
|
||||||
|
saveTransportKeys(rotated);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
// Schedule a periodic task to rotate the keys
|
||||||
|
long delay = rotationPeriodLength - now % rotationPeriodLength;
|
||||||
|
timer.scheduleAtFixedRate(this, delay, rotationPeriodLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locking: lock
|
||||||
|
private void addKeys(ContactId c, MutableTransportKeys m) {
|
||||||
|
encodeTags(c, m.getPreviousIncomingKeys());
|
||||||
|
encodeTags(c, m.getCurrentIncomingKeys());
|
||||||
|
encodeTags(c, m.getNextIncomingKeys());
|
||||||
|
outContexts.put(c, m.getCurrentOutgoingKeys());
|
||||||
|
keys.put(c, m);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locking: lock
|
||||||
|
private void encodeTags(ContactId c, MutableIncomingKeys inKeys) {
|
||||||
|
for (long streamNumber : inKeys.getWindow().getUnseen()) {
|
||||||
|
TagContext tagCtx = new TagContext(c, inKeys, streamNumber);
|
||||||
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
|
crypto.encodeTag(tag, inKeys.getTagKey(), streamNumber);
|
||||||
|
inContexts.put(new Bytes(tag), tagCtx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveTransportKeys(final Map<ContactId, TransportKeys> rotated) {
|
||||||
|
dbExecutor.execute(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
db.updateTransportKeys(rotated);
|
||||||
|
} catch (DbException e) {
|
||||||
|
if (LOG.isLoggable(WARNING))
|
||||||
|
LOG.log(WARNING, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void addContact(ContactId c, TransportKeys k) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
// Initialise mutable state for the contact
|
||||||
|
addKeys(c, new MutableTransportKeys(k));
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeContact(ContactId c) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
// Remove mutable state for the contact
|
||||||
|
Iterator<Entry<Bytes, TagContext>> it =
|
||||||
|
inContexts.entrySet().iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
if (it.next().getValue().contactId.equals(c)) it.remove();
|
||||||
|
outContexts.remove(c);
|
||||||
|
keys.remove(c);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamContext getStreamContext(ContactId c) {
|
||||||
|
StreamContext ctx;
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
// Look up the outgoing keys for the contact
|
||||||
|
MutableOutgoingKeys outKeys = outContexts.get(c);
|
||||||
|
if (outKeys == null) return null;
|
||||||
|
if (outKeys.getStreamCounter() > MAX_32_BIT_UNSIGNED) return null;
|
||||||
|
// Create a stream context
|
||||||
|
ctx = new StreamContext(c, transportId, outKeys.getTagKey(),
|
||||||
|
outKeys.getHeaderKey(), outKeys.getStreamCounter());
|
||||||
|
// Increment the stream counter and write it back to the DB
|
||||||
|
outKeys.incrementStreamCounter();
|
||||||
|
saveIncrementedStreamCounter(c, outKeys.getRotationPeriod());
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
// TODO: Wait for save to complete, return null if it fails
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveIncrementedStreamCounter(final ContactId c,
|
||||||
|
final long rotationPeriod) {
|
||||||
|
dbExecutor.execute(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
db.incrementStreamCounter(c, transportId, rotationPeriod);
|
||||||
|
} catch (DbException e) {
|
||||||
|
if (LOG.isLoggable(WARNING))
|
||||||
|
LOG.log(WARNING, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamContext recogniseTag(byte[] tag) {
|
||||||
|
StreamContext ctx;
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
// Look up the incoming keys for the tag
|
||||||
|
TagContext tagCtx = inContexts.remove(new Bytes(tag));
|
||||||
|
if (tagCtx == null) return null;
|
||||||
|
MutableIncomingKeys inKeys = tagCtx.inKeys;
|
||||||
|
// Create a stream context
|
||||||
|
ctx = new StreamContext(tagCtx.contactId, transportId,
|
||||||
|
inKeys.getTagKey(), inKeys.getHeaderKey(),
|
||||||
|
tagCtx.streamNumber);
|
||||||
|
// Update the reordering window
|
||||||
|
ReorderingWindow window = inKeys.getWindow();
|
||||||
|
Change change = window.setSeen(tagCtx.streamNumber);
|
||||||
|
// Add tags for any stream numbers added to the window
|
||||||
|
for (long streamNumber : change.getAdded()) {
|
||||||
|
byte[] addTag = new byte[TAG_LENGTH];
|
||||||
|
crypto.encodeTag(addTag, inKeys.getTagKey(), streamNumber);
|
||||||
|
inContexts.put(new Bytes(addTag), new TagContext(
|
||||||
|
tagCtx.contactId, inKeys, streamNumber));
|
||||||
|
}
|
||||||
|
// Remove tags for any stream numbers removed from the window
|
||||||
|
for (long streamNumber : change.getRemoved()) {
|
||||||
|
byte[] removeTag = new byte[TAG_LENGTH];
|
||||||
|
crypto.encodeTag(removeTag, inKeys.getTagKey(), streamNumber);
|
||||||
|
inContexts.remove(new Bytes(removeTag));
|
||||||
|
}
|
||||||
|
// Write the window back to the DB
|
||||||
|
saveReorderingWindow(tagCtx.contactId, inKeys.getRotationPeriod(),
|
||||||
|
window.getBase(), window.getBitmap());
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
// TODO: Wait for save to complete, return null if it fails
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveReorderingWindow(final ContactId c,
|
||||||
|
final long rotationPeriod, final long base, final byte[] bitmap) {
|
||||||
|
dbExecutor.execute(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
db.setReorderingWindow(c, transportId, rotationPeriod,
|
||||||
|
base, bitmap);
|
||||||
|
} catch (DbException e) {
|
||||||
|
if (LOG.isLoggable(WARNING))
|
||||||
|
LOG.log(WARNING, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
// Rotate the keys to the current rotation period
|
||||||
|
Map<ContactId, TransportKeys> rotated =
|
||||||
|
new HashMap<ContactId, TransportKeys>();
|
||||||
|
Map<ContactId, TransportKeys> current =
|
||||||
|
new HashMap<ContactId, TransportKeys>();
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
long rotationPeriod = now / rotationPeriodLength;
|
||||||
|
for (Entry<ContactId, MutableTransportKeys> e : keys.entrySet()) {
|
||||||
|
ContactId c = e.getKey();
|
||||||
|
TransportKeys k = e.getValue().snapshot();
|
||||||
|
TransportKeys k1 = crypto.rotateTransportKeys(k,
|
||||||
|
rotationPeriod);
|
||||||
|
if (k1.getRotationPeriod() > k.getRotationPeriod())
|
||||||
|
rotated.put(c, k1);
|
||||||
|
current.put(c, k1);
|
||||||
|
}
|
||||||
|
// Rebuild the mutable state for all contacts
|
||||||
|
inContexts.clear();
|
||||||
|
outContexts.clear();
|
||||||
|
keys.clear();
|
||||||
|
for (Entry<ContactId, TransportKeys> e : current.entrySet())
|
||||||
|
addKeys(e.getKey(), new MutableTransportKeys(e.getValue()));
|
||||||
|
// Write any rotated keys back to the DB
|
||||||
|
saveTransportKeys(rotated);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TagContext {
|
||||||
|
|
||||||
|
private final ContactId contactId;
|
||||||
|
private final MutableIncomingKeys inKeys;
|
||||||
|
private final long streamNumber;
|
||||||
|
|
||||||
|
private TagContext(ContactId contactId, MutableIncomingKeys inKeys,
|
||||||
|
long streamNumber) {
|
||||||
|
this.contactId = contactId;
|
||||||
|
this.inKeys = inKeys;
|
||||||
|
this.streamNumber = streamNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,20 @@
|
|||||||
package org.briarproject.transport;
|
package org.briarproject.transport;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import org.briarproject.api.crypto.KeyManager;
|
|
||||||
import org.briarproject.api.lifecycle.LifecycleManager;
|
|
||||||
import org.briarproject.api.transport.StreamReaderFactory;
|
|
||||||
import org.briarproject.api.transport.StreamWriterFactory;
|
|
||||||
import org.briarproject.api.transport.TagRecogniser;
|
|
||||||
|
|
||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.Provides;
|
import com.google.inject.Provides;
|
||||||
|
|
||||||
|
import org.briarproject.api.lifecycle.LifecycleManager;
|
||||||
|
import org.briarproject.api.transport.KeyManager;
|
||||||
|
import org.briarproject.api.transport.StreamReaderFactory;
|
||||||
|
import org.briarproject.api.transport.StreamWriterFactory;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
public class TransportModule extends AbstractModule {
|
public class TransportModule extends AbstractModule {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
bind(StreamReaderFactory.class).to(StreamReaderFactoryImpl.class);
|
bind(StreamReaderFactory.class).to(StreamReaderFactoryImpl.class);
|
||||||
bind(TagRecogniser.class).to(
|
|
||||||
TagRecogniserImpl.class).in(Singleton.class);
|
|
||||||
bind(StreamWriterFactory.class).to(StreamWriterFactoryImpl.class);
|
bind(StreamWriterFactory.class).to(StreamWriterFactoryImpl.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,215 +0,0 @@
|
|||||||
package org.briarproject.transport;
|
|
||||||
|
|
||||||
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
import org.briarproject.api.Bytes;
|
|
||||||
import org.briarproject.api.ContactId;
|
|
||||||
import org.briarproject.api.TransportId;
|
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
|
||||||
import org.briarproject.api.db.DbException;
|
|
||||||
import org.briarproject.api.transport.StreamContext;
|
|
||||||
import org.briarproject.api.transport.TemporarySecret;
|
|
||||||
|
|
||||||
// FIXME: Don't make alien calls with a lock held
|
|
||||||
/**
|
|
||||||
* A {@link org.briarproject.api.transport.TagRecogniser TagRecogniser} for a
|
|
||||||
* specific transport.
|
|
||||||
*/
|
|
||||||
class TransportTagRecogniser {
|
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
|
||||||
private final DatabaseComponent db;
|
|
||||||
private final TransportId transportId;
|
|
||||||
private final Lock lock = new ReentrantLock();
|
|
||||||
|
|
||||||
// The following are locking: lock
|
|
||||||
private final Map<Bytes, TagContext> tagMap;
|
|
||||||
private final Map<RemovalKey, RemovalContext> removalMap;
|
|
||||||
|
|
||||||
TransportTagRecogniser(CryptoComponent crypto, DatabaseComponent db,
|
|
||||||
TransportId transportId) {
|
|
||||||
this.crypto = crypto;
|
|
||||||
this.db = db;
|
|
||||||
this.transportId = transportId;
|
|
||||||
tagMap = new HashMap<Bytes, TagContext>();
|
|
||||||
removalMap = new HashMap<RemovalKey, RemovalContext>();
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamContext recogniseTag(byte[] tag) throws DbException {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
TagContext t = tagMap.remove(new Bytes(tag));
|
|
||||||
if (t == null) return null; // The tag was not expected
|
|
||||||
// Update the reordering window and the expected tags
|
|
||||||
SecretKey key = crypto.deriveTagKey(t.secret, !t.alice);
|
|
||||||
for (long streamNumber : t.window.setSeen(t.streamNumber)) {
|
|
||||||
byte[] tag1 = new byte[TAG_LENGTH];
|
|
||||||
crypto.encodeTag(tag1, key, streamNumber);
|
|
||||||
if (streamNumber < t.streamNumber) {
|
|
||||||
TagContext removed = tagMap.remove(new Bytes(tag1));
|
|
||||||
assert removed != null;
|
|
||||||
} else {
|
|
||||||
TagContext added = new TagContext(t, streamNumber);
|
|
||||||
TagContext duplicate = tagMap.put(new Bytes(tag1), added);
|
|
||||||
assert duplicate == null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Store the updated reordering window in the DB
|
|
||||||
db.setReorderingWindow(t.contactId, transportId, t.period,
|
|
||||||
t.window.getCentre(), t.window.getBitmap());
|
|
||||||
return new StreamContext(t.contactId, transportId, t.secret,
|
|
||||||
t.streamNumber, t.alice);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void addSecret(TemporarySecret s) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
ContactId contactId = s.getContactId();
|
|
||||||
boolean alice = s.getAlice();
|
|
||||||
long period = s.getPeriod();
|
|
||||||
byte[] secret = s.getSecret();
|
|
||||||
long centre = s.getWindowCentre();
|
|
||||||
byte[] bitmap = s.getWindowBitmap();
|
|
||||||
// Create the reordering window and the expected tags
|
|
||||||
SecretKey key = crypto.deriveTagKey(secret, !alice);
|
|
||||||
ReorderingWindow window = new ReorderingWindow(centre, bitmap);
|
|
||||||
for (long streamNumber : window.getUnseen()) {
|
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
|
||||||
crypto.encodeTag(tag, key, streamNumber);
|
|
||||||
TagContext added = new TagContext(contactId, alice, period,
|
|
||||||
secret, window, streamNumber);
|
|
||||||
TagContext duplicate = tagMap.put(new Bytes(tag), added);
|
|
||||||
assert duplicate == null;
|
|
||||||
}
|
|
||||||
// Create a removal context to remove the window and the tags later
|
|
||||||
RemovalContext r = new RemovalContext(window, secret, alice);
|
|
||||||
removalMap.put(new RemovalKey(contactId, period), r);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeSecret(ContactId contactId, long period) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
RemovalKey k = new RemovalKey(contactId, period);
|
|
||||||
RemovalContext removed = removalMap.remove(k);
|
|
||||||
if (removed == null) throw new IllegalArgumentException();
|
|
||||||
removeSecret(removed);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Locking: lock
|
|
||||||
private void removeSecret(RemovalContext r) {
|
|
||||||
// Remove the expected tags
|
|
||||||
SecretKey key = crypto.deriveTagKey(r.secret, !r.alice);
|
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
|
||||||
for (long streamNumber : r.window.getUnseen()) {
|
|
||||||
crypto.encodeTag(tag, key, streamNumber);
|
|
||||||
TagContext removed = tagMap.remove(new Bytes(tag));
|
|
||||||
assert removed != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeSecrets(ContactId c) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
Collection<RemovalKey> keysToRemove = new ArrayList<RemovalKey>();
|
|
||||||
for (RemovalKey k : removalMap.keySet())
|
|
||||||
if (k.contactId.equals(c)) keysToRemove.add(k);
|
|
||||||
for (RemovalKey k : keysToRemove)
|
|
||||||
removeSecret(k.contactId, k.period);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeSecrets() {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
for (RemovalContext r : removalMap.values()) removeSecret(r);
|
|
||||||
assert tagMap.isEmpty();
|
|
||||||
removalMap.clear();
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class TagContext {
|
|
||||||
|
|
||||||
private final ContactId contactId;
|
|
||||||
private final boolean alice;
|
|
||||||
private final long period;
|
|
||||||
private final byte[] secret;
|
|
||||||
private final ReorderingWindow window;
|
|
||||||
private final long streamNumber;
|
|
||||||
|
|
||||||
private TagContext(ContactId contactId, boolean alice, long period,
|
|
||||||
byte[] secret, ReorderingWindow window, long streamNumber) {
|
|
||||||
this.contactId = contactId;
|
|
||||||
this.alice = alice;
|
|
||||||
this.period = period;
|
|
||||||
this.secret = secret;
|
|
||||||
this.window = window;
|
|
||||||
this.streamNumber = streamNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TagContext(TagContext t, long streamNumber) {
|
|
||||||
this(t.contactId, t.alice, t.period, t.secret, t.window,
|
|
||||||
streamNumber);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class RemovalKey {
|
|
||||||
|
|
||||||
private final ContactId contactId;
|
|
||||||
private final long period;
|
|
||||||
|
|
||||||
private RemovalKey(ContactId contactId, long period) {
|
|
||||||
this.contactId = contactId;
|
|
||||||
this.period = period;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return contactId.hashCode() ^ (int) (period ^ (period >>> 32));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (o instanceof RemovalKey) {
|
|
||||||
RemovalKey k = (RemovalKey) o;
|
|
||||||
return contactId.equals(k.contactId) && period == k.period;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class RemovalContext {
|
|
||||||
|
|
||||||
private final ReorderingWindow window;
|
|
||||||
private final byte[] secret;
|
|
||||||
private final boolean alice;
|
|
||||||
|
|
||||||
private RemovalContext(ReorderingWindow window, byte[] secret,
|
|
||||||
boolean alice) {
|
|
||||||
this.window = window;
|
|
||||||
this.secret = secret;
|
|
||||||
this.alice = alice;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,6 +10,7 @@ import org.briarproject.api.TransportId;
|
|||||||
import org.briarproject.api.TransportProperties;
|
import org.briarproject.api.TransportProperties;
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.api.crypto.KeyPair;
|
import org.briarproject.api.crypto.KeyPair;
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
import org.briarproject.api.messaging.Ack;
|
import org.briarproject.api.messaging.Ack;
|
||||||
import org.briarproject.api.messaging.Group;
|
import org.briarproject.api.messaging.Group;
|
||||||
import org.briarproject.api.messaging.GroupFactory;
|
import org.briarproject.api.messaging.GroupFactory;
|
||||||
@@ -44,7 +45,6 @@ import java.io.OutputStream;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
@@ -61,14 +61,9 @@ public class ProtocolIntegrationTest extends BriarTestCase {
|
|||||||
private final MessageVerifier messageVerifier;
|
private final MessageVerifier messageVerifier;
|
||||||
|
|
||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final byte[] secret;
|
private final SecretKey tagKey, headerKey;
|
||||||
private final Author author;
|
|
||||||
private final Group group;
|
private final Group group;
|
||||||
private final Message message, message1;
|
private final Message message, message1;
|
||||||
private final String authorName = "Alice";
|
|
||||||
private final String contentType = "text/plain";
|
|
||||||
private final long timestamp = System.currentTimeMillis();
|
|
||||||
private final String messageBody = "Hello world";
|
|
||||||
private final Collection<MessageId> messageIds;
|
private final Collection<MessageId> messageIds;
|
||||||
private final TransportId transportId;
|
private final TransportId transportId;
|
||||||
private final TransportProperties transportProperties;
|
private final TransportProperties transportProperties;
|
||||||
@@ -85,9 +80,9 @@ public class ProtocolIntegrationTest extends BriarTestCase {
|
|||||||
packetWriterFactory = i.getInstance(PacketWriterFactory.class);
|
packetWriterFactory = i.getInstance(PacketWriterFactory.class);
|
||||||
messageVerifier = i.getInstance(MessageVerifier.class);
|
messageVerifier = i.getInstance(MessageVerifier.class);
|
||||||
contactId = new ContactId(234);
|
contactId = new ContactId(234);
|
||||||
// Create a shared secret
|
// Create the transport keys
|
||||||
secret = new byte[32];
|
tagKey = TestUtils.createSecretKey();
|
||||||
new Random().nextBytes(secret);
|
headerKey = TestUtils.createSecretKey();
|
||||||
// Create a group
|
// Create a group
|
||||||
GroupFactory groupFactory = i.getInstance(GroupFactory.class);
|
GroupFactory groupFactory = i.getInstance(GroupFactory.class);
|
||||||
group = groupFactory.createGroup("Group");
|
group = groupFactory.createGroup("Group");
|
||||||
@@ -95,12 +90,15 @@ public class ProtocolIntegrationTest extends BriarTestCase {
|
|||||||
AuthorFactory authorFactory = i.getInstance(AuthorFactory.class);
|
AuthorFactory authorFactory = i.getInstance(AuthorFactory.class);
|
||||||
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
|
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
|
||||||
KeyPair authorKeyPair = crypto.generateSignatureKeyPair();
|
KeyPair authorKeyPair = crypto.generateSignatureKeyPair();
|
||||||
author = authorFactory.createAuthor(authorName,
|
Author author = authorFactory.createAuthor("Alice",
|
||||||
authorKeyPair.getPublic().getEncoded());
|
authorKeyPair.getPublic().getEncoded());
|
||||||
// Create two messages to the group: one anonymous, one pseudonymous
|
// Create two messages to the group: one anonymous, one pseudonymous
|
||||||
MessageFactory messageFactory = i.getInstance(MessageFactory.class);
|
MessageFactory messageFactory = i.getInstance(MessageFactory.class);
|
||||||
|
String contentType = "text/plain";
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
String messageBody = "Hello world";
|
||||||
message = messageFactory.createAnonymousMessage(null, group,
|
message = messageFactory.createAnonymousMessage(null, group,
|
||||||
contentType, timestamp, messageBody.getBytes("UTF-8"));
|
"text/plain", timestamp, messageBody.getBytes("UTF-8"));
|
||||||
message1 = messageFactory.createPseudonymousMessage(null, group,
|
message1 = messageFactory.createPseudonymousMessage(null, group,
|
||||||
author, authorKeyPair.getPrivate(), contentType, timestamp,
|
author, authorKeyPair.getPrivate(), contentType, timestamp,
|
||||||
messageBody.getBytes("UTF-8"));
|
messageBody.getBytes("UTF-8"));
|
||||||
@@ -118,8 +116,8 @@ public class ProtocolIntegrationTest extends BriarTestCase {
|
|||||||
|
|
||||||
private byte[] write() throws Exception {
|
private byte[] write() throws Exception {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
StreamContext ctx = new StreamContext(contactId, transportId, secret,
|
StreamContext ctx = new StreamContext(contactId, transportId, tagKey,
|
||||||
0, true);
|
headerKey, 0);
|
||||||
OutputStream streamWriter =
|
OutputStream streamWriter =
|
||||||
streamWriterFactory.createStreamWriter(out, ctx);
|
streamWriterFactory.createStreamWriter(out, ctx);
|
||||||
PacketWriter packetWriter = packetWriterFactory.createPacketWriter(
|
PacketWriter packetWriter = packetWriterFactory.createPacketWriter(
|
||||||
@@ -134,7 +132,8 @@ public class ProtocolIntegrationTest extends BriarTestCase {
|
|||||||
|
|
||||||
packetWriter.writeRequest(new Request(messageIds));
|
packetWriter.writeRequest(new Request(messageIds));
|
||||||
|
|
||||||
SubscriptionUpdate su = new SubscriptionUpdate(Arrays.asList(group), 1);
|
SubscriptionUpdate su = new SubscriptionUpdate(
|
||||||
|
Collections.singletonList(group), 1);
|
||||||
packetWriter.writeSubscriptionUpdate(su);
|
packetWriter.writeSubscriptionUpdate(su);
|
||||||
|
|
||||||
TransportUpdate tu = new TransportUpdate(transportId,
|
TransportUpdate tu = new TransportUpdate(transportId,
|
||||||
@@ -150,8 +149,8 @@ public class ProtocolIntegrationTest extends BriarTestCase {
|
|||||||
byte[] tag = new byte[TAG_LENGTH];
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
assertEquals(TAG_LENGTH, in.read(tag, 0, TAG_LENGTH));
|
assertEquals(TAG_LENGTH, in.read(tag, 0, TAG_LENGTH));
|
||||||
// FIXME: Check that the expected tag was received
|
// FIXME: Check that the expected tag was received
|
||||||
StreamContext ctx = new StreamContext(contactId, transportId, secret,
|
StreamContext ctx = new StreamContext(contactId, transportId, tagKey,
|
||||||
0, false);
|
headerKey, 0);
|
||||||
InputStream streamReader =
|
InputStream streamReader =
|
||||||
streamReaderFactory.createStreamReader(in, ctx);
|
streamReaderFactory.createStreamReader(in, ctx);
|
||||||
PacketReader packetReader = packetReaderFactory.createPacketReader(
|
PacketReader packetReader = packetReaderFactory.createPacketReader(
|
||||||
@@ -184,7 +183,7 @@ public class ProtocolIntegrationTest extends BriarTestCase {
|
|||||||
// Read the subscription update
|
// Read the subscription update
|
||||||
assertTrue(packetReader.hasSubscriptionUpdate());
|
assertTrue(packetReader.hasSubscriptionUpdate());
|
||||||
SubscriptionUpdate su = packetReader.readSubscriptionUpdate();
|
SubscriptionUpdate su = packetReader.readSubscriptionUpdate();
|
||||||
assertEquals(Arrays.asList(group), su.getGroups());
|
assertEquals(Collections.singletonList(group), su.getGroups());
|
||||||
assertEquals(1, su.getVersion());
|
assertEquals(1, su.getVersion());
|
||||||
|
|
||||||
// Read the transport update
|
// Read the transport update
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import java.io.File;
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.briarproject.api.UniqueId;
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
|
||||||
public class TestUtils {
|
public class TestUtils {
|
||||||
|
|
||||||
private static final AtomicInteger nextTestDir =
|
private static final AtomicInteger nextTestDir =
|
||||||
@@ -35,4 +38,10 @@ public class TestUtils {
|
|||||||
c[i] = (char) ('a' + random.nextInt(26));
|
c[i] = (char) ('a' + random.nextInt(26));
|
||||||
return new String(c);
|
return new String(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SecretKey createSecretKey() {
|
||||||
|
byte[] b = new byte[SecretKey.LENGTH];
|
||||||
|
random.nextBytes(b);
|
||||||
|
return new SecretKey(b);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.briarproject.BriarTestCase;
|
|||||||
import org.briarproject.TestSeedProvider;
|
import org.briarproject.TestSeedProvider;
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.api.crypto.KeyPair;
|
import org.briarproject.api.crypto.KeyPair;
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
import org.briarproject.api.system.SeedProvider;
|
import org.briarproject.api.system.SeedProvider;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -19,8 +20,8 @@ public class KeyAgreementTest extends BriarTestCase {
|
|||||||
byte[] aPub = aPair.getPublic().getEncoded();
|
byte[] aPub = aPair.getPublic().getEncoded();
|
||||||
KeyPair bPair = crypto.generateAgreementKeyPair();
|
KeyPair bPair = crypto.generateAgreementKeyPair();
|
||||||
byte[] bPub = bPair.getPublic().getEncoded();
|
byte[] bPub = bPair.getPublic().getEncoded();
|
||||||
byte[] aSecret = crypto.deriveMasterSecret(aPub, bPair, true);
|
SecretKey aMaster = crypto.deriveMasterSecret(aPub, bPair, true);
|
||||||
byte[] bSecret = crypto.deriveMasterSecret(bPub, aPair, false);
|
SecretKey bMaster = crypto.deriveMasterSecret(bPub, aPair, false);
|
||||||
assertArrayEquals(aSecret, bSecret);
|
assertArrayEquals(aMaster.getBytes(), bMaster.getBytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,72 +2,164 @@ package org.briarproject.crypto;
|
|||||||
|
|
||||||
import org.briarproject.BriarTestCase;
|
import org.briarproject.BriarTestCase;
|
||||||
import org.briarproject.TestSeedProvider;
|
import org.briarproject.TestSeedProvider;
|
||||||
|
import org.briarproject.TestUtils;
|
||||||
|
import org.briarproject.api.TransportId;
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.api.crypto.SecretKey;
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.api.transport.TransportKeys;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
public class KeyDerivationTest extends BriarTestCase {
|
public class KeyDerivationTest extends BriarTestCase {
|
||||||
|
|
||||||
|
private final TransportId transportId = new TransportId("id");
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
private final byte[] secret;
|
private final SecretKey master;
|
||||||
|
|
||||||
public KeyDerivationTest() {
|
public KeyDerivationTest() {
|
||||||
crypto = new CryptoComponentImpl(new TestSeedProvider());
|
crypto = new CryptoComponentImpl(new TestSeedProvider());
|
||||||
secret = new byte[32];
|
master = TestUtils.createSecretKey();
|
||||||
new Random().nextBytes(secret);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeysAreDistinct() {
|
public void testKeysAreDistinct() {
|
||||||
List<SecretKey> keys = new ArrayList<SecretKey>();
|
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
|
||||||
keys.add(crypto.deriveFrameKey(secret, 0, true));
|
123, true);
|
||||||
keys.add(crypto.deriveFrameKey(secret, 0, false));
|
assertAllDifferent(k);
|
||||||
keys.add(crypto.deriveTagKey(secret, true));
|
|
||||||
keys.add(crypto.deriveTagKey(secret, false));
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
byte[] keyI = keys.get(i).getBytes();
|
|
||||||
for (int j = 0; j < 4; j++) {
|
|
||||||
byte[] keyJ = keys.get(j).getBytes();
|
|
||||||
assertEquals(i == j, Arrays.equals(keyI, keyJ));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSecretAffectsDerivation() {
|
public void testCurrentKeysMatchCurrentKeysOfContact() {
|
||||||
Random r = new Random();
|
// Start in rotation period 123
|
||||||
List<byte[]> secrets = new ArrayList<byte[]>();
|
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
|
||||||
for (int i = 0; i < 20; i++) {
|
123, true);
|
||||||
byte[] b = new byte[32];
|
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
|
||||||
r.nextBytes(b);
|
123, false);
|
||||||
secrets.add(crypto.deriveNextSecret(b, 0));
|
// Alice's incoming keys should equal Bob's outgoing keys
|
||||||
}
|
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
|
||||||
for (int i = 0; i < 20; i++) {
|
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||||
byte[] secretI = secrets.get(i);
|
assertArrayEquals(kA.getCurrentIncomingKeys().getHeaderKey().getBytes(),
|
||||||
for (int j = 0; j < 20; j++) {
|
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
|
||||||
byte[] secretJ = secrets.get(j);
|
// Alice's outgoing keys should equal Bob's incoming keys
|
||||||
assertEquals(i == j, Arrays.equals(secretI, secretJ));
|
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
|
||||||
}
|
kB.getCurrentIncomingKeys().getTagKey().getBytes());
|
||||||
}
|
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
|
||||||
|
kB.getCurrentIncomingKeys().getHeaderKey().getBytes());
|
||||||
|
// Rotate into the future
|
||||||
|
kA = crypto.rotateTransportKeys(kA, 456);
|
||||||
|
kB = crypto.rotateTransportKeys(kB, 456);
|
||||||
|
// Alice's incoming keys should equal Bob's outgoing keys
|
||||||
|
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
|
||||||
|
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||||
|
assertArrayEquals(kA.getCurrentIncomingKeys().getHeaderKey().getBytes(),
|
||||||
|
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
|
||||||
|
// Alice's outgoing keys should equal Bob's incoming keys
|
||||||
|
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
|
||||||
|
kB.getCurrentIncomingKeys().getTagKey().getBytes());
|
||||||
|
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
|
||||||
|
kB.getCurrentIncomingKeys().getHeaderKey().getBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStreamNumberAffectsDerivation() {
|
public void testPreviousKeysMatchPreviousKeysOfContact() {
|
||||||
List<byte[]> secrets = new ArrayList<byte[]>();
|
// Start in rotation period 123
|
||||||
for (int i = 0; i < 20; i++)
|
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
|
||||||
secrets.add(crypto.deriveNextSecret(secret, i));
|
123, true);
|
||||||
for (int i = 0; i < 20; i++) {
|
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
|
||||||
byte[] secretI = secrets.get(i);
|
123, false);
|
||||||
for (int j = 0; j < 20; j++) {
|
// Compare Alice's previous keys in period 456 with Bob's current keys
|
||||||
byte[] secretJ = secrets.get(j);
|
// in period 455
|
||||||
assertEquals(i == j, Arrays.equals(secretI, secretJ));
|
kA = crypto.rotateTransportKeys(kA, 456);
|
||||||
|
kB = crypto.rotateTransportKeys(kB, 455);
|
||||||
|
// Alice's previous incoming keys should equal Bob's outgoing keys
|
||||||
|
assertArrayEquals(kA.getPreviousIncomingKeys().getTagKey().getBytes(),
|
||||||
|
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||||
|
assertArrayEquals(kA.getPreviousIncomingKeys().getHeaderKey().getBytes(),
|
||||||
|
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
|
||||||
|
// Compare Alice's current keys in period 456 with Bob's previous keys
|
||||||
|
// in period 457
|
||||||
|
kB = crypto.rotateTransportKeys(kB, 457);
|
||||||
|
// Alice's outgoing keys should equal Bob's previous incoming keys
|
||||||
|
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
|
||||||
|
kB.getPreviousIncomingKeys().getTagKey().getBytes());
|
||||||
|
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
|
||||||
|
kB.getPreviousIncomingKeys().getHeaderKey().getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNextKeysMatchNextKeysOfContact() {
|
||||||
|
// Start in rotation period 123
|
||||||
|
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
|
||||||
|
123, true);
|
||||||
|
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
|
||||||
|
123, false);
|
||||||
|
// Compare Alice's current keys in period 456 with Bob's next keys in
|
||||||
|
// period 455
|
||||||
|
kA = crypto.rotateTransportKeys(kA, 456);
|
||||||
|
kB = crypto.rotateTransportKeys(kB, 455);
|
||||||
|
// Alice's outgoing keys should equal Bob's next incoming keys
|
||||||
|
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
|
||||||
|
kB.getNextIncomingKeys().getTagKey().getBytes());
|
||||||
|
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
|
||||||
|
kB.getNextIncomingKeys().getHeaderKey().getBytes());
|
||||||
|
// Compare Alice's next keys in period 456 with Bob's current keys
|
||||||
|
// in period 457
|
||||||
|
kB = crypto.rotateTransportKeys(kB, 457);
|
||||||
|
// Alice's next incoming keys should equal Bob's outgoing keys
|
||||||
|
assertArrayEquals(kA.getNextIncomingKeys().getTagKey().getBytes(),
|
||||||
|
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||||
|
assertArrayEquals(kA.getNextIncomingKeys().getHeaderKey().getBytes(),
|
||||||
|
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMasterKeyAffectsOutput() {
|
||||||
|
SecretKey master1 = TestUtils.createSecretKey();
|
||||||
|
assertFalse(Arrays.equals(master.getBytes(), master1.getBytes()));
|
||||||
|
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
|
||||||
|
123, true);
|
||||||
|
TransportKeys k1 = crypto.deriveTransportKeys(transportId, master1,
|
||||||
|
123, true);
|
||||||
|
assertAllDifferent(k, k1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransportIdAffectsOutput() {
|
||||||
|
TransportId transportId1 = new TransportId("id1");
|
||||||
|
assertFalse(transportId.getString().equals(transportId1.getString()));
|
||||||
|
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
|
||||||
|
123, true);
|
||||||
|
TransportKeys k1 = crypto.deriveTransportKeys(transportId1, master,
|
||||||
|
123, true);
|
||||||
|
assertAllDifferent(k, k1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertAllDifferent(TransportKeys... transportKeys) {
|
||||||
|
List<SecretKey> secretKeys = new ArrayList<SecretKey>();
|
||||||
|
for (TransportKeys k : transportKeys) {
|
||||||
|
secretKeys.add(k.getPreviousIncomingKeys().getTagKey());
|
||||||
|
secretKeys.add(k.getPreviousIncomingKeys().getHeaderKey());
|
||||||
|
secretKeys.add(k.getCurrentIncomingKeys().getTagKey());
|
||||||
|
secretKeys.add(k.getCurrentIncomingKeys().getHeaderKey());
|
||||||
|
secretKeys.add(k.getNextIncomingKeys().getTagKey());
|
||||||
|
secretKeys.add(k.getNextIncomingKeys().getHeaderKey());
|
||||||
|
secretKeys.add(k.getCurrentOutgoingKeys().getTagKey());
|
||||||
|
secretKeys.add(k.getCurrentOutgoingKeys().getHeaderKey());
|
||||||
|
}
|
||||||
|
assertAllDifferent(secretKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertAllDifferent(List<SecretKey> keys) {
|
||||||
|
for (SecretKey ki : keys) {
|
||||||
|
for (SecretKey kj : keys) {
|
||||||
|
if (ki == kj) assertArrayEquals(ki.getBytes(), kj.getBytes());
|
||||||
|
else assertFalse(Arrays.equals(ki.getBytes(), kj.getBytes()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.briarproject.api.LocalAuthor;
|
|||||||
import org.briarproject.api.TransportConfig;
|
import org.briarproject.api.TransportConfig;
|
||||||
import org.briarproject.api.TransportId;
|
import org.briarproject.api.TransportId;
|
||||||
import org.briarproject.api.TransportProperties;
|
import org.briarproject.api.TransportProperties;
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
import org.briarproject.api.db.DatabaseComponent;
|
||||||
import org.briarproject.api.db.NoSuchContactException;
|
import org.briarproject.api.db.NoSuchContactException;
|
||||||
import org.briarproject.api.db.NoSuchLocalAuthorException;
|
import org.briarproject.api.db.NoSuchLocalAuthorException;
|
||||||
@@ -45,8 +46,9 @@ import org.briarproject.api.messaging.SubscriptionAck;
|
|||||||
import org.briarproject.api.messaging.SubscriptionUpdate;
|
import org.briarproject.api.messaging.SubscriptionUpdate;
|
||||||
import org.briarproject.api.messaging.TransportAck;
|
import org.briarproject.api.messaging.TransportAck;
|
||||||
import org.briarproject.api.messaging.TransportUpdate;
|
import org.briarproject.api.messaging.TransportUpdate;
|
||||||
import org.briarproject.api.transport.Endpoint;
|
import org.briarproject.api.transport.IncomingKeys;
|
||||||
import org.briarproject.api.transport.TemporarySecret;
|
import org.briarproject.api.transport.OutgoingKeys;
|
||||||
|
import org.briarproject.api.transport.TransportKeys;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.Mockery;
|
import org.jmock.Mockery;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -84,8 +86,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
protected final int maxLatency;
|
protected final int maxLatency;
|
||||||
protected final ContactId contactId;
|
protected final ContactId contactId;
|
||||||
protected final Contact contact;
|
protected final Contact contact;
|
||||||
protected final Endpoint endpoint;
|
|
||||||
protected final TemporarySecret temporarySecret;
|
|
||||||
|
|
||||||
public DatabaseComponentTest() {
|
public DatabaseComponentTest() {
|
||||||
groupId = new GroupId(TestUtils.getRandomId());
|
groupId = new GroupId(TestUtils.getRandomId());
|
||||||
@@ -112,9 +112,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
maxLatency = Integer.MAX_VALUE;
|
maxLatency = Integer.MAX_VALUE;
|
||||||
contactId = new ContactId(234);
|
contactId = new ContactId(234);
|
||||||
contact = new Contact(contactId, author, localAuthorId);
|
contact = new Contact(contactId, author, localAuthorId);
|
||||||
endpoint = new Endpoint(contactId, transportId, 123, true);
|
|
||||||
temporarySecret = new TemporarySecret(contactId, transportId, 123,
|
|
||||||
false, 234, new byte[32], 345, 456, new byte[4]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract <T> DatabaseComponent createDatabaseComponent(
|
protected abstract <T> DatabaseComponent createDatabaseComponent(
|
||||||
@@ -157,7 +154,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
|
oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
|
||||||
// getContacts()
|
// getContacts()
|
||||||
oneOf(database).getContacts(txn);
|
oneOf(database).getContacts(txn);
|
||||||
will(returnValue(Arrays.asList(contact)));
|
will(returnValue(Collections.singletonList(contact)));
|
||||||
// getRemoteProperties()
|
// getRemoteProperties()
|
||||||
oneOf(database).getRemoteProperties(txn, transportId);
|
oneOf(database).getRemoteProperties(txn, transportId);
|
||||||
will(returnValue(Collections.emptyMap()));
|
will(returnValue(Collections.emptyMap()));
|
||||||
@@ -177,7 +174,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
will(returnValue(Collections.emptyList()));
|
will(returnValue(Collections.emptyList()));
|
||||||
// getGroups()
|
// getGroups()
|
||||||
oneOf(database).getGroups(txn);
|
oneOf(database).getGroups(txn);
|
||||||
will(returnValue(Arrays.asList(groupId)));
|
will(returnValue(Collections.singletonList(group)));
|
||||||
// removeGroup()
|
// removeGroup()
|
||||||
oneOf(database).containsGroup(txn, groupId);
|
oneOf(database).containsGroup(txn, groupId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
@@ -213,13 +210,13 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
assertFalse(db.open());
|
assertFalse(db.open());
|
||||||
db.addLocalAuthor(localAuthor);
|
db.addLocalAuthor(localAuthor);
|
||||||
assertEquals(contactId, db.addContact(author, localAuthorId));
|
assertEquals(contactId, db.addContact(author, localAuthorId));
|
||||||
assertEquals(Arrays.asList(contact), db.getContacts());
|
assertEquals(Collections.singletonList(contact), db.getContacts());
|
||||||
assertEquals(Collections.emptyMap(),
|
assertEquals(Collections.emptyMap(),
|
||||||
db.getRemoteProperties(transportId));
|
db.getRemoteProperties(transportId));
|
||||||
db.addGroup(group); // First time - listeners called
|
db.addGroup(group); // First time - listeners called
|
||||||
db.addGroup(group); // Second time - not called
|
db.addGroup(group); // Second time - not called
|
||||||
assertEquals(Collections.emptyList(), db.getMessageHeaders(groupId));
|
assertEquals(Collections.emptyList(), db.getMessageHeaders(groupId));
|
||||||
assertEquals(Arrays.asList(groupId), db.getGroups());
|
assertEquals(Collections.singletonList(group), db.getGroups());
|
||||||
db.removeGroup(group);
|
db.removeGroup(group);
|
||||||
db.removeContact(contactId);
|
db.removeContact(contactId);
|
||||||
db.removeLocalAuthor(localAuthorId);
|
db.removeLocalAuthor(localAuthorId);
|
||||||
@@ -297,9 +294,9 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
oneOf(database).addMessage(txn, message, true);
|
oneOf(database).addMessage(txn, message, true);
|
||||||
oneOf(database).setReadFlag(txn, messageId, true);
|
oneOf(database).setReadFlag(txn, messageId, true);
|
||||||
oneOf(database).getVisibility(txn, groupId);
|
oneOf(database).getVisibility(txn, groupId);
|
||||||
will(returnValue(Arrays.asList(contactId)));
|
will(returnValue(Collections.singletonList(contactId)));
|
||||||
oneOf(database).getContactIds(txn);
|
oneOf(database).getContactIds(txn);
|
||||||
will(returnValue(Arrays.asList(contactId)));
|
will(returnValue(Collections.singletonList(contactId)));
|
||||||
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(database).addStatus(txn, contactId, messageId, false, false);
|
oneOf(database).addStatus(txn, contactId, messageId, false, false);
|
||||||
@@ -336,7 +333,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
eventBus, shutdown);
|
eventBus, shutdown);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.addEndpoint(endpoint);
|
db.addTransportKeys(contactId, createTransportKeys());
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchContactException expected) {}
|
} catch (NoSuchContactException expected) {}
|
||||||
|
|
||||||
@@ -401,7 +398,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
} catch (NoSuchContactException expected) {}
|
} catch (NoSuchContactException expected) {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Ack a = new Ack(Arrays.asList(messageId));
|
Ack a = new Ack(Collections.singletonList(messageId));
|
||||||
db.receiveAck(contactId, a);
|
db.receiveAck(contactId, a);
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchContactException expected) {}
|
} catch (NoSuchContactException expected) {}
|
||||||
@@ -412,7 +409,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
} catch (NoSuchContactException expected) {}
|
} catch (NoSuchContactException expected) {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Offer o = new Offer(Arrays.asList(messageId));
|
Offer o = new Offer(Collections.singletonList(messageId));
|
||||||
db.receiveOffer(contactId, o);
|
db.receiveOffer(contactId, o);
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchContactException expected) {}
|
} catch (NoSuchContactException expected) {}
|
||||||
@@ -594,7 +591,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
// Check whether the transport is in the DB (which it's not)
|
// Check whether the transport is in the DB (which it's not)
|
||||||
exactly(8).of(database).startTransaction();
|
exactly(8).of(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
exactly(3).of(database).containsContact(txn, contactId);
|
exactly(2).of(database).containsContact(txn, contactId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
exactly(8).of(database).containsTransport(txn, transportId);
|
exactly(8).of(database).containsTransport(txn, transportId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
@@ -606,11 +603,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
db.addLocalAuthor(localAuthor);
|
db.addLocalAuthor(localAuthor);
|
||||||
assertEquals(contactId, db.addContact(author, localAuthorId));
|
assertEquals(contactId, db.addContact(author, localAuthorId));
|
||||||
|
|
||||||
try {
|
|
||||||
db.addEndpoint(endpoint);
|
|
||||||
fail();
|
|
||||||
} catch (NoSuchTransportException expected) {}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.getConfig(transportId);
|
db.getConfig(transportId);
|
||||||
fail();
|
fail();
|
||||||
@@ -621,6 +613,11 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
fail();
|
fail();
|
||||||
} catch (NoSuchTransportException expected) {}
|
} catch (NoSuchTransportException expected) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.getTransportKeys(transportId);
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchTransportException expected) {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.mergeConfig(transportId, new TransportConfig());
|
db.mergeConfig(transportId, new TransportConfig());
|
||||||
fail();
|
fail();
|
||||||
@@ -909,7 +906,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
oneOf(database).containsContact(txn, contactId);
|
oneOf(database).containsContact(txn, contactId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(database).getSubscriptionUpdate(txn, contactId, maxLatency);
|
oneOf(database).getSubscriptionUpdate(txn, contactId, maxLatency);
|
||||||
will(returnValue(new SubscriptionUpdate(Arrays.asList(group), 1)));
|
will(returnValue(new SubscriptionUpdate(
|
||||||
|
Collections.singletonList(group), 1)));
|
||||||
oneOf(database).commitTransaction(txn);
|
oneOf(database).commitTransaction(txn);
|
||||||
}});
|
}});
|
||||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||||
@@ -917,7 +915,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
|
|
||||||
SubscriptionUpdate u = db.generateSubscriptionUpdate(contactId,
|
SubscriptionUpdate u = db.generateSubscriptionUpdate(contactId,
|
||||||
maxLatency);
|
maxLatency);
|
||||||
assertEquals(Arrays.asList(group), u.getGroups());
|
assertEquals(Collections.singletonList(group), u.getGroups());
|
||||||
assertEquals(1, u.getVersion());
|
assertEquals(1, u.getVersion());
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
@@ -962,8 +960,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
oneOf(database).containsContact(txn, contactId);
|
oneOf(database).containsContact(txn, contactId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(database).getTransportUpdates(txn, contactId, maxLatency);
|
oneOf(database).getTransportUpdates(txn, contactId, maxLatency);
|
||||||
will(returnValue(Arrays.asList(new TransportUpdate(transportId,
|
will(returnValue(Collections.singletonList(new TransportUpdate(
|
||||||
transportProperties, 1))));
|
transportId, transportProperties, 1))));
|
||||||
oneOf(database).commitTransaction(txn);
|
oneOf(database).commitTransaction(txn);
|
||||||
}});
|
}});
|
||||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||||
@@ -1003,7 +1001,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||||
eventBus, shutdown);
|
eventBus, shutdown);
|
||||||
|
|
||||||
db.receiveAck(contactId, new Ack(Arrays.asList(messageId)));
|
db.receiveAck(contactId, new Ack(Collections.singletonList(messageId)));
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
@@ -1027,9 +1025,9 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(database).addMessage(txn, message, false);
|
oneOf(database).addMessage(txn, message, false);
|
||||||
oneOf(database).getVisibility(txn, groupId);
|
oneOf(database).getVisibility(txn, groupId);
|
||||||
will(returnValue(Arrays.asList(contactId)));
|
will(returnValue(Collections.singletonList(contactId)));
|
||||||
oneOf(database).getContactIds(txn);
|
oneOf(database).getContactIds(txn);
|
||||||
will(returnValue(Arrays.asList(contactId)));
|
will(returnValue(Collections.singletonList(contactId)));
|
||||||
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(database).addStatus(txn, contactId, messageId, false, true);
|
oneOf(database).addStatus(txn, contactId, messageId, false, true);
|
||||||
@@ -1176,7 +1174,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||||
eventBus, shutdown);
|
eventBus, shutdown);
|
||||||
|
|
||||||
db.receiveRequest(contactId, new Request(Arrays.asList(messageId)));
|
db.receiveRequest(contactId, new Request(Collections.singletonList(
|
||||||
|
messageId)));
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
@@ -1244,13 +1243,15 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
oneOf(database).containsContact(txn, contactId);
|
oneOf(database).containsContact(txn, contactId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(database).setGroups(txn, contactId, Arrays.asList(group), 1);
|
oneOf(database).setGroups(txn, contactId,
|
||||||
|
Collections.singletonList(group), 1);
|
||||||
oneOf(database).commitTransaction(txn);
|
oneOf(database).commitTransaction(txn);
|
||||||
}});
|
}});
|
||||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||||
eventBus, shutdown);
|
eventBus, shutdown);
|
||||||
|
|
||||||
SubscriptionUpdate u = new SubscriptionUpdate(Arrays.asList(group), 1);
|
SubscriptionUpdate u = new SubscriptionUpdate(
|
||||||
|
Collections.singletonList(group), 1);
|
||||||
db.receiveSubscriptionUpdate(contactId, u);
|
db.receiveSubscriptionUpdate(contactId, u);
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
@@ -1398,7 +1399,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||||
eventBus, shutdown);
|
eventBus, shutdown);
|
||||||
|
|
||||||
db.setVisibility(groupId, Arrays.asList(contactId));
|
db.setVisibility(groupId, Collections.singletonList(contactId));
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
@@ -1467,7 +1468,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(database).setVisibleToAll(txn, groupId, true);
|
oneOf(database).setVisibleToAll(txn, groupId, true);
|
||||||
oneOf(database).getVisibility(txn, groupId);
|
oneOf(database).getVisibility(txn, groupId);
|
||||||
will(returnValue(Arrays.asList(contactId)));
|
will(returnValue(Collections.singletonList(contactId)));
|
||||||
oneOf(database).getContactIds(txn);
|
oneOf(database).getContactIds(txn);
|
||||||
will(returnValue(both));
|
will(returnValue(both));
|
||||||
oneOf(database).addVisibility(txn, contactId1, groupId);
|
oneOf(database).addVisibility(txn, contactId1, groupId);
|
||||||
@@ -1478,14 +1479,15 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||||
eventBus, shutdown);
|
eventBus, shutdown);
|
||||||
|
|
||||||
db.setVisibility(groupId, Arrays.asList(contactId));
|
db.setVisibility(groupId, Collections.singletonList(contactId));
|
||||||
db.setVisibleToAll(groupId, true);
|
db.setVisibleToAll(groupId, true);
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTemporarySecrets() throws Exception {
|
public void testTransportKeys() throws Exception {
|
||||||
|
final TransportKeys keys = createTransportKeys();
|
||||||
Mockery context = new Mockery();
|
Mockery context = new Mockery();
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
final Database<Object> database = context.mock(Database.class);
|
final Database<Object> database = context.mock(Database.class);
|
||||||
@@ -1493,28 +1495,52 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
|
|||||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||||
final EventBus eventBus = context.mock(EventBus.class);
|
final EventBus eventBus = context.mock(EventBus.class);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// addSecrets()
|
// updateTransportKeys()
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
oneOf(database).containsContact(txn, contactId);
|
oneOf(database).containsContact(txn, contactId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(database).containsTransport(txn, transportId);
|
oneOf(database).containsTransport(txn, transportId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(database).addSecrets(txn, Arrays.asList(temporarySecret));
|
oneOf(database).updateTransportKeys(txn,
|
||||||
|
Collections.singletonMap(contactId, keys));
|
||||||
oneOf(database).commitTransaction(txn);
|
oneOf(database).commitTransaction(txn);
|
||||||
// getSecrets()
|
// getTransportKeys()
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
oneOf(database).getSecrets(txn);
|
oneOf(database).containsTransport(txn, transportId);
|
||||||
will(returnValue(Arrays.asList(temporarySecret)));
|
will(returnValue(true));
|
||||||
|
oneOf(database).getTransportKeys(txn, transportId);
|
||||||
|
will(returnValue(Collections.singletonMap(contactId, keys)));
|
||||||
oneOf(database).commitTransaction(txn);
|
oneOf(database).commitTransaction(txn);
|
||||||
}});
|
}});
|
||||||
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
DatabaseComponent db = createDatabaseComponent(database, cleaner,
|
||||||
eventBus, shutdown);
|
eventBus, shutdown);
|
||||||
|
|
||||||
db.addSecrets(Arrays.asList(temporarySecret));
|
db.updateTransportKeys(Collections.singletonMap(contactId, keys));
|
||||||
assertEquals(Arrays.asList(temporarySecret), db.getSecrets());
|
assertEquals(Collections.singletonMap(contactId, keys),
|
||||||
|
db.getTransportKeys(transportId));
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TransportKeys createTransportKeys() {
|
||||||
|
SecretKey inPrevTagKey = TestUtils.createSecretKey();
|
||||||
|
SecretKey inPrevHeaderKey = TestUtils.createSecretKey();
|
||||||
|
IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
|
||||||
|
1, 123, new byte[4]);
|
||||||
|
SecretKey inCurrTagKey = TestUtils.createSecretKey();
|
||||||
|
SecretKey inCurrHeaderKey = TestUtils.createSecretKey();
|
||||||
|
IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey,
|
||||||
|
2, 234, new byte[4]);
|
||||||
|
SecretKey inNextTagKey = TestUtils.createSecretKey();
|
||||||
|
SecretKey inNextHeaderKey = TestUtils.createSecretKey();
|
||||||
|
IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey,
|
||||||
|
3, 345, new byte[4]);
|
||||||
|
SecretKey outCurrTagKey = TestUtils.createSecretKey();
|
||||||
|
SecretKey outCurrHeaderKey = TestUtils.createSecretKey();
|
||||||
|
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
|
||||||
|
2, 456);
|
||||||
|
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,16 @@ import org.briarproject.api.LocalAuthor;
|
|||||||
import org.briarproject.api.TransportConfig;
|
import org.briarproject.api.TransportConfig;
|
||||||
import org.briarproject.api.TransportId;
|
import org.briarproject.api.TransportId;
|
||||||
import org.briarproject.api.TransportProperties;
|
import org.briarproject.api.TransportProperties;
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
import org.briarproject.api.db.DbException;
|
import org.briarproject.api.db.DbException;
|
||||||
import org.briarproject.api.db.MessageHeader;
|
import org.briarproject.api.db.MessageHeader;
|
||||||
import org.briarproject.api.messaging.Group;
|
import org.briarproject.api.messaging.Group;
|
||||||
import org.briarproject.api.messaging.GroupId;
|
import org.briarproject.api.messaging.GroupId;
|
||||||
import org.briarproject.api.messaging.Message;
|
import org.briarproject.api.messaging.Message;
|
||||||
import org.briarproject.api.messaging.MessageId;
|
import org.briarproject.api.messaging.MessageId;
|
||||||
import org.briarproject.api.transport.Endpoint;
|
import org.briarproject.api.transport.IncomingKeys;
|
||||||
import org.briarproject.api.transport.TemporarySecret;
|
import org.briarproject.api.transport.OutgoingKeys;
|
||||||
|
import org.briarproject.api.transport.TransportKeys;
|
||||||
import org.briarproject.system.SystemClock;
|
import org.briarproject.system.SystemClock;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@@ -34,6 +36,7 @@ import java.util.HashSet;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
@@ -59,7 +62,6 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
private final Random random = new Random();
|
private final Random random = new Random();
|
||||||
private final GroupId groupId;
|
private final GroupId groupId;
|
||||||
private final Group group;
|
private final Group group;
|
||||||
private final AuthorId authorId;
|
|
||||||
private final Author author;
|
private final Author author;
|
||||||
private final AuthorId localAuthorId;
|
private final AuthorId localAuthorId;
|
||||||
private final LocalAuthor localAuthor;
|
private final LocalAuthor localAuthor;
|
||||||
@@ -75,7 +77,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
public H2DatabaseTest() throws Exception {
|
public H2DatabaseTest() throws Exception {
|
||||||
groupId = new GroupId(TestUtils.getRandomId());
|
groupId = new GroupId(TestUtils.getRandomId());
|
||||||
group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH]);
|
group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH]);
|
||||||
authorId = new AuthorId(TestUtils.getRandomId());
|
AuthorId authorId = new AuthorId(TestUtils.getRandomId());
|
||||||
author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
|
author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
|
||||||
localAuthorId = new AuthorId(TestUtils.getRandomId());
|
localAuthorId = new AuthorId(TestUtils.getRandomId());
|
||||||
localAuthor = new LocalAuthor(localAuthorId, "Bob",
|
localAuthor = new LocalAuthor(localAuthorId, "Bob",
|
||||||
@@ -171,7 +173,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addVisibility(txn, contactId, groupId);
|
db.addVisibility(txn, contactId, groupId);
|
||||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||||
db.addMessage(txn, message, true);
|
db.addMessage(txn, message, true);
|
||||||
|
|
||||||
// The message has no status yet, so it should not be sendable
|
// The message has no status yet, so it should not be sendable
|
||||||
@@ -216,7 +218,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
assertTrue(ids.isEmpty());
|
assertTrue(ids.isEmpty());
|
||||||
|
|
||||||
// The contact subscribing should make the message sendable
|
// The contact subscribing should make the message sendable
|
||||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||||
assertFalse(ids.isEmpty());
|
assertFalse(ids.isEmpty());
|
||||||
Iterator<MessageId> it = ids.iterator();
|
Iterator<MessageId> it = ids.iterator();
|
||||||
@@ -243,7 +245,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addVisibility(txn, contactId, groupId);
|
db.addVisibility(txn, contactId, groupId);
|
||||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||||
db.addMessage(txn, message, true);
|
db.addMessage(txn, message, true);
|
||||||
db.addStatus(txn, contactId, messageId, false, false);
|
db.addStatus(txn, contactId, messageId, false, false);
|
||||||
|
|
||||||
@@ -273,7 +275,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
db.addLocalAuthor(txn, localAuthor);
|
db.addLocalAuthor(txn, localAuthor);
|
||||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||||
db.addMessage(txn, message, true);
|
db.addMessage(txn, message, true);
|
||||||
db.addStatus(txn, contactId, messageId, false, false);
|
db.addStatus(txn, contactId, messageId, false, false);
|
||||||
|
|
||||||
@@ -305,7 +307,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
db.addLocalAuthor(txn, localAuthor);
|
db.addLocalAuthor(txn, localAuthor);
|
||||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||||
|
|
||||||
// Add some messages to ack
|
// Add some messages to ack
|
||||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||||
@@ -342,7 +344,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
db.addLocalAuthor(txn, localAuthor);
|
db.addLocalAuthor(txn, localAuthor);
|
||||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||||
|
|
||||||
// Receive the same message twice
|
// Receive the same message twice
|
||||||
db.addMessage(txn, message, true);
|
db.addMessage(txn, message, true);
|
||||||
@@ -352,10 +354,10 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
|
|
||||||
// The message ID should only be returned once
|
// The message ID should only be returned once
|
||||||
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||||
assertEquals(Arrays.asList(messageId), ids);
|
assertEquals(Collections.singletonList(messageId), ids);
|
||||||
|
|
||||||
// Remove the message ID
|
// Remove the message ID
|
||||||
db.lowerAckFlag(txn, contactId, Arrays.asList(messageId));
|
db.lowerAckFlag(txn, contactId, Collections.singletonList(messageId));
|
||||||
|
|
||||||
// The message ID should have been removed
|
// The message ID should have been removed
|
||||||
assertEquals(Collections.emptyList(), db.getMessagesToAck(txn,
|
assertEquals(Collections.emptyList(), db.getMessagesToAck(txn,
|
||||||
@@ -375,7 +377,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addVisibility(txn, contactId, groupId);
|
db.addVisibility(txn, contactId, groupId);
|
||||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||||
db.addMessage(txn, message, true);
|
db.addMessage(txn, message, true);
|
||||||
db.addStatus(txn, contactId, messageId, false, false);
|
db.addStatus(txn, contactId, messageId, false, false);
|
||||||
|
|
||||||
@@ -674,7 +676,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addVisibility(txn, contactId, groupId);
|
db.addVisibility(txn, contactId, groupId);
|
||||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||||
|
|
||||||
// The message is not in the database
|
// The message is not in the database
|
||||||
assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
|
assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
|
||||||
@@ -692,7 +694,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
// Add a contact with a subscription
|
// Add a contact with a subscription
|
||||||
db.addLocalAuthor(txn, localAuthor);
|
db.addLocalAuthor(txn, localAuthor);
|
||||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||||
|
|
||||||
// There's no local subscription for the group
|
// There's no local subscription for the group
|
||||||
assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
|
assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
|
||||||
@@ -711,7 +713,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
db.addLocalAuthor(txn, localAuthor);
|
db.addLocalAuthor(txn, localAuthor);
|
||||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||||
db.addMessage(txn, message, true);
|
db.addMessage(txn, message, true);
|
||||||
db.addStatus(txn, contactId, messageId, false, false);
|
db.addStatus(txn, contactId, messageId, false, false);
|
||||||
|
|
||||||
@@ -737,7 +739,8 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
|
|
||||||
// Make the group visible to the contact
|
// Make the group visible to the contact
|
||||||
db.addVisibility(txn, contactId, groupId);
|
db.addVisibility(txn, contactId, groupId);
|
||||||
assertEquals(Arrays.asList(contactId), db.getVisibility(txn, groupId));
|
assertEquals(Collections.singletonList(contactId),
|
||||||
|
db.getVisibility(txn, groupId));
|
||||||
|
|
||||||
// Make the group invisible again
|
// Make the group invisible again
|
||||||
db.removeVisibility(txn, contactId, groupId);
|
db.removeVisibility(txn, contactId, groupId);
|
||||||
@@ -1111,183 +1114,97 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTemporarySecrets() throws Exception {
|
public void testTransportKeys() throws Exception {
|
||||||
// Create an endpoint and four consecutive temporary secrets
|
TransportKeys keys = createTransportKeys();
|
||||||
long epoch = 123;
|
|
||||||
int latency = 234;
|
|
||||||
boolean alice = false;
|
|
||||||
long outgoing1 = 345, centre1 = 456;
|
|
||||||
long outgoing2 = 567, centre2 = 678;
|
|
||||||
long outgoing3 = 789, centre3 = 890;
|
|
||||||
long outgoing4 = 901, centre4 = 123;
|
|
||||||
Endpoint ep = new Endpoint(contactId, transportId, epoch, alice);
|
|
||||||
Random random = new Random();
|
|
||||||
byte[] secret1 = new byte[32], bitmap1 = new byte[4];
|
|
||||||
random.nextBytes(secret1);
|
|
||||||
random.nextBytes(bitmap1);
|
|
||||||
TemporarySecret s1 = new TemporarySecret(contactId, transportId, epoch,
|
|
||||||
alice, 0, secret1, outgoing1, centre1, bitmap1);
|
|
||||||
byte[] secret2 = new byte[32], bitmap2 = new byte[4];
|
|
||||||
random.nextBytes(secret2);
|
|
||||||
random.nextBytes(bitmap2);
|
|
||||||
TemporarySecret s2 = new TemporarySecret(contactId, transportId, epoch,
|
|
||||||
alice, 1, secret2, outgoing2, centre2, bitmap2);
|
|
||||||
byte[] secret3 = new byte[32], bitmap3 = new byte[4];
|
|
||||||
random.nextBytes(secret3);
|
|
||||||
random.nextBytes(bitmap3);
|
|
||||||
TemporarySecret s3 = new TemporarySecret(contactId, transportId, epoch,
|
|
||||||
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();
|
||||||
|
|
||||||
// Initially there should be no secrets in the database
|
// Initially there should be no transport keys in the database
|
||||||
assertEquals(Collections.emptyList(), db.getSecrets(txn));
|
assertEquals(Collections.emptyMap(),
|
||||||
|
db.getTransportKeys(txn, transportId));
|
||||||
|
|
||||||
// Add the contact, the transport, the endpoint and the first three
|
// Add the contact, the transport and the transport keys
|
||||||
// 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, 123);
|
||||||
db.addEndpoint(txn, ep);
|
db.addTransportKeys(txn, contactId, keys);
|
||||||
db.addSecrets(txn, Arrays.asList(s1, s2, s3));
|
|
||||||
|
|
||||||
// Retrieve the first three secrets
|
// Retrieve the transport keys
|
||||||
Collection<TemporarySecret> secrets = db.getSecrets(txn);
|
Map<ContactId, TransportKeys> newKeys =
|
||||||
assertEquals(3, secrets.size());
|
db.getTransportKeys(txn, transportId);
|
||||||
boolean foundFirst = false, foundSecond = false, foundThird = false;
|
assertEquals(1, newKeys.size());
|
||||||
for (TemporarySecret s : secrets) {
|
Entry<ContactId, TransportKeys> e =
|
||||||
assertEquals(contactId, s.getContactId());
|
newKeys.entrySet().iterator().next();
|
||||||
assertEquals(transportId, s.getTransportId());
|
assertEquals(contactId, e.getKey());
|
||||||
assertEquals(epoch, s.getEpoch());
|
TransportKeys k = e.getValue();
|
||||||
assertEquals(alice, s.getAlice());
|
assertEquals(transportId, k.getTransportId());
|
||||||
if (s.getPeriod() == 0) {
|
assertKeysEquals(keys.getPreviousIncomingKeys(),
|
||||||
assertArrayEquals(secret1, s.getSecret());
|
k.getPreviousIncomingKeys());
|
||||||
assertEquals(outgoing1, s.getOutgoingStreamCounter());
|
assertKeysEquals(keys.getCurrentIncomingKeys(),
|
||||||
assertEquals(centre1, s.getWindowCentre());
|
k.getCurrentIncomingKeys());
|
||||||
assertArrayEquals(bitmap1, s.getWindowBitmap());
|
assertKeysEquals(keys.getNextIncomingKeys(),
|
||||||
foundFirst = true;
|
k.getNextIncomingKeys());
|
||||||
} else if (s.getPeriod() == 1) {
|
assertKeysEquals(keys.getCurrentOutgoingKeys(),
|
||||||
assertArrayEquals(secret2, s.getSecret());
|
k.getCurrentOutgoingKeys());
|
||||||
assertEquals(outgoing2, s.getOutgoingStreamCounter());
|
|
||||||
assertEquals(centre2, s.getWindowCentre());
|
|
||||||
assertArrayEquals(bitmap2, s.getWindowBitmap());
|
|
||||||
foundSecond = true;
|
|
||||||
} else if (s.getPeriod() == 2) {
|
|
||||||
assertArrayEquals(secret3, s.getSecret());
|
|
||||||
assertEquals(outgoing3, s.getOutgoingStreamCounter());
|
|
||||||
assertEquals(centre3, s.getWindowCentre());
|
|
||||||
assertArrayEquals(bitmap3, s.getWindowBitmap());
|
|
||||||
foundThird = true;
|
|
||||||
} else {
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assertTrue(foundFirst);
|
|
||||||
assertTrue(foundSecond);
|
|
||||||
assertTrue(foundThird);
|
|
||||||
|
|
||||||
// Adding the fourth secret (period 3) should delete the first
|
// Removing the contact should remove the transport keys
|
||||||
db.addSecrets(txn, Arrays.asList(s4));
|
|
||||||
secrets = db.getSecrets(txn);
|
|
||||||
assertEquals(3, secrets.size());
|
|
||||||
foundSecond = foundThird = false;
|
|
||||||
boolean foundFourth = false;
|
|
||||||
for (TemporarySecret s : secrets) {
|
|
||||||
assertEquals(contactId, s.getContactId());
|
|
||||||
assertEquals(transportId, s.getTransportId());
|
|
||||||
assertEquals(epoch, s.getEpoch());
|
|
||||||
assertEquals(alice, s.getAlice());
|
|
||||||
if (s.getPeriod() == 1) {
|
|
||||||
assertArrayEquals(secret2, s.getSecret());
|
|
||||||
assertEquals(outgoing2, s.getOutgoingStreamCounter());
|
|
||||||
assertEquals(centre2, s.getWindowCentre());
|
|
||||||
assertArrayEquals(bitmap2, s.getWindowBitmap());
|
|
||||||
foundSecond = true;
|
|
||||||
} else if (s.getPeriod() == 2) {
|
|
||||||
assertArrayEquals(secret3, s.getSecret());
|
|
||||||
assertEquals(outgoing3, s.getOutgoingStreamCounter());
|
|
||||||
assertEquals(centre3, s.getWindowCentre());
|
|
||||||
assertArrayEquals(bitmap3, s.getWindowBitmap());
|
|
||||||
foundThird = true;
|
|
||||||
} else if (s.getPeriod() == 3) {
|
|
||||||
assertArrayEquals(secret4, s.getSecret());
|
|
||||||
assertEquals(outgoing4, s.getOutgoingStreamCounter());
|
|
||||||
assertEquals(centre4, s.getWindowCentre());
|
|
||||||
assertArrayEquals(bitmap4, s.getWindowBitmap());
|
|
||||||
foundFourth = true;
|
|
||||||
} else {
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assertTrue(foundSecond);
|
|
||||||
assertTrue(foundThird);
|
|
||||||
assertTrue(foundFourth);
|
|
||||||
|
|
||||||
// Removing the contact should remove the secrets
|
|
||||||
db.removeContact(txn, contactId);
|
db.removeContact(txn, contactId);
|
||||||
assertEquals(Collections.emptyList(), db.getSecrets(txn));
|
assertEquals(Collections.emptyMap(),
|
||||||
|
db.getTransportKeys(txn, transportId));
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertKeysEquals(IncomingKeys expected, IncomingKeys actual) {
|
||||||
|
assertArrayEquals(expected.getTagKey().getBytes(),
|
||||||
|
actual.getTagKey().getBytes());
|
||||||
|
assertArrayEquals(expected.getHeaderKey().getBytes(),
|
||||||
|
actual.getHeaderKey().getBytes());
|
||||||
|
assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod());
|
||||||
|
assertEquals(expected.getWindowBase(), actual.getWindowBase());
|
||||||
|
assertArrayEquals(expected.getWindowBitmap(), actual.getWindowBitmap());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertKeysEquals(OutgoingKeys expected, OutgoingKeys actual) {
|
||||||
|
assertArrayEquals(expected.getTagKey().getBytes(),
|
||||||
|
actual.getTagKey().getBytes());
|
||||||
|
assertArrayEquals(expected.getHeaderKey().getBytes(),
|
||||||
|
actual.getHeaderKey().getBytes());
|
||||||
|
assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod());
|
||||||
|
assertEquals(expected.getStreamCounter(), actual.getStreamCounter());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIncrementStreamCounter() throws Exception {
|
public void testIncrementStreamCounter() throws Exception {
|
||||||
// Create an endpoint and a temporary secret
|
TransportKeys keys = createTransportKeys();
|
||||||
long epoch = 123;
|
long rotationPeriod = keys.getCurrentOutgoingKeys().getRotationPeriod();
|
||||||
int latency = 234;
|
long streamCounter = keys.getCurrentOutgoingKeys().getStreamCounter();
|
||||||
boolean alice = false;
|
|
||||||
long period = 345, outgoing = 456, centre = 567;
|
|
||||||
Endpoint ep = new Endpoint(contactId, transportId, epoch, alice);
|
|
||||||
Random random = new Random();
|
|
||||||
byte[] secret = new byte[32], bitmap = new byte[4];
|
|
||||||
random.nextBytes(secret);
|
|
||||||
TemporarySecret s = new TemporarySecret(contactId, transportId, epoch,
|
|
||||||
alice, period, secret, outgoing, centre, bitmap);
|
|
||||||
|
|
||||||
Database<Connection> db = open(false);
|
Database<Connection> db = open(false);
|
||||||
Connection txn = db.startTransaction();
|
Connection txn = db.startTransaction();
|
||||||
|
|
||||||
// Add the contact, transport, endpoint and temporary secret
|
// Add the contact, transport and transport keys
|
||||||
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, 123);
|
||||||
db.addEndpoint(txn, ep);
|
db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys));
|
||||||
db.addSecrets(txn, Arrays.asList(s));
|
|
||||||
|
|
||||||
// Retrieve the secret
|
// Increment the stream counter twice and retrieve the transport keys
|
||||||
Collection<TemporarySecret> secrets = db.getSecrets(txn);
|
db.incrementStreamCounter(txn, contactId, transportId, rotationPeriod);
|
||||||
assertEquals(1, secrets.size());
|
db.incrementStreamCounter(txn, contactId, transportId, rotationPeriod);
|
||||||
s = secrets.iterator().next();
|
Map<ContactId, TransportKeys> newKeys =
|
||||||
assertEquals(contactId, s.getContactId());
|
db.getTransportKeys(txn, transportId);
|
||||||
assertEquals(transportId, s.getTransportId());
|
assertEquals(1, newKeys.size());
|
||||||
assertEquals(period, s.getPeriod());
|
Entry<ContactId, TransportKeys> e =
|
||||||
assertArrayEquals(secret, s.getSecret());
|
newKeys.entrySet().iterator().next();
|
||||||
assertEquals(outgoing, s.getOutgoingStreamCounter());
|
assertEquals(contactId, e.getKey());
|
||||||
assertEquals(centre, s.getWindowCentre());
|
TransportKeys k = e.getValue();
|
||||||
assertArrayEquals(bitmap, s.getWindowBitmap());
|
assertEquals(transportId, k.getTransportId());
|
||||||
|
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||||
// Increment the stream counter twice and retrieve the secret again
|
assertEquals(rotationPeriod, outCurr.getRotationPeriod());
|
||||||
assertEquals(outgoing, db.incrementStreamCounter(txn,
|
assertEquals(streamCounter + 2, outCurr.getStreamCounter());
|
||||||
s.getContactId(), s.getTransportId(), s.getPeriod()));
|
|
||||||
assertEquals(outgoing + 1, db.incrementStreamCounter(txn,
|
|
||||||
s.getContactId(), s.getTransportId(), s.getPeriod()));
|
|
||||||
secrets = db.getSecrets(txn);
|
|
||||||
assertEquals(1, secrets.size());
|
|
||||||
s = secrets.iterator().next();
|
|
||||||
assertEquals(contactId, s.getContactId());
|
|
||||||
assertEquals(transportId, s.getTransportId());
|
|
||||||
assertEquals(period, s.getPeriod());
|
|
||||||
assertArrayEquals(secret, s.getSecret());
|
|
||||||
assertEquals(outgoing + 2, s.getOutgoingStreamCounter());
|
|
||||||
assertEquals(centre, s.getWindowCentre());
|
|
||||||
assertArrayEquals(bitmap, s.getWindowBitmap());
|
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -1295,123 +1212,36 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetReorderingWindow() throws Exception {
|
public void testSetReorderingWindow() throws Exception {
|
||||||
// Create an endpoint and a temporary secret
|
TransportKeys keys = createTransportKeys();
|
||||||
long epoch = 123;
|
long rotationPeriod = keys.getCurrentIncomingKeys().getRotationPeriod();
|
||||||
int latency = 234;
|
long base = keys.getCurrentIncomingKeys().getWindowBase();
|
||||||
boolean alice = false;
|
byte[] bitmap = keys.getCurrentIncomingKeys().getWindowBitmap();
|
||||||
long period = 345, outgoing = 456, centre = 567;
|
|
||||||
Endpoint ep = new Endpoint(contactId, transportId, epoch, alice);
|
|
||||||
Random random = new Random();
|
|
||||||
byte[] secret = new byte[32], bitmap = new byte[4];
|
|
||||||
random.nextBytes(secret);
|
|
||||||
TemporarySecret s = new TemporarySecret(contactId, transportId, epoch,
|
|
||||||
alice, period, secret, outgoing, centre, bitmap);
|
|
||||||
|
|
||||||
Database<Connection> db = open(false);
|
Database<Connection> db = open(false);
|
||||||
Connection txn = db.startTransaction();
|
Connection txn = db.startTransaction();
|
||||||
|
|
||||||
// Add the contact, transport, endpoint and temporary secret
|
// Add the contact, transport and transport keys
|
||||||
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, 123);
|
||||||
db.addEndpoint(txn, ep);
|
db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys));
|
||||||
db.addSecrets(txn, Arrays.asList(s));
|
|
||||||
|
|
||||||
// Retrieve the secret
|
// Update the reordering window and retrieve the transport keys
|
||||||
Collection<TemporarySecret> secrets = db.getSecrets(txn);
|
|
||||||
assertEquals(1, secrets.size());
|
|
||||||
s = secrets.iterator().next();
|
|
||||||
assertEquals(contactId, s.getContactId());
|
|
||||||
assertEquals(transportId, s.getTransportId());
|
|
||||||
assertEquals(period, s.getPeriod());
|
|
||||||
assertArrayEquals(secret, s.getSecret());
|
|
||||||
assertEquals(outgoing, s.getOutgoingStreamCounter());
|
|
||||||
assertEquals(centre, s.getWindowCentre());
|
|
||||||
assertArrayEquals(bitmap, s.getWindowBitmap());
|
|
||||||
|
|
||||||
// Update the reordering window and retrieve the secret again
|
|
||||||
random.nextBytes(bitmap);
|
random.nextBytes(bitmap);
|
||||||
db.setReorderingWindow(txn, contactId, transportId, period, centre,
|
db.setReorderingWindow(txn, contactId, transportId, rotationPeriod,
|
||||||
bitmap);
|
base + 1, bitmap);
|
||||||
secrets = db.getSecrets(txn);
|
Map<ContactId, TransportKeys> newKeys =
|
||||||
assertEquals(1, secrets.size());
|
db.getTransportKeys(txn, transportId);
|
||||||
s = secrets.iterator().next();
|
assertEquals(1, newKeys.size());
|
||||||
assertEquals(contactId, s.getContactId());
|
Entry<ContactId, TransportKeys> e =
|
||||||
assertEquals(transportId, s.getTransportId());
|
newKeys.entrySet().iterator().next();
|
||||||
assertEquals(period, s.getPeriod());
|
assertEquals(contactId, e.getKey());
|
||||||
assertArrayEquals(secret, s.getSecret());
|
TransportKeys k = e.getValue();
|
||||||
assertEquals(outgoing, s.getOutgoingStreamCounter());
|
assertEquals(transportId, k.getTransportId());
|
||||||
assertEquals(centre, s.getWindowCentre());
|
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
||||||
assertArrayEquals(bitmap, s.getWindowBitmap());
|
assertEquals(rotationPeriod, inCurr.getRotationPeriod());
|
||||||
|
assertEquals(base + 1, inCurr.getWindowBase());
|
||||||
// Updating a nonexistent window should not throw an exception
|
assertArrayEquals(bitmap, inCurr.getWindowBitmap());
|
||||||
db.setReorderingWindow(txn, contactId, transportId, period + 1, 1,
|
|
||||||
bitmap);
|
|
||||||
// The nonexistent window should not have been created
|
|
||||||
secrets = db.getSecrets(txn);
|
|
||||||
assertEquals(1, secrets.size());
|
|
||||||
s = secrets.iterator().next();
|
|
||||||
assertEquals(contactId, s.getContactId());
|
|
||||||
assertEquals(transportId, s.getTransportId());
|
|
||||||
assertEquals(period, s.getPeriod());
|
|
||||||
assertArrayEquals(secret, s.getSecret());
|
|
||||||
assertEquals(outgoing, s.getOutgoingStreamCounter());
|
|
||||||
assertEquals(centre, s.getWindowCentre());
|
|
||||||
assertArrayEquals(bitmap, s.getWindowBitmap());
|
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
|
||||||
db.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEndpoints() throws Exception {
|
|
||||||
// Create some endpoints
|
|
||||||
long epoch1 = 123, epoch2 = 234;
|
|
||||||
int latency1 = 345, latency2 = 456;
|
|
||||||
boolean alice1 = true, alice2 = false;
|
|
||||||
TransportId transportId1 = new TransportId("bar");
|
|
||||||
TransportId transportId2 = new TransportId("baz");
|
|
||||||
Endpoint ep1 = new Endpoint(contactId, transportId1, epoch1, alice1);
|
|
||||||
Endpoint ep2 = new Endpoint(contactId, transportId2, epoch2, alice2);
|
|
||||||
|
|
||||||
Database<Connection> db = open(false);
|
|
||||||
Connection txn = db.startTransaction();
|
|
||||||
|
|
||||||
// Initially there should be no endpoints in the database
|
|
||||||
assertEquals(Collections.emptyList(), db.getEndpoints(txn));
|
|
||||||
|
|
||||||
// Add the contact, the transports and the endpoints
|
|
||||||
db.addLocalAuthor(txn, localAuthor);
|
|
||||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
|
||||||
db.addTransport(txn, transportId1, latency1);
|
|
||||||
db.addTransport(txn, transportId2, latency2);
|
|
||||||
db.addEndpoint(txn, ep1);
|
|
||||||
db.addEndpoint(txn, ep2);
|
|
||||||
|
|
||||||
// Retrieve the endpoints
|
|
||||||
Collection<Endpoint> endpoints = db.getEndpoints(txn);
|
|
||||||
assertEquals(2, endpoints.size());
|
|
||||||
boolean foundFirst = false, foundSecond = false;
|
|
||||||
for (Endpoint ep : endpoints) {
|
|
||||||
assertEquals(contactId, ep.getContactId());
|
|
||||||
if (ep.getTransportId().equals(transportId1)) {
|
|
||||||
assertEquals(epoch1, ep.getEpoch());
|
|
||||||
assertEquals(alice1, ep.getAlice());
|
|
||||||
foundFirst = true;
|
|
||||||
} else if (ep.getTransportId().equals(transportId2)) {
|
|
||||||
assertEquals(epoch2, ep.getEpoch());
|
|
||||||
assertEquals(alice2, ep.getAlice());
|
|
||||||
foundSecond = true;
|
|
||||||
} else {
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assertTrue(foundFirst);
|
|
||||||
assertTrue(foundSecond);
|
|
||||||
|
|
||||||
// Removing the contact should remove the endpoints
|
|
||||||
db.removeContact(txn, contactId);
|
|
||||||
assertEquals(Collections.emptyList(), db.getEndpoints(txn));
|
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -1431,27 +1261,30 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
db.addLocalAuthor(txn, localAuthor);
|
db.addLocalAuthor(txn, localAuthor);
|
||||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||||
assertEquals(contactId1, db.addContact(txn, author1, localAuthorId));
|
assertEquals(contactId1, db.addContact(txn, author1, localAuthorId));
|
||||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||||
db.setGroups(txn, contactId1, Arrays.asList(group), 1);
|
db.setGroups(txn, contactId1, Collections.singletonList(group), 1);
|
||||||
|
|
||||||
// The group should be available
|
// The group should be available
|
||||||
assertEquals(Collections.emptyList(), db.getGroups(txn));
|
assertEquals(Collections.emptyList(), db.getGroups(txn));
|
||||||
assertEquals(Arrays.asList(group), db.getAvailableGroups(txn));
|
assertEquals(Collections.singletonList(group),
|
||||||
|
db.getAvailableGroups(txn));
|
||||||
|
|
||||||
// Subscribe to the group - it should no longer be available
|
// Subscribe to the group - it should no longer be available
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
assertEquals(Arrays.asList(group), db.getGroups(txn));
|
assertEquals(Collections.singletonList(group), db.getGroups(txn));
|
||||||
assertEquals(Collections.emptyList(), db.getAvailableGroups(txn));
|
assertEquals(Collections.emptyList(), db.getAvailableGroups(txn));
|
||||||
|
|
||||||
// Unsubscribe from the group - it should be available again
|
// Unsubscribe from the group - it should be available again
|
||||||
db.removeGroup(txn, groupId);
|
db.removeGroup(txn, groupId);
|
||||||
assertEquals(Collections.emptyList(), db.getGroups(txn));
|
assertEquals(Collections.emptyList(), db.getGroups(txn));
|
||||||
assertEquals(Arrays.asList(group), db.getAvailableGroups(txn));
|
assertEquals(Collections.singletonList(group),
|
||||||
|
db.getAvailableGroups(txn));
|
||||||
|
|
||||||
// The first contact unsubscribes - it should still be available
|
// The first contact unsubscribes - it should still be available
|
||||||
db.setGroups(txn, contactId, Collections.<Group>emptyList(), 2);
|
db.setGroups(txn, contactId, Collections.<Group>emptyList(), 2);
|
||||||
assertEquals(Collections.emptyList(), db.getGroups(txn));
|
assertEquals(Collections.emptyList(), db.getGroups(txn));
|
||||||
assertEquals(Arrays.asList(group), db.getAvailableGroups(txn));
|
assertEquals(Collections.singletonList(group),
|
||||||
|
db.getAvailableGroups(txn));
|
||||||
|
|
||||||
// The second contact unsubscribes - it should no longer be available
|
// The second contact unsubscribes - it should no longer be available
|
||||||
db.setGroups(txn, contactId1, Collections.<Group>emptyList(), 2);
|
db.setGroups(txn, contactId1, Collections.<Group>emptyList(), 2);
|
||||||
@@ -1501,9 +1334,8 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
db.getInboxMessageHeaders(txn, contactId));
|
db.getInboxMessageHeaders(txn, contactId));
|
||||||
|
|
||||||
// Add a message to the inbox group - the header should be returned
|
// Add a message to the inbox group - the header should be returned
|
||||||
boolean local = true, seen = false;
|
db.addMessage(txn, message, true);
|
||||||
db.addMessage(txn, message, local);
|
db.addStatus(txn, contactId, messageId, false, false);
|
||||||
db.addStatus(txn, contactId, messageId, false, seen);
|
|
||||||
Collection<MessageHeader> headers =
|
Collection<MessageHeader> headers =
|
||||||
db.getInboxMessageHeaders(txn, contactId);
|
db.getInboxMessageHeaders(txn, contactId);
|
||||||
assertEquals(1, headers.size());
|
assertEquals(1, headers.size());
|
||||||
@@ -1514,7 +1346,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
assertEquals(localAuthor, header.getAuthor());
|
assertEquals(localAuthor, header.getAuthor());
|
||||||
assertEquals(contentType, header.getContentType());
|
assertEquals(contentType, header.getContentType());
|
||||||
assertEquals(timestamp, header.getTimestamp());
|
assertEquals(timestamp, header.getTimestamp());
|
||||||
assertEquals(local, header.isLocal());
|
assertEquals(true, header.isLocal());
|
||||||
assertEquals(false, header.isRead());
|
assertEquals(false, header.isRead());
|
||||||
assertEquals(STORED, header.getStatus());
|
assertEquals(STORED, header.getStatus());
|
||||||
assertFalse(header.isRead());
|
assertFalse(header.isRead());
|
||||||
@@ -1560,7 +1392,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
// Add a contact who subscribes to a group
|
// Add a contact who subscribes to a group
|
||||||
db.addLocalAuthor(txn, localAuthor);
|
db.addLocalAuthor(txn, localAuthor);
|
||||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
|
||||||
db.setGroups(txn, contactId, Arrays.asList(group), 1);
|
db.setGroups(txn, contactId, Collections.singletonList(group), 1);
|
||||||
|
|
||||||
// Subscribe to the group and make it visible to the contact
|
// Subscribe to the group and make it visible to the contact
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
@@ -1571,7 +1403,7 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
db.addStatus(txn, contactId, messageId, false, false);
|
db.addStatus(txn, contactId, messageId, false, false);
|
||||||
Collection<MessageId> sendable = db.getMessagesToSend(txn, contactId,
|
Collection<MessageId> sendable = db.getMessagesToSend(txn, contactId,
|
||||||
ONE_MEGABYTE);
|
ONE_MEGABYTE);
|
||||||
assertEquals(Arrays.asList(messageId), sendable);
|
assertEquals(Collections.singletonList(messageId), sendable);
|
||||||
|
|
||||||
// Mark the message as seen - it should no longer be sendable
|
// Mark the message as seen - it should no longer be sendable
|
||||||
db.raiseSeenFlag(txn, contactId, messageId);
|
db.raiseSeenFlag(txn, contactId, messageId);
|
||||||
@@ -1584,9 +1416,9 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
assertEquals(Collections.emptyList(), sendable);
|
assertEquals(Collections.emptyList(), sendable);
|
||||||
|
|
||||||
// The contact resubscribes - the message should be sendable again
|
// The contact resubscribes - the message should be sendable again
|
||||||
db.setGroups(txn, contactId, Arrays.asList(group), 3);
|
db.setGroups(txn, contactId, Collections.singletonList(group), 3);
|
||||||
sendable = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
sendable = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||||
assertEquals(Arrays.asList(messageId), sendable);
|
assertEquals(Collections.singletonList(messageId), sendable);
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -1616,6 +1448,26 @@ public class H2DatabaseTest extends BriarTestCase {
|
|||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TransportKeys createTransportKeys() {
|
||||||
|
SecretKey inPrevTagKey = TestUtils.createSecretKey();
|
||||||
|
SecretKey inPrevHeaderKey = TestUtils.createSecretKey();
|
||||||
|
IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
|
||||||
|
1, 123, new byte[4]);
|
||||||
|
SecretKey inCurrTagKey = TestUtils.createSecretKey();
|
||||||
|
SecretKey inCurrHeaderKey = TestUtils.createSecretKey();
|
||||||
|
IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey,
|
||||||
|
2, 234, new byte[4]);
|
||||||
|
SecretKey inNextTagKey = TestUtils.createSecretKey();
|
||||||
|
SecretKey inNextHeaderKey = TestUtils.createSecretKey();
|
||||||
|
IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey,
|
||||||
|
3, 345, new byte[4]);
|
||||||
|
SecretKey outCurrTagKey = TestUtils.createSecretKey();
|
||||||
|
SecretKey outCurrHeaderKey = TestUtils.createSecretKey();
|
||||||
|
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
|
||||||
|
2, 456);
|
||||||
|
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
|
||||||
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
TestUtils.deleteTestDirectory(testDir);
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import org.briarproject.api.AuthorId;
|
|||||||
import org.briarproject.api.ContactId;
|
import org.briarproject.api.ContactId;
|
||||||
import org.briarproject.api.LocalAuthor;
|
import org.briarproject.api.LocalAuthor;
|
||||||
import org.briarproject.api.TransportId;
|
import org.briarproject.api.TransportId;
|
||||||
import org.briarproject.api.crypto.KeyManager;
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
import org.briarproject.api.db.DatabaseComponent;
|
||||||
import org.briarproject.api.event.Event;
|
import org.briarproject.api.event.Event;
|
||||||
import org.briarproject.api.event.EventBus;
|
import org.briarproject.api.event.EventBus;
|
||||||
@@ -29,11 +30,11 @@ import org.briarproject.api.messaging.PacketReader;
|
|||||||
import org.briarproject.api.messaging.PacketReaderFactory;
|
import org.briarproject.api.messaging.PacketReaderFactory;
|
||||||
import org.briarproject.api.messaging.PacketWriter;
|
import org.briarproject.api.messaging.PacketWriter;
|
||||||
import org.briarproject.api.messaging.PacketWriterFactory;
|
import org.briarproject.api.messaging.PacketWriterFactory;
|
||||||
import org.briarproject.api.transport.Endpoint;
|
import org.briarproject.api.transport.KeyManager;
|
||||||
import org.briarproject.api.transport.StreamContext;
|
import org.briarproject.api.transport.StreamContext;
|
||||||
import org.briarproject.api.transport.StreamReaderFactory;
|
import org.briarproject.api.transport.StreamReaderFactory;
|
||||||
import org.briarproject.api.transport.StreamWriterFactory;
|
import org.briarproject.api.transport.StreamWriterFactory;
|
||||||
import org.briarproject.api.transport.TagRecogniser;
|
import org.briarproject.api.transport.TransportKeys;
|
||||||
import org.briarproject.crypto.CryptoModule;
|
import org.briarproject.crypto.CryptoModule;
|
||||||
import org.briarproject.data.DataModule;
|
import org.briarproject.data.DataModule;
|
||||||
import org.briarproject.db.DatabaseModule;
|
import org.briarproject.db.DatabaseModule;
|
||||||
@@ -49,7 +50,7 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.Random;
|
import java.util.Collections;
|
||||||
|
|
||||||
import static org.briarproject.api.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
import static org.briarproject.api.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
import static org.briarproject.api.messaging.MessagingConstants.GROUP_SALT_LENGTH;
|
import static org.briarproject.api.messaging.MessagingConstants.GROUP_SALT_LENGTH;
|
||||||
@@ -63,26 +64,18 @@ import static org.junit.Assert.assertTrue;
|
|||||||
public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||||
|
|
||||||
private static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
|
private static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
|
||||||
private static final int ROTATION_PERIOD =
|
private static final long ROTATION_PERIOD_LENGTH =
|
||||||
MAX_CLOCK_DIFFERENCE + MAX_LATENCY;
|
MAX_LATENCY + MAX_CLOCK_DIFFERENCE;
|
||||||
|
|
||||||
private final File testDir = TestUtils.getTestDirectory();
|
private final File testDir = TestUtils.getTestDirectory();
|
||||||
private final File aliceDir = new File(testDir, "alice");
|
private final File aliceDir = new File(testDir, "alice");
|
||||||
private final File bobDir = new File(testDir, "bob");
|
private final File bobDir = new File(testDir, "bob");
|
||||||
private final TransportId transportId;
|
private final TransportId transportId = new TransportId("id");
|
||||||
private final byte[] initialSecret;
|
private final SecretKey master = TestUtils.createSecretKey();
|
||||||
private final long epoch;
|
private final long timestamp = System.currentTimeMillis();
|
||||||
|
|
||||||
private Injector alice, bob;
|
private Injector alice, bob;
|
||||||
|
|
||||||
public SimplexMessagingIntegrationTest() throws Exception {
|
|
||||||
transportId = new TransportId("id");
|
|
||||||
// Create matching secrets for Alice and Bob
|
|
||||||
initialSecret = new byte[32];
|
|
||||||
new Random().nextBytes(initialSecret);
|
|
||||||
epoch = System.currentTimeMillis() - 2 * ROTATION_PERIOD;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
testDir.mkdirs();
|
testDir.mkdirs();
|
||||||
@@ -125,14 +118,17 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
|||||||
Group group = gf.createGroup("Group", new byte[GROUP_SALT_LENGTH]);
|
Group group = gf.createGroup("Group", new byte[GROUP_SALT_LENGTH]);
|
||||||
db.addGroup(group);
|
db.addGroup(group);
|
||||||
db.setInboxGroup(contactId, group);
|
db.setInboxGroup(contactId, group);
|
||||||
// Add the transport and the endpoint
|
// Add the transport
|
||||||
db.addTransport(transportId, MAX_LATENCY);
|
db.addTransport(transportId, MAX_LATENCY);
|
||||||
Endpoint ep = new Endpoint(contactId, transportId, epoch, true);
|
// Derive and store the transport keys
|
||||||
db.addEndpoint(ep);
|
long rotationPeriod = timestamp / ROTATION_PERIOD_LENGTH;
|
||||||
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret);
|
CryptoComponent crypto = alice.getInstance(CryptoComponent.class);
|
||||||
|
TransportKeys keys = crypto.deriveTransportKeys(transportId, master,
|
||||||
|
rotationPeriod, true);
|
||||||
|
db.addTransportKeys(contactId, keys);
|
||||||
|
keyManager.contactAdded(contactId, Collections.singletonList(keys));
|
||||||
// Send Bob a message
|
// Send Bob a message
|
||||||
String contentType = "text/plain";
|
String contentType = "text/plain";
|
||||||
long timestamp = System.currentTimeMillis();
|
|
||||||
byte[] body = "Hi Bob!".getBytes("UTF-8");
|
byte[] body = "Hi Bob!".getBytes("UTF-8");
|
||||||
MessageFactory messageFactory = alice.getInstance(MessageFactory.class);
|
MessageFactory messageFactory = alice.getInstance(MessageFactory.class);
|
||||||
Message message = messageFactory.createAnonymousMessage(null, group,
|
Message message = messageFactory.createAnonymousMessage(null, group,
|
||||||
@@ -166,7 +162,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
|||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void read(byte[] b) throws Exception {
|
private void read(byte[] stream) throws Exception {
|
||||||
// Open Bob's database
|
// Open Bob's database
|
||||||
DatabaseComponent db = bob.getInstance(DatabaseComponent.class);
|
DatabaseComponent db = bob.getInstance(DatabaseComponent.class);
|
||||||
assertFalse(db.open());
|
assertFalse(db.open());
|
||||||
@@ -188,21 +184,24 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
|||||||
Group group = gf.createGroup("Group", new byte[GROUP_SALT_LENGTH]);
|
Group group = gf.createGroup("Group", new byte[GROUP_SALT_LENGTH]);
|
||||||
db.addGroup(group);
|
db.addGroup(group);
|
||||||
db.setInboxGroup(contactId, group);
|
db.setInboxGroup(contactId, group);
|
||||||
// Add the transport and the endpoint
|
// Add the transport
|
||||||
db.addTransport(transportId, MAX_LATENCY);
|
db.addTransport(transportId, MAX_LATENCY);
|
||||||
Endpoint ep = new Endpoint(contactId, transportId, epoch, false);
|
// Derive and store the transport keys
|
||||||
db.addEndpoint(ep);
|
long rotationPeriod = timestamp / ROTATION_PERIOD_LENGTH;
|
||||||
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret);
|
CryptoComponent crypto = bob.getInstance(CryptoComponent.class);
|
||||||
|
TransportKeys keys = crypto.deriveTransportKeys(transportId, master,
|
||||||
|
rotationPeriod, false);
|
||||||
|
db.addTransportKeys(contactId, keys);
|
||||||
|
keyManager.contactAdded(contactId, Collections.singletonList(keys));
|
||||||
// Set up an event listener
|
// Set up an event listener
|
||||||
MessageListener listener = new MessageListener();
|
MessageListener listener = new MessageListener();
|
||||||
bob.getInstance(EventBus.class).addListener(listener);
|
bob.getInstance(EventBus.class).addListener(listener);
|
||||||
// Create a tag recogniser and recognise the tag
|
// Read and recognise the tag
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
ByteArrayInputStream in = new ByteArrayInputStream(stream);
|
||||||
TagRecogniser rec = bob.getInstance(TagRecogniser.class);
|
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
int read = in.read(tag);
|
int read = in.read(tag);
|
||||||
assertEquals(tag.length, read);
|
assertEquals(tag.length, read);
|
||||||
StreamContext ctx = rec.recogniseTag(transportId, tag);
|
StreamContext ctx = keyManager.recogniseTag(transportId, tag);
|
||||||
assertNotNull(ctx);
|
assertNotNull(ctx);
|
||||||
// Create a stream reader
|
// Create a stream reader
|
||||||
StreamReaderFactory streamReaderFactory =
|
StreamReaderFactory streamReaderFactory =
|
||||||
|
|||||||
@@ -1,600 +1,14 @@
|
|||||||
package org.briarproject.transport;
|
package org.briarproject.transport;
|
||||||
|
|
||||||
import org.briarproject.BriarTestCase;
|
import org.briarproject.BriarTestCase;
|
||||||
import org.briarproject.api.ContactId;
|
|
||||||
import org.briarproject.api.TransportId;
|
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
|
||||||
import org.briarproject.api.event.EventBus;
|
|
||||||
import org.briarproject.api.event.EventListener;
|
|
||||||
import org.briarproject.api.system.Clock;
|
|
||||||
import org.briarproject.api.system.Timer;
|
|
||||||
import org.briarproject.api.transport.Endpoint;
|
|
||||||
import org.briarproject.api.transport.StreamContext;
|
|
||||||
import org.briarproject.api.transport.TagRecogniser;
|
|
||||||
import org.briarproject.api.transport.TemporarySecret;
|
|
||||||
import org.jmock.Expectations;
|
|
||||||
import org.jmock.Mockery;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import static org.junit.Assert.fail;
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
public class KeyManagerImplTest extends BriarTestCase {
|
public class KeyManagerImplTest extends BriarTestCase {
|
||||||
|
|
||||||
private static final long EPOCH = 1000L * 1000L * 1000L * 1000L;
|
|
||||||
private static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
|
|
||||||
private static final int ROTATION_PERIOD =
|
|
||||||
MAX_CLOCK_DIFFERENCE + MAX_LATENCY;
|
|
||||||
|
|
||||||
private final ContactId contactId;
|
|
||||||
private final TransportId transportId;
|
|
||||||
private final byte[] secret0, secret1, secret2, secret3, secret4;
|
|
||||||
private final byte[] initialSecret;
|
|
||||||
|
|
||||||
public KeyManagerImplTest() {
|
|
||||||
contactId = new ContactId(234);
|
|
||||||
transportId = new TransportId("id");
|
|
||||||
secret0 = new byte[32];
|
|
||||||
secret1 = new byte[32];
|
|
||||||
secret2 = new byte[32];
|
|
||||||
secret3 = new byte[32];
|
|
||||||
secret4 = new byte[32];
|
|
||||||
for (int i = 0; i < secret0.length; i++) secret0[i] = 1;
|
|
||||||
for (int i = 0; i < secret1.length; i++) secret1[i] = 2;
|
|
||||||
for (int i = 0; i < secret2.length; i++) secret2[i] = 3;
|
|
||||||
for (int i = 0; i < secret3.length; i++) secret3[i] = 4;
|
|
||||||
for (int i = 0; i < secret4.length; i++) secret4[i] = 5;
|
|
||||||
initialSecret = new byte[32];
|
|
||||||
for (int i = 0; i < initialSecret.length; i++) initialSecret[i] = 123;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStartAndStop() throws Exception {
|
public void testUnitTestsExist() {
|
||||||
Mockery context = new Mockery();
|
fail(); // FIXME: Write tests
|
||||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
|
||||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
final EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
|
|
||||||
final Clock clock = context.mock(Clock.class);
|
|
||||||
final Timer timer = context.mock(Timer.class);
|
|
||||||
|
|
||||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
|
||||||
eventBus, tagRecogniser, clock, timer);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
// start()
|
|
||||||
oneOf(eventBus).addListener(with(any(EventListener.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(keyManager),
|
|
||||||
with(any(long.class)), with(any(long.class)));
|
|
||||||
// stop()
|
|
||||||
oneOf(eventBus).removeListener(with(any(EventListener.class)));
|
|
||||||
oneOf(timer).cancel();
|
|
||||||
oneOf(tagRecogniser).removeSecrets();
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertTrue(keyManager.start());
|
|
||||||
keyManager.stop();
|
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEndpointAdded() throws Exception {
|
|
||||||
Mockery context = new Mockery();
|
|
||||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
|
||||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
final EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
|
|
||||||
final Clock clock = context.mock(Clock.class);
|
|
||||||
final Timer timer = context.mock(Timer.class);
|
|
||||||
|
|
||||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
|
||||||
eventBus, tagRecogniser, clock, timer);
|
|
||||||
|
|
||||||
// The secrets for periods 0 - 2 should be derived
|
|
||||||
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(eventBus).addListener(with(any(EventListener.class)));
|
|
||||||
oneOf(db).getSecrets();
|
|
||||||
will(returnValue(Collections.emptyList()));
|
|
||||||
oneOf(db).getTransportLatencies();
|
|
||||||
will(returnValue(Collections.singletonMap(transportId,
|
|
||||||
MAX_LATENCY)));
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH));
|
|
||||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
|
||||||
with(any(long.class)), with(any(long.class)));
|
|
||||||
// endpointAdded() during rotation period 1
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH));
|
|
||||||
oneOf(crypto).deriveNextSecret(initialSecret, 0);
|
|
||||||
will(returnValue(secret0));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret0, 1);
|
|
||||||
will(returnValue(secret1));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
|
||||||
will(returnValue(secret2));
|
|
||||||
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
|
|
||||||
// The secrets for periods 0 - 2 should be added to the recogniser
|
|
||||||
oneOf(tagRecogniser).addSecret(s0);
|
|
||||||
oneOf(tagRecogniser).addSecret(s1);
|
|
||||||
oneOf(tagRecogniser).addSecret(s2);
|
|
||||||
// stop()
|
|
||||||
oneOf(eventBus).removeListener(with(any(EventListener.class)));
|
|
||||||
oneOf(timer).cancel();
|
|
||||||
oneOf(tagRecogniser).removeSecrets();
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertTrue(keyManager.start());
|
|
||||||
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret);
|
|
||||||
keyManager.stop();
|
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEndpointAddedAndGetConnectionContext() throws Exception {
|
|
||||||
Mockery context = new Mockery();
|
|
||||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
|
||||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
final EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
|
|
||||||
final Clock clock = context.mock(Clock.class);
|
|
||||||
final Timer timer = context.mock(Timer.class);
|
|
||||||
|
|
||||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
|
||||||
eventBus, tagRecogniser, clock, timer);
|
|
||||||
|
|
||||||
// The secrets for periods 0 - 2 should be derived
|
|
||||||
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(eventBus).addListener(with(any(EventListener.class)));
|
|
||||||
oneOf(db).getSecrets();
|
|
||||||
will(returnValue(Collections.emptyList()));
|
|
||||||
oneOf(db).getTransportLatencies();
|
|
||||||
will(returnValue(Collections.singletonMap(transportId,
|
|
||||||
MAX_LATENCY)));
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH));
|
|
||||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
|
||||||
with(any(long.class)), with(any(long.class)));
|
|
||||||
// endpointAdded() during rotation period 1
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH));
|
|
||||||
oneOf(crypto).deriveNextSecret(initialSecret, 0);
|
|
||||||
will(returnValue(secret0));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret0, 1);
|
|
||||||
will(returnValue(secret1));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
|
||||||
will(returnValue(secret2));
|
|
||||||
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
|
|
||||||
// The secrets for periods 0 - 2 should be added to the recogniser
|
|
||||||
oneOf(tagRecogniser).addSecret(s0);
|
|
||||||
oneOf(tagRecogniser).addSecret(s1);
|
|
||||||
oneOf(tagRecogniser).addSecret(s2);
|
|
||||||
// getConnectionContext()
|
|
||||||
oneOf(db).incrementStreamCounter(contactId, transportId, 1);
|
|
||||||
will(returnValue(0L));
|
|
||||||
// stop()
|
|
||||||
oneOf(eventBus).removeListener(with(any(EventListener.class)));
|
|
||||||
oneOf(timer).cancel();
|
|
||||||
oneOf(tagRecogniser).removeSecrets();
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertTrue(keyManager.start());
|
|
||||||
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret);
|
|
||||||
StreamContext ctx =
|
|
||||||
keyManager.getStreamContext(contactId, transportId);
|
|
||||||
assertNotNull(ctx);
|
|
||||||
assertEquals(contactId, ctx.getContactId());
|
|
||||||
assertEquals(transportId, ctx.getTransportId());
|
|
||||||
assertArrayEquals(secret1, ctx.getSecret());
|
|
||||||
assertEquals(0, ctx.getStreamNumber());
|
|
||||||
assertEquals(true, ctx.getAlice());
|
|
||||||
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 EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
|
|
||||||
final Clock clock = context.mock(Clock.class);
|
|
||||||
final Timer timer = context.mock(Timer.class);
|
|
||||||
|
|
||||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
|
||||||
eventBus, tagRecogniser, clock, timer);
|
|
||||||
|
|
||||||
// The DB contains the 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(eventBus).addListener(with(any(EventListener.class)));
|
|
||||||
oneOf(db).getSecrets();
|
|
||||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
|
||||||
oneOf(db).getTransportLatencies();
|
|
||||||
will(returnValue(Collections.singletonMap(transportId,
|
|
||||||
MAX_LATENCY)));
|
|
||||||
// The current time is the epoch, the start of period 1
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH));
|
|
||||||
// The secrets for periods 0 - 2 should be added to the recogniser
|
|
||||||
oneOf(tagRecogniser).addSecret(s0);
|
|
||||||
oneOf(tagRecogniser).addSecret(s1);
|
|
||||||
oneOf(tagRecogniser).addSecret(s2);
|
|
||||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
|
||||||
with(any(long.class)), with(any(long.class)));
|
|
||||||
// stop()
|
|
||||||
oneOf(eventBus).removeListener(with(any(EventListener.class)));
|
|
||||||
oneOf(timer).cancel();
|
|
||||||
oneOf(tagRecogniser).removeSecrets();
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertTrue(keyManager.start());
|
|
||||||
keyManager.stop();
|
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLoadSecretsAtStartOfPeriod2() throws Exception {
|
|
||||||
Mockery context = new Mockery();
|
|
||||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
|
||||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
final EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
|
|
||||||
final Clock clock = context.mock(Clock.class);
|
|
||||||
final Timer timer = context.mock(Timer.class);
|
|
||||||
|
|
||||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
|
||||||
eventBus, tagRecogniser, clock, timer);
|
|
||||||
|
|
||||||
// The DB contains the 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);
|
|
||||||
// The secret for period 3 should be derived and stored
|
|
||||||
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
// start()
|
|
||||||
oneOf(eventBus).addListener(with(any(EventListener.class)));
|
|
||||||
oneOf(db).getSecrets();
|
|
||||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
|
||||||
oneOf(db).getTransportLatencies();
|
|
||||||
will(returnValue(Collections.singletonMap(transportId,
|
|
||||||
MAX_LATENCY)));
|
|
||||||
// The current time is the start of period 2
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH + ROTATION_PERIOD));
|
|
||||||
// The secret for period 3 should be derived and stored
|
|
||||||
oneOf(crypto).deriveNextSecret(secret0, 1);
|
|
||||||
will(returnValue(secret1));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
|
||||||
will(returnValue(secret2));
|
|
||||||
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(tagRecogniser).addSecret(s1);
|
|
||||||
oneOf(tagRecogniser).addSecret(s2);
|
|
||||||
oneOf(tagRecogniser).addSecret(s3);
|
|
||||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
|
||||||
with(any(long.class)), with(any(long.class)));
|
|
||||||
// stop()
|
|
||||||
oneOf(eventBus).removeListener(with(any(EventListener.class)));
|
|
||||||
oneOf(timer).cancel();
|
|
||||||
oneOf(tagRecogniser).removeSecrets();
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertTrue(keyManager.start());
|
|
||||||
keyManager.stop();
|
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLoadSecretsAtEndOfPeriod3() throws Exception {
|
|
||||||
Mockery context = new Mockery();
|
|
||||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
|
||||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
final EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
|
|
||||||
final Clock clock = context.mock(Clock.class);
|
|
||||||
final Timer timer = context.mock(Timer.class);
|
|
||||||
|
|
||||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
|
||||||
eventBus, tagRecogniser, clock, timer);
|
|
||||||
|
|
||||||
// The DB contains the 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);
|
|
||||||
// The secrets for periods 3 and 4 should be derived and stored
|
|
||||||
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3);
|
|
||||||
final TemporarySecret s4 = new TemporarySecret(ep, 4, secret4);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
// start()
|
|
||||||
oneOf(eventBus).addListener(with(any(EventListener.class)));
|
|
||||||
oneOf(db).getSecrets();
|
|
||||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
|
||||||
oneOf(db).getTransportLatencies();
|
|
||||||
will(returnValue(Collections.singletonMap(transportId,
|
|
||||||
MAX_LATENCY)));
|
|
||||||
// The current time is the end of period 3
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH + 3 * ROTATION_PERIOD - 1));
|
|
||||||
// The secrets for periods 3 and 4 should be derived from secret 1
|
|
||||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
|
||||||
will(returnValue(secret2));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret2, 3);
|
|
||||||
will(returnValue(secret3));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret3, 4);
|
|
||||||
will(returnValue(secret4));
|
|
||||||
// The new secrets should be stored
|
|
||||||
oneOf(db).addSecrets(Arrays.asList(s3, s4));
|
|
||||||
// The secrets for periods 2 - 4 should be added to the recogniser
|
|
||||||
oneOf(tagRecogniser).addSecret(s2);
|
|
||||||
oneOf(tagRecogniser).addSecret(s3);
|
|
||||||
oneOf(tagRecogniser).addSecret(s4);
|
|
||||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
|
||||||
with(any(long.class)), with(any(long.class)));
|
|
||||||
// stop()
|
|
||||||
oneOf(eventBus).removeListener(with(any(EventListener.class)));
|
|
||||||
oneOf(timer).cancel();
|
|
||||||
oneOf(tagRecogniser).removeSecrets();
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertTrue(keyManager.start());
|
|
||||||
keyManager.stop();
|
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLoadSecretsAndRotateInSamePeriod() throws Exception {
|
|
||||||
Mockery context = new Mockery();
|
|
||||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
|
||||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
final EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
|
|
||||||
final Clock clock = context.mock(Clock.class);
|
|
||||||
final Timer timer = context.mock(Timer.class);
|
|
||||||
|
|
||||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
|
||||||
eventBus, tagRecogniser, clock, timer);
|
|
||||||
|
|
||||||
// The DB contains the 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(eventBus).addListener(with(any(EventListener.class)));
|
|
||||||
oneOf(db).getSecrets();
|
|
||||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
|
||||||
oneOf(db).getTransportLatencies();
|
|
||||||
will(returnValue(Collections.singletonMap(transportId,
|
|
||||||
MAX_LATENCY)));
|
|
||||||
// The current time is the epoch, the start of period 1
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH));
|
|
||||||
// The secrets for periods 0 - 2 should be added to the recogniser
|
|
||||||
oneOf(tagRecogniser).addSecret(s0);
|
|
||||||
oneOf(tagRecogniser).addSecret(s1);
|
|
||||||
oneOf(tagRecogniser).addSecret(s2);
|
|
||||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
|
||||||
with(any(long.class)), with(any(long.class)));
|
|
||||||
// run() during period 1: the secrets should not be affected
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH + 1));
|
|
||||||
// getConnectionContext()
|
|
||||||
oneOf(db).incrementStreamCounter(contactId, transportId, 1);
|
|
||||||
will(returnValue(0L));
|
|
||||||
// stop()
|
|
||||||
oneOf(eventBus).removeListener(with(any(EventListener.class)));
|
|
||||||
oneOf(timer).cancel();
|
|
||||||
oneOf(tagRecogniser).removeSecrets();
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertTrue(keyManager.start());
|
|
||||||
keyManager.run();
|
|
||||||
StreamContext ctx =
|
|
||||||
keyManager.getStreamContext(contactId, transportId);
|
|
||||||
assertNotNull(ctx);
|
|
||||||
assertEquals(contactId, ctx.getContactId());
|
|
||||||
assertEquals(transportId, ctx.getTransportId());
|
|
||||||
assertArrayEquals(secret1, ctx.getSecret());
|
|
||||||
assertEquals(0, ctx.getStreamNumber());
|
|
||||||
assertEquals(true, ctx.getAlice());
|
|
||||||
keyManager.stop();
|
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLoadSecretsAndRotateInNextPeriod() throws Exception {
|
|
||||||
Mockery context = new Mockery();
|
|
||||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
|
||||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
final EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
|
|
||||||
final Clock clock = context.mock(Clock.class);
|
|
||||||
final Timer timer = context.mock(Timer.class);
|
|
||||||
|
|
||||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
|
||||||
eventBus, tagRecogniser, clock, timer);
|
|
||||||
|
|
||||||
// The DB contains the 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);
|
|
||||||
// The secret for period 3 should be derived and stored
|
|
||||||
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
// start()
|
|
||||||
oneOf(eventBus).addListener(with(any(EventListener.class)));
|
|
||||||
oneOf(db).getSecrets();
|
|
||||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
|
||||||
oneOf(db).getTransportLatencies();
|
|
||||||
will(returnValue(Collections.singletonMap(transportId,
|
|
||||||
MAX_LATENCY)));
|
|
||||||
// The current time is the epoch, the start of period 1
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH));
|
|
||||||
// The secrets for periods 0 - 2 should be added to the recogniser
|
|
||||||
oneOf(tagRecogniser).addSecret(s0);
|
|
||||||
oneOf(tagRecogniser).addSecret(s1);
|
|
||||||
oneOf(tagRecogniser).addSecret(s2);
|
|
||||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
|
||||||
with(any(long.class)), with(any(long.class)));
|
|
||||||
// run() during period 2: the secrets should be rotated
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH + ROTATION_PERIOD + 1));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret0, 1);
|
|
||||||
will(returnValue(secret1));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
|
||||||
will(returnValue(secret2));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret2, 3);
|
|
||||||
will(returnValue(secret3));
|
|
||||||
oneOf(tagRecogniser).removeSecret(contactId, transportId, 0);
|
|
||||||
oneOf(db).addSecrets(Arrays.asList(s3));
|
|
||||||
oneOf(tagRecogniser).addSecret(s3);
|
|
||||||
// getConnectionContext()
|
|
||||||
oneOf(db).incrementStreamCounter(contactId, transportId, 2);
|
|
||||||
will(returnValue(0L));
|
|
||||||
// stop()
|
|
||||||
oneOf(eventBus).removeListener(with(any(EventListener.class)));
|
|
||||||
oneOf(timer).cancel();
|
|
||||||
oneOf(tagRecogniser).removeSecrets();
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertTrue(keyManager.start());
|
|
||||||
keyManager.run();
|
|
||||||
StreamContext ctx =
|
|
||||||
keyManager.getStreamContext(contactId, transportId);
|
|
||||||
assertNotNull(ctx);
|
|
||||||
assertEquals(contactId, ctx.getContactId());
|
|
||||||
assertEquals(transportId, ctx.getTransportId());
|
|
||||||
assertArrayEquals(secret2, ctx.getSecret());
|
|
||||||
assertEquals(0, ctx.getStreamNumber());
|
|
||||||
assertEquals(true, ctx.getAlice());
|
|
||||||
keyManager.stop();
|
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLoadSecretsAndRotateAWholePeriodLate() throws Exception {
|
|
||||||
Mockery context = new Mockery();
|
|
||||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
|
||||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
final EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
|
|
||||||
final Clock clock = context.mock(Clock.class);
|
|
||||||
final Timer timer = context.mock(Timer.class);
|
|
||||||
|
|
||||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
|
||||||
eventBus, tagRecogniser, clock, timer);
|
|
||||||
|
|
||||||
// The DB contains the 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);
|
|
||||||
// The secrets for periods 3 and 4 should be derived and stored
|
|
||||||
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3);
|
|
||||||
final TemporarySecret s4 = new TemporarySecret(ep, 4, secret4);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
// start()
|
|
||||||
oneOf(eventBus).addListener(with(any(EventListener.class)));
|
|
||||||
oneOf(db).getSecrets();
|
|
||||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
|
||||||
oneOf(db).getTransportLatencies();
|
|
||||||
will(returnValue(Collections.singletonMap(transportId,
|
|
||||||
MAX_LATENCY)));
|
|
||||||
// The current time is the epoch, the start of period 1
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH));
|
|
||||||
// The secrets for periods 0 - 2 should be added to the recogniser
|
|
||||||
oneOf(tagRecogniser).addSecret(s0);
|
|
||||||
oneOf(tagRecogniser).addSecret(s1);
|
|
||||||
oneOf(tagRecogniser).addSecret(s2);
|
|
||||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
|
||||||
with(any(long.class)), with(any(long.class)));
|
|
||||||
// run() during period 3 (late): the secrets should be rotated
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH + 2 * ROTATION_PERIOD + 1));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
|
||||||
will(returnValue(secret2));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret2, 3);
|
|
||||||
will(returnValue(secret3));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret3, 4);
|
|
||||||
will(returnValue(secret4));
|
|
||||||
oneOf(tagRecogniser).removeSecret(contactId, transportId, 0);
|
|
||||||
oneOf(tagRecogniser).removeSecret(contactId, transportId, 1);
|
|
||||||
oneOf(db).addSecrets(Arrays.asList(s3, s4));
|
|
||||||
oneOf(tagRecogniser).addSecret(s3);
|
|
||||||
oneOf(tagRecogniser).addSecret(s4);
|
|
||||||
// getConnectionContext()
|
|
||||||
oneOf(db).incrementStreamCounter(contactId, transportId, 3);
|
|
||||||
will(returnValue(0L));
|
|
||||||
// stop()
|
|
||||||
oneOf(eventBus).removeListener(with(any(EventListener.class)));
|
|
||||||
oneOf(timer).cancel();
|
|
||||||
oneOf(tagRecogniser).removeSecrets();
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertTrue(keyManager.start());
|
|
||||||
keyManager.run();
|
|
||||||
StreamContext ctx =
|
|
||||||
keyManager.getStreamContext(contactId, transportId);
|
|
||||||
assertNotNull(ctx);
|
|
||||||
assertEquals(contactId, ctx.getContactId());
|
|
||||||
assertEquals(transportId, ctx.getTransportId());
|
|
||||||
assertArrayEquals(secret3, ctx.getSecret());
|
|
||||||
assertEquals(0, ctx.getStreamNumber());
|
|
||||||
assertEquals(true, ctx.getAlice());
|
|
||||||
keyManager.stop();
|
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,772 +0,0 @@
|
|||||||
package org.briarproject.transport;
|
|
||||||
|
|
||||||
import org.briarproject.BriarTestCase;
|
|
||||||
import org.briarproject.api.ContactId;
|
|
||||||
import org.briarproject.api.TransportId;
|
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
|
||||||
import org.briarproject.api.event.EventBus;
|
|
||||||
import org.briarproject.api.event.EventListener;
|
|
||||||
import org.briarproject.api.system.Clock;
|
|
||||||
import org.briarproject.api.system.Timer;
|
|
||||||
import org.briarproject.api.transport.Endpoint;
|
|
||||||
import org.briarproject.api.transport.StreamContext;
|
|
||||||
import org.briarproject.api.transport.TagRecogniser;
|
|
||||||
import org.briarproject.api.transport.TemporarySecret;
|
|
||||||
import org.briarproject.util.ByteUtils;
|
|
||||||
import org.hamcrest.Description;
|
|
||||||
import org.jmock.Expectations;
|
|
||||||
import org.jmock.Mockery;
|
|
||||||
import org.jmock.api.Action;
|
|
||||||
import org.jmock.api.Invocation;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
|
||||||
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
public class KeyRotationIntegrationTest extends BriarTestCase {
|
|
||||||
|
|
||||||
private static final long EPOCH = 1000L * 1000L * 1000L * 1000L;
|
|
||||||
private static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
|
|
||||||
private static final int ROTATION_PERIOD =
|
|
||||||
MAX_CLOCK_DIFFERENCE + MAX_LATENCY;
|
|
||||||
|
|
||||||
private final ContactId contactId;
|
|
||||||
private final TransportId transportId;
|
|
||||||
private final byte[] secret0, secret1, secret2, secret3, secret4;
|
|
||||||
private final byte[] key0, key1, key2, key3, key4;
|
|
||||||
private final SecretKey k0, k1, k2, k3, k4;
|
|
||||||
private final byte[] initialSecret;
|
|
||||||
|
|
||||||
public KeyRotationIntegrationTest() {
|
|
||||||
contactId = new ContactId(234);
|
|
||||||
transportId = new TransportId("id");
|
|
||||||
secret0 = new byte[32];
|
|
||||||
secret1 = new byte[32];
|
|
||||||
secret2 = new byte[32];
|
|
||||||
secret3 = new byte[32];
|
|
||||||
secret4 = new byte[32];
|
|
||||||
for (int i = 0; i < secret0.length; i++) secret0[i] = 1;
|
|
||||||
for (int i = 0; i < secret1.length; i++) secret1[i] = 2;
|
|
||||||
for (int i = 0; i < secret2.length; i++) secret2[i] = 3;
|
|
||||||
for (int i = 0; i < secret3.length; i++) secret3[i] = 4;
|
|
||||||
for (int i = 0; i < secret4.length; i++) secret4[i] = 5;
|
|
||||||
key0 = new byte[32];
|
|
||||||
key1 = new byte[32];
|
|
||||||
key2 = new byte[32];
|
|
||||||
key3 = new byte[32];
|
|
||||||
key4 = new byte[32];
|
|
||||||
k0 = new SecretKey(key0);
|
|
||||||
k1 = new SecretKey(key1);
|
|
||||||
k2 = new SecretKey(key2);
|
|
||||||
k3 = new SecretKey(key3);
|
|
||||||
k4 = new SecretKey(key4);
|
|
||||||
for (int i = 0; i < key0.length; i++) key0[i] = 1;
|
|
||||||
for (int i = 0; i < key1.length; i++) key1[i] = 2;
|
|
||||||
for (int i = 0; i < key2.length; i++) key2[i] = 3;
|
|
||||||
for (int i = 0; i < key3.length; i++) key3[i] = 4;
|
|
||||||
for (int i = 0; i < key4.length; i++) key4[i] = 5;
|
|
||||||
initialSecret = new byte[32];
|
|
||||||
for (int i = 0; i < initialSecret.length; i++) initialSecret[i] = 123;
|
|
||||||
}
|
|
||||||
|
|
||||||
@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 EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
final Clock clock = context.mock(Clock.class);
|
|
||||||
final Timer timer = context.mock(Timer.class);
|
|
||||||
|
|
||||||
final TagRecogniser tagRecogniser = new TagRecogniserImpl(crypto, db);
|
|
||||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
|
||||||
eventBus, tagRecogniser, clock, timer);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
// start()
|
|
||||||
oneOf(eventBus).addListener(with(any(EventListener.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(keyManager),
|
|
||||||
with(any(long.class)), with(any(long.class)));
|
|
||||||
// stop()
|
|
||||||
oneOf(eventBus).removeListener(with(any(EventListener.class)));
|
|
||||||
oneOf(timer).cancel();
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertTrue(keyManager.start());
|
|
||||||
keyManager.stop();
|
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEndpointAdded() throws Exception {
|
|
||||||
Mockery context = new Mockery();
|
|
||||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
|
||||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
final EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
final Clock clock = context.mock(Clock.class);
|
|
||||||
final Timer timer = context.mock(Timer.class);
|
|
||||||
|
|
||||||
final TagRecogniser tagRecogniser = new TagRecogniserImpl(crypto, db);
|
|
||||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
|
||||||
eventBus, tagRecogniser, clock, timer);
|
|
||||||
|
|
||||||
// The secrets for periods 0 - 2 should be derived
|
|
||||||
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(eventBus).addListener(with(any(EventListener.class)));
|
|
||||||
oneOf(db).getSecrets();
|
|
||||||
will(returnValue(Collections.emptyList()));
|
|
||||||
oneOf(db).getTransportLatencies();
|
|
||||||
will(returnValue(Collections.singletonMap(transportId,
|
|
||||||
MAX_LATENCY)));
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH));
|
|
||||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
|
||||||
with(any(long.class)), with(any(long.class)));
|
|
||||||
// endpointAdded() during rotation period 1
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH));
|
|
||||||
oneOf(crypto).deriveNextSecret(initialSecret, 0);
|
|
||||||
will(returnValue(secret0));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret0, 1);
|
|
||||||
will(returnValue(secret1));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
|
||||||
will(returnValue(secret2));
|
|
||||||
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
|
|
||||||
// The recogniser should derive the tags for period 0
|
|
||||||
oneOf(crypto).deriveTagKey(secret0, false);
|
|
||||||
will(returnValue(k0));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 1
|
|
||||||
oneOf(crypto).deriveTagKey(secret1, false);
|
|
||||||
will(returnValue(k1));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 2
|
|
||||||
oneOf(crypto).deriveTagKey(secret2, false);
|
|
||||||
will(returnValue(k2));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// stop()
|
|
||||||
// The recogniser should derive the tags for period 0
|
|
||||||
oneOf(crypto).deriveTagKey(secret0, false);
|
|
||||||
will(returnValue(k0));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 1
|
|
||||||
oneOf(crypto).deriveTagKey(secret1, false);
|
|
||||||
will(returnValue(k1));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 2
|
|
||||||
oneOf(crypto).deriveTagKey(secret2, false);
|
|
||||||
will(returnValue(k2));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// Remove the listener and stop the timer
|
|
||||||
oneOf(eventBus).removeListener(with(any(EventListener.class)));
|
|
||||||
oneOf(timer).cancel();
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertTrue(keyManager.start());
|
|
||||||
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret);
|
|
||||||
keyManager.stop();
|
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEndpointAddedAndGetConnectionContext() throws Exception {
|
|
||||||
Mockery context = new Mockery();
|
|
||||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
|
||||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
final EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
final Clock clock = context.mock(Clock.class);
|
|
||||||
final Timer timer = context.mock(Timer.class);
|
|
||||||
|
|
||||||
final TagRecogniser tagRecogniser = new TagRecogniserImpl(crypto, db);
|
|
||||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
|
||||||
eventBus, tagRecogniser, clock, timer);
|
|
||||||
|
|
||||||
// The secrets for periods 0 - 2 should be derived
|
|
||||||
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(eventBus).addListener(with(any(EventListener.class)));
|
|
||||||
oneOf(db).getSecrets();
|
|
||||||
will(returnValue(Collections.emptyList()));
|
|
||||||
oneOf(db).getTransportLatencies();
|
|
||||||
will(returnValue(Collections.singletonMap(transportId,
|
|
||||||
MAX_LATENCY)));
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH));
|
|
||||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
|
||||||
with(any(long.class)), with(any(long.class)));
|
|
||||||
// endpointAdded() during rotation period 1
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH));
|
|
||||||
oneOf(crypto).deriveNextSecret(initialSecret, 0);
|
|
||||||
will(returnValue(secret0));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret0, 1);
|
|
||||||
will(returnValue(secret1));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
|
||||||
will(returnValue(secret2));
|
|
||||||
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
|
|
||||||
// The recogniser should derive the tags for period 0
|
|
||||||
oneOf(crypto).deriveTagKey(secret0, false);
|
|
||||||
will(returnValue(k0));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 1
|
|
||||||
oneOf(crypto).deriveTagKey(secret1, false);
|
|
||||||
will(returnValue(k1));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 2
|
|
||||||
oneOf(crypto).deriveTagKey(secret2, false);
|
|
||||||
will(returnValue(k2));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// getConnectionContext()
|
|
||||||
oneOf(db).incrementStreamCounter(contactId, transportId, 1);
|
|
||||||
will(returnValue(0L));
|
|
||||||
// stop()
|
|
||||||
// The recogniser should derive the tags for period 0
|
|
||||||
oneOf(crypto).deriveTagKey(secret0, false);
|
|
||||||
will(returnValue(k0));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 1
|
|
||||||
oneOf(crypto).deriveTagKey(secret1, false);
|
|
||||||
will(returnValue(k1));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 2
|
|
||||||
oneOf(crypto).deriveTagKey(secret2, false);
|
|
||||||
will(returnValue(k2));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// Remove the listener and stop the timer
|
|
||||||
oneOf(eventBus).removeListener(with(any(EventListener.class)));
|
|
||||||
oneOf(timer).cancel();
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertTrue(keyManager.start());
|
|
||||||
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret);
|
|
||||||
StreamContext ctx =
|
|
||||||
keyManager.getStreamContext(contactId, transportId);
|
|
||||||
assertNotNull(ctx);
|
|
||||||
assertEquals(contactId, ctx.getContactId());
|
|
||||||
assertEquals(transportId, ctx.getTransportId());
|
|
||||||
assertArrayEquals(secret1, ctx.getSecret());
|
|
||||||
assertEquals(0, ctx.getStreamNumber());
|
|
||||||
assertEquals(true, ctx.getAlice());
|
|
||||||
keyManager.stop();
|
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEndpointAddedAndAcceptConnection() throws Exception {
|
|
||||||
Mockery context = new Mockery();
|
|
||||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
|
||||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
final EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
final Clock clock = context.mock(Clock.class);
|
|
||||||
final Timer timer = context.mock(Timer.class);
|
|
||||||
|
|
||||||
final TagRecogniser tagRecogniser = new TagRecogniserImpl(crypto, db);
|
|
||||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
|
||||||
eventBus, tagRecogniser, clock, timer);
|
|
||||||
|
|
||||||
// The secrets for periods 0 - 2 should be derived
|
|
||||||
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(eventBus).addListener(with(any(EventListener.class)));
|
|
||||||
oneOf(db).getSecrets();
|
|
||||||
will(returnValue(Collections.emptyList()));
|
|
||||||
oneOf(db).getTransportLatencies();
|
|
||||||
will(returnValue(Collections.singletonMap(transportId,
|
|
||||||
MAX_LATENCY)));
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH));
|
|
||||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
|
||||||
with(any(long.class)), with(any(long.class)));
|
|
||||||
// endpointAdded() during rotation period 1
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH));
|
|
||||||
oneOf(crypto).deriveNextSecret(initialSecret, 0);
|
|
||||||
will(returnValue(secret0));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret0, 1);
|
|
||||||
will(returnValue(secret1));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
|
||||||
will(returnValue(secret2));
|
|
||||||
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
|
|
||||||
// The recogniser should derive the tags for period 0
|
|
||||||
oneOf(crypto).deriveTagKey(secret0, false);
|
|
||||||
will(returnValue(k0));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 1
|
|
||||||
oneOf(crypto).deriveTagKey(secret1, false);
|
|
||||||
will(returnValue(k1));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 2
|
|
||||||
oneOf(crypto).deriveTagKey(secret2, false);
|
|
||||||
will(returnValue(k2));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// acceptConnection()
|
|
||||||
oneOf(crypto).deriveTagKey(secret2, false);
|
|
||||||
will(returnValue(k2));
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
|
||||||
with(16L));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
oneOf(db).setReorderingWindow(contactId, transportId, 2, 1,
|
|
||||||
new byte[] {0, 1, 0, 0});
|
|
||||||
// stop()
|
|
||||||
// The recogniser should derive the tags for period 0
|
|
||||||
oneOf(crypto).deriveTagKey(secret0, false);
|
|
||||||
will(returnValue(k0));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 1
|
|
||||||
oneOf(crypto).deriveTagKey(secret1, false);
|
|
||||||
will(returnValue(k1));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the updated tags for period 2
|
|
||||||
oneOf(crypto).deriveTagKey(secret2, false);
|
|
||||||
will(returnValue(k2));
|
|
||||||
for (int i = 1; i < 17; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// Remove the listener and stop the timer
|
|
||||||
oneOf(eventBus).removeListener(with(any(EventListener.class)));
|
|
||||||
oneOf(timer).cancel();
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertTrue(keyManager.start());
|
|
||||||
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret);
|
|
||||||
// Recognise the tag for connection 0 in period 2
|
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
|
||||||
encodeTag(tag, key2, 0);
|
|
||||||
StreamContext ctx = tagRecogniser.recogniseTag(transportId, tag);
|
|
||||||
assertNotNull(ctx);
|
|
||||||
assertEquals(contactId, ctx.getContactId());
|
|
||||||
assertEquals(transportId, ctx.getTransportId());
|
|
||||||
assertArrayEquals(secret2, ctx.getSecret());
|
|
||||||
assertEquals(0, ctx.getStreamNumber());
|
|
||||||
assertEquals(true, ctx.getAlice());
|
|
||||||
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 EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
final Clock clock = context.mock(Clock.class);
|
|
||||||
final Timer timer = context.mock(Timer.class);
|
|
||||||
|
|
||||||
final TagRecogniser tagRecogniser = new TagRecogniserImpl(crypto, db);
|
|
||||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
|
||||||
eventBus, tagRecogniser, clock, timer);
|
|
||||||
|
|
||||||
// The DB contains the 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(eventBus).addListener(with(any(EventListener.class)));
|
|
||||||
oneOf(db).getSecrets();
|
|
||||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
|
||||||
oneOf(db).getTransportLatencies();
|
|
||||||
will(returnValue(Collections.singletonMap(transportId,
|
|
||||||
MAX_LATENCY)));
|
|
||||||
// The current time is the epoch, the start of period 1
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH));
|
|
||||||
// The recogniser should derive the tags for period 0
|
|
||||||
oneOf(crypto).deriveTagKey(secret0, false);
|
|
||||||
will(returnValue(k0));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 1
|
|
||||||
oneOf(crypto).deriveTagKey(secret1, false);
|
|
||||||
will(returnValue(k1));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 2
|
|
||||||
oneOf(crypto).deriveTagKey(secret2, false);
|
|
||||||
will(returnValue(k2));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// Start the timer
|
|
||||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
|
||||||
with(any(long.class)), with(any(long.class)));
|
|
||||||
// stop()
|
|
||||||
// The recogniser should remove the tags for period 0
|
|
||||||
oneOf(crypto).deriveTagKey(secret0, false);
|
|
||||||
will(returnValue(k0));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 1
|
|
||||||
oneOf(crypto).deriveTagKey(secret1, false);
|
|
||||||
will(returnValue(k1));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 2
|
|
||||||
oneOf(crypto).deriveTagKey(secret2, false);
|
|
||||||
will(returnValue(k2));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// Remove the listener and stop the timer
|
|
||||||
oneOf(eventBus).removeListener(with(any(EventListener.class)));
|
|
||||||
oneOf(timer).cancel();
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertTrue(keyManager.start());
|
|
||||||
keyManager.stop();
|
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLoadSecretsAtStartOfPeriod2() throws Exception {
|
|
||||||
Mockery context = new Mockery();
|
|
||||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
|
||||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
final EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
final Clock clock = context.mock(Clock.class);
|
|
||||||
final Timer timer = context.mock(Timer.class);
|
|
||||||
|
|
||||||
final TagRecogniser tagRecogniser = new TagRecogniserImpl(crypto, db);
|
|
||||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
|
||||||
eventBus, tagRecogniser, clock, timer);
|
|
||||||
|
|
||||||
// The DB contains the 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);
|
|
||||||
// The secret for period 3 should be derived and stored
|
|
||||||
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
// start()
|
|
||||||
oneOf(eventBus).addListener(with(any(EventListener.class)));
|
|
||||||
oneOf(db).getSecrets();
|
|
||||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
|
||||||
oneOf(db).getTransportLatencies();
|
|
||||||
will(returnValue(Collections.singletonMap(transportId,
|
|
||||||
MAX_LATENCY)));
|
|
||||||
// The current time is the start of period 2
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH + ROTATION_PERIOD));
|
|
||||||
// The secret for period 3 should be derived and stored
|
|
||||||
oneOf(crypto).deriveNextSecret(secret0, 1);
|
|
||||||
will(returnValue(secret1));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
|
||||||
will(returnValue(secret2));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret2, 3);
|
|
||||||
will(returnValue(secret3));
|
|
||||||
oneOf(db).addSecrets(Arrays.asList(s3));
|
|
||||||
// The recogniser should derive the tags for period 1
|
|
||||||
oneOf(crypto).deriveTagKey(secret1, false);
|
|
||||||
will(returnValue(k1));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 2
|
|
||||||
oneOf(crypto).deriveTagKey(secret2, false);
|
|
||||||
will(returnValue(k2));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 3
|
|
||||||
oneOf(crypto).deriveTagKey(secret3, false);
|
|
||||||
will(returnValue(k3));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k3),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// Start the timer
|
|
||||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
|
||||||
with(any(long.class)), with(any(long.class)));
|
|
||||||
// stop()
|
|
||||||
// The recogniser should derive the tags for period 1
|
|
||||||
oneOf(crypto).deriveTagKey(secret1, false);
|
|
||||||
will(returnValue(k1));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 2
|
|
||||||
oneOf(crypto).deriveTagKey(secret2, false);
|
|
||||||
will(returnValue(k2));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should remove the tags for period 3
|
|
||||||
oneOf(crypto).deriveTagKey(secret3, false);
|
|
||||||
will(returnValue(k3));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k3),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// Remove the listener and stop the timer
|
|
||||||
oneOf(eventBus).removeListener(with(any(EventListener.class)));
|
|
||||||
oneOf(timer).cancel();
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertTrue(keyManager.start());
|
|
||||||
keyManager.stop();
|
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLoadSecretsAtEndOfPeriod3() throws Exception {
|
|
||||||
Mockery context = new Mockery();
|
|
||||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
|
||||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
final EventBus eventBus = context.mock(EventBus.class);
|
|
||||||
final Clock clock = context.mock(Clock.class);
|
|
||||||
final Timer timer = context.mock(Timer.class);
|
|
||||||
|
|
||||||
final TagRecogniser tagRecogniser = new TagRecogniserImpl(crypto, db);
|
|
||||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
|
||||||
eventBus, tagRecogniser, clock, timer);
|
|
||||||
|
|
||||||
// The DB contains the 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);
|
|
||||||
// The secrets for periods 3 and 4 should be derived and stored
|
|
||||||
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3);
|
|
||||||
final TemporarySecret s4 = new TemporarySecret(ep, 4, secret4);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
// start()
|
|
||||||
oneOf(eventBus).addListener(with(any(EventListener.class)));
|
|
||||||
oneOf(db).getSecrets();
|
|
||||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
|
||||||
oneOf(db).getTransportLatencies();
|
|
||||||
will(returnValue(Collections.singletonMap(transportId,
|
|
||||||
MAX_LATENCY)));
|
|
||||||
// The current time is the end of period 3
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(EPOCH + 3 * ROTATION_PERIOD - 1));
|
|
||||||
// The secrets for periods 3 and 4 should be derived from secret 1
|
|
||||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
|
||||||
will(returnValue(secret2));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret2, 3);
|
|
||||||
will(returnValue(secret3));
|
|
||||||
oneOf(crypto).deriveNextSecret(secret3, 4);
|
|
||||||
will(returnValue(secret4));
|
|
||||||
// The new secrets should be stored
|
|
||||||
oneOf(db).addSecrets(Arrays.asList(s3, s4));
|
|
||||||
// The recogniser should derive the tags for period 2
|
|
||||||
oneOf(crypto).deriveTagKey(secret2, false);
|
|
||||||
will(returnValue(k2));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 3
|
|
||||||
oneOf(crypto).deriveTagKey(secret3, false);
|
|
||||||
will(returnValue(k3));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k3),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 4
|
|
||||||
oneOf(crypto).deriveTagKey(secret4, false);
|
|
||||||
will(returnValue(k4));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k4),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// Start the timer
|
|
||||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
|
||||||
with(any(long.class)), with(any(long.class)));
|
|
||||||
// stop()
|
|
||||||
// The recogniser should derive the tags for period 2
|
|
||||||
oneOf(crypto).deriveTagKey(secret2, false);
|
|
||||||
will(returnValue(k2));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should remove the tags for period 3
|
|
||||||
oneOf(crypto).deriveTagKey(secret3, false);
|
|
||||||
will(returnValue(k3));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k3),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// The recogniser should derive the tags for period 4
|
|
||||||
oneOf(crypto).deriveTagKey(secret4, false);
|
|
||||||
will(returnValue(k4));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k4),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// Remove the listener and stop the timer
|
|
||||||
oneOf(eventBus).removeListener(with(any(EventListener.class)));
|
|
||||||
oneOf(timer).cancel();
|
|
||||||
}});
|
|
||||||
|
|
||||||
assertTrue(keyManager.start());
|
|
||||||
keyManager.stop();
|
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void encodeTag(byte[] tag, byte[] rawKey, long streamNumber) {
|
|
||||||
// Encode a fake tag based on the key and stream number
|
|
||||||
System.arraycopy(rawKey, 0, tag, 0, tag.length);
|
|
||||||
ByteUtils.writeUint32(streamNumber, tag, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class EncodeTagAction implements Action {
|
|
||||||
|
|
||||||
public void describeTo(Description description) {
|
|
||||||
description.appendText("Encodes a tag");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object invoke(Invocation invocation) throws Throwable {
|
|
||||||
byte[] tag = (byte[]) invocation.getParameter(0);
|
|
||||||
SecretKey key = (SecretKey) invocation.getParameter(1);
|
|
||||||
long streamNumber = (Long) invocation.getParameter(2);
|
|
||||||
encodeTag(tag, key.getBytes(), streamNumber);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
package org.briarproject.transport;
|
package org.briarproject.transport;
|
||||||
|
|
||||||
|
import org.briarproject.BriarTestCase;
|
||||||
|
import org.briarproject.api.transport.TransportConstants;
|
||||||
|
import org.briarproject.transport.ReorderingWindow.Change;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
import org.briarproject.BriarTestCase;
|
import org.briarproject.BriarTestCase;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -13,148 +18,102 @@ import static org.junit.Assert.assertFalse;
|
|||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.briarproject.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
|
||||||
public class ReorderingWindowTest extends BriarTestCase {
|
public class ReorderingWindowTest extends BriarTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWindowSliding() {
|
public void testBitmapConversion() {
|
||||||
ReorderingWindow w = new ReorderingWindow();
|
Random random = new Random();
|
||||||
for (int i = 0; i < 100; i++) {
|
|
||||||
assertFalse(w.isSeen(i));
|
|
||||||
w.setSeen(i);
|
|
||||||
assertTrue(w.isSeen(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWindowJumping() {
|
|
||||||
ReorderingWindow w = new ReorderingWindow();
|
|
||||||
for (int i = 0; i < 100; i += 13) {
|
|
||||||
assertFalse(w.isSeen(i));
|
|
||||||
w.setSeen(i);
|
|
||||||
assertTrue(w.isSeen(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWindowUpperLimit() {
|
|
||||||
ReorderingWindow w = new ReorderingWindow();
|
|
||||||
// Centre is 0, highest value in window is 15
|
|
||||||
w.setSeen(15);
|
|
||||||
// Centre is 16, highest value in window is 31
|
|
||||||
w.setSeen(31);
|
|
||||||
try {
|
|
||||||
// Centre is 32, highest value in window is 47
|
|
||||||
w.setSeen(48);
|
|
||||||
fail();
|
|
||||||
} catch (IllegalArgumentException expected) {}
|
|
||||||
// Centre is max - 1, highest value in window is max
|
|
||||||
byte[] bitmap = new byte[REORDERING_WINDOW_SIZE / 8];
|
byte[] bitmap = new byte[REORDERING_WINDOW_SIZE / 8];
|
||||||
w = new ReorderingWindow(MAX_32_BIT_UNSIGNED - 1, bitmap);
|
for (int i = 0; i < 1000; i++) {
|
||||||
assertFalse(w.isSeen(MAX_32_BIT_UNSIGNED - 1));
|
random.nextBytes(bitmap);
|
||||||
assertFalse(w.isSeen(MAX_32_BIT_UNSIGNED));
|
ReorderingWindow window = new ReorderingWindow(0L, bitmap);
|
||||||
// Values greater than max should never be allowed
|
assertArrayEquals(bitmap, window.getBitmap());
|
||||||
try {
|
}
|
||||||
w.setSeen(MAX_32_BIT_UNSIGNED + 1);
|
|
||||||
fail();
|
|
||||||
} catch (IllegalArgumentException expected) {}
|
|
||||||
w.setSeen(MAX_32_BIT_UNSIGNED);
|
|
||||||
assertTrue(w.isSeen(MAX_32_BIT_UNSIGNED));
|
|
||||||
// Centre should have moved to max + 1
|
|
||||||
assertEquals(MAX_32_BIT_UNSIGNED + 1, w.getCentre());
|
|
||||||
// The bit corresponding to max should be set
|
|
||||||
byte[] expectedBitmap = new byte[REORDERING_WINDOW_SIZE / 8];
|
|
||||||
expectedBitmap[expectedBitmap.length / 2 - 1] = 1; // 00000001
|
|
||||||
assertArrayEquals(expectedBitmap, w.getBitmap());
|
|
||||||
// Values greater than max should never be allowed even if centre > max
|
|
||||||
try {
|
|
||||||
w.setSeen(MAX_32_BIT_UNSIGNED + 1);
|
|
||||||
fail();
|
|
||||||
} catch (IllegalArgumentException expected) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWindowLowerLimit() {
|
public void testWindowSlidesWhenFirstElementIsSeen() {
|
||||||
ReorderingWindow w = new ReorderingWindow();
|
byte[] bitmap = new byte[REORDERING_WINDOW_SIZE / 8];
|
||||||
// Centre is 0, negative values should never be allowed
|
ReorderingWindow window = new ReorderingWindow(0L, bitmap);
|
||||||
try {
|
// Set the first element seen
|
||||||
w.setSeen(-1);
|
Change change = window.setSeen(0L);
|
||||||
fail();
|
// The window should slide by one element
|
||||||
} catch (IllegalArgumentException expected) {}
|
assertEquals(1L, window.getBase());
|
||||||
// Slide the window
|
assertEquals(Collections.singletonList((long) REORDERING_WINDOW_SIZE), change.getAdded());
|
||||||
w.setSeen(15);
|
assertEquals(Collections.singletonList(0L), change.getRemoved());
|
||||||
// Centre is 16, lowest value in window is 0
|
// All elements in the window should be unseen
|
||||||
w.setSeen(0);
|
assertArrayEquals(bitmap, window.getBitmap());
|
||||||
// Slide the window
|
|
||||||
w.setSeen(16);
|
|
||||||
// Centre is 17, lowest value in window is 1
|
|
||||||
w.setSeen(1);
|
|
||||||
try {
|
|
||||||
w.setSeen(0);
|
|
||||||
fail();
|
|
||||||
} catch (IllegalArgumentException expected) {}
|
|
||||||
// Slide the window
|
|
||||||
w.setSeen(25);
|
|
||||||
// Centre is 26, lowest value in window is 10
|
|
||||||
w.setSeen(10);
|
|
||||||
try {
|
|
||||||
w.setSeen(9);
|
|
||||||
fail();
|
|
||||||
} catch (IllegalArgumentException expected) {}
|
|
||||||
// Centre should still be 26
|
|
||||||
assertEquals(26, w.getCentre());
|
|
||||||
// The bits corresponding to 10, 15, 16 and 25 should be set
|
|
||||||
byte[] expectedBitmap = new byte[REORDERING_WINDOW_SIZE / 8];
|
|
||||||
expectedBitmap[0] = (byte) 134; // 10000110
|
|
||||||
expectedBitmap[1] = 1; // 00000001
|
|
||||||
assertArrayEquals(expectedBitmap, w.getBitmap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCannotSetSeenTwice() {
|
public void testWindowDoesNotSlideWhenElementBelowMidpointIsSeen() {
|
||||||
ReorderingWindow w = new ReorderingWindow();
|
byte[] bitmap = new byte[REORDERING_WINDOW_SIZE / 8];
|
||||||
w.setSeen(15);
|
ReorderingWindow window = new ReorderingWindow(0L, bitmap);
|
||||||
try {
|
// Set an element below the midpoint seen
|
||||||
w.setSeen(15);
|
Change change = window.setSeen(1L);
|
||||||
fail();
|
// The window should not slide
|
||||||
} catch (IllegalArgumentException expected) {}
|
assertEquals(0L, window.getBase());
|
||||||
|
assertEquals(Collections.emptyList(), change.getAdded());
|
||||||
|
assertEquals(Collections.singletonList(1L), change.getRemoved());
|
||||||
|
// The second element in the window should be seen
|
||||||
|
bitmap[0] = 0x40; // 0100 0000
|
||||||
|
assertArrayEquals(bitmap, window.getBitmap());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetUnseenStreamNumbers() {
|
public void testWindowSlidesWhenElementAboveMidpointIsSeen() {
|
||||||
ReorderingWindow w = new ReorderingWindow();
|
byte[] bitmap = new byte[REORDERING_WINDOW_SIZE / 8];
|
||||||
// Centre is 0; window should cover 0 to 15, inclusive, with none seen
|
ReorderingWindow window = new ReorderingWindow(0, bitmap);
|
||||||
Collection<Long> unseen = w.getUnseen();
|
long aboveMidpoint = REORDERING_WINDOW_SIZE / 2;
|
||||||
assertEquals(16, unseen.size());
|
// Set an element above the midpoint seen
|
||||||
for (int i = 0; i < 16; i++) {
|
Change change = window.setSeen(aboveMidpoint);
|
||||||
assertTrue(unseen.contains(Long.valueOf(i)));
|
// The window should slide by one element
|
||||||
assertFalse(w.isSeen(i));
|
assertEquals(1L, window.getBase());
|
||||||
}
|
assertEquals(Collections.singletonList((long) REORDERING_WINDOW_SIZE), change.getAdded());
|
||||||
w.setSeen(3);
|
assertEquals(Arrays.asList(0L, aboveMidpoint), change.getRemoved());
|
||||||
w.setSeen(4);
|
// The highest element below the midpoint should be seen
|
||||||
// Centre is 5; window should cover 0 to 20, inclusive, with two seen
|
bitmap[bitmap.length / 2 - 1] = (byte) 0x01; // 0000 0001
|
||||||
unseen = w.getUnseen();
|
assertArrayEquals(bitmap, window.getBitmap());
|
||||||
assertEquals(19, unseen.size());
|
}
|
||||||
for (int i = 0; i < 21; i++) {
|
|
||||||
if (i == 3 || i == 4) {
|
@Test
|
||||||
assertFalse(unseen.contains(Long.valueOf(i)));
|
public void testWindowSlidesUntilLowestElementIsUnseenWhenFirstElementIsSeen() {
|
||||||
assertTrue(w.isSeen(i));
|
byte[] bitmap = new byte[REORDERING_WINDOW_SIZE / 8];
|
||||||
} else {
|
ReorderingWindow window = new ReorderingWindow(0L, bitmap);
|
||||||
assertTrue(unseen.contains(Long.valueOf(i)));
|
window.setSeen(1L);
|
||||||
assertFalse(w.isSeen(i));
|
// Set the first element seen
|
||||||
}
|
Change change = window.setSeen(0L);
|
||||||
}
|
// The window should slide by two elements
|
||||||
w.setSeen(19);
|
assertEquals(2L, window.getBase());
|
||||||
// Centre is 20; window should cover 4 to 35, inclusive, with two seen
|
assertEquals(Arrays.asList((long) REORDERING_WINDOW_SIZE,
|
||||||
unseen = w.getUnseen();
|
(long) (REORDERING_WINDOW_SIZE + 1)), change.getAdded());
|
||||||
assertEquals(30, unseen.size());
|
assertEquals(Collections.singletonList(0L), change.getRemoved());
|
||||||
for (int i = 4; i < 36; i++) {
|
// All elements in the window should be unseen
|
||||||
if (i == 4 || i == 19) {
|
assertArrayEquals(bitmap, window.getBitmap());
|
||||||
assertFalse(unseen.contains(Long.valueOf(i)));
|
}
|
||||||
assertTrue(w.isSeen(i));
|
|
||||||
} else {
|
@Test
|
||||||
assertTrue(unseen.contains(Long.valueOf(i)));
|
public void testWindowSlidesUntilLowestElementIsUnseenWhenElementAboveMidpointIsSeen() {
|
||||||
assertFalse(w.isSeen(i));
|
byte[] bitmap = new byte[REORDERING_WINDOW_SIZE / 8];
|
||||||
}
|
ReorderingWindow window = new ReorderingWindow(0L, bitmap);
|
||||||
}
|
window.setSeen(1L);
|
||||||
|
long aboveMidpoint = REORDERING_WINDOW_SIZE / 2;
|
||||||
|
// Set an element above the midpoint seen
|
||||||
|
Change change = window.setSeen(aboveMidpoint);
|
||||||
|
// The window should slide by two elements
|
||||||
|
assertEquals(2L, window.getBase());
|
||||||
|
assertEquals(Arrays.asList((long) REORDERING_WINDOW_SIZE,
|
||||||
|
(long) (REORDERING_WINDOW_SIZE + 1)), change.getAdded());
|
||||||
|
assertEquals(Arrays.asList(0L, aboveMidpoint), change.getRemoved());
|
||||||
|
// The second-highest element below the midpoint should be seen
|
||||||
|
bitmap[bitmap.length / 2 - 1] = (byte) 0x02; // 0000 0010
|
||||||
|
assertArrayEquals(bitmap, window.getBitmap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.briarproject.transport;
|
||||||
|
|
||||||
|
import org.briarproject.BriarTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
public class TransportKeyManagerTest extends BriarTestCase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnitTestsExist() {
|
||||||
|
fail(); // FIXME: Write tests
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
package org.briarproject.transport;
|
|
||||||
|
|
||||||
import org.briarproject.BriarTestCase;
|
|
||||||
import org.briarproject.api.ContactId;
|
|
||||||
import org.briarproject.api.TransportId;
|
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
|
||||||
import org.briarproject.api.transport.StreamContext;
|
|
||||||
import org.briarproject.api.transport.TemporarySecret;
|
|
||||||
import org.briarproject.util.ByteUtils;
|
|
||||||
import org.hamcrest.Description;
|
|
||||||
import org.jmock.Expectations;
|
|
||||||
import org.jmock.Mockery;
|
|
||||||
import org.jmock.api.Action;
|
|
||||||
import org.jmock.api.Invocation;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
|
|
||||||
public class TransportTagRecogniserTest extends BriarTestCase {
|
|
||||||
|
|
||||||
private final ContactId contactId = new ContactId(234);
|
|
||||||
private final TransportId transportId = new TransportId("id");
|
|
||||||
private final SecretKey tagKey = new SecretKey(new byte[32]);
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAddAndRemoveSecret() {
|
|
||||||
Mockery context = new Mockery();
|
|
||||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
|
||||||
final byte[] secret = new byte[32];
|
|
||||||
new Random().nextBytes(secret);
|
|
||||||
final boolean alice = false;
|
|
||||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
// Add secret
|
|
||||||
oneOf(crypto).deriveTagKey(secret, !alice);
|
|
||||||
will(returnValue(tagKey));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(tagKey),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// Remove secret
|
|
||||||
oneOf(crypto).deriveTagKey(secret, !alice);
|
|
||||||
will(returnValue(tagKey));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(tagKey),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
}});
|
|
||||||
TemporarySecret s = new TemporarySecret(contactId, transportId, 123,
|
|
||||||
alice, 0, secret, 0, 0, new byte[4]);
|
|
||||||
TransportTagRecogniser recogniser =
|
|
||||||
new TransportTagRecogniser(crypto, db, transportId);
|
|
||||||
recogniser.addSecret(s);
|
|
||||||
recogniser.removeSecret(contactId, 0);
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRecogniseTag() throws Exception {
|
|
||||||
Mockery context = new Mockery();
|
|
||||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
|
||||||
final byte[] secret = new byte[32];
|
|
||||||
new Random().nextBytes(secret);
|
|
||||||
final boolean alice = false;
|
|
||||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
// Add secret
|
|
||||||
oneOf(crypto).deriveTagKey(secret, !alice);
|
|
||||||
will(returnValue(tagKey));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(tagKey),
|
|
||||||
with((long) i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// Recognise tag 0
|
|
||||||
oneOf(crypto).deriveTagKey(secret, !alice);
|
|
||||||
will(returnValue(tagKey));
|
|
||||||
// The window should slide to include tag 16
|
|
||||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(tagKey),
|
|
||||||
with(16L));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
// The updated window should be stored
|
|
||||||
oneOf(db).setReorderingWindow(contactId, transportId, 0, 1,
|
|
||||||
new byte[] {0, 1, 0, 0});
|
|
||||||
// Recognise tag again - no expectations
|
|
||||||
}});
|
|
||||||
TemporarySecret s = new TemporarySecret(contactId, transportId, 123,
|
|
||||||
alice, 0, secret, 0, 0, new byte[4]);
|
|
||||||
TransportTagRecogniser recogniser =
|
|
||||||
new TransportTagRecogniser(crypto, db, transportId);
|
|
||||||
recogniser.addSecret(s);
|
|
||||||
// Tag 0 should be expected
|
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
|
||||||
StreamContext ctx = recogniser.recogniseTag(tag);
|
|
||||||
assertNotNull(ctx);
|
|
||||||
assertEquals(contactId, ctx.getContactId());
|
|
||||||
assertEquals(transportId, ctx.getTransportId());
|
|
||||||
assertArrayEquals(secret, ctx.getSecret());
|
|
||||||
assertEquals(0, ctx.getStreamNumber());
|
|
||||||
assertEquals(alice, ctx.getAlice());
|
|
||||||
// Tag 0 should not be expected again
|
|
||||||
assertNull(recogniser.recogniseTag(tag));
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class EncodeTagAction implements Action {
|
|
||||||
|
|
||||||
public void describeTo(Description description) {
|
|
||||||
description.appendText("Encodes a tag");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object invoke(Invocation invocation) throws Throwable {
|
|
||||||
byte[] tag = (byte[]) invocation.getParameter(0);
|
|
||||||
long streamNumber = (Long) invocation.getParameter(2);
|
|
||||||
// Encode a fake tag based on the stream number
|
|
||||||
ByteUtils.writeUint32(streamNumber, tag, 0);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user