Compare commits

...

46 Commits

Author SHA1 Message Date
akwizgran
37d0b61d7b Bump version numbers for 1.1.7 release. 2019-05-16 16:07:15 +01:00
akwizgran
98a1ec89d7 Update translations. 2019-05-16 16:06:29 +01:00
akwizgran
a61860af85 Merge branch '1369-thread-layout' into 'master'
Adapt private group join notices to new threaded layout

Closes #1369

See merge request briar/briar!1097
2019-05-16 14:54:46 +00:00
Torsten Grote
90437f4fa4 [android] use same color for thread dividers than indentation dividers 2019-05-16 11:35:20 -03:00
Torsten Grote
06212453b2 [android] Adapt private group join notices to new threaded layout 2019-05-16 11:12:48 -03:00
akwizgran
ddc9b5b066 Merge branch '1256-adding-contacts-headless' into 'master'
Add a REST endpoint for adding contacts

Closes #1256

See merge request briar/briar!1094
2019-05-16 14:05:48 +00:00
Torsten Grote
0aa6166afa Merge branch 'account-logging' into 'master'
Log contents of account directory for debugging

See merge request briar/briar!1096
2019-05-16 11:51:46 +00:00
akwizgran
60b91bc317 Log contents of account directory for debugging. 2019-05-16 10:06:16 +01:00
akwizgran
20481a3790 Merge branch '1369-thread-layout' into 'master'
Move AuthorView up in threaded conversation

Closes #1369

See merge request briar/briar!1095
2019-05-16 08:46:39 +00:00
Torsten Grote
576934910e Merge branch '1566-equivalent-public-keys' into 'master'
Add tests for equivalent handshake public keys

See merge request briar/briar!1093
2019-05-15 20:02:52 +00:00
Torsten Grote
4abc26093e Merge branch '1556-key-manager-methods-for-pending-contacts' into 'master'
Add key manager methods for pending contacts

Closes #1556

See merge request briar/briar!1089
2019-05-15 19:57:32 +00:00
Torsten Grote
aed63746e7 [android] Move AuthorView up in threaded conversation 2019-05-15 16:33:06 -03:00
Torsten Grote
816598b631 [headless] only include alias in contact's JSON representation if it exists 2019-05-15 16:03:02 -03:00
Torsten Grote
c062c16d27 Merge branch 'merge-handshake-and-transport-keys' into 'master'
Add support for handshake keys to KeyManager

See merge request briar/briar!1088
2019-05-15 16:27:33 +00:00
Torsten Grote
435b43488a [headless] address review comments for remote contact adding 2019-05-15 12:36:33 -03:00
Torsten Grote
faa6a85148 [headless] Add tests to ensure that remote contact adding needs auth token 2019-05-15 11:28:01 -03:00
Torsten Grote
3770a9f217 [headless] make events related to adding contacts available via websocket 2019-05-15 11:28:01 -03:00
Torsten Grote
c6211be488 [bramble-core] Broadcast events when pending contacts are added or removed 2019-05-15 11:27:59 -03:00
Torsten Grote
5a73e50248 [headless] expose ContactManager methods for adding contacts remotely 2019-05-15 11:26:21 -03:00
Torsten Grote
dc6971734a [briar-core] Add a getRealHandshakeLink() method to BriarTestUtils
Also allow testOutput from briar-core to be used in briar-headless
2019-05-15 11:26:20 -03:00
Torsten Grote
69e57bee61 [bramble] Let TestUtils return a PendingContact with random state 2019-05-15 11:26:19 -03:00
Torsten Grote
af8cabbb28 [headless] update dependencies 2019-05-15 11:25:11 -03:00
akwizgran
6f31a3c2ad Merge branch 'key-pair-refactoring' into 'master'
Key pair refactoring

See merge request briar/briar!1083
2019-05-15 14:19:48 +00:00
akwizgran
d3469e3782 Merge branch '1482-startup-activity' into 'master'
Combine Password and OpenDatabase Activity into StartupActivity

Closes #1482

See merge request briar/briar!1087
2019-05-15 09:57:01 +00:00
akwizgran
9d64b186ff Add tests for hashing public keys into shared secret. 2019-05-15 10:18:13 +01:00
akwizgran
ca591b5c7b Add test for equivalent public keys. 2019-05-15 10:18:09 +01:00
akwizgran
2c4188caf5 Use lambdas for tasks requiring a manager lookup. 2019-05-14 17:59:35 +01:00
akwizgran
0b30a0786e Rename key manager methods for clarity. 2019-05-14 17:59:35 +01:00
akwizgran
f9b928c12a Annotate equals() argument as nullable. 2019-05-14 17:59:35 +01:00
akwizgran
afa0b96293 Add utility method for null checks. 2019-05-14 17:59:34 +01:00
akwizgran
dd50f4bcd4 Add key manager methods for pending contacts. 2019-05-14 17:59:34 +01:00
akwizgran
f42fc5213e Add key manager method for contacts with handshake keys. 2019-05-14 17:59:34 +01:00
akwizgran
84e2402404 Update key management terminology. 2019-05-14 17:57:23 +01:00
akwizgran
5adc9d8dbd Add handshake keys to TransportKeyManagerImpl. 2019-05-14 17:57:22 +01:00
akwizgran
3f51ad6c07 Add handshake mode to MutableTransportKeys. 2019-05-14 17:57:22 +01:00
akwizgran
1fd6d7a6d5 Use @GuardedBy annotation. 2019-05-14 17:57:22 +01:00
akwizgran
7dc4dc566f Merge handshake and transport keys. 2019-05-14 17:57:19 +01:00
akwizgran
658c63d94e Rename an argument for clarity. 2019-05-14 17:56:19 +01:00
akwizgran
ee05c32871 Allow pending contact state update not to affect any rows. 2019-05-14 17:55:38 +01:00
akwizgran
d2951eb3cd Rename key parser classes. 2019-05-14 17:26:28 +01:00
akwizgran
de8a60ea21 Use PublicKey and PrivateKey everywhere. 2019-05-14 17:26:26 +01:00
akwizgran
0e77a47cc1 Refactor key handling to use public classes. 2019-05-14 17:24:19 +01:00
Torsten Grote
22ebdd8e42 [android] Ensure keyboard is shown for entering password
in new StartupActivity and when creating a new account.
2019-05-13 11:58:07 -03:00
Torsten Grote
e37ee7ee04 [android] Use LiveEvent to communicate password validation and account deletion 2019-05-13 08:21:28 -03:00
Torsten Grote
5676e18a22 [android] StartupActivity: Address first round of review comments 2019-05-13 08:21:28 -03:00
Torsten Grote
5ece6505da [android] Combine Password and OpenDatabase Activity into StartupActivity 2019-05-13 08:21:27 -03:00
204 changed files with 4712 additions and 2886 deletions

View File

@@ -11,8 +11,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 26 targetSdkVersion 26
versionCode 10106 versionCode 10107
versionName "1.1.6" versionName "1.1.7"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

View File

@@ -9,7 +9,6 @@ import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.util.IoUtils;
import java.io.File; import java.io.File;
import java.util.HashSet; import java.util.HashSet;
@@ -20,6 +19,9 @@ import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.IoUtils.deleteFileOrDir;
import static org.briarproject.bramble.util.LogUtils.logFileOrDir;
class AndroidAccountManager extends AccountManagerImpl class AndroidAccountManager extends AccountManagerImpl
implements AccountManager { implements AccountManager {
@@ -41,6 +43,16 @@ class AndroidAccountManager extends AccountManagerImpl
appContext = app.getApplicationContext(); appContext = app.getApplicationContext();
} }
@Override
public boolean accountExists() {
boolean exists = super.accountExists();
if (!exists && LOG.isLoggable(INFO)) {
LOG.info("Account does not exist. Contents of account directory:");
logFileOrDir(LOG, INFO, getDataDir());
}
return exists;
}
// Locking: stateChangeLock // Locking: stateChangeLock
@Override @Override
@Nullable @Nullable
@@ -74,9 +86,17 @@ class AndroidAccountManager extends AccountManagerImpl
@Override @Override
public void deleteAccount() { public void deleteAccount() {
synchronized (stateChangeLock) { synchronized (stateChangeLock) {
if (LOG.isLoggable(INFO)) {
LOG.info("Contents of account directory before deleting:");
logFileOrDir(LOG, INFO, getDataDir());
}
super.deleteAccount(); super.deleteAccount();
SharedPreferences defaultPrefs = getDefaultSharedPreferences(); SharedPreferences defaultPrefs = getDefaultSharedPreferences();
deleteAppData(prefs, defaultPrefs); deleteAppData(prefs, defaultPrefs);
if (LOG.isLoggable(INFO)) {
LOG.info("Contents of account directory after deleting:");
logFileOrDir(LOG, INFO, getDataDir());
}
} }
} }
@@ -94,7 +114,7 @@ class AndroidAccountManager extends AccountManagerImpl
} }
// Delete files, except lib and shared_prefs directories // Delete files, except lib and shared_prefs directories
Set<File> files = new HashSet<>(); Set<File> files = new HashSet<>();
File dataDir = new File(appContext.getApplicationInfo().dataDir); File dataDir = getDataDir();
@Nullable @Nullable
File[] fileArray = dataDir.listFiles(); File[] fileArray = dataDir.listFiles();
if (fileArray == null) { if (fileArray == null) {
@@ -121,13 +141,17 @@ class AndroidAccountManager extends AccountManagerImpl
} }
} }
for (File file : files) { for (File file : files) {
IoUtils.deleteFileOrDir(file); deleteFileOrDir(file);
} }
// Recreate the cache dir as some OpenGL drivers expect it to exist // Recreate the cache dir as some OpenGL drivers expect it to exist
if (!new File(dataDir, "cache").mkdirs()) if (!new File(dataDir, "cache").mkdirs())
LOG.warning("Could not recreate cache dir"); LOG.warning("Could not recreate cache dir");
} }
private File getDataDir() {
return new File(appContext.getApplicationInfo().dataDir);
}
private void addIfNotNull(Set<File> files, @Nullable File file) { private void addIfNotNull(Set<File> files, @Nullable File file) {
if (file != null) files.add(file); if (file != null) files.add(file);
} }

View File

@@ -128,7 +128,7 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
will(returnValue(editor)); will(returnValue(editor));
oneOf(editor).commit(); oneOf(editor).commit();
will(returnValue(true)); will(returnValue(true));
oneOf(app).getApplicationInfo(); allowing(app).getApplicationInfo();
will(returnValue(applicationInfo)); will(returnValue(applicationInfo));
oneOf(app).getFilesDir(); oneOf(app).getFilesDir();
will(returnValue(filesDir)); will(returnValue(filesDir));

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.util.StringUtils;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
/** /**
@@ -38,7 +39,7 @@ public class Bytes implements Comparable<Bytes> {
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(@Nullable Object o) {
return o instanceof Bytes && Arrays.equals(bytes, ((Bytes) o).bytes); return o instanceof Bytes && Arrays.equals(bytes, ((Bytes) o).bytes);
} }

View File

@@ -1,6 +1,8 @@
package org.briarproject.bramble.api.client; package org.briarproject.bramble.api.client;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
@@ -96,14 +98,18 @@ public interface ClientHelper {
BdfList toList(Author a); BdfList toList(Author a);
byte[] sign(String label, BdfList toSign, byte[] privateKey) byte[] sign(String label, BdfList toSign, PrivateKey privateKey)
throws FormatException, GeneralSecurityException; throws FormatException, GeneralSecurityException;
void verifySignature(byte[] signature, String label, BdfList signed, void verifySignature(byte[] signature, String label, BdfList signed,
byte[] publicKey) throws FormatException, GeneralSecurityException; PublicKey publicKey)
throws FormatException, GeneralSecurityException;
Author parseAndValidateAuthor(BdfList author) throws FormatException; Author parseAndValidateAuthor(BdfList author) throws FormatException;
PublicKey parseAndValidateAgreementPublicKey(byte[] publicKeyBytes)
throws FormatException;
TransportProperties parseAndValidateTransportProperties( TransportProperties parseAndValidateTransportProperties(
BdfDictionary properties) throws FormatException; BdfDictionary properties) throws FormatException;

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.api.contact; package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -8,7 +9,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.util.StringUtils.toUtf8; import static org.briarproject.bramble.util.StringUtils.toUtf8;
@Immutable @Immutable
@@ -21,21 +21,17 @@ public class Contact {
@Nullable @Nullable
private final String alias; private final String alias;
@Nullable @Nullable
private final byte[] handshakePublicKey; private final PublicKey handshakePublicKey;
private final boolean verified; private final boolean verified;
public Contact(ContactId id, Author author, AuthorId localAuthorId, public Contact(ContactId id, Author author, AuthorId localAuthorId,
@Nullable String alias, @Nullable byte[] handshakePublicKey, @Nullable String alias, @Nullable PublicKey handshakePublicKey,
boolean verified) { boolean verified) {
if (alias != null) { if (alias != null) {
int aliasLength = toUtf8(alias).length; int aliasLength = toUtf8(alias).length;
if (aliasLength == 0 || aliasLength > MAX_AUTHOR_NAME_LENGTH) if (aliasLength == 0 || aliasLength > MAX_AUTHOR_NAME_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
if (handshakePublicKey != null && (handshakePublicKey.length == 0 ||
handshakePublicKey.length > MAX_PUBLIC_KEY_LENGTH)) {
throw new IllegalArgumentException();
}
this.id = id; this.id = id;
this.author = author; this.author = author;
this.localAuthorId = localAuthorId; this.localAuthorId = localAuthorId;
@@ -62,7 +58,7 @@ public class Contact {
} }
@Nullable @Nullable
public byte[] getHandshakePublicKey() { public PublicKey getHandshakePublicKey() {
return handshakePublicKey; return handshakePublicKey;
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
@@ -28,7 +29,7 @@ public class ContactId {
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(@Nullable Object o) {
return o instanceof ContactId && id == ((ContactId) o).id; return o instanceof ContactId && id == ((ContactId) o).id;
} }
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.api.contact; package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@@ -9,12 +10,12 @@ import javax.annotation.concurrent.Immutable;
public class PendingContact { public class PendingContact {
private final PendingContactId id; private final PendingContactId id;
private final byte[] publicKey; private final PublicKey publicKey;
private final String alias; private final String alias;
private final PendingContactState state; private final PendingContactState state;
private final long timestamp; private final long timestamp;
public PendingContact(PendingContactId id, byte[] publicKey, public PendingContact(PendingContactId id, PublicKey publicKey,
String alias, PendingContactState state, long timestamp) { String alias, PendingContactState state, long timestamp) {
this.id = id; this.id = id;
this.publicKey = publicKey; this.publicKey = publicKey;
@@ -27,7 +28,7 @@ public class PendingContact {
return id; return id;
} }
public byte[] getPublicKey() { public PublicKey getPublicKey() {
return publicKey; return publicKey;
} }

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
/** /**
@@ -17,9 +18,8 @@ public class PendingContactId extends UniqueId {
super(id); super(id);
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(@Nullable Object o) {
return o instanceof PendingContactId && super.equals(o); return o instanceof PendingContactId && super.equals(o);
} }
} }

View File

@@ -0,0 +1,30 @@
package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT;
/**
* Type-safe wrapper for a private key used for key agreement.
*/
@Immutable
@NotNullByDefault
public class AgreementPrivateKey extends Bytes implements PrivateKey {
public AgreementPrivateKey(byte[] encoded) {
super(encoded);
}
@Override
public String getKeyType() {
return KEY_TYPE_AGREEMENT;
}
@Override
public byte[] getEncoded() {
return getBytes();
}
}

View File

@@ -0,0 +1,35 @@
package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
/**
* Type-safe wrapper for a public key used for key agreement.
*/
@Immutable
@NotNullByDefault
public class AgreementPublicKey extends Bytes implements PublicKey {
public AgreementPublicKey(byte[] encoded) {
super(encoded);
if (encoded.length == 0 ||
encoded.length > MAX_AGREEMENT_PUBLIC_KEY_BYTES) {
throw new IllegalArgumentException();
}
}
@Override
public String getKeyType() {
return KEY_TYPE_AGREEMENT;
}
@Override
public byte[] getEncoded() {
return getBytes();
}
}

View File

@@ -55,7 +55,7 @@ public interface CryptoComponent {
* signature, to prevent it from being repurposed or colliding with a * signature, to prevent it from being repurposed or colliding with a
* signature created for another purpose * signature created for another purpose
*/ */
byte[] sign(String label, byte[] toSign, byte[] privateKey) byte[] sign(String label, byte[] toSign, PrivateKey privateKey)
throws GeneralSecurityException; throws GeneralSecurityException;
/** /**
@@ -68,7 +68,7 @@ public interface CryptoComponent {
* @return true if the signature was valid, false otherwise. * @return true if the signature was valid, false otherwise.
*/ */
boolean verifySignature(byte[] signature, String label, byte[] signed, boolean verifySignature(byte[] signature, String label, byte[] signed,
byte[] publicKey) throws GeneralSecurityException; PublicKey publicKey) throws GeneralSecurityException;
/** /**
* Returns the hash of the given inputs. The inputs are unambiguously * Returns the hash of the given inputs. The inputs are unambiguously

View File

@@ -7,11 +7,21 @@ public interface CryptoConstants {
*/ */
int MAX_AGREEMENT_PUBLIC_KEY_BYTES = 32; int MAX_AGREEMENT_PUBLIC_KEY_BYTES = 32;
/**
* The key type for agreement key pairs.
*/
String KEY_TYPE_AGREEMENT = "Curve25519";
/** /**
* The maximum length of a signature public key in bytes. * The maximum length of a signature public key in bytes.
*/ */
int MAX_SIGNATURE_PUBLIC_KEY_BYTES = 32; int MAX_SIGNATURE_PUBLIC_KEY_BYTES = 32;
/**
* The key type for signature key pairs.
*/
String KEY_TYPE_SIGNATURE = "Ed25519";
/** /**
* The maximum length of a signature in bytes. * The maximum length of a signature in bytes.
*/ */

View File

@@ -15,6 +15,8 @@ public class KeyPair {
private final PrivateKey privateKey; private final PrivateKey privateKey;
public KeyPair(PublicKey publicKey, PrivateKey privateKey) { public KeyPair(PublicKey publicKey, PrivateKey privateKey) {
if (!publicKey.getKeyType().equals(privateKey.getKeyType()))
throw new IllegalArgumentException();
this.publicKey = publicKey; this.publicKey = publicKey;
this.privateKey = privateKey; this.privateKey = privateKey;
} }

View File

@@ -8,6 +8,11 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault @NotNullByDefault
public interface PrivateKey { public interface PrivateKey {
/**
* Returns the type of this key pair.
*/
String getKeyType();
/** /**
* Returns the encoded representation of this key. * Returns the encoded representation of this key.
*/ */

View File

@@ -8,6 +8,11 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault @NotNullByDefault
public interface PublicKey { public interface PublicKey {
/**
* Returns the type of this key pair.
*/
String getKeyType();
/** /**
* Returns the encoded representation of this key. * Returns the encoded representation of this key.
*/ */

View File

@@ -0,0 +1,30 @@
package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
/**
* Type-safe wrapper for a public key used for signing.
*/
@Immutable
@NotNullByDefault
public class SignaturePrivateKey extends Bytes implements PrivateKey {
public SignaturePrivateKey(byte[] bytes) {
super(bytes);
}
@Override
public String getKeyType() {
return KEY_TYPE_SIGNATURE;
}
@Override
public byte[] getEncoded() {
return getBytes();
}
}

View File

@@ -0,0 +1,35 @@
package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_PUBLIC_KEY_BYTES;
/**
* Type-safe wrapper for a public key used for verifying signatures.
*/
@Immutable
@NotNullByDefault
public class SignaturePublicKey extends Bytes implements PublicKey {
public SignaturePublicKey(byte[] encoded) {
super(encoded);
if (encoded.length == 0 ||
encoded.length > MAX_SIGNATURE_PUBLIC_KEY_BYTES) {
throw new IllegalArgumentException();
}
}
@Override
public String getKeyType() {
return KEY_TYPE_SIGNATURE;
}
@Override
public byte[] getEncoded() {
return getBytes();
}
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.api.crypto; package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.HandshakeKeys;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
/** /**
@@ -11,35 +10,29 @@ import org.briarproject.bramble.api.transport.TransportKeys;
public interface TransportCrypto { public interface TransportCrypto {
/** /**
* Derives initial transport keys for the given transport in the given * Derives initial rotation mode transport keys for the given transport in
* time period from the given root key. * the given time period from the given root key.
* *
* @param alice whether the keys are for use by Alice or Bob. * @param alice Whether the keys are for use by Alice or Bob
* @param active whether the keys are usable for outgoing streams. * @param active Whether the keys are usable for outgoing streams
*/ */
TransportKeys deriveTransportKeys(TransportId t, SecretKey rootKey, TransportKeys deriveRotationKeys(TransportId t, SecretKey rootKey,
long timePeriod, boolean alice, boolean active); long timePeriod, boolean alice, boolean active);
/**
* Rotates the given transport keys to the given time period. If the keys
* are for the given period or any later period they are not rotated.
*/
TransportKeys rotateTransportKeys(TransportKeys k, long timePeriod);
/** /**
* Derives handshake keys for the given transport in the given time period * Derives handshake keys for the given transport in the given time period
* from the given root key. * from the given root key.
* *
* @param alice whether the keys are for use by Alice or Bob. * @param alice Whether the keys are for use by Alice or Bob
*/ */
HandshakeKeys deriveHandshakeKeys(TransportId t, SecretKey rootKey, TransportKeys deriveHandshakeKeys(TransportId t, SecretKey rootKey,
long timePeriod, boolean alice); long timePeriod, boolean alice);
/** /**
* Updates the given handshake keys to the given time period. If the keys * Updates the given transport keys to the given time period. If the keys
* are for the given period or any later period they are not updated. * are for the given period or any later period they are not updated.
*/ */
HandshakeKeys updateHandshakeKeys(HandshakeKeys k, long timePeriod); TransportKeys updateTransportKeys(TransportKeys k, long timePeriod);
/** /**
* Encodes the pseudo-random tag that is used to recognise a stream. * Encodes the pseudo-random tag that is used to recognise a stream.

View File

@@ -4,6 +4,8 @@ import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactId; import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
@@ -22,11 +24,8 @@ import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.sync.Offer; import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.Request; import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.validation.MessageState; import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.transport.HandshakeKeySet; import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.HandshakeKeySetId;
import org.briarproject.bramble.api.transport.HandshakeKeys;
import org.briarproject.bramble.api.transport.TransportKeySet; import org.briarproject.bramble.api.transport.TransportKeySet;
import org.briarproject.bramble.api.transport.TransportKeySetId;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
import java.util.Collection; import java.util.Collection;
@@ -113,20 +112,6 @@ public interface DatabaseComponent {
*/ */
void addGroup(Transaction txn, Group g) throws DbException; void addGroup(Transaction txn, Group g) throws DbException;
/**
* Stores the given handshake keys for the given contact and returns a
* key set ID.
*/
HandshakeKeySetId addHandshakeKeys(Transaction txn, ContactId c,
HandshakeKeys k) throws DbException;
/**
* Stores the given handshake keys for the given pending contact and
* returns a key set ID.
*/
HandshakeKeySetId addHandshakeKeys(Transaction txn, PendingContactId p,
HandshakeKeys k) throws DbException;
/** /**
* Stores an identity. * Stores an identity.
*/ */
@@ -154,7 +139,14 @@ public interface DatabaseComponent {
* Stores the given transport keys for the given contact and returns a * Stores the given transport keys for the given contact and returns a
* key set ID. * key set ID.
*/ */
TransportKeySetId addTransportKeys(Transaction txn, ContactId c, KeySetId addTransportKeys(Transaction txn, ContactId c, TransportKeys k)
throws DbException;
/**
* Stores the given transport keys for the given pending contact and
* returns a key set ID.
*/
KeySetId addTransportKeys(Transaction txn, PendingContactId p,
TransportKeys k) throws DbException; TransportKeys k) throws DbException;
/** /**
@@ -274,7 +266,7 @@ public interface DatabaseComponent {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<ContactId> getContacts(Transaction txn, AuthorId a) Collection<ContactId> getContacts(Transaction txn, AuthorId local)
throws DbException; throws DbException;
/** /**
@@ -308,14 +300,6 @@ public interface DatabaseComponent {
Visibility getGroupVisibility(Transaction txn, ContactId c, GroupId g) Visibility getGroupVisibility(Transaction txn, ContactId c, GroupId g)
throws DbException; throws DbException;
/**
* Returns all handshake keys for the given transport.
* <p/>
* Read-only.
*/
Collection<HandshakeKeySet> getHandshakeKeys(Transaction txn, TransportId t)
throws DbException;
/** /**
* Returns the identity for the local pseudonym with the given ID. * Returns the identity for the local pseudonym with the given ID.
* <p/> * <p/>
@@ -487,17 +471,11 @@ public interface DatabaseComponent {
Collection<TransportKeySet> getTransportKeys(Transaction txn, TransportId t) Collection<TransportKeySet> getTransportKeys(Transaction txn, TransportId t)
throws DbException; throws DbException;
/**
* Increments the outgoing stream counter for the given handshake keys.
*/
void incrementStreamCounter(Transaction txn, TransportId t,
HandshakeKeySetId k) throws DbException;
/** /**
* Increments the outgoing stream counter for the given transport keys. * Increments the outgoing stream counter for the given transport keys.
*/ */
void incrementStreamCounter(Transaction txn, TransportId t, void incrementStreamCounter(Transaction txn, TransportId t, KeySetId k)
TransportKeySetId k) throws DbException; throws DbException;
/** /**
* Merges the given metadata with the existing metadata for the given * Merges the given metadata with the existing metadata for the given
@@ -552,12 +530,6 @@ public interface DatabaseComponent {
*/ */
void removeGroup(Transaction txn, Group g) throws DbException; void removeGroup(Transaction txn, Group g) throws DbException;
/**
* Removes the given handshake keys from the database.
*/
void removeHandshakeKeys(Transaction txn, TransportId t,
HandshakeKeySetId k) throws DbException;
/** /**
* Removes an identity (and all associated state) from the database. * Removes an identity (and all associated state) from the database.
*/ */
@@ -582,8 +554,8 @@ public interface DatabaseComponent {
/** /**
* Removes the given transport keys from the database. * Removes the given transport keys from the database.
*/ */
void removeTransportKeys(Transaction txn, TransportId t, void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
TransportKeySetId k) throws DbException; throws DbException;
/** /**
* Marks the given contact as verified. * Marks the given contact as verified.
@@ -622,35 +594,20 @@ public interface DatabaseComponent {
/** /**
* Sets the handshake key pair for the identity with the given ID. * Sets the handshake key pair for the identity with the given ID.
*/ */
void setHandshakeKeyPair(Transaction txn, AuthorId local, byte[] publicKey, void setHandshakeKeyPair(Transaction txn, AuthorId local,
byte[] privateKey) throws DbException; PublicKey publicKey, PrivateKey privateKey) throws DbException;
/** /**
* Sets the reordering window for the given transport key set in the given * Sets the reordering window for the given transport keys in the given
* time period. * time period.
*/ */
void setReorderingWindow(Transaction txn, TransportKeySetId k, void setReorderingWindow(Transaction txn, KeySetId k, TransportId t,
TransportId t, long timePeriod, long base, byte[] bitmap) long timePeriod, long base, byte[] bitmap) throws DbException;
throws DbException;
/**
* Sets the reordering window for the given handshake key set in the given
* time period.
*/
void setReorderingWindow(Transaction txn, HandshakeKeySetId k,
TransportId t, long timePeriod, long base, byte[] bitmap)
throws DbException;
/** /**
* Marks the given transport keys as usable for outgoing streams. * Marks the given transport keys as usable for outgoing streams.
*/ */
void setTransportKeysActive(Transaction txn, TransportId t, void setTransportKeysActive(Transaction txn, TransportId t, KeySetId k)
TransportKeySetId k) throws DbException;
/**
* Stores the given handshake keys, deleting any keys they have replaced.
*/
void updateHandshakeKeys(Transaction txn, Collection<HandshakeKeySet> keys)
throws DbException; throws DbException;
/** /**

View File

@@ -1,13 +1,14 @@
package org.briarproject.bramble.api.identity; package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.Nameable; import org.briarproject.bramble.api.Nameable;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.StringUtils;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.util.StringUtils.toUtf8;
/** /**
* A pseudonym for a user. * A pseudonym for a user.
@@ -24,14 +25,14 @@ public class Author implements Nameable {
private final AuthorId id; private final AuthorId id;
private final int formatVersion; private final int formatVersion;
private final String name; private final String name;
private final byte[] publicKey; private final PublicKey publicKey;
public Author(AuthorId id, int formatVersion, String name, public Author(AuthorId id, int formatVersion, String name,
byte[] publicKey) { PublicKey publicKey) {
int nameLength = StringUtils.toUtf8(name).length; int nameLength = toUtf8(name).length;
if (nameLength == 0 || nameLength > MAX_AUTHOR_NAME_LENGTH) if (nameLength == 0 || nameLength > MAX_AUTHOR_NAME_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if (publicKey.length == 0 || publicKey.length > MAX_PUBLIC_KEY_LENGTH) if (!publicKey.getKeyType().equals(KEY_TYPE_SIGNATURE))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
this.id = id; this.id = id;
this.formatVersion = formatVersion; this.formatVersion = formatVersion;
@@ -63,7 +64,7 @@ public class Author implements Nameable {
/** /**
* Returns the public key used to verify the pseudonym's signatures. * Returns the public key used to verify the pseudonym's signatures.
*/ */
public byte[] getPublicKey() { public PublicKey getPublicKey() {
return publicKey; return publicKey;
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.api.identity; package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault @NotNullByDefault
@@ -9,12 +10,12 @@ public interface AuthorFactory {
* Creates an author with the current format version and the given name and * Creates an author with the current format version and the given name and
* public key. * public key.
*/ */
Author createAuthor(String name, byte[] publicKey); Author createAuthor(String name, PublicKey publicKey);
/** /**
* Creates an author with the given format version, name and public key. * Creates an author with the given format version, name and public key.
*/ */
Author createAuthor(int formatVersion, String name, byte[] publicKey); Author createAuthor(int formatVersion, String name, PublicKey publicKey);
/** /**
* Creates a local author with the current format version and the given * Creates a local author with the current format version and the given

View File

@@ -1,13 +1,13 @@
package org.briarproject.bramble.api.identity; package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Arrays;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -15,15 +15,24 @@ public class Identity {
private final LocalAuthor localAuthor; private final LocalAuthor localAuthor;
@Nullable @Nullable
private final byte[] handshakePublicKey, handshakePrivateKey; private final PublicKey handshakePublicKey;
@Nullable
private final PrivateKey handshakePrivateKey;
private final long created; private final long created;
public Identity(LocalAuthor localAuthor, public Identity(LocalAuthor localAuthor,
@Nullable byte[] handshakePublicKey, @Nullable PublicKey handshakePublicKey,
@Nullable byte[] handshakePrivateKey, long created) { @Nullable PrivateKey handshakePrivateKey, long created) {
if (handshakePublicKey != null) { if (handshakePublicKey != null) {
int keyLength = handshakePublicKey.length; if (handshakePrivateKey == null)
if (keyLength == 0 || keyLength > MAX_AGREEMENT_PUBLIC_KEY_BYTES) throw new IllegalArgumentException();
if (!handshakePublicKey.getKeyType().equals(KEY_TYPE_AGREEMENT))
throw new IllegalArgumentException();
}
if (handshakePrivateKey != null) {
if (handshakePublicKey == null)
throw new IllegalArgumentException();
if (!handshakePrivateKey.getKeyType().equals(KEY_TYPE_AGREEMENT))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
this.localAuthor = localAuthor; this.localAuthor = localAuthor;
@@ -57,7 +66,7 @@ public class Identity {
* Returns the public key used for handshaking, or null if no key exists. * Returns the public key used for handshaking, or null if no key exists.
*/ */
@Nullable @Nullable
public byte[] getHandshakePublicKey() { public PublicKey getHandshakePublicKey() {
return handshakePublicKey; return handshakePublicKey;
} }
@@ -65,7 +74,7 @@ public class Identity {
* Returns the private key used for handshaking, or null if no key exists. * Returns the private key used for handshaking, or null if no key exists.
*/ */
@Nullable @Nullable
public byte[] getHandshakePrivateKey() { public PrivateKey getHandshakePrivateKey() {
return handshakePrivateKey; return handshakePrivateKey;
} }
@@ -76,21 +85,4 @@ public class Identity {
public long getTimeCreated() { public long getTimeCreated() {
return created; return created;
} }
@Override
public int hashCode() {
return localAuthor.getId().hashCode();
}
@Override
public boolean equals(Object o) {
if (o instanceof Identity) {
Identity i = (Identity) o;
return created == i.created &&
localAuthor.equals(i.localAuthor) &&
Arrays.equals(handshakePublicKey, i.handshakePublicKey) &&
Arrays.equals(handshakePrivateKey, i.handshakePrivateKey);
}
return false;
}
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.api.identity; package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
@@ -41,9 +42,6 @@ public interface IdentityManager {
* Returns the cached handshake keys or loads them from the database. * Returns the cached handshake keys or loads them from the database.
* <p/> * <p/>
* Read-only. * Read-only.
*
* @return A two-element array containing the public key in the first
* element and the private key in the second
*/ */
byte[][] getHandshakeKeys(Transaction txn) throws DbException; KeyPair getHandshakeKeys(Transaction txn) throws DbException;
} }

View File

@@ -1,9 +1,13 @@
package org.briarproject.bramble.api.identity; package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
/** /**
* A pseudonym for the local user. * A pseudonym for the local user.
*/ */
@@ -11,18 +15,20 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault @NotNullByDefault
public class LocalAuthor extends Author { public class LocalAuthor extends Author {
private final byte[] privateKey; private final PrivateKey privateKey;
public LocalAuthor(AuthorId id, int formatVersion, String name, public LocalAuthor(AuthorId id, int formatVersion, String name,
byte[] publicKey, byte[] privateKey) { PublicKey publicKey, PrivateKey privateKey) {
super(id, formatVersion, name, publicKey); super(id, formatVersion, name, publicKey);
if (!privateKey.getKeyType().equals(KEY_TYPE_SIGNATURE))
throw new IllegalArgumentException();
this.privateKey = privateKey; this.privateKey = privateKey;
} }
/** /**
* Returns the private key used to generate the pseudonym's signatures. * Returns the private key used to generate the pseudonym's signatures.
*/ */
public byte[] getPrivateKey() { public PrivateKey getPrivateKey() {
return privateKey; return privateKey;
} }
} }

View File

@@ -6,10 +6,20 @@ import javax.annotation.Nullable;
public class NullSafety { public class NullSafety {
/** /**
* Stand-in for `Objects.requireNonNull()`. * Stand-in for {@code Objects.requireNonNull()}.
*/ */
public static <T> T requireNonNull(@Nullable T t) { public static <T> T requireNonNull(@Nullable T t) {
if (t == null) throw new NullPointerException(); if (t == null) throw new NullPointerException();
return t; return t;
} }
/**
* Checks that exactly one of the arguments is null.
*
* @throws AssertionError If both or neither of the arguments are null
*/
public static void requireExactlyOneNull(@Nullable Object a,
@Nullable Object b) {
if ((a == null) == (b == null)) throw new AssertionError();
}
} }

View File

@@ -1,57 +0,0 @@
package org.briarproject.bramble.api.transport;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable;
/**
* Abstract superclass for {@link TransportKeys} and {@link HandshakeKeys}.
*/
@Immutable
@NotNullByDefault
public abstract class AbstractTransportKeys {
private final TransportId transportId;
private final IncomingKeys inPrev, inCurr, inNext;
private final OutgoingKeys outCurr;
AbstractTransportKeys(TransportId transportId, IncomingKeys inPrev,
IncomingKeys inCurr, IncomingKeys inNext, OutgoingKeys outCurr) {
if (inPrev.getTimePeriod() != outCurr.getTimePeriod() - 1)
throw new IllegalArgumentException();
if (inCurr.getTimePeriod() != outCurr.getTimePeriod())
throw new IllegalArgumentException();
if (inNext.getTimePeriod() != outCurr.getTimePeriod() + 1)
throw new IllegalArgumentException();
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 getTimePeriod() {
return outCurr.getTimePeriod();
}
}

View File

@@ -1,70 +0,0 @@
package org.briarproject.bramble.api.transport;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
* A set of keys for handshaking with a given contact or pending contact over a
* given transport. Unlike a {@link TransportKeySet} these keys do not provide
* forward secrecy.
*/
@Immutable
@NotNullByDefault
public class HandshakeKeySet {
private final HandshakeKeySetId keySetId;
@Nullable
private final ContactId contactId;
@Nullable
private final PendingContactId pendingContactId;
private final HandshakeKeys keys;
public HandshakeKeySet(HandshakeKeySetId keySetId, ContactId contactId,
HandshakeKeys keys) {
this.keySetId = keySetId;
this.contactId = contactId;
this.keys = keys;
pendingContactId = null;
}
public HandshakeKeySet(HandshakeKeySetId keySetId,
PendingContactId pendingContactId, HandshakeKeys keys) {
this.keySetId = keySetId;
this.pendingContactId = pendingContactId;
this.keys = keys;
contactId = null;
}
public HandshakeKeySetId getKeySetId() {
return keySetId;
}
@Nullable
public ContactId getContactId() {
return contactId;
}
@Nullable
public PendingContactId getPendingContactId() {
return pendingContactId;
}
public HandshakeKeys getKeys() {
return keys;
}
@Override
public int hashCode() {
return keySetId.hashCode();
}
@Override
public boolean equals(Object o) {
return o instanceof HandshakeKeySet &&
keySetId.equals(((HandshakeKeySet) o).keySetId);
}
}

View File

@@ -1,36 +0,0 @@
package org.briarproject.bramble.api.transport;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable;
/**
* Keys for handshaking with a given contact or pending contact over a given
* transport. Unlike {@link TransportKeys} these keys do not provide forward
* secrecy.
*/
@Immutable
@NotNullByDefault
public class HandshakeKeys extends AbstractTransportKeys {
private final SecretKey rootKey;
private final boolean alice;
public HandshakeKeys(TransportId transportId, IncomingKeys inPrev,
IncomingKeys inCurr, IncomingKeys inNext, OutgoingKeys outCurr,
SecretKey rootKey, boolean alice) {
super(transportId, inPrev, inCurr, inNext, outCurr);
this.rootKey = rootKey;
this.alice = alice;
}
public SecretKey getRootKey() {
return rootKey;
}
public boolean isAlice() {
return alice;
}
}

View File

@@ -8,8 +8,8 @@ import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE; import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
/** /**
* Contains transport keys for receiving streams from a given contact over a * Contains transport keys for receiving streams from a given contact or
* given transport in a given time period. * pending contact over a given transport in a given time period.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.api.transport; package org.briarproject.bramble.api.transport;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
@@ -18,23 +19,51 @@ public interface KeyManager {
/** /**
* Informs the key manager that a new contact has been added. Derives and * Informs the key manager that a new contact has been added. Derives and
* stores a set of transport keys for communicating with the contact over * stores a set of rotation mode transport keys for communicating with the
* each transport and returns the key set IDs. * contact over each transport and returns the key set IDs.
* <p/> * <p/>
* {@link StreamContext StreamContexts} for the contact can be created * {@link StreamContext StreamContexts} for the contact can be created
* after this method has returned. * after this method has returned.
* *
* @param alice true if the local party is Alice * @param alice True if the local party is Alice
* @param active whether the derived keys can be used for outgoing streams * @param active Whether the derived keys can be used for outgoing streams
*/ */
Map<TransportId, TransportKeySetId> addContact(Transaction txn, ContactId c, Map<TransportId, KeySetId> addContactWithRotationKeys(Transaction txn,
SecretKey rootKey, long timestamp, boolean alice, boolean active) ContactId c, SecretKey rootKey, long timestamp, boolean alice,
boolean active) throws DbException;
/**
* Informs the key manager that a new contact has been added. Derives and
* stores a set of handshake mode transport keys for communicating with the
* contact over each transport and returns the key set IDs.
* <p/>
* {@link StreamContext StreamContexts} for the contact can be created
* after this method has returned.
*
* @param alice True if the local party is Alice
*/
Map<TransportId, KeySetId> addContactWithHandshakeKeys(Transaction txn,
ContactId c, SecretKey rootKey, boolean alice) throws DbException;
/**
* Informs the key manager that a new pending contact has been added.
* Derives and stores a set of handshake mode transport keys for
* communicating with the pending contact over each transport and returns
* the key set IDs.
* <p/>
* {@link StreamContext StreamContexts} for the pending contact can be
* created after this method has returned.
*
* @param alice True if the local party is Alice
*/
Map<TransportId, KeySetId> addPendingContact(Transaction txn,
PendingContactId p, SecretKey rootKey, boolean alice)
throws DbException; throws DbException;
/** /**
* Marks the given transport keys as usable for outgoing streams. * Marks the given transport keys as usable for outgoing streams.
*/ */
void activateKeys(Transaction txn, Map<TransportId, TransportKeySetId> keys) void activateKeys(Transaction txn, Map<TransportId, KeySetId> keys)
throws DbException; throws DbException;
/** /**
@@ -43,15 +72,28 @@ public interface KeyManager {
*/ */
boolean canSendOutgoingStreams(ContactId c, TransportId t); boolean canSendOutgoingStreams(ContactId c, TransportId t);
/**
* Returns true if we have keys that can be used for outgoing streams to
* the given pending contact over the given transport.
*/
boolean canSendOutgoingStreams(PendingContactId p, TransportId t);
/** /**
* Returns a {@link StreamContext} for sending a stream to the given * 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 over the given transport, or null if an error occurs.
* contact does not support the transport.
*/ */
@Nullable @Nullable
StreamContext getStreamContext(ContactId c, TransportId t) StreamContext getStreamContext(ContactId c, TransportId t)
throws DbException; throws DbException;
/**
* Returns a {@link StreamContext} for sending a stream to the given
* pending contact over the given transport, or null if an error occurs.
*/
@Nullable
StreamContext getStreamContext(PendingContactId p, TransportId t)
throws DbException;
/** /**
* Looks up the given tag and returns a {@link StreamContext} for reading * Looks up the given tag and returns a {@link StreamContext} for reading
* from the corresponding stream, or null if an error occurs or the tag was * from the corresponding stream, or null if an error occurs or the tag was

View File

@@ -5,17 +5,16 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* Type-safe wrapper for an integer that uniquely identifies a * Type-safe wrapper for an integer that uniquely identifies a set of
* {@link HandshakeKeySet set of handshake keys} within the scope of the local * {@link TransportKeySet transport keys} within the scope of the local device.
* device.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class HandshakeKeySetId { public class KeySetId {
private final int id; private final int id;
public HandshakeKeySetId(int id) { public KeySetId(int id) {
this.id = id; this.id = id;
} }
@@ -30,7 +29,6 @@ public class HandshakeKeySetId {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
return o instanceof HandshakeKeySetId && return o instanceof KeySetId && id == ((KeySetId) o).id;
id == ((HandshakeKeySetId) o).id;
} }
} }

View File

@@ -6,8 +6,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* Contains transport keys for sending streams to a given contact over a given * Contains transport keys for sending streams to a given contact or pending
* transport in a given time period. * contact over a given transport in a given time period.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault

View File

@@ -1,34 +1,53 @@
package org.briarproject.bramble.api.transport; package org.briarproject.bramble.api.transport;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireExactlyOneNull;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class StreamContext { public class StreamContext {
@Nullable
private final ContactId contactId; private final ContactId contactId;
@Nullable
private final PendingContactId pendingContactId;
private final TransportId transportId; private final TransportId transportId;
private final SecretKey tagKey, headerKey; private final SecretKey tagKey, headerKey;
private final long streamNumber; private final long streamNumber;
private final boolean handshakeMode;
public StreamContext(ContactId contactId, TransportId transportId, public StreamContext(@Nullable ContactId contactId,
SecretKey tagKey, SecretKey headerKey, long streamNumber) { @Nullable PendingContactId pendingContactId,
TransportId transportId, SecretKey tagKey, SecretKey headerKey,
long streamNumber, boolean handshakeMode) {
requireExactlyOneNull(contactId, pendingContactId);
this.contactId = contactId; this.contactId = contactId;
this.pendingContactId = pendingContactId;
this.transportId = transportId; this.transportId = transportId;
this.tagKey = tagKey; this.tagKey = tagKey;
this.headerKey = headerKey; this.headerKey = headerKey;
this.streamNumber = streamNumber; this.streamNumber = streamNumber;
this.handshakeMode = handshakeMode;
} }
@Nullable
public ContactId getContactId() { public ContactId getContactId() {
return contactId; return contactId;
} }
@Nullable
public PendingContactId getPendingContactId() {
return pendingContactId;
}
public TransportId getTransportId() { public TransportId getTransportId() {
return transportId; return transportId;
} }
@@ -44,4 +63,8 @@ public class StreamContext {
public long getStreamNumber() { public long getStreamNumber() {
return streamNumber; return streamNumber;
} }
public boolean isHandshakeMode() {
return handshakeMode;
}
} }

View File

@@ -1,37 +1,52 @@
package org.briarproject.bramble.api.transport; package org.briarproject.bramble.api.transport;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireExactlyOneNull;
/** /**
* A set of keys for communicating with a given contact over a given transport. * A set of keys for communicating with a given contact or pending contact
* Unlike a {@link HandshakeKeySet} these keys provide forward secrecy. * over a given transport.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class TransportKeySet { public class TransportKeySet {
private final TransportKeySetId keySetId; private final KeySetId keySetId;
@Nullable
private final ContactId contactId; private final ContactId contactId;
@Nullable
private final PendingContactId pendingContactId;
private final TransportKeys keys; private final TransportKeys keys;
public TransportKeySet(TransportKeySetId keySetId, ContactId contactId, public TransportKeySet(KeySetId keySetId, @Nullable ContactId contactId,
TransportKeys keys) { @Nullable PendingContactId pendingContactId, TransportKeys keys) {
requireExactlyOneNull(contactId, pendingContactId);
this.keySetId = keySetId; this.keySetId = keySetId;
this.contactId = contactId; this.contactId = contactId;
this.pendingContactId = pendingContactId;
this.keys = keys; this.keys = keys;
} }
public TransportKeySetId getKeySetId() { public KeySetId getKeySetId() {
return keySetId; return keySetId;
} }
@Nullable
public ContactId getContactId() { public ContactId getContactId() {
return contactId; return contactId;
} }
@Nullable
public PendingContactId getPendingContactId() {
return pendingContactId;
}
public TransportKeys getKeys() { public TransportKeys getKeys() {
return keys; return keys;
} }

View File

@@ -1,38 +0,0 @@
package org.briarproject.bramble.api.transport;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* Type-safe wrapper for an integer that uniquely identifies a
* {@link TransportKeySet set of transport keys} within the scope of the local
* device.
* <p/>
* Key sets created on a given device must have increasing identifiers.
*/
@Immutable
@NotNullByDefault
public class TransportKeySetId {
private final int id;
public TransportKeySetId(int id) {
this.id = id;
}
public int getInt() {
return id;
}
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object o) {
return o instanceof TransportKeySetId &&
id == ((TransportKeySetId) o).id;
}
}

View File

@@ -1,20 +1,108 @@
package org.briarproject.bramble.api.transport; package org.briarproject.bramble.api.transport;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* Keys for communicating with a given contact over a given transport. Unlike * Keys for communicating with a given contact or pending contact over a given
* {@link HandshakeKeys} these keys provide forward secrecy. * transport.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class TransportKeys extends AbstractTransportKeys { public class TransportKeys {
private final TransportId transportId;
private final IncomingKeys inPrev, inCurr, inNext;
private final OutgoingKeys outCurr;
@Nullable
private final SecretKey rootKey;
private final boolean alice;
/**
* Constructor for rotation mode.
*/
public TransportKeys(TransportId transportId, IncomingKeys inPrev, public TransportKeys(TransportId transportId, IncomingKeys inPrev,
IncomingKeys inCurr, IncomingKeys inNext, OutgoingKeys outCurr) { IncomingKeys inCurr, IncomingKeys inNext, OutgoingKeys outCurr) {
super(transportId, inPrev, inCurr, inNext, outCurr); this(transportId, inPrev, inCurr, inNext, outCurr, null, false);
}
/**
* Constructor for handshake mode.
*/
public TransportKeys(TransportId transportId, IncomingKeys inPrev,
IncomingKeys inCurr, IncomingKeys inNext, OutgoingKeys outCurr,
@Nullable SecretKey rootKey, boolean alice) {
if (inPrev.getTimePeriod() != outCurr.getTimePeriod() - 1)
throw new IllegalArgumentException();
if (inCurr.getTimePeriod() != outCurr.getTimePeriod())
throw new IllegalArgumentException();
if (inNext.getTimePeriod() != outCurr.getTimePeriod() + 1)
throw new IllegalArgumentException();
this.transportId = transportId;
this.inPrev = inPrev;
this.inCurr = inCurr;
this.inNext = inNext;
this.outCurr = outCurr;
this.rootKey = rootKey;
this.alice = alice;
}
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 getTimePeriod() {
return outCurr.getTimePeriod();
}
/**
* Returns true if these keys are for use in handshake mode or false if
* they're for use in rotation mode.
*/
public boolean isHandshakeMode() {
return rootKey != null;
}
/**
* If these keys are for use in handshake mode, returns the root key.
*
* @throws UnsupportedOperationException If these keys are for use in
* rotation mode
*/
public SecretKey getRootKey() {
if (rootKey == null) throw new UnsupportedOperationException();
return rootKey;
}
/**
* If these keys are for use in handshake mode, returns true if the local
* party is Alice.
*
* @throws UnsupportedOperationException If these keys are for use in
* rotation mode
*/
public boolean isAlice() {
if (rootKey == null) throw new UnsupportedOperationException();
return alice;
} }
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.util; package org.briarproject.bramble.util;
import java.io.File;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -19,6 +20,7 @@ public class LogUtils {
/** /**
* Logs the duration of a task. * Logs the duration of a task.
*
* @param logger the logger to use * @param logger the logger to use
* @param task a description of the task * @param task a description of the task
* @param start the start time of the task, as returned by {@link #now()} * @param start the start time of the task, as returned by {@link #now()}
@@ -33,4 +35,26 @@ public class LogUtils {
public static void logException(Logger logger, Level level, Throwable t) { public static void logException(Logger logger, Level level, Throwable t) {
if (logger.isLoggable(level)) logger.log(level, t.toString(), t); if (logger.isLoggable(level)) logger.log(level, t.toString(), t);
} }
public static void logFileOrDir(Logger logger, Level level, File f) {
if (logger.isLoggable(level)) {
if (f.isFile()) {
logWithType(logger, level, f, "F");
} else if (f.isDirectory()) {
logWithType(logger, level, f, "D");
File[] children = f.listFiles();
if (children != null) {
for (File child : children)
logFileOrDir(logger, level, child);
}
} else if (f.exists()) {
logWithType(logger, level, f, "?");
}
}
}
private static void logWithType(Logger logger, Level level, File f,
String type) {
logger.log(level, type + " " + f.getAbsolutePath() + " " + f.length());
}
} }

View File

@@ -5,7 +5,14 @@ import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactId; import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.contact.PendingContactState;
import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.SignaturePrivateKey;
import org.briarproject.bramble.api.crypto.SignaturePublicKey;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.Identity; import org.briarproject.bramble.api.identity.Identity;
@@ -30,11 +37,10 @@ import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION; import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH; import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
import static org.briarproject.bramble.api.sync.ClientId.MAX_CLIENT_ID_LENGTH; import static org.briarproject.bramble.api.sync.ClientId.MAX_CLIENT_ID_LENGTH;
@@ -101,10 +107,28 @@ public class TestUtils {
return new SecretKey(getRandomBytes(SecretKey.LENGTH)); return new SecretKey(getRandomBytes(SecretKey.LENGTH));
} }
public static PublicKey getSignaturePublicKey() {
byte[] key = getRandomBytes(MAX_SIGNATURE_PUBLIC_KEY_BYTES);
return new SignaturePublicKey(key);
}
public static PrivateKey getSignaturePrivateKey() {
return new SignaturePrivateKey(getRandomBytes(123));
}
public static PublicKey getAgreementPublicKey() {
byte[] key = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES);
return new AgreementPublicKey(key);
}
public static PrivateKey getAgreementPrivateKey() {
return new AgreementPrivateKey(getRandomBytes(123));
}
public static Identity getIdentity() { public static Identity getIdentity() {
LocalAuthor localAuthor = getLocalAuthor(); LocalAuthor localAuthor = getLocalAuthor();
byte[] handshakePub = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES); PublicKey handshakePub = getAgreementPublicKey();
byte[] handshakePriv = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES); PrivateKey handshakePriv = getAgreementPrivateKey();
return new Identity(localAuthor, handshakePub, handshakePriv, return new Identity(localAuthor, handshakePub, handshakePriv,
timestamp); timestamp);
} }
@@ -113,8 +137,8 @@ public class TestUtils {
AuthorId id = new AuthorId(getRandomId()); AuthorId id = new AuthorId(getRandomId());
int nameLength = 1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH); int nameLength = 1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH);
String name = getRandomString(nameLength); String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); PublicKey publicKey = getSignaturePublicKey();
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); PrivateKey privateKey = getSignaturePrivateKey();
return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey); return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey);
} }
@@ -122,7 +146,7 @@ public class TestUtils {
AuthorId id = new AuthorId(getRandomId()); AuthorId id = new AuthorId(getRandomId());
int nameLength = 1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH); int nameLength = 1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH);
String name = getRandomString(nameLength); String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); PublicKey publicKey = getSignaturePublicKey();
return new Author(id, FORMAT_VERSION, name, publicKey); return new Author(id, FORMAT_VERSION, name, publicKey);
} }
@@ -155,10 +179,12 @@ public class TestUtils {
public static PendingContact getPendingContact(int nameLength) { public static PendingContact getPendingContact(int nameLength) {
PendingContactId id = new PendingContactId(getRandomId()); PendingContactId id = new PendingContactId(getRandomId());
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); PublicKey publicKey = getAgreementPublicKey();
String alias = getRandomString(nameLength); String alias = getRandomString(nameLength);
return new PendingContact(id, publicKey, alias, WAITING_FOR_CONNECTION, int stateIndex =
timestamp); random.nextInt(PendingContactState.values().length - 1);
PendingContactState state = PendingContactState.values()[stateIndex];
return new PendingContact(id, publicKey, alias, state, timestamp);
} }
public static ContactId getContactId() { public static ContactId getContactId() {
@@ -179,7 +205,7 @@ public class TestUtils {
boolean verified) { boolean verified) {
return new Contact(c, remote, local, return new Contact(c, remote, local,
getRandomString(MAX_AUTHOR_NAME_LENGTH), getRandomString(MAX_AUTHOR_NAME_LENGTH),
getRandomBytes(MAX_PUBLIC_KEY_LENGTH), verified); getAgreementPublicKey(), verified);
} }
public static double getMedian(Collection<? extends Number> samples) { public static double getMedian(Collection<? extends Number> samples) {

View File

@@ -3,6 +3,9 @@ package org.briarproject.bramble.client;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader; import org.briarproject.bramble.api.data.BdfReader;
@@ -305,14 +308,15 @@ class ClientHelperImpl implements ClientHelper {
} }
@Override @Override
public byte[] sign(String label, BdfList toSign, byte[] privateKey) public byte[] sign(String label, BdfList toSign, PrivateKey privateKey)
throws FormatException, GeneralSecurityException { throws FormatException, GeneralSecurityException {
return crypto.sign(label, toByteArray(toSign), privateKey); return crypto.sign(label, toByteArray(toSign), privateKey);
} }
@Override @Override
public void verifySignature(byte[] signature, String label, BdfList signed, public void verifySignature(byte[] signature, String label, BdfList signed,
byte[] publicKey) throws FormatException, GeneralSecurityException { PublicKey publicKey)
throws FormatException, GeneralSecurityException {
if (!crypto.verifySignature(signature, label, toByteArray(signed), if (!crypto.verifySignature(signature, label, toByteArray(signed),
publicKey)) { publicKey)) {
throw new GeneralSecurityException("Invalid signature"); throw new GeneralSecurityException("Invalid signature");
@@ -327,11 +331,29 @@ class ClientHelperImpl implements ClientHelper {
if (formatVersion != FORMAT_VERSION) throw new FormatException(); if (formatVersion != FORMAT_VERSION) throw new FormatException();
String name = author.getString(1); String name = author.getString(1);
checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH); checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
byte[] publicKey = author.getRaw(2); byte[] publicKeyBytes = author.getRaw(2);
checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH); checkLength(publicKeyBytes, 1, MAX_PUBLIC_KEY_LENGTH);
KeyParser parser = crypto.getSignatureKeyParser();
PublicKey publicKey;
try {
publicKey = parser.parsePublicKey(publicKeyBytes);
} catch (GeneralSecurityException e) {
throw new FormatException();
}
return authorFactory.createAuthor(formatVersion, name, publicKey); return authorFactory.createAuthor(formatVersion, name, publicKey);
} }
@Override
public PublicKey parseAndValidateAgreementPublicKey(byte[] publicKeyBytes)
throws FormatException {
KeyParser parser = crypto.getAgreementKeyParser();
try {
return parser.parsePublicKey(publicKeyBytes);
} catch (GeneralSecurityException e) {
throw new FormatException();
}
}
@Override @Override
public TransportProperties parseAndValidateTransportProperties( public TransportProperties parseAndValidateTransportProperties(
BdfDictionary properties) throws FormatException { BdfDictionary properties) throws FormatException {

View File

@@ -70,7 +70,8 @@ class ContactManagerImpl implements ContactManager {
SecretKey rootKey, long timestamp, boolean alice, boolean verified, SecretKey rootKey, long timestamp, boolean alice, boolean verified,
boolean active) throws DbException { boolean active) throws DbException {
ContactId c = db.addContact(txn, remote, local, verified); ContactId c = db.addContact(txn, remote, local, verified);
keyManager.addContact(txn, c, rootKey, timestamp, alice, active); keyManager.addContactWithRotationKeys(txn, c, rootKey, timestamp,
alice, active);
Contact contact = db.getContact(txn, c); Contact contact = db.getContact(txn, c);
for (ContactHook hook : hooks) hook.addingContact(txn, contact); for (ContactHook hook : hooks) hook.addingContact(txn, contact);
return c; return c;

View File

@@ -39,8 +39,8 @@ class PendingContactFactoryImpl implements PendingContactFactory {
PublicKey publicKey = parseHandshakeLink(link); PublicKey publicKey = parseHandshakeLink(link);
PendingContactId id = getPendingContactId(publicKey); PendingContactId id = getPendingContactId(publicKey);
long timestamp = clock.currentTimeMillis(); long timestamp = clock.currentTimeMillis();
return new PendingContact(id, publicKey.getEncoded(), alias, return new PendingContact(id, publicKey, alias, WAITING_FOR_CONNECTION,
WAITING_FOR_CONNECTION, timestamp); timestamp);
} }
private PublicKey parseHandshakeLink(String link) throws FormatException { private PublicKey parseHandshakeLink(String link) throws FormatException {

View File

@@ -1,5 +1,7 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.KeyParser; import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
@@ -7,21 +9,24 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault @NotNullByDefault
class Curve25519KeyParser implements KeyParser { class AgreementKeyParser implements KeyParser {
@Override @Override
public PublicKey parsePublicKey(byte[] encodedKey) public PublicKey parsePublicKey(byte[] encodedKey)
throws GeneralSecurityException { throws GeneralSecurityException {
if (encodedKey.length != 32) throw new GeneralSecurityException(); if (encodedKey.length != 32) throw new GeneralSecurityException();
return new Curve25519PublicKey(encodedKey); return new AgreementPublicKey(encodedKey);
} }
@Override @Override
public PrivateKey parsePrivateKey(byte[] encodedKey) public PrivateKey parsePrivateKey(byte[] encodedKey)
throws GeneralSecurityException { throws GeneralSecurityException {
if (encodedKey.length != 32) throw new GeneralSecurityException(); if (encodedKey.length != 32) throw new GeneralSecurityException();
return new Curve25519PrivateKey(clamp(encodedKey)); return new AgreementPrivateKey(clamp(encodedKey));
} }
static byte[] clamp(byte[] b) { static byte[] clamp(byte[] b) {

View File

@@ -4,12 +4,16 @@ import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey; import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.KeyPairGenerator; import net.i2p.crypto.eddsa.KeyPairGenerator;
import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.KeyParser; import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.SignaturePrivateKey;
import org.briarproject.bramble.api.crypto.SignaturePublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.SecureRandomProvider; import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.util.ByteUtils; import org.briarproject.bramble.util.ByteUtils;
@@ -31,6 +35,8 @@ import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES; import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@@ -80,8 +86,8 @@ class CryptoComponentImpl implements CryptoComponent {
signatureKeyPairGenerator = new KeyPairGenerator(); signatureKeyPairGenerator = new KeyPairGenerator();
signatureKeyPairGenerator.initialize(SIGNATURE_KEY_PAIR_BITS, signatureKeyPairGenerator.initialize(SIGNATURE_KEY_PAIR_BITS,
secureRandom); secureRandom);
agreementKeyParser = new Curve25519KeyParser(); agreementKeyParser = new AgreementKeyParser();
signatureKeyParser = new EdKeyParser(); signatureKeyParser = new SignatureKeyParser();
messageEncrypter = new MessageEncrypter(secureRandom); messageEncrypter = new MessageEncrypter(secureRandom);
} }
@@ -125,9 +131,9 @@ class CryptoComponentImpl implements CryptoComponent {
// Package access for testing // Package access for testing
byte[] performRawKeyAgreement(PrivateKey priv, PublicKey pub) byte[] performRawKeyAgreement(PrivateKey priv, PublicKey pub)
throws GeneralSecurityException { throws GeneralSecurityException {
if (!(priv instanceof Curve25519PrivateKey)) if (!priv.getKeyType().equals(KEY_TYPE_AGREEMENT))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if (!(pub instanceof Curve25519PublicKey)) if (!pub.getKeyType().equals(KEY_TYPE_AGREEMENT))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
long start = now(); long start = now();
byte[] secret = curve25519.calculateAgreement(pub.getEncoded(), byte[] secret = curve25519.calculateAgreement(pub.getEncoded(),
@@ -143,8 +149,8 @@ class CryptoComponentImpl implements CryptoComponent {
@Override @Override
public KeyPair generateAgreementKeyPair() { public KeyPair generateAgreementKeyPair() {
Curve25519KeyPair keyPair = curve25519.generateKeyPair(); Curve25519KeyPair keyPair = curve25519.generateKeyPair();
PublicKey pub = new Curve25519PublicKey(keyPair.getPublicKey()); PublicKey pub = new AgreementPublicKey(keyPair.getPublicKey());
PrivateKey priv = new Curve25519PrivateKey(keyPair.getPrivateKey()); PrivateKey priv = new AgreementPrivateKey(keyPair.getPrivateKey());
return new KeyPair(pub, priv); return new KeyPair(pub, priv);
} }
@@ -158,9 +164,9 @@ class CryptoComponentImpl implements CryptoComponent {
java.security.KeyPair keyPair = java.security.KeyPair keyPair =
signatureKeyPairGenerator.generateKeyPair(); signatureKeyPairGenerator.generateKeyPair();
EdDSAPublicKey edPublicKey = (EdDSAPublicKey) keyPair.getPublic(); EdDSAPublicKey edPublicKey = (EdDSAPublicKey) keyPair.getPublic();
PublicKey publicKey = new EdPublicKey(edPublicKey.getAbyte()); PublicKey publicKey = new SignaturePublicKey(edPublicKey.getAbyte());
EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) keyPair.getPrivate(); EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) keyPair.getPrivate();
PrivateKey privateKey = new EdPrivateKey(edPrivateKey.getSeed()); PrivateKey privateKey = new SignaturePrivateKey(edPrivateKey.getSeed());
return new KeyPair(publicKey, privateKey); return new KeyPair(publicKey, privateKey);
} }
@@ -195,21 +201,22 @@ class CryptoComponentImpl implements CryptoComponent {
} }
@Override @Override
public byte[] sign(String label, byte[] toSign, byte[] privateKey) public byte[] sign(String label, byte[] toSign, PrivateKey privateKey)
throws GeneralSecurityException { throws GeneralSecurityException {
PrivateKey key = signatureKeyParser.parsePrivateKey(privateKey);
Signature sig = new EdSignature(); Signature sig = new EdSignature();
sig.initSign(key); sig.initSign(privateKey);
updateSignature(sig, label, toSign); updateSignature(sig, label, toSign);
return sig.sign(); return sig.sign();
} }
@Override @Override
public boolean verifySignature(byte[] signature, String label, public boolean verifySignature(byte[] signature, String label,
byte[] signed, byte[] publicKey) throws GeneralSecurityException { byte[] signed, PublicKey publicKey)
PublicKey key = signatureKeyParser.parsePublicKey(publicKey); throws GeneralSecurityException {
if (!publicKey.getKeyType().equals(KEY_TYPE_SIGNATURE))
throw new IllegalArgumentException();
Signature sig = new EdSignature(); Signature sig = new EdSignature();
sig.initVerify(key); sig.initVerify(publicKey);
updateSignature(sig, label, signed); updateSignature(sig, label, signed);
return sig.verify(signature); return sig.verify(signature);
} }

View File

@@ -1,18 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
class Curve25519PrivateKey extends Bytes implements PrivateKey {
Curve25519PrivateKey(byte[] bytes) {
super(bytes);
}
@Override
public byte[] getEncoded() {
return getBytes();
}
}

View File

@@ -1,18 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
class Curve25519PublicKey extends Bytes implements PublicKey {
Curve25519PublicKey(byte[] bytes) {
super(bytes);
}
@Override
public byte[] getEncoded() {
return getBytes();
}
}

View File

@@ -1,18 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
class EdPrivateKey extends Bytes implements PrivateKey {
EdPrivateKey(byte[] bytes) {
super(bytes);
}
@Override
public byte[] getEncoded() {
return getBytes();
}
}

View File

@@ -1,18 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
class EdPublicKey extends Bytes implements PublicKey {
EdPublicKey(byte[] bytes) {
super(bytes);
}
@Override
public byte[] getEncoded() {
return getBytes();
}
}

View File

@@ -17,6 +17,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.Provider; import java.security.Provider;
import static net.i2p.crypto.eddsa.EdDSAEngine.SIGNATURE_ALGORITHM; import static net.i2p.crypto.eddsa.EdDSAEngine.SIGNATURE_ALGORITHM;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
@NotNullByDefault @NotNullByDefault
class EdSignature implements Signature { class EdSignature implements Signature {
@@ -39,7 +40,7 @@ class EdSignature implements Signature {
@Override @Override
public void initSign(PrivateKey k) throws GeneralSecurityException { public void initSign(PrivateKey k) throws GeneralSecurityException {
if (!(k instanceof EdPrivateKey)) if (!k.getKeyType().equals(KEY_TYPE_SIGNATURE))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
EdDSAPrivateKey privateKey = new EdDSAPrivateKey( EdDSAPrivateKey privateKey = new EdDSAPrivateKey(
new EdDSAPrivateKeySpec(k.getEncoded(), CURVE_SPEC)); new EdDSAPrivateKeySpec(k.getEncoded(), CURVE_SPEC));
@@ -48,7 +49,7 @@ class EdSignature implements Signature {
@Override @Override
public void initVerify(PublicKey k) throws GeneralSecurityException { public void initVerify(PublicKey k) throws GeneralSecurityException {
if (!(k instanceof EdPublicKey)) if (!k.getKeyType().equals(KEY_TYPE_SIGNATURE))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
EdDSAPublicKey publicKey = new EdDSAPublicKey( EdDSAPublicKey publicKey = new EdDSAPublicKey(
new EdDSAPublicKeySpec(k.getEncoded(), CURVE_SPEC)); new EdDSAPublicKeySpec(k.getEncoded(), CURVE_SPEC));

View File

@@ -49,6 +49,7 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault @NotNullByDefault
public class MessageEncrypter { public class MessageEncrypter {
private static final String KEY_TYPE = "SEC1_brainpoolp512r1";
private static final ECDomainParameters PARAMETERS; private static final ECDomainParameters PARAMETERS;
private static final int MESSAGE_KEY_BITS = 512; private static final int MESSAGE_KEY_BITS = 512;
private static final int MAC_KEY_BITS = 256; private static final int MAC_KEY_BITS = 256;
@@ -69,7 +70,7 @@ public class MessageEncrypter {
MessageEncrypter(SecureRandom random) { MessageEncrypter(SecureRandom random) {
generator = new ECKeyPairGenerator(); generator = new ECKeyPairGenerator();
generator.init(new ECKeyGenerationParameters(PARAMETERS, random)); generator.init(new ECKeyGenerationParameters(PARAMETERS, random));
parser = new Sec1KeyParser(PARAMETERS, MESSAGE_KEY_BITS); parser = new Sec1KeyParser(KEY_TYPE, PARAMETERS, MESSAGE_KEY_BITS);
KeyEncoder encoder = new PublicKeyEncoder(); KeyEncoder encoder = new PublicKeyEncoder();
ephemeralGenerator = new EphemeralKeyPairGenerator(generator, encoder); ephemeralGenerator = new EphemeralKeyPairGenerator(generator, encoder);
ephemeralParser = new PublicKeyParser(PARAMETERS); ephemeralParser = new PublicKeyParser(PARAMETERS);
@@ -80,11 +81,11 @@ public class MessageEncrypter {
// Return a wrapper that uses the SEC 1 encoding // Return a wrapper that uses the SEC 1 encoding
ECPublicKeyParameters ecPublicKey = ECPublicKeyParameters ecPublicKey =
(ECPublicKeyParameters) keyPair.getPublic(); (ECPublicKeyParameters) keyPair.getPublic();
PublicKey publicKey = new Sec1PublicKey(ecPublicKey); PublicKey publicKey = new Sec1PublicKey(KEY_TYPE, ecPublicKey);
ECPrivateKeyParameters ecPrivateKey = ECPrivateKeyParameters ecPrivateKey =
(ECPrivateKeyParameters) keyPair.getPrivate(); (ECPrivateKeyParameters) keyPair.getPrivate();
PrivateKey privateKey = PrivateKey privateKey =
new Sec1PrivateKey(ecPrivateKey, MESSAGE_KEY_BITS); new Sec1PrivateKey(KEY_TYPE, ecPrivateKey, MESSAGE_KEY_BITS);
return new KeyPair(publicKey, privateKey); return new KeyPair(publicKey, privateKey);
} }

View File

@@ -31,11 +31,13 @@ class Sec1KeyParser implements KeyParser {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(Sec1KeyParser.class.getName()); Logger.getLogger(Sec1KeyParser.class.getName());
private final String keyType;
private final ECDomainParameters params; private final ECDomainParameters params;
private final BigInteger modulus; private final BigInteger modulus;
private final int keyBits, bytesPerInt, publicKeyBytes, privateKeyBytes; private final int keyBits, bytesPerInt, publicKeyBytes, privateKeyBytes;
Sec1KeyParser(ECDomainParameters params, int keyBits) { Sec1KeyParser(String keyType, ECDomainParameters params, int keyBits) {
this.keyType = keyType;
this.params = params; this.params = params;
this.keyBits = keyBits; this.keyBits = keyBits;
modulus = ((ECCurve.Fp) params.getCurve()).getQ(); modulus = ((ECCurve.Fp) params.getCurve()).getQ();
@@ -80,7 +82,7 @@ class Sec1KeyParser implements KeyParser {
throw new GeneralSecurityException(); throw new GeneralSecurityException();
// Construct a public key from the point (x, y) and the params // Construct a public key from the point (x, y) and the params
ECPublicKeyParameters k = new ECPublicKeyParameters(pub, params); ECPublicKeyParameters k = new ECPublicKeyParameters(pub, params);
PublicKey p = new Sec1PublicKey(k); PublicKey p = new Sec1PublicKey(keyType, k);
logDuration(LOG, "Parsing public key", start); logDuration(LOG, "Parsing public key", start);
return p; return p;
} }
@@ -97,7 +99,7 @@ class Sec1KeyParser implements KeyParser {
throw new GeneralSecurityException(); throw new GeneralSecurityException();
// Construct a private key from the private value and the params // Construct a private key from the private value and the params
ECPrivateKeyParameters k = new ECPrivateKeyParameters(d, params); ECPrivateKeyParameters k = new ECPrivateKeyParameters(d, params);
PrivateKey p = new Sec1PrivateKey(k, keyBits); PrivateKey p = new Sec1PrivateKey(keyType, k, keyBits);
logDuration(LOG, "Parsing private key", start); logDuration(LOG, "Parsing private key", start);
return p; return p;
} }

View File

@@ -10,14 +10,21 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault @NotNullByDefault
class Sec1PrivateKey implements PrivateKey { class Sec1PrivateKey implements PrivateKey {
private final String keyType;
private final ECPrivateKeyParameters key; private final ECPrivateKeyParameters key;
private final int bytesPerInt; private final int bytesPerInt;
Sec1PrivateKey(ECPrivateKeyParameters key, int keyBits) { Sec1PrivateKey(String keyType, ECPrivateKeyParameters key, int keyBits) {
this.keyType = keyType;
this.key = key; this.key = key;
bytesPerInt = (keyBits + 7) / 8; bytesPerInt = (keyBits + 7) / 8;
} }
@Override
public String getKeyType() {
return keyType;
}
@Override @Override
public byte[] getEncoded() { public byte[] getEncoded() {
byte[] encodedKey = new byte[bytesPerInt]; byte[] encodedKey = new byte[bytesPerInt];

View File

@@ -15,12 +15,19 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault @NotNullByDefault
class Sec1PublicKey implements PublicKey { class Sec1PublicKey implements PublicKey {
private final String keyType;
private final ECPublicKeyParameters key; private final ECPublicKeyParameters key;
Sec1PublicKey(ECPublicKeyParameters key) { Sec1PublicKey(String keyType, ECPublicKeyParameters key) {
this.keyType = keyType;
this.key = key; this.key = key;
} }
@Override
public String getKeyType() {
return keyType;
}
@Override @Override
public byte[] getEncoded() { public byte[] getEncoded() {
return key.getQ().getEncoded(false); return key.getQ().getEncoded(false);

View File

@@ -3,24 +3,29 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.KeyParser; import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SignaturePrivateKey;
import org.briarproject.bramble.api.crypto.SignaturePublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault @NotNullByDefault
class EdKeyParser implements KeyParser { class SignatureKeyParser implements KeyParser {
@Override @Override
public PublicKey parsePublicKey(byte[] encodedKey) public PublicKey parsePublicKey(byte[] encodedKey)
throws GeneralSecurityException { throws GeneralSecurityException {
if (encodedKey.length != 32) throw new GeneralSecurityException(); if (encodedKey.length != 32) throw new GeneralSecurityException();
return new EdPublicKey(encodedKey); return new SignaturePublicKey(encodedKey);
} }
@Override @Override
public PrivateKey parsePrivateKey(byte[] encodedKey) public PrivateKey parsePrivateKey(byte[] encodedKey)
throws GeneralSecurityException { throws GeneralSecurityException {
if (encodedKey.length != 32) throw new GeneralSecurityException(); if (encodedKey.length != 32) throw new GeneralSecurityException();
return new EdPrivateKey(encodedKey); return new SignaturePrivateKey(encodedKey);
} }
} }

View File

@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto; import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.HandshakeKeys;
import org.briarproject.bramble.api.transport.IncomingKeys; import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.OutgoingKeys; import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
@@ -42,7 +41,7 @@ class TransportCryptoImpl implements TransportCrypto {
} }
@Override @Override
public TransportKeys deriveTransportKeys(TransportId t, public TransportKeys deriveRotationKeys(TransportId t,
SecretKey rootKey, long timePeriod, boolean weAreAlice, SecretKey rootKey, long timePeriod, boolean weAreAlice,
boolean active) { boolean active) {
// Keys for the previous period are derived from the root key // Keys for the previous period are derived from the root key
@@ -70,31 +69,6 @@ class TransportCryptoImpl implements TransportCrypto {
return new TransportKeys(t, inPrev, inCurr, inNext, outCurr); return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
} }
@Override
public TransportKeys rotateTransportKeys(TransportKeys k, long timePeriod) {
if (k.getTimePeriod() >= timePeriod) return k;
IncomingKeys inPrev = k.getPreviousIncomingKeys();
IncomingKeys inCurr = k.getCurrentIncomingKeys();
IncomingKeys inNext = k.getNextIncomingKeys();
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
long startPeriod = outCurr.getTimePeriod();
boolean active = outCurr.isActive();
// Rotate the keys
for (long p = startPeriod + 1; p <= timePeriod; 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 + 1);
SecretKey outCurrTag = rotateKey(outCurr.getTagKey(), p);
SecretKey outCurrHeader = rotateKey(outCurr.getHeaderKey(), p);
outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p, active);
}
// Collect and return the keys
return new TransportKeys(k.getTransportId(), inPrev, inCurr, inNext,
outCurr);
}
private SecretKey rotateKey(SecretKey k, long timePeriod) { private SecretKey rotateKey(SecretKey k, long timePeriod) {
byte[] period = new byte[INT_64_BYTES]; byte[] period = new byte[INT_64_BYTES];
writeUint64(timePeriod, period, 0); writeUint64(timePeriod, period, 0);
@@ -117,7 +91,7 @@ class TransportCryptoImpl implements TransportCrypto {
} }
@Override @Override
public HandshakeKeys deriveHandshakeKeys(TransportId t, SecretKey rootKey, public TransportKeys deriveHandshakeKeys(TransportId t, SecretKey rootKey,
long timePeriod, boolean weAreAlice) { long timePeriod, boolean weAreAlice) {
if (timePeriod < 1) throw new IllegalArgumentException(); if (timePeriod < 1) throw new IllegalArgumentException();
IncomingKeys inPrev = deriveIncomingHandshakeKeys(t, rootKey, IncomingKeys inPrev = deriveIncomingHandshakeKeys(t, rootKey,
@@ -128,7 +102,7 @@ class TransportCryptoImpl implements TransportCrypto {
weAreAlice, timePeriod + 1); weAreAlice, timePeriod + 1);
OutgoingKeys outCurr = deriveOutgoingHandshakeKeys(t, rootKey, OutgoingKeys outCurr = deriveOutgoingHandshakeKeys(t, rootKey,
weAreAlice, timePeriod); weAreAlice, timePeriod);
return new HandshakeKeys(t, inPrev, inCurr, inNext, outCurr, rootKey, return new TransportKeys(t, inPrev, inCurr, inNext, outCurr, rootKey,
weAreAlice); weAreAlice);
} }
@@ -171,7 +145,13 @@ class TransportCryptoImpl implements TransportCrypto {
} }
@Override @Override
public HandshakeKeys updateHandshakeKeys(HandshakeKeys k, long timePeriod) { public TransportKeys updateTransportKeys(TransportKeys k, long timePeriod) {
if (k.isHandshakeMode()) return updateHandshakeKeys(k, timePeriod);
else return updateRotationKeys(k, timePeriod);
}
private TransportKeys updateHandshakeKeys(TransportKeys k,
long timePeriod) {
long elapsed = timePeriod - k.getTimePeriod(); long elapsed = timePeriod - k.getTimePeriod();
TransportId t = k.getTransportId(); TransportId t = k.getTransportId();
SecretKey rootKey = k.getRootKey(); SecretKey rootKey = k.getRootKey();
@@ -188,7 +168,7 @@ class TransportCryptoImpl implements TransportCrypto {
weAreAlice, timePeriod + 1); weAreAlice, timePeriod + 1);
OutgoingKeys outCurr = deriveOutgoingHandshakeKeys(t, rootKey, OutgoingKeys outCurr = deriveOutgoingHandshakeKeys(t, rootKey,
weAreAlice, timePeriod); weAreAlice, timePeriod);
return new HandshakeKeys(t, inPrev, inCurr, inNext, outCurr, return new TransportKeys(t, inPrev, inCurr, inNext, outCurr,
rootKey, weAreAlice); rootKey, weAreAlice);
} else if (elapsed == 2) { } else if (elapsed == 2) {
// The keys are two periods old - shift by two periods, keeping // The keys are two periods old - shift by two periods, keeping
@@ -200,7 +180,7 @@ class TransportCryptoImpl implements TransportCrypto {
weAreAlice, timePeriod + 1); weAreAlice, timePeriod + 1);
OutgoingKeys outCurr = deriveOutgoingHandshakeKeys(t, rootKey, OutgoingKeys outCurr = deriveOutgoingHandshakeKeys(t, rootKey,
weAreAlice, timePeriod); weAreAlice, timePeriod);
return new HandshakeKeys(t, inPrev, inCurr, inNext, outCurr, return new TransportKeys(t, inPrev, inCurr, inNext, outCurr,
rootKey, weAreAlice); rootKey, weAreAlice);
} else { } else {
// The keys are more than two periods old - derive fresh keys // The keys are more than two periods old - derive fresh keys
@@ -208,6 +188,30 @@ class TransportCryptoImpl implements TransportCrypto {
} }
} }
private TransportKeys updateRotationKeys(TransportKeys k, long timePeriod) {
if (k.getTimePeriod() >= timePeriod) return k;
IncomingKeys inPrev = k.getPreviousIncomingKeys();
IncomingKeys inCurr = k.getCurrentIncomingKeys();
IncomingKeys inNext = k.getNextIncomingKeys();
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
long startPeriod = outCurr.getTimePeriod();
boolean active = outCurr.isActive();
// Rotate the keys
for (long p = startPeriod + 1; p <= timePeriod; 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 + 1);
SecretKey outCurrTag = rotateKey(outCurr.getTagKey(), p);
SecretKey outCurrHeader = rotateKey(outCurr.getHeaderKey(), p);
outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p, active);
}
// Collect and return the keys
return new TransportKeys(k.getTransportId(), inPrev, inCurr, inNext,
outCurr);
}
@Override @Override
public void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion, public void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
long streamNumber) { long streamNumber) {

View File

@@ -5,6 +5,8 @@ import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactId; import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.contact.PendingContactState; import org.briarproject.bramble.api.contact.PendingContactState;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DataTooNewException; import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException; import org.briarproject.bramble.api.db.DataTooOldException;
@@ -27,11 +29,8 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.sync.validation.MessageState; import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.transport.HandshakeKeySet; import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.HandshakeKeySetId;
import org.briarproject.bramble.api.transport.HandshakeKeys;
import org.briarproject.bramble.api.transport.TransportKeySet; import org.briarproject.bramble.api.transport.TransportKeySet;
import org.briarproject.bramble.api.transport.TransportKeySetId;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
import java.util.Collection; import java.util.Collection;
@@ -105,20 +104,6 @@ interface Database<T> {
void addGroupVisibility(T txn, ContactId c, GroupId g, boolean shared) void addGroupVisibility(T txn, ContactId c, GroupId g, boolean shared)
throws DbException; throws DbException;
/**
* Stores the given handshake keys for the given contact and returns a
* key set ID.
*/
HandshakeKeySetId addHandshakeKeys(T txn, ContactId c, HandshakeKeys k)
throws DbException;
/**
* Stores the given handshake keys for the given pending contact and
* returns a key set ID.
*/
HandshakeKeySetId addHandshakeKeys(T txn, PendingContactId p,
HandshakeKeys k) throws DbException;
/** /**
* Stores an identity. * Stores an identity.
*/ */
@@ -160,7 +145,14 @@ interface Database<T> {
* Stores the given transport keys for the given contact and returns a * Stores the given transport keys for the given contact and returns a
* key set ID. * key set ID.
*/ */
TransportKeySetId addTransportKeys(T txn, ContactId c, TransportKeys k) KeySetId addTransportKeys(T txn, ContactId c, TransportKeys k)
throws DbException;
/**
* Stores the given transport keys for the given pending contact and
* returns a key set ID.
*/
KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k)
throws DbException; throws DbException;
/** /**
@@ -273,7 +265,7 @@ interface Database<T> {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<ContactId> getContacts(T txn, AuthorId a) throws DbException; Collection<ContactId> getContacts(T txn, AuthorId local) throws DbException;
/** /**
* Returns the group with the given ID. * Returns the group with the given ID.
@@ -315,14 +307,6 @@ interface Database<T> {
Map<ContactId, Boolean> getGroupVisibility(T txn, GroupId g) Map<ContactId, Boolean> getGroupVisibility(T txn, GroupId g)
throws DbException; throws DbException;
/**
* Returns all handshake keys for the given transport.
* <p/>
* Read-only.
*/
Collection<HandshakeKeySet> getHandshakeKeys(T txn, TransportId t)
throws DbException;
/** /**
* Returns the identity for local pseudonym with the given ID. * Returns the identity for local pseudonym with the given ID.
* <p/> * <p/>
@@ -545,16 +529,10 @@ interface Database<T> {
Collection<TransportKeySet> getTransportKeys(T txn, TransportId t) Collection<TransportKeySet> getTransportKeys(T txn, TransportId t)
throws DbException; throws DbException;
/**
* Increments the outgoing stream counter for the given handshake keys.
*/
void incrementStreamCounter(T txn, TransportId t, HandshakeKeySetId k)
throws DbException;
/** /**
* Increments the outgoing stream counter for the given transport keys. * Increments the outgoing stream counter for the given transport keys.
*/ */
void incrementStreamCounter(T txn, TransportId t, TransportKeySetId k) void incrementStreamCounter(T txn, TransportId t, KeySetId k)
throws DbException; throws DbException;
/** /**
@@ -623,12 +601,6 @@ interface Database<T> {
void removeGroupVisibility(T txn, ContactId c, GroupId g) void removeGroupVisibility(T txn, ContactId c, GroupId g)
throws DbException; throws DbException;
/**
* Removes the given handshake keys from the database.
*/
void removeHandshakeKeys(T txn, TransportId t, HandshakeKeySetId k)
throws DbException;
/** /**
* Removes an identity (and all associated state) from the database. * Removes an identity (and all associated state) from the database.
*/ */
@@ -659,8 +631,7 @@ interface Database<T> {
/** /**
* Removes the given transport keys from the database. * Removes the given transport keys from the database.
*/ */
void removeTransportKeys(T txn, TransportId t, TransportKeySetId k) void removeTransportKeys(T txn, TransportId t, KeySetId k) throws DbException;
throws DbException;
/** /**
* Resets the transmission count and expiry time of the given message with * Resets the transmission count and expiry time of the given message with
@@ -689,8 +660,8 @@ interface Database<T> {
/** /**
* Sets the handshake key pair for the identity with the given ID. * Sets the handshake key pair for the identity with the given ID.
*/ */
void setHandshakeKeyPair(T txn, AuthorId local, byte[] publicKey, void setHandshakeKeyPair(T txn, AuthorId local, PublicKey publicKey,
byte[] privateKey) throws DbException; PrivateKey privateKey) throws DbException;
/** /**
* Marks the given message as shared. * Marks the given message as shared.
@@ -710,23 +681,16 @@ interface Database<T> {
PendingContactState state) throws DbException; PendingContactState state) throws DbException;
/** /**
* Sets the reordering window for the given transport key set in the given * Sets the reordering window for the given transport keys in the given
* time period. * time period.
*/ */
void setReorderingWindow(T txn, TransportKeySetId k, TransportId t, void setReorderingWindow(T txn, KeySetId k, TransportId t,
long timePeriod, long base, byte[] bitmap) throws DbException;
/**
* Sets the reordering window for the given handshake key set in the given
* time period.
*/
void setReorderingWindow(T txn, HandshakeKeySetId k, TransportId t,
long timePeriod, long base, byte[] bitmap) throws DbException; long timePeriod, long base, byte[] bitmap) throws DbException;
/** /**
* Marks the given transport keys as usable for outgoing streams. * Marks the given transport keys as usable for outgoing streams.
*/ */
void setTransportKeysActive(T txn, TransportId t, TransportKeySetId k) void setTransportKeysActive(T txn, TransportId t, KeySetId k)
throws DbException; throws DbException;
/** /**
@@ -738,12 +702,7 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Updates the given handshake keys. * Stores the given transport keys, deleting any keys they have replaced.
*/
void updateHandshakeKeys(T txn, HandshakeKeySet ks) throws DbException;
/**
* Updates the given transport keys following key rotation.
*/ */
void updateTransportKeys(T txn, TransportKeySet ks) throws DbException; void updateTransportKeys(T txn, TransportKeySet ks) throws DbException;
} }

View File

@@ -7,6 +7,10 @@ import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent; import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.ContactVerifiedEvent; import org.briarproject.bramble.api.contact.event.ContactVerifiedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.CommitAction; import org.briarproject.bramble.api.db.CommitAction;
import org.briarproject.bramble.api.db.CommitAction.Visitor; import org.briarproject.bramble.api.db.CommitAction.Visitor;
@@ -62,11 +66,8 @@ import org.briarproject.bramble.api.sync.event.MessageToRequestEvent;
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent; import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
import org.briarproject.bramble.api.sync.event.MessagesSentEvent; import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
import org.briarproject.bramble.api.sync.validation.MessageState; import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.transport.HandshakeKeySet; import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.HandshakeKeySetId;
import org.briarproject.bramble.api.transport.HandshakeKeys;
import org.briarproject.bramble.api.transport.TransportKeySet; import org.briarproject.bramble.api.transport.TransportKeySet;
import org.briarproject.bramble.api.transport.TransportKeySetId;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
import java.util.ArrayList; import java.util.ArrayList;
@@ -258,30 +259,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
} }
@Override
public HandshakeKeySetId addHandshakeKeys(Transaction transaction,
ContactId c, HandshakeKeys k) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
if (!db.containsTransport(txn, k.getTransportId()))
throw new NoSuchTransportException();
return db.addHandshakeKeys(txn, c, k);
}
@Override
public HandshakeKeySetId addHandshakeKeys(Transaction transaction,
PendingContactId p, HandshakeKeys k) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsPendingContact(txn, p))
throw new NoSuchPendingContactException();
if (!db.containsTransport(txn, k.getTransportId()))
throw new NoSuchTransportException();
return db.addHandshakeKeys(txn, p, k);
}
@Override @Override
public void addIdentity(Transaction transaction, Identity i) public void addIdentity(Transaction transaction, Identity i)
throws DbException { throws DbException {
@@ -318,6 +295,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (db.containsPendingContact(txn, p.getId())) if (db.containsPendingContact(txn, p.getId()))
throw new PendingContactExistsException(); throw new PendingContactExistsException();
db.addPendingContact(txn, p); db.addPendingContact(txn, p);
transaction.attach(new PendingContactStateChangedEvent(p.getId(),
p.getState()));
} }
@Override @Override
@@ -330,8 +309,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public TransportKeySetId addTransportKeys(Transaction transaction, public KeySetId addTransportKeys(Transaction transaction, ContactId c,
ContactId c, TransportKeys k) throws DbException { TransportKeys k) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
@@ -341,6 +320,18 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.addTransportKeys(txn, c, k); return db.addTransportKeys(txn, c, k);
} }
@Override
public KeySetId addTransportKeys(Transaction transaction,
PendingContactId p, TransportKeys k) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsPendingContact(txn, p))
throw new NoSuchPendingContactException();
if (!db.containsTransport(txn, k.getTransportId()))
throw new NoSuchTransportException();
return db.addTransportKeys(txn, p, k);
}
@Override @Override
public boolean containsContact(Transaction transaction, AuthorId remote, public boolean containsContact(Transaction transaction, AuthorId remote,
AuthorId local) throws DbException { AuthorId local) throws DbException {
@@ -503,11 +494,11 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Override @Override
public Collection<ContactId> getContacts(Transaction transaction, public Collection<ContactId> getContacts(Transaction transaction,
AuthorId a) throws DbException { AuthorId local) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsIdentity(txn, a)) if (!db.containsIdentity(txn, local))
throw new NoSuchIdentityException(); throw new NoSuchIdentityException();
return db.getContacts(txn, a); return db.getContacts(txn, local);
} }
@Override @Override
@@ -544,15 +535,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getGroupVisibility(txn, c, g); return db.getGroupVisibility(txn, c, g);
} }
@Override
public Collection<HandshakeKeySet> getHandshakeKeys(Transaction transaction,
TransportId t) throws DbException {
T txn = unbox(transaction);
if (!db.containsTransport(txn, t))
throw new NoSuchTransportException();
return db.getHandshakeKeys(txn, t);
}
@Override @Override
public Identity getIdentity(Transaction transaction, AuthorId a) public Identity getIdentity(Transaction transaction, AuthorId a)
throws DbException { throws DbException {
@@ -735,17 +717,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Override @Override
public void incrementStreamCounter(Transaction transaction, TransportId t, public void incrementStreamCounter(Transaction transaction, TransportId t,
HandshakeKeySetId k) throws DbException { KeySetId k) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsTransport(txn, t))
throw new NoSuchTransportException();
db.incrementStreamCounter(txn, t, k);
}
@Override
public void incrementStreamCounter(Transaction transaction, TransportId t,
TransportKeySetId k) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsTransport(txn, t)) if (!db.containsTransport(txn, t))
@@ -894,16 +866,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new GroupVisibilityUpdatedEvent(affected)); transaction.attach(new GroupVisibilityUpdatedEvent(affected));
} }
@Override
public void removeHandshakeKeys(Transaction transaction,
TransportId t, HandshakeKeySetId k) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsTransport(txn, t))
throw new NoSuchTransportException();
db.removeHandshakeKeys(txn, t, k);
}
@Override @Override
public void removeIdentity(Transaction transaction, AuthorId a) public void removeIdentity(Transaction transaction, AuthorId a)
throws DbException { throws DbException {
@@ -934,6 +896,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (!db.containsPendingContact(txn, p)) if (!db.containsPendingContact(txn, p))
throw new NoSuchPendingContactException(); throw new NoSuchPendingContactException();
db.removePendingContact(txn, p); db.removePendingContact(txn, p);
transaction.attach(new PendingContactRemovedEvent(p));
} }
@Override @Override
@@ -947,8 +910,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public void removeTransportKeys(Transaction transaction, public void removeTransportKeys(Transaction transaction, TransportId t,
TransportId t, TransportKeySetId k) throws DbException { KeySetId k) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsTransport(txn, t)) if (!db.containsTransport(txn, t))
@@ -1037,7 +1000,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Override @Override
public void setHandshakeKeyPair(Transaction transaction, AuthorId local, public void setHandshakeKeyPair(Transaction transaction, AuthorId local,
byte[] publicKey, byte[] privateKey) throws DbException { PublicKey publicKey, PrivateKey privateKey) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsIdentity(txn, local)) if (!db.containsIdentity(txn, local))
@@ -1046,20 +1009,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public void setReorderingWindow(Transaction transaction, public void setReorderingWindow(Transaction transaction, KeySetId k,
TransportKeySetId k, TransportId t, long timePeriod, long base, TransportId t, long timePeriod, long base, byte[] bitmap)
byte[] bitmap) throws DbException { throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsTransport(txn, t))
throw new NoSuchTransportException();
db.setReorderingWindow(txn, k, t, timePeriod, base, bitmap);
}
@Override
public void setReorderingWindow(Transaction transaction,
HandshakeKeySetId k, TransportId t, long timePeriod, long base,
byte[] bitmap) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsTransport(txn, t)) if (!db.containsTransport(txn, t))
@@ -1069,7 +1021,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Override @Override
public void setTransportKeysActive(Transaction transaction, TransportId t, public void setTransportKeysActive(Transaction transaction, TransportId t,
TransportKeySetId k) throws DbException { KeySetId k) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsTransport(txn, t)) if (!db.containsTransport(txn, t))
@@ -1077,18 +1029,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.setTransportKeysActive(txn, t, k); db.setTransportKeysActive(txn, t, k);
} }
@Override
public void updateHandshakeKeys(Transaction transaction,
Collection<HandshakeKeySet> keys) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
for (HandshakeKeySet ks : keys) {
TransportId t = ks.getKeys().getTransportId();
if (db.containsTransport(txn, t))
db.updateHandshakeKeys(txn, ks);
}
}
@Override @Override
public void updateTransportKeys(Transaction transaction, public void updateTransportKeys(Transaction transaction,
Collection<TransportKeySet> keys) throws DbException { Collection<TransportKeySet> keys) throws DbException {

View File

@@ -20,9 +20,11 @@ import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose; import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logFileOrDir;
/** /**
* Contains all the H2-specific code for the database. * Contains all the H2-specific code for the database.
@@ -61,8 +63,18 @@ class H2Database extends JdbcDatabase {
public boolean open(SecretKey key, @Nullable MigrationListener listener) public boolean open(SecretKey key, @Nullable MigrationListener listener)
throws DbException { throws DbException {
this.key = key; this.key = key;
boolean reopen = !config.getDatabaseDirectory().mkdirs(); File dir = config.getDatabaseDirectory();
if (LOG.isLoggable(INFO)) {
LOG.info("Contents of account directory before opening DB:");
logFileOrDir(LOG, INFO, dir.getParentFile());
}
boolean reopen = !dir.mkdirs();
if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen);
super.open("org.h2.Driver", reopen, key, listener); super.open("org.h2.Driver", reopen, key, listener);
if (LOG.isLoggable(INFO)) {
LOG.info("Contents of account directory after opening DB:");
logFileOrDir(LOG, INFO, dir.getParentFile());
}
return reopen; return reopen;
} }

View File

@@ -5,7 +5,13 @@ import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactId; import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.contact.PendingContactState; import org.briarproject.bramble.api.contact.PendingContactState;
import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.SignaturePrivateKey;
import org.briarproject.bramble.api.crypto.SignaturePublicKey;
import org.briarproject.bramble.api.db.DataTooNewException; import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException; import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DbClosedException; import org.briarproject.bramble.api.db.DbClosedException;
@@ -30,13 +36,10 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.sync.validation.MessageState; import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.transport.HandshakeKeySet;
import org.briarproject.bramble.api.transport.HandshakeKeySetId;
import org.briarproject.bramble.api.transport.HandshakeKeys;
import org.briarproject.bramble.api.transport.IncomingKeys; import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.OutgoingKeys; import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeySet; import org.briarproject.bramble.api.transport.TransportKeySet;
import org.briarproject.bramble.api.transport.TransportKeySetId;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
import java.sql.Connection; import java.sql.Connection;
@@ -62,11 +65,13 @@ import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static java.sql.Types.BINARY; import static java.sql.Types.BINARY;
import static java.sql.Types.BOOLEAN;
import static java.sql.Types.INTEGER; import static java.sql.Types.INTEGER;
import static java.sql.Types.VARCHAR; import static java.sql.Types.VARCHAR;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
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 java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.db.Metadata.REMOVE; import static org.briarproject.bramble.api.db.Metadata.REMOVE;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
@@ -93,7 +98,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
abstract class JdbcDatabase implements Database<Connection> { abstract class JdbcDatabase implements Database<Connection> {
// Package access for testing // Package access for testing
static final int CODE_SCHEMA_VERSION = 43; static final int CODE_SCHEMA_VERSION = 44;
// Time period offsets for incoming transport keys // Time period offsets for incoming transport keys
private static final int OFFSET_PREV = -1; private static final int OFFSET_PREV = -1;
@@ -254,16 +259,28 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " maxLatency INT NOT NULL," + " maxLatency INT NOT NULL,"
+ " PRIMARY KEY (transportId))"; + " PRIMARY KEY (transportId))";
private static final String CREATE_PENDING_CONTACTS =
"CREATE TABLE pendingContacts"
+ " (pendingContactId _HASH NOT NULL,"
+ " publicKey _BINARY NOT NULL,"
+ " alias _STRING NOT NULL,"
+ " state INT NOT NULL,"
+ " timestamp BIGINT NOT NULL,"
+ " PRIMARY KEY (pendingContactId))";
private static final String CREATE_OUTGOING_KEYS = private static final String CREATE_OUTGOING_KEYS =
"CREATE TABLE outgoingKeys" "CREATE TABLE outgoingKeys"
+ " (transportId _STRING NOT NULL," + " (transportId _STRING NOT NULL,"
+ " keySetId _COUNTER," + " keySetId _COUNTER,"
+ " timePeriod BIGINT NOT NULL," + " timePeriod BIGINT NOT NULL,"
+ " contactId INT NOT NULL," + " contactId INT," // Null if contact is pending
+ " pendingContactId _HASH," // Null if not pending
+ " tagKey _SECRET NOT NULL," + " tagKey _SECRET NOT NULL,"
+ " headerKey _SECRET NOT NULL," + " headerKey _SECRET NOT NULL,"
+ " stream BIGINT NOT NULL," + " stream BIGINT NOT NULL,"
+ " active BOOLEAN NOT NULL," + " active BOOLEAN NOT NULL,"
+ " rootKey _SECRET," // Null for rotation keys
+ " alice BOOLEAN," // Null for rotation keys
+ " PRIMARY KEY (transportId, keySetId)," + " PRIMARY KEY (transportId, keySetId),"
+ " FOREIGN KEY (transportId)" + " FOREIGN KEY (transportId)"
+ " REFERENCES transports (transportId)" + " REFERENCES transports (transportId)"
@@ -271,6 +288,9 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " UNIQUE (keySetId)," + " UNIQUE (keySetId),"
+ " FOREIGN KEY (contactId)" + " FOREIGN KEY (contactId)"
+ " REFERENCES contacts (contactId)" + " REFERENCES contacts (contactId)"
+ " ON DELETE CASCADE,"
+ " FOREIGN KEY (pendingContactId)"
+ " REFERENCES pendingContacts (pendingContactId)"
+ " ON DELETE CASCADE)"; + " ON DELETE CASCADE)";
private static final String CREATE_INCOMING_KEYS = private static final String CREATE_INCOMING_KEYS =
@@ -291,57 +311,6 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " REFERENCES outgoingKeys (keySetId)" + " REFERENCES outgoingKeys (keySetId)"
+ " ON DELETE CASCADE)"; + " ON DELETE CASCADE)";
private static final String CREATE_PENDING_CONTACTS =
"CREATE TABLE pendingContacts"
+ " (pendingContactId _HASH NOT NULL,"
+ " publicKey _BINARY NOT NULL,"
+ " alias _STRING NOT NULL,"
+ " state INT NOT NULL,"
+ " timestamp BIGINT NOT NULL,"
+ " PRIMARY KEY (pendingContactId))";
private static final String CREATE_OUTGOING_HANDSHAKE_KEYS =
"CREATE TABLE outgoingHandshakeKeys"
+ " (transportId _STRING NOT NULL,"
+ " keySetId _COUNTER,"
+ " timePeriod BIGINT NOT NULL,"
+ " contactId INT," // Null if contact is pending
+ " pendingContactId _HASH," // Null if not pending
+ " rootKey _SECRET NOT NULL,"
+ " alice BOOLEAN NOT NULL,"
+ " tagKey _SECRET NOT NULL,"
+ " headerKey _SECRET NOT NULL,"
+ " stream BIGINT NOT NULL,"
+ " PRIMARY KEY (transportId, keySetId),"
+ " FOREIGN KEY (transportId)"
+ " REFERENCES transports (transportId)"
+ " ON DELETE CASCADE,"
+ " UNIQUE (keySetId),"
+ " FOREIGN KEY (contactId)"
+ " REFERENCES contacts (contactId)"
+ " ON DELETE CASCADE,"
+ " FOREIGN KEY (pendingContactId)"
+ " REFERENCES pendingContacts (pendingContactId)"
+ " ON DELETE CASCADE)";
private static final String CREATE_INCOMING_HANDSHAKE_KEYS =
"CREATE TABLE incomingHandshakeKeys"
+ " (transportId _STRING NOT NULL,"
+ " keySetId INT NOT NULL,"
+ " timePeriod BIGINT NOT NULL,"
+ " tagKey _SECRET NOT NULL,"
+ " headerKey _SECRET NOT NULL,"
+ " base BIGINT NOT NULL,"
+ " bitmap _BINARY NOT NULL,"
+ " periodOffset INT NOT NULL,"
+ " PRIMARY KEY (transportId, keySetId, periodOffset),"
+ " FOREIGN KEY (transportId)"
+ " REFERENCES transports (transportId)"
+ " ON DELETE CASCADE,"
+ " FOREIGN KEY (keySetId)"
+ " REFERENCES outgoingHandshakeKeys (keySetId)"
+ " ON DELETE CASCADE)";
private static final String INDEX_CONTACTS_BY_AUTHOR_ID = private static final String INDEX_CONTACTS_BY_AUTHOR_ID =
"CREATE INDEX IF NOT EXISTS contactsByAuthorId" "CREATE INDEX IF NOT EXISTS contactsByAuthorId"
+ " ON contacts (authorId)"; + " ON contacts (authorId)";
@@ -367,7 +336,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " ON statuses (contactId, timestamp)"; + " ON statuses (contactId, timestamp)";
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(JdbcDatabase.class.getName()); getLogger(JdbcDatabase.class.getName());
// Different database libraries use different names for certain types // Different database libraries use different names for certain types
private final MessageFactory messageFactory; private final MessageFactory messageFactory;
@@ -487,7 +456,8 @@ abstract class JdbcDatabase implements Database<Connection> {
new Migration39_40(), new Migration39_40(),
new Migration40_41(dbTypes), new Migration40_41(dbTypes),
new Migration41_42(dbTypes), new Migration41_42(dbTypes),
new Migration42_43(dbTypes) new Migration42_43(dbTypes),
new Migration43_44(dbTypes)
); );
} }
@@ -535,13 +505,9 @@ abstract class JdbcDatabase implements Database<Connection> {
s.executeUpdate(dbTypes.replaceTypes(CREATE_OFFERS)); s.executeUpdate(dbTypes.replaceTypes(CREATE_OFFERS));
s.executeUpdate(dbTypes.replaceTypes(CREATE_STATUSES)); s.executeUpdate(dbTypes.replaceTypes(CREATE_STATUSES));
s.executeUpdate(dbTypes.replaceTypes(CREATE_TRANSPORTS)); s.executeUpdate(dbTypes.replaceTypes(CREATE_TRANSPORTS));
s.executeUpdate(dbTypes.replaceTypes(CREATE_PENDING_CONTACTS));
s.executeUpdate(dbTypes.replaceTypes(CREATE_OUTGOING_KEYS)); s.executeUpdate(dbTypes.replaceTypes(CREATE_OUTGOING_KEYS));
s.executeUpdate(dbTypes.replaceTypes(CREATE_INCOMING_KEYS)); s.executeUpdate(dbTypes.replaceTypes(CREATE_INCOMING_KEYS));
s.executeUpdate(dbTypes.replaceTypes(CREATE_PENDING_CONTACTS));
s.executeUpdate(dbTypes.replaceTypes(
CREATE_OUTGOING_HANDSHAKE_KEYS));
s.executeUpdate(dbTypes.replaceTypes(
CREATE_INCOMING_HANDSHAKE_KEYS));
s.close(); s.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(s, LOG, WARNING); tryToClose(s, LOG, WARNING);
@@ -677,7 +643,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setBytes(1, remote.getId().getBytes()); ps.setBytes(1, remote.getId().getBytes());
ps.setInt(2, remote.getFormatVersion()); ps.setInt(2, remote.getFormatVersion());
ps.setString(3, remote.getName()); ps.setString(3, remote.getName());
ps.setBytes(4, remote.getPublicKey()); ps.setBytes(4, remote.getPublicKey().getEncoded());
ps.setBytes(5, local.getBytes()); ps.setBytes(5, local.getBytes());
ps.setBoolean(6, verified); ps.setBoolean(6, verified);
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
@@ -777,103 +743,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public HandshakeKeySetId addHandshakeKeys(Connection txn, ContactId c,
HandshakeKeys k) throws DbException {
return addHandshakeKeys(txn, c, null, k);
}
@Override
public HandshakeKeySetId addHandshakeKeys(Connection txn,
PendingContactId p, HandshakeKeys k) throws DbException {
return addHandshakeKeys(txn, null, p, k);
}
private HandshakeKeySetId addHandshakeKeys(Connection txn,
@Nullable ContactId c, @Nullable PendingContactId p,
HandshakeKeys k) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
// Store the outgoing keys
String sql = "INSERT INTO outgoingHandshakeKeys (contactId,"
+ " pendingContactId, transportId, rootKey, alice,"
+ " timePeriod, tagKey, headerKey, stream)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
if (c == null) ps.setNull(1, INTEGER);
else ps.setInt(1, c.getInt());
if (p == null) ps.setNull(2, BINARY);
else ps.setBytes(2, p.getBytes());
ps.setString(3, k.getTransportId().getString());
ps.setBytes(4, k.getRootKey().getBytes());
ps.setBoolean(5, k.isAlice());
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
ps.setLong(6, outCurr.getTimePeriod());
ps.setBytes(7, outCurr.getTagKey().getBytes());
ps.setBytes(8, outCurr.getHeaderKey().getBytes());
ps.setLong(9, outCurr.getStreamCounter());
int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException();
ps.close();
// Get the new (highest) key set ID
sql = "SELECT keySetId FROM outgoingHandshakeKeys"
+ " ORDER BY keySetId DESC LIMIT 1";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
HandshakeKeySetId keySetId = new HandshakeKeySetId(rs.getInt(1));
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
// Store the incoming keys
sql = "INSERT INTO incomingHandshakeKeys (keySetId, transportId,"
+ " timePeriod, tagKey, headerKey, base, bitmap,"
+ " periodOffset)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, keySetId.getInt());
ps.setString(2, k.getTransportId().getString());
// Previous time period
IncomingKeys inPrev = k.getPreviousIncomingKeys();
ps.setLong(3, inPrev.getTimePeriod());
ps.setBytes(4, inPrev.getTagKey().getBytes());
ps.setBytes(5, inPrev.getHeaderKey().getBytes());
ps.setLong(6, inPrev.getWindowBase());
ps.setBytes(7, inPrev.getWindowBitmap());
ps.setInt(8, OFFSET_PREV);
ps.addBatch();
// Current time period
IncomingKeys inCurr = k.getCurrentIncomingKeys();
ps.setLong(3, inCurr.getTimePeriod());
ps.setBytes(4, inCurr.getTagKey().getBytes());
ps.setBytes(5, inCurr.getHeaderKey().getBytes());
ps.setLong(6, inCurr.getWindowBase());
ps.setBytes(7, inCurr.getWindowBitmap());
ps.setInt(8, OFFSET_CURR);
ps.addBatch();
// Next time period
IncomingKeys inNext = k.getNextIncomingKeys();
ps.setLong(3, inNext.getTimePeriod());
ps.setBytes(4, inNext.getTagKey().getBytes());
ps.setBytes(5, inNext.getHeaderKey().getBytes());
ps.setLong(6, inNext.getWindowBase());
ps.setBytes(7, inNext.getWindowBitmap());
ps.setInt(8, OFFSET_NEXT);
ps.addBatch();
int[] batchAffected = ps.executeBatch();
if (batchAffected.length != 3) throw new DbStateException();
for (int rows : batchAffected)
if (rows != 1) throw new DbStateException();
ps.close();
return keySetId;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public void addIdentity(Connection txn, Identity i) throws DbException { public void addIdentity(Connection txn, Identity i) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -887,12 +756,12 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setBytes(1, local.getId().getBytes()); ps.setBytes(1, local.getId().getBytes());
ps.setInt(2, local.getFormatVersion()); ps.setInt(2, local.getFormatVersion());
ps.setString(3, local.getName()); ps.setString(3, local.getName());
ps.setBytes(4, local.getPublicKey()); ps.setBytes(4, local.getPublicKey().getEncoded());
ps.setBytes(5, local.getPrivateKey()); ps.setBytes(5, local.getPrivateKey().getEncoded());
if (i.getHandshakePublicKey() == null) ps.setNull(6, BINARY); if (i.getHandshakePublicKey() == null) ps.setNull(6, BINARY);
else ps.setBytes(6, i.getHandshakePublicKey()); else ps.setBytes(6, i.getHandshakePublicKey().getEncoded());
if (i.getHandshakePrivateKey() == null) ps.setNull(7, BINARY); if (i.getHandshakePrivateKey() == null) ps.setNull(7, BINARY);
else ps.setBytes(7, i.getHandshakePrivateKey()); else ps.setBytes(7, i.getHandshakePrivateKey().getEncoded());
ps.setLong(8, i.getTimeCreated()); ps.setLong(8, i.getTimeCreated());
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
@@ -1068,7 +937,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " VALUES (?, ?, ?, ?, ?)"; + " VALUES (?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, p.getId().getBytes()); ps.setBytes(1, p.getId().getBytes());
ps.setBytes(2, p.getPublicKey()); ps.setBytes(2, p.getPublicKey().getEncoded());
ps.setString(3, p.getAlias()); ps.setString(3, p.getAlias());
ps.setInt(4, p.getState().getValue()); ps.setInt(4, p.getState().getValue());
ps.setLong(5, p.getTimestamp()); ps.setLong(5, p.getTimestamp());
@@ -1101,24 +970,47 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public TransportKeySetId addTransportKeys(Connection txn, ContactId c, public KeySetId addTransportKeys(Connection txn, ContactId c,
TransportKeys k) throws DbException {
return addTransportKeys(txn, c, null, k);
}
@Override
public KeySetId addTransportKeys(Connection txn,
PendingContactId p, TransportKeys k) throws DbException {
return addTransportKeys(txn, null, p, k);
}
private KeySetId addTransportKeys(Connection txn,
@Nullable ContactId c, @Nullable PendingContactId p,
TransportKeys k) throws DbException { TransportKeys k) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
// Store the outgoing keys // Store the outgoing keys
String sql = "INSERT INTO outgoingKeys (contactId, transportId," String sql = "INSERT INTO outgoingKeys (transportId, timePeriod,"
+ " timePeriod, tagKey, headerKey, stream, active)" + " contactId, pendingContactId, tagKey, headerKey,"
+ " VALUES (?, ?, ?, ?, ?, ?, ?)"; + " stream, active, rootKey, alice)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt()); ps.setString(1, k.getTransportId().getString());
ps.setString(2, k.getTransportId().getString()); ps.setLong(2, k.getTimePeriod());
if (c == null) ps.setNull(3, INTEGER);
else ps.setInt(3, c.getInt());
if (p == null) ps.setNull(4, BINARY);
else ps.setBytes(4, p.getBytes());
OutgoingKeys outCurr = k.getCurrentOutgoingKeys(); OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
ps.setLong(3, outCurr.getTimePeriod()); ps.setBytes(5, outCurr.getTagKey().getBytes());
ps.setBytes(4, outCurr.getTagKey().getBytes()); ps.setBytes(6, outCurr.getHeaderKey().getBytes());
ps.setBytes(5, outCurr.getHeaderKey().getBytes()); ps.setLong(7, outCurr.getStreamCounter());
ps.setLong(6, outCurr.getStreamCounter()); ps.setBoolean(8, outCurr.isActive());
ps.setBoolean(7, outCurr.isActive()); if (k.isHandshakeMode()) {
ps.setBytes(9, k.getRootKey().getBytes());
ps.setBoolean(10, k.isAlice());
} else {
ps.setNull(9, BINARY);
ps.setNull(10, BOOLEAN);
}
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
@@ -1128,18 +1020,18 @@ abstract class JdbcDatabase implements Database<Connection> {
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
rs = ps.executeQuery(); rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException(); if (!rs.next()) throw new DbStateException();
TransportKeySetId keySetId = new TransportKeySetId(rs.getInt(1)); KeySetId keySetId = new KeySetId(rs.getInt(1));
if (rs.next()) throw new DbStateException(); if (rs.next()) throw new DbStateException();
rs.close(); rs.close();
ps.close(); ps.close();
// Store the incoming keys // Store the incoming keys
sql = "INSERT INTO incomingKeys (keySetId, transportId," sql = "INSERT INTO incomingKeys (transportId, keySetId,"
+ " timePeriod, tagKey, headerKey, base, bitmap," + " timePeriod, tagKey, headerKey, base, bitmap,"
+ " periodOffset)" + " periodOffset)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; + " VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, keySetId.getInt()); ps.setString(1, k.getTransportId().getString());
ps.setString(2, k.getTransportId().getString()); ps.setInt(2, keySetId.getInt());
// Previous time period // Previous time period
IncomingKeys inPrev = k.getPreviousIncomingKeys(); IncomingKeys inPrev = k.getPreviousIncomingKeys();
ps.setLong(3, inPrev.getTimePeriod()); ps.setLong(3, inPrev.getTimePeriod());
@@ -1444,14 +1336,16 @@ abstract class JdbcDatabase implements Database<Connection> {
int formatVersion = rs.getInt(2); int formatVersion = rs.getInt(2);
String name = rs.getString(3); String name = rs.getString(3);
String alias = rs.getString(4); String alias = rs.getString(4);
byte[] publicKey = rs.getBytes(5); PublicKey publicKey = new SignaturePublicKey(rs.getBytes(5));
byte[] handshakePublicKey = rs.getBytes(6); byte[] handshakePub = rs.getBytes(6);
AuthorId localAuthorId = new AuthorId(rs.getBytes(7)); AuthorId localAuthorId = new AuthorId(rs.getBytes(7));
boolean verified = rs.getBoolean(8); boolean verified = rs.getBoolean(8);
rs.close(); rs.close();
ps.close(); ps.close();
Author author = Author author =
new Author(authorId, formatVersion, name, publicKey); new Author(authorId, formatVersion, name, publicKey);
PublicKey handshakePublicKey = handshakePub == null ?
null : new AgreementPublicKey(handshakePub);
return new Contact(c, author, localAuthorId, alias, return new Contact(c, author, localAuthorId, alias,
handshakePublicKey, verified); handshakePublicKey, verified);
} catch (SQLException e) { } catch (SQLException e) {
@@ -1479,12 +1373,14 @@ abstract class JdbcDatabase implements Database<Connection> {
int formatVersion = rs.getInt(3); int formatVersion = rs.getInt(3);
String name = rs.getString(4); String name = rs.getString(4);
String alias = rs.getString(5); String alias = rs.getString(5);
byte[] publicKey = rs.getBytes(6); PublicKey publicKey = new SignaturePublicKey(rs.getBytes(6));
byte[] handshakePublicKey = rs.getBytes(7); byte[] handshakePub = rs.getBytes(7);
AuthorId localAuthorId = new AuthorId(rs.getBytes(8)); AuthorId localAuthorId = new AuthorId(rs.getBytes(8));
boolean verified = rs.getBoolean(9); boolean verified = rs.getBoolean(9);
Author author = Author author =
new Author(authorId, formatVersion, name, publicKey); new Author(authorId, formatVersion, name, publicKey);
PublicKey handshakePublicKey = handshakePub == null ?
null : new AgreementPublicKey(handshakePub);
contacts.add(new Contact(contactId, author, localAuthorId, contacts.add(new Contact(contactId, author, localAuthorId,
alias, handshakePublicKey, verified)); alias, handshakePublicKey, verified));
} }
@@ -1540,12 +1436,14 @@ abstract class JdbcDatabase implements Database<Connection> {
int formatVersion = rs.getInt(2); int formatVersion = rs.getInt(2);
String name = rs.getString(3); String name = rs.getString(3);
String alias = rs.getString(4); String alias = rs.getString(4);
byte[] publicKey = rs.getBytes(5); PublicKey publicKey = new SignaturePublicKey(rs.getBytes(5));
byte[] handshakePublicKey = rs.getBytes(6); byte[] handshakePub = rs.getBytes(6);
AuthorId localAuthorId = new AuthorId(rs.getBytes(7)); AuthorId localAuthorId = new AuthorId(rs.getBytes(7));
boolean verified = rs.getBoolean(8); boolean verified = rs.getBoolean(8);
Author author = Author author =
new Author(remote, formatVersion, name, publicKey); new Author(remote, formatVersion, name, publicKey);
PublicKey handshakePublicKey = handshakePub == null ?
null : new AgreementPublicKey(handshakePub);
contacts.add(new Contact(contactId, author, localAuthorId, contacts.add(new Contact(contactId, author, localAuthorId,
alias, handshakePublicKey, verified)); alias, handshakePublicKey, verified));
} }
@@ -1661,86 +1559,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public Collection<HandshakeKeySet> getHandshakeKeys(Connection txn,
TransportId t) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
// Retrieve the incoming keys
String sql = "SELECT timePeriod, tagKey, headerKey, base, bitmap"
+ " FROM incomingHandshakeKeys"
+ " WHERE transportId = ?"
+ " ORDER BY keySetId, periodOffset";
ps = txn.prepareStatement(sql);
ps.setString(1, t.getString());
rs = ps.executeQuery();
List<IncomingKeys> inKeys = new ArrayList<>();
while (rs.next()) {
long timePeriod = 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, timePeriod,
windowBase, windowBitmap));
}
rs.close();
ps.close();
// Retrieve the outgoing keys in the same order
sql = "SELECT keySetId, contactId, pendingContactId, timePeriod,"
+ " tagKey, headerKey, rootKey, alice, stream"
+ " FROM outgoingHandshakeKeys"
+ " WHERE transportId = ?"
+ " ORDER BY keySetId";
ps = txn.prepareStatement(sql);
ps.setString(1, t.getString());
rs = ps.executeQuery();
Collection<HandshakeKeySet> keys = new ArrayList<>();
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();
HandshakeKeySetId keySetId =
new HandshakeKeySetId(rs.getInt(1));
ContactId contactId = null;
int cId = rs.getInt(2);
if (!rs.wasNull()) contactId = new ContactId(cId);
PendingContactId pendingContactId = null;
byte[] pId = rs.getBytes(3);
if (!rs.wasNull()) pendingContactId = new PendingContactId(pId);
long timePeriod = rs.getLong(4);
SecretKey tagKey = new SecretKey(rs.getBytes(5));
SecretKey headerKey = new SecretKey(rs.getBytes(6));
SecretKey rootKey = new SecretKey(rs.getBytes(7));
boolean alice = rs.getBoolean(8);
long streamCounter = rs.getLong(9);
OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey,
timePeriod, streamCounter, true);
IncomingKeys inPrev = inKeys.get(i * 3);
IncomingKeys inCurr = inKeys.get(i * 3 + 1);
IncomingKeys inNext = inKeys.get(i * 3 + 2);
HandshakeKeys handshakeKeys = new HandshakeKeys(t, inPrev,
inCurr, inNext, outCurr, rootKey, alice);
if (contactId == null) {
if (pendingContactId == null) throw new DbStateException();
keys.add(new HandshakeKeySet(keySetId, pendingContactId,
handshakeKeys));
} else {
if (pendingContactId != null) throw new DbStateException();
keys.add(new HandshakeKeySet(keySetId, contactId,
handshakeKeys));
}
}
rs.close();
ps.close();
return keys;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public Identity getIdentity(Connection txn, AuthorId a) throws DbException { public Identity getIdentity(Connection txn, AuthorId a) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -1756,16 +1574,20 @@ abstract class JdbcDatabase implements Database<Connection> {
if (!rs.next()) throw new DbStateException(); if (!rs.next()) throw new DbStateException();
int formatVersion = rs.getInt(1); int formatVersion = rs.getInt(1);
String name = rs.getString(2); String name = rs.getString(2);
byte[] publicKey = rs.getBytes(3); PublicKey publicKey = new SignaturePublicKey(rs.getBytes(3));
byte[] privateKey = rs.getBytes(4); PrivateKey privateKey = new SignaturePrivateKey(rs.getBytes(4));
byte[] handshakePublicKey = rs.getBytes(5); byte[] handshakePub = rs.getBytes(5);
byte[] handshakePrivateKey = rs.getBytes(6); byte[] handshakePriv = rs.getBytes(6);
long created = rs.getLong(7); long created = rs.getLong(7);
if (rs.next()) throw new DbStateException(); if (rs.next()) throw new DbStateException();
rs.close(); rs.close();
ps.close(); ps.close();
LocalAuthor local = new LocalAuthor(a, formatVersion, name, LocalAuthor local = new LocalAuthor(a, formatVersion, name,
publicKey, privateKey); publicKey, privateKey);
PublicKey handshakePublicKey = handshakePub == null ?
null : new AgreementPublicKey(handshakePub);
PrivateKey handshakePrivateKey = handshakePriv == null ?
null : new AgreementPrivateKey(handshakePriv);
return new Identity(local, handshakePublicKey, handshakePrivateKey, return new Identity(local, handshakePublicKey, handshakePrivateKey,
created); created);
} catch (SQLException e) { } catch (SQLException e) {
@@ -1792,13 +1614,17 @@ abstract class JdbcDatabase implements Database<Connection> {
AuthorId authorId = new AuthorId(rs.getBytes(1)); AuthorId authorId = new AuthorId(rs.getBytes(1));
int formatVersion = rs.getInt(2); int formatVersion = rs.getInt(2);
String name = rs.getString(3); String name = rs.getString(3);
byte[] publicKey = rs.getBytes(4); PublicKey publicKey = new SignaturePublicKey(rs.getBytes(4));
byte[] privateKey = rs.getBytes(5); PrivateKey privateKey = new SignaturePrivateKey(rs.getBytes(5));
byte[] handshakePublicKey = rs.getBytes(6); byte[] handshakePub = rs.getBytes(6);
byte[] handshakePrivateKey = rs.getBytes(7); byte[] handshakePriv = rs.getBytes(7);
long created = rs.getLong(8); long created = rs.getLong(8);
LocalAuthor local = new LocalAuthor(authorId, formatVersion, LocalAuthor local = new LocalAuthor(authorId, formatVersion,
name, publicKey, privateKey); name, publicKey, privateKey);
PublicKey handshakePublicKey = handshakePub == null ?
null : new AgreementPublicKey(handshakePub);
PrivateKey handshakePrivateKey = handshakePriv == null ?
null : new AgreementPrivateKey(handshakePriv);
identities.add(new Identity(local, handshakePublicKey, identities.add(new Identity(local, handshakePublicKey,
handshakePrivateKey, created)); handshakePrivateKey, created));
} }
@@ -2395,7 +2221,7 @@ abstract class JdbcDatabase implements Database<Connection> {
List<PendingContact> pendingContacts = new ArrayList<>(); List<PendingContact> pendingContacts = new ArrayList<>();
while (rs.next()) { while (rs.next()) {
PendingContactId id = new PendingContactId(rs.getBytes(1)); PendingContactId id = new PendingContactId(rs.getBytes(1));
byte[] publicKey = rs.getBytes(2); PublicKey publicKey = new AgreementPublicKey(rs.getBytes(2));
String alias = rs.getString(3); String alias = rs.getString(3);
PendingContactState state = PendingContactState state =
PendingContactState.fromValue(rs.getInt(4)); PendingContactState.fromValue(rs.getInt(4));
@@ -2502,8 +2328,8 @@ abstract class JdbcDatabase implements Database<Connection> {
rs.close(); rs.close();
ps.close(); ps.close();
// Retrieve the outgoing keys in the same order // Retrieve the outgoing keys in the same order
sql = "SELECT keySetId, contactId, timePeriod," sql = "SELECT keySetId, timePeriod, contactId, pendingContactId,"
+ " tagKey, headerKey, stream, active" + " tagKey, headerKey, stream, active, rootKey, alice"
+ " FROM outgoingKeys" + " FROM outgoingKeys"
+ " WHERE transportId = ?" + " WHERE transportId = ?"
+ " ORDER BY keySetId"; + " ORDER BY keySetId";
@@ -2514,23 +2340,34 @@ abstract class JdbcDatabase implements Database<Connection> {
for (int i = 0; rs.next(); i++) { for (int i = 0; rs.next(); i++) {
// There should be three times as many incoming keys // There should be three times as many incoming keys
if (inKeys.size() < (i + 1) * 3) throw new DbStateException(); if (inKeys.size() < (i + 1) * 3) throw new DbStateException();
TransportKeySetId keySetId = KeySetId keySetId = new KeySetId(rs.getInt(1));
new TransportKeySetId(rs.getInt(1)); long timePeriod = rs.getLong(2);
ContactId contactId = new ContactId(rs.getInt(2)); int cId = rs.getInt(3);
long timePeriod = rs.getLong(3); ContactId contactId = rs.wasNull() ? null : new ContactId(cId);
SecretKey tagKey = new SecretKey(rs.getBytes(4)); byte[] pId = rs.getBytes(4);
SecretKey headerKey = new SecretKey(rs.getBytes(5)); PendingContactId pendingContactId = pId == null ?
long streamCounter = rs.getLong(6); null : new PendingContactId(pId);
boolean active = rs.getBoolean(7); SecretKey tagKey = new SecretKey(rs.getBytes(5));
SecretKey headerKey = new SecretKey(rs.getBytes(6));
long streamCounter = rs.getLong(7);
boolean active = rs.getBoolean(8);
byte[] rootKey = rs.getBytes(9);
boolean alice = rs.getBoolean(10);
OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey, OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey,
timePeriod, streamCounter, active); timePeriod, streamCounter, active);
IncomingKeys inPrev = inKeys.get(i * 3); IncomingKeys inPrev = inKeys.get(i * 3);
IncomingKeys inCurr = inKeys.get(i * 3 + 1); IncomingKeys inCurr = inKeys.get(i * 3 + 1);
IncomingKeys inNext = inKeys.get(i * 3 + 2); IncomingKeys inNext = inKeys.get(i * 3 + 2);
TransportKeys transportKeys = new TransportKeys(t, inPrev, TransportKeys transportKeys;
inCurr, inNext, outCurr); if (rootKey == null) {
transportKeys = new TransportKeys(t, inPrev, inCurr,
inNext, outCurr);
} else {
transportKeys = new TransportKeys(t, inPrev, inCurr,
inNext, outCurr, new SecretKey(rootKey), alice);
}
keys.add(new TransportKeySet(keySetId, contactId, keys.add(new TransportKeySet(keySetId, contactId,
transportKeys)); pendingContactId, transportKeys));
} }
rs.close(); rs.close();
ps.close(); ps.close();
@@ -2544,26 +2381,7 @@ abstract class JdbcDatabase implements Database<Connection> {
@Override @Override
public void incrementStreamCounter(Connection txn, TransportId t, public void incrementStreamCounter(Connection txn, TransportId t,
HandshakeKeySetId k) throws DbException { KeySetId k) throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE outgoingHandshakeKeys SET stream = stream + 1"
+ " WHERE transportId = ? AND keySetId = ?";
ps = txn.prepareStatement(sql);
ps.setString(1, t.getString());
ps.setInt(2, k.getInt());
int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public void incrementStreamCounter(Connection txn, TransportId t,
TransportKeySetId k) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "UPDATE outgoingKeys SET stream = stream + 1" String sql = "UPDATE outgoingKeys SET stream = stream + 1"
@@ -2941,27 +2759,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public void removeHandshakeKeys(Connection txn, TransportId t,
HandshakeKeySetId k) throws DbException {
PreparedStatement ps = null;
try {
// Delete any existing outgoing keys - this will also remove any
// incoming keys with the same key set ID
String sql = "DELETE FROM outgoingHandshakeKeys"
+ " WHERE transportId = ? AND keySetId = ?";
ps = txn.prepareStatement(sql);
ps.setString(1, t.getString());
ps.setInt(2, k.getInt());
int affected = ps.executeUpdate();
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public void removeIdentity(Connection txn, AuthorId a) throws DbException { public void removeIdentity(Connection txn, AuthorId a) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -3074,8 +2871,8 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public void removeTransportKeys(Connection txn, TransportId t, public void removeTransportKeys(Connection txn, TransportId t, KeySetId k)
TransportKeySetId k) throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
// Delete any existing outgoing keys - this will also remove any // Delete any existing outgoing keys - this will also remove any
@@ -3182,15 +2979,15 @@ abstract class JdbcDatabase implements Database<Connection> {
@Override @Override
public void setHandshakeKeyPair(Connection txn, AuthorId local, public void setHandshakeKeyPair(Connection txn, AuthorId local,
byte[] publicKey, byte[] privateKey) throws DbException { PublicKey publicKey, PrivateKey privateKey) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "UPDATE localAuthors" String sql = "UPDATE localAuthors"
+ " SET handshakePublicKey = ?, handshakePrivateKey = ?" + " SET handshakePublicKey = ?, handshakePrivateKey = ?"
+ " WHERE authorId = ?"; + " WHERE authorId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, publicKey); ps.setBytes(1, publicKey.getEncoded());
ps.setBytes(2, privateKey); ps.setBytes(2, privateKey.getEncoded());
ps.setBytes(3, local.getBytes()); ps.setBytes(3, local.getBytes());
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException(); if (affected < 0 || affected > 1) throw new DbStateException();
@@ -3291,7 +3088,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setInt(1, state.getValue()); ps.setInt(1, state.getValue());
ps.setBytes(2, p.getBytes()); ps.setBytes(2, p.getBytes());
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException(); if (affected < 0 || affected > 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps, LOG, WARNING); tryToClose(ps, LOG, WARNING);
@@ -3300,7 +3097,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public void setReorderingWindow(Connection txn, TransportKeySetId k, public void setReorderingWindow(Connection txn, KeySetId k,
TransportId t, long timePeriod, long base, byte[] bitmap) TransportId t, long timePeriod, long base, byte[] bitmap)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -3323,33 +3120,9 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public void setReorderingWindow(Connection txn, HandshakeKeySetId k,
TransportId t, long timePeriod, long base, byte[] bitmap)
throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE incomingHandshakeKeys SET base = ?, bitmap = ?"
+ " WHERE transportId = ? AND keySetId = ?"
+ " AND timePeriod = ?";
ps = txn.prepareStatement(sql);
ps.setLong(1, base);
ps.setBytes(2, bitmap);
ps.setString(3, t.getString());
ps.setInt(4, k.getInt());
ps.setLong(5, timePeriod);
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public void setTransportKeysActive(Connection txn, TransportId t, public void setTransportKeysActive(Connection txn, TransportId t,
TransportKeySetId k) throws DbException { KeySetId k) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "UPDATE outgoingKeys SET active = true" String sql = "UPDATE outgoingKeys SET active = true"
@@ -3469,71 +3242,4 @@ abstract class JdbcDatabase implements Database<Connection> {
throw new DbException(e); throw new DbException(e);
} }
} }
@Override
public void updateHandshakeKeys(Connection txn, HandshakeKeySet ks)
throws DbException {
PreparedStatement ps = null;
try {
// Update the outgoing keys
String sql = "UPDATE outgoingHandshakeKeys SET timePeriod = ?,"
+ " tagKey = ?, headerKey = ?, stream = ?"
+ " WHERE transportId = ? AND keySetId = ?";
ps = txn.prepareStatement(sql);
HandshakeKeys k = ks.getKeys();
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
ps.setLong(1, outCurr.getTimePeriod());
ps.setBytes(2, outCurr.getTagKey().getBytes());
ps.setBytes(3, outCurr.getHeaderKey().getBytes());
ps.setLong(4, outCurr.getStreamCounter());
ps.setString(5, k.getTransportId().getString());
ps.setInt(6, ks.getKeySetId().getInt());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
// Update the incoming keys
sql = "UPDATE incomingHandshakeKeys SET timePeriod = ?,"
+ " tagKey = ?, headerKey = ?, base = ?, bitmap = ?"
+ " WHERE transportId = ? AND keySetId = ?"
+ " AND periodOffset = ?";
ps = txn.prepareStatement(sql);
ps.setString(6, k.getTransportId().getString());
ps.setInt(7, ks.getKeySetId().getInt());
// Previous time period
IncomingKeys inPrev = k.getPreviousIncomingKeys();
ps.setLong(1, inPrev.getTimePeriod());
ps.setBytes(2, inPrev.getTagKey().getBytes());
ps.setBytes(3, inPrev.getHeaderKey().getBytes());
ps.setLong(4, inPrev.getWindowBase());
ps.setBytes(5, inPrev.getWindowBitmap());
ps.setInt(8, OFFSET_PREV);
ps.addBatch();
// Current time period
IncomingKeys inCurr = k.getCurrentIncomingKeys();
ps.setLong(1, inCurr.getTimePeriod());
ps.setBytes(2, inCurr.getTagKey().getBytes());
ps.setBytes(3, inCurr.getHeaderKey().getBytes());
ps.setLong(4, inCurr.getWindowBase());
ps.setBytes(5, inCurr.getWindowBitmap());
ps.setInt(8, OFFSET_CURR);
ps.addBatch();
// Next time period
IncomingKeys inNext = k.getNextIncomingKeys();
ps.setLong(1, inNext.getTimePeriod());
ps.setBytes(2, inNext.getTagKey().getBytes());
ps.setBytes(3, inNext.getHeaderKey().getBytes());
ps.setLong(4, inNext.getWindowBase());
ps.setBytes(5, inNext.getWindowBitmap());
ps.setInt(8, OFFSET_NEXT);
ps.addBatch();
int[] batchAffected = ps.executeBatch();
if (batchAffected.length != 3) throw new DbStateException();
for (int rows : batchAffected)
if (rows < 0 || rows > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
} }

View File

@@ -0,0 +1,58 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DbException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
class Migration43_44 implements Migration<Connection> {
private static final Logger LOG = getLogger(Migration43_44.class.getName());
private final DatabaseTypes dbTypes;
Migration43_44(DatabaseTypes dbTypes) {
this.dbTypes = dbTypes;
}
@Override
public int getStartVersion() {
return 43;
}
@Override
public int getEndVersion() {
return 44;
}
@Override
public void migrate(Connection txn) throws DbException {
Statement s = null;
try {
s = txn.createStatement();
s.execute("DROP TABLE outgoingHandshakeKeys");
s.execute("DROP TABLE incomingHandshakeKeys");
s.execute("ALTER TABLE outgoingKeys"
+ " ALTER COLUMN contactId DROP NOT NULL");
s.execute(dbTypes.replaceTypes("ALTER TABLE outgoingKeys"
+ " ADD COLUMN pendingContactId _HASH"));
s.execute("ALTER TABLE outgoingKeys"
+ " ADD FOREIGN KEY (pendingContactId)"
+ " REFERENCES pendingContacts (pendingContactId)"
+ " ON DELETE CASCADE");
s.execute(dbTypes.replaceTypes("ALTER TABLE outgoingKeys"
+ " ADD COLUMN rootKey _SECRET"));
s.execute("ALTER TABLE outgoingKeys"
+ " ADD COLUMN alice BOOLEAN");
} catch (SQLException e) {
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
}

View File

@@ -2,13 +2,13 @@ package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory; import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.bramble.util.StringUtils;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
@@ -16,6 +16,8 @@ import javax.inject.Inject;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION; import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorId.LABEL; import static org.briarproject.bramble.api.identity.AuthorId.LABEL;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES; import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
import static org.briarproject.bramble.util.ByteUtils.writeUint32;
import static org.briarproject.bramble.util.StringUtils.toUtf8;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -29,13 +31,13 @@ class AuthorFactoryImpl implements AuthorFactory {
} }
@Override @Override
public Author createAuthor(String name, byte[] publicKey) { public Author createAuthor(String name, PublicKey publicKey) {
return createAuthor(FORMAT_VERSION, name, publicKey); return createAuthor(FORMAT_VERSION, name, publicKey);
} }
@Override @Override
public Author createAuthor(int formatVersion, String name, public Author createAuthor(int formatVersion, String name,
byte[] publicKey) { PublicKey publicKey) {
AuthorId id = getId(formatVersion, name, publicKey); AuthorId id = getId(formatVersion, name, publicKey);
return new Author(id, formatVersion, name, publicKey); return new Author(id, formatVersion, name, publicKey);
} }
@@ -43,16 +45,17 @@ class AuthorFactoryImpl implements AuthorFactory {
@Override @Override
public LocalAuthor createLocalAuthor(String name) { public LocalAuthor createLocalAuthor(String name) {
KeyPair signatureKeyPair = crypto.generateSignatureKeyPair(); KeyPair signatureKeyPair = crypto.generateSignatureKeyPair();
byte[] publicKey = signatureKeyPair.getPublic().getEncoded(); PublicKey publicKey = signatureKeyPair.getPublic();
byte[] privateKey = signatureKeyPair.getPrivate().getEncoded(); PrivateKey privateKey = signatureKeyPair.getPrivate();
AuthorId id = getId(FORMAT_VERSION, name, publicKey); AuthorId id = getId(FORMAT_VERSION, name, publicKey);
return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey); return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey);
} }
private AuthorId getId(int formatVersion, String name, byte[] publicKey) { private AuthorId getId(int formatVersion, String name,
PublicKey publicKey) {
byte[] formatVersionBytes = new byte[INT_32_BYTES]; byte[] formatVersionBytes = new byte[INT_32_BYTES];
ByteUtils.writeUint32(formatVersion, formatVersionBytes, 0); writeUint32(formatVersion, formatVersionBytes, 0);
return new AuthorId(crypto.hash(LABEL, formatVersionBytes, return new AuthorId(crypto.hash(LABEL, formatVersionBytes,
StringUtils.toUtf8(name), publicKey)); toUtf8(name), publicKey.getEncoded()));
} }
} }

View File

@@ -2,6 +2,8 @@ package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
@@ -73,8 +75,8 @@ class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook {
long start = now(); long start = now();
LocalAuthor localAuthor = authorFactory.createLocalAuthor(name); LocalAuthor localAuthor = authorFactory.createLocalAuthor(name);
KeyPair handshakeKeyPair = crypto.generateAgreementKeyPair(); KeyPair handshakeKeyPair = crypto.generateAgreementKeyPair();
byte[] handshakePub = handshakeKeyPair.getPublic().getEncoded(); PublicKey handshakePub = handshakeKeyPair.getPublic();
byte[] handshakePriv = handshakeKeyPair.getPrivate().getEncoded(); PrivateKey handshakePriv = handshakeKeyPair.getPrivate();
logDuration(LOG, "Creating identity", start); logDuration(LOG, "Creating identity", start);
return new Identity(localAuthor, handshakePub, handshakePriv, return new Identity(localAuthor, handshakePub, handshakePriv,
clock.currentTimeMillis()); clock.currentTimeMillis());
@@ -98,9 +100,9 @@ class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook {
} else if (shouldStoreKeys) { } else if (shouldStoreKeys) {
// Handshake keys were generated when loading the identity - // Handshake keys were generated when loading the identity -
// store them // store them
byte[] handshakePub = PublicKey handshakePub =
requireNonNull(cached.getHandshakePublicKey()); requireNonNull(cached.getHandshakePublicKey());
byte[] handshakePriv = PrivateKey handshakePriv =
requireNonNull(cached.getHandshakePrivateKey()); requireNonNull(cached.getHandshakePrivateKey());
db.setHandshakeKeyPair(txn, cached.getId(), handshakePub, db.setHandshakeKeyPair(txn, cached.getId(), handshakePub,
handshakePriv); handshakePriv);
@@ -122,12 +124,12 @@ class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook {
} }
@Override @Override
public byte[][] getHandshakeKeys(Transaction txn) throws DbException { public KeyPair getHandshakeKeys(Transaction txn) throws DbException {
Identity cached = getCachedIdentity(txn); Identity cached = getCachedIdentity(txn);
return new byte[][] { PublicKey handshakePub = requireNonNull(cached.getHandshakePublicKey());
cached.getHandshakePublicKey(), PrivateKey handshakePriv =
cached.getHandshakePrivateKey() requireNonNull(cached.getHandshakePrivateKey());
}; return new KeyPair(handshakePub, handshakePriv);
} }
/** /**
@@ -159,8 +161,8 @@ class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook {
LOG.info("Identity loaded"); LOG.info("Identity loaded");
if (i.hasHandshakeKeyPair()) return i; if (i.hasHandshakeKeyPair()) return i;
KeyPair handshakeKeyPair = crypto.generateAgreementKeyPair(); KeyPair handshakeKeyPair = crypto.generateAgreementKeyPair();
byte[] handshakePub = handshakeKeyPair.getPublic().getEncoded(); PublicKey handshakePub = handshakeKeyPair.getPublic();
byte[] handshakePriv = handshakeKeyPair.getPrivate().getEncoded(); PrivateKey handshakePriv = handshakeKeyPair.getPrivate();
LOG.info("Handshake key pair generated"); LOG.info("Handshake key pair generated");
shouldStoreKeys = true; shouldStoreKeys = true;
return new Identity(i.getLocalAuthor(), handshakePub, handshakePriv, return new Identity(i.getLocalAuthor(), handshakePub, handshakePriv,

View File

@@ -96,6 +96,7 @@ class ConnectionManagerImpl implements ConnectionManager {
TransportConnectionReader r) throws IOException { TransportConnectionReader r) throws IOException {
InputStream streamReader = streamReaderFactory.createStreamReader( InputStream streamReader = streamReaderFactory.createStreamReader(
r.getInputStream(), ctx); r.getInputStream(), ctx);
// TODO: Pending contacts, handshake mode
return syncSessionFactory.createIncomingSession(ctx.getContactId(), return syncSessionFactory.createIncomingSession(ctx.getContactId(),
streamReader); streamReader);
} }
@@ -104,6 +105,7 @@ class ConnectionManagerImpl implements ConnectionManager {
TransportConnectionWriter w) throws IOException { TransportConnectionWriter w) throws IOException {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter( StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx); w.getOutputStream(), ctx);
// TODO: Pending contacts, handshake mode
return syncSessionFactory.createSimplexOutgoingSession( return syncSessionFactory.createSimplexOutgoingSession(
ctx.getContactId(), w.getMaxLatency(), streamWriter); ctx.getContactId(), w.getMaxLatency(), streamWriter);
} }
@@ -112,6 +114,7 @@ class ConnectionManagerImpl implements ConnectionManager {
TransportConnectionWriter w) throws IOException { TransportConnectionWriter w) throws IOException {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter( StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx); w.getOutputStream(), ctx);
// TODO: Pending contacts, handshake mode
return syncSessionFactory.createDuplexOutgoingSession( return syncSessionFactory.createDuplexOutgoingSession(
ctx.getContactId(), w.getMaxLatency(), w.getMaxIdleTime(), ctx.getContactId(), w.getMaxLatency(), w.getMaxIdleTime(),
streamWriter); streamWriter);
@@ -145,6 +148,7 @@ class ConnectionManagerImpl implements ConnectionManager {
disposeReader(false, false); disposeReader(false, false);
return; return;
} }
// TODO: Pending contacts
ContactId contactId = ctx.getContactId(); ContactId contactId = ctx.getContactId();
connectionRegistry.registerConnection(contactId, transportId, true); connectionRegistry.registerConnection(contactId, transportId, true);
try { try {
@@ -388,7 +392,7 @@ class ConnectionManagerImpl implements ConnectionManager {
return; return;
} }
// Check that the stream comes from the expected contact // Check that the stream comes from the expected contact
if (!ctx.getContactId().equals(contactId)) { if (!contactId.equals(ctx.getContactId())) {
LOG.warning("Wrong contact ID for returning stream"); LOG.warning("Wrong contact ID for returning stream");
disposeReader(true, true); disposeReader(true, true);
return; return;

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.transport; package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -17,8 +18,8 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.StreamContext; import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.TransportKeySetId;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -28,6 +29,7 @@ import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
@@ -88,29 +90,53 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
} }
@Override @Override
public Map<TransportId, TransportKeySetId> addContact(Transaction txn, public Map<TransportId, KeySetId> addContactWithRotationKeys(
ContactId c, SecretKey rootKey, long timestamp, boolean alice, Transaction txn, ContactId c, SecretKey rootKey, long timestamp,
boolean active) throws DbException { boolean alice, boolean active) throws DbException {
Map<TransportId, TransportKeySetId> ids = new HashMap<>(); Map<TransportId, KeySetId> ids = new HashMap<>();
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) { for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
TransportId t = e.getKey(); TransportId t = e.getKey();
TransportKeyManager m = e.getValue(); TransportKeyManager m = e.getValue();
ids.put(t, m.addContact(txn, c, rootKey, timestamp, alice, active)); ids.put(t, m.addContactWithRotationKeys(txn, c, rootKey, timestamp,
alice, active));
} }
return ids; return ids;
} }
@Override @Override
public void activateKeys(Transaction txn, Map<TransportId, public Map<TransportId, KeySetId> addContactWithHandshakeKeys(
TransportKeySetId> keys) throws DbException { Transaction txn, ContactId c, SecretKey rootKey, boolean alice)
for (Entry<TransportId, TransportKeySetId> e : keys.entrySet()) { throws DbException {
Map<TransportId, KeySetId> ids = new HashMap<>();
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
TransportId t = e.getKey(); TransportId t = e.getKey();
TransportKeyManager m = managers.get(t); TransportKeyManager m = e.getValue();
if (m == null) { ids.put(t, m.addContactWithHandshakeKeys(txn, c, rootKey, alice));
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t); }
} else { return ids;
}
@Override
public Map<TransportId, KeySetId> addPendingContact(Transaction txn,
PendingContactId p, SecretKey rootKey, boolean alice)
throws DbException {
Map<TransportId, KeySetId> ids = new HashMap<>();
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
TransportId t = e.getKey();
TransportKeyManager m = e.getValue();
ids.put(t, m.addPendingContact(txn, p, rootKey, alice));
}
return ids;
}
@Override
public void activateKeys(Transaction txn, Map<TransportId, KeySetId> keys)
throws DbException {
for (Entry<TransportId, KeySetId> e : keys.entrySet()) {
withManager(e.getKey(), m -> {
m.activateKeys(txn, e.getValue()); m.activateKeys(txn, e.getValue());
} return null;
});
} }
} }
@@ -120,28 +146,34 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
return m != null && m.canSendOutgoingStreams(c); return m != null && m.canSendOutgoingStreams(c);
} }
@Override
public boolean canSendOutgoingStreams(PendingContactId p, TransportId t) {
TransportKeyManager m = managers.get(t);
return m != null && m.canSendOutgoingStreams(p);
}
@Override @Override
public StreamContext getStreamContext(ContactId c, TransportId t) public StreamContext getStreamContext(ContactId c, TransportId t)
throws DbException { throws DbException {
TransportKeyManager m = managers.get(t); return withManager(t, m ->
if (m == null) { db.transactionWithNullableResult(false, txn ->
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t); m.getStreamContext(txn, c)));
return null; }
}
return db.transactionWithNullableResult(false, txn -> @Override
m.getStreamContext(txn, c)); public StreamContext getStreamContext(PendingContactId p, TransportId t)
throws DbException {
return withManager(t, m ->
db.transactionWithNullableResult(false, txn ->
m.getStreamContext(txn, p)));
} }
@Override @Override
public StreamContext getStreamContext(TransportId t, byte[] tag) public StreamContext getStreamContext(TransportId t, byte[] tag)
throws DbException { throws DbException {
TransportKeyManager m = managers.get(t); return withManager(t, m ->
if (m == null) { db.transactionWithNullableResult(false, txn ->
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t); m.getStreamContext(txn, tag)));
return null;
}
return db.transactionWithNullableResult(false, txn ->
m.getStreamContext(txn, tag));
} }
@Override @Override
@@ -156,4 +188,20 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
for (TransportKeyManager m : managers.values()) m.removeContact(c); for (TransportKeyManager m : managers.values()) m.removeContact(c);
}); });
} }
@Nullable
private <T> T withManager(TransportId t, ManagerTask<T> task)
throws DbException {
TransportKeyManager m = managers.get(t);
if (m == null) {
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
return null;
}
return task.run(m);
}
private interface ManagerTask<T> {
@Nullable
T run(TransportKeyManager m) throws DbException;
}
} }

View File

@@ -1,30 +0,0 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.transport.TransportKeySetId;
class MutableKeySet {
private final TransportKeySetId keySetId;
private final ContactId contactId;
private final MutableTransportKeys transportKeys;
MutableKeySet(TransportKeySetId keySetId, ContactId contactId,
MutableTransportKeys transportKeys) {
this.keySetId = keySetId;
this.contactId = contactId;
this.transportKeys = transportKeys;
}
TransportKeySetId getKeySetId() {
return keySetId;
}
ContactId getContactId() {
return contactId;
}
MutableTransportKeys getTransportKeys() {
return transportKeys;
}
}

View File

@@ -0,0 +1,51 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.transport.KeySetId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireExactlyOneNull;
@NotThreadSafe
@NotNullByDefault
class MutableTransportKeySet {
private final KeySetId keySetId;
@Nullable
private final ContactId contactId;
@Nullable
private final PendingContactId pendingContactId;
private final MutableTransportKeys keys;
MutableTransportKeySet(KeySetId keySetId, @Nullable ContactId contactId,
@Nullable PendingContactId pendingContactId,
MutableTransportKeys keys) {
requireExactlyOneNull(contactId, pendingContactId);
this.keySetId = keySetId;
this.contactId = contactId;
this.pendingContactId = pendingContactId;
this.keys = keys;
}
KeySetId getKeySetId() {
return keySetId;
}
@Nullable
ContactId getContactId() {
return contactId;
}
@Nullable
PendingContactId getPendingContactId() {
return pendingContactId;
}
MutableTransportKeys getKeys() {
return keys;
}
}

View File

@@ -1,9 +1,11 @@
package org.briarproject.bramble.transport; package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe @NotThreadSafe
@@ -13,6 +15,9 @@ class MutableTransportKeys {
private final TransportId transportId; private final TransportId transportId;
private final MutableIncomingKeys inPrev, inCurr, inNext; private final MutableIncomingKeys inPrev, inCurr, inNext;
private final MutableOutgoingKeys outCurr; private final MutableOutgoingKeys outCurr;
@Nullable
private final SecretKey rootKey;
private final boolean alice;
MutableTransportKeys(TransportKeys k) { MutableTransportKeys(TransportKeys k) {
transportId = k.getTransportId(); transportId = k.getTransportId();
@@ -20,11 +25,24 @@ class MutableTransportKeys {
inCurr = new MutableIncomingKeys(k.getCurrentIncomingKeys()); inCurr = new MutableIncomingKeys(k.getCurrentIncomingKeys());
inNext = new MutableIncomingKeys(k.getNextIncomingKeys()); inNext = new MutableIncomingKeys(k.getNextIncomingKeys());
outCurr = new MutableOutgoingKeys(k.getCurrentOutgoingKeys()); outCurr = new MutableOutgoingKeys(k.getCurrentOutgoingKeys());
if (k.isHandshakeMode()) {
rootKey = k.getRootKey();
alice = k.isAlice();
} else {
rootKey = null;
alice = false;
}
} }
TransportKeys snapshot() { TransportKeys snapshot() {
return new TransportKeys(transportId, inPrev.snapshot(), if (rootKey == null) {
inCurr.snapshot(), inNext.snapshot(), outCurr.snapshot()); return new TransportKeys(transportId, inPrev.snapshot(),
inCurr.snapshot(), inNext.snapshot(), outCurr.snapshot());
} else {
return new TransportKeys(transportId, inPrev.snapshot(),
inCurr.snapshot(), inNext.snapshot(), outCurr.snapshot(),
rootKey, alice);
}
} }
TransportId getTransportId() { TransportId getTransportId() {
@@ -46,4 +64,18 @@ class MutableTransportKeys {
MutableOutgoingKeys getCurrentOutgoingKeys() { MutableOutgoingKeys getCurrentOutgoingKeys() {
return outCurr; return outCurr;
} }
boolean isHandshakeMode() {
return rootKey != null;
}
SecretKey getRootKey() {
if (rootKey == null) throw new UnsupportedOperationException();
return rootKey;
}
boolean isAlice() {
if (rootKey == null) throw new UnsupportedOperationException();
return alice;
}
} }

View File

@@ -1,12 +1,13 @@
package org.briarproject.bramble.transport; package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.StreamContext; import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.TransportKeySetId;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -15,20 +16,34 @@ interface TransportKeyManager {
void start(Transaction txn) throws DbException; void start(Transaction txn) throws DbException;
TransportKeySetId addContact(Transaction txn, ContactId c, KeySetId addContactWithRotationKeys(Transaction txn, ContactId c,
SecretKey rootKey, long timestamp, boolean alice, boolean active) SecretKey rootKey, long timestamp, boolean alice, boolean active)
throws DbException; throws DbException;
void activateKeys(Transaction txn, TransportKeySetId k) throws DbException; KeySetId addContactWithHandshakeKeys(Transaction txn, ContactId c,
SecretKey rootKey, boolean alice) throws DbException;
KeySetId addPendingContact(Transaction txn, PendingContactId p,
SecretKey rootKey, boolean alice) throws DbException;
void activateKeys(Transaction txn, KeySetId k) throws DbException;
void removeContact(ContactId c); void removeContact(ContactId c);
void removePendingContact(PendingContactId p);
boolean canSendOutgoingStreams(ContactId c); boolean canSendOutgoingStreams(ContactId c);
boolean canSendOutgoingStreams(PendingContactId p);
@Nullable @Nullable
StreamContext getStreamContext(Transaction txn, ContactId c) StreamContext getStreamContext(Transaction txn, ContactId c)
throws DbException; throws DbException;
@Nullable
StreamContext getStreamContext(Transaction txn, PendingContactId p)
throws DbException;
@Nullable @Nullable
StreamContext getStreamContext(Transaction txn, byte[] tag) StreamContext getStreamContext(Transaction txn, byte[] tag)
throws DbException; throws DbException;

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.Bytes; import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto; import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -11,9 +12,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.Scheduler; import org.briarproject.bramble.api.system.Scheduler;
import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.StreamContext; import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.TransportKeySet; import org.briarproject.bramble.api.transport.TransportKeySet;
import org.briarproject.bramble.api.transport.TransportKeySetId;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.transport.ReorderingWindow.Change; import org.briarproject.bramble.transport.ReorderingWindow.Change;
@@ -28,10 +29,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireExactlyOneNull;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE; import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH; import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
@@ -43,7 +48,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
class TransportKeyManagerImpl implements TransportKeyManager { class TransportKeyManagerImpl implements TransportKeyManager {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(TransportKeyManagerImpl.class.getName()); getLogger(TransportKeyManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final TransportCrypto transportCrypto; private final TransportCrypto transportCrypto;
@@ -55,10 +60,16 @@ class TransportKeyManagerImpl implements TransportKeyManager {
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
private final ReentrantLock lock = new ReentrantLock(); private final ReentrantLock lock = new ReentrantLock();
// The following are locking: lock @GuardedBy("lock")
private final Map<TransportKeySetId, MutableKeySet> keys = new HashMap<>(); private final Map<KeySetId, MutableTransportKeySet> keys = new HashMap<>();
@GuardedBy("lock")
private final Map<Bytes, TagContext> inContexts = new HashMap<>(); private final Map<Bytes, TagContext> inContexts = new HashMap<>();
private final Map<ContactId, MutableKeySet> outContexts = new HashMap<>(); @GuardedBy("lock")
private final Map<ContactId, MutableTransportKeySet>
contactOutContexts = new HashMap<>();
@GuardedBy("lock")
private final Map<PendingContactId, MutableTransportKeySet>
pendingContactOutContexts = new HashMap<>();
TransportKeyManagerImpl(DatabaseComponent db, TransportKeyManagerImpl(DatabaseComponent db,
TransportCrypto transportCrypto, Executor dbExecutor, TransportCrypto transportCrypto, Executor dbExecutor,
@@ -82,62 +93,71 @@ class TransportKeyManagerImpl implements TransportKeyManager {
// Load the transport keys from the DB // Load the transport keys from the DB
Collection<TransportKeySet> loaded = Collection<TransportKeySet> loaded =
db.getTransportKeys(txn, transportId); db.getTransportKeys(txn, transportId);
// Rotate the keys to the current time period // Update the keys to the current time period
RotationResult rotationResult = rotateKeys(loaded, now); UpdateResult updateResult = updateKeys(loaded, now);
// Initialise mutable state for all contacts // Initialise mutable state for all contacts
addKeys(rotationResult.current); addKeys(updateResult.current);
// Write any rotated keys back to the DB // Write any updated keys back to the DB
if (!rotationResult.rotated.isEmpty()) if (!updateResult.updated.isEmpty())
db.updateTransportKeys(txn, rotationResult.rotated); db.updateTransportKeys(txn, updateResult.updated);
} finally { } finally {
lock.unlock(); lock.unlock();
} }
// Schedule the next key rotation // Schedule the next key update
scheduleKeyRotation(now); scheduleKeyUpdate(now);
} }
private RotationResult rotateKeys(Collection<TransportKeySet> keys, private UpdateResult updateKeys(Collection<TransportKeySet> keys,
long now) { long now) {
RotationResult rotationResult = new RotationResult(); UpdateResult updateResult = new UpdateResult();
long timePeriod = now / timePeriodLength; long timePeriod = now / timePeriodLength;
for (TransportKeySet ks : keys) { for (TransportKeySet ks : keys) {
TransportKeys k = ks.getKeys(); TransportKeys k = ks.getKeys();
TransportKeys k1 = transportCrypto.rotateTransportKeys(k, TransportKeys k1 = transportCrypto.updateTransportKeys(k,
timePeriod); timePeriod);
TransportKeySet ks1 = new TransportKeySet(ks.getKeySetId(), TransportKeySet ks1 = new TransportKeySet(ks.getKeySetId(),
ks.getContactId(), k1); ks.getContactId(), null, k1);
if (k1.getTimePeriod() > k.getTimePeriod()) if (k1.getTimePeriod() > k.getTimePeriod())
rotationResult.rotated.add(ks1); updateResult.updated.add(ks1);
rotationResult.current.add(ks1); updateResult.current.add(ks1);
} }
return rotationResult; return updateResult;
} }
// Locking: lock @GuardedBy("lock")
private void addKeys(Collection<TransportKeySet> keys) { private void addKeys(Collection<TransportKeySet> keys) {
for (TransportKeySet ks : keys) { for (TransportKeySet ks : keys) {
addKeys(ks.getKeySetId(), ks.getContactId(), addKeys(ks.getKeySetId(), ks.getContactId(),
ks.getPendingContactId(),
new MutableTransportKeys(ks.getKeys())); new MutableTransportKeys(ks.getKeys()));
} }
} }
// Locking: lock @GuardedBy("lock")
private void addKeys(TransportKeySetId keySetId, ContactId contactId, private void addKeys(KeySetId keySetId, @Nullable ContactId contactId,
MutableTransportKeys m) { @Nullable PendingContactId pendingContactId,
MutableKeySet ks = new MutableKeySet(keySetId, contactId, m); MutableTransportKeys keys) {
keys.put(keySetId, ks); requireExactlyOneNull(contactId, pendingContactId);
encodeTags(keySetId, contactId, m.getPreviousIncomingKeys()); MutableTransportKeySet ks = new MutableTransportKeySet(keySetId,
encodeTags(keySetId, contactId, m.getCurrentIncomingKeys()); contactId, pendingContactId, keys);
encodeTags(keySetId, contactId, m.getNextIncomingKeys()); this.keys.put(keySetId, ks);
boolean handshakeMode = keys.isHandshakeMode();
encodeTags(keySetId, contactId, pendingContactId,
keys.getPreviousIncomingKeys(), handshakeMode);
encodeTags(keySetId, contactId, pendingContactId,
keys.getCurrentIncomingKeys(), handshakeMode);
encodeTags(keySetId, contactId, pendingContactId,
keys.getNextIncomingKeys(), handshakeMode);
considerReplacingOutgoingKeys(ks); considerReplacingOutgoingKeys(ks);
} }
// Locking: lock @GuardedBy("lock")
private void encodeTags(TransportKeySetId keySetId, ContactId contactId, private void encodeTags(KeySetId keySetId, @Nullable ContactId contactId,
MutableIncomingKeys inKeys) { @Nullable PendingContactId pendingContactId,
MutableIncomingKeys inKeys, boolean handshakeMode) {
for (long streamNumber : inKeys.getWindow().getUnseen()) { for (long streamNumber : inKeys.getWindow().getUnseen()) {
TagContext tagCtx = TagContext tagCtx = new TagContext(keySetId, contactId,
new TagContext(keySetId, contactId, inKeys, streamNumber); pendingContactId, inKeys, streamNumber, handshakeMode);
byte[] tag = new byte[TAG_LENGTH]; byte[] tag = new byte[TAG_LENGTH];
transportCrypto.encodeTag(tag, inKeys.getTagKey(), PROTOCOL_VERSION, transportCrypto.encodeTag(tag, inKeys.getTagKey(), PROTOCOL_VERSION,
streamNumber); streamNumber);
@@ -145,27 +165,41 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
} }
// Locking: lock @GuardedBy("lock")
private void considerReplacingOutgoingKeys(MutableKeySet ks) { private void considerReplacingOutgoingKeys(MutableTransportKeySet ks) {
// Use the active outgoing keys with the highest key set ID // Use the active outgoing keys with the highest key set ID, preferring
if (ks.getTransportKeys().getCurrentOutgoingKeys().isActive()) { // rotation keys to handshake keys
MutableKeySet old = outContexts.get(ks.getContactId()); if (ks.getKeys().getCurrentOutgoingKeys().isActive()) {
if (old == null || MutableTransportKeySet old = getOutgoingKeySet(ks.getContactId(),
ks.getPendingContactId());
if (old == null || (old.getKeys().isHandshakeMode() &&
!ks.getKeys().isHandshakeMode()) ||
old.getKeySetId().getInt() < ks.getKeySetId().getInt()) { old.getKeySetId().getInt() < ks.getKeySetId().getInt()) {
outContexts.put(ks.getContactId(), ks); if (ks.getContactId() == null)
pendingContactOutContexts.put(ks.getPendingContactId(), ks);
else contactOutContexts.put(ks.getContactId(), ks);
} }
} }
} }
private void scheduleKeyRotation(long now) { @GuardedBy("lock")
long delay = timePeriodLength - now % timePeriodLength; @Nullable
scheduler.schedule((Runnable) this::rotateKeys, delay, MILLISECONDS); private MutableTransportKeySet getOutgoingKeySet(@Nullable ContactId c,
@Nullable PendingContactId p) {
requireExactlyOneNull(c, p);
if (c == null) return pendingContactOutContexts.get(p);
else return contactOutContexts.get(c);
} }
private void rotateKeys() { private void scheduleKeyUpdate(long now) {
long delay = timePeriodLength - now % timePeriodLength;
scheduler.schedule((Runnable) this::updateKeys, delay, MILLISECONDS);
}
private void updateKeys() {
dbExecutor.execute(() -> { dbExecutor.execute(() -> {
try { try {
db.transaction(false, this::rotateKeys); db.transaction(false, this::updateKeys);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
@@ -173,7 +207,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
@Override @Override
public TransportKeySetId addContact(Transaction txn, ContactId c, public KeySetId addContactWithRotationKeys(Transaction txn, ContactId c,
SecretKey rootKey, long timestamp, boolean alice, boolean active) SecretKey rootKey, long timestamp, boolean alice, boolean active)
throws DbException { throws DbException {
lock.lock(); lock.lock();
@@ -181,15 +215,15 @@ class TransportKeyManagerImpl implements TransportKeyManager {
// Work out what time period the timestamp belongs to // Work out what time period the timestamp belongs to
long timePeriod = timestamp / timePeriodLength; long timePeriod = timestamp / timePeriodLength;
// Derive the transport keys // Derive the transport keys
TransportKeys k = transportCrypto.deriveTransportKeys(transportId, TransportKeys k = transportCrypto.deriveRotationKeys(transportId,
rootKey, timePeriod, alice, active); rootKey, timePeriod, alice, active);
// Rotate the keys to the current time period if necessary // Update the keys to the current time period if necessary
timePeriod = clock.currentTimeMillis() / timePeriodLength; timePeriod = clock.currentTimeMillis() / timePeriodLength;
k = transportCrypto.rotateTransportKeys(k, timePeriod); k = transportCrypto.updateTransportKeys(k, timePeriod);
// Write the keys back to the DB // Write the keys back to the DB
TransportKeySetId keySetId = db.addTransportKeys(txn, c, k); KeySetId keySetId = db.addTransportKeys(txn, c, k);
// Initialise mutable state for the contact // Initialise mutable state for the contact
addKeys(keySetId, c, new MutableTransportKeys(k)); addKeys(keySetId, c, null, new MutableTransportKeys(k));
return keySetId; return keySetId;
} finally { } finally {
lock.unlock(); lock.unlock();
@@ -197,13 +231,52 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
@Override @Override
public void activateKeys(Transaction txn, TransportKeySetId k) public KeySetId addContactWithHandshakeKeys(Transaction txn, ContactId c,
throws DbException { SecretKey rootKey, boolean alice) throws DbException {
lock.lock(); lock.lock();
try { try {
MutableKeySet ks = keys.get(k); // Work out what time period we're in
long timePeriod = clock.currentTimeMillis() / timePeriodLength;
// Derive the transport keys
TransportKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, timePeriod, alice);
// Write the keys back to the DB
KeySetId keySetId = db.addTransportKeys(txn, c, k);
// Initialise mutable state for the contact
addKeys(keySetId, c, null, new MutableTransportKeys(k));
return keySetId;
} finally {
lock.unlock();
}
}
@Override
public KeySetId addPendingContact(Transaction txn, PendingContactId p,
SecretKey rootKey, boolean alice) throws DbException {
lock.lock();
try {
// Work out what time period we're in
long timePeriod = clock.currentTimeMillis() / timePeriodLength;
// Derive the transport keys
TransportKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, timePeriod, alice);
// Write the keys back to the DB
KeySetId keySetId = db.addTransportKeys(txn, p, k);
// Initialise mutable state for the pending contact
addKeys(keySetId, null, p, new MutableTransportKeys(k));
return keySetId;
} finally {
lock.unlock();
}
}
@Override
public void activateKeys(Transaction txn, KeySetId k) throws DbException {
lock.lock();
try {
MutableTransportKeySet ks = keys.get(k);
if (ks == null) throw new IllegalArgumentException(); if (ks == null) throw new IllegalArgumentException();
MutableTransportKeys m = ks.getTransportKeys(); MutableTransportKeys m = ks.getKeys();
m.getCurrentOutgoingKeys().activate(); m.getCurrentOutgoingKeys().activate();
considerReplacingOutgoingKeys(ks); considerReplacingOutgoingKeys(ks);
db.setTransportKeysActive(txn, m.getTransportId(), k); db.setTransportKeysActive(txn, m.getTransportId(), k);
@@ -218,13 +291,29 @@ class TransportKeyManagerImpl implements TransportKeyManager {
try { try {
// Remove mutable state for the contact // Remove mutable state for the contact
Iterator<TagContext> it = inContexts.values().iterator(); Iterator<TagContext> it = inContexts.values().iterator();
while (it.hasNext()) if (it.next().contactId.equals(c)) it.remove(); while (it.hasNext())
outContexts.remove(c); if (c.equals(it.next().contactId)) it.remove();
Iterator<MutableKeySet> it1 = keys.values().iterator(); contactOutContexts.remove(c);
while (it1.hasNext()) { Iterator<MutableTransportKeySet> it1 = keys.values().iterator();
ContactId c1 = it1.next().getContactId(); while (it1.hasNext())
if (c1 != null && c1.equals(c)) it1.remove(); if (c.equals(it1.next().getContactId())) it1.remove();
} } finally {
lock.unlock();
}
}
@Override
public void removePendingContact(PendingContactId p) {
lock.lock();
try {
// Remove mutable state for the pending contact
Iterator<TagContext> it = inContexts.values().iterator();
while (it.hasNext())
if (p.equals(it.next().pendingContactId)) it.remove();
pendingContactOutContexts.remove(p);
Iterator<MutableTransportKeySet> it1 = keys.values().iterator();
while (it1.hasNext())
if (p.equals(it1.next().getPendingContactId())) it1.remove();
} finally { } finally {
lock.unlock(); lock.unlock();
} }
@@ -232,12 +321,21 @@ class TransportKeyManagerImpl implements TransportKeyManager {
@Override @Override
public boolean canSendOutgoingStreams(ContactId c) { public boolean canSendOutgoingStreams(ContactId c) {
return canSendOutgoingStreams(c, null);
}
@Override
public boolean canSendOutgoingStreams(PendingContactId p) {
return canSendOutgoingStreams(null, p);
}
private boolean canSendOutgoingStreams(@Nullable ContactId c,
@Nullable PendingContactId p) {
lock.lock(); lock.lock();
try { try {
MutableKeySet ks = outContexts.get(c); MutableTransportKeySet ks = getOutgoingKeySet(c, p);
if (ks == null) return false; if (ks == null) return false;
MutableOutgoingKeys outKeys = MutableOutgoingKeys outKeys = ks.getKeys().getCurrentOutgoingKeys();
ks.getTransportKeys().getCurrentOutgoingKeys();
if (!outKeys.isActive()) throw new AssertionError(); if (!outKeys.isActive()) throw new AssertionError();
return outKeys.getStreamCounter() <= MAX_32_BIT_UNSIGNED; return outKeys.getStreamCounter() <= MAX_32_BIT_UNSIGNED;
} finally { } finally {
@@ -248,19 +346,32 @@ class TransportKeyManagerImpl implements TransportKeyManager {
@Override @Override
public StreamContext getStreamContext(Transaction txn, ContactId c) public StreamContext getStreamContext(Transaction txn, ContactId c)
throws DbException { throws DbException {
return getStreamContext(txn, c, null);
}
@Override
public StreamContext getStreamContext(Transaction txn, PendingContactId p)
throws DbException {
return getStreamContext(txn, null, p);
}
@Nullable
private StreamContext getStreamContext(Transaction txn,
@Nullable ContactId c, @Nullable PendingContactId p)
throws DbException {
lock.lock(); lock.lock();
try { try {
// Look up the outgoing keys for the contact // Look up the outgoing keys for the contact
MutableKeySet ks = outContexts.get(c); MutableTransportKeySet ks = getOutgoingKeySet(c, p);
if (ks == null) return null; if (ks == null) return null;
MutableOutgoingKeys outKeys = MutableTransportKeys keys = ks.getKeys();
ks.getTransportKeys().getCurrentOutgoingKeys(); MutableOutgoingKeys outKeys = keys.getCurrentOutgoingKeys();
if (!outKeys.isActive()) throw new AssertionError(); if (!outKeys.isActive()) throw new AssertionError();
if (outKeys.getStreamCounter() > MAX_32_BIT_UNSIGNED) return null; if (outKeys.getStreamCounter() > MAX_32_BIT_UNSIGNED) return null;
// Create a stream context // Create a stream context
StreamContext ctx = new StreamContext(c, transportId, StreamContext ctx = new StreamContext(c, p, transportId,
outKeys.getTagKey(), outKeys.getHeaderKey(), outKeys.getTagKey(), outKeys.getHeaderKey(),
outKeys.getStreamCounter()); outKeys.getStreamCounter(), keys.isHandshakeMode());
// Increment the stream counter and write it back to the DB // Increment the stream counter and write it back to the DB
outKeys.incrementStreamCounter(); outKeys.incrementStreamCounter();
db.incrementStreamCounter(txn, transportId, ks.getKeySetId()); db.incrementStreamCounter(txn, transportId, ks.getKeySetId());
@@ -280,9 +391,10 @@ class TransportKeyManagerImpl implements TransportKeyManager {
if (tagCtx == null) return null; if (tagCtx == null) return null;
MutableIncomingKeys inKeys = tagCtx.inKeys; MutableIncomingKeys inKeys = tagCtx.inKeys;
// Create a stream context // Create a stream context
StreamContext ctx = new StreamContext(tagCtx.contactId, transportId, StreamContext ctx = new StreamContext(tagCtx.contactId,
tagCtx.pendingContactId, transportId,
inKeys.getTagKey(), inKeys.getHeaderKey(), inKeys.getTagKey(), inKeys.getHeaderKey(),
tagCtx.streamNumber); tagCtx.streamNumber, tagCtx.handshakeMode);
// Update the reordering window // Update the reordering window
ReorderingWindow window = inKeys.getWindow(); ReorderingWindow window = inKeys.getWindow();
Change change = window.setSeen(tagCtx.streamNumber); Change change = window.setSeen(tagCtx.streamNumber);
@@ -292,7 +404,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
transportCrypto.encodeTag(addTag, inKeys.getTagKey(), transportCrypto.encodeTag(addTag, inKeys.getTagKey(),
PROTOCOL_VERSION, streamNumber); PROTOCOL_VERSION, streamNumber);
TagContext tagCtx1 = new TagContext(tagCtx.keySetId, TagContext tagCtx1 = new TagContext(tagCtx.keySetId,
tagCtx.contactId, inKeys, streamNumber); tagCtx.contactId, tagCtx.pendingContactId, inKeys,
streamNumber, tagCtx.handshakeMode);
inContexts.put(new Bytes(addTag), tagCtx1); inContexts.put(new Bytes(addTag), tagCtx1);
} }
// Remove tags for any stream numbers removed from the window // Remove tags for any stream numbers removed from the window
@@ -308,9 +421,9 @@ class TransportKeyManagerImpl implements TransportKeyManager {
inKeys.getTimePeriod(), window.getBase(), inKeys.getTimePeriod(), window.getBase(),
window.getBitmap()); window.getBitmap());
// If the outgoing keys are inactive, activate them // If the outgoing keys are inactive, activate them
MutableKeySet ks = keys.get(tagCtx.keySetId); MutableTransportKeySet ks = keys.get(tagCtx.keySetId);
MutableOutgoingKeys outKeys = MutableOutgoingKeys outKeys =
ks.getTransportKeys().getCurrentOutgoingKeys(); ks.getKeys().getCurrentOutgoingKeys();
if (!outKeys.isActive()) { if (!outKeys.isActive()) {
LOG.info("Activating outgoing keys"); LOG.info("Activating outgoing keys");
outKeys.activate(); outKeys.activate();
@@ -323,51 +436,62 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
} }
private void rotateKeys(Transaction txn) throws DbException { private void updateKeys(Transaction txn) throws DbException {
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
lock.lock(); lock.lock();
try { try {
// Rotate the keys to the current time period // Update the keys to the current time period
Collection<TransportKeySet> snapshot = new ArrayList<>(keys.size()); Collection<TransportKeySet> snapshot = new ArrayList<>(keys.size());
for (MutableKeySet ks : keys.values()) { for (MutableTransportKeySet ks : keys.values()) {
snapshot.add(new TransportKeySet(ks.getKeySetId(), snapshot.add(new TransportKeySet(ks.getKeySetId(),
ks.getContactId(), ks.getTransportKeys().snapshot())); ks.getContactId(), ks.getPendingContactId(),
ks.getKeys().snapshot()));
} }
RotationResult rotationResult = rotateKeys(snapshot, now); UpdateResult updateResult = updateKeys(snapshot, now);
// Rebuild the mutable state for all contacts // Rebuild the mutable state for all contacts
inContexts.clear(); inContexts.clear();
outContexts.clear(); contactOutContexts.clear();
pendingContactOutContexts.clear();
keys.clear(); keys.clear();
addKeys(rotationResult.current); addKeys(updateResult.current);
// Write any rotated keys back to the DB // Write any updated keys back to the DB
if (!rotationResult.rotated.isEmpty()) if (!updateResult.updated.isEmpty())
db.updateTransportKeys(txn, rotationResult.rotated); db.updateTransportKeys(txn, updateResult.updated);
} finally { } finally {
lock.unlock(); lock.unlock();
} }
// Schedule the next key rotation // Schedule the next key update
scheduleKeyRotation(now); scheduleKeyUpdate(now);
} }
private static class TagContext { private static class TagContext {
private final TransportKeySetId keySetId; private final KeySetId keySetId;
@Nullable
private final ContactId contactId; private final ContactId contactId;
@Nullable
private final PendingContactId pendingContactId;
private final MutableIncomingKeys inKeys; private final MutableIncomingKeys inKeys;
private final long streamNumber; private final long streamNumber;
private final boolean handshakeMode;
private TagContext(TransportKeySetId keySetId, ContactId contactId, private TagContext(KeySetId keySetId, @Nullable ContactId contactId,
MutableIncomingKeys inKeys, long streamNumber) { @Nullable PendingContactId pendingContactId,
MutableIncomingKeys inKeys, long streamNumber,
boolean handshakeMode) {
requireExactlyOneNull(contactId, pendingContactId);
this.keySetId = keySetId; this.keySetId = keySetId;
this.contactId = contactId; this.contactId = contactId;
this.pendingContactId = pendingContactId;
this.inKeys = inKeys; this.inKeys = inKeys;
this.streamNumber = streamNumber; this.streamNumber = streamNumber;
this.handshakeMode = handshakeMode;
} }
} }
private static class RotationResult { private static class UpdateResult {
private final Collection<TransportKeySet> current = new ArrayList<>(); private final Collection<TransportKeySet> current = new ArrayList<>();
private final Collection<TransportKeySet> rotated = new ArrayList<>(); private final Collection<TransportKeySet> updated = new ArrayList<>();
} }
} }

View File

@@ -3,6 +3,9 @@ package org.briarproject.bramble.client;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry; import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
@@ -43,6 +46,8 @@ import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSignaturePrivateKey;
import static org.briarproject.bramble.test.TestUtils.getSignaturePublicKey;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -66,6 +71,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
context.mock(CryptoComponent.class); context.mock(CryptoComponent.class);
private final AuthorFactory authorFactory = private final AuthorFactory authorFactory =
context.mock(AuthorFactory.class); context.mock(AuthorFactory.class);
private final KeyParser keyParser = context.mock(KeyParser.class);
private final GroupId groupId = new GroupId(getRandomId()); private final GroupId groupId = new GroupId(getRandomId());
private final BdfDictionary dictionary = new BdfDictionary(); private final BdfDictionary dictionary = new BdfDictionary();
@@ -262,24 +268,25 @@ public class ClientHelperImplTest extends BrambleTestCase {
@Test @Test
public void testSign() throws Exception { public void testSign() throws Exception {
byte[] privateKey = getRandomBytes(42); PrivateKey privateKey = getSignaturePrivateKey();
byte[] signed = getRandomBytes(42); byte[] signature = getRandomBytes(42);
byte[] bytes = expectToByteArray(list); byte[] bytes = expectToByteArray(list);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(cryptoComponent).sign(label, bytes, privateKey); oneOf(cryptoComponent).sign(label, bytes, privateKey);
will(returnValue(signed)); will(returnValue(signature));
}}); }});
assertArrayEquals(signed, clientHelper.sign(label, list, privateKey)); assertArrayEquals(signature,
clientHelper.sign(label, list, privateKey));
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@Test @Test
public void testVerifySignature() throws Exception { public void testVerifySignature() throws Exception {
byte[] signature = getRandomBytes(MAX_SIGNATURE_LENGTH); byte[] signature = getRandomBytes(MAX_SIGNATURE_LENGTH);
byte[] publicKey = getRandomBytes(42);
byte[] signed = expectToByteArray(list); byte[] signed = expectToByteArray(list);
PublicKey publicKey = getSignaturePublicKey();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(cryptoComponent).verifySignature(signature, label, signed, oneOf(cryptoComponent).verifySignature(signature, label, signed,
@@ -294,8 +301,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
@Test @Test
public void testVerifyWrongSignature() throws Exception { public void testVerifyWrongSignature() throws Exception {
byte[] signature = getRandomBytes(MAX_SIGNATURE_LENGTH); byte[] signature = getRandomBytes(MAX_SIGNATURE_LENGTH);
byte[] publicKey = getRandomBytes(42);
byte[] signed = expectToByteArray(list); byte[] signed = expectToByteArray(list);
PublicKey publicKey = getSignaturePublicKey();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(cryptoComponent).verifySignature(signature, label, signed, oneOf(cryptoComponent).verifySignature(signature, label, signed,
@@ -315,6 +322,10 @@ public class ClientHelperImplTest extends BrambleTestCase {
@Test @Test
public void testParsesAndEncodesAuthor() throws Exception { public void testParsesAndEncodesAuthor() throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(cryptoComponent).getSignatureKeyParser();
will(returnValue(keyParser));
oneOf(keyParser).parsePublicKey(author.getPublicKey().getEncoded());
will(returnValue(author.getPublicKey()));
oneOf(authorFactory).createAuthor(author.getFormatVersion(), oneOf(authorFactory).createAuthor(author.getFormatVersion(),
author.getName(), author.getPublicKey()); author.getName(), author.getPublicKey());
will(returnValue(author)); will(returnValue(author));
@@ -329,10 +340,14 @@ public class ClientHelperImplTest extends BrambleTestCase {
BdfList authorList = BdfList.of( BdfList authorList = BdfList.of(
author.getFormatVersion(), author.getFormatVersion(),
author.getName(), author.getName(),
author.getPublicKey() author.getPublicKey().getEncoded()
); );
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(cryptoComponent).getSignatureKeyParser();
will(returnValue(keyParser));
oneOf(keyParser).parsePublicKey(author.getPublicKey().getEncoded());
will(returnValue(author.getPublicKey()));
oneOf(authorFactory).createAuthor(author.getFormatVersion(), oneOf(authorFactory).createAuthor(author.getFormatVersion(),
author.getName(), author.getPublicKey()); author.getName(), author.getPublicKey());
will(returnValue(author)); will(returnValue(author));
@@ -355,7 +370,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
BdfList invalidAuthor = BdfList.of( BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(), author.getFormatVersion(),
author.getName(), author.getName(),
author.getPublicKey(), author.getPublicKey().getEncoded(),
"foo" "foo"
); );
clientHelper.parseAndValidateAuthor(invalidAuthor); clientHelper.parseAndValidateAuthor(invalidAuthor);
@@ -366,7 +381,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
BdfList invalidAuthor = BdfList.of( BdfList invalidAuthor = BdfList.of(
null, null,
author.getName(), author.getName(),
author.getPublicKey() author.getPublicKey().getEncoded()
); );
clientHelper.parseAndValidateAuthor(invalidAuthor); clientHelper.parseAndValidateAuthor(invalidAuthor);
} }
@@ -377,7 +392,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
BdfList invalidAuthor = BdfList.of( BdfList invalidAuthor = BdfList.of(
"foo", "foo",
author.getName(), author.getName(),
author.getPublicKey() author.getPublicKey().getEncoded()
); );
clientHelper.parseAndValidateAuthor(invalidAuthor); clientHelper.parseAndValidateAuthor(invalidAuthor);
} }
@@ -387,7 +402,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
BdfList invalidAuthor = BdfList.of( BdfList invalidAuthor = BdfList.of(
author.getFormatVersion() + 1, author.getFormatVersion() + 1,
author.getName(), author.getName(),
author.getPublicKey() author.getPublicKey().getEncoded()
); );
clientHelper.parseAndValidateAuthor(invalidAuthor); clientHelper.parseAndValidateAuthor(invalidAuthor);
} }
@@ -397,7 +412,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
BdfList invalidAuthor = BdfList.of( BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(), author.getFormatVersion(),
"", "",
author.getPublicKey() author.getPublicKey().getEncoded()
); );
clientHelper.parseAndValidateAuthor(invalidAuthor); clientHelper.parseAndValidateAuthor(invalidAuthor);
} }
@@ -407,7 +422,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
BdfList invalidAuthor = BdfList.of( BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(), author.getFormatVersion(),
getRandomString(MAX_AUTHOR_NAME_LENGTH + 1), getRandomString(MAX_AUTHOR_NAME_LENGTH + 1),
author.getPublicKey() author.getPublicKey().getEncoded()
); );
clientHelper.parseAndValidateAuthor(invalidAuthor); clientHelper.parseAndValidateAuthor(invalidAuthor);
} }
@@ -417,7 +432,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
BdfList invalidAuthor = BdfList.of( BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(), author.getFormatVersion(),
null, null,
author.getPublicKey() author.getPublicKey().getEncoded()
); );
clientHelper.parseAndValidateAuthor(invalidAuthor); clientHelper.parseAndValidateAuthor(invalidAuthor);
} }
@@ -472,6 +487,24 @@ public class ClientHelperImplTest extends BrambleTestCase {
clientHelper.parseAndValidateAuthor(invalidAuthor); clientHelper.parseAndValidateAuthor(invalidAuthor);
} }
@Test(expected = FormatException.class)
public void testRejectsAuthorWithInvalidPublicKey() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
author.getName(),
author.getPublicKey().getEncoded()
);
context.checking(new Expectations() {{
oneOf(cryptoComponent).getSignatureKeyParser();
will(returnValue(keyParser));
oneOf(keyParser).parsePublicKey(author.getPublicKey().getEncoded());
will(throwException(new GeneralSecurityException()));
}});
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
private byte[] expectToByteArray(BdfList list) throws Exception { private byte[] expectToByteArray(BdfList list) throws Exception {
BdfWriter bdfWriter = context.mock(BdfWriter.class); BdfWriter bdfWriter = context.mock(BdfWriter.class);

View File

@@ -73,8 +73,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
oneOf(db).transactionWithResult(with(false), withDbCallable(txn)); oneOf(db).transactionWithResult(with(false), withDbCallable(txn));
oneOf(db).addContact(txn, remote, local, verified); oneOf(db).addContact(txn, remote, local, verified);
will(returnValue(contactId)); will(returnValue(contactId));
oneOf(keyManager).addContact(txn, contactId, rootKey, timestamp, oneOf(keyManager).addContactWithRotationKeys(txn, contactId,
alice, active); rootKey, timestamp, alice, active);
oneOf(db).getContact(txn, contactId); oneOf(db).getContact(txn, contactId);
will(returnValue(contact)); will(returnValue(contact));
}}); }});

View File

@@ -21,7 +21,7 @@ import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.ID_LAB
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
@@ -34,12 +34,11 @@ public class PendingContactFactoryImplTest extends BrambleMockTestCase {
private final CryptoComponent crypto = context.mock(CryptoComponent.class); private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final Clock clock = context.mock(Clock.class); private final Clock clock = context.mock(Clock.class);
private final KeyParser keyParser = context.mock(KeyParser.class); private final KeyParser keyParser = context.mock(KeyParser.class);
private final PublicKey publicKey = context.mock(PublicKey.class);
private final PendingContactFactory pendingContactFactory = private final PendingContactFactory pendingContactFactory =
new PendingContactFactoryImpl(crypto, clock); new PendingContactFactoryImpl(crypto, clock);
private final String alias = getRandomString(MAX_AUTHOR_NAME_LENGTH); private final String alias = getRandomString(MAX_AUTHOR_NAME_LENGTH);
private final byte[] publicKeyBytes = getRandomBytes(RAW_LINK_BYTES - 1); private final PublicKey publicKey = getAgreementPublicKey();
private final byte[] idBytes = getRandomId(); private final byte[] idBytes = getRandomId();
private final long timestamp = System.currentTimeMillis(); private final long timestamp = System.currentTimeMillis();
@@ -64,7 +63,7 @@ public class PendingContactFactoryImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(crypto).getAgreementKeyParser(); oneOf(crypto).getAgreementKeyParser();
will(returnValue(keyParser)); will(returnValue(keyParser));
oneOf(keyParser).parsePublicKey(with(equal(publicKeyBytes))); oneOf(keyParser).parsePublicKey(publicKey.getEncoded());
will(throwException(new GeneralSecurityException())); will(throwException(new GeneralSecurityException()));
}}); }});
@@ -95,11 +94,9 @@ public class PendingContactFactoryImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(crypto).getAgreementKeyParser(); oneOf(crypto).getAgreementKeyParser();
will(returnValue(keyParser)); will(returnValue(keyParser));
oneOf(keyParser).parsePublicKey(with(equal(publicKeyBytes))); oneOf(keyParser).parsePublicKey(publicKey.getEncoded());
will(returnValue(publicKey)); will(returnValue(publicKey));
allowing(publicKey).getEncoded(); oneOf(crypto).hash(ID_LABEL, publicKey.getEncoded());
will(returnValue(publicKeyBytes));
oneOf(crypto).hash(ID_LABEL, publicKeyBytes);
will(returnValue(idBytes)); will(returnValue(idBytes));
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
will(returnValue(timestamp)); will(returnValue(timestamp));
@@ -108,7 +105,8 @@ public class PendingContactFactoryImplTest extends BrambleMockTestCase {
PendingContact p = PendingContact p =
pendingContactFactory.createPendingContact(link, alias); pendingContactFactory.createPendingContact(link, alias);
assertArrayEquals(idBytes, p.getId().getBytes()); assertArrayEquals(idBytes, p.getId().getBytes());
assertArrayEquals(publicKeyBytes, p.getPublicKey()); assertArrayEquals(publicKey.getEncoded(),
p.getPublicKey().getEncoded());
assertEquals(alias, p.getAlias()); assertEquals(alias, p.getAlias());
assertEquals(WAITING_FOR_CONNECTION, p.getState()); assertEquals(WAITING_FOR_CONNECTION, p.getState());
assertEquals(timestamp, p.getTimestamp()); assertEquals(timestamp, p.getTimestamp());
@@ -121,6 +119,7 @@ public class PendingContactFactoryImplTest extends BrambleMockTestCase {
private String encodeLink(int formatVersion) { private String encodeLink(int formatVersion) {
byte[] rawLink = new byte[RAW_LINK_BYTES]; byte[] rawLink = new byte[RAW_LINK_BYTES];
rawLink[0] = (byte) formatVersion; rawLink[0] = (byte) formatVersion;
byte[] publicKeyBytes = publicKey.getEncoded();
arraycopy(publicKeyBytes, 0, rawLink, 1, publicKeyBytes.length); arraycopy(publicKeyBytes, 0, rawLink, 1, publicKeyBytes.length);
String base32 = Base32.encode(rawLink).toLowerCase(); String base32 = Base32.encode(rawLink).toLowerCase();
assertEquals(BASE32_LINK_BYTES, base32.length()); assertEquals(BASE32_LINK_BYTES, base32.length());

View File

@@ -1,6 +1,10 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SignaturePrivateKey;
import org.briarproject.bramble.api.crypto.SignaturePublicKey;
import org.junit.Test; import org.junit.Test;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
@@ -137,14 +141,14 @@ public class EdSignatureTest extends SignatureTest {
} }
@Override @Override
protected byte[] sign(String label, byte[] toSign, byte[] privateKey) protected byte[] sign(String label, byte[] toSign, PrivateKey privateKey)
throws GeneralSecurityException { throws GeneralSecurityException {
return crypto.sign(label, toSign, privateKey); return crypto.sign(label, toSign, privateKey);
} }
@Override @Override
protected boolean verify(byte[] signature, String label, byte[] signed, protected boolean verify(byte[] signature, String label, byte[] signed,
byte[] publicKey) throws GeneralSecurityException { PublicKey publicKey) throws GeneralSecurityException {
return crypto.verifySignature(signature, label, signed, publicKey); return crypto.verifySignature(signature, label, signed, publicKey);
} }
@@ -157,11 +161,11 @@ public class EdSignatureTest extends SignatureTest {
byte[] signatureBytes = fromHexString(vector[3]); byte[] signatureBytes = fromHexString(vector[3]);
EdSignature signature = new EdSignature(); EdSignature signature = new EdSignature();
signature.initSign(new EdPrivateKey(privateKeyBytes)); signature.initSign(new SignaturePrivateKey(privateKeyBytes));
signature.update(messageBytes); signature.update(messageBytes);
assertArrayEquals(signatureBytes, signature.sign()); assertArrayEquals(signatureBytes, signature.sign());
signature.initVerify(new EdPublicKey(publicKeyBytes)); signature.initVerify(new SignaturePublicKey(publicKeyBytes));
signature.update(messageBytes); signature.update(messageBytes);
assertTrue(signature.verify(signatureBytes)); assertTrue(signature.verify(signatureBytes));
} }

View File

@@ -1,167 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.HandshakeKeys;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.junit.Test;
import java.util.Arrays;
import static org.briarproject.bramble.crypto.KeyDerivationTestUtils.assertAllDifferent;
import static org.briarproject.bramble.crypto.KeyDerivationTestUtils.assertMatches;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertSame;
public class HandshakeKeyDerivationTest extends BrambleTestCase {
private final CryptoComponent crypto =
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
private final TransportCrypto transportCrypto =
new TransportCryptoImpl(crypto);
private final TransportId transportId = getTransportId();
private final SecretKey rootKey = getSecretKey();
@Test
public void testKeysAreDistinct() {
HandshakeKeys kA = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
HandshakeKeys kB = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, false);
assertAllDifferent(kA);
assertAllDifferent(kB);
}
@Test
public void testKeysAreNotUpdatedToPreviousPeriod() {
HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
HandshakeKeys k1 = transportCrypto.updateHandshakeKeys(k, 122);
assertSame(k, k1);
}
@Test
public void testKeysAreNotUpdatedToCurrentPeriod() {
HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
HandshakeKeys k1 = transportCrypto.updateHandshakeKeys(k, 123);
assertSame(k, k1);
}
@Test
public void testKeysAreUpdatedByOnePeriod() {
HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
HandshakeKeys k1 = transportCrypto.updateHandshakeKeys(k, 124);
assertSame(k.getCurrentIncomingKeys(), k1.getPreviousIncomingKeys());
assertSame(k.getNextIncomingKeys(), k1.getCurrentIncomingKeys());
}
@Test
public void testKeysAreUpdatedByTwoPeriods() {
HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
HandshakeKeys k1 = transportCrypto.updateHandshakeKeys(k, 125);
assertSame(k.getNextIncomingKeys(), k1.getPreviousIncomingKeys());
}
@Test
public void testKeysAreUpdatedByThreePeriods() {
HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
HandshakeKeys k1 = transportCrypto.updateHandshakeKeys(k, 126);
assertAllDifferent(k, k1);
}
@Test
public void testCurrentKeysMatchContact() {
// Start in time period 123
HandshakeKeys kA = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
HandshakeKeys kB = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, false);
// Alice's incoming keys should equal Bob's outgoing keys
assertMatches(kA.getCurrentIncomingKeys(), kB.getCurrentOutgoingKeys());
// Bob's incoming keys should equal Alice's outgoing keys
assertMatches(kB.getCurrentIncomingKeys(), kA.getCurrentOutgoingKeys());
// Update into the future
kA = transportCrypto.updateHandshakeKeys(kA, 456);
kB = transportCrypto.updateHandshakeKeys(kB, 456);
// Alice's incoming keys should equal Bob's outgoing keys
assertMatches(kA.getCurrentIncomingKeys(), kB.getCurrentOutgoingKeys());
// Bob's incoming keys should equal Alice's outgoing keys
assertMatches(kB.getCurrentIncomingKeys(), kA.getCurrentOutgoingKeys());
}
@Test
public void testPreviousKeysMatchContact() {
// Start in time period 123
HandshakeKeys kA = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
HandshakeKeys kB = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, false);
// Compare Alice's previous keys in period 456 with Bob's current keys
// in period 455
kA = transportCrypto.updateHandshakeKeys(kA, 456);
kB = transportCrypto.updateHandshakeKeys(kB, 455);
// Alice's previous incoming keys should equal Bob's current
// outgoing keys
assertMatches(kA.getPreviousIncomingKeys(),
kB.getCurrentOutgoingKeys());
// Compare Alice's current keys in period 456 with Bob's previous keys
// in period 457
kB = transportCrypto.updateHandshakeKeys(kB, 457);
// Bob's previous incoming keys should equal Alice's current
// outgoing keys
assertMatches(kB.getPreviousIncomingKeys(),
kA.getCurrentOutgoingKeys());
}
@Test
public void testNextKeysMatchContact() {
// Start in time period 123
HandshakeKeys kA = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
HandshakeKeys kB = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, false);
// Compare Alice's current keys in period 456 with Bob's next keys in
// period 455
kA = transportCrypto.updateHandshakeKeys(kA, 456);
kB = transportCrypto.updateHandshakeKeys(kB, 455);
// Bob's next incoming keys should equal Alice's current outgoing keys
assertMatches(kB.getNextIncomingKeys(), kA.getCurrentOutgoingKeys());
// Compare Alice's next keys in period 456 with Bob's current keys
// in period 457
kB = transportCrypto.updateHandshakeKeys(kB, 457);
// Alice's next incoming keys should equal Bob's current outgoing keys
assertMatches(kA.getNextIncomingKeys(), kB.getCurrentOutgoingKeys());
}
@Test
public void testRootKeyAffectsOutput() {
SecretKey rootKey1 = getSecretKey();
assertFalse(Arrays.equals(rootKey.getBytes(), rootKey1.getBytes()));
HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
HandshakeKeys k1 = transportCrypto.deriveHandshakeKeys(transportId,
rootKey1, 123, true);
assertAllDifferent(k, k1);
}
@Test
public void testTransportIdAffectsOutput() {
TransportId transportId1 = getTransportId();
assertNotEquals(transportId.getString(), transportId1.getString());
HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
HandshakeKeys k1 = transportCrypto.deriveHandshakeKeys(transportId1,
rootKey, 123, true);
assertAllDifferent(k, k1);
}
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
@@ -10,12 +11,14 @@ import org.junit.Test;
import org.whispersystems.curve25519.Curve25519; import org.whispersystems.curve25519.Curve25519;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Random; import java.util.Random;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.util.StringUtils.fromHexString; import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
public class KeyAgreementTest extends BrambleTestCase { public class KeyAgreementTest extends BrambleTestCase {
@@ -35,6 +38,7 @@ public class KeyAgreementTest extends BrambleTestCase {
private final CryptoComponent crypto = private final CryptoComponent crypto =
new CryptoComponentImpl(new TestSecureRandomProvider(), null); new CryptoComponentImpl(new TestSecureRandomProvider(), null);
private final String label = getRandomString(123);
private final byte[][] inputs; private final byte[][] inputs;
public KeyAgreementTest() { public KeyAgreementTest() {
@@ -48,9 +52,9 @@ public class KeyAgreementTest extends BrambleTestCase {
public void testDerivesSharedSecret() throws Exception { public void testDerivesSharedSecret() throws Exception {
KeyPair aPair = crypto.generateAgreementKeyPair(); KeyPair aPair = crypto.generateAgreementKeyPair();
KeyPair bPair = crypto.generateAgreementKeyPair(); KeyPair bPair = crypto.generateAgreementKeyPair();
SecretKey aShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL, SecretKey aShared = crypto.deriveSharedSecret(label,
bPair.getPublic(), aPair, inputs); bPair.getPublic(), aPair, inputs);
SecretKey bShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL, SecretKey bShared = crypto.deriveSharedSecret(label,
aPair.getPublic(), bPair, inputs); aPair.getPublic(), bPair, inputs);
assertArrayEquals(aShared.getBytes(), bShared.getBytes()); assertArrayEquals(aShared.getBytes(), bShared.getBytes());
} }
@@ -58,18 +62,15 @@ public class KeyAgreementTest extends BrambleTestCase {
@Test(expected = GeneralSecurityException.class) @Test(expected = GeneralSecurityException.class)
public void testRejectsInvalidPublicKey() throws Exception { public void testRejectsInvalidPublicKey() throws Exception {
KeyPair keyPair = crypto.generateAgreementKeyPair(); KeyPair keyPair = crypto.generateAgreementKeyPair();
PublicKey invalid = new Curve25519PublicKey(new byte[32]); PublicKey invalid = new AgreementPublicKey(new byte[32]);
crypto.deriveSharedSecret(SHARED_SECRET_LABEL, invalid, keyPair, crypto.deriveSharedSecret(label, invalid, keyPair, inputs);
inputs);
} }
@Test @Test
public void testRfc7748TestVector() throws Exception { public void testRfc7748TestVector() {
// Private keys need to be clamped because curve25519-java does the byte[] aPriv = parsePrivateKey(ALICE_PRIVATE);
// clamping at key generation time, not multiplication time
byte[] aPriv = Curve25519KeyParser.clamp(fromHexString(ALICE_PRIVATE));
byte[] aPub = fromHexString(ALICE_PUBLIC); byte[] aPub = fromHexString(ALICE_PUBLIC);
byte[] bPriv = Curve25519KeyParser.clamp(fromHexString(BOB_PRIVATE)); byte[] bPriv = parsePrivateKey(BOB_PRIVATE);
byte[] bPub = fromHexString(BOB_PUBLIC); byte[] bPub = fromHexString(BOB_PUBLIC);
byte[] sharedSecret = fromHexString(SHARED_SECRET); byte[] sharedSecret = fromHexString(SHARED_SECRET);
Curve25519 curve25519 = Curve25519.getInstance("java"); Curve25519 curve25519 = Curve25519.getInstance("java");
@@ -78,4 +79,82 @@ public class KeyAgreementTest extends BrambleTestCase {
assertArrayEquals(sharedSecret, assertArrayEquals(sharedSecret,
curve25519.calculateAgreement(bPub, aPriv)); curve25519.calculateAgreement(bPub, aPriv));
} }
@Test
public void testDerivesSameSharedSecretFromEquivalentPublicKey() {
byte[] aPub = fromHexString(ALICE_PUBLIC);
byte[] bPriv = parsePrivateKey(BOB_PRIVATE);
byte[] sharedSecret = fromHexString(SHARED_SECRET);
Curve25519 curve25519 = Curve25519.getInstance("java");
// Flip the unused most significant bit of the little-endian public key
byte[] aPubEquiv = aPub.clone();
aPubEquiv[31] ^= (byte) 128;
// The public keys should be different but give the same shared secret
assertFalse(Arrays.equals(aPub, aPubEquiv));
assertArrayEquals(sharedSecret,
curve25519.calculateAgreement(aPub, bPriv));
assertArrayEquals(sharedSecret,
curve25519.calculateAgreement(aPubEquiv, bPriv));
}
@Test
public void testDerivesSameSharedSecretFromEquivalentPublicKeyWithoutPublicKeysHashedIn()
throws Exception {
KeyPair aPair = crypto.generateAgreementKeyPair();
KeyPair bPair = crypto.generateAgreementKeyPair();
// Flip the unused most significant bit of the little-endian public key
byte[] aPub = aPair.getPublic().getEncoded();
byte[] aPubEquiv = aPub.clone();
aPubEquiv[31] ^= (byte) 128;
KeyPair aPairEquiv = new KeyPair(new AgreementPublicKey(aPubEquiv),
aPair.getPrivate());
// The public keys should be different but give the same shared secret
assertFalse(Arrays.equals(aPub, aPubEquiv));
SecretKey shared = crypto.deriveSharedSecret(label,
aPair.getPublic(), bPair);
SecretKey sharedEquiv = crypto.deriveSharedSecret(label,
aPairEquiv.getPublic(), bPair);
assertArrayEquals(shared.getBytes(), sharedEquiv.getBytes());
}
@Test
public void testDerivesDifferentSharedSecretFromEquivalentPublicKeyWithPublicKeysHashedIn()
throws Exception {
KeyPair aPair = crypto.generateAgreementKeyPair();
KeyPair bPair = crypto.generateAgreementKeyPair();
// Flip the unused most significant bit of the little-endian public key
byte[] aPub = aPair.getPublic().getEncoded();
byte[] aPubEquiv = aPub.clone();
aPubEquiv[31] ^= (byte) 128;
KeyPair aPairEquiv = new KeyPair(new AgreementPublicKey(aPubEquiv),
aPair.getPrivate());
// The public keys should be different and give different shared secrets
assertFalse(Arrays.equals(aPub, aPubEquiv));
SecretKey shared = deriveSharedSecretWithPublicKeysHashedIn(label,
aPair.getPublic(), bPair);
SecretKey sharedEquiv = deriveSharedSecretWithPublicKeysHashedIn(label,
aPairEquiv.getPublic(), bPair);
assertFalse(Arrays.equals(shared.getBytes(), sharedEquiv.getBytes()));
}
private SecretKey deriveSharedSecretWithPublicKeysHashedIn(String label,
PublicKey publicKey, KeyPair keyPair) throws Exception {
byte[][] inputs = new byte[][] {
publicKey.getEncoded(),
keyPair.getPublic().getEncoded()
};
return crypto.deriveSharedSecret(label, publicKey, keyPair, inputs);
}
private byte[] parsePrivateKey(String hex) {
// Private keys need to be clamped because curve25519-java does the
// clamping at key generation time, not multiplication time
return AgreementKeyParser.clamp(fromHexString(hex));
}
} }

View File

@@ -1,45 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.transport.AbstractTransportKeys;
import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.OutgoingKeys;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
class KeyDerivationTestUtils {
static void assertAllDifferent(AbstractTransportKeys... transportKeys) {
List<SecretKey> secretKeys = new ArrayList<>();
for (AbstractTransportKeys 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);
}
static void assertAllDifferent(List<SecretKey> keys) {
Set<Bytes> set = new HashSet<>();
for (SecretKey k : keys) assertTrue(set.add(new Bytes(k.getBytes())));
}
static void assertMatches(IncomingKeys in, OutgoingKeys out) {
assertArrayEquals(in.getTagKey().getBytes(),
out.getTagKey().getBytes());
assertArrayEquals(in.getHeaderKey().getBytes(),
out.getHeaderKey().getBytes());
}
}

View File

@@ -23,7 +23,7 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
new CryptoComponentImpl(new TestSecureRandomProvider(), null); new CryptoComponentImpl(new TestSecureRandomProvider(), null);
@Test @Test
public void testAgreementPublicKeyLength() throws Exception { public void testAgreementPublicKeyLength() {
// Generate 10 agreement key pairs // Generate 10 agreement key pairs
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
KeyPair keyPair = crypto.generateAgreementKeyPair(); KeyPair keyPair = crypto.generateAgreementKeyPair();
@@ -70,7 +70,7 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
} }
@Test @Test
public void testAgreementKeyParserByFuzzing() throws Exception { public void testAgreementKeyParserByFuzzing() {
KeyParser parser = crypto.getAgreementKeyParser(); KeyParser parser = crypto.getAgreementKeyParser();
// Generate a key pair to get the proper public key length // Generate a key pair to get the proper public key length
KeyPair p = crypto.generateAgreementKeyPair(); KeyPair p = crypto.generateAgreementKeyPair();
@@ -92,7 +92,7 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
} }
@Test @Test
public void testSignaturePublicKeyLength() throws Exception { public void testSignaturePublicKeyLength() {
// Generate 10 signature key pairs // Generate 10 signature key pairs
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
KeyPair keyPair = crypto.generateSignatureKeyPair(); KeyPair keyPair = crypto.generateSignatureKeyPair();
@@ -107,10 +107,10 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
// Generate 10 signature key pairs // Generate 10 signature key pairs
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
KeyPair keyPair = crypto.generateSignatureKeyPair(); KeyPair keyPair = crypto.generateSignatureKeyPair();
byte[] key = keyPair.getPrivate().getEncoded(); PrivateKey privateKey = keyPair.getPrivate();
// Sign some random data and check the length of the signature // Sign some random data and check the length of the signature
byte[] toBeSigned = getRandomBytes(1234); byte[] toBeSigned = getRandomBytes(1234);
byte[] signature = crypto.sign("label", toBeSigned, key); byte[] signature = crypto.sign("label", toBeSigned, privateKey);
assertTrue(signature.length <= MAX_SIGNATURE_BYTES); assertTrue(signature.length <= MAX_SIGNATURE_BYTES);
} }
} }
@@ -123,16 +123,15 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
PublicKey publicKey = keyPair.getPublic(); PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate(); PrivateKey privateKey = keyPair.getPrivate();
byte[] message = getRandomBytes(123); byte[] message = getRandomBytes(123);
byte[] signature = crypto.sign("test", message, byte[] signature = crypto.sign("test", message, privateKey);
privateKey.getEncoded());
// Verify the signature // Verify the signature
assertTrue(crypto.verifySignature(signature, "test", message, assertTrue(crypto.verifySignature(signature, "test", message,
publicKey.getEncoded())); publicKey));
// Encode and parse the public key - no exceptions should be thrown // Encode and parse the public key - no exceptions should be thrown
publicKey = parser.parsePublicKey(publicKey.getEncoded()); publicKey = parser.parsePublicKey(publicKey.getEncoded());
// Verify the signature again // Verify the signature again
assertTrue(crypto.verifySignature(signature, "test", message, assertTrue(crypto.verifySignature(signature, "test", message,
publicKey.getEncoded())); publicKey));
} }
@Test @Test
@@ -143,23 +142,21 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
PublicKey publicKey = keyPair.getPublic(); PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate(); PrivateKey privateKey = keyPair.getPrivate();
byte[] message = getRandomBytes(123); byte[] message = getRandomBytes(123);
byte[] signature = crypto.sign("test", message, byte[] signature = crypto.sign("test", message, privateKey);
privateKey.getEncoded());
// Verify the signature // Verify the signature
assertTrue(crypto.verifySignature(signature, "test", message, assertTrue(crypto.verifySignature(signature, "test", message,
publicKey.getEncoded())); publicKey));
// Encode and parse the private key - no exceptions should be thrown // Encode and parse the private key - no exceptions should be thrown
privateKey = parser.parsePrivateKey(privateKey.getEncoded()); privateKey = parser.parsePrivateKey(privateKey.getEncoded());
// Sign the data again - the signatures should be the same // Sign the data again - the signatures should be the same
byte[] signature1 = crypto.sign("test", message, byte[] signature1 = crypto.sign("test", message, privateKey);
privateKey.getEncoded());
assertTrue(crypto.verifySignature(signature1, "test", message, assertTrue(crypto.verifySignature(signature1, "test", message,
publicKey.getEncoded())); publicKey));
assertArrayEquals(signature, signature1); assertArrayEquals(signature, signature1);
} }
@Test @Test
public void testSignatureKeyParserByFuzzing() throws Exception { public void testSignatureKeyParserByFuzzing() {
KeyParser parser = crypto.getSignatureKeyParser(); KeyParser parser = crypto.getSignatureKeyParser();
// Generate a key pair to get the proper public key length // Generate a key pair to get the proper public key length
KeyPair p = crypto.generateSignatureKeyPair(); KeyPair p = crypto.generateSignatureKeyPair();

View File

@@ -2,6 +2,8 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider; import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils; import org.briarproject.bramble.test.TestUtils;
@@ -19,23 +21,24 @@ public abstract class SignatureTest extends BrambleTestCase {
protected final CryptoComponent crypto; protected final CryptoComponent crypto;
private final byte[] publicKey, privateKey; private final PublicKey publicKey;
private final PrivateKey privateKey;
private final String label = StringUtils.getRandomString(42); private final String label = StringUtils.getRandomString(42);
private final byte[] inputBytes = TestUtils.getRandomBytes(123); private final byte[] inputBytes = TestUtils.getRandomBytes(123);
protected abstract KeyPair generateKeyPair(); protected abstract KeyPair generateKeyPair();
protected abstract byte[] sign(String label, byte[] toSign, protected abstract byte[] sign(String label, byte[] toSign,
byte[] privateKey) throws GeneralSecurityException; PrivateKey privateKey) throws GeneralSecurityException;
protected abstract boolean verify(byte[] signature, String label, protected abstract boolean verify(byte[] signature, String label,
byte[] signed, byte[] publicKey) throws GeneralSecurityException; byte[] signed, PublicKey publicKey) throws GeneralSecurityException;
SignatureTest() { SignatureTest() {
crypto = new CryptoComponentImpl(new TestSecureRandomProvider(), null); crypto = new CryptoComponentImpl(new TestSecureRandomProvider(), null);
KeyPair k = generateKeyPair(); KeyPair k = generateKeyPair();
publicKey = k.getPublic().getEncoded(); publicKey = k.getPublic();
privateKey = k.getPrivate().getEncoded(); privateKey = k.getPrivate();
} }
@Test @Test
@@ -51,7 +54,7 @@ public abstract class SignatureTest extends BrambleTestCase {
public void testDifferentKeysProduceDifferentSignatures() throws Exception { public void testDifferentKeysProduceDifferentSignatures() throws Exception {
// Generate second private key // Generate second private key
KeyPair k2 = generateKeyPair(); KeyPair k2 = generateKeyPair();
byte[] privateKey2 = k2.getPrivate().getEncoded(); PrivateKey privateKey2 = k2.getPrivate();
// Calculate the signature with each key // Calculate the signature with each key
byte[] sig1 = sign(label, inputBytes, privateKey); byte[] sig1 = sign(label, inputBytes, privateKey);
byte[] sig2 = sign(label, inputBytes, privateKey2); byte[] sig2 = sign(label, inputBytes, privateKey2);
@@ -92,7 +95,7 @@ public abstract class SignatureTest extends BrambleTestCase {
public void testDifferentKeyFailsVerification() throws Exception { public void testDifferentKeyFailsVerification() throws Exception {
// Generate second private key // Generate second private key
KeyPair k2 = generateKeyPair(); KeyPair k2 = generateKeyPair();
byte[] privateKey2 = k2.getPrivate().getEncoded(); PrivateKey privateKey2 = k2.getPrivate();
// calculate the signature with different key, should fail to verify // calculate the signature with different key, should fail to verify
byte[] sig = sign(label, inputBytes, privateKey2); byte[] sig = sign(label, inputBytes, privateKey2);
assertFalse(verify(sig, label, inputBytes, publicKey)); assertFalse(verify(sig, label, inputBytes, publicKey));

View File

@@ -1,23 +1,30 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto; import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider; import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.junit.Test; import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.briarproject.bramble.crypto.KeyDerivationTestUtils.assertAllDifferent;
import static org.briarproject.bramble.crypto.KeyDerivationTestUtils.assertMatches;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class TransportKeyDerivationTest extends BrambleTestCase { public class TransportKeyDerivationTest extends BrambleTestCase {
@@ -29,70 +36,70 @@ public class TransportKeyDerivationTest extends BrambleTestCase {
private final SecretKey rootKey = getSecretKey(); private final SecretKey rootKey = getSecretKey();
@Test @Test
public void testKeysAreDistinct() { public void testRotationKeysAreDistinct() {
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId, TransportKeys kA = transportCrypto.deriveRotationKeys(transportId,
rootKey, 123, true, true); rootKey, 123, true, true);
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId, TransportKeys kB = transportCrypto.deriveRotationKeys(transportId,
rootKey, 123, false, true); rootKey, 123, false, true);
assertAllDifferent(kA); assertAllDifferent(kA);
assertAllDifferent(kB); assertAllDifferent(kB);
} }
@Test @Test
public void testKeysAreNotRotatedToPreviousPeriod() { public void testRotationKeysAreNotRotatedToPreviousPeriod() {
TransportKeys k = transportCrypto.deriveTransportKeys(transportId, TransportKeys k = transportCrypto.deriveRotationKeys(transportId,
rootKey, 123, true, true); rootKey, 123, true, true);
TransportKeys k1 = transportCrypto.rotateTransportKeys(k, 122); TransportKeys k1 = transportCrypto.updateTransportKeys(k, 122);
assertSame(k, k1); assertSame(k, k1);
} }
@Test @Test
public void testKeysAreNotRotatedToCurrentPeriod() { public void testRotationKeysAreNotRotatedToCurrentPeriod() {
TransportKeys k = transportCrypto.deriveTransportKeys(transportId, TransportKeys k = transportCrypto.deriveRotationKeys(transportId,
rootKey, 123, true, true); rootKey, 123, true, true);
TransportKeys k1 = transportCrypto.rotateTransportKeys(k, 123); TransportKeys k1 = transportCrypto.updateTransportKeys(k, 123);
assertSame(k, k1); assertSame(k, k1);
} }
@Test @Test
public void testKeysAreRotatedByOnePeriod() { public void testRotationKeysAreRotatedByOnePeriod() {
TransportKeys k = transportCrypto.deriveTransportKeys(transportId, TransportKeys k = transportCrypto.deriveRotationKeys(transportId,
rootKey, 123, true, true); rootKey, 123, true, true);
TransportKeys k1 = transportCrypto.rotateTransportKeys(k, 124); TransportKeys k1 = transportCrypto.updateTransportKeys(k, 124);
assertSame(k.getCurrentIncomingKeys(), k1.getPreviousIncomingKeys()); assertSame(k.getCurrentIncomingKeys(), k1.getPreviousIncomingKeys());
assertSame(k.getNextIncomingKeys(), k1.getCurrentIncomingKeys()); assertSame(k.getNextIncomingKeys(), k1.getCurrentIncomingKeys());
} }
@Test @Test
public void testKeysAreRotatedByTwoPeriods() { public void testRotationKeysAreRotatedByTwoPeriods() {
TransportKeys k = transportCrypto.deriveTransportKeys(transportId, TransportKeys k = transportCrypto.deriveRotationKeys(transportId,
rootKey, 123, true, true); rootKey, 123, true, true);
TransportKeys k1 = transportCrypto.rotateTransportKeys(k, 125); TransportKeys k1 = transportCrypto.updateTransportKeys(k, 125);
assertSame(k.getNextIncomingKeys(), k1.getPreviousIncomingKeys()); assertSame(k.getNextIncomingKeys(), k1.getPreviousIncomingKeys());
} }
@Test @Test
public void testKeysAreRotatedByThreePeriods() { public void testRotationKeysAreRotatedByThreePeriods() {
TransportKeys k = transportCrypto.deriveTransportKeys(transportId, TransportKeys k = transportCrypto.deriveRotationKeys(transportId,
rootKey, 123, true, true); rootKey, 123, true, true);
TransportKeys k1 = transportCrypto.rotateTransportKeys(k, 126); TransportKeys k1 = transportCrypto.updateTransportKeys(k, 126);
assertAllDifferent(k, k1); assertAllDifferent(k, k1);
} }
@Test @Test
public void testCurrentKeysMatchContact() { public void testCurrentRotationKeysMatchContact() {
// Start in time period 123 // Start in time period 123
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId, TransportKeys kA = transportCrypto.deriveRotationKeys(transportId,
rootKey, 123, true, true); rootKey, 123, true, true);
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId, TransportKeys kB = transportCrypto.deriveRotationKeys(transportId,
rootKey, 123, false, true); rootKey, 123, false, true);
// Alice's incoming keys should equal Bob's outgoing keys // Alice's incoming keys should equal Bob's outgoing keys
assertMatches(kA.getCurrentIncomingKeys(), kB.getCurrentOutgoingKeys()); assertMatches(kA.getCurrentIncomingKeys(), kB.getCurrentOutgoingKeys());
// Bob's incoming keys should equal Alice's outgoing keys // Bob's incoming keys should equal Alice's outgoing keys
assertMatches(kB.getCurrentIncomingKeys(), kA.getCurrentOutgoingKeys()); assertMatches(kB.getCurrentIncomingKeys(), kA.getCurrentOutgoingKeys());
// Rotate into the future // Rotate into the future
kA = transportCrypto.rotateTransportKeys(kA, 456); kA = transportCrypto.updateTransportKeys(kA, 456);
kB = transportCrypto.rotateTransportKeys(kB, 456); kB = transportCrypto.updateTransportKeys(kB, 456);
// Alice's incoming keys should equal Bob's outgoing keys // Alice's incoming keys should equal Bob's outgoing keys
assertMatches(kA.getCurrentIncomingKeys(), kB.getCurrentOutgoingKeys()); assertMatches(kA.getCurrentIncomingKeys(), kB.getCurrentOutgoingKeys());
// Bob's incoming keys should equal Alice's outgoing keys // Bob's incoming keys should equal Alice's outgoing keys
@@ -100,23 +107,23 @@ public class TransportKeyDerivationTest extends BrambleTestCase {
} }
@Test @Test
public void testPreviousKeysMatchContact() { public void testPreviousRotationKeysMatchContact() {
// Start in time period 123 // Start in time period 123
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId, TransportKeys kA = transportCrypto.deriveRotationKeys(transportId,
rootKey, 123, true, true); rootKey, 123, true, true);
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId, TransportKeys kB = transportCrypto.deriveRotationKeys(transportId,
rootKey, 123, false, true); rootKey, 123, false, true);
// Compare Alice's previous keys in period 456 with Bob's current keys // Compare Alice's previous keys in period 456 with Bob's current keys
// in period 455 // in period 455
kA = transportCrypto.rotateTransportKeys(kA, 456); kA = transportCrypto.updateTransportKeys(kA, 456);
kB = transportCrypto.rotateTransportKeys(kB, 455); kB = transportCrypto.updateTransportKeys(kB, 455);
// Alice's previous incoming keys should equal Bob's current // Alice's previous incoming keys should equal Bob's current
// outgoing keys // outgoing keys
assertMatches(kA.getPreviousIncomingKeys(), assertMatches(kA.getPreviousIncomingKeys(),
kB.getCurrentOutgoingKeys()); kB.getCurrentOutgoingKeys());
// Compare Alice's current keys in period 456 with Bob's previous keys // Compare Alice's current keys in period 456 with Bob's previous keys
// in period 457 // in period 457
kB = transportCrypto.rotateTransportKeys(kB, 457); kB = transportCrypto.updateTransportKeys(kB, 457);
// Bob's previous incoming keys should equal Alice's current // Bob's previous incoming keys should equal Alice's current
// outgoing keys // outgoing keys
assertMatches(kB.getPreviousIncomingKeys(), assertMatches(kB.getPreviousIncomingKeys(),
@@ -124,44 +131,208 @@ public class TransportKeyDerivationTest extends BrambleTestCase {
} }
@Test @Test
public void testNextKeysMatchContact() { public void testNextRotationKeysMatchContact() {
// Start in time period 123 // Start in time period 123
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId, TransportKeys kA = transportCrypto.deriveRotationKeys(transportId,
rootKey, 123, true, true); rootKey, 123, true, true);
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId, TransportKeys kB = transportCrypto.deriveRotationKeys(transportId,
rootKey, 123, false, true); rootKey, 123, false, true);
// Compare Alice's current keys in period 456 with Bob's next keys in // Compare Alice's current keys in period 456 with Bob's next keys in
// period 455 // period 455
kA = transportCrypto.rotateTransportKeys(kA, 456); kA = transportCrypto.updateTransportKeys(kA, 456);
kB = transportCrypto.rotateTransportKeys(kB, 455); kB = transportCrypto.updateTransportKeys(kB, 455);
// Bob's next incoming keys should equal Alice's current outgoing keys // Bob's next incoming keys should equal Alice's current outgoing keys
assertMatches(kB.getNextIncomingKeys(), kA.getCurrentOutgoingKeys()); assertMatches(kB.getNextIncomingKeys(), kA.getCurrentOutgoingKeys());
// Compare Alice's next keys in period 456 with Bob's current keys // Compare Alice's next keys in period 456 with Bob's current keys
// in period 457 // in period 457
kB = transportCrypto.rotateTransportKeys(kB, 457); kB = transportCrypto.updateTransportKeys(kB, 457);
// Alice's next incoming keys should equal Bob's current outgoing keys // Alice's next incoming keys should equal Bob's current outgoing keys
assertMatches(kA.getNextIncomingKeys(), kB.getCurrentOutgoingKeys()); assertMatches(kA.getNextIncomingKeys(), kB.getCurrentOutgoingKeys());
} }
@Test @Test
public void testRootKeyAffectsOutput() { public void testRootKeyAffectsRotationKeyDerivation() {
SecretKey rootKey1 = getSecretKey(); SecretKey rootKey1 = getSecretKey();
assertFalse(Arrays.equals(rootKey.getBytes(), rootKey1.getBytes())); assertFalse(Arrays.equals(rootKey.getBytes(), rootKey1.getBytes()));
TransportKeys k = transportCrypto.deriveTransportKeys(transportId, TransportKeys k = transportCrypto.deriveRotationKeys(transportId,
rootKey, 123, true, true); rootKey, 123, true, true);
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId, TransportKeys k1 = transportCrypto.deriveRotationKeys(transportId,
rootKey1, 123, true, true); rootKey1, 123, true, true);
assertAllDifferent(k, k1); assertAllDifferent(k, k1);
} }
@Test @Test
public void testTransportIdAffectsOutput() { public void testTransportIdAffectsRotationKeyDerivation() {
TransportId transportId1 = getTransportId(); TransportId transportId1 = getTransportId();
assertNotEquals(transportId.getString(), transportId1.getString()); assertNotEquals(transportId.getString(), transportId1.getString());
TransportKeys k = transportCrypto.deriveTransportKeys(transportId, TransportKeys k = transportCrypto.deriveRotationKeys(transportId,
rootKey, 123, true, true); rootKey, 123, true, true);
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId1, TransportKeys k1 = transportCrypto.deriveRotationKeys(transportId1,
rootKey, 123, true, true); rootKey, 123, true, true);
assertAllDifferent(k, k1); assertAllDifferent(k, k1);
} }
@Test
public void testHandshakeKeysAreDistinct() {
TransportKeys kA = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
TransportKeys kB = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, false);
assertAllDifferent(kA);
assertAllDifferent(kB);
}
@Test
public void testHandshakeKeysAreNotUpdatedToPreviousPeriod() {
TransportKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
TransportKeys k1 = transportCrypto.updateTransportKeys(k, 122);
assertSame(k, k1);
}
@Test
public void testHandshakeKeysAreNotUpdatedToCurrentPeriod() {
TransportKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
TransportKeys k1 = transportCrypto.updateTransportKeys(k, 123);
assertSame(k, k1);
}
@Test
public void testHandshakeKeysAreUpdatedByOnePeriod() {
TransportKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
TransportKeys k1 = transportCrypto.updateTransportKeys(k, 124);
assertSame(k.getCurrentIncomingKeys(), k1.getPreviousIncomingKeys());
assertSame(k.getNextIncomingKeys(), k1.getCurrentIncomingKeys());
}
@Test
public void testHandshakeKeysAreUpdatedByTwoPeriods() {
TransportKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
TransportKeys k1 = transportCrypto.updateTransportKeys(k, 125);
assertSame(k.getNextIncomingKeys(), k1.getPreviousIncomingKeys());
}
@Test
public void testHandshakeKeysAreUpdatedByThreePeriods() {
TransportKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
TransportKeys k1 = transportCrypto.updateTransportKeys(k, 126);
assertAllDifferent(k, k1);
}
@Test
public void testCurrentHandshakeKeysMatchContact() {
// Start in time period 123
TransportKeys kA = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
TransportKeys kB = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, false);
// Alice's incoming keys should equal Bob's outgoing keys
assertMatches(kA.getCurrentIncomingKeys(), kB.getCurrentOutgoingKeys());
// Bob's incoming keys should equal Alice's outgoing keys
assertMatches(kB.getCurrentIncomingKeys(), kA.getCurrentOutgoingKeys());
// Update into the future
kA = transportCrypto.updateTransportKeys(kA, 456);
kB = transportCrypto.updateTransportKeys(kB, 456);
// Alice's incoming keys should equal Bob's outgoing keys
assertMatches(kA.getCurrentIncomingKeys(), kB.getCurrentOutgoingKeys());
// Bob's incoming keys should equal Alice's outgoing keys
assertMatches(kB.getCurrentIncomingKeys(), kA.getCurrentOutgoingKeys());
}
@Test
public void testPreviousHandshakeKeysMatchContact() {
// Start in time period 123
TransportKeys kA = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
TransportKeys kB = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, false);
// Compare Alice's previous keys in period 456 with Bob's current keys
// in period 455
kA = transportCrypto.updateTransportKeys(kA, 456);
kB = transportCrypto.updateTransportKeys(kB, 455);
// Alice's previous incoming keys should equal Bob's current
// outgoing keys
assertMatches(kA.getPreviousIncomingKeys(),
kB.getCurrentOutgoingKeys());
// Compare Alice's current keys in period 456 with Bob's previous keys
// in period 457
kB = transportCrypto.updateTransportKeys(kB, 457);
// Bob's previous incoming keys should equal Alice's current
// outgoing keys
assertMatches(kB.getPreviousIncomingKeys(),
kA.getCurrentOutgoingKeys());
}
@Test
public void testNextHandshakeKeysMatchContact() {
// Start in time period 123
TransportKeys kA = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
TransportKeys kB = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, false);
// Compare Alice's current keys in period 456 with Bob's next keys in
// period 455
kA = transportCrypto.updateTransportKeys(kA, 456);
kB = transportCrypto.updateTransportKeys(kB, 455);
// Bob's next incoming keys should equal Alice's current outgoing keys
assertMatches(kB.getNextIncomingKeys(), kA.getCurrentOutgoingKeys());
// Compare Alice's next keys in period 456 with Bob's current keys
// in period 457
kB = transportCrypto.updateTransportKeys(kB, 457);
// Alice's next incoming keys should equal Bob's current outgoing keys
assertMatches(kA.getNextIncomingKeys(), kB.getCurrentOutgoingKeys());
}
@Test
public void testRootKeyAffectsHandshakeKeyDerivation() {
SecretKey rootKey1 = getSecretKey();
assertFalse(Arrays.equals(rootKey.getBytes(), rootKey1.getBytes()));
TransportKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
TransportKeys k1 = transportCrypto.deriveHandshakeKeys(transportId,
rootKey1, 123, true);
assertAllDifferent(k, k1);
}
@Test
public void testTransportIdAffectsHandshakeKeyDerivation() {
TransportId transportId1 = getTransportId();
assertNotEquals(transportId.getString(), transportId1.getString());
TransportKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, 123, true);
TransportKeys k1 = transportCrypto.deriveHandshakeKeys(transportId1,
rootKey, 123, true);
assertAllDifferent(k, k1);
}
private void assertAllDifferent(TransportKeys... transportKeys) {
List<SecretKey> secretKeys = new ArrayList<>();
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) {
Set<Bytes> set = new HashSet<>();
for (SecretKey k : keys) assertTrue(set.add(new Bytes(k.getBytes())));
}
private void assertMatches(IncomingKeys in, OutgoingKeys out) {
assertArrayEquals(in.getTagKey().getBytes(),
out.getTagKey().getBytes());
assertArrayEquals(in.getHeaderKey().getBytes(),
out.getHeaderKey().getBytes());
}
} }

View File

@@ -5,6 +5,8 @@ import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId; import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent; import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.ContactExistsException; import org.briarproject.bramble.api.db.ContactExistsException;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -46,11 +48,10 @@ import org.briarproject.bramble.api.sync.event.MessageToAckEvent;
import org.briarproject.bramble.api.sync.event.MessageToRequestEvent; import org.briarproject.bramble.api.sync.event.MessageToRequestEvent;
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent; import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
import org.briarproject.bramble.api.sync.event.MessagesSentEvent; import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
import org.briarproject.bramble.api.transport.HandshakeKeys;
import org.briarproject.bramble.api.transport.IncomingKeys; import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.OutgoingKeys; import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeySet; import org.briarproject.bramble.api.transport.TransportKeySet;
import org.briarproject.bramble.api.transport.TransportKeySetId;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction; import org.briarproject.bramble.test.CaptureArgumentAction;
@@ -66,7 +67,6 @@ import java.util.concurrent.atomic.AtomicReference;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
@@ -75,13 +75,14 @@ import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERE
import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN; import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE; import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
import static org.briarproject.bramble.db.DatabaseConstants.MAX_OFFERED_MESSAGES; import static org.briarproject.bramble.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getClientId; import static org.briarproject.bramble.test.TestUtils.getClientId;
import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getIdentity; import static org.briarproject.bramble.test.TestUtils.getIdentity;
import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
@@ -117,7 +118,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
private final int maxLatency; private final int maxLatency;
private final ContactId contactId; private final ContactId contactId;
private final Contact contact; private final Contact contact;
private final TransportKeySetId keySetId; private final KeySetId keySetId;
private final PendingContactId pendingContactId; private final PendingContactId pendingContactId;
public DatabaseComponentImplTest() { public DatabaseComponentImplTest() {
@@ -139,7 +140,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
contact = getContact(author, localAuthor.getId(), true); contact = getContact(author, localAuthor.getId(), true);
contactId = contact.getId(); contactId = contact.getId();
alias = contact.getAlias(); alias = contact.getAlias();
keySetId = new TransportKeySetId(345); keySetId = new KeySetId(345);
pendingContactId = new PendingContactId(getRandomId()); pendingContactId = new PendingContactId(getRandomId());
} }
@@ -284,24 +285,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Check whether the contact is in the DB (which it's not) // Check whether the contact is in the DB (which it's not)
exactly(17).of(database).startTransaction(); exactly(16).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
exactly(17).of(database).containsContact(txn, contactId); exactly(16).of(database).containsContact(txn, contactId);
will(returnValue(false)); will(returnValue(false));
exactly(17).of(database).abortTransaction(txn); exactly(16).of(database).abortTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); eventExecutor, shutdownManager);
try {
db.transaction(false, transaction ->
db.addHandshakeKeys(transaction, contactId,
createHandshakeKeys()));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.addTransportKeys(transaction, contactId, db.addTransportKeys(transaction, contactId,
@@ -476,8 +468,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} }
try { try {
byte[] publicKey = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES); PublicKey publicKey = getAgreementPublicKey();
byte[] privateKey = getRandomBytes(123); PrivateKey privateKey = getAgreementPrivateKey();
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.setHandshakeKeyPair(transaction, localAuthor.getId(), db.setHandshakeKeyPair(transaction, localAuthor.getId(),
publicKey, privateKey)); publicKey, privateKey));
@@ -497,8 +489,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
exactly(8).of(database).containsGroup(txn, groupId); exactly(8).of(database).containsGroup(txn, groupId);
will(returnValue(false)); will(returnValue(false));
exactly(8).of(database).abortTransaction(txn); exactly(8).of(database).abortTransaction(txn);
// This is needed for getMessageStatus() and setGroupVisibility() // Allow other checks to pass
exactly(2).of(database).containsContact(txn, contactId); allowing(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
@@ -581,8 +573,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
exactly(11).of(database).containsMessage(txn, messageId); exactly(11).of(database).containsMessage(txn, messageId);
will(returnValue(false)); will(returnValue(false));
exactly(11).of(database).abortTransaction(txn); exactly(11).of(database).abortTransaction(txn);
// This is needed for getMessageStatus() to proceed // Allow other checks to pass
exactly(1).of(database).containsContact(txn, contactId); allowing(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
@@ -682,15 +674,38 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// 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(5).of(database).startTransaction(); exactly(8).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
exactly(5).of(database).containsTransport(txn, transportId); exactly(8).of(database).containsTransport(txn, transportId);
will(returnValue(false)); will(returnValue(false));
exactly(5).of(database).abortTransaction(txn); exactly(8).of(database).abortTransaction(txn);
// Allow other checks to pass
allowing(database).containsContact(txn, contactId);
will(returnValue(true));
allowing(database).containsPendingContact(txn, pendingContactId);
will(returnValue(true));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); eventExecutor, shutdownManager);
try {
db.transaction(false, transaction ->
db.addTransportKeys(transaction, contactId,
createHandshakeKeys()));
fail();
} catch (NoSuchTransportException expected) {
// Expected
}
try {
db.transaction(false, transaction ->
db.addTransportKeys(transaction, pendingContactId,
createHandshakeKeys()));
fail();
} catch (NoSuchTransportException expected) {
// Expected
}
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.getTransportKeys(transaction, transportId)); db.getTransportKeys(transaction, transportId));
@@ -710,7 +725,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.removeTransport(transaction, transportId)); db.removeTransportKeys(transaction, transportId, keySetId));
fail(); fail();
} catch (NoSuchTransportException expected) { } catch (NoSuchTransportException expected) {
// Expected // Expected
@@ -718,7 +733,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.removeTransportKeys(transaction, transportId, keySetId)); db.removeTransport(transaction, transportId));
fail(); fail();
} catch (NoSuchTransportException expected) { } catch (NoSuchTransportException expected) {
// Expected // Expected
@@ -732,6 +747,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} catch (NoSuchTransportException expected) { } catch (NoSuchTransportException expected) {
// Expected // Expected
} }
try {
db.transaction(false, transaction ->
db.setTransportKeysActive(transaction, transportId,
keySetId));
fail();
} catch (NoSuchTransportException expected) {
// Expected
}
} }
@Test @Test
@@ -751,7 +775,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.addHandshakeKeys(transaction, pendingContactId, db.addTransportKeys(transaction, pendingContactId,
createHandshakeKeys())); createHandshakeKeys()));
fail(); fail();
} catch (NoSuchPendingContactException expected) { } catch (NoSuchPendingContactException expected) {
@@ -1167,7 +1191,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
public void testTransportKeys() throws Exception { public void testTransportKeys() throws Exception {
TransportKeys transportKeys = createTransportKeys(); TransportKeys transportKeys = createTransportKeys();
TransportKeySet ks = TransportKeySet ks =
new TransportKeySet(keySetId, contactId, transportKeys); new TransportKeySet(keySetId, contactId, null, transportKeys);
Collection<TransportKeySet> keys = singletonList(ks); Collection<TransportKeySet> keys = singletonList(ks);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -1295,7 +1319,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}); });
} }
private HandshakeKeys createHandshakeKeys() { private TransportKeys createHandshakeKeys() {
SecretKey inPrevTagKey = getSecretKey(); SecretKey inPrevTagKey = getSecretKey();
SecretKey inPrevHeaderKey = getSecretKey(); SecretKey inPrevHeaderKey = getSecretKey();
IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey, IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
@@ -1312,7 +1336,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
SecretKey outCurrHeaderKey = getSecretKey(); SecretKey outCurrHeaderKey = getSecretKey();
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey, OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
2, 456, true); 2, 456, true);
return new HandshakeKeys(transportId, inPrev, inCurr, inNext, outCurr, return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr,
getSecretKey(), true); getSecretKey(), true);
} }

View File

@@ -3,6 +3,8 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
@@ -22,13 +24,10 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.sync.validation.MessageState; import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.transport.HandshakeKeySet;
import org.briarproject.bramble.api.transport.HandshakeKeySetId;
import org.briarproject.bramble.api.transport.HandshakeKeys;
import org.briarproject.bramble.api.transport.IncomingKeys; import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.OutgoingKeys; import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeySet; import org.briarproject.bramble.api.transport.TransportKeySet;
import org.briarproject.bramble.api.transport.TransportKeySetId;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.system.SystemClock; import org.briarproject.bramble.system.SystemClock;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
@@ -58,7 +57,6 @@ import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static org.briarproject.bramble.api.contact.PendingContactState.FAILED; import static org.briarproject.bramble.api.contact.PendingContactState.FAILED;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.api.db.Metadata.REMOVE; import static org.briarproject.bramble.api.db.Metadata.REMOVE;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
@@ -72,13 +70,14 @@ import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPAC
import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY; import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY;
import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS; import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getClientId; import static org.briarproject.bramble.test.TestUtils.getClientId;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getIdentity; import static org.briarproject.bramble.test.TestUtils.getIdentity;
import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getPendingContact; import static org.briarproject.bramble.test.TestUtils.getPendingContact;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
@@ -112,8 +111,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private final MessageId messageId; private final MessageId messageId;
private final TransportId transportId; private final TransportId transportId;
private final ContactId contactId; private final ContactId contactId;
private final TransportKeySetId keySetId, keySetId1; private final KeySetId keySetId, keySetId1;
private final HandshakeKeySetId handshakeKeySetId, handshakeKeySetId1;
private final PendingContact pendingContact; private final PendingContact pendingContact;
private final Random random = new Random(); private final Random random = new Random();
@@ -129,10 +127,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
messageId = message.getId(); messageId = message.getId();
transportId = getTransportId(); transportId = getTransportId();
contactId = new ContactId(1); contactId = new ContactId(1);
keySetId = new TransportKeySetId(1); keySetId = new KeySetId(1);
keySetId1 = new TransportKeySetId(2); keySetId1 = new KeySetId(2);
handshakeKeySetId = new HandshakeKeySetId(1);
handshakeKeySetId1 = new HandshakeKeySetId(2);
pendingContact = getPendingContact(); pendingContact = getPendingContact();
} }
@@ -701,14 +697,14 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
} }
} }
// Rotate the transport keys // Update the transport keys
TransportKeys rotated = createTransportKeys(timePeriod + 1, active); TransportKeys updated = createTransportKeys(timePeriod + 1, active);
TransportKeys rotated1 = TransportKeys updated1 =
createTransportKeys(timePeriod1 + 1, active); createTransportKeys(timePeriod1 + 1, active);
db.updateTransportKeys(txn, new TransportKeySet(keySetId, contactId, db.updateTransportKeys(txn, new TransportKeySet(keySetId, contactId,
rotated)); null, updated));
db.updateTransportKeys(txn, new TransportKeySet(keySetId1, contactId, db.updateTransportKeys(txn, new TransportKeySet(keySetId1, contactId,
rotated1)); null, updated1));
// Retrieve the transport keys again // Retrieve the transport keys again
allKeys = db.getTransportKeys(txn, transportId); allKeys = db.getTransportKeys(txn, transportId);
@@ -716,10 +712,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
for (TransportKeySet ks : allKeys) { for (TransportKeySet ks : allKeys) {
assertEquals(contactId, ks.getContactId()); assertEquals(contactId, ks.getContactId());
if (ks.getKeySetId().equals(keySetId)) { if (ks.getKeySetId().equals(keySetId)) {
assertKeysEquals(rotated, ks.getKeys()); assertKeysEquals(updated, ks.getKeys());
} else { } else {
assertEquals(keySetId1, ks.getKeySetId()); assertEquals(keySetId1, ks.getKeySetId());
assertKeysEquals(rotated1, ks.getKeys()); assertKeysEquals(updated1, ks.getKeys());
} }
} }
@@ -743,6 +739,14 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
actual.getNextIncomingKeys()); actual.getNextIncomingKeys());
assertKeysEquals(expected.getCurrentOutgoingKeys(), assertKeysEquals(expected.getCurrentOutgoingKeys(),
actual.getCurrentOutgoingKeys()); actual.getCurrentOutgoingKeys());
if (expected.isHandshakeMode()) {
assertTrue(actual.isHandshakeMode());
assertArrayEquals(expected.getRootKey().getBytes(),
actual.getRootKey().getBytes());
assertEquals(expected.isAlice(), actual.isAlice());
} else {
assertFalse(actual.isHandshakeMode());
}
} }
private void assertKeysEquals(IncomingKeys expected, IncomingKeys actual) { private void assertKeysEquals(IncomingKeys expected, IncomingKeys actual) {
@@ -771,154 +775,135 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
boolean alice = random.nextBoolean(); boolean alice = random.nextBoolean();
SecretKey rootKey = getSecretKey(); SecretKey rootKey = getSecretKey();
SecretKey rootKey1 = getSecretKey(); SecretKey rootKey1 = getSecretKey();
HandshakeKeys keys = createHandshakeKeys(timePeriod, rootKey, alice); TransportKeys keys = createHandshakeKeys(timePeriod, rootKey, alice);
HandshakeKeys keys1 = createHandshakeKeys(timePeriod1, rootKey1, alice); TransportKeys keys1 = createHandshakeKeys(timePeriod1, rootKey1, alice);
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Initially there should be no handshake keys in the database // Initially there should be no handshake keys in the database
assertEquals(emptyList(), db.getHandshakeKeys(txn, transportId)); assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
// Add the contact, the transport and the handshake keys // Add the contact, the transport and the handshake keys
db.addIdentity(txn, identity); db.addIdentity(txn, identity);
assertEquals(contactId, assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true)); db.addContact(txn, author, localAuthor.getId(), true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
assertEquals(handshakeKeySetId, assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
db.addHandshakeKeys(txn, contactId, keys)); assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1));
assertEquals(handshakeKeySetId1,
db.addHandshakeKeys(txn, contactId, keys1));
// Retrieve the handshake keys // Retrieve the handshake keys
Collection<HandshakeKeySet> allKeys = Collection<TransportKeySet> allKeys =
db.getHandshakeKeys(txn, transportId); db.getTransportKeys(txn, transportId);
assertEquals(2, allKeys.size()); assertEquals(2, allKeys.size());
for (HandshakeKeySet ks : allKeys) { for (TransportKeySet ks : allKeys) {
assertEquals(contactId, ks.getContactId()); assertEquals(contactId, ks.getContactId());
assertNull(ks.getPendingContactId()); assertNull(ks.getPendingContactId());
if (ks.getKeySetId().equals(handshakeKeySetId)) { if (ks.getKeySetId().equals(keySetId)) {
assertKeysEquals(keys, ks.getKeys()); assertKeysEquals(keys, ks.getKeys());
} else { } else {
assertEquals(handshakeKeySetId1, ks.getKeySetId()); assertEquals(keySetId1, ks.getKeySetId());
assertKeysEquals(keys1, ks.getKeys()); assertKeysEquals(keys1, ks.getKeys());
} }
} }
// Update the handshake keys // Update the handshake keys
HandshakeKeys updated = TransportKeys updated =
createHandshakeKeys(timePeriod + 1, rootKey, alice); createHandshakeKeys(timePeriod + 1, rootKey, alice);
HandshakeKeys updated1 = TransportKeys updated1 =
createHandshakeKeys(timePeriod1 + 1, rootKey1, alice); createHandshakeKeys(timePeriod1 + 1, rootKey1, alice);
db.updateHandshakeKeys(txn, new HandshakeKeySet(handshakeKeySetId, db.updateTransportKeys(txn, new TransportKeySet(keySetId, contactId,
contactId, updated)); null, updated));
db.updateHandshakeKeys(txn, new HandshakeKeySet(handshakeKeySetId1, db.updateTransportKeys(txn, new TransportKeySet(keySetId1, contactId,
contactId, updated1)); null, updated1));
// Retrieve the handshake keys again // Retrieve the handshake keys again
allKeys = db.getHandshakeKeys(txn, transportId); allKeys = db.getTransportKeys(txn, transportId);
assertEquals(2, allKeys.size()); assertEquals(2, allKeys.size());
for (HandshakeKeySet ks : allKeys) { for (TransportKeySet ks : allKeys) {
assertEquals(contactId, ks.getContactId()); assertEquals(contactId, ks.getContactId());
assertNull(ks.getPendingContactId()); assertNull(ks.getPendingContactId());
if (ks.getKeySetId().equals(handshakeKeySetId)) { if (ks.getKeySetId().equals(keySetId)) {
assertKeysEquals(updated, ks.getKeys()); assertKeysEquals(updated, ks.getKeys());
} else { } else {
assertEquals(handshakeKeySetId1, ks.getKeySetId()); assertEquals(keySetId1, ks.getKeySetId());
assertKeysEquals(updated1, ks.getKeys()); assertKeysEquals(updated1, ks.getKeys());
} }
} }
// Removing the contact should remove the handshake keys // Removing the contact should remove the handshake keys
db.removeContact(txn, contactId); db.removeContact(txn, contactId);
assertEquals(emptyList(), db.getHandshakeKeys(txn, transportId)); assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
} }
private void assertKeysEquals(HandshakeKeys expected,
HandshakeKeys actual) {
assertEquals(expected.getTransportId(), actual.getTransportId());
assertEquals(expected.getTimePeriod(), actual.getTimePeriod());
assertArrayEquals(expected.getRootKey().getBytes(),
actual.getRootKey().getBytes());
assertEquals(expected.isAlice(), actual.isAlice());
assertKeysEquals(expected.getPreviousIncomingKeys(),
actual.getPreviousIncomingKeys());
assertKeysEquals(expected.getCurrentIncomingKeys(),
actual.getCurrentIncomingKeys());
assertKeysEquals(expected.getNextIncomingKeys(),
actual.getNextIncomingKeys());
assertKeysEquals(expected.getCurrentOutgoingKeys(),
actual.getCurrentOutgoingKeys());
}
@Test @Test
public void testHandshakeKeysForPendingContact() throws Exception { public void testHandshakeKeysForPendingContact() throws Exception {
long timePeriod = 123, timePeriod1 = 234; long timePeriod = 123, timePeriod1 = 234;
boolean alice = random.nextBoolean(); boolean alice = random.nextBoolean();
SecretKey rootKey = getSecretKey(); SecretKey rootKey = getSecretKey();
SecretKey rootKey1 = getSecretKey(); SecretKey rootKey1 = getSecretKey();
HandshakeKeys keys = createHandshakeKeys(timePeriod, rootKey, alice); TransportKeys keys = createHandshakeKeys(timePeriod, rootKey, alice);
HandshakeKeys keys1 = createHandshakeKeys(timePeriod1, rootKey1, alice); TransportKeys keys1 = createHandshakeKeys(timePeriod1, rootKey1, alice);
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Initially there should be no handshake keys in the database // Initially there should be no handshake keys in the database
assertEquals(emptyList(), db.getHandshakeKeys(txn, transportId)); assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
// Add the pending contact, the transport and the handshake keys // Add the pending contact, the transport and the handshake keys
db.addPendingContact(txn, pendingContact); db.addPendingContact(txn, pendingContact);
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
assertEquals(handshakeKeySetId, db.addHandshakeKeys(txn, assertEquals(keySetId,
pendingContact.getId(), keys)); db.addTransportKeys(txn, pendingContact.getId(), keys));
assertEquals(handshakeKeySetId1, db.addHandshakeKeys(txn, assertEquals(keySetId1,
pendingContact.getId(), keys1)); db.addTransportKeys(txn, pendingContact.getId(), keys1));
// Retrieve the handshake keys // Retrieve the handshake keys
Collection<HandshakeKeySet> allKeys = Collection<TransportKeySet> allKeys =
db.getHandshakeKeys(txn, transportId); db.getTransportKeys(txn, transportId);
assertEquals(2, allKeys.size()); assertEquals(2, allKeys.size());
for (HandshakeKeySet ks : allKeys) { for (TransportKeySet ks : allKeys) {
assertNull(ks.getContactId()); assertNull(ks.getContactId());
assertEquals(pendingContact.getId(), ks.getPendingContactId()); assertEquals(pendingContact.getId(), ks.getPendingContactId());
if (ks.getKeySetId().equals(handshakeKeySetId)) { if (ks.getKeySetId().equals(keySetId)) {
assertKeysEquals(keys, ks.getKeys()); assertKeysEquals(keys, ks.getKeys());
} else { } else {
assertEquals(handshakeKeySetId1, ks.getKeySetId()); assertEquals(keySetId1, ks.getKeySetId());
assertKeysEquals(keys1, ks.getKeys()); assertKeysEquals(keys1, ks.getKeys());
} }
} }
// Update the handshake keys // Update the handshake keys
HandshakeKeys updated = TransportKeys updated =
createHandshakeKeys(timePeriod + 1, rootKey, alice); createHandshakeKeys(timePeriod + 1, rootKey, alice);
HandshakeKeys updated1 = TransportKeys updated1 =
createHandshakeKeys(timePeriod1 + 1, rootKey1, alice); createHandshakeKeys(timePeriod1 + 1, rootKey1, alice);
db.updateHandshakeKeys(txn, new HandshakeKeySet(handshakeKeySetId, db.updateTransportKeys(txn, new TransportKeySet(keySetId, null,
pendingContact.getId(), updated)); pendingContact.getId(), updated));
db.updateHandshakeKeys(txn, new HandshakeKeySet(handshakeKeySetId1, db.updateTransportKeys(txn, new TransportKeySet(keySetId1, null,
pendingContact.getId(), updated1)); pendingContact.getId(), updated1));
// Retrieve the handshake keys again // Retrieve the handshake keys again
allKeys = db.getHandshakeKeys(txn, transportId); allKeys = db.getTransportKeys(txn, transportId);
assertEquals(2, allKeys.size()); assertEquals(2, allKeys.size());
for (HandshakeKeySet ks : allKeys) { for (TransportKeySet ks : allKeys) {
assertNull(ks.getContactId()); assertNull(ks.getContactId());
assertEquals(pendingContact.getId(), ks.getPendingContactId()); assertEquals(pendingContact.getId(), ks.getPendingContactId());
if (ks.getKeySetId().equals(handshakeKeySetId)) { if (ks.getKeySetId().equals(keySetId)) {
assertKeysEquals(updated, ks.getKeys()); assertKeysEquals(updated, ks.getKeys());
} else { } else {
assertEquals(handshakeKeySetId1, ks.getKeySetId()); assertEquals(keySetId1, ks.getKeySetId());
assertKeysEquals(updated1, ks.getKeys()); assertKeysEquals(updated1, ks.getKeys());
} }
} }
// Removing the pending contact should remove the handshake keys // Removing the pending contact should remove the handshake keys
db.removePendingContact(txn, pendingContact.getId()); db.removePendingContact(txn, pendingContact.getId());
assertEquals(emptyList(), db.getHandshakeKeys(txn, transportId)); assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -971,7 +956,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
long timePeriod = 123; long timePeriod = 123;
SecretKey rootKey = getSecretKey(); SecretKey rootKey = getSecretKey();
boolean alice = random.nextBoolean(); boolean alice = random.nextBoolean();
HandshakeKeys keys = createHandshakeKeys(timePeriod, rootKey, alice); TransportKeys keys = createHandshakeKeys(timePeriod, rootKey, alice);
long streamCounter = keys.getCurrentOutgoingKeys().getStreamCounter(); long streamCounter = keys.getCurrentOutgoingKeys().getStreamCounter();
Database<Connection> db = open(false); Database<Connection> db = open(false);
@@ -982,20 +967,20 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(contactId, assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true)); db.addContact(txn, author, localAuthor.getId(), true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
assertEquals(handshakeKeySetId, assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
db.addHandshakeKeys(txn, contactId, keys));
// Increment the stream counter twice and retrieve the handshake keys // Increment the stream counter twice and retrieve the handshake keys
db.incrementStreamCounter(txn, transportId, handshakeKeySetId); db.incrementStreamCounter(txn, transportId, keySetId);
db.incrementStreamCounter(txn, transportId, handshakeKeySetId); db.incrementStreamCounter(txn, transportId, keySetId);
Collection<HandshakeKeySet> newKeys = Collection<TransportKeySet> newKeys =
db.getHandshakeKeys(txn, transportId); db.getTransportKeys(txn, transportId);
assertEquals(1, newKeys.size()); assertEquals(1, newKeys.size());
HandshakeKeySet ks = newKeys.iterator().next(); TransportKeySet ks = newKeys.iterator().next();
assertEquals(handshakeKeySetId, ks.getKeySetId()); assertEquals(keySetId, ks.getKeySetId());
assertEquals(contactId, ks.getContactId()); assertEquals(contactId, ks.getContactId());
HandshakeKeys k = ks.getKeys(); TransportKeys k = ks.getKeys();
assertEquals(transportId, k.getTransportId()); assertEquals(transportId, k.getTransportId());
assertNotNull(k.getRootKey());
assertArrayEquals(rootKey.getBytes(), k.getRootKey().getBytes()); assertArrayEquals(rootKey.getBytes(), k.getRootKey().getBytes());
assertEquals(alice, k.isAlice()); assertEquals(alice, k.isAlice());
OutgoingKeys outCurr = k.getCurrentOutgoingKeys(); OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
@@ -1064,7 +1049,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
long timePeriod = 123; long timePeriod = 123;
SecretKey rootKey = getSecretKey(); SecretKey rootKey = getSecretKey();
boolean alice = random.nextBoolean(); boolean alice = random.nextBoolean();
HandshakeKeys keys = createHandshakeKeys(timePeriod, rootKey, alice); TransportKeys keys = createHandshakeKeys(timePeriod, rootKey, alice);
long base = keys.getCurrentIncomingKeys().getWindowBase(); long base = keys.getCurrentIncomingKeys().getWindowBase();
byte[] bitmap = keys.getCurrentIncomingKeys().getWindowBitmap(); byte[] bitmap = keys.getCurrentIncomingKeys().getWindowBitmap();
@@ -1076,21 +1061,21 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(contactId, assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true)); db.addContact(txn, author, localAuthor.getId(), true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
assertEquals(handshakeKeySetId, assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
db.addHandshakeKeys(txn, contactId, keys));
// Update the reordering window and retrieve the handshake keys // Update the reordering window and retrieve the handshake keys
random.nextBytes(bitmap); random.nextBytes(bitmap);
db.setReorderingWindow(txn, handshakeKeySetId, transportId, timePeriod, db.setReorderingWindow(txn, keySetId, transportId, timePeriod,
base + 1, bitmap); base + 1, bitmap);
Collection<HandshakeKeySet> newKeys = Collection<TransportKeySet> newKeys =
db.getHandshakeKeys(txn, transportId); db.getTransportKeys(txn, transportId);
assertEquals(1, newKeys.size()); assertEquals(1, newKeys.size());
HandshakeKeySet ks = newKeys.iterator().next(); TransportKeySet ks = newKeys.iterator().next();
assertEquals(handshakeKeySetId, ks.getKeySetId()); assertEquals(keySetId, ks.getKeySetId());
assertEquals(contactId, ks.getContactId()); assertEquals(contactId, ks.getContactId());
HandshakeKeys k = ks.getKeys(); TransportKeys k = ks.getKeys();
assertEquals(transportId, k.getTransportId()); assertEquals(transportId, k.getTransportId());
assertNotNull(k.getRootKey());
assertArrayEquals(rootKey.getBytes(), k.getRootKey().getBytes()); assertArrayEquals(rootKey.getBytes(), k.getRootKey().getBytes());
assertEquals(alice, k.isAlice()); assertEquals(alice, k.isAlice());
IncomingKeys inCurr = k.getCurrentIncomingKeys(); IncomingKeys inCurr = k.getCurrentIncomingKeys();
@@ -2250,8 +2235,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Identity withoutKeys = Identity withoutKeys =
new Identity(localAuthor, null, null, identity.getTimeCreated()); new Identity(localAuthor, null, null, identity.getTimeCreated());
assertFalse(withoutKeys.hasHandshakeKeyPair()); assertFalse(withoutKeys.hasHandshakeKeyPair());
byte[] publicKey = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES); PublicKey publicKey = getAgreementPublicKey();
byte[] privateKey = getRandomBytes(123); PrivateKey privateKey = getAgreementPrivateKey();
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
@@ -2262,8 +2247,12 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.setHandshakeKeyPair(txn, localAuthor.getId(), publicKey, privateKey); db.setHandshakeKeyPair(txn, localAuthor.getId(), publicKey, privateKey);
retrieved = db.getIdentity(txn, localAuthor.getId()); retrieved = db.getIdentity(txn, localAuthor.getId());
assertTrue(retrieved.hasHandshakeKeyPair()); assertTrue(retrieved.hasHandshakeKeyPair());
assertArrayEquals(publicKey, retrieved.getHandshakePublicKey()); PublicKey handshakePub = retrieved.getHandshakePublicKey();
assertArrayEquals(privateKey, retrieved.getHandshakePrivateKey()); assertNotNull(handshakePub);
assertArrayEquals(publicKey.getEncoded(), handshakePub.getEncoded());
PrivateKey handshakePriv = retrieved.getHandshakePrivateKey();
assertNotNull(handshakePriv);
assertArrayEquals(privateKey.getEncoded(), handshakePriv.getEncoded());
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -2302,7 +2291,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr); return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
} }
private HandshakeKeys createHandshakeKeys(long timePeriod, private TransportKeys createHandshakeKeys(long timePeriod,
SecretKey rootKey, boolean alice) { SecretKey rootKey, boolean alice) {
SecretKey inPrevTagKey = getSecretKey(); SecretKey inPrevTagKey = getSecretKey();
SecretKey inPrevHeaderKey = getSecretKey(); SecretKey inPrevHeaderKey = getSecretKey();
@@ -2320,7 +2309,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
SecretKey outCurrHeaderKey = getSecretKey(); SecretKey outCurrHeaderKey = getSecretKey();
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey, OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
timePeriod, 456, true); timePeriod, 456, true);
return new HandshakeKeys(transportId, inPrev, inCurr, inNext, outCurr, return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr,
rootKey, alice); rootKey, alice);
} }

View File

@@ -18,6 +18,8 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
import static org.briarproject.bramble.test.TestUtils.getIdentity; import static org.briarproject.bramble.test.TestUtils.getIdentity;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -28,21 +30,16 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
private final AuthorFactory authorFactory = private final AuthorFactory authorFactory =
context.mock(AuthorFactory.class); context.mock(AuthorFactory.class);
private final Clock clock = context.mock(Clock.class); private final Clock clock = context.mock(Clock.class);
private final PublicKey handshakePublicKey = context.mock(PublicKey.class);
private final PrivateKey handshakePrivateKey =
context.mock(PrivateKey.class);
private final Transaction txn = new Transaction(null, false); private final Transaction txn = new Transaction(null, false);
private final Identity identityWithKeys = getIdentity(); private final Identity identityWithKeys = getIdentity();
private final LocalAuthor localAuthor = identityWithKeys.getLocalAuthor(); private final LocalAuthor localAuthor = identityWithKeys.getLocalAuthor();
private final Identity identityWithoutKeys = new Identity(localAuthor, private final Identity identityWithoutKeys = new Identity(localAuthor,
null, null, identityWithKeys.getTimeCreated()); null, null, identityWithKeys.getTimeCreated());
private final PublicKey handshakePublicKey = getAgreementPublicKey();
private final PrivateKey handshakePrivateKey = getAgreementPrivateKey();
private final KeyPair handshakeKeyPair = private final KeyPair handshakeKeyPair =
new KeyPair(handshakePublicKey, handshakePrivateKey); new KeyPair(handshakePublicKey, handshakePrivateKey);
private final byte[] handshakePublicKeyBytes =
identityWithKeys.getHandshakePublicKey();
private final byte[] handshakePrivateKeyBytes =
identityWithKeys.getHandshakePrivateKey();
private IdentityManagerImpl identityManager; private IdentityManagerImpl identityManager;
@@ -69,12 +66,8 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
will(returnValue(singletonList(identityWithoutKeys))); will(returnValue(singletonList(identityWithoutKeys)));
oneOf(crypto).generateAgreementKeyPair(); oneOf(crypto).generateAgreementKeyPair();
will(returnValue(handshakeKeyPair)); will(returnValue(handshakeKeyPair));
oneOf(handshakePublicKey).getEncoded();
will(returnValue(handshakePublicKeyBytes));
oneOf(handshakePrivateKey).getEncoded();
will(returnValue(handshakePrivateKeyBytes));
oneOf(db).setHandshakeKeyPair(txn, localAuthor.getId(), oneOf(db).setHandshakeKeyPair(txn, localAuthor.getId(),
handshakePublicKeyBytes, handshakePrivateKeyBytes); handshakePublicKey, handshakePrivateKey);
}}); }});
identityManager.onDatabaseOpened(txn); identityManager.onDatabaseOpened(txn);
@@ -104,10 +97,6 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
will(returnValue(singletonList(identityWithoutKeys))); will(returnValue(singletonList(identityWithoutKeys)));
oneOf(crypto).generateAgreementKeyPair(); oneOf(crypto).generateAgreementKeyPair();
will(returnValue(handshakeKeyPair)); will(returnValue(handshakeKeyPair));
oneOf(handshakePublicKey).getEncoded();
will(returnValue(handshakePublicKeyBytes));
oneOf(handshakePrivateKey).getEncoded();
will(returnValue(handshakePrivateKeyBytes));
}}); }});
assertEquals(localAuthor, identityManager.getLocalAuthor()); assertEquals(localAuthor, identityManager.getLocalAuthor());

View File

@@ -16,10 +16,13 @@ import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_KEY_LABEL; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_KEY_LABEL;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
@@ -34,21 +37,17 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
setImposteriser(ClassImposteriser.INSTANCE); setImposteriser(ClassImposteriser.INSTANCE);
}}; }};
private final PublicKey alicePubKey = private final PublicKey alicePubKey = getAgreementPublicKey();
context.mock(PublicKey.class, "alice");
private final byte[] alicePubKeyBytes = getRandomBytes(32);
private final byte[] aliceCommit = getRandomBytes(COMMIT_LENGTH); private final byte[] aliceCommit = getRandomBytes(COMMIT_LENGTH);
private final byte[] alicePayload = getRandomBytes(COMMIT_LENGTH + 8); private final byte[] alicePayload = getRandomBytes(COMMIT_LENGTH + 8);
private final byte[] aliceConfirm = getRandomBytes(SecretKey.LENGTH); private final byte[] aliceConfirm = getRandomBytes(SecretKey.LENGTH);
private final PublicKey bobPubKey = context.mock(PublicKey.class, "bob"); private final PublicKey bobPubKey = getAgreementPublicKey();
private final byte[] bobPubKeyBytes = getRandomBytes(32);
private final byte[] bobCommit = getRandomBytes(COMMIT_LENGTH); private final byte[] bobCommit = getRandomBytes(COMMIT_LENGTH);
private final byte[] bobPayload = getRandomBytes(COMMIT_LENGTH + 19); private final byte[] bobPayload = getRandomBytes(COMMIT_LENGTH + 19);
private final byte[] bobConfirm = getRandomBytes(SecretKey.LENGTH); private final byte[] bobConfirm = getRandomBytes(SecretKey.LENGTH);
private final PublicKey badPubKey = context.mock(PublicKey.class, "bad"); private final PublicKey badPubKey = getAgreementPublicKey();
private final byte[] badPubKeyBytes = getRandomBytes(32);
private final byte[] badCommit = getRandomBytes(COMMIT_LENGTH); private final byte[] badCommit = getRandomBytes(COMMIT_LENGTH);
private final byte[] badConfirm = getRandomBytes(SecretKey.LENGTH); private final byte[] badConfirm = getRandomBytes(SecretKey.LENGTH);
@@ -64,15 +63,13 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
PayloadEncoder payloadEncoder; PayloadEncoder payloadEncoder;
@Mock @Mock
KeyAgreementTransport transport; KeyAgreementTransport transport;
@Mock
PublicKey ourPubKey;
@Test @Test
public void testAliceProtocol() throws Exception { public void testAliceProtocol() throws Exception {
// set up // set up
Payload theirPayload = new Payload(bobCommit, null); Payload theirPayload = new Payload(bobCommit, emptyList());
Payload ourPayload = new Payload(aliceCommit, null); Payload ourPayload = new Payload(aliceCommit, emptyList());
KeyPair ourKeyPair = new KeyPair(ourPubKey, null); KeyPair ourKeyPair = new KeyPair(alicePubKey, getAgreementPrivateKey());
SecretKey sharedSecret = getSecretKey(); SecretKey sharedSecret = getSecretKey();
SecretKey masterKey = getSecretKey(); SecretKey masterKey = getSecretKey();
@@ -87,24 +84,18 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
will(returnValue(alicePayload)); will(returnValue(alicePayload));
allowing(payloadEncoder).encode(theirPayload); allowing(payloadEncoder).encode(theirPayload);
will(returnValue(bobPayload)); will(returnValue(bobPayload));
allowing(ourPubKey).getEncoded();
will(returnValue(alicePubKeyBytes));
allowing(crypto).getAgreementKeyParser(); allowing(crypto).getAgreementKeyParser();
will(returnValue(keyParser)); will(returnValue(keyParser));
allowing(alicePubKey).getEncoded();
will(returnValue(alicePubKeyBytes));
allowing(bobPubKey).getEncoded();
will(returnValue(bobPubKeyBytes));
// Alice sends her public key // Alice sends her public key
oneOf(transport).sendKey(alicePubKeyBytes); oneOf(transport).sendKey(alicePubKey.getEncoded());
// Alice receives Bob's public key // Alice receives Bob's public key
oneOf(callbacks).connectionWaiting(); oneOf(callbacks).connectionWaiting();
oneOf(transport).receiveKey(); oneOf(transport).receiveKey();
will(returnValue(bobPubKeyBytes)); will(returnValue(bobPubKey.getEncoded()));
oneOf(callbacks).initialRecordReceived(); oneOf(callbacks).initialRecordReceived();
oneOf(keyParser).parsePublicKey(bobPubKeyBytes); oneOf(keyParser).parsePublicKey(bobPubKey.getEncoded());
will(returnValue(bobPubKey)); will(returnValue(bobPubKey));
// Alice verifies Bob's public key // Alice verifies Bob's public key
@@ -114,7 +105,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
// Alice computes shared secret // Alice computes shared secret
oneOf(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, bobPubKey, oneOf(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, bobPubKey,
ourKeyPair, new byte[] {PROTOCOL_VERSION}, ourKeyPair, new byte[] {PROTOCOL_VERSION},
alicePubKeyBytes, bobPubKeyBytes); alicePubKey.getEncoded(), bobPubKey.getEncoded());
will(returnValue(sharedSecret)); will(returnValue(sharedSecret));
// Alice sends her confirmation record // Alice sends her confirmation record
@@ -146,9 +137,9 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
@Test @Test
public void testBobProtocol() throws Exception { public void testBobProtocol() throws Exception {
// set up // set up
Payload theirPayload = new Payload(aliceCommit, null); Payload theirPayload = new Payload(aliceCommit, emptyList());
Payload ourPayload = new Payload(bobCommit, null); Payload ourPayload = new Payload(bobCommit, emptyList());
KeyPair ourKeyPair = new KeyPair(ourPubKey, null); KeyPair ourKeyPair = new KeyPair(bobPubKey, getAgreementPrivateKey());
SecretKey sharedSecret = getSecretKey(); SecretKey sharedSecret = getSecretKey();
SecretKey masterKey = getSecretKey(); SecretKey masterKey = getSecretKey();
@@ -163,20 +154,14 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
will(returnValue(bobPayload)); will(returnValue(bobPayload));
allowing(payloadEncoder).encode(theirPayload); allowing(payloadEncoder).encode(theirPayload);
will(returnValue(alicePayload)); will(returnValue(alicePayload));
allowing(ourPubKey).getEncoded();
will(returnValue(bobPubKeyBytes));
allowing(crypto).getAgreementKeyParser(); allowing(crypto).getAgreementKeyParser();
will(returnValue(keyParser)); will(returnValue(keyParser));
allowing(alicePubKey).getEncoded();
will(returnValue(alicePubKeyBytes));
allowing(bobPubKey).getEncoded();
will(returnValue(bobPubKeyBytes));
// Bob receives Alice's public key // Bob receives Alice's public key
oneOf(transport).receiveKey(); oneOf(transport).receiveKey();
will(returnValue(alicePubKeyBytes)); will(returnValue(alicePubKey.getEncoded()));
oneOf(callbacks).initialRecordReceived(); oneOf(callbacks).initialRecordReceived();
oneOf(keyParser).parsePublicKey(alicePubKeyBytes); oneOf(keyParser).parsePublicKey(alicePubKey.getEncoded());
will(returnValue(alicePubKey)); will(returnValue(alicePubKey));
// Bob verifies Alice's public key // Bob verifies Alice's public key
@@ -184,12 +169,12 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
will(returnValue(aliceCommit)); will(returnValue(aliceCommit));
// Bob sends his public key // Bob sends his public key
oneOf(transport).sendKey(bobPubKeyBytes); oneOf(transport).sendKey(bobPubKey.getEncoded());
// Bob computes shared secret // Bob computes shared secret
oneOf(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, alicePubKey, oneOf(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, alicePubKey,
ourKeyPair, new byte[] {PROTOCOL_VERSION}, ourKeyPair, new byte[] {PROTOCOL_VERSION},
alicePubKeyBytes, bobPubKeyBytes); alicePubKey.getEncoded(), bobPubKey.getEncoded());
will(returnValue(sharedSecret)); will(returnValue(sharedSecret));
// Bob receives Alices's confirmation record // Bob receives Alices's confirmation record
@@ -221,9 +206,9 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
@Test(expected = AbortException.class) @Test(expected = AbortException.class)
public void testAliceProtocolAbortOnBadKey() throws Exception { public void testAliceProtocolAbortOnBadKey() throws Exception {
// set up // set up
Payload theirPayload = new Payload(bobCommit, null); Payload theirPayload = new Payload(bobCommit, emptyList());
Payload ourPayload = new Payload(aliceCommit, null); Payload ourPayload = new Payload(aliceCommit, emptyList());
KeyPair ourKeyPair = new KeyPair(ourPubKey, null); KeyPair ourKeyPair = new KeyPair(alicePubKey, getAgreementPrivateKey());
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks, KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
crypto, keyAgreementCrypto, payloadEncoder, transport, crypto, keyAgreementCrypto, payloadEncoder, transport,
@@ -232,20 +217,18 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
// expectations // expectations
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Helpers // Helpers
allowing(ourPubKey).getEncoded();
will(returnValue(alicePubKeyBytes));
allowing(crypto).getAgreementKeyParser(); allowing(crypto).getAgreementKeyParser();
will(returnValue(keyParser)); will(returnValue(keyParser));
// Alice sends her public key // Alice sends her public key
oneOf(transport).sendKey(alicePubKeyBytes); oneOf(transport).sendKey(alicePubKey.getEncoded());
// Alice receives a bad public key // Alice receives a bad public key
oneOf(callbacks).connectionWaiting(); oneOf(callbacks).connectionWaiting();
oneOf(transport).receiveKey(); oneOf(transport).receiveKey();
will(returnValue(badPubKeyBytes)); will(returnValue(badPubKey.getEncoded()));
oneOf(callbacks).initialRecordReceived(); oneOf(callbacks).initialRecordReceived();
oneOf(keyParser).parsePublicKey(badPubKeyBytes); oneOf(keyParser).parsePublicKey(badPubKey.getEncoded());
will(returnValue(badPubKey)); will(returnValue(badPubKey));
// Alice verifies Bob's public key // Alice verifies Bob's public key
@@ -258,7 +241,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
// Alice never computes shared secret // Alice never computes shared secret
never(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, badPubKey, never(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, badPubKey,
ourKeyPair, new byte[] {PROTOCOL_VERSION}, ourKeyPair, new byte[] {PROTOCOL_VERSION},
alicePubKeyBytes, bobPubKeyBytes); alicePubKey.getEncoded(), bobPubKey.getEncoded());
}}); }});
// execute // execute
@@ -268,9 +251,9 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
@Test(expected = AbortException.class) @Test(expected = AbortException.class)
public void testBobProtocolAbortOnBadKey() throws Exception { public void testBobProtocolAbortOnBadKey() throws Exception {
// set up // set up
Payload theirPayload = new Payload(aliceCommit, null); Payload theirPayload = new Payload(aliceCommit, emptyList());
Payload ourPayload = new Payload(bobCommit, null); Payload ourPayload = new Payload(bobCommit, emptyList());
KeyPair ourKeyPair = new KeyPair(ourPubKey, null); KeyPair ourKeyPair = new KeyPair(bobPubKey, getAgreementPrivateKey());
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks, KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
crypto, keyAgreementCrypto, payloadEncoder, transport, crypto, keyAgreementCrypto, payloadEncoder, transport,
@@ -279,16 +262,14 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
// expectations // expectations
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Helpers // Helpers
allowing(ourPubKey).getEncoded();
will(returnValue(bobPubKeyBytes));
allowing(crypto).getAgreementKeyParser(); allowing(crypto).getAgreementKeyParser();
will(returnValue(keyParser)); will(returnValue(keyParser));
// Bob receives a bad public key // Bob receives a bad public key
oneOf(transport).receiveKey(); oneOf(transport).receiveKey();
will(returnValue(badPubKeyBytes)); will(returnValue(badPubKey.getEncoded()));
oneOf(callbacks).initialRecordReceived(); oneOf(callbacks).initialRecordReceived();
oneOf(keyParser).parsePublicKey(badPubKeyBytes); oneOf(keyParser).parsePublicKey(badPubKey.getEncoded());
will(returnValue(badPubKey)); will(returnValue(badPubKey));
// Bob verifies Alice's public key // Bob verifies Alice's public key
@@ -299,7 +280,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
oneOf(transport).sendAbort(false); oneOf(transport).sendAbort(false);
// Bob never sends his public key // Bob never sends his public key
never(transport).sendKey(bobPubKeyBytes); never(transport).sendKey(bobPubKey.getEncoded());
}}); }});
// execute // execute
@@ -309,9 +290,9 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
@Test(expected = AbortException.class) @Test(expected = AbortException.class)
public void testAliceProtocolAbortOnBadConfirm() throws Exception { public void testAliceProtocolAbortOnBadConfirm() throws Exception {
// set up // set up
Payload theirPayload = new Payload(bobCommit, null); Payload theirPayload = new Payload(bobCommit, emptyList());
Payload ourPayload = new Payload(aliceCommit, null); Payload ourPayload = new Payload(aliceCommit, emptyList());
KeyPair ourKeyPair = new KeyPair(ourPubKey, null); KeyPair ourKeyPair = new KeyPair(alicePubKey, getAgreementPrivateKey());
SecretKey sharedSecret = getSecretKey(); SecretKey sharedSecret = getSecretKey();
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks, KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
@@ -325,22 +306,18 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
will(returnValue(alicePayload)); will(returnValue(alicePayload));
allowing(payloadEncoder).encode(theirPayload); allowing(payloadEncoder).encode(theirPayload);
will(returnValue(bobPayload)); will(returnValue(bobPayload));
allowing(ourPubKey).getEncoded();
will(returnValue(alicePubKeyBytes));
allowing(crypto).getAgreementKeyParser(); allowing(crypto).getAgreementKeyParser();
will(returnValue(keyParser)); will(returnValue(keyParser));
allowing(bobPubKey).getEncoded();
will(returnValue(bobPubKeyBytes));
// Alice sends her public key // Alice sends her public key
oneOf(transport).sendKey(alicePubKeyBytes); oneOf(transport).sendKey(alicePubKey.getEncoded());
// Alice receives Bob's public key // Alice receives Bob's public key
oneOf(callbacks).connectionWaiting(); oneOf(callbacks).connectionWaiting();
oneOf(transport).receiveKey(); oneOf(transport).receiveKey();
will(returnValue(bobPubKeyBytes)); will(returnValue(bobPubKey.getEncoded()));
oneOf(callbacks).initialRecordReceived(); oneOf(callbacks).initialRecordReceived();
oneOf(keyParser).parsePublicKey(bobPubKeyBytes); oneOf(keyParser).parsePublicKey(bobPubKey.getEncoded());
will(returnValue(bobPubKey)); will(returnValue(bobPubKey));
// Alice verifies Bob's public key // Alice verifies Bob's public key
@@ -350,7 +327,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
// Alice computes shared secret // Alice computes shared secret
oneOf(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, bobPubKey, oneOf(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, bobPubKey,
ourKeyPair, new byte[] {PROTOCOL_VERSION}, ourKeyPair, new byte[] {PROTOCOL_VERSION},
alicePubKeyBytes, bobPubKeyBytes); alicePubKey.getEncoded(), bobPubKey.getEncoded());
will(returnValue(sharedSecret)); will(returnValue(sharedSecret));
// Alice sends her confirmation record // Alice sends her confirmation record
@@ -384,9 +361,9 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
@Test(expected = AbortException.class) @Test(expected = AbortException.class)
public void testBobProtocolAbortOnBadConfirm() throws Exception { public void testBobProtocolAbortOnBadConfirm() throws Exception {
// set up // set up
Payload theirPayload = new Payload(aliceCommit, null); Payload theirPayload = new Payload(aliceCommit, emptyList());
Payload ourPayload = new Payload(bobCommit, null); Payload ourPayload = new Payload(bobCommit, emptyList());
KeyPair ourKeyPair = new KeyPair(ourPubKey, null); KeyPair ourKeyPair = new KeyPair(bobPubKey, getAgreementPrivateKey());
SecretKey sharedSecret = getSecretKey(); SecretKey sharedSecret = getSecretKey();
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks, KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
@@ -400,18 +377,14 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
will(returnValue(bobPayload)); will(returnValue(bobPayload));
allowing(payloadEncoder).encode(theirPayload); allowing(payloadEncoder).encode(theirPayload);
will(returnValue(alicePayload)); will(returnValue(alicePayload));
allowing(ourPubKey).getEncoded();
will(returnValue(bobPubKeyBytes));
allowing(crypto).getAgreementKeyParser(); allowing(crypto).getAgreementKeyParser();
will(returnValue(keyParser)); will(returnValue(keyParser));
allowing(alicePubKey).getEncoded();
will(returnValue(alicePubKeyBytes));
// Bob receives Alice's public key // Bob receives Alice's public key
oneOf(transport).receiveKey(); oneOf(transport).receiveKey();
will(returnValue(alicePubKeyBytes)); will(returnValue(alicePubKey.getEncoded()));
oneOf(callbacks).initialRecordReceived(); oneOf(callbacks).initialRecordReceived();
oneOf(keyParser).parsePublicKey(alicePubKeyBytes); oneOf(keyParser).parsePublicKey(alicePubKey.getEncoded());
will(returnValue(alicePubKey)); will(returnValue(alicePubKey));
// Bob verifies Alice's public key // Bob verifies Alice's public key
@@ -419,12 +392,12 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
will(returnValue(aliceCommit)); will(returnValue(aliceCommit));
// Bob sends his public key // Bob sends his public key
oneOf(transport).sendKey(bobPubKeyBytes); oneOf(transport).sendKey(bobPubKey.getEncoded());
// Bob computes shared secret // Bob computes shared secret
oneOf(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, alicePubKey, oneOf(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, alicePubKey,
ourKeyPair, new byte[] {PROTOCOL_VERSION}, ourKeyPair, new byte[] {PROTOCOL_VERSION},
alicePubKeyBytes, bobPubKeyBytes); alicePubKey.getEncoded(), bobPubKey.getEncoded());
will(returnValue(sharedSecret)); will(returnValue(sharedSecret));
// Bob receives a bad confirmation record // Bob receives a bad confirmation record

View File

@@ -101,8 +101,8 @@ public class SyncIntegrationTest extends BrambleTestCase {
private byte[] write() throws Exception { private byte[] write() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamContext ctx = new StreamContext(contactId, transportId, tagKey, StreamContext ctx = new StreamContext(contactId, null, transportId,
headerKey, streamNumber); tagKey, headerKey, streamNumber, false);
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(out, StreamWriter streamWriter = streamWriterFactory.createStreamWriter(out,
ctx); ctx);
SyncRecordWriter recordWriter = recordWriterFactory.createRecordWriter( SyncRecordWriter recordWriter = recordWriterFactory.createRecordWriter(
@@ -131,8 +131,8 @@ public class SyncIntegrationTest extends BrambleTestCase {
assertArrayEquals(expectedTag, tag); assertArrayEquals(expectedTag, tag);
// Create the readers // Create the readers
StreamContext ctx = new StreamContext(contactId, transportId, tagKey, StreamContext ctx = new StreamContext(contactId, null, transportId,
headerKey, streamNumber); tagKey, headerKey, streamNumber, false);
InputStream streamReader = streamReaderFactory.createStreamReader(in, InputStream streamReader = streamReaderFactory.createStreamReader(in,
ctx); ctx);
SyncRecordReader recordReader = recordReaderFactory.createRecordReader( SyncRecordReader recordReader = recordReaderFactory.createRecordReader(

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.transport; package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -8,8 +9,8 @@ import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.plugin.PluginConfig; import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.StreamContext; import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.TransportKeySetId;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations; import org.jmock.Expectations;
@@ -26,6 +27,7 @@ import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH; import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getContactId; import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -43,11 +45,17 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
private final DeterministicExecutor executor = new DeterministicExecutor(); private final DeterministicExecutor executor = new DeterministicExecutor();
private final Transaction txn = new Transaction(null, false); private final Transaction txn = new Transaction(null, false);
private final ContactId contactId = getContactId(); private final ContactId contactId = getContactId();
private final TransportKeySetId keySetId = new TransportKeySetId(345); private final PendingContactId pendingContactId =
new PendingContactId(getRandomId());
private final KeySetId keySetId = new KeySetId(345);
private final TransportId transportId = getTransportId(); private final TransportId transportId = getTransportId();
private final TransportId unknownTransportId = getTransportId(); private final TransportId unknownTransportId = getTransportId();
private final StreamContext streamContext = new StreamContext(contactId, private final StreamContext contactStreamContext =
transportId, getSecretKey(), getSecretKey(), 1); new StreamContext(contactId, null, transportId, getSecretKey(),
getSecretKey(), 1, false);
private final StreamContext pendingContactStreamContext =
new StreamContext(null, pendingContactId, transportId,
getSecretKey(), getSecretKey(), 1, true);
private final byte[] tag = getRandomBytes(TAG_LENGTH); private final byte[] tag = getRandomBytes(TAG_LENGTH);
private final Random random = new Random(); private final Random random = new Random();
@@ -83,41 +91,94 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testAddContact() throws Exception { public void testAddContactWithRotationModeKeys() throws Exception {
SecretKey secretKey = getSecretKey(); SecretKey secretKey = getSecretKey();
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
boolean alice = random.nextBoolean(); boolean alice = random.nextBoolean();
boolean active = random.nextBoolean(); boolean active = random.nextBoolean();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(transportKeyManager).addContact(txn, contactId, secretKey, oneOf(transportKeyManager).addContactWithRotationKeys(txn,
timestamp, alice, active); contactId, secretKey, timestamp, alice, active);
will(returnValue(keySetId)); will(returnValue(keySetId));
}}); }});
Map<TransportId, TransportKeySetId> ids = keyManager.addContact(txn, Map<TransportId, KeySetId> ids = keyManager.addContactWithRotationKeys(
contactId, secretKey, timestamp, alice, active); txn, contactId, secretKey, timestamp, alice, active);
assertEquals(singletonMap(transportId, keySetId), ids); assertEquals(singletonMap(transportId, keySetId), ids);
} }
@Test @Test
public void testGetStreamContextForUnknownTransport() throws Exception { public void testAddContactWithHandshakeModeKeys() throws Exception {
SecretKey secretKey = getSecretKey();
boolean alice = random.nextBoolean();
context.checking(new Expectations() {{
oneOf(transportKeyManager).addContactWithHandshakeKeys(
txn, contactId, secretKey, alice);
will(returnValue(keySetId));
}});
Map<TransportId, KeySetId> ids = keyManager.addContactWithHandshakeKeys(
txn, contactId, secretKey, alice);
assertEquals(singletonMap(transportId, keySetId), ids);
}
@Test
public void testAddPendingContact() throws Exception {
SecretKey secretKey = getSecretKey();
boolean alice = random.nextBoolean();
context.checking(new Expectations() {{
oneOf(transportKeyManager).addPendingContact(txn, pendingContactId,
secretKey, alice);
will(returnValue(keySetId));
}});
Map<TransportId, KeySetId> ids = keyManager.addPendingContact(txn,
pendingContactId, secretKey, alice);
assertEquals(singletonMap(transportId, keySetId), ids);
}
@Test
public void testGetStreamContextForContactWithUnknownTransport()
throws Exception {
assertNull(keyManager.getStreamContext(contactId, unknownTransportId)); assertNull(keyManager.getStreamContext(contactId, unknownTransportId));
} }
@Test
public void testGetStreamContextForPendingContactWithUnknownTransport()
throws Exception {
assertNull(keyManager.getStreamContext(pendingContactId,
unknownTransportId));
}
@Test @Test
public void testGetStreamContextForContact() throws Exception { public void testGetStreamContextForContact() throws Exception {
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithNullableResult(with(false), oneOf(db).transactionWithNullableResult(with(false),
withNullableDbCallable(txn)); withNullableDbCallable(txn));
oneOf(transportKeyManager).getStreamContext(txn, contactId); oneOf(transportKeyManager).getStreamContext(txn, contactId);
will(returnValue(streamContext)); will(returnValue(contactStreamContext));
}}); }});
assertEquals(streamContext, assertEquals(contactStreamContext,
keyManager.getStreamContext(contactId, transportId)); keyManager.getStreamContext(contactId, transportId));
} }
@Test
public void testGetStreamContextForPendingContact() throws Exception {
context.checking(new DbExpectations() {{
oneOf(db).transactionWithNullableResult(with(false),
withNullableDbCallable(txn));
oneOf(transportKeyManager).getStreamContext(txn, pendingContactId);
will(returnValue(pendingContactStreamContext));
}});
assertEquals(pendingContactStreamContext,
keyManager.getStreamContext(pendingContactId, transportId));
}
@Test @Test
public void testGetStreamContextForTagAndUnknownTransport() public void testGetStreamContextForTagAndUnknownTransport()
throws Exception { throws Exception {
@@ -130,10 +191,10 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
oneOf(db).transactionWithNullableResult(with(false), oneOf(db).transactionWithNullableResult(with(false),
withNullableDbCallable(txn)); withNullableDbCallable(txn));
oneOf(transportKeyManager).getStreamContext(txn, tag); oneOf(transportKeyManager).getStreamContext(txn, tag);
will(returnValue(streamContext)); will(returnValue(contactStreamContext));
}}); }});
assertEquals(streamContext, assertEquals(contactStreamContext,
keyManager.getStreamContext(transportId, tag)); keyManager.getStreamContext(transportId, tag));
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.transport; package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto; import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -8,10 +9,10 @@ import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.transport.IncomingKeys; import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.OutgoingKeys; import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.StreamContext; import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.TransportKeySet; import org.briarproject.bramble.api.transport.TransportKeySet;
import org.briarproject.bramble.api.transport.TransportKeySetId;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
@@ -37,6 +38,7 @@ import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE; import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH; import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getContactId; import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED; import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
@@ -61,22 +63,26 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
private final long timePeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE; private final long timePeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
private final ContactId contactId = getContactId(); private final ContactId contactId = getContactId();
private final ContactId contactId1 = getContactId(); private final ContactId contactId1 = getContactId();
private final TransportKeySetId keySetId = new TransportKeySetId(345); private final PendingContactId pendingContactId =
private final TransportKeySetId keySetId1 = new TransportKeySetId(456); new PendingContactId(getRandomId());
private final KeySetId keySetId = new KeySetId(345);
private final KeySetId keySetId1 = new KeySetId(456);
private final SecretKey tagKey = getSecretKey(); private final SecretKey tagKey = getSecretKey();
private final SecretKey headerKey = getSecretKey(); private final SecretKey headerKey = getSecretKey();
private final SecretKey rootKey = getSecretKey(); private final SecretKey rootKey = getSecretKey();
private final Random random = new Random(); private final Random random = new Random();
@Test @Test
public void testKeysAreRotatedAtStartup() throws Exception { public void testKeysAreUpdatedAtStartup() throws Exception {
TransportKeys shouldRotate = createTransportKeys(900, 0, true); boolean active = random.nextBoolean();
TransportKeys shouldNotRotate = createTransportKeys(1000, 0, true); TransportKeys shouldUpdate = createTransportKeys(900, 0, active);
TransportKeys shouldNotUpdate = createTransportKeys(1000, 0, active);
Collection<TransportKeySet> loaded = asList( Collection<TransportKeySet> loaded = asList(
new TransportKeySet(keySetId, contactId, shouldRotate), new TransportKeySet(keySetId, contactId, null, shouldUpdate),
new TransportKeySet(keySetId1, contactId1, shouldNotRotate) new TransportKeySet(keySetId1, contactId1, null,
shouldNotUpdate)
); );
TransportKeys rotated = createTransportKeys(1000, 0, true); TransportKeys updated = createTransportKeys(1000, 0, active);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -86,11 +92,11 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
// Load the transport keys // Load the transport keys
oneOf(db).getTransportKeys(txn, transportId); oneOf(db).getTransportKeys(txn, transportId);
will(returnValue(loaded)); will(returnValue(loaded));
// Rotate the transport keys // Update the transport keys
oneOf(transportCrypto).rotateTransportKeys(shouldRotate, 1000); oneOf(transportCrypto).updateTransportKeys(shouldUpdate, 1000);
will(returnValue(rotated)); will(returnValue(updated));
oneOf(transportCrypto).rotateTransportKeys(shouldNotRotate, 1000); oneOf(transportCrypto).updateTransportKeys(shouldNotUpdate, 1000);
will(returnValue(shouldNotRotate)); will(returnValue(shouldNotUpdate));
// Encode the tags (3 sets per contact) // Encode the tags (3 sets per contact)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) { for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(6).of(transportCrypto).encodeTag( exactly(6).of(transportCrypto).encodeTag(
@@ -98,10 +104,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
with(PROTOCOL_VERSION), with(i)); with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction()); will(new EncodeTagAction());
} }
// Save the keys that were rotated // Save the keys that were updated
oneOf(db).updateTransportKeys(txn, singletonList( oneOf(db).updateTransportKeys(txn, singletonList(
new TransportKeySet(keySetId, contactId, rotated))); new TransportKeySet(keySetId, contactId, null, updated)));
// Schedule key rotation at the start of the next time period // Schedule a key update at the start of the next time period
oneOf(scheduler).schedule(with(any(Runnable.class)), oneOf(scheduler).schedule(with(any(Runnable.class)),
with(timePeriodLength - 1), with(MILLISECONDS)); with(timePeriodLength - 1), with(MILLISECONDS));
}}); }});
@@ -110,26 +116,29 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
db, transportCrypto, dbExecutor, scheduler, clock, transportId, db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency); maxLatency);
transportKeyManager.start(txn); transportKeyManager.start(txn);
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId)); assertEquals(active,
transportKeyManager.canSendOutgoingStreams(contactId));
} }
@Test @Test
public void testKeysAreRotatedWhenAddingContact() throws Exception { public void testRotationKeysAreDerivedAndUpdatedWhenAddingContact()
throws Exception {
boolean alice = random.nextBoolean(); boolean alice = random.nextBoolean();
TransportKeys transportKeys = createTransportKeys(999, 0, true); boolean active = random.nextBoolean();
TransportKeys rotated = createTransportKeys(1000, 0, true); TransportKeys transportKeys = createTransportKeys(999, 0, active);
TransportKeys updated = createTransportKeys(1000, 0, active);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(transportCrypto).deriveTransportKeys(transportId, rootKey, oneOf(transportCrypto).deriveRotationKeys(transportId, rootKey,
999, alice, true); 999, alice, active);
will(returnValue(transportKeys)); will(returnValue(transportKeys));
// Get the current time (1 ms after start of time period 1000) // Get the current time (1 ms after start of time period 1000)
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
will(returnValue(timePeriodLength * 1000 + 1)); will(returnValue(timePeriodLength * 1000 + 1));
// Rotate the transport keys // Update the transport keys
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000); oneOf(transportCrypto).updateTransportKeys(transportKeys, 1000);
will(returnValue(rotated)); will(returnValue(updated));
// Encode the tags (3 sets) // Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) { for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(transportCrypto).encodeTag( exactly(3).of(transportCrypto).encodeTag(
@@ -138,7 +147,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
will(new EncodeTagAction()); will(new EncodeTagAction());
} }
// Save the keys // Save the keys
oneOf(db).addTransportKeys(txn, contactId, rotated); oneOf(db).addTransportKeys(txn, contactId, updated);
will(returnValue(keySetId)); will(returnValue(keySetId));
}}); }});
@@ -147,11 +156,83 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is 1 ms before the start of time period 1000 // The timestamp is 1 ms before the start of time period 1000
long timestamp = timePeriodLength * 1000 - 1; long timestamp = timePeriodLength * 1000 - 1;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, assertEquals(keySetId, transportKeyManager.addContactWithRotationKeys(
rootKey, timestamp, alice, true)); txn, contactId, rootKey, timestamp, alice, active));
assertEquals(active,
transportKeyManager.canSendOutgoingStreams(contactId));
}
@Test
public void testHandshakeKeysAreDerivedWhenAddingContact()
throws Exception {
boolean alice = random.nextBoolean();
TransportKeys transportKeys = createHandshakeKeys(1000, 0, alice);
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
// Get the current time (1 ms after start of time period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(timePeriodLength * 1000 + 1));
// Derive the transport keys
oneOf(transportCrypto).deriveHandshakeKeys(transportId, rootKey,
1000, alice);
will(returnValue(transportKeys));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
will(returnValue(keySetId));
}});
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency);
assertEquals(keySetId, transportKeyManager.addContactWithHandshakeKeys(
txn, contactId, rootKey, alice));
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId)); assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
} }
@Test
public void testHandshakeKeysAreDerivedWhenAddingPendingContact()
throws Exception {
boolean alice = random.nextBoolean();
TransportKeys transportKeys = createHandshakeKeys(1000, 0, alice);
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
// Get the current time (1 ms after start of time period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(timePeriodLength * 1000 + 1));
// Derive the transport keys
oneOf(transportCrypto).deriveHandshakeKeys(transportId, rootKey,
1000, alice);
will(returnValue(transportKeys));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Save the keys
oneOf(db).addTransportKeys(txn, pendingContactId, transportKeys);
will(returnValue(keySetId));
}});
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency);
assertEquals(keySetId, transportKeyManager.addPendingContact(txn,
pendingContactId, rootKey, alice));
assertTrue(transportKeyManager.canSendOutgoingStreams(
pendingContactId));
}
@Test @Test
public void testOutgoingStreamContextIsNullIfContactIsNotFound() public void testOutgoingStreamContextIsNullIfContactIsNotFound()
throws Exception { throws Exception {
@@ -164,6 +245,19 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId)); assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
} }
@Test
public void testOutgoingStreamContextIsNullIfPendingContactIsNotFound()
throws Exception {
Transaction txn = new Transaction(null, false);
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency);
assertNull(transportKeyManager.getStreamContext(txn, pendingContactId));
assertFalse(transportKeyManager.canSendOutgoingStreams(
pendingContactId));
}
@Test @Test
public void testOutgoingStreamContextIsNullIfStreamCounterIsExhausted() public void testOutgoingStreamContextIsNullIfStreamCounterIsExhausted()
throws Exception { throws Exception {
@@ -173,15 +267,15 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
MAX_32_BIT_UNSIGNED + 1, true); MAX_32_BIT_UNSIGNED + 1, true);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
expectAddContactNoRotation(alice, true, transportKeys, txn); expectAddContactKeysNotUpdated(alice, true, transportKeys, txn);
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl( TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId, db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency); maxLatency);
// The timestamp is at the start of time period 1000 // The timestamp is at the start of time period 1000
long timestamp = timePeriodLength * 1000; long timestamp = timePeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, assertEquals(keySetId, transportKeyManager.addContactWithRotationKeys(
rootKey, timestamp, alice, true)); txn, contactId, rootKey, timestamp, alice, true));
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId)); assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
assertNull(transportKeyManager.getStreamContext(txn, contactId)); assertNull(transportKeyManager.getStreamContext(txn, contactId));
} }
@@ -194,7 +288,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
MAX_32_BIT_UNSIGNED, true); MAX_32_BIT_UNSIGNED, true);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
expectAddContactNoRotation(alice, true, transportKeys, txn); expectAddContactKeysNotUpdated(alice, true, transportKeys, txn);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Increment the stream counter // Increment the stream counter
@@ -206,8 +300,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is at the start of time period 1000 // The timestamp is at the start of time period 1000
long timestamp = timePeriodLength * 1000; long timestamp = timePeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, assertEquals(keySetId, transportKeyManager.addContactWithRotationKeys(
rootKey, timestamp, alice, true)); txn, contactId, rootKey, timestamp, alice, true));
// The first request should return a stream context // The first request should return a stream context
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId)); assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
StreamContext ctx = transportKeyManager.getStreamContext(txn, StreamContext ctx = transportKeyManager.getStreamContext(txn,
@@ -231,15 +325,15 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
TransportKeys transportKeys = createTransportKeys(1000, 0, active); TransportKeys transportKeys = createTransportKeys(1000, 0, active);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
expectAddContactNoRotation(alice, active, transportKeys, txn); expectAddContactKeysNotUpdated(alice, active, transportKeys, txn);
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl( TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId, db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency); maxLatency);
// The timestamp is at the start of time period 1000 // The timestamp is at the start of time period 1000
long timestamp = timePeriodLength * 1000; long timestamp = timePeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, assertEquals(keySetId, transportKeyManager.addContactWithRotationKeys(
rootKey, timestamp, alice, active)); txn, contactId, rootKey, timestamp, alice, active));
assertEquals(active, assertEquals(active,
transportKeyManager.canSendOutgoingStreams(contactId)); transportKeyManager.canSendOutgoingStreams(contactId));
// The tag should not be recognised // The tag should not be recognised
@@ -257,7 +351,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
List<byte[]> tags = new ArrayList<>(); List<byte[]> tags = new ArrayList<>();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(transportCrypto).deriveTransportKeys(transportId, rootKey, oneOf(transportCrypto).deriveRotationKeys(transportId, rootKey,
1000, alice, true); 1000, alice, true);
will(returnValue(transportKeys)); will(returnValue(transportKeys));
// Get the current time (the start of time period 1000) // Get the current time (the start of time period 1000)
@@ -270,8 +364,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
with(PROTOCOL_VERSION), with(i)); with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction(tags)); will(new EncodeTagAction(tags));
} }
// Rotate the transport keys (the keys are unaffected) // Updated the transport keys (the keys are unaffected)
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000); oneOf(transportCrypto).updateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys)); will(returnValue(transportKeys));
// Save the keys // Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys); oneOf(db).addTransportKeys(txn, contactId, transportKeys);
@@ -291,8 +385,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is at the start of time period 1000 // The timestamp is at the start of time period 1000
long timestamp = timePeriodLength * 1000; long timestamp = timePeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, assertEquals(keySetId, transportKeyManager.addContactWithRotationKeys(
rootKey, timestamp, alice, true)); txn, contactId, rootKey, timestamp, alice, true));
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId)); assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
// Use the first tag (previous time period, stream number 0) // Use the first tag (previous time period, stream number 0)
assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size()); assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size());
@@ -312,11 +406,11 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testKeysAreRotatedToCurrentPeriod() throws Exception { public void testKeysAreUpdatedToCurrentPeriod() throws Exception {
TransportKeys transportKeys = createTransportKeys(1000, 0, true); TransportKeys transportKeys = createTransportKeys(1000, 0, true);
Collection<TransportKeySet> loaded = singletonList( Collection<TransportKeySet> loaded = singletonList(
new TransportKeySet(keySetId, contactId, transportKeys)); new TransportKeySet(keySetId, contactId, null, transportKeys));
TransportKeys rotated = createTransportKeys(1001, 0, true); TransportKeys updated = createTransportKeys(1001, 0, true);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Transaction txn1 = new Transaction(null, false); Transaction txn1 = new Transaction(null, false);
@@ -327,8 +421,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
// Load the transport keys // Load the transport keys
oneOf(db).getTransportKeys(txn, transportId); oneOf(db).getTransportKeys(txn, transportId);
will(returnValue(loaded)); will(returnValue(loaded));
// Rotate the transport keys (the keys are unaffected) // Update the transport keys (the keys are unaffected)
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000); oneOf(transportCrypto).updateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys)); will(returnValue(transportKeys));
// Encode the tags (3 sets) // Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) { for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
@@ -337,21 +431,21 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
with(PROTOCOL_VERSION), with(i)); with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction()); will(new EncodeTagAction());
} }
// Schedule key rotation at the start of the next time period // Schedule a key update at the start of the next time period
oneOf(scheduler).schedule(with(any(Runnable.class)), oneOf(scheduler).schedule(with(any(Runnable.class)),
with(timePeriodLength), with(MILLISECONDS)); with(timePeriodLength), with(MILLISECONDS));
will(new RunAction()); will(new RunAction());
oneOf(dbExecutor).execute(with(any(Runnable.class))); oneOf(dbExecutor).execute(with(any(Runnable.class)));
will(new RunAction()); will(new RunAction());
// Start a transaction for key rotation // Start a transaction for updating keys
oneOf(db).transaction(with(false), withDbRunnable(txn1)); oneOf(db).transaction(with(false), withDbRunnable(txn1));
// Get the current time (the start of time period 1001) // Get the current time (the start of time period 1001)
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
will(returnValue(timePeriodLength * 1001)); will(returnValue(timePeriodLength * 1001));
// Rotate the transport keys // Update the transport keys
oneOf(transportCrypto).rotateTransportKeys( oneOf(transportCrypto).updateTransportKeys(
with(any(TransportKeys.class)), with(1001L)); with(any(TransportKeys.class)), with(1001L));
will(returnValue(rotated)); will(returnValue(updated));
// Encode the tags (3 sets) // Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) { for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(transportCrypto).encodeTag( exactly(3).of(transportCrypto).encodeTag(
@@ -359,10 +453,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
with(PROTOCOL_VERSION), with(i)); with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction()); will(new EncodeTagAction());
} }
// Save the keys that were rotated // Save the keys that were updated
oneOf(db).updateTransportKeys(txn1, singletonList( oneOf(db).updateTransportKeys(txn1, singletonList(
new TransportKeySet(keySetId, contactId, rotated))); new TransportKeySet(keySetId, contactId, null, updated)));
// Schedule key rotation at the start of the next time period // Schedule a key update at the start of the next time period
oneOf(scheduler).schedule(with(any(Runnable.class)), oneOf(scheduler).schedule(with(any(Runnable.class)),
with(timePeriodLength), with(MILLISECONDS)); with(timePeriodLength), with(MILLISECONDS));
}}); }});
@@ -380,7 +474,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
TransportKeys transportKeys = createTransportKeys(1000, 0, false); TransportKeys transportKeys = createTransportKeys(1000, 0, false);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
expectAddContactNoRotation(alice, false, transportKeys, txn); expectAddContactKeysNotUpdated(alice, false, transportKeys, txn);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Activate the keys // Activate the keys
@@ -394,8 +488,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is at the start of time period 1000 // The timestamp is at the start of time period 1000
long timestamp = timePeriodLength * 1000; long timestamp = timePeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, assertEquals(keySetId, transportKeyManager.addContactWithRotationKeys(
rootKey, timestamp, alice, false)); txn, contactId, rootKey, timestamp, alice, false));
// The keys are inactive so no stream context should be returned // The keys are inactive so no stream context should be returned
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId)); assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
assertNull(transportKeyManager.getStreamContext(txn, contactId)); assertNull(transportKeyManager.getStreamContext(txn, contactId));
@@ -422,7 +516,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
List<byte[]> tags = new ArrayList<>(); List<byte[]> tags = new ArrayList<>();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(transportCrypto).deriveTransportKeys(transportId, rootKey, oneOf(transportCrypto).deriveRotationKeys(transportId, rootKey,
1000, alice, false); 1000, alice, false);
will(returnValue(transportKeys)); will(returnValue(transportKeys));
// Get the current time (the start of time period 1000) // Get the current time (the start of time period 1000)
@@ -435,8 +529,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
with(PROTOCOL_VERSION), with(i)); with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction(tags)); will(new EncodeTagAction(tags));
} }
// Rotate the transport keys (the keys are unaffected) // Update the transport keys (the keys are unaffected)
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000); oneOf(transportCrypto).updateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys)); will(returnValue(transportKeys));
// Save the keys // Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys); oneOf(db).addTransportKeys(txn, contactId, transportKeys);
@@ -460,8 +554,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is at the start of time period 1000 // The timestamp is at the start of time period 1000
long timestamp = timePeriodLength * 1000; long timestamp = timePeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, assertEquals(keySetId, transportKeyManager.addContactWithRotationKeys(
rootKey, timestamp, alice, false)); txn, contactId, rootKey, timestamp, alice, false));
// The keys are inactive so no stream context should be returned // The keys are inactive so no stream context should be returned
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId)); assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
assertNull(transportKeyManager.getStreamContext(txn, contactId)); assertNull(transportKeyManager.getStreamContext(txn, contactId));
@@ -486,10 +580,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
assertEquals(0L, ctx.getStreamNumber()); assertEquals(0L, ctx.getStreamNumber());
} }
private void expectAddContactNoRotation(boolean alice, boolean active, private void expectAddContactKeysNotUpdated(boolean alice, boolean active,
TransportKeys transportKeys, Transaction txn) throws Exception { TransportKeys transportKeys, Transaction txn) throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(transportCrypto).deriveTransportKeys(transportId, rootKey, oneOf(transportCrypto).deriveRotationKeys(transportId, rootKey,
1000, alice, active); 1000, alice, active);
will(returnValue(transportKeys)); will(returnValue(transportKeys));
// Get the current time (the start of time period 1000) // Get the current time (the start of time period 1000)
@@ -502,8 +596,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
with(PROTOCOL_VERSION), with(i)); with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction()); will(new EncodeTagAction());
} }
// Rotate the transport keys (the keys are unaffected) // Upate the transport keys (the keys are unaffected)
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000); oneOf(transportCrypto).updateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys)); will(returnValue(transportKeys));
// Save the keys // Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys); oneOf(db).addTransportKeys(txn, contactId, transportKeys);
@@ -524,6 +618,21 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr); return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
} }
@SuppressWarnings("SameParameterValue")
private TransportKeys createHandshakeKeys(long timePeriod,
long streamCounter, boolean alice) {
IncomingKeys inPrev = new IncomingKeys(tagKey, headerKey,
timePeriod - 1);
IncomingKeys inCurr = new IncomingKeys(tagKey, headerKey,
timePeriod);
IncomingKeys inNext = new IncomingKeys(tagKey, headerKey,
timePeriod + 1);
OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey,
timePeriod, streamCounter, true);
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr,
rootKey, alice);
}
private class EncodeTagAction implements Action { private class EncodeTagAction implements Action {
private final Collection<byte[]> tags; private final Collection<byte[]> tags;

View File

@@ -22,8 +22,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 26 targetSdkVersion 26
versionCode 10106 versionCode 10107
versionName "1.1.6" versionName "1.1.7"
applicationId "org.briarproject.briar.android" applicationId "org.briarproject.briar.android"
buildConfigField "String", "GitHash", buildConfigField "String", "GitHash",
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\"" "\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""

View File

@@ -73,13 +73,12 @@
</activity> </activity>
<activity <activity
android:name="org.briarproject.briar.android.login.PasswordActivity" android:name="org.briarproject.briar.android.login.StartupActivity"
android:label="@string/app_name" android:label="@string/app_name">
android:windowSoftInputMode="stateVisible">
</activity> </activity>
<activity <activity
android:name="org.briarproject.briar.android.login.SetupActivity" android:name="org.briarproject.briar.android.account.SetupActivity"
android:label="@string/setup_title" android:label="@string/setup_title"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
</activity> </activity>
@@ -94,11 +93,6 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".android.login.OpenDatabaseActivity"
android:label="@string/app_name"
android:launchMode="singleTop"/>
<activity <activity
android:name="org.briarproject.briar.android.navdrawer.NavDrawerActivity" android:name="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
android:theme="@style/BriarTheme.NoActionBar" android:theme="@style/BriarTheme.NoActionBar"
@@ -420,7 +414,7 @@
</activity> </activity>
<activity <activity
android:name=".android.login.UnlockActivity" android:name=".android.account.UnlockActivity"
android:label="@string/lock_unlock" android:label="@string/lock_unlock"
android:launchMode="singleTask" android:launchMode="singleTask"
android:theme="@style/BriarTheme.NoActionBar"/> android:theme="@style/BriarTheme.NoActionBar"/>

View File

@@ -1,7 +1,10 @@
package org.briarproject.briar.android; package org.briarproject.briar.android;
import android.app.Activity;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import java.util.Collection; import java.util.Collection;
import java.util.logging.LogRecord; import java.util.logging.LogRecord;
@@ -11,6 +14,8 @@ import java.util.logging.LogRecord;
*/ */
public interface BriarApplication { public interface BriarApplication {
Class<? extends Activity> ENTRY_ACTIVITY = NavDrawerActivity.class;
Collection<LogRecord> getRecentLogRecords(); Collection<LogRecord> getRecentLogRecords();
AndroidComponent getApplicationComponent(); AndroidComponent getApplicationComponent();

View File

@@ -22,7 +22,6 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.logout.HideUiActivity; import org.briarproject.briar.android.logout.HideUiActivity;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.LockManager; import org.briarproject.briar.api.android.LockManager;
@@ -48,6 +47,7 @@ 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.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_NOTIFICATION_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_NOTIFICATION_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_ID;
@@ -182,7 +182,7 @@ public class BriarService extends Service {
NotificationManager nm = (NotificationManager) o; NotificationManager nm = (NotificationManager) o;
nm.notify(FAILURE_NOTIFICATION_ID, b.build()); nm.notify(FAILURE_NOTIFICATION_ID, b.build());
// Bring the dashboard to the front to clear the back stack // Bring the dashboard to the front to clear the back stack
i = new Intent(BriarService.this, NavDrawerActivity.class); i = new Intent(BriarService.this, ENTRY_ACTIVITY);
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP); i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
i.putExtra(EXTRA_STARTUP_FAILED, true); i.putExtra(EXTRA_STARTUP_FAILED, true);
startActivity(i); startActivity(i);

View File

@@ -74,7 +74,7 @@ public class StartupFailureActivity extends BaseActivity implements
@Override @Override
public void runOnDbThread(@NonNull Runnable runnable) { public void runOnDbThread(@NonNull Runnable runnable) {
throw new AssertionError("Deprecated and should not be used"); throw new UnsupportedOperationException();
} }
} }

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.TextInputEditText; import android.support.design.widget.TextInputEditText;
@@ -21,6 +21,7 @@ import static android.view.inputmethod.EditorInfo.IME_ACTION_NONE;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.briar.android.util.UiUtils.setError; import static org.briarproject.briar.android.util.UiUtils.setError;
import static org.briarproject.briar.android.util.UiUtils.showSoftKeyboard;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -63,6 +64,12 @@ public class AuthorNameFragment extends SetupFragment {
return TAG; return TAG;
} }
@Override
public void onResume() {
super.onResume();
showSoftKeyboard(authorNameInput);
}
@Override @Override
protected String getHelpText() { protected String getHelpText() {
return getString(R.string.setup_name_explanation); return getString(R.string.setup_name_explanation);

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Intent; import android.content.Intent;
@@ -13,8 +13,8 @@ import android.widget.ProgressBar;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.account.PowerView.OnCheckedChangedListener;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.login.PowerView.OnCheckedChangedListener;
import org.briarproject.briar.android.util.UiUtils; import org.briarproject.briar.android.util.UiUtils;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.content.Context; import android.content.Context;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.content.Context; import android.content.Context;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.content.Context; import android.content.Context;
import android.os.Parcel; import android.os.Parcel;

View File

@@ -0,0 +1,131 @@
package org.briarproject.briar.android.account;
import android.os.Bundle;
import android.os.IBinder;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.ProgressBar;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.login.StrengthMeter;
import javax.annotation.Nullable;
import static android.content.Context.INPUT_METHOD_SERVICE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
import static org.briarproject.briar.android.util.UiUtils.setError;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class SetPasswordFragment extends SetupFragment {
private final static String TAG = SetPasswordFragment.class.getName();
private TextInputLayout passwordEntryWrapper;
private TextInputLayout passwordConfirmationWrapper;
private TextInputEditText passwordEntry;
private TextInputEditText passwordConfirmation;
private StrengthMeter strengthMeter;
private Button nextButton;
private ProgressBar progressBar;
public static SetPasswordFragment newInstance() {
return new SetPasswordFragment();
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireNonNull(getActivity()).setTitle(getString(R.string.setup_password_intro));
View v = inflater.inflate(R.layout.fragment_setup_password, container,
false);
strengthMeter = v.findViewById(R.id.strength_meter);
passwordEntryWrapper = v.findViewById(R.id.password_entry_wrapper);
passwordEntry = v.findViewById(R.id.password_entry);
passwordEntry.requestFocus();
passwordConfirmationWrapper =
v.findViewById(R.id.password_confirm_wrapper);
passwordConfirmation = v.findViewById(R.id.password_confirm);
nextButton = v.findViewById(R.id.next);
progressBar = v.findViewById(R.id.progress);
passwordEntry.addTextChangedListener(this);
passwordConfirmation.addTextChangedListener(this);
nextButton.setOnClickListener(this);
if (!setupController.needToShowDozeFragment()) {
nextButton.setText(R.string.create_account_button);
passwordConfirmation.setImeOptions(IME_ACTION_DONE);
}
return v;
}
@Override
public String getUniqueTag() {
return TAG;
}
@Override
protected String getHelpText() {
return getString(R.string.setup_password_explanation);
}
@Override
public void onTextChanged(CharSequence authorName, int i, int i1, int i2) {
String password1 = passwordEntry.getText().toString();
String password2 = passwordConfirmation.getText().toString();
boolean passwordsMatch = password1.equals(password2);
strengthMeter
.setVisibility(password1.length() > 0 ? VISIBLE : INVISIBLE);
float strength = setupController.estimatePasswordStrength(password1);
strengthMeter.setStrength(strength);
boolean strongEnough = strength >= QUITE_WEAK;
setError(passwordEntryWrapper, getString(R.string.password_too_weak),
password1.length() > 0 && !strongEnough);
setError(passwordConfirmationWrapper,
getString(R.string.passwords_do_not_match),
password2.length() > 0 && !passwordsMatch);
boolean enabled = passwordsMatch && strongEnough;
nextButton.setEnabled(enabled);
passwordConfirmation.setOnEditorActionListener(enabled ? this : null);
}
@Override
public void onClick(View view) {
IBinder token = passwordEntry.getWindowToken();
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
setupController.setPassword(passwordEntry.getText().toString());
if (setupController.needToShowDozeFragment()) {
setupController.showDozeFragment();
} else {
nextButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
setupController.createAccount();
}
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Intent; import android.content.Intent;
@@ -15,8 +15,11 @@ import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -86,7 +89,7 @@ public class SetupActivity extends BaseActivity
void showPasswordFragment() { void showPasswordFragment() {
if (authorName == null) throw new IllegalStateException(); if (authorName == null) throw new IllegalStateException();
showNextFragment(PasswordFragment.newInstance()); showNextFragment(SetPasswordFragment.newInstance());
} }
@TargetApi(23) @TargetApi(23)
@@ -97,8 +100,9 @@ public class SetupActivity extends BaseActivity
} }
void showApp() { void showApp() {
Intent i = new Intent(this, OpenDatabaseActivity.class); Intent i = new Intent(this, ENTRY_ACTIVITY);
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME); i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME |
FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i); startActivity(i);
supportFinishAfterTransition(); supportFinishAfterTransition();
overridePendingTransition(R.anim.screen_new_in, R.anim.screen_old_out); overridePendingTransition(R.anim.screen_new_in, R.anim.screen_old_out);

View File

@@ -1,9 +1,9 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault @NotNullByDefault
public interface SetupController extends PasswordController { public interface SetupController {
void setSetupActivity(SetupActivity setupActivity); void setSetupActivity(SetupActivity setupActivity);
@@ -11,6 +11,8 @@ public interface SetupController extends PasswordController {
void setAuthorName(String authorName); void setAuthorName(String authorName);
float estimatePasswordStrength(String password);
void setPassword(String password); void setPassword(String password);
/** /**

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@@ -15,12 +15,15 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
@NotNullByDefault @NotNullByDefault
public class SetupControllerImpl extends PasswordControllerImpl public class SetupControllerImpl implements SetupController {
implements SetupController {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(SetupControllerImpl.class.getName()); Logger.getLogger(SetupControllerImpl.class.getName());
private final AccountManager accountManager;
private final PasswordStrengthEstimator strengthEstimator;
@IoExecutor
private final Executor ioExecutor;
@Nullable @Nullable
private volatile SetupActivity setupActivity; private volatile SetupActivity setupActivity;
@@ -28,7 +31,9 @@ public class SetupControllerImpl extends PasswordControllerImpl
SetupControllerImpl(AccountManager accountManager, SetupControllerImpl(AccountManager accountManager,
@IoExecutor Executor ioExecutor, @IoExecutor Executor ioExecutor,
PasswordStrengthEstimator strengthEstimator) { PasswordStrengthEstimator strengthEstimator) {
super(accountManager, ioExecutor, strengthEstimator); this.accountManager = accountManager;
this.strengthEstimator = strengthEstimator;
this.ioExecutor = ioExecutor;
} }
@Override @Override
@@ -51,6 +56,11 @@ public class SetupControllerImpl extends PasswordControllerImpl
setupActivity.setAuthorName(authorName); setupActivity.setAuthorName(authorName);
} }
@Override
public float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password);
}
@Override @Override
public void setPassword(String password) { public void setPassword(String password) {
SetupActivity setupActivity = this.setupActivity; SetupActivity setupActivity = this.setupActivity;

Some files were not shown because too many files have changed in this diff Show More