Compare commits

...

98 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
421ca309c7 Merge branch '1538-create-handshake-key-pair' into 'master'
Generate and store handshake key pair at startup if necessary

Closes #1538

See merge request briar/briar!1082
2019-05-14 15:39:44 +00:00
akwizgran
43787deafd Address review comments. 2019-05-14 15:55:42 +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
Torsten Grote
451edba467 Merge branch 'live-event-reduce-visibility' into 'master'
Reduce visibility of LiveEvent inner classes

See merge request briar/briar!1092
2019-05-10 16:52:49 +00:00
Torsten Grote
5880479987 Merge branch '1537-contact-manager-pending-contacts' into 'master'
Implement contact manager methods for pending contacts

Closes #1537

See merge request briar/briar!1081
2019-05-10 15:06:08 +00:00
akwizgran
71d8fb2083 Add unit tests for Base32 encoding and decoding. 2019-05-10 15:33:19 +01:00
akwizgran
0825e77dd7 Static import. 2019-05-10 15:11:44 +01:00
akwizgran
593a709a7f Remove redundant regex. 2019-05-10 15:06:12 +01:00
akwizgran
322fefb2a2 Use matcher to discard prefix if present. 2019-05-10 15:02:47 +01:00
akwizgran
8005cdc659 Reduce visibility of LiveEvent inner classes. 2019-05-10 10:20:49 +01:00
akwizgran
33fdca4aa1 Merge branch 'live-event' into 'master'
Migrate existing uses of event-like LiveData to LiveEvent

See merge request briar/briar!1090
2019-05-10 09:09:33 +00:00
akwizgran
e5fc91b620 Rename exceptions and events. 2019-05-10 10:02:49 +01:00
akwizgran
9c08073e49 Rename account to identity. 2019-05-10 10:02:49 +01:00
akwizgran
5553b7d0e4 Remove unused method. 2019-05-10 10:02:48 +01:00
akwizgran
2cce0f5fe2 Remove OpenDatabaseHook priorities. 2019-05-10 10:02:48 +01:00
akwizgran
ebae1037be Remove unnecessary null check. 2019-05-10 10:02:48 +01:00
akwizgran
0c99ef0e5b Clean up some duplicated code. 2019-05-10 10:02:48 +01:00
akwizgran
faba9a6b70 Generate handshake keys on demand, store when DB is opened. 2019-05-10 10:02:47 +01:00
akwizgran
891c82b2e5 Add javadocs to DB hook interfaces. 2019-05-10 10:02:47 +01:00
akwizgran
56fbc93962 Move handshake keys from LocalAuthor to Account. 2019-05-10 10:02:47 +01:00
akwizgran
251eb9e712 Add javadoc for handshakeKeys flag. 2019-05-10 10:02:47 +01:00
akwizgran
8b2b7599f9 Generate and store handshake keys at startup if needed. 2019-05-10 10:02:46 +01:00
akwizgran
8c315382e2 Add DB method for setting local handshake key pair. 2019-05-10 10:02:46 +01:00
akwizgran
8183a48ebb Add unit test for OpenDatabaseHook priority. 2019-05-10 10:02:45 +01:00
akwizgran
f6611daf7b Replace Client interface with OpenDatabaseHook. 2019-05-10 10:02:45 +01:00
akwizgran
00bc8ac768 Include handshake keys when loading all local authors. 2019-05-10 10:02:45 +01:00
akwizgran
75776eb7de Generate handshake keys when creating local author. 2019-05-10 10:02:45 +01:00
akwizgran
f0a3130bf3 Test that UnsupportedVersionException is thrown. 2019-05-10 10:01:32 +01:00
akwizgran
64aa121c9c Reuse UnsupportedVersionException for handshake links. 2019-05-10 10:01:22 +01:00
akwizgran
cc3486df94 Move UnsupportedVersionException to bramble.api package. 2019-05-10 10:01:06 +01:00
akwizgran
cd24be7e42 Add unit tests for pending contact factory. 2019-05-10 10:01:05 +01:00
akwizgran
fa562b40bc Implement contact manager methods for pending contacts. 2019-05-10 10:01:03 +01:00
akwizgran
fc8ca872a8 Add base32 encoder/decoder. 2019-05-10 09:59:16 +01:00
Torsten Grote
5b63eab314 [android] migrate existing uses of event-like LiveData to LiveEvent 2019-05-09 14:47:16 -03:00
akwizgran
6f0ab8b688 Merge branch '1234-remote-contacts' into 'master'
Implement UX for adding contacts remotely

Closes #1234

See merge request briar/briar!1035
2019-05-09 16:52:24 +00:00
Torsten Grote
dfc567cbfd [bramble] Remove PendingContact test code from ContactManagerImpl 2019-05-09 13:36:21 -03:00
Torsten Grote
de98a4cb12 [android] Introduce a (Mutable)LiveEvent for single-use LiveData 2019-05-09 13:20:09 -03:00
Torsten Grote
fbe375cc4e Use event instead of CommitAction to handle removed PendingContacts 2019-05-09 11:43:22 -03:00
Torsten Grote
19bc73ac61 [android] show Toast when user shares own handshake link
This also limits the AddContactActivity to run within one single task
2019-05-03 11:47:51 -03:00
Torsten Grote
d17331b578 [android] Set handshake link when received via sharing intent or link click 2019-05-03 10:18:47 -03:00
Torsten Grote
bec1f117ba Remote Contact Adding: Rename methods and add more exception handling 2019-05-03 09:48:20 -03:00
Torsten Grote
2c014b4e46 Only remove PendingContact from UI when removed from DB 2019-05-03 09:48:19 -03:00
Torsten Grote
7a71d2bad4 Remote Contact Adding UI: Address review comments 2019-05-03 09:48:19 -03:00
Torsten Grote
4bf21b2f3b [android] hide feature to add contacts remotely behind feature flag 2019-05-03 09:48:19 -03:00
Torsten Grote
4a57939b80 [android] finalize list of pending contacts and add test code 2019-05-03 09:48:19 -03:00
Torsten Grote
66cdf4f595 Refactored IntroductionSucceededEvent into more generic ContactAddedRemotelyEvent 2019-05-03 09:48:18 -03:00
Torsten Grote
3384477499 [android] Add BriarSnackbarBuilder to standardize snackbar creation 2019-05-03 09:48:18 -03:00
Torsten Grote
58ffc6e761 [android] rough sketch of UI for adding contacts remotely 2019-05-03 09:48:15 -03:00
akwizgran
df5ac59fc9 Merge branch 'gradle-android-3.4' into 'master'
Update the Android gradle plugin to version 3.4.0

See merge request briar/briar!1085
2019-05-03 09:36:46 +00:00
akwizgran
dc649b195a Merge branch '1552-send-controller-exception' into 'master'
Don't disable TextInputView directly, use controller

Closes #1552

See merge request briar/briar!1086
2019-05-01 09:38:34 +00:00
Torsten Grote
3d9a8f9bf8 [android] Use TextSendController to disable TextInputView
Fixes #1552
2019-04-26 13:45:49 -03:00
Torsten Grote
96975e0d43 Upgrade dagger, mockito and okhttp 2019-04-26 09:07:26 -03:00
Torsten Grote
6691e708e4 Update the Android gradle plugin to version 3.4.0 2019-04-25 20:55:30 -03:00
333 changed files with 8285 additions and 3717 deletions

View File

@@ -39,31 +39,6 @@
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </JetCodeStyleSettings>
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" fileNamingConvention="NONE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
</extensions>
</Objective-C-extensions>
<XML> <XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" /> <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML> </XML>

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"
@@ -33,7 +33,7 @@ dependencies {
tor 'org.briarproject:tor-android:0.3.5.8@zip' tor 'org.briarproject:tor-android:0.3.5.8@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.9@zip' tor 'org.briarproject:obfs4proxy-android:0.0.9@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.19' annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
compileOnly 'javax.annotation:jsr250-api:1.0' compileOnly 'javax.annotation:jsr250-api:1.0'

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

@@ -1,45 +1,45 @@
dependencyVerification { dependencyVerification {
verify = [ verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861', 'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
'com.android.tools.analytics-library:protos:26.3.2:protos-26.3.2.jar:50238fb4298b297217b184b9cd93c14f83536fcee829eb0ca850bdb5b53251f0', 'com.android.tools.analytics-library:protos:26.4.0:protos-26.4.0.jar:ad760915586797d39319f402837b378bff3bb4ed583e3e0c48c965631fb2135f',
'com.android.tools.analytics-library:shared:26.3.2:shared-26.3.2.jar:ddd80dcf21905018b7c0583ba72b7282f446084d4952422609a09fbf8237ef71', 'com.android.tools.analytics-library:shared:26.4.0:shared-26.4.0.jar:1332106a905d48909c81268c9e414946de3e83487db394c6073b0a9b5c3d0ed2',
'com.android.tools.analytics-library:tracker:26.3.2:tracker-26.3.2.jar:28c575d2d1af003e96d7b375a668ee10b2673a2dd0f6438750aa8c3b42e7d0ad', 'com.android.tools.analytics-library:tracker:26.4.0:tracker-26.4.0.jar:d0020cfbfd4cd75935f2972d6a24089840d4a10df6f3ef2a796093217dd37796',
'com.android.tools.build:apksig:3.3.2:apksig-3.3.2.jar:84c4aaa20127c6c1fe6bdd334b3f5df71f54ad080be9029c8a10f43b6a908acd', 'com.android.tools.build:apksig:3.4.0:apksig-3.4.0.jar:91d5a1866139c69756280355a6f61b4d619d0516841580114f45a10f2177327e',
'com.android.tools.build:apkzlib:3.3.2:apkzlib-3.3.2.jar:d34e523278e5dff565eba3ef3c089d515b2b5cc7b47dc77e2f3465e5e47176ac', 'com.android.tools.build:apkzlib:3.4.0:apkzlib-3.4.0.jar:8653c85f5fdf1dde840e8b8af7396aeb79c34b66e541b5860059616006535592',
'com.android.tools.build:builder-model:3.3.2:builder-model-3.3.2.jar:055e3db0ecee9e06b9f024034999a29cd92cb1885207b37542126bd8bcc57f46', 'com.android.tools.build:builder-model:3.4.0:builder-model-3.4.0.jar:a88f138124a9f016a70bcb4760359a502f65c7deed56507ee4014f4dd9ea853b',
'com.android.tools.build:builder-test-api:3.3.2:builder-test-api-3.3.2.jar:0b2e4cd7615bbcad14a3c91fe45ae26693508d06e40ba06c5968b8bc24416618', 'com.android.tools.build:builder-test-api:3.4.0:builder-test-api-3.4.0.jar:31089ab1ec19ca7687a010867d2f3807513c805b8226979706f4247b5d4df26f',
'com.android.tools.build:builder:3.3.2:builder-3.3.2.jar:65649704da7aef0487235fd326f0f2e99ed5cf958e80f204496e6e08a42bd9f5', 'com.android.tools.build:builder:3.4.0:builder-3.4.0.jar:476221b5203a7f50089bf185ed95000a34b6f5020ef0a17815afd58606922679',
'com.android.tools.build:gradle-api:3.3.2:gradle-api-3.3.2.jar:3cbd47e41bb70330dd72ec2c9fe51e6173554b484a03829b5a2de9e00841e040', 'com.android.tools.build:gradle-api:3.4.0:gradle-api-3.4.0.jar:215eca38f6719213c2f492b4d622cdd11676c66c9871f8a2aed0c66d00175628',
'com.android.tools.build:manifest-merger:26.3.2:manifest-merger-26.3.2.jar:05c4a6d8b02fb9f08744876477d0a68547c03a8a9069b1f086684fa04af97c33', 'com.android.tools.build:manifest-merger:26.4.0:manifest-merger-26.4.0.jar:29e45e690dedd165035e97c21c2ca94d0bd4ec16b6b210daa26669a582b6f220',
'com.android.tools.ddms:ddmlib:26.3.2:ddmlib-26.3.2.jar:d248da8a563d6e46d2c7ebbf371a4877e00510f4ca763c0bb272d5a281bf8b85', 'com.android.tools.ddms:ddmlib:26.4.0:ddmlib-26.4.0.jar:93f56fe4630c3166adbd6c51d7bb602d96abb91b07ba5b1165fdcd071e88c940',
'com.android.tools.external.com-intellij:intellij-core:26.3.2:intellij-core-26.3.2.jar:6c5ecc968230e9f4dcd0fef28885379feace1f0cd8130de6f61d649c86139bf3', 'com.android.tools.external.com-intellij:intellij-core:26.4.0:intellij-core-26.4.0.jar:30cb0e879d4424de9677a50b537fb628636b4a50f5470af5e52437980c41421f',
'com.android.tools.external.com-intellij:kotlin-compiler:26.3.2:kotlin-compiler-26.3.2.jar:1007d9b07ccb49cd8eaf30fda10ed4681d4714f2f9ab2ecda39b4e539cc51bbe', 'com.android.tools.external.com-intellij:kotlin-compiler:26.4.0:kotlin-compiler-26.4.0.jar:dd1fe225c31a0e012dc025336363a5b783e2c5c20ffb69e77f8f57e89420d998',
'com.android.tools.external.org-jetbrains:uast:26.3.2:uast-26.3.2.jar:5d1833e562ea4f38a89708dfde695f0a162cbd39d003d3dde818c3fdc2b05317', 'com.android.tools.external.org-jetbrains:uast:26.4.0:uast-26.4.0.jar:f25f3285b775a983327583ff6584dea54e447813ef69e0ce08b05a45b5f4aab0',
'com.android.tools.layoutlib:layoutlib-api:26.3.2:layoutlib-api-26.3.2.jar:d7e61e874ab95f5c350dd38b6a95b5c9dbe0083a02001884264cdb390cb255b8', 'com.android.tools.layoutlib:layoutlib-api:26.4.0:layoutlib-api-26.4.0.jar:52128f5cf293b224072be361919bfd416e59480ab7264ddcdbbf046b0d7a12e3',
'com.android.tools.lint:lint-api:26.3.2:lint-api-26.3.2.jar:5867dfd7fb4a4e161a816a5d29d045f9b542d34594c00a1efec46fb4cd0e1033', 'com.android.tools.lint:lint-api:26.4.0:lint-api-26.4.0.jar:fdb8fca8ae4c254f438338d03d72605e00ed106f2d5550405af41ca1c8509401',
'com.android.tools.lint:lint-checks:26.3.2:lint-checks-26.3.2.jar:4b163b9c93790d2771e92ba8de58a0d9e0671ffcf2ccef3cf496efd442e27517', 'com.android.tools.lint:lint-checks:26.4.0:lint-checks-26.4.0.jar:4ff52d40488cd3e22b9c6b2eb67784e0c3269d0b42ef9d17689cd75a7b2bceb4',
'com.android.tools.lint:lint-gradle-api:26.3.2:lint-gradle-api-26.3.2.jar:54cb282e0c054f9bed3f51302ce08b003c8ab7961dfd5a4f6de26c23cc23062f', 'com.android.tools.lint:lint-gradle-api:26.4.0:lint-gradle-api-26.4.0.jar:714b7a85c7d2aa10daeab16e969fe7530c659d0728a7f24021da456870418d0f',
'com.android.tools.lint:lint-gradle:26.3.2:lint-gradle-26.3.2.jar:bb139615f4ce97d42cc394b9389b49b76a6eb85be6785a5d272991543b519013', 'com.android.tools.lint:lint-gradle:26.4.0:lint-gradle-26.4.0.jar:b8c130d273f522388734457e1b96790f41528fcec6fda9e8eaa4e4d95a07cfbb',
'com.android.tools.lint:lint:26.3.2:lint-26.3.2.jar:ef7b369f8a56a92ccb0f4c1c357666b9339e4a711a9d84747d446441746cfe4e', 'com.android.tools.lint:lint:26.4.0:lint-26.4.0.jar:83aa062fb0405b60ed358d858c8c2955e1bae44a455b498068c6a60988755f00',
'com.android.tools:annotations:26.3.2:annotations-26.3.2.jar:5bcce8e98b6a2f5ccf13ebcefd8f734e0b35f8b19e456575665631442ce1f7a1', 'com.android.tools:annotations:26.4.0:annotations-26.4.0.jar:a7955b8e19c3a2a861d6faa43a58b7c0d46ea9112188ee3e235c6f9f439ecc1a',
'com.android.tools:common:26.3.2:common-26.3.2.jar:d9f8e7f0669e9a701568e3db6a87c89cf12d8fa6811c9991e969f950215ecfac', 'com.android.tools:common:26.4.0:common-26.4.0.jar:ea40b94b3c1284ea7700f011388e2906a8363a66abd902891722b3c557984852',
'com.android.tools:dvlib:26.3.2:dvlib-26.3.2.jar:d84aad56161c7773579303d69714ded6897c64c6ddfd7d456e453231e4dfe811', 'com.android.tools:dvlib:26.4.0:dvlib-26.4.0.jar:23af89c535b01ba36ceed1b6b309b672814eba624e643cd7dedf0519edad50cc',
'com.android.tools:repository:26.3.2:repository-26.3.2.jar:da611eeb06e9ab8750d25b9e2901e10db8e5ec6304eb4c8b7103d39e0921ea40', 'com.android.tools:repository:26.4.0:repository-26.4.0.jar:3d1763ab46199374dc6d94129bba11c70f1d5857e2c81a3ac4898abca40b176b',
'com.android.tools:sdk-common:26.3.2:sdk-common-26.3.2.jar:82823a3bf25e64fac33a286490f0cf5ac50c2cdb3c540149b030896bb44bf96c', 'com.android.tools:sdk-common:26.4.0:sdk-common-26.4.0.jar:78a522525b30ffc6b7bf1299c831d24ce385f68a9f4878f8f752e9baefa31b0f',
'com.android.tools:sdklib:26.3.2:sdklib-26.3.2.jar:424d15492af67321900963238646d27495ab60de2a5b19e6a416963bc5d6932b', 'com.android.tools:sdklib:26.4.0:sdklib-26.4.0.jar:b854c23892013a326d761cf071c72cf3e038ed0469d10f4a356829fa56e4c132',
'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed', 'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7', 'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.code.gson:gson:2.8.0:gson-2.8.0.jar:c6221763bd79c4f1c3dc7f750b5f29a0bb38b367b81314c4f71896e340c40825', 'com.google.code.gson:gson:2.8.0:gson-2.8.0.jar:c6221763bd79c4f1c3dc7f750b5f29a0bb38b367b81314c4f71896e340c40825',
'com.google.dagger:dagger-compiler:2.19:dagger-compiler-2.19.jar:27a4b202a2de908182edb261f8c0a264e08e5e4733d7514bc7fbf0d31da5c0fc', 'com.google.dagger:dagger-compiler:2.22.1:dagger-compiler-2.22.1.jar:e5f28302cbe70a79d3620cddebfb8ec0736814f3980ffe1e673bfe3342f507d3',
'com.google.dagger:dagger-producers:2.19:dagger-producers-2.19.jar:a17663abe0fc38b676026950907d4c5f5e2bf338375415861eaff6e3bdb0b768', 'com.google.dagger:dagger-producers:2.22.1:dagger-producers-2.22.1.jar:f834a0082014213a68ff06a0f048d750178d02196c58b0b15beb367d32b97e35',
'com.google.dagger:dagger-spi:2.19:dagger-spi-2.19.jar:e7a6379d82c841f6aac2866948ad1eed716528707814602842a8d844ce04e2e1', 'com.google.dagger:dagger-spi:2.22.1:dagger-spi-2.22.1.jar:4b0b922793b3bcb91b99fabb75dba77c68afd7ae4c5f0c4fd6ba681f0a291c7d',
'com.google.dagger:dagger:2.19:dagger-2.19.jar:514b6f1e0727c6572e1d65cb27e4ae668b7aeaeb93a29515182965265b609939', 'com.google.dagger:dagger:2.22.1:dagger-2.22.1.jar:329d4340f24c4f5717af016c097e90668bfea2a5376e6aa9964b01cef3fd241a',
'com.google.errorprone:error_prone_annotations:2.1.3:error_prone_annotations-2.1.3.jar:03d0329547c13da9e17c634d1049ea2ead093925e290567e1a364fd6b1fc7ff8', 'com.google.errorprone:error_prone_annotations:2.1.3:error_prone_annotations-2.1.3.jar:03d0329547c13da9e17c634d1049ea2ead093925e290567e1a364fd6b1fc7ff8',
'com.google.errorprone:javac-shaded:9-dev-r4023-3:javac-shaded-9-dev-r4023-3.jar:65bfccf60986c47fbc17c9ebab0be626afc41741e0a6ec7109e0768817a36f30', 'com.google.errorprone:javac-shaded:9-dev-r4023-3:javac-shaded-9-dev-r4023-3.jar:65bfccf60986c47fbc17c9ebab0be626afc41741e0a6ec7109e0768817a36f30',
'com.google.googlejavaformat:google-java-format:1.5:google-java-format-1.5.jar:aa19ad7850fb85178aa22f2fddb163b84d6ce4d0035872f30d4408195ca1144e', 'com.google.googlejavaformat:google-java-format:1.5:google-java-format-1.5.jar:aa19ad7850fb85178aa22f2fddb163b84d6ce4d0035872f30d4408195ca1144e',
'com.google.guava:guava:25.0-jre:guava-25.0-jre.jar:3fd4341776428c7e0e5c18a7c10de129475b69ab9d30aeafbb5c277bb6074fa9', 'com.google.guava:guava:25.0-jre:guava-25.0-jre.jar:3fd4341776428c7e0e5c18a7c10de129475b69ab9d30aeafbb5c277bb6074fa9',
'com.google.guava:guava:26.0-jre:guava-26.0-jre.jar:a0e9cabad665bc20bcd2b01f108e5fc03f756e13aea80abaadb9f407033bea2c', 'com.google.guava:guava:26.0-jre:guava-26.0-jre.jar:a0e9cabad665bc20bcd2b01f108e5fc03f756e13aea80abaadb9f407033bea2c',
'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:40ceb7157feb263949e0f503fe5f71689333a621021aa20ce0d0acee3badaa0f', 'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6',
'com.google.jimfs:jimfs:1.1:jimfs-1.1.jar:c4828e28d7c0a930af9387510b3bada7daa5c04d7c25a75c7b8b081f1c257ddd', 'com.google.jimfs:jimfs:1.1:jimfs-1.1.jar:c4828e28d7c0a930af9387510b3bada7daa5c04d7c25a75c7b8b081f1c257ddd',
'com.google.protobuf:protobuf-java:3.4.0:protobuf-java-3.4.0.jar:dce7e66b32456a1b1198da0caff3a8acb71548658391e798c79369241e6490a4', 'com.google.protobuf:protobuf-java:3.4.0:protobuf-java-3.4.0.jar:dce7e66b32456a1b1198da0caff3a8acb71548658391e798c79369241e6490a4',
'com.googlecode.json-simple:json-simple:1.1:json-simple-1.1.jar:2d9484f4c649f708f47f9a479465fc729770ee65617dca3011836602264f6439', 'com.googlecode.json-simple:json-simple:1.1:json-simple-1.1.jar:2d9484f4c649f708f47f9a479465fc729770ee65617dca3011836602264f6439',
@@ -48,7 +48,7 @@ dependencyVerification {
'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce', 'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce',
'com.sun.istack:istack-commons-runtime:2.21:istack-commons-runtime-2.21.jar:c33e67a0807095f02a0e2da139412dd7c4f9cc1a4c054b3e434f96831ba950f4', 'com.sun.istack:istack-commons-runtime:2.21:istack-commons-runtime-2.21.jar:c33e67a0807095f02a0e2da139412dd7c4f9cc1a4c054b3e434f96831ba950f4',
'com.sun.xml.fastinfoset:FastInfoset:1.2.13:FastInfoset-1.2.13.jar:27a77db909f3c2833c0b1a37c55af1db06045118ad2eed96ce567b6632bce038', 'com.sun.xml.fastinfoset:FastInfoset:1.2.13:FastInfoset-1.2.13.jar:27a77db909f3c2833c0b1a37c55af1db06045118ad2eed96ce567b6632bce038',
'commons-codec:commons-codec:1.9:commons-codec-1.9.jar:ad19d2601c3abf0b946b5c3a4113e226a8c1e3305e395b90013b78dd94a723ce', 'commons-codec:commons-codec:1.10:commons-codec-1.10.jar:4241dfa94e711d435f29a4604a3e2de5c4aa3c165e23bd066be6fc1fc4309569',
'commons-logging:commons-logging:1.2:commons-logging-1.2.jar:daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636', 'commons-logging:commons-logging:1.2:commons-logging-1.2.jar:daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636',
'it.unimi.dsi:fastutil:7.2.0:fastutil-7.2.0.jar:74fa208043740642f7e6eb09faba15965218ad2f50ce3020efb100136e4b591c', 'it.unimi.dsi:fastutil:7.2.0:fastutil-7.2.0.jar:74fa208043740642f7e6eb09faba15965218ad2f50ce3020efb100136e4b591c',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f', 'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
@@ -60,9 +60,9 @@ dependencyVerification {
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d', 'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8', 'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.apache.commons:commons-compress:1.12:commons-compress-1.12.jar:2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6', 'org.apache.commons:commons-compress:1.12:commons-compress-1.12.jar:2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6',
'org.apache.httpcomponents:httpclient:4.5.2:httpclient-4.5.2.jar:0dffc621400d6c632f55787d996b8aeca36b30746a716e079a985f24d8074057', 'org.apache.httpcomponents:httpclient:4.5.6:httpclient-4.5.6.jar:c03f813195e7a80e3608d0ddd8da80b21696a4c92a6a2298865bf149071551c7',
'org.apache.httpcomponents:httpcore:4.4.5:httpcore-4.4.5.jar:64d5453874cab7e40a7065cb01a9a9ca1053845a9786b478878b679e0580cec3', 'org.apache.httpcomponents:httpcore:4.4.10:httpcore-4.4.10.jar:78ba1096561957db1b55200a159b648876430342d15d461277e62360da19f6fd',
'org.apache.httpcomponents:httpmime:4.5.2:httpmime-4.5.2.jar:231a3f7e4962053db2be8461d5422e68fc458a3a7dd7d8ada803a348e21f8f07', 'org.apache.httpcomponents:httpmime:4.5.6:httpmime-4.5.6.jar:0b2b1102c18d3c7e05a77214b9b7501a6f6056174ae5604e0e256776eda7553e',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8', 'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca', 'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349', 'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',

View File

@@ -7,7 +7,7 @@ apply plugin: 'witness'
apply from: 'witness.gradle' apply from: 'witness.gradle'
dependencies { dependencies {
implementation "com.google.dagger:dagger:2.19" implementation "com.google.dagger:dagger:2.22.1"
implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation 'com.google.code.findbugs:jsr305:3.0.2'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'

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

@@ -0,0 +1,18 @@
package org.briarproject.bramble.api;
/**
* Thrown when data being parsed uses a protocol or format version that is not
* supported.
*/
public class UnsupportedVersionException extends FormatException {
private final boolean tooOld;
public UnsupportedVersionException(boolean tooOld) {
this.tooOld = tooOld;
}
public boolean isTooOld() {
return tooOld;
}
}

View File

@@ -33,7 +33,8 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook {
/** /**
* Called once for each incoming message that passes validation. * Called once for each incoming message that passes validation.
* *
* @return whether or not this message should be shared * @param txn A read-write transaction
* @return Whether or not this message should be shared
* @throws DbException Should only be used for real database errors. * @throws DbException Should only be used for real database errors.
* If this is thrown, delivery will be attempted again at next startup, * If this is thrown, delivery will be attempted again at next startup,
* whereas if a FormatException is thrown, the message will be permanently * whereas if a FormatException is thrown, the message will be permanently

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,7 +1,10 @@
package org.briarproject.bramble.api.contact; package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UnsupportedVersionException;
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.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
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;
@@ -53,34 +56,34 @@ public interface ContactManager {
throws DbException; throws DbException;
/** /**
* Returns the static link that needs to be sent to the contact to be added. * Returns the handshake link that needs to be sent to a contact we want
* to add.
*/ */
String getRemoteContactLink(); String getHandshakeLink() throws DbException;
/** /**
* Returns true if the given link is syntactically valid. * Creates a {@link PendingContact} from the given handshake link and
*/ * alias, adds it to the database and returns it.
boolean isValidRemoteContactLink(String link);
/**
* Requests a new contact to be added via the given {@code link}.
* *
* @param link The link received from the contact we want to add. * @param link The handshake link received from the contact we want to add
* @param alias The alias the user has given this contact. * @param alias The alias the user has given this contact
* @return A PendingContact representing the contact to be added. * @return A PendingContact representing the contact to be added
* @throws UnsupportedVersionException If the link uses a format version
* that is not supported
* @throws FormatException If the link is invalid
*/ */
PendingContact addRemoteContactRequest(String link, String alias); PendingContact addPendingContact(String link, String alias)
throws DbException, FormatException;
/** /**
* Returns a list of {@link PendingContact}s. * Returns a list of {@link PendingContact}s.
*/ */
Collection<PendingContact> getPendingContacts(); Collection<PendingContact> getPendingContacts() throws DbException;
/** /**
* Removes a {@link PendingContact} that is in state * Removes a {@link PendingContact}.
* {@link PendingContactState FAILED}.
*/ */
void removePendingContact(PendingContact pendingContact); void removePendingContact(PendingContactId p) throws DbException;
/** /**
* Returns the contact with the given ID. * Returns the contact with the given ID.
@@ -91,7 +94,7 @@ public interface ContactManager {
* Returns the contact with the given remoteAuthorId * Returns the contact with the given remoteAuthorId
* that was added by the LocalAuthor with the given localAuthorId * that was added by the LocalAuthor with the given localAuthorId
* *
* @throws org.briarproject.bramble.api.db.NoSuchContactException * @throws NoSuchContactException If the contact is not in the database
*/ */
Contact getContact(AuthorId remoteAuthorId, AuthorId localAuthorId) Contact getContact(AuthorId remoteAuthorId, AuthorId localAuthorId)
throws DbException; throws DbException;
@@ -100,7 +103,7 @@ public interface ContactManager {
* Returns the contact with the given remoteAuthorId * Returns the contact with the given remoteAuthorId
* that was added by the LocalAuthor with the given localAuthorId * that was added by the LocalAuthor with the given localAuthorId
* *
* @throws org.briarproject.bramble.api.db.NoSuchContactException * @throws NoSuchContactException If the contact is not in the database
*/ */
Contact getContact(Transaction txn, AuthorId remoteAuthorId, Contact getContact(Transaction txn, AuthorId remoteAuthorId,
AuthorId localAuthorId) throws DbException; AuthorId localAuthorId) throws DbException;
@@ -156,8 +159,20 @@ public interface ContactManager {
interface ContactHook { interface ContactHook {
/**
* Called when a contact is being added.
*
* @param txn A read-write transaction
* @param c The contact that is being added
*/
void addingContact(Transaction txn, Contact c) throws DbException; void addingContact(Transaction txn, Contact c) throws DbException;
/**
* Called when a contact is being removed
*
* @param txn A read-write transaction
* @param c The contact that is being removed
*/
void removingContact(Transaction txn, Contact c) throws DbException; void removingContact(Transaction txn, Contact c) throws DbException;
} }
} }

View File

@@ -0,0 +1,34 @@
package org.briarproject.bramble.api.contact;
import java.util.regex.Pattern;
public interface HandshakeLinkConstants {
/**
* The current version of the handshake link format.
*/
int FORMAT_VERSION = 0;
/**
* The length of a base32-encoded handshake link in bytes, excluding the
* 'briar://' prefix.
*/
int BASE32_LINK_BYTES = 53;
/**
* The length of a raw handshake link in bytes, before base32 encoding.
*/
int RAW_LINK_BYTES = 33;
/**
* Regular expression for matching handshake links, including or excluding
* the 'briar://' prefix.
*/
Pattern LINK_REGEX =
Pattern.compile("(briar://)?([a-z2-7]{" + BASE32_LINK_BYTES + "})");
/**
* Label for hashing handshake public keys to calculate their identifiers.
*/
String ID_LABEL = "org.briarproject.bramble/HANDSHAKE_KEY_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

@@ -1,4 +1,4 @@
package org.briarproject.briar.api.introduction.event; package org.briarproject.bramble.api.contact.event;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
@@ -8,11 +8,11 @@ import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class IntroductionSucceededEvent extends Event { public class ContactAddedRemotelyEvent extends Event {
private final Contact contact; private final Contact contact;
public IntroductionSucceededEvent(Contact contact) { public ContactAddedRemotelyEvent(Contact contact) {
this.contact = contact; this.contact = contact;
} }

View File

@@ -0,0 +1,26 @@
package org.briarproject.bramble.api.contact.event;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when a pending contact is removed.
*/
@Immutable
@NotNullByDefault
public class PendingContactRemovedEvent extends Event {
private final PendingContactId id;
public PendingContactRemovedEvent(PendingContactId id) {
this.id = id;
}
public PendingContactId getId() {
return id;
}
}

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,10 +4,12 @@ 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;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.Identity;
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.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
@@ -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;
@@ -114,23 +113,9 @@ 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 * Stores an identity.
* key set ID.
*/ */
HandshakeKeySetId addHandshakeKeys(Transaction txn, ContactId c, void addIdentity(Transaction txn, Identity i) throws DbException;
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 a local pseudonym.
*/
void addLocalAuthor(Transaction txn, LocalAuthor a) throws DbException;
/** /**
* Stores a local message. * Stores a local message.
@@ -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;
/** /**
@@ -174,12 +166,12 @@ public interface DatabaseComponent {
boolean containsGroup(Transaction txn, GroupId g) throws DbException; boolean containsGroup(Transaction txn, GroupId g) throws DbException;
/** /**
* Returns true if the database contains the given local author. * Returns true if the database contains an identity for the given
* pseudonym.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
boolean containsLocalAuthor(Transaction txn, AuthorId local) boolean containsIdentity(Transaction txn, AuthorId a) throws DbException;
throws DbException;
/** /**
* Returns true if the database contains the given pending contact. * Returns true if the database contains the given pending contact.
@@ -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;
/** /**
@@ -309,26 +301,18 @@ public interface DatabaseComponent {
throws DbException; throws DbException;
/** /**
* Returns all handshake keys for the given transport. * Returns the identity for the local pseudonym with the given ID.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<HandshakeKeySet> getHandshakeKeys(Transaction txn, TransportId t) Identity getIdentity(Transaction txn, AuthorId a) throws DbException;
throws DbException;
/** /**
* Returns the local pseudonym with the given ID. * Returns the identities for all local pseudonyms.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
LocalAuthor getLocalAuthor(Transaction txn, AuthorId a) throws DbException; Collection<Identity> getIdentities(Transaction txn) throws DbException;
/**
* Returns all local pseudonyms.
* <p/>
* Read-only.
*/
Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException;
/** /**
* Returns the message with the given ID. * Returns the message with the given ID.
@@ -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
@@ -553,15 +531,9 @@ 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. * Removes an identity (and all associated state) from the database.
*/ */
void removeHandshakeKeys(Transaction txn, TransportId t, void removeIdentity(Transaction txn, AuthorId a) throws DbException;
HandshakeKeySetId k) throws DbException;
/**
* Removes a local pseudonym (and all associated state) from the database.
*/
void removeLocalAuthor(Transaction txn, AuthorId a) throws DbException;
/** /**
* Removes a message (and all associated state) from the database. * Removes a message (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.
@@ -620,31 +592,22 @@ public interface DatabaseComponent {
Collection<MessageId> dependencies) throws DbException; Collection<MessageId> dependencies) throws DbException;
/** /**
* Sets the reordering window for the given transport key set in the given * Sets the handshake key pair for the identity with the given ID.
* time period.
*/ */
void setReorderingWindow(Transaction txn, TransportKeySetId k, void setHandshakeKeyPair(Transaction txn, AuthorId local,
TransportId t, long timePeriod, long base, byte[] bitmap) PublicKey publicKey, PrivateKey privateKey) throws DbException;
throws DbException;
/** /**
* Sets the reordering window for the given handshake 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, HandshakeKeySetId 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;
/** /**
* 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,9 +1,9 @@
package org.briarproject.bramble.api.db; package org.briarproject.bramble.api.db;
/** /**
* Thrown when a database operation is attempted for a pseudonym that is not in * Thrown when a database operation is attempted for an identity that is not in
* the database. This exception may occur due to concurrent updates and does * the database. This exception may occur due to concurrent updates and does
* not indicate a database error. * not indicate a database error.
*/ */
public class NoSuchLocalAuthorException extends DbException { public class NoSuchIdentityException extends 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,23 +10,16 @@ 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
* name and keys. * name.
*/ */
LocalAuthor createLocalAuthor(String name, byte[] publicKey, LocalAuthor createLocalAuthor(String name);
byte[] privateKey);
/**
* Creates a local author with the given format version, name and keys.
*/
LocalAuthor createLocalAuthor(int formatVersion, String name,
byte[] publicKey, byte[] privateKey);
} }

View File

@@ -0,0 +1,88 @@
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 javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT;
@Immutable
@NotNullByDefault
public class Identity {
private final LocalAuthor localAuthor;
@Nullable
private final PublicKey handshakePublicKey;
@Nullable
private final PrivateKey handshakePrivateKey;
private final long created;
public Identity(LocalAuthor localAuthor,
@Nullable PublicKey handshakePublicKey,
@Nullable PrivateKey handshakePrivateKey, long created) {
if (handshakePublicKey != null) {
if (handshakePrivateKey == null)
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();
}
this.localAuthor = localAuthor;
this.handshakePublicKey = handshakePublicKey;
this.handshakePrivateKey = handshakePrivateKey;
this.created = created;
}
/**
* Returns the ID of the user's pseudonym.
*/
public AuthorId getId() {
return localAuthor.getId();
}
/**
* Returns the user's pseudonym.
*/
public LocalAuthor getLocalAuthor() {
return localAuthor;
}
/**
* Returns true if the identity has a handshake key pair.
*/
public boolean hasHandshakeKeyPair() {
return handshakePublicKey != null && handshakePrivateKey != null;
}
/**
* Returns the public key used for handshaking, or null if no key exists.
*/
@Nullable
public PublicKey getHandshakePublicKey() {
return handshakePublicKey;
}
/**
* Returns the private key used for handshaking, or null if no key exists.
*/
@Nullable
public PrivateKey getHandshakePrivateKey() {
return handshakePrivateKey;
}
/**
* Returns the time the identity was created, in milliseconds since the
* Unix epoch.
*/
public long getTimeCreated() {
return created;
}
}

View File

@@ -1,30 +1,30 @@
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.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.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault @NotNullByDefault
public interface IdentityManager { public interface IdentityManager {
/** /**
* Creates a local identity with the given name. * Creates an identity with the given name. The identity includes a
* handshake key pair.
*/ */
@CryptoExecutor @CryptoExecutor
LocalAuthor createLocalAuthor(String name); Identity createIdentity(String name);
/** /**
* Registers the given local identity with the manager. The identity is * Registers the given identity with the manager. This method should be
* not stored until {@link #storeLocalAuthor()} is called. * called before {@link LifecycleManager#startServices(SecretKey)}. The
* identity is stored when {@link LifecycleManager#startServices(SecretKey)}
* is called. The identity must include a handshake key pair.
*/ */
void registerLocalAuthor(LocalAuthor a); void registerIdentity(Identity i);
/**
* Stores the local identity registered with
* {@link #registerLocalAuthor(LocalAuthor)}, if any.
*/
void storeLocalAuthor() throws DbException;
/** /**
* Returns the cached local identity or loads it from the database. * Returns the cached local identity or loads it from the database.
@@ -33,7 +33,15 @@ public interface IdentityManager {
/** /**
* Returns the cached local identity or loads it from the database. * Returns the cached local identity or loads it from the database.
* <p/>
* Read-only.
*/ */
LocalAuthor getLocalAuthor(Transaction txn) throws DbException; LocalAuthor getLocalAuthor(Transaction txn) throws DbException;
/**
* Returns the cached handshake keys or loads them from the database.
* <p/>
* Read-only.
*/
KeyPair getHandshakeKeys(Transaction txn) throws DbException;
} }

View File

@@ -1,11 +1,12 @@
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.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
/** /**
* A pseudonym for the local user. * A pseudonym for the local user.
@@ -14,62 +15,20 @@ import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_K
@NotNullByDefault @NotNullByDefault
public class LocalAuthor extends Author { public class LocalAuthor extends Author {
private final byte[] privateKey; private final PrivateKey privateKey;
@Nullable
private final byte[] handshakePublicKey, handshakePrivateKey;
private final long created;
public LocalAuthor(AuthorId id, int formatVersion, String name, public LocalAuthor(AuthorId id, int formatVersion, String name,
byte[] publicKey, byte[] privateKey, long created) { PublicKey publicKey, PrivateKey privateKey) {
super(id, formatVersion, name, publicKey); super(id, formatVersion, name, publicKey);
this.privateKey = privateKey; if (!privateKey.getKeyType().equals(KEY_TYPE_SIGNATURE))
this.created = created;
handshakePublicKey = null;
handshakePrivateKey = null;
}
public LocalAuthor(AuthorId id, int formatVersion, String name,
byte[] publicKey, byte[] privateKey, byte[] handshakePublicKey,
byte[] handshakePrivateKey, long created) {
super(id, formatVersion, name, publicKey);
if (handshakePublicKey.length == 0 ||
handshakePublicKey.length > MAX_PUBLIC_KEY_LENGTH) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
}
this.privateKey = privateKey; this.privateKey = privateKey;
this.handshakePublicKey = handshakePublicKey;
this.handshakePrivateKey = handshakePrivateKey;
this.created = created;
} }
/** /**
* 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;
} }
/**
* Returns the public key used for handshaking, or null if no key exists.
*/
@Nullable
public byte[] getHandshakePublicKey() {
return handshakePublicKey;
}
/**
* Returns the private key used for handshaking, or null if no key exists.
*/
@Nullable
public byte[] getHandshakePrivateKey() {
return handshakePrivateKey;
}
/**
* Returns the time the pseudonym was created, in milliseconds since the
* Unix epoch.
*/
public long getTimeCreated() {
return created;
}
} }

View File

@@ -7,15 +7,15 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* An event that is broadcast when a local pseudonym is added. * An event that is broadcast when an identity is added.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class LocalAuthorAddedEvent extends Event { public class IdentityAddedEvent extends Event {
private final AuthorId authorId; private final AuthorId authorId;
public LocalAuthorAddedEvent(AuthorId authorId) { public IdentityAddedEvent(AuthorId authorId) {
this.authorId = authorId; this.authorId = authorId;
} }

View File

@@ -7,15 +7,15 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* An event that is broadcast when a local pseudonym is removed. * An event that is broadcast when an identity is removed.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class LocalAuthorRemovedEvent extends Event { public class IdentityRemovedEvent extends Event {
private final AuthorId authorId; private final AuthorId authorId;
public LocalAuthorRemovedEvent(AuthorId authorId) { public IdentityRemovedEvent(AuthorId authorId) {
this.authorId = authorId; this.authorId = authorId;
} }

View File

@@ -1,20 +0,0 @@
package org.briarproject.bramble.api.keyagreement;
import java.io.IOException;
/**
* Thrown when a QR code that has been scanned uses a protocol version that is
* not supported.
*/
public class UnsupportedVersionException extends IOException {
private final boolean tooOld;
public UnsupportedVersionException(boolean tooOld) {
this.tooOld = tooOld;
}
public boolean isTooOld() {
return tooOld;
}
}

View File

@@ -2,16 +2,16 @@ package org.briarproject.bramble.api.lifecycle;
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;
import org.briarproject.bramble.api.db.DbException;
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.sync.Client;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
/** /**
* Manages the lifecycle of the app, starting {@link Client Clients}, starting * Manages the lifecycle of the app: opening and closing the
* and stopping {@link Service Services}, shutting down * {@link DatabaseComponent} starting and stopping {@link Service Services},
* {@link ExecutorService ExecutorServices}, and opening and closing the * and shutting down {@link ExecutorService ExecutorServices}.
* {@link DatabaseComponent}.
*/ */
@NotNullByDefault @NotNullByDefault
public interface LifecycleManager { public interface LifecycleManager {
@@ -42,18 +42,19 @@ public interface LifecycleManager {
} }
} }
/**
* Registers a hook to be called after the database is opened and before
* {@link Service services} are started. This method should be called
* before {@link #startServices(SecretKey)}.
*/
void registerOpenDatabaseHook(OpenDatabaseHook hook);
/** /**
* Registers a {@link Service} to be started and stopped. This method * Registers a {@link Service} to be started and stopped. This method
* should be called before {@link #startServices(SecretKey)}. * should be called before {@link #startServices(SecretKey)}.
*/ */
void registerService(Service s); void registerService(Service s);
/**
* Registers a {@link Client} to be started. This method should be called
* before {@link #startServices(SecretKey)}.
*/
void registerClient(Client c);
/** /**
* Registers an {@link ExecutorService} to be shut down. This method * Registers an {@link ExecutorService} to be shut down. This method
* should be called before {@link #startServices(SecretKey)}. * should be called before {@link #startServices(SecretKey)}.
@@ -62,7 +63,7 @@ public interface LifecycleManager {
/** /**
* Opens the {@link DatabaseComponent} using the given key and starts any * Opens the {@link DatabaseComponent} using the given key and starts any
* registered {@link Client Clients} and {@link Service Services}. * registered {@link Service Services}.
*/ */
StartResult startServices(SecretKey dbKey); StartResult startServices(SecretKey dbKey);
@@ -80,8 +81,7 @@ public interface LifecycleManager {
/** /**
* Waits for the {@link DatabaseComponent} to be opened and all registered * Waits for the {@link DatabaseComponent} to be opened and all registered
* {@link Client Clients} and {@link Service Services} to start before * {@link Service Services} to start before returning.
* returning.
*/ */
void waitForStartup() throws InterruptedException; void waitForStartup() throws InterruptedException;
@@ -97,4 +97,13 @@ public interface LifecycleManager {
*/ */
LifecycleState getLifecycleState(); LifecycleState getLifecycleState();
interface OpenDatabaseHook {
/**
* Called when the database is being opened, before
* {@link #waitForDatabase()} returns.
*
* @param txn A read-write transaction
*/
void onDatabaseOpened(Transaction txn) throws DbException;
}
} }

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,14 +0,0 @@
package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface Client {
/**
* Called at startup to create any local state needed by the client.
*/
void createLocalState(Transaction txn) throws DbException;
}

View File

@@ -11,7 +11,8 @@ public interface IncomingMessageHook {
/** /**
* Called once for each incoming message that passes validation. * Called once for each incoming message that passes validation.
* *
* @return whether or not this message should be shared * @param txn A read-write transaction
* @return Whether or not this message should be shared
* @throws DbException Should only be used for real database errors. * @throws DbException Should only be used for real database errors.
* If this is thrown, delivery will be attempted again at next startup, * If this is thrown, delivery will be attempted again at next startup,
* whereas if an InvalidMessageException is thrown, * whereas if an InvalidMessageException is thrown,

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

@@ -46,7 +46,14 @@ public interface ClientVersioningManager {
ClientId clientId, int majorVersion) throws DbException; ClientId clientId, int majorVersion) throws DbException;
interface ClientVersioningHook { interface ClientVersioningHook {
/**
* Called when the visibility of a client with respect to a contact is
* changing.
*
* @param txn A read-write transaction
* @param c The contact affected by the visibility change
* @param v The new visibility of the client
*/
void onClientVisibilityChanging(Transaction txn, Contact c, void onClientVisibilityChanging(Transaction txn, Contact c,
Visibility v) throws DbException; Visibility v) throws DbException;
} }

View File

@@ -0,0 +1,75 @@
package org.briarproject.bramble.util;
import java.io.ByteArrayOutputStream;
public class Base32 {
private static final char[] DIGITS = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', '2', '3', '4', '5', '6', '7'
};
public static String encode(byte[] b) {
StringBuilder s = new StringBuilder();
int byteIndex = 0, currentCode = 0x00;
int byteMask = 0x80, codeMask = 0x10;
while (byteIndex < b.length) {
if ((b[byteIndex] & byteMask) != 0) currentCode |= codeMask;
// After every 8 bits, move on to the next byte
if (byteMask == 0x01) {
byteMask = 0x80;
byteIndex++;
} else {
byteMask >>>= 1;
}
// After every 5 bits, move on to the next digit
if (codeMask == 0x01) {
s.append(DIGITS[currentCode]);
codeMask = 0x10;
currentCode = 0x00;
} else {
codeMask >>>= 1;
}
}
// If we're part-way through a digit, output it
if (codeMask != 0x10) s.append(DIGITS[currentCode]);
return s.toString();
}
public static byte[] decode(String s, boolean strict) {
ByteArrayOutputStream b = new ByteArrayOutputStream();
int digitIndex = 0, digitCount = s.length(), currentByte = 0x00;
int byteMask = 0x80, codeMask = 0x10;
while (digitIndex < digitCount) {
int code = decodeDigit(s.charAt(digitIndex));
if ((code & codeMask) != 0) currentByte |= byteMask;
// After every 8 bits, move on to the next byte
if (byteMask == 0x01) {
b.write(currentByte);
byteMask = 0x80;
currentByte = 0x00;
} else {
byteMask >>>= 1;
}
// After every 5 bits, move on to the next digit
if (codeMask == 0x01) {
codeMask = 0x10;
digitIndex++;
} else {
codeMask >>>= 1;
}
}
// If any extra bits were used for encoding, they should all be zero
if (strict && byteMask != 0x80 && currentByte != 0x00)
throw new IllegalArgumentException();
return b.toByteArray();
}
private static int decodeDigit(char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a';
if (c >= '2' && c <= '7') return c - '2' + 26;
throw new IllegalArgumentException("Not a base32 digit: " + c);
}
}

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

@@ -153,4 +153,13 @@ public class StringUtils {
return new String(c); return new String(c);
} }
public static String getRandomBase32String(int length) {
char[] c = new char[length];
for (int i = 0; i < length; i++) {
int character = random.nextInt(32);
if (character < 26) c[i] = (char) ('a' + character);
else c[i] = (char) ('2' + (character - 26));
}
return new String(c);
}
} }

View File

@@ -5,9 +5,17 @@ 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.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
@@ -29,10 +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_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;
@@ -99,27 +107,46 @@ public class TestUtils {
return new SecretKey(getRandomBytes(SecretKey.LENGTH)); return new SecretKey(getRandomBytes(SecretKey.LENGTH));
} }
public static LocalAuthor getLocalAuthor() { public static PublicKey getSignaturePublicKey() {
return getLocalAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH)); byte[] key = getRandomBytes(MAX_SIGNATURE_PUBLIC_KEY_BYTES);
return new SignaturePublicKey(key);
} }
public static LocalAuthor getLocalAuthor(int nameLength) { public static PrivateKey getSignaturePrivateKey() {
AuthorId id = new AuthorId(getRandomId()); return new SignaturePrivateKey(getRandomBytes(123));
String name = getRandomString(nameLength); }
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); public static PublicKey getAgreementPublicKey() {
return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey, 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() {
LocalAuthor localAuthor = getLocalAuthor();
PublicKey handshakePub = getAgreementPublicKey();
PrivateKey handshakePriv = getAgreementPrivateKey();
return new Identity(localAuthor, handshakePub, handshakePriv,
timestamp); timestamp);
} }
public static Author getAuthor() { public static LocalAuthor getLocalAuthor() {
return getAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH)); AuthorId id = new AuthorId(getRandomId());
int nameLength = 1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH);
String name = getRandomString(nameLength);
PublicKey publicKey = getSignaturePublicKey();
PrivateKey privateKey = getSignaturePrivateKey();
return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey);
} }
public static Author getAuthor(int nameLength) { public static Author getAuthor() {
AuthorId id = new AuthorId(getRandomId()); AuthorId id = new AuthorId(getRandomId());
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);
} }
@@ -152,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() {
@@ -176,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

@@ -2,7 +2,7 @@ dependencyVerification {
verify = [ verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861', 'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7', 'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.dagger:dagger:2.19:dagger-2.19.jar:514b6f1e0727c6572e1d65cb27e4ae668b7aeaeb93a29515182965265b609939', 'com.google.dagger:dagger:2.22.1:dagger-2.22.1.jar:329d4340f24c4f5717af016c097e90668bfea2a5376e6aa9964b01cef3fd241a',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a', 'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d', 'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',

View File

@@ -17,7 +17,7 @@ dependencies {
implementation 'org.whispersystems:curve25519-java:0.5.0' implementation 'org.whispersystems:curve25519-java:0.5.0'
implementation 'org.briarproject:jtorctl:0.3' implementation 'org.briarproject:jtorctl:0.3'
annotationProcessor 'com.google.dagger:dagger-compiler:2.19' annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
testImplementation project(path: ':bramble-api', configuration: 'testOutput') testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6 testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6
@@ -26,7 +26,7 @@ dependencies {
testImplementation "org.jmock:jmock-junit4:2.8.2" testImplementation "org.jmock:jmock-junit4:2.8.2"
testImplementation "org.jmock:jmock-legacy:2.8.2" testImplementation "org.jmock:jmock-legacy:2.8.2"
testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.19' testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
signature 'org.codehaus.mojo.signature:java16:1.1@signature' signature 'org.codehaus.mojo.signature:java16:1.1@signature'
} }

View File

@@ -4,8 +4,8 @@ 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.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.identity.Identity;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
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.bramble.util.IoUtils; import org.briarproject.bramble.util.IoUtils;
@@ -161,8 +161,8 @@ class AccountManagerImpl implements AccountManager {
synchronized (stateChangeLock) { synchronized (stateChangeLock) {
if (hasDatabaseKey()) if (hasDatabaseKey())
throw new AssertionError("Already have a database key"); throw new AssertionError("Already have a database key");
LocalAuthor localAuthor = identityManager.createLocalAuthor(name); Identity identity = identityManager.createIdentity(name);
identityManager.registerLocalAuthor(localAuthor); identityManager.registerIdentity(identity);
SecretKey key = crypto.generateSecretKey(); SecretKey key = crypto.generateSecretKey();
if (!encryptAndStoreDatabaseKey(key, password)) return false; if (!encryptAndStoreDatabaseKey(key, password)) return false;
databaseKey = key; databaseKey = key;

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

@@ -1,5 +1,6 @@
package org.briarproject.bramble.contact; package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException;
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.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
@@ -20,45 +21,42 @@ import org.briarproject.bramble.api.transport.KeyManager;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Pattern;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.emptyList; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
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.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
import static org.briarproject.bramble.util.StringUtils.getRandomBase32String;
import static org.briarproject.bramble.util.StringUtils.toUtf8; import static org.briarproject.bramble.util.StringUtils.toUtf8;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
class ContactManagerImpl implements ContactManager { class ContactManagerImpl implements ContactManager {
private static final int LINK_LENGTH = 64;
private static final String REMOTE_CONTACT_LINK = private static final String REMOTE_CONTACT_LINK =
"briar://" + getRandomBase32String(LINK_LENGTH); "briar://" + getRandomBase32String(BASE32_LINK_BYTES);
private static final Pattern LINK_REGEX =
Pattern.compile("(briar://)?([a-z2-7]{" + LINK_LENGTH + "})");
private final DatabaseComponent db; private final DatabaseComponent db;
private final KeyManager keyManager; private final KeyManager keyManager;
private final IdentityManager identityManager; private final IdentityManager identityManager;
private final PendingContactFactory pendingContactFactory;
private final List<ContactHook> hooks; private final List<ContactHook> hooks;
@Inject @Inject
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager, ContactManagerImpl(DatabaseComponent db, KeyManager keyManager,
IdentityManager identityManager) { IdentityManager identityManager,
PendingContactFactory pendingContactFactory) {
this.db = db; this.db = db;
this.keyManager = keyManager; this.keyManager = keyManager;
this.identityManager = identityManager; this.identityManager = identityManager;
this.pendingContactFactory = pendingContactFactory;
hooks = new CopyOnWriteArrayList<>(); hooks = new CopyOnWriteArrayList<>();
} }
@@ -72,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;
@@ -97,45 +96,28 @@ class ContactManagerImpl implements ContactManager {
} }
@Override @Override
public String getRemoteContactLink() { public String getHandshakeLink() {
// TODO replace with real implementation // TODO replace with real implementation
return REMOTE_CONTACT_LINK; return REMOTE_CONTACT_LINK;
} }
@SuppressWarnings("SameParameterValue") @Override
private static String getRandomBase32String(int length) { public PendingContact addPendingContact(String link, String alias)
Random random = new Random(); throws DbException, FormatException {
char[] c = new char[length]; PendingContact p =
for (int i = 0; i < length; i++) { pendingContactFactory.createPendingContact(link, alias);
int character = random.nextInt(32); db.transaction(false, txn -> db.addPendingContact(txn, p));
if (character < 26) c[i] = (char) ('a' + character); return p;
else c[i] = (char) ('2' + (character - 26));
}
return new String(c);
} }
@Override @Override
public boolean isValidRemoteContactLink(String link) { public Collection<PendingContact> getPendingContacts() throws DbException {
return LINK_REGEX.matcher(link).matches(); return db.transactionWithResult(true, db::getPendingContacts);
} }
@Override @Override
public PendingContact addRemoteContactRequest(String link, String alias) { public void removePendingContact(PendingContactId p) throws DbException {
// TODO replace with real implementation db.transaction(false, txn -> db.removePendingContact(txn, p));
PendingContactId id = new PendingContactId(link.getBytes());
return new PendingContact(id, new byte[MAX_PUBLIC_KEY_LENGTH], alias,
WAITING_FOR_CONNECTION, System.currentTimeMillis());
}
@Override
public Collection<PendingContact> getPendingContacts() {
// TODO replace with real implementation
return emptyList();
}
@Override
public void removePendingContact(PendingContact pendingContact) {
// TODO replace with real implementation
} }
@Override @Override

View File

@@ -28,4 +28,10 @@ public class ContactModule {
ContactExchangeTaskImpl contactExchangeTask) { ContactExchangeTaskImpl contactExchangeTask) {
return contactExchangeTask; return contactExchangeTask;
} }
@Provides
PendingContactFactory providePendingContactFactory(
PendingContactFactoryImpl pendingContactFactory) {
return pendingContactFactory;
}
} }

View File

@@ -0,0 +1,18 @@
package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.contact.PendingContact;
interface PendingContactFactory {
/**
* Creates a {@link PendingContact} from the given handshake link and alias.
*
* @throws UnsupportedVersionException If the link uses a format version
* that is not supported
* @throws FormatException If the link is invalid
*/
PendingContact createPendingContact(String link, String alias)
throws FormatException;
}

View File

@@ -0,0 +1,70 @@
package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.Base32;
import java.security.GeneralSecurityException;
import java.util.regex.Matcher;
import javax.inject.Inject;
import static java.lang.System.arraycopy;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.FORMAT_VERSION;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.ID_LABEL;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
class PendingContactFactoryImpl implements PendingContactFactory {
private final CryptoComponent crypto;
private final Clock clock;
@Inject
PendingContactFactoryImpl(CryptoComponent crypto, Clock clock) {
this.crypto = crypto;
this.clock = clock;
}
@Override
public PendingContact createPendingContact(String link, String alias)
throws FormatException {
PublicKey publicKey = parseHandshakeLink(link);
PendingContactId id = getPendingContactId(publicKey);
long timestamp = clock.currentTimeMillis();
return new PendingContact(id, publicKey, alias, WAITING_FOR_CONNECTION,
timestamp);
}
private PublicKey parseHandshakeLink(String link) throws FormatException {
Matcher matcher = LINK_REGEX.matcher(link);
if (!matcher.find()) throw new FormatException();
// Discard 'briar://' and anything before or after the link
link = matcher.group(2);
byte[] base32 = Base32.decode(link, false);
if (base32.length != RAW_LINK_BYTES) throw new AssertionError();
byte version = base32[0];
if (version != FORMAT_VERSION)
throw new UnsupportedVersionException(version < FORMAT_VERSION);
byte[] publicKeyBytes = new byte[base32.length - 1];
arraycopy(base32, 1, publicKeyBytes, 0, publicKeyBytes.length);
try {
KeyParser parser = crypto.getAgreementKeyParser();
return parser.parsePublicKey(publicKeyBytes);
} catch (GeneralSecurityException e) {
throw new FormatException();
}
}
private PendingContactId getPendingContactId(PublicKey publicKey) {
byte[] hash = crypto.hash(ID_LABEL, publicKey.getEncoded());
return new PendingContactId(hash);
}
}

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;
@@ -15,7 +17,7 @@ import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.MigrationListener; import org.briarproject.bramble.api.db.MigrationListener;
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.LocalAuthor; import org.briarproject.bramble.api.identity.Identity;
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.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
@@ -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;
@@ -106,23 +105,9 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Stores the given handshake keys for the given contact and returns a * Stores an identity.
* key set ID.
*/ */
HandshakeKeySetId addHandshakeKeys(T txn, ContactId c, HandshakeKeys k) void addIdentity(T txn, Identity i) throws DbException;
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 a local pseudonym.
*/
void addLocalAuthor(T txn, LocalAuthor a) throws DbException;
/** /**
* Stores a message. * Stores a message.
@@ -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;
/** /**
@@ -187,11 +179,12 @@ interface Database<T> {
boolean containsGroup(T txn, GroupId g) throws DbException; boolean containsGroup(T txn, GroupId g) throws DbException;
/** /**
* Returns true if the database contains the given local pseudonym. * Returns true if the database contains an identity for the given
* pseudonym.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
boolean containsLocalAuthor(T txn, AuthorId a) throws DbException; boolean containsIdentity(T txn, AuthorId a) throws DbException;
/** /**
* Returns true if the database contains the given message. * Returns true if the database contains the given message.
@@ -272,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,26 +308,18 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns all handshake keys for the given transport. * Returns the identity for local pseudonym with the given ID.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<HandshakeKeySet> getHandshakeKeys(T txn, TransportId t) Identity getIdentity(T txn, AuthorId a) throws DbException;
throws DbException;
/** /**
* Returns the local pseudonym with the given ID. * Returns the identities for all local pseudonyms.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
LocalAuthor getLocalAuthor(T txn, AuthorId a) throws DbException; Collection<Identity> getIdentities(T txn) throws DbException;
/**
* Returns all local pseudonyms.
* <p/>
* Read-only.
*/
Collection<LocalAuthor> getLocalAuthors(T txn) throws DbException;
/** /**
* Returns the message with the given ID. * Returns the message with the given ID.
@@ -544,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,15 +602,9 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Removes the given handshake keys from the database. * Removes an identity (and all associated state) from the database.
*/ */
void removeHandshakeKeys(T txn, TransportId t, HandshakeKeySetId k) void removeIdentity(T txn, AuthorId a) throws DbException;
throws DbException;
/**
* Removes a local pseudonym (and all associated state) from the database.
*/
void removeLocalAuthor(T txn, AuthorId a) throws DbException;
/** /**
* Removes a message (and all associated state) from the database. * Removes a message (and all associated state) from the database.
@@ -658,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
@@ -685,6 +657,12 @@ interface Database<T> {
void setGroupVisibility(T txn, ContactId c, GroupId g, boolean shared) void setGroupVisibility(T txn, ContactId c, GroupId g, boolean shared)
throws DbException; throws DbException;
/**
* Sets the handshake key pair for the identity with the given ID.
*/
void setHandshakeKeyPair(T txn, AuthorId local, PublicKey publicKey,
PrivateKey privateKey) throws DbException;
/** /**
* Marks the given message as shared. * Marks the given message as shared.
*/ */
@@ -703,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;
/** /**
@@ -731,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;
@@ -20,7 +24,7 @@ import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.MigrationListener; import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.db.NoSuchContactException; import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.NoSuchGroupException; import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException; import org.briarproject.bramble.api.db.NoSuchIdentityException;
import org.briarproject.bramble.api.db.NoSuchMessageException; import org.briarproject.bramble.api.db.NoSuchMessageException;
import org.briarproject.bramble.api.db.NoSuchPendingContactException; import org.briarproject.bramble.api.db.NoSuchPendingContactException;
import org.briarproject.bramble.api.db.NoSuchTransportException; import org.briarproject.bramble.api.db.NoSuchTransportException;
@@ -32,9 +36,9 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor; import org.briarproject.bramble.api.event.EventExecutor;
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.LocalAuthor; import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.event.LocalAuthorAddedEvent; import org.briarproject.bramble.api.identity.event.IdentityAddedEvent;
import org.briarproject.bramble.api.identity.event.LocalAuthorRemovedEvent; import org.briarproject.bramble.api.identity.event.IdentityRemovedEvent;
import org.briarproject.bramble.api.lifecycle.ShutdownManager; import org.briarproject.bramble.api.lifecycle.ShutdownManager;
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;
@@ -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;
@@ -237,9 +238,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throws DbException { throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsLocalAuthor(txn, local)) if (!db.containsIdentity(txn, local))
throw new NoSuchLocalAuthorException(); throw new NoSuchIdentityException();
if (db.containsLocalAuthor(txn, remote.getId())) if (db.containsIdentity(txn, remote.getId()))
throw new ContactExistsException(); throw new ContactExistsException();
if (db.containsContact(txn, remote.getId(), local)) if (db.containsContact(txn, remote.getId(), local))
throw new ContactExistsException(); throw new ContactExistsException();
@@ -259,37 +260,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public HandshakeKeySetId addHandshakeKeys(Transaction transaction, public void addIdentity(Transaction transaction, Identity i)
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
public void addLocalAuthor(Transaction transaction, LocalAuthor a)
throws DbException { throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsLocalAuthor(txn, a.getId())) { if (!db.containsIdentity(txn, i.getId())) {
db.addLocalAuthor(txn, a); db.addIdentity(txn, i);
transaction.attach(new LocalAuthorAddedEvent(a.getId())); transaction.attach(new IdentityAddedEvent(i.getId()));
} }
} }
@@ -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,12 +320,24 @@ 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 {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsLocalAuthor(txn, local)) if (!db.containsIdentity(txn, local))
throw new NoSuchLocalAuthorException(); throw new NoSuchIdentityException();
return db.containsContact(txn, remote, local); return db.containsContact(txn, remote, local);
} }
@@ -358,10 +349,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public boolean containsLocalAuthor(Transaction transaction, AuthorId local) public boolean containsIdentity(Transaction transaction, AuthorId a)
throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.containsLocalAuthor(txn, local); return db.containsIdentity(txn, a);
} }
@Override @Override
@@ -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.containsLocalAuthor(txn, a)) if (!db.containsIdentity(txn, local))
throw new NoSuchLocalAuthorException(); throw new NoSuchIdentityException();
return db.getContacts(txn, a); return db.getContacts(txn, local);
} }
@Override @Override
@@ -545,28 +536,19 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public Collection<HandshakeKeySet> getHandshakeKeys(Transaction transaction, public Identity getIdentity(Transaction transaction, AuthorId a)
TransportId t) throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsTransport(txn, t)) if (!db.containsIdentity(txn, a))
throw new NoSuchTransportException(); throw new NoSuchIdentityException();
return db.getHandshakeKeys(txn, t); return db.getIdentity(txn, a);
} }
@Override @Override
public LocalAuthor getLocalAuthor(Transaction transaction, AuthorId a) public Collection<Identity> getIdentities(Transaction transaction)
throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsLocalAuthor(txn, a)) return db.getIdentities(txn);
throw new NoSuchLocalAuthorException();
return db.getLocalAuthor(txn, a);
}
@Override
public Collection<LocalAuthor> getLocalAuthors(Transaction transaction)
throws DbException {
T txn = unbox(transaction);
return db.getLocalAuthors(txn);
} }
@Override @Override
@@ -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))
@@ -895,24 +867,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public void removeHandshakeKeys(Transaction transaction, public void removeIdentity(Transaction transaction, AuthorId a)
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
public void removeLocalAuthor(Transaction transaction, AuthorId a)
throws DbException { throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsLocalAuthor(txn, a)) if (!db.containsIdentity(txn, a))
throw new NoSuchLocalAuthorException(); throw new NoSuchIdentityException();
db.removeLocalAuthor(txn, a); db.removeIdentity(txn, a);
transaction.attach(new LocalAuthorRemovedEvent(a)); transaction.attach(new IdentityRemovedEvent(a));
} }
@Override @Override
@@ -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))
@@ -1036,20 +999,19 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public void setReorderingWindow(Transaction transaction, public void setHandshakeKeyPair(Transaction transaction, AuthorId local,
TransportKeySetId k, TransportId t, long timePeriod, long base, PublicKey publicKey, PrivateKey privateKey) throws DbException {
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.containsIdentity(txn, local))
throw new NoSuchTransportException(); throw new NoSuchIdentityException();
db.setReorderingWindow(txn, k, t, timePeriod, base, bitmap); db.setHandshakeKeyPair(txn, local, publicKey, privateKey);
} }
@Override @Override
public void setReorderingWindow(Transaction transaction, public void setReorderingWindow(Transaction transaction, KeySetId k,
HandshakeKeySetId 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(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsTransport(txn, t)) if (!db.containsTransport(txn, t))
@@ -1059,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))
@@ -1067,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;
@@ -15,6 +21,7 @@ import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.MigrationListener; import org.briarproject.bramble.api.db.MigrationListener;
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.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.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
@@ -29,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;
@@ -61,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;
@@ -92,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;
@@ -253,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)"
@@ -270,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 =
@@ -290,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)";
@@ -366,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;
@@ -486,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)
); );
} }
@@ -534,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);
@@ -676,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,105 +744,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public HandshakeKeySetId addHandshakeKeys(Connection txn, ContactId c, public void addIdentity(Connection txn, Identity i) throws DbException {
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
public void addLocalAuthor(Connection txn, LocalAuthor a)
throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "INSERT INTO localAuthors" String sql = "INSERT INTO localAuthors"
@@ -883,16 +752,17 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " handshakePublicKey, handshakePrivateKey, created)" + " handshakePublicKey, handshakePrivateKey, created)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; + " VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, a.getId().getBytes()); LocalAuthor local = i.getLocalAuthor();
ps.setInt(2, a.getFormatVersion()); ps.setBytes(1, local.getId().getBytes());
ps.setString(3, a.getName()); ps.setInt(2, local.getFormatVersion());
ps.setBytes(4, a.getPublicKey()); ps.setString(3, local.getName());
ps.setBytes(5, a.getPrivateKey()); ps.setBytes(4, local.getPublicKey().getEncoded());
if (a.getHandshakePublicKey() == null) ps.setNull(6, BINARY); ps.setBytes(5, local.getPrivateKey().getEncoded());
else ps.setBytes(6, a.getHandshakePublicKey()); if (i.getHandshakePublicKey() == null) ps.setNull(6, BINARY);
if (a.getHandshakePrivateKey() == null) ps.setNull(7, BINARY); else ps.setBytes(6, i.getHandshakePublicKey().getEncoded());
else ps.setBytes(7, a.getHandshakePrivateKey()); if (i.getHandshakePrivateKey() == null) ps.setNull(7, BINARY);
ps.setLong(8, a.getTimeCreated()); else ps.setBytes(7, i.getHandshakePrivateKey().getEncoded());
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();
ps.close(); ps.close();
@@ -1067,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());
@@ -1100,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();
@@ -1127,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());
@@ -1248,7 +1141,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public boolean containsLocalAuthor(Connection txn, AuthorId a) public boolean containsIdentity(Connection txn, AuthorId a)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
@@ -1443,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) {
@@ -1478,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));
} }
@@ -1539,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,8 +1560,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public LocalAuthor getLocalAuthor(Connection txn, AuthorId a) public Identity getIdentity(Connection txn, AuthorId a) throws DbException {
throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
@@ -1676,18 +1574,22 @@ 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);
LocalAuthor localAuthor = new LocalAuthor(a, formatVersion, name,
publicKey, privateKey, handshakePublicKey,
handshakePrivateKey, created);
if (rs.next()) throw new DbStateException(); if (rs.next()) throw new DbStateException();
rs.close(); rs.close();
ps.close(); ps.close();
return localAuthor; LocalAuthor local = new LocalAuthor(a, formatVersion, name,
publicKey, privateKey);
PublicKey handshakePublicKey = handshakePub == null ?
null : new AgreementPublicKey(handshakePub);
PrivateKey handshakePrivateKey = handshakePriv == null ?
null : new AgreementPrivateKey(handshakePriv);
return new Identity(local, handshakePublicKey, handshakePrivateKey,
created);
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs, LOG, WARNING); tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING); tryToClose(ps, LOG, WARNING);
@@ -1696,110 +1598,39 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public Collection<HandshakeKeySet> getHandshakeKeys(Connection txn, public Collection<Identity> getIdentities(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
public Collection<LocalAuthor> getLocalAuthors(Connection txn)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT authorId, formatVersion, name, publicKey," String sql = "SELECT authorId, formatVersion, name, publicKey,"
+ " privateKey, created" + " privateKey, handshakePublicKey, handshakePrivateKey,"
+ " created"
+ " FROM localAuthors"; + " FROM localAuthors";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
rs = ps.executeQuery(); rs = ps.executeQuery();
List<LocalAuthor> authors = new ArrayList<>(); List<Identity> identities = new ArrayList<>();
while (rs.next()) { while (rs.next()) {
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));
long created = rs.getLong(6); byte[] handshakePub = rs.getBytes(6);
authors.add(new LocalAuthor(authorId, formatVersion, name, byte[] handshakePriv = rs.getBytes(7);
publicKey, privateKey, created)); long created = rs.getLong(8);
LocalAuthor local = new LocalAuthor(authorId, formatVersion,
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,
handshakePrivateKey, created));
} }
rs.close(); rs.close();
ps.close(); ps.close();
return authors; return identities;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs, LOG, WARNING); tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING); tryToClose(ps, LOG, WARNING);
@@ -2390,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));
@@ -2497,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";
@@ -2509,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();
@@ -2539,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"
@@ -2937,29 +2760,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public void removeHandshakeKeys(Connection txn, TransportId t, public void removeIdentity(Connection txn, AuthorId a) throws DbException {
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
public void removeLocalAuthor(Connection txn, AuthorId a)
throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "DELETE FROM localAuthors WHERE authorId = ?"; String sql = "DELETE FROM localAuthors WHERE authorId = ?";
@@ -3070,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
@@ -3176,6 +2977,27 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public void setHandshakeKeyPair(Connection txn, AuthorId local,
PublicKey publicKey, PrivateKey privateKey) throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE localAuthors"
+ " SET handshakePublicKey = ?, handshakePrivateKey = ?"
+ " WHERE authorId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, publicKey.getEncoded());
ps.setBytes(2, privateKey.getEncoded());
ps.setBytes(3, local.getBytes());
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 setMessageShared(Connection txn, MessageId m) public void setMessageShared(Connection txn, MessageId m)
throws DbException { throws DbException {
@@ -3266,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);
@@ -3275,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;
@@ -3298,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"
@@ -3444,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

@@ -1,14 +1,14 @@
package org.briarproject.bramble.identity; 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.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.api.system.Clock;
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,50 +16,46 @@ 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
class AuthorFactoryImpl implements AuthorFactory { class AuthorFactoryImpl implements AuthorFactory {
private final CryptoComponent crypto; private final CryptoComponent crypto;
private final Clock clock;
@Inject @Inject
AuthorFactoryImpl(CryptoComponent crypto, Clock clock) { AuthorFactoryImpl(CryptoComponent crypto) {
this.crypto = crypto; this.crypto = crypto;
this.clock = clock;
} }
@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);
} }
@Override @Override
public LocalAuthor createLocalAuthor(String name, byte[] publicKey, public LocalAuthor createLocalAuthor(String name) {
byte[] privateKey) { KeyPair signatureKeyPair = crypto.generateSignatureKeyPair();
return createLocalAuthor(FORMAT_VERSION, name, publicKey, privateKey); PublicKey publicKey = signatureKeyPair.getPublic();
PrivateKey privateKey = signatureKeyPair.getPrivate();
AuthorId id = getId(FORMAT_VERSION, name, publicKey);
return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey);
} }
@Override private AuthorId getId(int formatVersion, String name,
public LocalAuthor createLocalAuthor(int formatVersion, String name, PublicKey publicKey) {
byte[] publicKey, byte[] privateKey) {
AuthorId id = getId(formatVersion, name, publicKey);
return new LocalAuthor(id, formatVersion, name, publicKey, privateKey,
clock.currentTimeMillis());
}
private AuthorId getId(int formatVersion, String name, byte[] 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,101 +2,170 @@ 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;
import org.briarproject.bramble.api.identity.AuthorFactory; import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import java.util.Collection;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
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;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
class IdentityManagerImpl implements IdentityManager { class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(IdentityManagerImpl.class.getName()); getLogger(IdentityManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final CryptoComponent crypto; private final CryptoComponent crypto;
private final AuthorFactory authorFactory; private final AuthorFactory authorFactory;
private final Clock clock;
// The local author is immutable so we can cache it /**
* The user's identity, or null if no identity has been registered or
* loaded. If non-null, this identity always has handshake keys.
*/
@Nullable @Nullable
private volatile LocalAuthor cachedAuthor; private volatile Identity cachedIdentity = null;
/**
* True if {@code cachedIdentity} was registered via
* {@link #registerIdentity(Identity)} and should be stored when
* {@link #onDatabaseOpened(Transaction)} is called.
*/
private volatile boolean shouldStoreIdentity = false;
/**
* True if the handshake keys in {@code cachedIdentity} were generated
* when the identity was loaded and should be stored when
* {@link #onDatabaseOpened(Transaction)} is called.
*/
private volatile boolean shouldStoreKeys = false;
@Inject @Inject
IdentityManagerImpl(DatabaseComponent db, CryptoComponent crypto, IdentityManagerImpl(DatabaseComponent db, CryptoComponent crypto,
AuthorFactory authorFactory) { AuthorFactory authorFactory, Clock clock) {
this.db = db; this.db = db;
this.crypto = crypto; this.crypto = crypto;
this.authorFactory = authorFactory; this.authorFactory = authorFactory;
this.clock = clock;
} }
@Override @Override
public LocalAuthor createLocalAuthor(String name) { public Identity createIdentity(String name) {
long start = now(); long start = now();
KeyPair keyPair = crypto.generateSignatureKeyPair(); LocalAuthor localAuthor = authorFactory.createLocalAuthor(name);
byte[] publicKey = keyPair.getPublic().getEncoded(); KeyPair handshakeKeyPair = crypto.generateAgreementKeyPair();
byte[] privateKey = keyPair.getPrivate().getEncoded(); PublicKey handshakePub = handshakeKeyPair.getPublic();
LocalAuthor localAuthor = authorFactory.createLocalAuthor(name, PrivateKey handshakePriv = handshakeKeyPair.getPrivate();
publicKey, privateKey); logDuration(LOG, "Creating identity", start);
logDuration(LOG, "Creating local author", start); return new Identity(localAuthor, handshakePub, handshakePriv,
return localAuthor; clock.currentTimeMillis());
} }
@Override @Override
public void registerLocalAuthor(LocalAuthor a) { public void registerIdentity(Identity i) {
cachedAuthor = a; if (!i.hasHandshakeKeyPair()) throw new IllegalArgumentException();
LOG.info("Local author registered"); cachedIdentity = i;
shouldStoreIdentity = true;
LOG.info("Identity registered");
} }
@Override @Override
public void storeLocalAuthor() throws DbException { public void onDatabaseOpened(Transaction txn) throws DbException {
LocalAuthor cached = cachedAuthor; Identity cached = getCachedIdentity(txn);
if (cached == null) { if (shouldStoreIdentity) {
LOG.info("No local author to store"); // The identity was registered at startup - store it
return; db.addIdentity(txn, cached);
LOG.info("Identity stored");
} else if (shouldStoreKeys) {
// Handshake keys were generated when loading the identity -
// store them
PublicKey handshakePub =
requireNonNull(cached.getHandshakePublicKey());
PrivateKey handshakePriv =
requireNonNull(cached.getHandshakePrivateKey());
db.setHandshakeKeyPair(txn, cached.getId(), handshakePub,
handshakePriv);
LOG.info("Handshake key pair stored");
} }
db.transaction(false, txn -> db.addLocalAuthor(txn, cached));
LOG.info("Local author stored");
} }
@Override @Override
public LocalAuthor getLocalAuthor() throws DbException { public LocalAuthor getLocalAuthor() throws DbException {
if (cachedAuthor == null) { Identity cached = cachedIdentity;
cachedAuthor = if (cached == null)
db.transactionWithResult(true, this::loadLocalAuthor); cached = db.transactionWithResult(true, this::getCachedIdentity);
LOG.info("Local author loaded"); return cached.getLocalAuthor();
}
LocalAuthor cached = cachedAuthor;
if (cached == null) throw new AssertionError();
return cached;
} }
@Override @Override
public LocalAuthor getLocalAuthor(Transaction txn) throws DbException { public LocalAuthor getLocalAuthor(Transaction txn) throws DbException {
if (cachedAuthor == null) { return getCachedIdentity(txn).getLocalAuthor();
cachedAuthor = loadLocalAuthor(txn); }
LOG.info("Local author loaded");
} @Override
LocalAuthor cached = cachedAuthor; public KeyPair getHandshakeKeys(Transaction txn) throws DbException {
if (cached == null) throw new AssertionError(); Identity cached = getCachedIdentity(txn);
PublicKey handshakePub = requireNonNull(cached.getHandshakePublicKey());
PrivateKey handshakePriv =
requireNonNull(cached.getHandshakePrivateKey());
return new KeyPair(handshakePub, handshakePriv);
}
/**
* Loads the identity if necessary and returns it. If
* {@code cachedIdentity} was not already set by calling
* {@link #registerIdentity(Identity)}, this method sets it. If
* {@code cachedIdentity} was already set, either by calling
* {@link #registerIdentity(Identity)} or by a previous call to this
* method, then this method returns the cached identity without hitting
* the database.
*/
private Identity getCachedIdentity(Transaction txn) throws DbException {
Identity cached = cachedIdentity;
if (cached == null)
cachedIdentity = cached = loadIdentityWithKeyPair(txn);
return cached; return cached;
} }
private LocalAuthor loadLocalAuthor(Transaction txn) throws DbException { /**
return db.getLocalAuthors(txn).iterator().next(); * Loads and returns the identity, generating a handshake key pair if
* necessary and setting {@code shouldStoreKeys} if a handshake key pair
* was generated.
*/
private Identity loadIdentityWithKeyPair(Transaction txn)
throws DbException {
Collection<Identity> identities = db.getIdentities(txn);
if (identities.size() != 1) throw new DbException();
Identity i = identities.iterator().next();
LOG.info("Identity loaded");
if (i.hasHandshakeKeyPair()) return i;
KeyPair handshakeKeyPair = crypto.generateAgreementKeyPair();
PublicKey handshakePub = handshakeKeyPair.getPublic();
PrivateKey handshakePriv = handshakeKeyPair.getPrivate();
LOG.info("Handshake key pair generated");
shouldStoreKeys = true;
return new Identity(i.getLocalAuthor(), handshakePub, handshakePriv,
i.getTimeCreated());
} }
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.identity.AuthorFactory; import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -24,8 +25,9 @@ public class IdentityModule {
@Provides @Provides
@Singleton @Singleton
IdentityManager provideIdentityManager( IdentityManager provideIdentityManager(LifecycleManager lifecycleManager,
IdentityManagerImpl identityManager) { IdentityManagerImpl identityManager) {
lifecycleManager.registerOpenDatabaseHook(identityManager);
return identityManager; return identityManager;
} }
} }

View File

@@ -1,13 +1,13 @@
package org.briarproject.bramble.keyagreement; package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UnsupportedVersionException;
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;
import org.briarproject.bramble.api.data.BdfReaderFactory; import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.keyagreement.Payload; import org.briarproject.bramble.api.keyagreement.Payload;
import org.briarproject.bramble.api.keyagreement.PayloadParser; import org.briarproject.bramble.api.keyagreement.PayloadParser;
import org.briarproject.bramble.api.keyagreement.TransportDescriptor; import org.briarproject.bramble.api.keyagreement.TransportDescriptor;
import org.briarproject.bramble.api.keyagreement.UnsupportedVersionException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothConstants; import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants; import org.briarproject.bramble.api.plugin.LanTcpConstants;

View File

@@ -7,13 +7,11 @@ 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.MigrationListener; import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException; import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent; import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Client;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
@@ -28,6 +26,7 @@ import javax.inject.Inject;
import static java.util.logging.Level.FINE; import static java.util.logging.Level.FINE;
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.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
@@ -49,14 +48,13 @@ import static org.briarproject.bramble.util.LogUtils.now;
class LifecycleManagerImpl implements LifecycleManager, MigrationListener { class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(LifecycleManagerImpl.class.getName()); getLogger(LifecycleManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final EventBus eventBus; private final EventBus eventBus;
private final List<Service> services; private final List<Service> services;
private final List<Client> clients; private final List<OpenDatabaseHook> openDatabaseHooks;
private final List<ExecutorService> executors; private final List<ExecutorService> executors;
private final IdentityManager identityManager;
private final Semaphore startStopSemaphore = new Semaphore(1); private final Semaphore startStopSemaphore = new Semaphore(1);
private final CountDownLatch dbLatch = new CountDownLatch(1); private final CountDownLatch dbLatch = new CountDownLatch(1);
private final CountDownLatch startupLatch = new CountDownLatch(1); private final CountDownLatch startupLatch = new CountDownLatch(1);
@@ -65,13 +63,11 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private volatile LifecycleState state = STARTING; private volatile LifecycleState state = STARTING;
@Inject @Inject
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus, LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus) {
IdentityManager identityManager) {
this.db = db; this.db = db;
this.eventBus = eventBus; this.eventBus = eventBus;
this.identityManager = identityManager;
services = new CopyOnWriteArrayList<>(); services = new CopyOnWriteArrayList<>();
clients = new CopyOnWriteArrayList<>(); openDatabaseHooks = new CopyOnWriteArrayList<>();
executors = new CopyOnWriteArrayList<>(); executors = new CopyOnWriteArrayList<>();
} }
@@ -83,10 +79,12 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
} }
@Override @Override
public void registerClient(Client c) { public void registerOpenDatabaseHook(OpenDatabaseHook hook) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO)) {
LOG.info("Registering client " + c.getClass().getSimpleName()); LOG.info("Registering open database hook "
clients.add(c); + hook.getClass().getSimpleName());
}
openDatabaseHooks.add(hook);
} }
@Override @Override
@@ -102,28 +100,28 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
return ALREADY_RUNNING; return ALREADY_RUNNING;
} }
try { try {
LOG.info("Starting services"); LOG.info("Opening database");
long start = now(); long start = now();
boolean reopened = db.open(dbKey, this); boolean reopened = db.open(dbKey, this);
if (reopened) logDuration(LOG, "Reopening database", start); if (reopened) logDuration(LOG, "Reopening database", start);
else logDuration(LOG, "Creating database", start); else logDuration(LOG, "Creating database", start);
identityManager.storeLocalAuthor();
db.transaction(false, txn -> {
for (OpenDatabaseHook hook : openDatabaseHooks) {
long start1 = now();
hook.onDatabaseOpened(txn);
if (LOG.isLoggable(FINE)) {
logDuration(LOG, "Calling open database hook "
+ hook.getClass().getSimpleName(), start1);
}
}
});
LOG.info("Starting services");
state = STARTING_SERVICES; state = STARTING_SERVICES;
dbLatch.countDown(); dbLatch.countDown();
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES)); eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
db.transaction(false, txn -> {
for (Client c : clients) {
long start1 = now();
c.createLocalState(txn);
if (LOG.isLoggable(FINE)) {
logDuration(LOG, "Starting client "
+ c.getClass().getSimpleName(), start1);
}
}
});
for (Service s : services) { for (Service s : services) {
start = now(); start = now();
s.startService(); s.startService();

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

@@ -48,7 +48,7 @@ public class PropertiesModule {
ValidationManager validationManager, ContactManager contactManager, ValidationManager validationManager, ContactManager contactManager,
ClientVersioningManager clientVersioningManager, ClientVersioningManager clientVersioningManager,
TransportPropertyManagerImpl transportPropertyManager) { TransportPropertyManagerImpl transportPropertyManager) {
lifecycleManager.registerClient(transportPropertyManager); lifecycleManager.registerOpenDatabaseHook(transportPropertyManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION, validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
transportPropertyManager); transportPropertyManager);
contactManager.registerContactHook(transportPropertyManager); contactManager.registerContactHook(transportPropertyManager);

View File

@@ -13,11 +13,11 @@ 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.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
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.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.Client;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Group.Visibility; import org.briarproject.bramble.api.sync.Group.Visibility;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
@@ -40,7 +40,8 @@ import javax.inject.Inject;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class TransportPropertyManagerImpl implements TransportPropertyManager, class TransportPropertyManagerImpl implements TransportPropertyManager,
Client, ContactHook, ClientVersioningHook, IncomingMessageHook { OpenDatabaseHook, ContactHook, ClientVersioningHook,
IncomingMessageHook {
private final DatabaseComponent db; private final DatabaseComponent db;
private final ClientHelper clientHelper; private final ClientHelper clientHelper;
@@ -67,7 +68,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
} }
@Override @Override
public void createLocalState(Transaction txn) throws DbException { public void onDatabaseOpened(Transaction txn) throws DbException {
if (db.containsGroup(txn, localGroup.getId())) return; if (db.containsGroup(txn, localGroup.getId())) return;
db.addGroup(txn, localGroup); db.addGroup(txn, localGroup);
// Set things up for any pre-existing contacts // Set things up for any pre-existing contacts

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

@@ -12,10 +12,10 @@ 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.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException; import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Client;
import org.briarproject.bramble.api.sync.ClientId; import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Group.Visibility; import org.briarproject.bramble.api.sync.Group.Visibility;
@@ -53,8 +53,8 @@ import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
@NotNullByDefault @NotNullByDefault
class ClientVersioningManagerImpl implements ClientVersioningManager, Client, class ClientVersioningManagerImpl implements ClientVersioningManager,
Service, ContactHook, IncomingMessageHook { Service, OpenDatabaseHook, ContactHook, IncomingMessageHook {
private final DatabaseComponent db; private final DatabaseComponent db;
private final ClientHelper clientHelper; private final ClientHelper clientHelper;
@@ -124,7 +124,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
} }
@Override @Override
public void createLocalState(Transaction txn) throws DbException { public void onDatabaseOpened(Transaction txn) throws DbException {
if (db.containsGroup(txn, localGroup.getId())) return; if (db.containsGroup(txn, localGroup.getId())) return;
db.addGroup(txn, localGroup); db.addGroup(txn, localGroup);
// Set things up for any pre-existing contacts // Set things up for any pre-existing contacts

View File

@@ -34,7 +34,7 @@ public class VersioningModule {
ClientVersioningManagerImpl clientVersioningManager, ClientVersioningManagerImpl clientVersioningManager,
LifecycleManager lifecycleManager, ContactManager contactManager, LifecycleManager lifecycleManager, ContactManager contactManager,
ValidationManager validationManager) { ValidationManager validationManager) {
lifecycleManager.registerClient(clientVersioningManager); lifecycleManager.registerOpenDatabaseHook(clientVersioningManager);
lifecycleManager.registerService(clientVersioningManager); lifecycleManager.registerService(clientVersioningManager);
contactManager.registerContactHook(clientVersioningManager); contactManager.registerContactHook(clientVersioningManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION, validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,

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