Compare commits

...

80 Commits

Author SHA1 Message Date
akwizgran
a872851a78 Revert minSdkVersion to 21 to fix Robolectric tests. 2023-09-05 17:29:05 +01:00
akwizgran
f3050f9fb8 Fix packaging of Tor binaries with minSdkVersion >= 23. 2023-09-05 17:07:59 +01:00
akwizgran
a3ba1ac91e Fix Gradle Witness. 2023-09-05 16:38:36 +01:00
akwizgran
0ac4b5c613 Don't delete .gitkeep when cleaning. 2023-09-05 15:27:01 +01:00
akwizgran
f4425acfaf Keep jniLibs dir, as it's now an input of the clean task. 2023-09-05 15:23:33 +01:00
akwizgran
7b65c63bc9 Package Android binaries, enable desugaring, bump min API level.
FIXME: Desugaring ConcurrentHashMap.newKeySet() requires
desugar_jdk_libs version 2, which requires bumping the Android Gradle
plugin version. This seems to have broken Gradle Witness.
2023-09-05 14:50:23 +01:00
akwizgran
bf2de56abe Use SqliteDatabase as default implementation. 2023-09-05 10:48:58 +01:00
akwizgran
8d7ac49bff Enable secure_delete for SQLite. 2023-09-05 10:48:36 +01:00
akwizgran
9b2c8b0f98 Fix default value for sync versions. 2023-09-05 10:37:07 +01:00
akwizgran
b7c0bc468f WIP: Create indexes on foreign key columns if needed. 2023-09-04 17:42:02 +01:00
akwizgran
852f3fd78b WIP: Temporarily skip failing test so we can run performance test. 2023-09-01 11:51:05 +01:00
akwizgran
6734284585 WIP: Add SQLite DB backend using sqlite-jdbc-crypt. 2023-09-01 11:42:24 +01:00
akwizgran
87ef5e58ee Update Play Store metadata. 2023-08-28 16:54:13 +01:00
akwizgran
b8b5e6c201 Update Play Store metadata. 2023-08-24 17:44:35 +01:00
akwizgran
b68d24dca5 Bump version numbers for 1.5.6 release. 2023-08-23 10:45:39 +01:00
Torsten Grote
8bb3ea8a85 Merge branch 'no-tv-for-you' into 'master'
Remove support for Android TV

See merge request briar/briar!1808
2023-08-23 07:51:20 +00:00
Torsten Grote
e13563952b Merge branch 'update-play-store-description' into 'master'
Add links to Play Store description

See merge request briar/briar!1807
2023-08-23 07:49:08 +00:00
Torsten Grote
c74ebabcd1 Merge branch 'update-readme' into 'master'
Update readme: add privacy policy, remove Flattr

See merge request briar/briar!1806
2023-08-23 07:47:35 +00:00
akwizgran
47b8f47f07 Remove support for Android TV.
Google requires apps that support Android TV to be published as app bundles.
2023-08-22 15:59:20 +01:00
akwizgran
d0feacd38f Add links to Play Store description. 2023-08-22 15:35:04 +01:00
akwizgran
2844adb8fa Bump version numbers for 1.5.5 release. 2023-08-21 14:54:06 +01:00
akwizgran
f02dcc9f70 Update translations. 2023-08-21 14:53:21 +01:00
akwizgran
8ab7eb7edf Update readme: add privacy policy, remove Flattr. 2023-08-21 14:44:29 +01:00
akwizgran
1ef1ccc1f7 Merge branch 'fix-group-invitation-state' into 'master'
fix SharingState for private group creator

See merge request briar/briar!1805
2023-08-15 15:33:37 +00:00
akwizgran
c7e382c1af Update translations. 2023-08-15 13:33:02 +01:00
ialokim
38a7217c3f fix SharingState for private group creator 2023-08-14 18:05:48 +02:00
Torsten Grote
6d3e81a074 Merge branch 'tor-0.4.7.14' into 'master'
Upgrade Tor to 0.4.7.14

See merge request briar/briar!1804
2023-08-11 12:44:57 +00:00
akwizgran
4591de2017 Upgrade Tor to 0.4.7.14. 2023-08-08 16:54:09 +01:00
Torsten Grote
6da34fac84 Merge branch 'bdf-javadocs' into 'master'
Add BDF javadocs

See merge request briar/briar!1801
2023-08-07 14:16:22 +00:00
Torsten Grote
810ac24cee Merge branch 'onionwrapper-0.0.5' into 'master'
Upgrade onionwrapper to 0.0.5

See merge request briar/briar!1803
2023-08-07 13:48:36 +00:00
akwizgran
704f69c9fd Upgrade onionwrapper to 0.0.5. 2023-08-07 14:17:11 +01:00
akwizgran
952ee42ad1 Merge branch 'blog-txns' into 'master'
Add transactional versions of BlogManager methods and a bug fix

See merge request briar/briar!1802
2023-07-13 20:30:21 +00:00
Torsten Grote
f61b09d5a9 Fix BlogManager tests after last commits 2023-07-13 14:50:05 -03:00
Torsten Grote
8f735d176e Add transactional versions of BlogManager methods 2023-07-13 13:01:30 -03:00
Torsten Grote
c47253fc5f Mark our own reblogs as read automatically 2023-07-13 12:54:17 -03:00
akwizgran
b1cc63cd49 Deprecate methods for handling non-canonical BDF. 2023-07-05 15:23:14 +01:00
akwizgran
8cd6546840 Add javadocs for BDF classes. 2023-07-05 15:23:08 +01:00
akwizgran
7a0fb74c09 Merge branch '2266-target-sdk-33' into 'master'
Target SDK 33

Closes #2266

See merge request briar/briar!1800
2023-07-03 10:55:46 +00:00
akwizgran
882f536b8d Don't try to get Bluetooth address from settings. 2023-06-30 18:14:12 +01:00
Torsten Grote
74f8e84a9b React to device light idle mode in DozeWatchdog as well 2023-06-29 10:58:32 -03:00
Torsten Grote
23df2a41c2 Add @NotNullByDefault annotation to ConditionManagers 2023-06-29 10:58:32 -03:00
Torsten Grote
c77eaf16d9 Log more mode changes in AndroidBatteryManager 2023-06-29 10:58:31 -03:00
Torsten Grote
9a6bb4b203 Set dozed to true when we are in LowPowerStandby 2023-06-29 10:58:31 -03:00
Torsten Grote
3d237a9104 Introduce tryToStartActivity() helper method 2023-06-29 10:58:31 -03:00
Torsten Grote
fa216ffc6f Move requestEnableWiFi() into AbstractConditionManager 2023-06-29 10:58:31 -03:00
Torsten Grote
a34631d36c Catch ActivityNotFoundException in places where we launch external intents 2023-06-29 10:58:31 -03:00
Torsten Grote
45cda191e5 Log changes to DeviceLightIdleMode in AndroidBatteryManager 2023-06-29 10:58:31 -03:00
Torsten Grote
2495b6f5c0 Add LowPowerStandby stub to DozeWatchdogImpl 2023-06-29 10:58:31 -03:00
Torsten Grote
03fc504f7d Log changes to LowPowerStandby in AndroidBatteryManager 2023-06-29 10:58:30 -03:00
Torsten Grote
d19062e319 Don't disable hotspot start button after click
to avoid issues when coming back to the screen after granting permissions.
2023-06-29 10:58:30 -03:00
Torsten Grote
fdb429ab7a Ask for NEARBY_WIFI_DEVICES permission on SDK 33 and up 2023-06-29 10:58:30 -03:00
Torsten Grote
d0c59a6d75 Target SDK 33 and ask for notification permission
when creating account and when signing in
2023-06-29 10:58:30 -03:00
akwizgran
3bb39c2aa3 Merge branch 'fix-macos-x86-issue' into 'master'
Fix architecture detection for macOS Intel CPUs

See merge request briar/briar!1799
2023-06-28 11:07:26 +00:00
Sebastian Kürten
917fc5e5b6 Fix architecture detection for macOS Intel CPUs 2023-06-28 12:57:20 +02:00
akwizgran
caa078585b Merge branch 'macos3' into 'master'
macOS support

See merge request briar/briar!1790
2023-06-22 17:04:09 +00:00
akwizgran
e68c0c7f4b Merge branch 'onionwrapper-0.0.4' into 'master'
Upgrade onionwrapper to 0.0.4

See merge request briar/briar!1798
2023-06-22 12:26:21 +00:00
akwizgran
a6b3749fb6 Extend comment explaining TorState -> State mapping. 2023-06-22 13:12:58 +01:00
Torsten Grote
a8f6e8e4bd Merge branch 'check-network-status-periodically' into 'master'
Check network status periodically

See merge request briar/briar!1797
2023-06-21 13:32:39 +00:00
akwizgran
4d884601f0 Check more often, only broadcast status if changed. 2023-06-20 17:01:45 +01:00
akwizgran
b71198d9b1 Check network status periodically on JavaSE. 2023-06-20 16:34:45 +01:00
Sebastian Kürten
079c6e0475 Add comment why we choose a differnt port for headless on macOS 2023-06-20 12:21:11 +02:00
Sebastian Kürten
3a0f8ed85c Document building of macOS headless jars and fix included native binaries on macOS 2023-06-15 18:07:18 +02:00
Sebastian Kürten
57f7501780 macOS support 2023-06-15 12:48:01 +02:00
akwizgran
3cc5699fe0 Upgrade onionwrapper to 0.0.4. 2023-06-14 17:06:10 +01:00
akwizgran
7d761710e6 Bump version numbers for 1.5.4 release. 2023-06-02 13:53:20 +01:00
Torsten Grote
7461d3c943 Merge branch '2434-use-us-locale-for-lowercasing-onion-hostname' into 'master'
Use US locale for lowercasing onion hostname

Closes #2434

See merge request briar/briar!1796
2023-05-31 16:27:24 +00:00
akwizgran
9291613175 Fix some other uses of toLowerCase() without a locale. 2023-05-30 22:06:18 +01:00
akwizgran
ce6739a9fd Use US locale for lowercasing onion hostname. 2023-05-30 22:00:41 +01:00
akwizgran
1f1a97f62d Bump version numbers for 1.5.3 release. 2023-05-24 11:33:49 +01:00
akwizgran
7a33d26533 Merge branch 'new-handshake' 2023-05-24 11:22:23 +01:00
akwizgran
f2c85f37be Merge branch '2391-share-link' into 'master'
Share a link to the Briar download page via another app

Closes #2391

See merge request briar/briar!1795
2023-05-22 15:35:43 +00:00
Torsten Grote
8e3fa872fd Move About settings item to the bottom 2023-05-17 08:53:24 -03:00
akwizgran
0d1e81ebdb Merge branch 'use-default-secure-random-provider-on-macos' into 'master'
Use system default secure random provider on macOS

Closes briar-desktop#132

See merge request briar/briar!1794
2023-05-17 09:25:42 +00:00
Sebastian Kürten
bded4e7bc8 Use system default secure random provider on macOS 2023-05-17 11:13:43 +02:00
Torsten Grote
bf1a5cf218 Allow sharing download link for Briar from settings actions 2023-05-16 16:55:19 -03:00
akwizgran
dd7a638984 Merge branch 'fa-string-fix' into 'master'
Fix translation

See merge request briar/briar!1793
2023-05-16 10:38:47 +00:00
paul
942222131e Fix translation. 2023-05-15 21:48:02 +00:00
akwizgran
4a4b04bec3 Rename version constant. 2023-04-26 17:10:23 +01:00
akwizgran
462f57c966 Upgrade handshake protocol to new key agreement method. 2023-03-10 16:05:59 +00:00
akwizgran
8d20c5d8b8 Reify RecordPredicate for easier testing. 2023-03-10 15:15:29 +00:00
133 changed files with 3584 additions and 1649 deletions

View File

@@ -104,3 +104,12 @@ mailbox integration test:
script: script:
- (cd briar-mailbox; git fetch; git reset --hard origin/main) - (cd briar-mailbox; git fetch; git reset --hard origin/main)
- MAILBOX_INTEGRATION_TESTS=true ./gradlew --info mailbox-integration-tests:test - MAILBOX_INTEGRATION_TESTS=true ./gradlew --info mailbox-integration-tests:test
db_performance_comparison_test:
extends: .base-test
stage: optional_tests
script:
- OPTIONAL_TESTS=org.briarproject.bramble.db.H2SqliteDatabasePerformanceComparisonTest ./gradlew --info -Djava.security.egd=file:/dev/urandom :bramble-core:test --tests H2SqliteDatabasePerformanceComparisonTest
rules:
- when: manual

View File

@@ -1,7 +1,7 @@
# Briar # Briar
Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate. Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate.
Unlike traditional messaging tools such as email, Twitter or Telegram, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices. Unlike traditional messaging apps, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices.
If the Internet's down, Briar can sync via Bluetooth or Wi-Fi, keeping information flowing in a crisis. If the Internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance. If the Internet's down, Briar can sync via Bluetooth or Wi-Fi, keeping information flowing in a crisis. If the Internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance.
@@ -14,14 +14,16 @@ You can also [download the APK file](https://briarproject.org/apk) directly from
our site. our site.
## Useful links ## Useful links
[briarproject.org](https://briarproject.org/) [Project website](https://briarproject.org/)
[Source code](https://code.briarproject.org/briar/briar/tree/master) [Source code](https://code.briarproject.org/briar/briar/tree/master)
[Manual](https://briarproject.org/manual/) [User manual](https://briarproject.org/manual/)
[Wiki](https://code.briarproject.org/briar/briar/-/wikis/home) [Wiki](https://code.briarproject.org/briar/briar/-/wikis/home)
[Privacy policy](https://briarproject.org/privacy)
## Reproducible builds ## Reproducible builds
We provide [docker images](https://code.briarproject.org/briar/briar-reproducer#briar-reproducer) We provide [docker images](https://code.briarproject.org/briar/briar-reproducer#briar-reproducer)
@@ -33,5 +35,5 @@ for reproduction.
## Donate ## Donate
[![Donate using Liberapay](https://briarproject.org/img/liberapay.svg)](https://liberapay.com/Briar/donate) [![Flattr this](https://briarproject.org/img/flattr-badge-large.png "Flattr this")](https://flattr.com/t/592836/) [![Donate using Liberapay](https://briarproject.org/img/liberapay.svg)](https://liberapay.com/Briar/donate)
Bitcoin and BCH: 1NZCKkUCtJV2U2Y9hDb9uq8S7ksFCFGR6K Bitcoin and BCH: 1NZCKkUCtJV2U2Y9hDb9uq8S7ksFCFGR6K

View File

@@ -4,3 +4,4 @@ build
.settings .settings
src/main/res/raw/*.zip src/main/res/raw/*.zip
src/main/jniLibs src/main/jniLibs
!src/main/jniLibs/.gitkeep

View File

@@ -11,10 +11,12 @@ android {
} }
defaultConfig { defaultConfig {
// FIXME: sqlite-jdbc-crypt uses __register_atfork which is only available on API >= 23.
// We might be able to solve this by recompiling (or asking upstream to recompile)
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 33
versionCode 10502 versionCode 10506
versionName "1.5.2" versionName "1.5.6"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -37,6 +39,7 @@ android {
configurations { configurations {
tor tor
sqliteJdbcCrypt
} }
dependencies { dependencies {
@@ -57,6 +60,8 @@ dependencies {
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version" tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version"
tor "org.briarproject:snowflake-android:$snowflake_version" tor "org.briarproject:snowflake-android:$snowflake_version"
sqliteJdbcCrypt "io.github.willena:sqlite-jdbc:$sqlite_jdbc_crypt_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
compileOnly 'javax.annotation:jsr250-api:1.0' compileOnly 'javax.annotation:jsr250-api:1.0'
@@ -69,26 +74,50 @@ dependencies {
testImplementation "org.jmock:jmock-imposters:$jmock_version" testImplementation "org.jmock:jmock-imposters:$jmock_version"
} }
def torLibsDir = 'src/main/jniLibs' def jniLibsDir = 'src/main/jniLibs'
task cleanTorBinaries { task cleanJniLibs {
outputs.dir torLibsDir inputs.dir jniLibsDir
outputs.dir jniLibsDir
doLast { doLast {
delete fileTree(torLibsDir) delete fileTree(jniLibsDir).filter { it.name.endsWith('.so') }
} }
} }
clean.dependsOn cleanTorBinaries clean.dependsOn cleanJniLibs
task unpackTorBinaries { task unpackJniLibs {
outputs.dir torLibsDir outputs.dir jniLibsDir
doLast { doLast {
// Tor
copy { copy {
from configurations.tor.collect { zipTree(it) } from configurations.tor.collect { zipTree(it) }
into torLibsDir into jniLibsDir
}
// sqlite-jdbc-crypt
def archMap = [
aarch64: 'arm64-v8a',
arm : 'armeabi-v7a',
x86 : 'x86',
x86_64 : 'x86_64'
]
configurations.sqliteJdbcCrypt.collect { File artifact ->
zipTree(artifact).each { File f ->
for (String arch : archMap.keySet()) {
if (f.absolutePath.endsWith("/Linux-Android/$arch/libsqlitejdbc.so")) {
def archDir = new File(jniLibsDir, archMap.get(arch))
archDir.mkdirs()
copy {
from f
into archDir
}
break
} }
} }
dependsOn cleanTorBinaries }
}
}
dependsOn cleanJniLibs
} }
preBuild.dependsOn unpackTorBinaries preBuild.dependsOn unpackJniLibs

View File

@@ -6,6 +6,9 @@
-dontwarn org.h2.** -dontwarn org.h2.**
-dontnote org.h2.** -dontnote org.h2.**
# Keep sqlite-jdbc-crypt classes that are loaded via reflection or accessed via JNI
-keep class org.sqlite.** { *; }
-keep class dagger.** { *; } -keep class dagger.** { *; }
-dontwarn dagger.** -dontwarn dagger.**
-dontnote dagger.** -dontnote dagger.**

View File

@@ -5,6 +5,7 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.PowerManager;
import org.briarproject.bramble.api.battery.BatteryManager; import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.battery.event.BatteryEvent; import org.briarproject.bramble.api.battery.event.BatteryEvent;
@@ -16,10 +17,17 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.RequiresApi;
import static android.content.Intent.ACTION_BATTERY_CHANGED; import static android.content.Intent.ACTION_BATTERY_CHANGED;
import static android.content.Intent.ACTION_POWER_CONNECTED; import static android.content.Intent.ACTION_POWER_CONNECTED;
import static android.content.Intent.ACTION_POWER_DISCONNECTED; import static android.content.Intent.ACTION_POWER_DISCONNECTED;
import static android.os.BatteryManager.EXTRA_PLUGGED; import static android.os.BatteryManager.EXTRA_PLUGGED;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
import static android.os.PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED;
import static android.os.PowerManager.ACTION_POWER_SAVE_MODE_CHANGED;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
@@ -57,6 +65,12 @@ class AndroidBatteryManager implements BatteryManager, Service {
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_POWER_CONNECTED); filter.addAction(ACTION_POWER_CONNECTED);
filter.addAction(ACTION_POWER_DISCONNECTED); filter.addAction(ACTION_POWER_DISCONNECTED);
filter.addAction(ACTION_POWER_SAVE_MODE_CHANGED);
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
if (SDK_INT >= 33) {
filter.addAction(ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
filter.addAction(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED);
}
appContext.registerReceiver(batteryReceiver, filter); appContext.registerReceiver(batteryReceiver, filter);
} }
@@ -76,6 +90,33 @@ class AndroidBatteryManager implements BatteryManager, Service {
eventBus.broadcast(new BatteryEvent(true)); eventBus.broadcast(new BatteryEvent(true));
else if (ACTION_POWER_DISCONNECTED.equals(action)) else if (ACTION_POWER_DISCONNECTED.equals(action))
eventBus.broadcast(new BatteryEvent(false)); eventBus.broadcast(new BatteryEvent(false));
else if (SDK_INT >= 23 &&
ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action) &&
LOG.isLoggable(INFO)) {
LOG.info("Device idle mode changed to: " +
getPowerManager(ctx).isDeviceIdleMode());
} else if (SDK_INT >= 23 &&
ACTION_POWER_SAVE_MODE_CHANGED.equals(action) &&
LOG.isLoggable(INFO)) {
LOG.info("Power save mode changed to: " +
getPowerManager(ctx).isPowerSaveMode());
} else if (SDK_INT >= 33 && LOG.isLoggable(INFO) &&
ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED.equals(action)) {
PowerManager powerManager =
ctx.getSystemService(PowerManager.class);
LOG.info("Low power standby now is: " +
powerManager.isLowPowerStandbyEnabled());
} else if (SDK_INT >= 33 && LOG.isLoggable(INFO) &&
ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED.equals(action)) {
PowerManager powerManager = getPowerManager(ctx);
LOG.info("Light idle mode now is: " +
powerManager.isDeviceLightIdleMode());
} }
} }
} }
@RequiresApi(api = 23)
private PowerManager getPowerManager(Context ctx) {
return ctx.getSystemService(PowerManager.class);
}
}

View File

@@ -63,11 +63,13 @@ public class AndroidUtils {
return new Pair<>(address, "adapter"); return new Pair<>(address, "adapter");
} }
// Return the address from settings if it's valid and not fake // Return the address from settings if it's valid and not fake
if (SDK_INT < 33) {
address = Settings.Secure.getString(ctx.getContentResolver(), address = Settings.Secure.getString(ctx.getContentResolver(),
"bluetooth_address"); "bluetooth_address");
if (isValidBluetoothAddress(address)) { if (isValidBluetoothAddress(address)) {
return new Pair<>(address, "settings"); return new Pair<>(address, "settings");
} }
}
// Try to get the address via reflection // Try to get the address via reflection
address = getBluetoothAddressByReflection(adapter); address = getBluetoothAddressByReflection(adapter);
if (isValidBluetoothAddress(address)) { if (isValidBluetoothAddress(address)) {

View File

@@ -18,6 +18,7 @@ dependencyVerification {
'com.google.j2objc:j2objc-annotations:1.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b', 'com.google.j2objc:j2objc-annotations:1.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b',
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291', 'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
'com.squareup:kotlinpoet:1.11.0:kotlinpoet-1.11.0.jar:2887ada1ca03dd83baa2758640d87e840d1907564db0ef88d2289c868a980492', 'com.squareup:kotlinpoet:1.11.0:kotlinpoet-1.11.0.jar:2887ada1ca03dd83baa2758640d87e840d1907564db0ef88d2289c868a980492',
'io.github.willena:sqlite-jdbc:3.41.2.1:sqlite-jdbc-3.41.2.1.jar:fb60e7137c1791db89240701338d31ca42a0bec5508c1aab1c1131cf885f2309',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f', 'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3', 'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
@@ -29,19 +30,19 @@ dependencyVerification {
'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b', 'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011', 'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011',
'org.briarproject:obfs4proxy-android:0.0.14-tor2:obfs4proxy-android-0.0.14-tor2.jar:a0a93770d6760ce57d9dbd31cc7177687374e00c3361dac22ab75e3b6e0f289e', 'org.briarproject:obfs4proxy-android:0.0.14-tor2:obfs4proxy-android-0.0.14-tor2.jar:a0a93770d6760ce57d9dbd31cc7177687374e00c3361dac22ab75e3b6e0f289e',
'org.briarproject:onionwrapper-android:0.0.2:onionwrapper-android-0.0.2.aar:d196f1fe5d8b750866ea09d80509716aae7645d39b2c85433994718dbebeb4d1', 'org.briarproject:onionwrapper-android:0.0.5:onionwrapper-android-0.0.5.aar:d761854dac454616b3e0ca099b2cd17060365ce4316afe495cc7ae86b6c81d15',
'org.briarproject:onionwrapper-core:0.0.2:onionwrapper-core-0.0.2.jar:7038e960c9e59803f0e2c19444dbb5214cd99e5a7463c0a01c45318e07a0eb80', 'org.briarproject:onionwrapper-core:0.0.5:onionwrapper-core-0.0.5.jar:9071678323535cb3dfe0f3add96066037db43ea024333eba0117c759bcbd8d63',
'org.briarproject:snowflake-android:2.5.1:snowflake-android-2.5.1.jar:88ec81c17b1b6fa884d06839dec0330e328b45c89f88c970a213ce91ca8eac87', 'org.briarproject:snowflake-android:2.5.1:snowflake-android-2.5.1.jar:88ec81c17b1b6fa884d06839dec0330e328b45c89f88c970a213ce91ca8eac87',
'org.briarproject:tor-android:0.4.7.13-2:tor-android-0.4.7.13-2.jar:453fd463b234a2104edd7f0d02d0649cbb5c5efbe47a76df3828f55a3f90f8b5', 'org.briarproject:tor-android:0.4.7.14:tor-android-0.4.7.14.jar:d39faa3a8abb116136c191c6ebadf8ea0e1f3e4785076d2c66a7b3b0f26988a2',
'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a', 'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a',
'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb', 'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9', 'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21', 'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050', 'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
'org.jacoco:org.jacoco.agent:0.8.7:org.jacoco.agent-0.8.7.jar:9cbcc986e0fbe821a78ff1f8f7d5216f200e5eb124e7f6837d1dc4a77b28b143', 'org.jacoco:org.jacoco.agent:0.8.8:org.jacoco.agent-0.8.8.jar:072ecbd496896623899a696fff12c01c1615f737616d2792e6d0e10cdf8a610d',
'org.jacoco:org.jacoco.ant:0.8.7:org.jacoco.ant-0.8.7.jar:97ca96a382c3f23a44d8eb4c4e6c3742a30cb8005774a76ced0fc4806ce49605', 'org.jacoco:org.jacoco.ant:0.8.8:org.jacoco.ant-0.8.8.jar:02e33bd2c48dc0be67c2fea84d43beececfd400da6797c58153253d4c30aca15',
'org.jacoco:org.jacoco.core:0.8.7:org.jacoco.core-0.8.7.jar:ad7739b5fb5969aa1a8aead3d74ed54dc82ed012f1f10f336bd1b96e71c1a13c', 'org.jacoco:org.jacoco.core:0.8.8:org.jacoco.core-0.8.8.jar:474c782f809d88924713dfdbf0acb79d330f904be576484803463d0465611643',
'org.jacoco:org.jacoco.report:0.8.7:org.jacoco.report-0.8.7.jar:cc89258623700a6c932592153cb528785876b6da183d5431f97efbba6f020e5b', 'org.jacoco:org.jacoco.report:0.8.8:org.jacoco.report-0.8.8.jar:2c129110f3e3fcaa1f8179578ea3894586199cb0826be5c7790278084c9622a9',
'org.jetbrains.kotlin:kotlin-reflect:1.6.10:kotlin-reflect-1.6.10.jar:3277ac102ae17aad10a55abec75ff5696c8d109790396434b496e75087854203', 'org.jetbrains.kotlin:kotlin-reflect:1.6.10:kotlin-reflect-1.6.10.jar:3277ac102ae17aad10a55abec75ff5696c8d109790396434b496e75087854203',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.0:kotlin-stdlib-common-1.7.0.jar:59c6ff64fe9a6604afce03e8aaa75f83586c6030ac71fb0b34ee7cdefed3618f', 'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.0:kotlin-stdlib-common-1.7.0.jar:59c6ff64fe9a6604afce03e8aaa75f83586c6030ac71fb0b34ee7cdefed3618f',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.8.0:kotlin-stdlib-common-1.8.0.jar:78ef93b59e603cc0fe51def9bd4c037b07cbace3b3b7806d1a490a42bc1f4cb2', 'org.jetbrains.kotlin:kotlin-stdlib-common:1.8.0:kotlin-stdlib-common-1.8.0.jar:78ef93b59e603cc0fe51def9bd4c037b07cbace3b3b7806d1a490a42bc1f4cb2',
@@ -58,10 +59,10 @@ dependencyVerification {
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04', 'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be', 'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984', 'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
'org.ow2.asm:asm-analysis:9.1:asm-analysis-9.1.jar:81a88041b1b8beda5a8a99646098046c48709538270c49def68abff25ac3be34', 'org.ow2.asm:asm-analysis:9.2:asm-analysis-9.2.jar:878fbe521731c072d14d2d65b983b1beae6ad06fda0007b6a8bae81f73f433c4',
'org.ow2.asm:asm-commons:9.1:asm-commons-9.1.jar:afcb26dc1fc12c0c4a99ada670908dd82e18dfc488caf5ee92546996b470c00c', 'org.ow2.asm:asm-commons:9.2:asm-commons-9.2.jar:be4ce53138a238bb522cd781cf91f3ba5ce2f6ca93ec62d46a162a127225e0a6',
'org.ow2.asm:asm-tree:9.1:asm-tree-9.1.jar:fd00afa49e9595d7646205b09cecb4a776a8ff0ba06f2d59b8f7bf9c704b4a73', 'org.ow2.asm:asm-tree:9.2:asm-tree-9.2.jar:aabf9bd23091a4ebfc109c1f3ee7cf3e4b89f6ba2d3f51c5243f16b3cffae011',
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de', 'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
'org.ow2.asm:asm:9.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2', 'org.ow2.asm:asm:9.2:asm-9.2.jar:b9d4fe4d71938df38839f0eca42aaaa64cf8b313d678da036f0cb3ca199b47f5',
] ]
} }

View File

@@ -31,6 +31,12 @@ public abstract class BdfMessageValidator implements MessageValidator {
protected final Clock clock; protected final Clock clock;
protected final boolean canonical; protected final boolean canonical;
/**
* Transitional alternative to
* {@link #BdfMessageValidator(ClientHelper, MetadataEncoder, Clock)} that
* accepts messages in non-canonical form, for backward compatibility.
*/
@Deprecated
protected BdfMessageValidator(ClientHelper clientHelper, protected BdfMessageValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock, boolean canonical) { MetadataEncoder metadataEncoder, Clock clock, boolean canonical) {
this.clientHelper = clientHelper; this.clientHelper = clientHelper;

View File

@@ -49,6 +49,15 @@ public interface ClientHelper {
BdfList getMessageAsList(Transaction txn, MessageId m) throws DbException, BdfList getMessageAsList(Transaction txn, MessageId m) throws DbException,
FormatException; FormatException;
/**
* Transitional alternative to
* {@link #getMessageAsList(Transaction, MessageId)} that allows the
* message to be in non-canonical form, for backward compatibility.
*
* @param canonical True if the message must be in canonical form (a
* {@link FormatException} will be thrown if it's not.
*/
@Deprecated
BdfList getMessageAsList(Transaction txn, MessageId m, boolean canonical) BdfList getMessageAsList(Transaction txn, MessageId m, boolean canonical)
throws DbException, FormatException; throws DbException, FormatException;
@@ -109,6 +118,14 @@ public interface ClientHelper {
BdfList toList(Message m) throws FormatException; BdfList toList(Message m) throws FormatException;
/**
* Transitional alternative to {@link #toList(Message)} that allows the
* message to be in non-canonical form, for backward compatibility.
*
* @param canonical True if the message must be in canonical form (a
* {@link FormatException} will be thrown if it's not.
*/
@Deprecated
BdfList toList(Message m, boolean canonical) throws FormatException; BdfList toList(Message m, boolean canonical) throws FormatException;
BdfList toList(Author a); BdfList toList(Author a);

View File

@@ -54,6 +54,38 @@ public interface CryptoComponent {
KeyPair ourKeyPair, byte[]... inputs) KeyPair ourKeyPair, byte[]... inputs)
throws GeneralSecurityException; throws GeneralSecurityException;
/**
* Derives a shared secret from two static and two ephemeral key pairs.
* <p>
* Do not use this method for new protocols. The shared secret can be
* re-derived using the ephemeral public keys and both static private
* keys, so keys derived from the shared secret should not be used if
* forward secrecy is required. Use {@link #deriveSharedSecret(String,
* PublicKey, PublicKey, KeyPair, KeyPair, boolean, byte[]...)} instead.
* <p>
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
* <p>
*
* @param label A namespaced label indicating the purpose of this shared
* secret, to prevent it from being repurposed or colliding with a shared
* secret derived for another purpose
* @param theirStaticPublicKey The static public key of the remote party
* @param theirEphemeralPublicKey The ephemeral public key of the remote
* party
* @param ourStaticKeyPair The static key pair of the local party
* @param ourEphemeralKeyPair The ephemeral key pair of the local party
* @param alice True if the local party is Alice
* @param inputs Additional inputs that will be included in the
* derivation of the shared secret
* @return The shared secret
*/
@Deprecated
SecretKey deriveSharedSecretBadly(String label,
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
boolean alice, byte[]... inputs)
throws GeneralSecurityException;
/** /**
* Derives a shared secret from two static and two ephemeral key pairs. * Derives a shared secret from two static and two ephemeral key pairs.
* *

View File

@@ -10,6 +10,27 @@ import java.util.TreeMap;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
/**
* A BDF dictionary contains zero or more key-value pairs, where the keys
* are strings and the values are BDF objects, which may be primitive types
* (null, boolean, integer, float, string, raw) or nested containers (list,
* dictionary).
* <p>
* Note that a BDF integer has the same range as a Java long, while a BDF
* float has the same range as a Java double. Method names in this class
* correspond to the Java types.
* <p>
* The getX() methods throw {@link FormatException} if the specified key is
* absent, the value is null, or the value does not have the requested type.
* <p>
* The getOptionalX() methods return null if the specified key is absent or
* the value is null, or throw {@link FormatException} if the value does not
* have the requested type.
* <p>
* The getX() methods that take a default value return the default value if
* the specified key is absent or the value is null, or throw
* {@link FormatException} if the value does not have the requested type.
*/
@NotThreadSafe @NotThreadSafe
public final class BdfDictionary extends TreeMap<String, Object> { public final class BdfDictionary extends TreeMap<String, Object> {
@@ -80,12 +101,33 @@ public final class BdfDictionary extends TreeMap<String, Object> {
return value == null ? defaultValue : value; return value == null ? defaultValue : value;
} }
/**
* Returns the integer with the specified key.
* <p>
* This method should be used in preference to
* <code>getLong(key).intValue()</code> as it checks for
* overflow/underflow.
*
* @throws FormatException if there is no value at the specified key,
* or if the value is null or cannot be represented as a Java int.
*/
public Integer getInt(String key) throws FormatException { public Integer getInt(String key) throws FormatException {
Integer value = getOptionalInt(key); Integer value = getOptionalInt(key);
if (value == null) throw new FormatException(); if (value == null) throw new FormatException();
return value; return value;
} }
/**
* Returns the integer with the specified key, or null if the key is
* absent or the value is null.
* <p>
* This method should be used in preference to
* <code>getOptionalLong(key).intValue()</code> as it checks for
* overflow/underflow.
*
* @throws FormatException if the value at the specified key is not null
* and cannot be represented as a Java int.
*/
@Nullable @Nullable
public Integer getOptionalInt(String key) throws FormatException { public Integer getOptionalInt(String key) throws FormatException {
Long value = getOptionalLong(key); Long value = getOptionalLong(key);
@@ -96,6 +138,17 @@ public final class BdfDictionary extends TreeMap<String, Object> {
return value.intValue(); return value.intValue();
} }
/**
* Returns the integer with the specified key, or the given default
* value if the key is absent or the value is null.
* <p>
* This method should be used in preference to
* <code>getLong(key, defaultValue).intValue()</code> as it checks for
* overflow/underflow.
*
* @throws FormatException if the value at the specified key is not null
* and cannot be represented as a Java int.
*/
public Integer getInt(String key, Integer defaultValue) public Integer getInt(String key, Integer defaultValue)
throws FormatException { throws FormatException {
Integer value = getOptionalInt(key); Integer value = getOptionalInt(key);

View File

@@ -6,6 +6,11 @@ import java.util.Map.Entry;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/**
* A convenience class for building {@link BdfDictionary BdfDictionaries}
* via the {@link BdfDictionary#of(Entry[]) factory method}. Entries in
* BdfDictionaries do not have to be BdfEntries.
*/
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class BdfEntry implements Entry<String, Object>, Comparable<BdfEntry> { public class BdfEntry implements Entry<String, Object>, Comparable<BdfEntry> {

View File

@@ -12,6 +12,29 @@ import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE; import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
/**
* A BDF list contains zero or more BDF objects, which may be primitive types
* (null, boolean, integer, float, string, raw) or nested containers (list,
* dictionary).
* <p>
* Note that a BDF integer has the same range as a Java long, while a BDF
* float has the same range as a Java double. Method names in this class
* correspond to the Java types.
* <p>
* The getX() methods throw {@link FormatException} if the object at the
* specified index is null or does not have the requested type.
* <p>
* The getOptionalX() methods return null if the object at the specified
* index is null, or throw {@link FormatException} if the object does not
* have the requested type.
* <p>
* The getX() methods that take a default value return the default value if
* the object at the specified index is null, or throw
* {@link FormatException} if the object does not have the requested type.
* <p>
* All of the getters throw {@link FormatException} if the specified index is
* out of range.
*/
@NotThreadSafe @NotThreadSafe
public final class BdfList extends ArrayList<Object> { public final class BdfList extends ArrayList<Object> {
@@ -82,12 +105,34 @@ public final class BdfList extends ArrayList<Object> {
return value == null ? defaultValue : value; return value == null ? defaultValue : value;
} }
/**
* Returns the integer at the specified index.
* <p>
* This method should be used in preference to
* <code>getLong(index).intValue()</code> as it checks for
* overflow/underflow.
*
* @throws FormatException if the index is out of range, or if the
* value at the specified index is null or cannot be represented as a
* Java int.
*/
public Integer getInt(int index) throws FormatException { public Integer getInt(int index) throws FormatException {
Integer value = getOptionalInt(index); Integer value = getOptionalInt(index);
if (value == null) throw new FormatException(); if (value == null) throw new FormatException();
return value; return value;
} }
/**
* Returns the integer at the specified index, or null if the object at
* the specified index is null.
* <p>
* This method should be used in preference to
* <code>getOptionalLong(index).intValue()</code> as it checks for
* overflow/underflow.
*
* @throws FormatException if the index is out of range, or if the value
* at the specified index cannot be represented as a Java int.
*/
@Nullable @Nullable
public Integer getOptionalInt(int index) throws FormatException { public Integer getOptionalInt(int index) throws FormatException {
Long value = getOptionalLong(index); Long value = getOptionalLong(index);
@@ -98,6 +143,17 @@ public final class BdfList extends ArrayList<Object> {
return value.intValue(); return value.intValue();
} }
/**
* Returns the integer at the specified index, or the given default value
* if the object at the specified index is null.
* <p>
* This method should be used in preference to
* <code>getLong(index, defaultValue).intValue()</code> as it checks for
* overflow/underflow.
*
* @throws FormatException if the index is out of range, or if the value
* at the specified index cannot be represented as a Java int.
*/
public Integer getInt(int index, Integer defaultValue) public Integer getInt(int index, Integer defaultValue)
throws FormatException { throws FormatException {
Integer value = getOptionalInt(index); Integer value = getOptionalInt(index);

View File

@@ -1,70 +1,178 @@
package org.briarproject.bramble.api.data; package org.briarproject.bramble.api.data;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException; import java.io.IOException;
/**
* An interface for reading BDF objects from an input stream.
* <p>
* The readX() methods throw {@link FormatException} if the data is not in
* canonical form, but the hasX() and skipX() methods do not check for
* canonical form.
*/
@NotNullByDefault @NotNullByDefault
public interface BdfReader { public interface BdfReader {
int DEFAULT_NESTED_LIMIT = 5; int DEFAULT_NESTED_LIMIT = 5;
int DEFAULT_MAX_BUFFER_SIZE = 64 * 1024; int DEFAULT_MAX_BUFFER_SIZE = 64 * 1024;
/**
* Returns true if the reader has reached the end of its input stream.
*/
boolean eof() throws IOException; boolean eof() throws IOException;
/**
* Closes the reader's input stream.
*/
void close() throws IOException; void close() throws IOException;
/**
* Returns true if the next object in the input is a BDF null.
*/
boolean hasNull() throws IOException; boolean hasNull() throws IOException;
/**
* Reads a BDF null from the input.
*/
void readNull() throws IOException; void readNull() throws IOException;
/**
* Skips over a BDF null.
*/
void skipNull() throws IOException; void skipNull() throws IOException;
/**
* Returns true if the next object in the input is a BDF boolean.
*/
boolean hasBoolean() throws IOException; boolean hasBoolean() throws IOException;
/**
* Reads a BDF boolean from the input and returns it.
*/
boolean readBoolean() throws IOException; boolean readBoolean() throws IOException;
/**
* Skips over a BDF boolean.
*/
void skipBoolean() throws IOException; void skipBoolean() throws IOException;
/**
* Returns true if the next object in the input is a BDF integer, which
* has the same range as a Java long.
*/
boolean hasLong() throws IOException; boolean hasLong() throws IOException;
/**
* Reads a BDF integer from the input and returns it as a Java long.
*/
long readLong() throws IOException; long readLong() throws IOException;
/**
* Skips over a BDF integer.
*/
void skipLong() throws IOException; void skipLong() throws IOException;
/**
* Returns true if the next object in the input is a BDF integer and the
* value would fit within the range of a Java int.
*/
boolean hasInt() throws IOException; boolean hasInt() throws IOException;
/**
* Reads a BDF integer from the input and returns it as a Java int.
*
* @throws FormatException if the value exceeds the range of a Java int.
*/
int readInt() throws IOException; int readInt() throws IOException;
/**
* Skips over a BDF integer.
*
* @throws FormatException if the value exceeds the range of a Java int.
*/
void skipInt() throws IOException; void skipInt() throws IOException;
/**
* Returns true if the next object in the input is a BDF float, which has
* the same range as a Java double.
*/
boolean hasDouble() throws IOException; boolean hasDouble() throws IOException;
/**
* Reads a BDF float from the input and returns it as a Java double.
*/
double readDouble() throws IOException; double readDouble() throws IOException;
/**
* Skips over a BDF float.
*/
void skipDouble() throws IOException; void skipDouble() throws IOException;
/**
* Returns true if the next object in the input is a BDF string.
*/
boolean hasString() throws IOException; boolean hasString() throws IOException;
/**
* Reads a BDF string from the input.
*
* @throws IOException If the string is not valid UTF-8.
*/
String readString() throws IOException; String readString() throws IOException;
/**
* Skips over a BDF string without checking whether it is valid UTF-8.
*/
void skipString() throws IOException; void skipString() throws IOException;
/**
* Returns true if the next object in the input is a BDF raw.
*/
boolean hasRaw() throws IOException; boolean hasRaw() throws IOException;
/**
* Reads a BDF raw from the input and returns it as a byte array.
*/
byte[] readRaw() throws IOException; byte[] readRaw() throws IOException;
/**
* Skips over a BDF raw.
*/
void skipRaw() throws IOException; void skipRaw() throws IOException;
/**
* Returns true if the next object in the input is a BDF list.
*/
boolean hasList() throws IOException; boolean hasList() throws IOException;
/**
* Reads a BDF list from the input and returns it. The list's contents
* are parsed and validated.
*/
BdfList readList() throws IOException; BdfList readList() throws IOException;
/**
* Skips over a BDF list. The list's contents are parsed (to determine
* their length) but not validated.
*/
void skipList() throws IOException; void skipList() throws IOException;
/**
* Returns true if the next object in the input is a BDF dictionary.
*/
boolean hasDictionary() throws IOException; boolean hasDictionary() throws IOException;
/**
* Reads a BDF dictionary from the input and returns it. The dictionary's
* contents are parsed and validated.
*/
BdfDictionary readDictionary() throws IOException; BdfDictionary readDictionary() throws IOException;
/**
* Skips over a BDF dictionary. The dictionary's contents are parsed
* (to determine their length) but not validated.
*/
void skipDictionary() throws IOException; void skipDictionary() throws IOException;
} }

View File

@@ -9,6 +9,12 @@ public interface BdfReaderFactory {
BdfReader createReader(InputStream in); BdfReader createReader(InputStream in);
/**
* Transitional alternative to {@link #createReader(InputStream)} that
* can create a reader that accepts non-canonical input, for backward
* compatibility.
*/
@Deprecated
BdfReader createReader(InputStream in, boolean canonical); BdfReader createReader(InputStream in, boolean canonical);
BdfReader createReader(InputStream in, int nestedLimit, BdfReader createReader(InputStream in, int nestedLimit,

View File

@@ -1,28 +1,74 @@
package org.briarproject.bramble.api.data; package org.briarproject.bramble.api.data;
import org.briarproject.bramble.api.FormatException;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
/**
* An interface for writing BDF objects to an output stream. The BDF output
* is in canonical form, ie integers and length fields are represented using
* the minimum number of bytes and dictionary keys are unique and sorted in
* lexicographic order.
*/
public interface BdfWriter { public interface BdfWriter {
/**
* Flushes the writer's output stream.
*/
void flush() throws IOException; void flush() throws IOException;
/**
* Closes the writer's output stream.
*/
void close() throws IOException; void close() throws IOException;
/**
* Writes a BDF null to the output stream.
*/
void writeNull() throws IOException; void writeNull() throws IOException;
/**
* Writes a BDF boolean to the output stream.
*/
void writeBoolean(boolean b) throws IOException; void writeBoolean(boolean b) throws IOException;
/**
* Writes a BDF integer (which has the same range as a Java long) to the
* output stream.
*/
void writeLong(long l) throws IOException; void writeLong(long l) throws IOException;
/**
* Writes a BDF float (which has the same range as a Java double) to the
* output stream.
*/
void writeDouble(double d) throws IOException; void writeDouble(double d) throws IOException;
/**
* Writes a BDF string (which uses UTF-8 encoding) to the output stream.
*/
void writeString(String s) throws IOException; void writeString(String s) throws IOException;
/**
* Writes a BDF raw to the output stream.
*/
void writeRaw(byte[] b) throws IOException; void writeRaw(byte[] b) throws IOException;
/**
* Writes a BDF list to the output stream.
*
* @throws FormatException if the contents of the given collection cannot
* be represented as (nested) BDF objects.
*/
void writeList(Collection<?> c) throws IOException; void writeList(Collection<?> c) throws IOException;
/**
* Writes a BDF dictionary to the output stream.
*
* @throws FormatException if the contents of the given map cannot be
* represented as (nested) BDF objects.
*/
void writeDictionary(Map<?, ?> m) throws IOException; void writeDictionary(Map<?, ?> m) throws IOException;
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.network;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@@ -27,4 +28,20 @@ public class NetworkStatus {
public boolean isIpv6Only() { public boolean isIpv6Only() {
return ipv6Only; return ipv6Only;
} }
@Override
public int hashCode() {
return (connected ? 1 : 0) | (wifi ? 2 : 0) | (ipv6Only ? 4 : 0);
}
@Override
public boolean equals(@Nullable Object o) {
if (o instanceof NetworkStatus) {
NetworkStatus s = (NetworkStatus) o;
return connected == s.connected
&& wifi == s.wifi
&& ipv6Only == s.ipv6Only;
}
return false;
}
} }

View File

@@ -32,8 +32,15 @@ public interface RecordReader {
* 'accept' or 'ignore' predicates * 'accept' or 'ignore' predicates
*/ */
@Nullable @Nullable
Record readRecord(Predicate<Record> accept, Predicate<Record> ignore) Record readRecord(RecordPredicate accept, RecordPredicate ignore)
throws IOException; throws IOException;
void close() throws IOException; void close() throws IOException;
/**
* Interface that reifies the generic interface {@code Predicate<Record>}
* for easier testing.
*/
interface RecordPredicate extends Predicate<Record> {
}
} }

View File

@@ -6,6 +6,7 @@ import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.Locale;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -51,7 +52,7 @@ public class PrivacyUtils {
} }
private static String scrubIpv6Address(byte[] ipv6) { private static String scrubIpv6Address(byte[] ipv6) {
String hex = toHexString(ipv6).toLowerCase(); String hex = toHexString(ipv6).toLowerCase(Locale.US);
return hex.substring(0, 2) + "[scrubbed]" + hex.substring(30); return hex.substring(0, 2) + "[scrubbed]" + hex.substring(30);
} }

View File

@@ -16,6 +16,7 @@ dependencies {
implementation "org.bouncycastle:bcprov-jdk15to18:$bouncy_castle_version" implementation "org.bouncycastle:bcprov-jdk15to18:$bouncy_castle_version"
//noinspection GradleDependency //noinspection GradleDependency
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6 implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
implementation "io.github.willena:sqlite-jdbc:$sqlite_jdbc_crypt_version"
implementation 'org.bitlet:weupnp:0.1.4' implementation 'org.bitlet:weupnp:0.1.4'
implementation 'net.i2p.crypto:eddsa:0.2.0' implementation 'net.i2p.crypto:eddsa:0.2.0'
implementation 'org.whispersystems:curve25519-java:0.5.0' implementation 'org.whispersystems:curve25519-java:0.5.0'

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.contact; package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactExchangeManager; import org.briarproject.bramble.api.contact.ContactExchangeManager;
@@ -24,6 +23,7 @@ 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.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
import org.briarproject.bramble.api.record.RecordReaderFactory; import org.briarproject.bramble.api.record.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriter; import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory; import org.briarproject.bramble.api.record.RecordWriterFactory;
@@ -61,12 +61,12 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
getLogger(ContactExchangeManagerImpl.class.getName()); getLogger(ContactExchangeManagerImpl.class.getName());
// Accept records with current protocol version, known record type // Accept records with current protocol version, known record type
private static final Predicate<Record> ACCEPT = r -> private static final RecordPredicate ACCEPT = r ->
r.getProtocolVersion() == PROTOCOL_VERSION && r.getProtocolVersion() == PROTOCOL_VERSION &&
isKnownRecordType(r.getRecordType()); isKnownRecordType(r.getRecordType());
// Ignore records with current protocol version, unknown record type // Ignore records with current protocol version, unknown record type
private static final Predicate<Record> IGNORE = r -> private static final RecordPredicate IGNORE = r ->
r.getProtocolVersion() == PROTOCOL_VERSION && r.getProtocolVersion() == PROTOCOL_VERSION &&
!isKnownRecordType(r.getRecordType()); !isKnownRecordType(r.getRecordType());

View File

@@ -5,14 +5,31 @@ import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
interface HandshakeConstants { interface HandshakeConstants {
/** /**
* The current version of the handshake protocol. * The current major version of the handshake protocol.
*/ */
byte PROTOCOL_VERSION = 0; byte PROTOCOL_MAJOR_VERSION = 0;
/** /**
* Label for deriving the master key. * The current minor version of the handshake protocol.
*/ */
String MASTER_KEY_LABEL = "org.briarproject.bramble.handshake/MASTER_KEY"; byte PROTOCOL_MINOR_VERSION = 1;
/**
* Label for deriving the master key when using the deprecated v0.0 key
* derivation method.
* <p>
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
*/
@Deprecated
String MASTER_KEY_LABEL_0_0 =
"org.briarproject.bramble.handshake/MASTER_KEY";
/**
* Label for deriving the master key when using the v0.1 key derivation
* method.
*/
String MASTER_KEY_LABEL_0_1 =
"org.briarproject.bramble.handshake/MASTER_KEY_0_1";
/** /**
* Label for deriving Alice's proof of ownership from the master key. * Label for deriving Alice's proof of ownership from the master key.

View File

@@ -13,11 +13,26 @@ interface HandshakeCrypto {
KeyPair generateEphemeralKeyPair(); KeyPair generateEphemeralKeyPair();
/** /**
* Derives the master key from the given static and ephemeral keys. * Derives the master key from the given static and ephemeral keys using
* the deprecated v0.0 key derivation method.
* <p>
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
* *
* @param alice Whether the local peer is Alice * @param alice Whether the local peer is Alice
*/ */
SecretKey deriveMasterKey(PublicKey theirStaticPublicKey, @Deprecated
SecretKey deriveMasterKey_0_0(PublicKey theirStaticPublicKey,
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
KeyPair ourEphemeralKeyPair, boolean alice)
throws GeneralSecurityException;
/**
* Derives the master key from the given static and ephemeral keys using
* the v0.1 key derivation method.
*
* @param alice Whether the local peer is Alice
*/
SecretKey deriveMasterKey_0_1(PublicKey theirStaticPublicKey,
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair, PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
KeyPair ourEphemeralKeyPair, boolean alice) KeyPair ourEphemeralKeyPair, boolean alice)
throws GeneralSecurityException; throws GeneralSecurityException;

View File

@@ -13,7 +13,8 @@ import javax.inject.Inject;
import static org.briarproject.bramble.contact.HandshakeConstants.ALICE_PROOF_LABEL; import static org.briarproject.bramble.contact.HandshakeConstants.ALICE_PROOF_LABEL;
import static org.briarproject.bramble.contact.HandshakeConstants.BOB_PROOF_LABEL; import static org.briarproject.bramble.contact.HandshakeConstants.BOB_PROOF_LABEL;
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL; import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL_0_0;
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL_0_1;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -32,7 +33,8 @@ class HandshakeCryptoImpl implements HandshakeCrypto {
} }
@Override @Override
public SecretKey deriveMasterKey(PublicKey theirStaticPublicKey, @Deprecated
public SecretKey deriveMasterKey_0_0(PublicKey theirStaticPublicKey,
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair, PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
KeyPair ourEphemeralKeyPair, boolean alice) throws KeyPair ourEphemeralKeyPair, boolean alice) throws
GeneralSecurityException { GeneralSecurityException {
@@ -46,9 +48,29 @@ class HandshakeCryptoImpl implements HandshakeCrypto {
alice ? ourEphemeral : theirEphemeral, alice ? ourEphemeral : theirEphemeral,
alice ? theirEphemeral : ourEphemeral alice ? theirEphemeral : ourEphemeral
}; };
return crypto.deriveSharedSecret(MASTER_KEY_LABEL, theirStaticPublicKey, return crypto.deriveSharedSecretBadly(MASTER_KEY_LABEL_0_0,
theirEphemeralPublicKey, ourStaticKeyPair, ourEphemeralKeyPair, theirStaticPublicKey, theirEphemeralPublicKey,
alice, inputs); ourStaticKeyPair, ourEphemeralKeyPair, alice, inputs);
}
@Override
public SecretKey deriveMasterKey_0_1(PublicKey theirStaticPublicKey,
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
KeyPair ourEphemeralKeyPair, boolean alice) throws
GeneralSecurityException {
byte[] theirStatic = theirStaticPublicKey.getEncoded();
byte[] theirEphemeral = theirEphemeralPublicKey.getEncoded();
byte[] ourStatic = ourStaticKeyPair.getPublic().getEncoded();
byte[] ourEphemeral = ourEphemeralKeyPair.getPublic().getEncoded();
byte[][] inputs = {
alice ? ourStatic : theirStatic,
alice ? theirStatic : ourStatic,
alice ? ourEphemeral : theirEphemeral,
alice ? theirEphemeral : ourEphemeral
};
return crypto.deriveSharedSecret(MASTER_KEY_LABEL_0_1,
theirStaticPublicKey, theirEphemeralPublicKey,
ourStaticKeyPair, ourEphemeralKeyPair, alice, inputs);
} }
@Override @Override

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.HandshakeManager; import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContact;
@@ -12,12 +11,12 @@ import org.briarproject.bramble.api.crypto.KeyPair;
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.TransportCrypto; import org.briarproject.bramble.api.crypto.TransportCrypto;
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.TransactionManager; import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
import org.briarproject.bramble.api.record.RecordReaderFactory; import org.briarproject.bramble.api.record.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriter; import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory; import org.briarproject.bramble.api.record.RecordWriterFactory;
@@ -28,15 +27,20 @@ import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.List;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES; import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES;
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MAJOR_VERSION;
import static org.briarproject.bramble.contact.HandshakeRecordTypes.EPHEMERAL_PUBLIC_KEY; import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MINOR_VERSION;
import static org.briarproject.bramble.contact.HandshakeRecordTypes.PROOF_OF_OWNERSHIP; import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_EPHEMERAL_PUBLIC_KEY;
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_MINOR_VERSION;
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_PROOF_OF_OWNERSHIP;
import static org.briarproject.bramble.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkLength;
@Immutable @Immutable
@@ -44,12 +48,14 @@ import static org.briarproject.bramble.util.ValidationUtils.checkLength;
class HandshakeManagerImpl implements HandshakeManager { class HandshakeManagerImpl implements HandshakeManager {
// Ignore records with current protocol version, unknown record type // Ignore records with current protocol version, unknown record type
private static final Predicate<Record> IGNORE = r -> private static final RecordPredicate IGNORE = r ->
r.getProtocolVersion() == PROTOCOL_VERSION && r.getProtocolVersion() == PROTOCOL_MAJOR_VERSION &&
!isKnownRecordType(r.getRecordType()); !isKnownRecordType(r.getRecordType());
private static boolean isKnownRecordType(byte type) { private static boolean isKnownRecordType(byte type) {
return type == EPHEMERAL_PUBLIC_KEY || type == PROOF_OF_OWNERSHIP; return type == RECORD_TYPE_EPHEMERAL_PUBLIC_KEY ||
type == RECORD_TYPE_PROOF_OF_OWNERSHIP ||
type == RECORD_TYPE_MINOR_VERSION;
} }
private final TransactionManager db; private final TransactionManager db;
@@ -61,7 +67,7 @@ class HandshakeManagerImpl implements HandshakeManager {
private final RecordWriterFactory recordWriterFactory; private final RecordWriterFactory recordWriterFactory;
@Inject @Inject
HandshakeManagerImpl(DatabaseComponent db, HandshakeManagerImpl(TransactionManager db,
IdentityManager identityManager, IdentityManager identityManager,
ContactManager contactManager, ContactManager contactManager,
TransportCrypto transportCrypto, TransportCrypto transportCrypto,
@@ -95,19 +101,31 @@ class HandshakeManagerImpl implements HandshakeManager {
.createRecordWriter(out.getOutputStream()); .createRecordWriter(out.getOutputStream());
KeyPair ourEphemeralKeyPair = KeyPair ourEphemeralKeyPair =
handshakeCrypto.generateEphemeralKeyPair(); handshakeCrypto.generateEphemeralKeyPair();
PublicKey theirEphemeralPublicKey; Pair<Byte, PublicKey> theirMinorVersionAndKey;
if (alice) { if (alice) {
sendMinorVersion(recordWriter);
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic()); sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
theirEphemeralPublicKey = receivePublicKey(recordReader); theirMinorVersionAndKey = receiveMinorVersionAndKey(recordReader);
} else { } else {
theirEphemeralPublicKey = receivePublicKey(recordReader); theirMinorVersionAndKey = receiveMinorVersionAndKey(recordReader);
sendMinorVersion(recordWriter);
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic()); sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
} }
byte theirMinorVersion = theirMinorVersionAndKey.getFirst();
PublicKey theirEphemeralPublicKey = theirMinorVersionAndKey.getSecond();
SecretKey masterKey; SecretKey masterKey;
try { try {
masterKey = handshakeCrypto.deriveMasterKey(theirStaticPublicKey, if (theirMinorVersion > 0) {
theirEphemeralPublicKey, ourStaticKeyPair, masterKey = handshakeCrypto.deriveMasterKey_0_1(
ourEphemeralKeyPair, alice); theirStaticPublicKey, theirEphemeralPublicKey,
ourStaticKeyPair, ourEphemeralKeyPair, alice);
} else {
// TODO: Remove this branch after a reasonable migration
// period (added 2023-03-10).
masterKey = handshakeCrypto.deriveMasterKey_0_0(
theirStaticPublicKey, theirEphemeralPublicKey,
ourStaticKeyPair, ourEphemeralKeyPair, alice);
}
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
throw new FormatException(); throw new FormatException();
} }
@@ -128,34 +146,91 @@ class HandshakeManagerImpl implements HandshakeManager {
} }
private void sendPublicKey(RecordWriter w, PublicKey k) throws IOException { private void sendPublicKey(RecordWriter w, PublicKey k) throws IOException {
w.writeRecord(new Record(PROTOCOL_VERSION, EPHEMERAL_PUBLIC_KEY, w.writeRecord(new Record(PROTOCOL_MAJOR_VERSION,
k.getEncoded())); RECORD_TYPE_EPHEMERAL_PUBLIC_KEY, k.getEncoded()));
w.flush(); w.flush();
} }
private PublicKey receivePublicKey(RecordReader r) throws IOException { /**
byte[] key = readRecord(r, EPHEMERAL_PUBLIC_KEY).getPayload(); * Receives the remote peer's protocol minor version and ephemeral public
* key.
* <p>
* In version 0.1 of the protocol, each peer sends a minor version record
* followed by an ephemeral public key record.
* <p>
* In version 0.0 of the protocol, each peer sends an ephemeral public key
* record without a preceding minor version record.
* <p>
* Therefore the remote peer's minor version must be non-zero if a minor
* version record is received, and is assumed to be zero if no minor
* version record is received.
*/
private Pair<Byte, PublicKey> receiveMinorVersionAndKey(RecordReader r)
throws IOException {
byte theirMinorVersion;
PublicKey theirEphemeralPublicKey;
// The first record can be either a minor version record or an
// ephemeral public key record
Record first = readRecord(r, asList(RECORD_TYPE_MINOR_VERSION,
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY));
if (first.getRecordType() == RECORD_TYPE_MINOR_VERSION) {
// The payload must be a single byte giving the remote peer's
// protocol minor version, which must be non-zero
byte[] payload = first.getPayload();
checkLength(payload, 1);
theirMinorVersion = payload[0];
if (theirMinorVersion == 0) throw new FormatException();
// The second record must be an ephemeral public key record
Record second = readRecord(r,
singletonList(RECORD_TYPE_EPHEMERAL_PUBLIC_KEY));
theirEphemeralPublicKey = parsePublicKey(second);
} else {
// The remote peer did not send a minor version record, so the
// remote peer's protocol minor version is assumed to be zero
// TODO: Remove this branch after a reasonable migration period
// (added 2023-03-10).
theirMinorVersion = 0;
theirEphemeralPublicKey = parsePublicKey(first);
}
return new Pair<>(theirMinorVersion, theirEphemeralPublicKey);
}
private PublicKey parsePublicKey(Record rec) throws IOException {
if (rec.getRecordType() != RECORD_TYPE_EPHEMERAL_PUBLIC_KEY) {
throw new AssertionError();
}
byte[] key = rec.getPayload();
checkLength(key, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES); checkLength(key, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES);
return new AgreementPublicKey(key); return new AgreementPublicKey(key);
} }
private void sendProof(RecordWriter w, byte[] proof) throws IOException { private void sendProof(RecordWriter w, byte[] proof) throws IOException {
w.writeRecord(new Record(PROTOCOL_VERSION, PROOF_OF_OWNERSHIP, proof)); w.writeRecord(new Record(PROTOCOL_MAJOR_VERSION,
RECORD_TYPE_PROOF_OF_OWNERSHIP, proof));
w.flush(); w.flush();
} }
private byte[] receiveProof(RecordReader r) throws IOException { private byte[] receiveProof(RecordReader r) throws IOException {
byte[] proof = readRecord(r, PROOF_OF_OWNERSHIP).getPayload(); Record rec = readRecord(r,
singletonList(RECORD_TYPE_PROOF_OF_OWNERSHIP));
byte[] proof = rec.getPayload();
checkLength(proof, PROOF_BYTES, PROOF_BYTES); checkLength(proof, PROOF_BYTES, PROOF_BYTES);
return proof; return proof;
} }
private Record readRecord(RecordReader r, byte expectedType) private void sendMinorVersion(RecordWriter w) throws IOException {
w.writeRecord(new Record(PROTOCOL_MAJOR_VERSION,
RECORD_TYPE_MINOR_VERSION,
new byte[] {PROTOCOL_MINOR_VERSION}));
w.flush();
}
private Record readRecord(RecordReader r, List<Byte> expectedTypes)
throws IOException { throws IOException {
// Accept records with current protocol version, expected type only // Accept records with current protocol version, expected types only
Predicate<Record> accept = rec -> RecordPredicate accept = rec ->
rec.getProtocolVersion() == PROTOCOL_VERSION && rec.getProtocolVersion() == PROTOCOL_MAJOR_VERSION &&
rec.getRecordType() == expectedType; expectedTypes.contains(rec.getRecordType());
Record rec = r.readRecord(accept, IGNORE); Record rec = r.readRecord(accept, IGNORE);
if (rec == null) throw new EOFException(); if (rec == null) throw new EOFException();
return rec; return rec;

View File

@@ -5,7 +5,9 @@ package org.briarproject.bramble.contact;
*/ */
interface HandshakeRecordTypes { interface HandshakeRecordTypes {
byte EPHEMERAL_PUBLIC_KEY = 0; byte RECORD_TYPE_EPHEMERAL_PUBLIC_KEY = 0;
byte PROOF_OF_OWNERSHIP = 1; byte RECORD_TYPE_PROOF_OF_OWNERSHIP = 1;
byte RECORD_TYPE_MINOR_VERSION = 2;
} }

View File

@@ -34,6 +34,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.Provider; import java.security.Provider;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Security; import java.security.Security;
import java.util.Locale;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -222,7 +223,8 @@ class CryptoComponentImpl implements CryptoComponent {
} }
@Override @Override
public SecretKey deriveSharedSecret(String label, @Deprecated
public SecretKey deriveSharedSecretBadly(String label,
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey, PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair, KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
boolean alice, byte[]... inputs) throws GeneralSecurityException { boolean alice, byte[]... inputs) throws GeneralSecurityException {
@@ -250,6 +252,35 @@ class CryptoComponentImpl implements CryptoComponent {
return new SecretKey(hash); return new SecretKey(hash);
} }
@Override
public SecretKey deriveSharedSecret(String label,
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
boolean alice, byte[]... inputs) throws GeneralSecurityException {
PrivateKey ourStaticPrivateKey = ourStaticKeyPair.getPrivate();
PrivateKey ourEphemeralPrivateKey = ourEphemeralKeyPair.getPrivate();
byte[][] hashInputs = new byte[inputs.length + 3][];
// Alice ephemeral/Bob ephemeral
hashInputs[0] = performRawKeyAgreement(ourEphemeralPrivateKey,
theirEphemeralPublicKey);
// Alice static/Bob ephemeral, Bob static/Alice ephemeral
if (alice) {
hashInputs[1] = performRawKeyAgreement(ourStaticPrivateKey,
theirEphemeralPublicKey);
hashInputs[2] = performRawKeyAgreement(ourEphemeralPrivateKey,
theirStaticPublicKey);
} else {
hashInputs[1] = performRawKeyAgreement(ourEphemeralPrivateKey,
theirStaticPublicKey);
hashInputs[2] = performRawKeyAgreement(ourStaticPrivateKey,
theirEphemeralPublicKey);
}
arraycopy(inputs, 0, hashInputs, 3, inputs.length);
byte[] hash = hash(label, hashInputs);
if (hash.length != SecretKey.LENGTH) throw new IllegalStateException();
return new SecretKey(hash);
}
@Override @Override
public byte[] sign(String label, byte[] toSign, PrivateKey privateKey) public byte[] sign(String label, byte[] toSign, PrivateKey privateKey)
throws GeneralSecurityException { throws GeneralSecurityException {
@@ -470,7 +501,7 @@ class CryptoComponentImpl implements CryptoComponent {
arraycopy(publicKey, 0, address, 0, publicKey.length); arraycopy(publicKey, 0, address, 0, publicKey.length);
arraycopy(checksum, 0, address, publicKey.length, ONION_CHECKSUM_BYTES); arraycopy(checksum, 0, address, publicKey.length, ONION_CHECKSUM_BYTES);
address[address.length - 1] = ONION_HS_PROTOCOL_VERSION; address[address.length - 1] = ONION_HS_PROTOCOL_VERSION;
return Base32.encode(address).toLowerCase(); return Base32.encode(address).toLowerCase(Locale.US);
} }
} }

View File

@@ -413,6 +413,9 @@ interface Database<T> {
*/ */
Collection<MessageId> getMessageIds(T txn, GroupId g) throws DbException; Collection<MessageId> getMessageIds(T txn, GroupId g) throws DbException;
Collection<String> explainGetMessageIds(T txn, GroupId g)
throws DbException;
/** /**
* Returns the IDs of any delivered messages in the given group with * Returns the IDs of any delivered messages in the given group with
* metadata that matches all entries in the given query. If the query is * metadata that matches all entries in the given query. If the query is

View File

@@ -24,7 +24,7 @@ public class DatabaseModule {
@Singleton @Singleton
Database<Connection> provideDatabase(DatabaseConfig config, Database<Connection> provideDatabase(DatabaseConfig config,
MessageFactory messageFactory, Clock clock) { MessageFactory messageFactory, Clock clock) {
return new H2Database(config, messageFactory, clock); return new SqliteDatabase(config, messageFactory, clock);
} }
@Provides @Provides

View File

@@ -4,14 +4,16 @@ class DatabaseTypes {
private final String hashType, secretType, binaryType; private final String hashType, secretType, binaryType;
private final String counterType, stringType; private final String counterType, stringType;
private final String explainCommand; // FIXME: Remove
public DatabaseTypes(String hashType, String secretType, String binaryType, public DatabaseTypes(String hashType, String secretType, String binaryType,
String counterType, String stringType) { String counterType, String stringType, String explainCommand) {
this.hashType = hashType; this.hashType = hashType;
this.secretType = secretType; this.secretType = secretType;
this.binaryType = binaryType; this.binaryType = binaryType;
this.counterType = counterType; this.counterType = counterType;
this.stringType = stringType; this.stringType = stringType;
this.explainCommand = explainCommand;
} }
/** /**
@@ -22,6 +24,7 @@ class DatabaseTypes {
* <li> _BINARY * <li> _BINARY
* <li> _COUNTER * <li> _COUNTER
* <li> _STRING * <li> _STRING
* <li> _EXPLAIN
*/ */
String replaceTypes(String s) { String replaceTypes(String s) {
s = s.replaceAll("_HASH", hashType); s = s.replaceAll("_HASH", hashType);
@@ -29,6 +32,7 @@ class DatabaseTypes {
s = s.replaceAll("_BINARY", binaryType); s = s.replaceAll("_BINARY", binaryType);
s = s.replaceAll("_COUNTER", counterType); s = s.replaceAll("_COUNTER", counterType);
s = s.replaceAll("_STRING", stringType); s = s.replaceAll("_STRING", stringType);
s = s.replaceAll("_EXPLAIN", explainCommand);
return s; return s;
} }
} }

View File

@@ -39,10 +39,13 @@ class H2Database extends JdbcDatabase {
private static final String HASH_TYPE = "BINARY(32)"; private static final String HASH_TYPE = "BINARY(32)";
private static final String SECRET_TYPE = "BINARY(32)"; private static final String SECRET_TYPE = "BINARY(32)";
private static final String BINARY_TYPE = "BINARY"; private static final String BINARY_TYPE = "BINARY";
private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT"; private static final String COUNTER_TYPE =
"INT NOT NULL AUTO_INCREMENT PRIMARY KEY";
private static final String STRING_TYPE = "VARCHAR"; private static final String STRING_TYPE = "VARCHAR";
private static final String EXPLAIN_COMMAND = "EXPLAIN";
private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE, private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE,
SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE); SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
EXPLAIN_COMMAND);
private final DatabaseConfig config; private final DatabaseConfig config;
private final String url; private final String url;
@@ -73,7 +76,7 @@ class H2Database extends JdbcDatabase {
boolean reopen = isNonEmptyDirectory(dir); boolean reopen = isNonEmptyDirectory(dir);
if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen); if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen);
if (!reopen && dir.mkdirs()) LOG.info("Created database directory"); if (!reopen && dir.mkdirs()) LOG.info("Created database directory");
super.open("org.h2.Driver", reopen, key, listener); super.open("org.h2.Driver", reopen, false, key, listener);
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Contents of account directory after opening DB:"); LOG.info("Contents of account directory after opening DB:");
logFileOrDir(LOG, INFO, dir.getParentFile()); logFileOrDir(LOG, INFO, dir.getParentFile());

View File

@@ -38,11 +38,13 @@ class HyperSqlDatabase extends JdbcDatabase {
private static final String HASH_TYPE = "BINARY(32)"; private static final String HASH_TYPE = "BINARY(32)";
private static final String SECRET_TYPE = "BINARY(32)"; private static final String SECRET_TYPE = "BINARY(32)";
private static final String BINARY_TYPE = "BINARY"; private static final String BINARY_TYPE = "BINARY";
private static final String COUNTER_TYPE = private static final String COUNTER_TYPE = "INTEGER NOT NULL"
"INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY(START WITH 1)"; + " PRIMARY KEY GENERATED ALWAYS AS IDENTITY(START WITH 1)";
private static final String STRING_TYPE = "VARCHAR"; private static final String STRING_TYPE = "VARCHAR";
private static final String EXPLAIN_COMMAND = "EXPLAIN PLAN FOR";
private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE, private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE,
SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE); SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
EXPLAIN_COMMAND);
private final DatabaseConfig config; private final DatabaseConfig config;
private final String url; private final String url;
@@ -70,7 +72,7 @@ class HyperSqlDatabase extends JdbcDatabase {
boolean reopen = isNonEmptyDirectory(dir); boolean reopen = isNonEmptyDirectory(dir);
if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen); if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen);
if (!reopen && dir.mkdirs()) LOG.info("Created database directory"); if (!reopen && dir.mkdirs()) LOG.info("Created database directory");
super.open("org.hsqldb.jdbc.JDBCDriver", reopen, key, listener); super.open("org.hsqldb.jdbc.JDBCDriver", reopen, true, key, listener);
return reopen; return reopen;
} }

View File

@@ -143,8 +143,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " handshakePublicKey _BINARY," // Null if key is unknown + " handshakePublicKey _BINARY," // Null if key is unknown
+ " localAuthorId _HASH NOT NULL," + " localAuthorId _HASH NOT NULL,"
+ " verified BOOLEAN NOT NULL," + " verified BOOLEAN NOT NULL,"
+ " syncVersions _BINARY DEFAULT '00' NOT NULL," + " syncVersions _BINARY DEFAULT x'00' NOT NULL,"
+ " PRIMARY KEY (contactId),"
+ " FOREIGN KEY (localAuthorId)" + " FOREIGN KEY (localAuthorId)"
+ " REFERENCES localAuthors (authorId)" + " REFERENCES localAuthors (authorId)"
+ " ON DELETE CASCADE)"; + " ON DELETE CASCADE)";
@@ -295,11 +294,11 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " active BOOLEAN NOT NULL," + " active BOOLEAN NOT NULL,"
+ " rootKey _SECRET," // Null for rotation keys + " rootKey _SECRET," // Null for rotation keys
+ " alice BOOLEAN," // Null for rotation keys + " alice BOOLEAN," // Null for rotation keys
+ " PRIMARY KEY (transportId, keySetId)," // FIXME: Primary key has changed, migration needed
+ " FOREIGN KEY (transportId)" + " FOREIGN KEY (transportId)"
+ " REFERENCES transports (transportId)" + " REFERENCES transports (transportId)"
+ " ON DELETE CASCADE," + " ON DELETE CASCADE,"
+ " UNIQUE (keySetId)," // FIXME: Unique constraint removed, migration needed
+ " FOREIGN KEY (contactId)" + " FOREIGN KEY (contactId)"
+ " REFERENCES contacts (contactId)" + " REFERENCES contacts (contactId)"
+ " ON DELETE CASCADE," + " ON DELETE CASCADE,"
@@ -358,6 +357,85 @@ abstract class JdbcDatabase implements Database<Connection> {
"CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline" "CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline"
+ " ON messages (cleanupDeadline)"; + " ON messages (cleanupDeadline)";
// FIXME: Migration needs to add new index
private static final String INDEX_OUTGOING_KEYS_BY_TRANSPORT_ID_KEY_SET_ID =
"CREATE INDEX IF NOT EXISTS outgoingKeysByTransportIdKeySetId"
+ " ON outgoingKeys (transportId, keySetId)";
private static final String FOREIGN_INDEX_CONTACTS_BY_LOCAL_AUTHOR_ID =
"CREATE INDEX IF NOT EXISTS contactsByLocalAuthorId"
+ " ON contacts (localAuthorId)";
private static final String FOREIGN_INDEX_GROUP_METADATA_BY_GROUP_ID =
"CREATE INDEX IF NOT EXISTS groupMetadataByGroupId"
+ " ON groupMetadata (groupId)";
private static final String FOREIGN_INDEX_GROUP_VISIBILITIES_BY_CONTACT_ID =
"CREATE INDEX IF NOT EXISTS groupVisibilitiesByContactId"
+ " ON groupVisibilities (contactId)";
private static final String FOREIGN_INDEX_GROUP_VISIBILITIES_BY_GROUP_ID =
"CREATE INDEX IF NOT EXISTS groupVisibilitiesByGroupId"
+ " ON groupVisibilities (groupId)";
private static final String FOREIGN_INDEX_MESSAGES_BY_GROUP_ID =
"CREATE INDEX IF NOT EXISTS messagesByGroupId"
+ " ON messages (groupId)";
private static final String FOREIGN_INDEX_MESSAGE_METADATA_BY_MESSAGE_ID =
"CREATE INDEX IF NOT EXISTS messageMetadataByMessageId"
+ " ON messageMetadata (messageId)";
private static final String FOREIGN_INDEX_MESSAGE_METADATA_BY_GROUP_ID =
"CREATE INDEX IF NOT EXISTS messageMetadataByGroupId"
+ " ON messageMetadata (groupId)";
private static final String FOREIGN_INDEX_MESSAGE_DEPENDENCIES_BY_GROUP_ID =
"CREATE INDEX IF NOT EXISTS messageDependenciesByGroupId"
+ " ON messageDependencies (groupId)";
private static final String
FOREIGN_INDEX_MESSAGE_DEPENDENCIES_BY_MESSAGE_ID =
"CREATE INDEX IF NOT EXISTS messageDependenciesByMessageId"
+ " ON messageDependencies (messageId)";
private static final String FOREIGN_INDEX_OFFERS_BY_CONTACT_ID =
"CREATE INDEX IF NOT EXISTS offersByContactId"
+ " ON offers (contactId)";
private static final String FOREIGN_INDEX_STATUSES_BY_MESSAGE_ID =
"CREATE INDEX IF NOT EXISTS statusesByMessageId"
+ " ON statuses (messageId)";
private static final String FOREIGN_INDEX_STATUSES_BY_CONTACT_ID =
"CREATE INDEX IF NOT EXISTS statusesByContactId"
+ " ON statuses (contactId)";
private static final String FOREIGN_INDEX_STATUSES_BY_GROUP_ID =
"CREATE INDEX IF NOT EXISTS statusesByGroupId"
+ " ON statuses (groupId)";
private static final String FOREIGN_INDEX_OUTGOING_KEYS_BY_TRANSPORT_ID =
"CREATE INDEX IF NOT EXISTS outgoingKeysByTransportId"
+ " ON outgoingKeys (transportId)";
private static final String FOREIGN_INDEX_OUTGOING_KEYS_BY_CONTACT_ID =
"CREATE INDEX IF NOT EXISTS outgoingKeysByContactId"
+ " ON outgoingKeys (contactId)";
private static final String
FOREIGN_INDEX_OUTGOING_KEYS_BY_PENDING_CONTACT_ID =
"CREATE INDEX IF NOT EXISTS outgoingKeysByPendingContactId"
+ " ON outgoingKeys (pendingContactId)";
private static final String FOREIGN_INDEX_INCOMING_KEYS_BY_TRANSPORT_ID =
"CREATE INDEX IF NOT EXISTS incomingKeysByTransportId"
+ " ON incomingKeys (transportId)";
private static final String FOREIGN_INDEX_INCOMING_KEYS_BY_KEY_SET_ID =
"CREATE INDEX IF NOT EXISTS incomingKeysByKeySetId"
+ " ON incomingKeys (keySetId)";
private static final Logger LOG = private static final Logger LOG =
getLogger(JdbcDatabase.class.getName()); getLogger(JdbcDatabase.class.getName());
@@ -393,6 +471,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
protected void open(String driverClass, boolean reopen, protected void open(String driverClass, boolean reopen,
boolean createForeignKeyIndexes,
@SuppressWarnings("unused") SecretKey key, @SuppressWarnings("unused") SecretKey key,
@Nullable MigrationListener listener) throws DbException { @Nullable MigrationListener listener) throws DbException {
// Load the JDBC driver // Load the JDBC driver
@@ -419,7 +498,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("db dirty? " + wasDirtyOnInitialisation); LOG.info("db dirty? " + wasDirtyOnInitialisation);
} }
createIndexes(txn); createIndexes(txn, createForeignKeyIndexes);
setDirty(txn, true); setDirty(txn, true);
commitTransaction(txn); commitTransaction(txn);
} catch (DbException e) { } catch (DbException e) {
@@ -552,7 +631,8 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
private void createIndexes(Connection txn) throws DbException { private void createIndexes(Connection txn, boolean createForeignKeyIndexes)
throws DbException {
Statement s = null; Statement s = null;
try { try {
s = txn.createStatement(); s = txn.createStatement();
@@ -564,6 +644,31 @@ abstract class JdbcDatabase implements Database<Connection> {
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP); s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP); s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP);
s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE); s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE);
s.executeUpdate(INDEX_OUTGOING_KEYS_BY_TRANSPORT_ID_KEY_SET_ID);
// Some DB implementations automatically create indexes on columns
// that are foreign keys, others don't
if (createForeignKeyIndexes) {
s.executeUpdate(FOREIGN_INDEX_CONTACTS_BY_LOCAL_AUTHOR_ID);
s.executeUpdate(FOREIGN_INDEX_GROUP_METADATA_BY_GROUP_ID);
s.executeUpdate(FOREIGN_INDEX_GROUP_VISIBILITIES_BY_CONTACT_ID);
s.executeUpdate(FOREIGN_INDEX_GROUP_VISIBILITIES_BY_GROUP_ID);
s.executeUpdate(FOREIGN_INDEX_MESSAGES_BY_GROUP_ID);
s.executeUpdate(FOREIGN_INDEX_MESSAGE_METADATA_BY_MESSAGE_ID);
s.executeUpdate(FOREIGN_INDEX_MESSAGE_METADATA_BY_GROUP_ID);
s.executeUpdate(FOREIGN_INDEX_MESSAGE_DEPENDENCIES_BY_GROUP_ID);
s.executeUpdate(
FOREIGN_INDEX_MESSAGE_DEPENDENCIES_BY_MESSAGE_ID);
s.executeUpdate(FOREIGN_INDEX_OFFERS_BY_CONTACT_ID);
s.executeUpdate(FOREIGN_INDEX_STATUSES_BY_MESSAGE_ID);
s.executeUpdate(FOREIGN_INDEX_STATUSES_BY_CONTACT_ID);
s.executeUpdate(FOREIGN_INDEX_STATUSES_BY_GROUP_ID);
s.executeUpdate(FOREIGN_INDEX_OUTGOING_KEYS_BY_TRANSPORT_ID);
s.executeUpdate(FOREIGN_INDEX_OUTGOING_KEYS_BY_CONTACT_ID);
s.executeUpdate(
FOREIGN_INDEX_OUTGOING_KEYS_BY_PENDING_CONTACT_ID);
s.executeUpdate(FOREIGN_INDEX_INCOMING_KEYS_BY_TRANSPORT_ID);
s.executeUpdate(FOREIGN_INDEX_INCOMING_KEYS_BY_KEY_SET_ID);
}
s.close(); s.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(s, LOG, WARNING); tryToClose(s, LOG, WARNING);
@@ -1914,6 +2019,38 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public Collection<String> explainGetMessageIds(Connection txn, GroupId g)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = dbTypes.replaceTypes("_EXPLAIN SELECT messageId"
+ " FROM messages"
+ " WHERE groupId = ? AND state = ?");
ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes());
ps.setInt(2, DELIVERED.getValue());
rs = ps.executeQuery();
int cols = rs.getMetaData().getColumnCount();
List<String> explanation = new ArrayList<>();
while (rs.next()) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= cols; i++) {
sb.append(rs.getString(i)).append(' ');
}
explanation.add(sb.toString());
}
rs.close();
ps.close();
return explanation;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public Collection<MessageId> getMessageIds(Connection txn, GroupId g, public Collection<MessageId> getMessageIds(Connection txn, GroupId g,
Metadata query) throws DbException { Metadata query) throws DbException {
@@ -2597,6 +2734,9 @@ abstract class JdbcDatabase implements Database<Connection> {
PublicKey publicKey = new AgreementPublicKey(rs.getBytes(1)); PublicKey publicKey = new AgreementPublicKey(rs.getBytes(1));
String alias = rs.getString(2); String alias = rs.getString(2);
long timestamp = rs.getLong(3); long timestamp = rs.getLong(3);
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
return new PendingContact(p, publicKey, alias, timestamp); return new PendingContact(p, publicKey, alias, timestamp);
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs, LOG, WARNING); tryToClose(rs, LOG, WARNING);

View File

@@ -0,0 +1,117 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.db.DbClosedException;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.nullsafety.NotNullByDefault;
import org.sqlite.mc.SQLiteMCSqlCipherConfig;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
import static org.briarproject.bramble.util.IoUtils.isNonEmptyDirectory;
/**
* Contains all the SQLite-specific code for the database.
*/
@NotNullByDefault
class SqliteDatabase extends JdbcDatabase {
private static final Logger LOG = getLogger(SqliteDatabase.class.getName());
private static final String HASH_TYPE = "BLOB";
private static final String SECRET_TYPE = "BLOB";
private static final String BINARY_TYPE = "BLOB";
private static final String COUNTER_TYPE =
"INTEGER PRIMARY KEY AUTOINCREMENT";
private static final String STRING_TYPE = "VARCHAR";
private static final String EXPLAIN_COMMAND = "EXPLAIN QUERY PLAN";
private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE,
SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
EXPLAIN_COMMAND);
private final DatabaseConfig config;
private final String url;
@Nullable
private volatile Properties properties = null;
@Inject
SqliteDatabase(DatabaseConfig config, MessageFactory messageFactory,
Clock clock) {
super(dbTypes, messageFactory, clock);
this.config = config;
File dir = config.getDatabaseDirectory();
String path = new File(dir, "db").getAbsolutePath();
url = "jdbc:sqlite:" + path + "?cipher=sqlcipher";
}
@Override
public boolean open(SecretKey key, @Nullable MigrationListener listener)
throws DbException {
properties = SQLiteMCSqlCipherConfig.getDefault()
.withHexKey(key.getBytes())
.build()
.toProperties();
File dir = config.getDatabaseDirectory();
boolean reopen = isNonEmptyDirectory(dir);
if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen);
if (!reopen && dir.mkdirs()) LOG.info("Created database directory");
super.open("org.sqlite.JDBC", reopen, true, key, listener);
return reopen;
}
@Override
public void close() throws DbException {
Connection c = null;
try {
c = createConnection();
setDirty(c, false);
c.close();
closeAllConnections();
} catch (SQLException e) {
tryToClose(c, LOG, WARNING);
throw new DbException(e);
}
}
@Override
protected Connection createConnection() throws DbException, SQLException {
Properties properties = this.properties;
if (properties == null) throw new DbClosedException();
Connection c = DriverManager.getConnection(url, properties);
Statement s = null;
try {
s = c.createStatement();
s.execute("PRAGMA foreign_keys = ON");
s.execute("PRAGMA secure_delete = ON");
s.close();
} catch (SQLException e) {
tryToClose(s, LOG, WARNING);
tryToClose(c, LOG, WARNING);
throw new DbException(e);
}
return c;
}
@Override
protected void compactAndClose() throws DbException {
close();
}
}

View File

@@ -1,11 +1,11 @@
package org.briarproject.bramble.keyagreement; package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection; import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
import org.briarproject.bramble.api.record.RecordReaderFactory; import org.briarproject.bramble.api.record.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriter; import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory; import org.briarproject.bramble.api.record.RecordWriterFactory;
@@ -34,12 +34,12 @@ class KeyAgreementTransport {
Logger.getLogger(KeyAgreementTransport.class.getName()); Logger.getLogger(KeyAgreementTransport.class.getName());
// Accept records with current protocol version, known record type // Accept records with current protocol version, known record type
private static final Predicate<Record> ACCEPT = r -> private static final RecordPredicate ACCEPT = r ->
r.getProtocolVersion() == PROTOCOL_VERSION && r.getProtocolVersion() == PROTOCOL_VERSION &&
isKnownRecordType(r.getRecordType()); isKnownRecordType(r.getRecordType());
// Ignore records with current protocol version, unknown record type // Ignore records with current protocol version, unknown record type
private static final Predicate<Record> IGNORE = r -> private static final RecordPredicate IGNORE = r ->
r.getProtocolVersion() == PROTOCOL_VERSION && r.getProtocolVersion() == PROTOCOL_VERSION &&
!isKnownRecordType(r.getRecordType()); !isKnownRecordType(r.getRecordType());

View File

@@ -311,6 +311,9 @@ class TorPlugin implements DuplexPlugin, EventListener {
tor.stop(); tor.stop();
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} catch (InterruptedException e) {
LOG.warning("Interrupted while stopping Tor");
Thread.currentThread().interrupt();
} }
} }
@@ -625,12 +628,23 @@ class TorPlugin implements DuplexPlugin, EventListener {
} }
private synchronized State getState(TorState torState) { private synchronized State getState(TorState torState) {
if (torState == TorState.STARTING_STOPPING || !settingsChecked) { // Treat TorState.STARTED as State.STARTING_STOPPING because it's
// only seen during startup, before TorWrapper#enableNetwork() is
// called for the first time. TorState.NOT_STARTED and
// TorState.STOPPED are mapped to State.STARTING_STOPPING because
// that's the State before we've started and after we've stopped.
if (torState == TorState.NOT_STARTED ||
torState == TorState.STARTING ||
torState == TorState.STARTED ||
torState == TorState.STOPPING ||
torState == TorState.STOPPED ||
!settingsChecked) {
return STARTING_STOPPING; return STARTING_STOPPING;
} }
if (reasonsDisabled != 0) return DISABLED; if (reasonsDisabled != 0) return DISABLED;
if (torState == TorState.CONNECTING) return ENABLING; if (torState == TorState.CONNECTING) return ENABLING;
if (torState == TorState.CONNECTED) return ACTIVE; if (torState == TorState.CONNECTED) return ACTIVE;
// The plugin is enabled in settings but the device is offline
return INACTIVE; return INACTIVE;
} }

View File

@@ -311,6 +311,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
if (latest == null) { if (latest == null) {
merged = new TransportProperties(p); merged = new TransportProperties(p);
Iterator<String> it = merged.values().iterator(); Iterator<String> it = merged.values().iterator();
//noinspection Java8CollectionRemoveIf
while (it.hasNext()) { while (it.hasNext()) {
if (isNullOrEmpty(it.next())) it.remove(); if (isNullOrEmpty(it.next())) it.remove();
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.record; package org.briarproject.bramble.record;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.util.ByteUtils; import org.briarproject.bramble.util.ByteUtils;
@@ -45,7 +44,7 @@ class RecordReaderImpl implements RecordReader {
@Nullable @Nullable
@Override @Override
public Record readRecord(Predicate<Record> accept, Predicate<Record> ignore) public Record readRecord(RecordPredicate accept, RecordPredicate ignore)
throws IOException { throws IOException {
while (true) { while (true) {
if (eof()) return null; if (eof()) return null;

View File

@@ -1,10 +1,10 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory; import org.briarproject.bramble.api.sync.MessageFactory;
@@ -41,12 +41,12 @@ import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
class SyncRecordReaderImpl implements SyncRecordReader { class SyncRecordReaderImpl implements SyncRecordReader {
// Accept records with current protocol version, known record type // Accept records with current protocol version, known record type
private static final Predicate<Record> ACCEPT = r -> private static final RecordPredicate ACCEPT = r ->
r.getProtocolVersion() == PROTOCOL_VERSION && r.getProtocolVersion() == PROTOCOL_VERSION &&
isKnownRecordType(r.getRecordType()); isKnownRecordType(r.getRecordType());
// Ignore records with current protocol version, unknown record type // Ignore records with current protocol version, unknown record type
private static final Predicate<Record> IGNORE = r -> private static final RecordPredicate IGNORE = r ->
r.getProtocolVersion() == PROTOCOL_VERSION && r.getProtocolVersion() == PROTOCOL_VERSION &&
!isKnownRecordType(r.getRecordType()); !isKnownRecordType(r.getRecordType());

View File

@@ -0,0 +1,316 @@
package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.HandshakeManager.HandshakeResult;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
import org.briarproject.bramble.api.record.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.PredicateMatcher;
import org.jmock.Expectations;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES;
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MAJOR_VERSION;
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MINOR_VERSION;
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_EPHEMERAL_PUBLIC_KEY;
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_MINOR_VERSION;
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_PROOF_OF_OWNERSHIP;
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
import static org.briarproject.bramble.test.TestUtils.getPendingContact;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class HandshakeManagerImplTest extends BrambleMockTestCase {
private final TransactionManager db =
context.mock(TransactionManager.class);
private final IdentityManager identityManager =
context.mock(IdentityManager.class);
private final ContactManager contactManager =
context.mock(ContactManager.class);
private final TransportCrypto transportCrypto =
context.mock(TransportCrypto.class);
private final HandshakeCrypto handshakeCrypto =
context.mock(HandshakeCrypto.class);
private final RecordReaderFactory recordReaderFactory =
context.mock(RecordReaderFactory.class);
private final RecordWriterFactory recordWriterFactory =
context.mock(RecordWriterFactory.class);
private final RecordReader recordReader = context.mock(RecordReader.class);
private final RecordWriter recordWriter = context.mock(RecordWriter.class);
private final StreamWriter streamWriter = context.mock(StreamWriter.class);
private final PendingContact pendingContact = getPendingContact();
private final PublicKey theirStaticPublicKey =
pendingContact.getPublicKey();
private final PublicKey ourStaticPublicKey = getAgreementPublicKey();
private final PrivateKey ourStaticPrivateKey = getAgreementPrivateKey();
private final KeyPair ourStaticKeyPair =
new KeyPair(ourStaticPublicKey, ourStaticPrivateKey);
private final PublicKey theirEphemeralPublicKey = getAgreementPublicKey();
private final PublicKey ourEphemeralPublicKey = getAgreementPublicKey();
private final PrivateKey ourEphemeralPrivateKey = getAgreementPrivateKey();
private final KeyPair ourEphemeralKeyPair =
new KeyPair(ourEphemeralPublicKey, ourEphemeralPrivateKey);
private final SecretKey masterKey = getSecretKey();
private final byte[] ourProof = getRandomBytes(PROOF_BYTES);
private final byte[] theirProof = getRandomBytes(PROOF_BYTES);
private final InputStream in = new ByteArrayInputStream(new byte[0]);
private final OutputStream out = new ByteArrayOutputStream(0);
private final HandshakeManagerImpl handshakeManager =
new HandshakeManagerImpl(db, identityManager, contactManager,
transportCrypto, handshakeCrypto, recordReaderFactory,
recordWriterFactory);
@Test
public void testHandshakeAsAliceWithPeerVersion_0_1() throws Exception {
testHandshakeWithPeerVersion_0_1(true);
}
@Test
public void testHandshakeAsBobWithPeerVersion_0_1() throws Exception {
testHandshakeWithPeerVersion_0_1(false);
}
private void testHandshakeWithPeerVersion_0_1(boolean alice)
throws Exception {
expectPrepareForHandshake(alice);
expectSendMinorVersion();
expectSendKey();
// Remote peer sends minor version, so use new key derivation
expectReceiveMinorVersion();
expectReceiveKey();
expectDeriveMasterKey_0_1(alice);
expectDeriveProof(alice);
expectSendProof();
expectReceiveProof();
expectSendEof();
expectReceiveEof();
expectVerifyOwnership(alice, true);
HandshakeResult result = handshakeManager.handshake(
pendingContact.getId(), in, streamWriter);
assertArrayEquals(masterKey.getBytes(),
result.getMasterKey().getBytes());
assertEquals(alice, result.isAlice());
}
@Test
public void testHandshakeAsAliceWithPeerVersion_0_0() throws Exception {
testHandshakeWithPeerVersion_0_0(true);
}
@Test
public void testHandshakeAsBobWithPeerVersion_0_0() throws Exception {
testHandshakeWithPeerVersion_0_0(false);
}
private void testHandshakeWithPeerVersion_0_0(boolean alice)
throws Exception {
expectPrepareForHandshake(alice);
expectSendMinorVersion();
expectSendKey();
// Remote peer does not send minor version, so use old key derivation
expectReceiveKey();
expectDeriveMasterKey_0_0(alice);
expectDeriveProof(alice);
expectSendProof();
expectReceiveProof();
expectSendEof();
expectReceiveEof();
expectVerifyOwnership(alice, true);
HandshakeResult result = handshakeManager.handshake(
pendingContact.getId(), in, streamWriter);
assertArrayEquals(masterKey.getBytes(),
result.getMasterKey().getBytes());
assertEquals(alice, result.isAlice());
}
@Test(expected = FormatException.class)
public void testProofOfOwnershipNotVerifiedAsAlice() throws Exception {
testProofOfOwnershipNotVerified(true);
}
@Test(expected = FormatException.class)
public void testProofOfOwnershipNotVerifiedAsBob() throws Exception {
testProofOfOwnershipNotVerified(false);
}
private void testProofOfOwnershipNotVerified(boolean alice)
throws Exception {
expectPrepareForHandshake(alice);
expectSendMinorVersion();
expectSendKey();
expectReceiveMinorVersion();
expectReceiveKey();
expectDeriveMasterKey_0_1(alice);
expectDeriveProof(alice);
expectSendProof();
expectReceiveProof();
expectSendEof();
expectReceiveEof();
expectVerifyOwnership(alice, false);
handshakeManager.handshake(pendingContact.getId(), in, streamWriter);
}
private void expectPrepareForHandshake(boolean alice) throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(contactManager).getPendingContact(txn,
pendingContact.getId());
will(returnValue(pendingContact));
oneOf(identityManager).getHandshakeKeys(txn);
will(returnValue(ourStaticKeyPair));
oneOf(transportCrypto).isAlice(theirStaticPublicKey,
ourStaticKeyPair);
will(returnValue(alice));
oneOf(recordReaderFactory).createRecordReader(in);
will(returnValue(recordReader));
oneOf(streamWriter).getOutputStream();
will(returnValue(out));
oneOf(recordWriterFactory).createRecordWriter(out);
will(returnValue(recordWriter));
oneOf(handshakeCrypto).generateEphemeralKeyPair();
will(returnValue(ourEphemeralKeyPair));
}});
}
private void expectSendMinorVersion() throws Exception {
expectWriteRecord(new Record(PROTOCOL_MAJOR_VERSION,
RECORD_TYPE_MINOR_VERSION,
new byte[] {PROTOCOL_MINOR_VERSION}));
}
private void expectReceiveMinorVersion() throws Exception {
expectReadRecord(new Record(PROTOCOL_MAJOR_VERSION,
RECORD_TYPE_MINOR_VERSION,
new byte[] {PROTOCOL_MINOR_VERSION}));
}
private void expectSendKey() throws Exception {
expectWriteRecord(new Record(PROTOCOL_MAJOR_VERSION,
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY,
ourEphemeralPublicKey.getEncoded()));
}
private void expectReceiveKey() throws Exception {
expectReadRecord(new Record(PROTOCOL_MAJOR_VERSION,
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY,
theirEphemeralPublicKey.getEncoded()));
}
private void expectDeriveMasterKey_0_1(boolean alice) throws Exception {
context.checking(new Expectations() {{
oneOf(handshakeCrypto).deriveMasterKey_0_1(theirStaticPublicKey,
theirEphemeralPublicKey, ourStaticKeyPair,
ourEphemeralKeyPair, alice);
will(returnValue(masterKey));
}});
}
private void expectDeriveMasterKey_0_0(boolean alice) throws Exception {
context.checking(new Expectations() {{
oneOf(handshakeCrypto).deriveMasterKey_0_0(theirStaticPublicKey,
theirEphemeralPublicKey, ourStaticKeyPair,
ourEphemeralKeyPair, alice);
will(returnValue(masterKey));
}});
}
private void expectDeriveProof(boolean alice) {
context.checking(new Expectations() {{
oneOf(handshakeCrypto).proveOwnership(masterKey, alice);
will(returnValue(ourProof));
}});
}
private void expectSendProof() throws Exception {
expectWriteRecord(new Record(PROTOCOL_MAJOR_VERSION,
RECORD_TYPE_PROOF_OF_OWNERSHIP, ourProof));
}
private void expectReceiveProof() throws Exception {
expectReadRecord(new Record(PROTOCOL_MAJOR_VERSION,
RECORD_TYPE_PROOF_OF_OWNERSHIP, theirProof));
}
private void expectSendEof() throws Exception {
context.checking(new Expectations() {{
oneOf(streamWriter).sendEndOfStream();
}});
}
private void expectReceiveEof() throws Exception {
context.checking(new Expectations() {{
oneOf(recordReader).readRecord(with(any(RecordPredicate.class)),
with(any(RecordPredicate.class)));
will(returnValue(null));
}});
}
private void expectVerifyOwnership(boolean alice, boolean verified) {
context.checking(new Expectations() {{
oneOf(handshakeCrypto).verifyOwnership(masterKey, !alice,
theirProof);
will(returnValue(verified));
}});
}
private void expectWriteRecord(Record record) throws Exception {
context.checking(new Expectations() {{
oneOf(recordWriter).writeRecord(with(new PredicateMatcher<>(
Record.class, r -> recordEquals(record, r))));
oneOf(recordWriter).flush();
}});
}
private boolean recordEquals(Record expected, Record actual) {
return expected.getProtocolVersion() == actual.getProtocolVersion() &&
expected.getRecordType() == actual.getRecordType() &&
Arrays.equals(expected.getPayload(), actual.getPayload());
}
private void expectReadRecord(Record record) throws Exception {
context.checking(new Expectations() {{
// Test that the `accept` predicate passed to the reader would
// accept the expected record
oneOf(recordReader).readRecord(with(new PredicateMatcher<>(
RecordPredicate.class, rp -> rp.test(record))),
with(any(RecordPredicate.class)));
will(returnValue(record));
}});
}
}

View File

@@ -13,6 +13,7 @@ import org.jmock.Expectations;
import org.junit.Test; import org.junit.Test;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Locale;
import static java.lang.System.arraycopy; import static java.lang.System.arraycopy;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
@@ -174,7 +175,7 @@ public class PendingContactFactoryImplTest extends BrambleMockTestCase {
rawLink[0] = (byte) formatVersion; rawLink[0] = (byte) formatVersion;
byte[] publicKeyBytes = publicKey.getEncoded(); byte[] publicKeyBytes = publicKey.getEncoded();
arraycopy(publicKeyBytes, 0, rawLink, 1, publicKeyBytes.length); arraycopy(publicKeyBytes, 0, rawLink, 1, publicKeyBytes.length);
String base32 = Base32.encode(rawLink).toLowerCase(); String base32 = Base32.encode(rawLink).toLowerCase(Locale.US);
assertEquals(BASE32_LINK_BYTES, base32.length()); assertEquals(BASE32_LINK_BYTES, base32.length());
return base32; return base32;
} }

View File

@@ -60,6 +60,22 @@ public class KeyAgreementTest extends BrambleTestCase {
assertArrayEquals(aShared.getBytes(), bShared.getBytes()); assertArrayEquals(aShared.getBytes(), bShared.getBytes());
} }
@Test
public void testDerivesStaticEphemeralSharedSecretBadly() throws Exception {
String label = getRandomString(123);
KeyPair aStatic = crypto.generateAgreementKeyPair();
KeyPair aEphemeral = crypto.generateAgreementKeyPair();
KeyPair bStatic = crypto.generateAgreementKeyPair();
KeyPair bEphemeral = crypto.generateAgreementKeyPair();
SecretKey aShared = crypto.deriveSharedSecretBadly(label,
bStatic.getPublic(), bEphemeral.getPublic(), aStatic,
aEphemeral, true, inputs);
SecretKey bShared = crypto.deriveSharedSecretBadly(label,
aStatic.getPublic(), aEphemeral.getPublic(), bStatic,
bEphemeral, false, inputs);
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
}
@Test @Test
public void testDerivesStaticEphemeralSharedSecret() throws Exception { public void testDerivesStaticEphemeralSharedSecret() throws Exception {
String label = getRandomString(123); String label = getRandomString(123);

View File

@@ -0,0 +1,30 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.junit.Test;
import java.security.SecureRandom;
import java.util.regex.Pattern;
import static org.junit.Assert.assertTrue;
public class OnionEncodingTest extends BrambleTestCase {
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
private final CryptoComponent crypto =
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
private final SecureRandom secureRandom = new SecureRandom();
@Test
public void testHostnameIsValid() {
byte[] publicKey = new byte[32];
for (int i = 0; i < 100; i++) {
secureRandom.nextBytes(publicKey);
String onion = crypto.encodeOnion(publicKey);
assertTrue(onion, ONION_V3.matcher(onion).matches());
}
}
}

View File

@@ -0,0 +1,37 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.system.Clock;
import org.junit.BeforeClass;
import java.sql.Connection;
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
import static org.junit.Assume.assumeTrue;
public class H2SqliteDatabasePerformanceComparisonTest
extends DatabasePerformanceComparisonTest {
@BeforeClass
public static void setUpClass() {
assumeTrue(isOptionalTestEnabled(
H2SqliteDatabasePerformanceComparisonTest.class));
}
@Override
Database<Connection> createDatabase(boolean conditionA,
DatabaseConfig databaseConfig, MessageFactory messageFactory,
Clock clock) {
if (conditionA) {
return new H2Database(databaseConfig, messageFactory, clock);
} else {
return new SqliteDatabase(databaseConfig, messageFactory, clock);
}
}
@Override
protected String getTestName() {
return getClass().getSimpleName();
}
}

View File

@@ -10,9 +10,11 @@ import static org.junit.Assume.assumeTrue;
public class HyperSqlDatabaseTest extends JdbcDatabaseTest { public class HyperSqlDatabaseTest extends JdbcDatabaseTest {
@Override
@Before @Before
public void setUp() { public void setUp() {
assumeTrue(isCryptoStrengthUnlimited()); assumeTrue(isCryptoStrengthUnlimited());
super.setUp();
} }
@Override @Override
@@ -20,4 +22,9 @@ public class HyperSqlDatabaseTest extends JdbcDatabaseTest {
MessageFactory messageFactory, Clock clock) { MessageFactory messageFactory, Clock clock) {
return new HyperSqlDatabase(config, messageFactory, clock); return new HyperSqlDatabase(config, messageFactory, clock);
} }
@Override
public void testExplainGetMessageIds() {
// Ugh, HSQLDB can't handle EXPLAIN PLAN FOR in prepared statements
}
} }

View File

@@ -2500,6 +2500,21 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(NO_CLEANUP_DEADLINE, db.getNextCleanupDeadline(txn)); assertEquals(NO_CLEANUP_DEADLINE, db.getNextCleanupDeadline(txn));
} }
// FIXME: Remove
@Test
public void testExplainGetMessageIds() throws Exception {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
db.addGroup(txn, group);
Collection<String> explanation = db.explainGetMessageIds(txn, groupId);
db.commitTransaction(txn);
db.close();
System.out.println("getMessageIds(T, GroupId)");
for (String line : explanation) System.out.println(line);
System.out.println();
}
private Database<Connection> open(boolean resume) throws Exception { private Database<Connection> open(boolean resume) throws Exception {
return open(resume, new TestMessageFactory(), new SystemClock()); return open(resume, new TestMessageFactory(), new SystemClock());
} }

View File

@@ -0,0 +1,25 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.system.Clock;
import org.junit.Before;
import static org.briarproject.bramble.test.TestUtils.isCryptoStrengthUnlimited;
import static org.junit.Assume.assumeTrue;
public class SqliteDatabaseTest extends JdbcDatabaseTest {
@Override
@Before
public void setUp() {
assumeTrue(isCryptoStrengthUnlimited());
super.setUp();
}
@Override
protected JdbcDatabase createDatabase(DatabaseConfig config,
MessageFactory messageFactory, Clock clock) {
return new SqliteDatabase(config, messageFactory, clock);
}
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.keyagreement; package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection; import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
@@ -8,11 +7,13 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
import org.briarproject.bramble.api.record.RecordReaderFactory; import org.briarproject.bramble.api.record.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriter; import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory; import org.briarproject.bramble.api.record.RecordWriterFactory;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction; import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.test.PredicateMatcher;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.jmock.imposters.ByteBuddyClassImposteriser; import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.junit.Test; import org.junit.Test;
@@ -21,8 +22,6 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT; import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT;
import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM; import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM;
@@ -119,7 +118,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
public void testReceiveKeyThrowsExceptionIfAtEndOfStream() public void testReceiveKeyThrowsExceptionIfAtEndOfStream()
throws Exception { throws Exception {
setup(); setup();
expectReadRecord(null); expectReadEof();
kat.receiveKey(); kat.receiveKey();
} }
@@ -148,7 +147,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
public void testReceiveConfirmThrowsExceptionIfAtEndOfStream() public void testReceiveConfirmThrowsExceptionIfAtEndOfStream()
throws Exception { throws Exception {
setup(); setup();
expectReadRecord(null); expectReadEof();
kat.receiveConfirm(); kat.receiveConfirm();
} }
@@ -209,12 +208,22 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
assertArrayEquals(expectedPayload, actual.getPayload()); assertArrayEquals(expectedPayload, actual.getPayload());
} }
private void expectReadRecord(@Nullable Record record) throws Exception { private void expectReadRecord(Record record) throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
//noinspection unchecked // Test that the `accept` predicate passed to the reader would
oneOf(recordReader).readRecord(with(any(Predicate.class)), // accept the expected record
with(any(Predicate.class))); oneOf(recordReader).readRecord(with(new PredicateMatcher<>(
RecordPredicate.class, rp -> rp.test(record))),
with(any(RecordPredicate.class)));
will(returnValue(record)); will(returnValue(record));
}}); }});
} }
private void expectReadEof() throws Exception {
context.checking(new Expectations() {{
oneOf(recordReader).readRecord(with(any(RecordPredicate.class)),
with(any(RecordPredicate.class)));
will(returnValue(null));
}});
}
} }

View File

@@ -1,9 +1,9 @@
package org.briarproject.bramble.record; package org.briarproject.bramble.record;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.util.ByteUtils; import org.briarproject.bramble.util.ByteUtils;
import org.junit.Test; import org.junit.Test;
@@ -128,12 +128,12 @@ public class RecordReaderImplTest extends BrambleTestCase {
RecordReader reader = new RecordReaderImpl(in); RecordReader reader = new RecordReaderImpl(in);
// Accept records with version 0, type 0 or 1 // Accept records with version 0, type 0 or 1
Predicate<Record> accept = r -> { RecordPredicate accept = r -> {
byte version = r.getProtocolVersion(), type = r.getRecordType(); byte version = r.getProtocolVersion(), type = r.getRecordType();
return version == 0 && (type == 0 || type == 1); return version == 0 && (type == 0 || type == 1);
}; };
// Ignore records with version 0, any other type // Ignore records with version 0, any other type
Predicate<Record> ignore = r -> { RecordPredicate ignore = r -> {
byte version = r.getProtocolVersion(), type = r.getRecordType(); byte version = r.getProtocolVersion(), type = r.getRecordType();
return version == 0 && !(type == 0 || type == 1); return version == 0 && !(type == 0 || type == 1);
}; };
@@ -183,12 +183,12 @@ public class RecordReaderImplTest extends BrambleTestCase {
RecordReader reader = new RecordReaderImpl(in); RecordReader reader = new RecordReaderImpl(in);
// Accept records with version 0, type 0 or 1 // Accept records with version 0, type 0 or 1
Predicate<Record> accept = r -> { RecordPredicate accept = r -> {
byte version = r.getProtocolVersion(), type = r.getRecordType(); byte version = r.getProtocolVersion(), type = r.getRecordType();
return version == 0 && (type == 0 || type == 1); return version == 0 && (type == 0 || type == 1);
}; };
// Ignore records with version 0, any other type // Ignore records with version 0, any other type
Predicate<Record> ignore = r -> { RecordPredicate ignore = r -> {
byte version = r.getProtocolVersion(), type = r.getRecordType(); byte version = r.getProtocolVersion(), type = r.getRecordType();
return version == 0 && !(type == 0 || type == 1); return version == 0 && !(type == 0 || type == 1);
}; };

View File

@@ -1,10 +1,10 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
@@ -24,8 +24,6 @@ import org.junit.Test;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.util.List; import java.util.List;
import javax.annotation.Nullable;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES; import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
import static org.briarproject.bramble.api.sync.RecordTypes.ACK; import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE; import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
@@ -186,7 +184,7 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
@Test @Test
public void testEofReturnsTrueWhenAtEndOfStream() throws Exception { public void testEofReturnsTrueWhenAtEndOfStream() throws Exception {
expectReadRecord(createAck()); expectReadRecord(createAck());
expectReadRecord(null); expectReadEof();
SyncRecordReader reader = SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader); new SyncRecordReaderImpl(messageFactory, recordReader);
@@ -212,15 +210,25 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
}}); }});
} }
private void expectReadRecord(@Nullable Record record) throws Exception { private void expectReadRecord(Record record) throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
//noinspection unchecked // Test that the `accept` predicate passed to the reader would
oneOf(recordReader).readRecord(with(any(Predicate.class)), // accept the expected record
with(any(Predicate.class))); oneOf(recordReader).readRecord(with(new PredicateMatcher<>(
RecordPredicate.class, rp -> rp.test(record))),
with(any(RecordPredicate.class)));
will(returnValue(record)); will(returnValue(record));
}}); }});
} }
private void expectReadEof() throws Exception {
context.checking(new Expectations() {{
oneOf(recordReader).readRecord(with(any(RecordPredicate.class)),
with(any(RecordPredicate.class)));
will(returnValue(null));
}});
}
private Record createMessage(int payloadLength) { private Record createMessage(int payloadLength) {
return new Record(PROTOCOL_VERSION, MESSAGE, new byte[payloadLength]); return new Record(PROTOCOL_VERSION, MESSAGE, new byte[payloadLength]);
} }

View File

@@ -24,6 +24,7 @@ dependencyVerification {
'com.squareup.okio:okio-jvm:3.0.0:okio-jvm-3.0.0.jar:be64a0cc1f28ea9cd5c970dd7e7557af72c808d738c495b397bf897c9921e907', 'com.squareup.okio:okio-jvm:3.0.0:okio-jvm-3.0.0.jar:be64a0cc1f28ea9cd5c970dd7e7557af72c808d738c495b397bf897c9921e907',
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291', 'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
'com.squareup:kotlinpoet:1.11.0:kotlinpoet-1.11.0.jar:2887ada1ca03dd83baa2758640d87e840d1907564db0ef88d2289c868a980492', 'com.squareup:kotlinpoet:1.11.0:kotlinpoet-1.11.0.jar:2887ada1ca03dd83baa2758640d87e840d1907564db0ef88d2289c868a980492',
'io.github.willena:sqlite-jdbc:3.41.2.1:sqlite-jdbc-3.41.2.1.jar:fb60e7137c1791db89240701338d31ca42a0bec5508c1aab1c1131cf885f2309',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3', 'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11', 'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
@@ -36,7 +37,7 @@ dependencyVerification {
'org.bouncycastle:bcprov-jdk15to18:1.71:bcprov-jdk15to18-1.71.jar:143aaa4a40edd5fc2a18db7900059f6c16f4d931b94b94b20f7e2238e6662886', 'org.bouncycastle:bcprov-jdk15to18:1.71:bcprov-jdk15to18-1.71.jar:143aaa4a40edd5fc2a18db7900059f6c16f4d931b94b94b20f7e2238e6662886',
'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b', 'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011', 'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011',
'org.briarproject:onionwrapper-core:0.0.2:onionwrapper-core-0.0.2.jar:7038e960c9e59803f0e2c19444dbb5214cd99e5a7463c0a01c45318e07a0eb80', 'org.briarproject:onionwrapper-core:0.0.5:onionwrapper-core-0.0.5.jar:9071678323535cb3dfe0f3add96066037db43ea024333eba0117c759bcbd8d63',
'org.briarproject:socks-socket:0.1:socks-socket-0.1.jar:e5898822d10f5390363c5dddb945891648c92cf93ba50709e07f0d173ec0eb4b', 'org.briarproject:socks-socket:0.1:socks-socket-0.1.jar:e5898822d10f5390363c5dddb945891648c92cf93ba50709e07f0d173ec0eb4b',
'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a', 'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a',
'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb', 'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb',

View File

@@ -11,7 +11,7 @@ dependencies {
implementation project(':bramble-core') implementation project(':bramble-core')
implementation fileTree(dir: 'libs', include: '*.jar') implementation fileTree(dir: 'libs', include: '*.jar')
def jna_version = '4.5.2' def jna_version = '5.13.0'
implementation "net.java.dev.jna:jna:$jna_version" implementation "net.java.dev.jna:jna:$jna_version"
implementation "net.java.dev.jna:jna-platform:$jna_version" implementation "net.java.dev.jna:jna-platform:$jna_version"
implementation "org.briarproject:onionwrapper-java:$onionwrapper_version" implementation "org.briarproject:onionwrapper-java:$onionwrapper_version"

View File

@@ -0,0 +1,17 @@
package org.briarproject.bramble;
import org.briarproject.bramble.network.JavaNetworkModule;
import org.briarproject.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface BrambleJavaEagerSingletons {
void inject(JavaNetworkModule.EagerSingletons init);
class Helper {
public static void injectEagerSingletons(BrambleJavaEagerSingletons c) {
c.inject(new JavaNetworkModule.EagerSingletons());
}
}
}

View File

@@ -1,33 +1,51 @@
package org.briarproject.bramble.network; package org.briarproject.bramble.network;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.network.NetworkStatus; import org.briarproject.bramble.api.network.NetworkStatus;
import org.briarproject.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.nullsafety.NotNullByDefault;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.list; import static java.util.Collections.list;
import static java.util.concurrent.TimeUnit.SECONDS;
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.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces; import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
@MethodsNotNullByDefault @NotNullByDefault
@ParametersNotNullByDefault class JavaNetworkManager implements NetworkManager, Service {
class JavaNetworkManager implements NetworkManager {
private static final Logger LOG = private static final Logger LOG =
getLogger(JavaNetworkManager.class.getName()); getLogger(JavaNetworkManager.class.getName());
private final TaskScheduler scheduler;
private final Executor ioExecutor;
private final EventBus eventBus;
private final AtomicReference<NetworkStatus> lastStatus =
new AtomicReference<>();
@Inject @Inject
JavaNetworkManager() { JavaNetworkManager(TaskScheduler scheduler,
@IoExecutor Executor ioExecutor,
EventBus eventBus) {
this.scheduler = scheduler;
this.ioExecutor = ioExecutor;
this.eventBus = eventBus;
} }
@Override @Override
@@ -48,7 +66,29 @@ class JavaNetworkManager implements NetworkManager {
} catch (SocketException e) { } catch (SocketException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
if (LOG.isLoggable(INFO)) {
LOG.info("Connected: " + connected
+ ", has IPv4 address: " + hasIpv4
+ ", has IPv6 unicast address: " + hasIpv6Unicast);
}
return new NetworkStatus(connected, false, !hasIpv4 && hasIpv6Unicast); return new NetworkStatus(connected, false, !hasIpv4 && hasIpv6Unicast);
} }
private void broadcastNetworkStatusIfChanged() {
NetworkStatus status = getNetworkStatus();
NetworkStatus old = lastStatus.getAndSet(status);
if (!status.equals(old)) {
eventBus.broadcast(new NetworkStatusEvent(status));
}
}
@Override
public void startService() {
scheduler.scheduleWithFixedDelay(this::broadcastNetworkStatusIfChanged,
ioExecutor, 0, 10, SECONDS);
}
@Override
public void stopService() {
}
} }

View File

@@ -1,7 +1,9 @@
package org.briarproject.bramble.network; package org.briarproject.bramble.network;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
@@ -10,9 +12,16 @@ import dagger.Provides;
@Module @Module
public class JavaNetworkModule { public class JavaNetworkModule {
public static class EagerSingletons {
@Inject
NetworkManager networkManager;
}
@Provides @Provides
@Singleton @Singleton
NetworkManager provideNetworkManager(JavaNetworkManager networkManager) { NetworkManager provideNetworkManager(LifecycleManager lifecycleManager,
JavaNetworkManager networkManager) {
lifecycleManager.registerService(networkManager);
return networkManager; return networkManager;
} }
} }

View File

@@ -0,0 +1,85 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TorControlPort;
import org.briarproject.bramble.api.plugin.TorDirectory;
import org.briarproject.bramble.api.plugin.TorSocksPort;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
import org.briarproject.nullsafety.NotNullByDefault;
import org.briarproject.onionwrapper.CircumventionProvider;
import org.briarproject.onionwrapper.LocationUtils;
import org.briarproject.onionwrapper.MacTorWrapper;
import org.briarproject.onionwrapper.TorWrapper;
import org.briarproject.onionwrapper.UnixTorWrapper;
import java.io.File;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.net.SocketFactory;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.OsUtils.isMac;
@Immutable
@NotNullByDefault
public class MacTorPluginFactory extends TorPluginFactory {
@Inject
MacTorPluginFactory(@IoExecutor Executor ioExecutor,
@EventExecutor Executor eventExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
EventBus eventBus,
SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Clock clock,
CryptoComponent crypto,
@TorDirectory File torDirectory,
@TorSocksPort int torSocksPort,
@TorControlPort int torControlPort) {
super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
locationUtils, eventBus, torSocketFactory, backoffFactory,
circumventionProvider, batteryManager, clock, crypto,
torDirectory, torSocksPort, torControlPort);
}
@Nullable
@Override
String getArchitectureForTorBinary() {
if (!isMac()) return null;
String arch = System.getProperty("os.arch");
if (LOG.isLoggable(INFO)) {
LOG.info("System's os.arch is " + arch);
}
if (arch.equals("x86_64")) return "x86_64";
else if (arch.equals("aarch64")) return "aarch64";
return null;
}
@Override
TorPlugin createPluginInstance(Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
String architecture) {
TorWrapper tor = new MacTorWrapper(ioExecutor, eventExecutor,
architecture, torDirectory, torSocksPort, torControlPort);
return new TorPlugin(ioExecutor, wakefulIoExecutor, networkManager,
locationUtils, torSocketFactory, circumventionProvider,
batteryManager, backoff, torRendezvousCrypto, tor, callback,
MAX_LATENCY, MAX_IDLE_TIME, true);
}
}

View File

@@ -16,7 +16,7 @@ public class DesktopSecureRandomModule {
@Provides @Provides
@Singleton @Singleton
SecureRandomProvider provideSecureRandomProvider() { SecureRandomProvider provideSecureRandomProvider() {
if (isLinux() || isMac()) return new UnixSecureRandomProvider(); if (isLinux()) return new UnixSecureRandomProvider();
return () -> null; // Use system default return () -> null; // Use system default
} }
} }

View File

@@ -20,15 +20,15 @@ dependencyVerification {
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3', 'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11', 'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
'net.java.dev.jna:jna-platform:4.5.2:jna-platform-4.5.2.jar:f1d00c167d8921c6e23c626ef9f1c3ae0be473c95c68ffa012bc7ae55a87e2d6', 'net.java.dev.jna:jna-platform:5.13.0:jna-platform-5.13.0.jar:474d7b88f6e97009b6ec1d98c3024dd95c23187c65dabfbc35331bcac3d173dd',
'net.java.dev.jna:jna:4.5.2:jna-4.5.2.jar:0c8eb7acf67261656d79005191debaba3b6bf5dd60a43735a245429381dbecff', 'net.java.dev.jna:jna:5.13.0:jna-5.13.0.jar:66d4f819a062a51a1d5627bffc23fac55d1677f0e0a1feba144aabdd670a64bb',
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0', 'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd', 'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047', 'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b', 'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011', 'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011',
'org.briarproject:onionwrapper-core:0.0.2:onionwrapper-core-0.0.2.jar:7038e960c9e59803f0e2c19444dbb5214cd99e5a7463c0a01c45318e07a0eb80', 'org.briarproject:onionwrapper-core:0.0.5:onionwrapper-core-0.0.5.jar:9071678323535cb3dfe0f3add96066037db43ea024333eba0117c759bcbd8d63',
'org.briarproject:onionwrapper-java:0.0.2:onionwrapper-java-0.0.2.jar:87a3f4082174dbbd32c4f5f062b46af1d3fedd8cfa1ec84f6ce6ccb6e3674fb6', 'org.briarproject:onionwrapper-java:0.0.5:onionwrapper-java-0.0.5.jar:19503ce1dd661f7119eee8ccd2f22b667a28d746c862dc5bb3d2e476db47e27d',
'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a', 'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a',
'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb', 'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9', 'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',

View File

@@ -1,123 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Ebene_1"
x="0px"
y="0px"
viewBox="0 0 320 179.99999"
xml:space="preserve"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="briar-android_tv_artwork_logo_horizontal_black.svg"
width="320"
height="180"
inkscape:export-filename="C:\Users\hughi\Downloads\briar-android_tv_artwork_logo_horizontal_black.png"
inkscape:export-xdpi="95.967941"
inkscape:export-ydpi="95.967941"><metadata
id="metadata71"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs69" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1366"
inkscape:window-height="705"
id="namedview67"
showgrid="false"
inkscape:zoom="2"
inkscape:cx="215.47343"
inkscape:cy="62.929329"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Ebene_1" /><style
type="text/css"
id="style3">
.st0{display:none;fill:#87C214;}
.st1{fill:#87C214;}
.st2{display:none;fill:#FFFFFF;}
.st3{fill:#95D220;}
.st4{display:none;fill:#95D220;}
</style><rect
style="opacity:1;fill:#ffffff;fill-opacity:0.98492461;stroke:none;stroke-width:0;stroke-miterlimit:1.41420996;stroke-dasharray:none;stroke-opacity:1"
id="rect3747"
width="320"
height="180"
x="0"
y="0" /><rect
style="display:none;fill:#87c214"
id="rect11"
height="43.700001"
width="43.700001"
class="st0"
y="-82.800049"
x="47.200001" /><path
class="st2"
d="m 73.2,-130 c 9.7,0 17.7,8 17.7,17.7 V 87.4 c 0,9.7 -8,17.7 -17.7,17.7 h -8.3 c -9.7,0 -17.7,-8 -17.7,-17.7 v -199.7 c 0,-9.7 7.9,-17.7 17.6,-17.7 h 8.4 m 0,-7 h -8.3 c -13.7,0 -24.7,11.1 -24.7,24.7 V 87.4 c 0,13.6 11.1,24.7 24.7,24.7 h 8.3 c 13.6,0 24.7,-11.1 24.7,-24.7 V -112.3 C 97.8,-125.9 86.8,-137 73.2,-137 Z"
id="path17"
inkscape:connector-curvature="0"
style="display:none;fill:#ffffff" /><rect
style="display:none;fill:#87c214"
id="rect25"
height="43.700001"
width="43.700001"
class="st0"
y="14.199951"
x="144.2" /><path
class="st2"
d="m 170.2,-130 c 9.7,0 17.7,8 17.7,17.7 V 87.4 c 0,9.7 -7.9,17.7 -17.7,17.7 h -8.3 c -9.7,0 -17.7,-8 -17.7,-17.7 v -199.7 c 0,-9.7 8,-17.7 17.7,-17.7 h 8.3 m 0,-7 h -8.3 c -13.6,0 -24.7,11.1 -24.7,24.7 V 87.4 c 0,13.6 11.1,24.7 24.7,24.7 h 8.3 c 13.6,0 24.7,-11.1 24.7,-24.7 v -199.7 c -0.1,-13.6 -11.1,-24.7 -24.7,-24.7 z"
id="path29"
inkscape:connector-curvature="0"
style="display:none;fill:#ffffff" /><g
id="g3745"
transform="matrix(0.65979376,0,0,0.65979376,0,-1020.103)"><path
inkscape:connector-curvature="0"
id="path13"
d="m 64.900391,1565 c -9.7,0 -17.701172,7.9992 -17.701172,17.6992 v 22.5 h 43.601562 v -22.5 c 0,-9.7 -7.901562,-17.6992 -17.601562,-17.6992 z m 96.999999,0 c -9.7,0 -17.70117,7.9992 -17.70117,17.6992 v 119.5 h 43.60156 v -119.5 c 0,-9.7 -7.90156,-17.6992 -17.60156,-17.6992 z m -114.701171,97.8008 v 119.5 c 0,9.7 7.901172,17.6992 17.701172,17.6992 h 8.298828 c 9.7,0 17.701172,-7.9992 17.701172,-17.6992 v -119.5 z m 97.000001,97 v 22.5 c 0,9.7 8.00117,17.6992 17.70117,17.6992 h 8.29883 c 9.7,0 17.70117,-7.9992 17.70117,-17.6992 v -22.5 z"
style="fill:#87c214" /><path
inkscape:connector-curvature="0"
id="path35"
d="M 17.699219,1612.1992 C 7.9992186,1612.1992 0,1620.1004 0,1629.9004 v 8.2988 c 0,9.7 7.8992186,17.7012 17.699219,17.7012 H 137.19922 v -43.7012 z m 177.101561,0 v 43.7012 h 22.5 c 9.7,0 17.69922,-7.9012 17.69922,-17.7012 v -8.2988 c 0,-9.8 -7.99922,-17.7012 -17.69922,-17.7012 z m -177.101561,97 C 7.9992186,1709.1992 0,1717.1004 0,1726.9004 v 8.2988 c 0,9.7 7.8992186,17.7012 17.699219,17.7012 h 22.5 v -43.7012 z m 80.101562,0 v 43.7012 H 217.30078 c 9.7,0 17.69922,-8.0012 17.69922,-17.7012 v -8.2988 c 0,-9.8 -7.99922,-17.7012 -17.69922,-17.7012 z"
style="fill:#95d220" /></g><rect
style="display:none;fill:#95d220"
id="rect37"
height="43.700001"
width="43.700001"
class="st4"
y="14.199951"
x="47.200001" /><path
class="st2"
d="m 217.3,14.2 c 9.7,0 17.7,7.9 17.7,17.7 v 8.3 c 0,9.7 -8,17.7 -17.7,17.7 H 17.7 C 8,57.9 0,49.9 0,40.2 V 31.9 C 0,22.2 7.9,14.2 17.7,14.2 h 199.6 m 0,-7 H 17.7 C 4.1,7.2 -7,18.3 -7,31.9 v 8.3 c 0,13.6 11.1,24.7 24.7,24.7 h 199.7 c 13.6,0 24.7,-11.1 24.7,-24.7 V 31.9 C 242,18.2 230.9,7.2 217.3,7.2 Z"
id="path41"
inkscape:connector-curvature="0"
style="display:none;fill:#ffffff" /><rect
style="display:none;fill:#95d220"
id="rect47"
height="43.700001"
width="43.700001"
class="st4"
y="-82.800049"
x="144.2" /><path
class="st2"
d="m 217.3,-82.8 c 9.7,0 17.7,7.9 17.7,17.7 v 8.3 c 0,9.7 -8,17.7 -17.7,17.7 H 17.7 C 8,-39.1 0,-47 0,-56.8 v -8.3 c 0,-9.7 7.9,-17.7 17.7,-17.7 h 199.6 m 0,-7 H 17.7 c -13.6,0 -24.7,11 -24.7,24.6 v 8.3 c 0,13.6 11.1,24.7 24.7,24.7 h 199.7 c 13.6,0 24.7,-11.1 24.7,-24.7 v -8.3 C 242,-78.8 230.9,-89.8 217.3,-89.8 Z"
id="path53"
inkscape:connector-curvature="0"
style="display:none;fill:#ffffff" /><path
inkscape:connector-curvature="0"
d="m 164.94845,130.5118 v 37.0142 h 17.3299 c 8.17094,0 12.45497,-4.0911 12.45497,-10.4911 0,-4.156 -1.91138,-7.2578 -5.73332,-8.9728 v -0.083 c 2.89934,-1.7808 4.15207,-4.0233 4.15207,-7.521 0,-5.2791 -3.62408,-9.9627 -11.26806,-9.9627 z m 35.97934,0 v 37.0142 h 4.34796 v -14.8454 l -0.45977,-0.4615 h 8.76264 c 4.87623,0 7.64484,1.7133 9.424,5.5433 l 4.61191,9.7636 h 4.87655 l -5.46909,-11.5461 c -1.18614,-2.5738 -3.0974,-4.2227 -4.81061,-4.9483 v -0.083 c 4.0196,-1.0565 7.38013,-4.6856 7.38013,-9.5024 0,-7.3236 -5.66649,-10.9515 -12.45366,-10.9515 z m 36.10959,0 v 37.0142 h 4.34922 v -37.0142 z m 25.56832,0 -16.27714,37.0142 h 4.74504 l 3.95341,-8.9069 -0.19703,-0.4619 h 20.2293 l -0.19713,0.4619 3.9535,8.9069 h 4.74491 l -16.34284,-37.0142 z m 25.82985,0 v 37.0142 h 4.34912 v -14.8454 l -0.4612,-0.4615 h 8.76406 c 4.87643,0 7.64485,1.7133 9.42429,5.5433 l 4.61186,9.7636 H 320 l -5.46903,-11.5461 c -1.18594,-2.5738 -3.09737,-4.2227 -4.81072,-4.9483 v -0.083 c 4.01975,-1.0565 7.38026,-4.6856 7.38026,-9.5024 0,-7.3236 -5.6664,-10.9515 -12.45361,-10.9515 z m -119.59919,4.0908 h 12.58636 c 4.7444,0 7.24733,1.9136 7.24733,5.8723 0,3.2982 -1.97576,5.9394 -7.24733,5.9394 h -12.58636 l 0.46124,-0.4633 v -10.8868 z m 35.97962,0 h 12.32187 c 4.48085,0 7.907,1.8468 7.97296,6.7961 0,3.9585 -3.09676,6.7292 -8.43423,6.7292 h -11.8606 l 0.45977,-0.4614 v -12.6023 z m 87.44177,0 h 12.32223 c 4.54673,0 7.97414,1.8468 7.97414,6.7961 0,3.9585 -3.09683,6.7292 -8.43417,6.7292 h -11.8622 l 0.46141,-0.4614 v -12.6023 z m -27.28106,0.4616 h 0.0674 l 1.11984,3.6954 6.52328,14.8464 0.46124,0.46 h -16.27578 l 0.46131,-0.46 6.52318,-14.8464 z m -96.14033,15.4387 h 13.44199 c 5.46934,0 7.97421,2.5073 7.97421,6.532 0,4.0908 -2.30722,6.401 -7.97421,6.401 h -13.44199 l 0.46124,-0.463 V 150.966 Z"
id="path57"
style="stroke-width:0.65979397" /></svg>

Before

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -21,22 +21,31 @@ android {
packagingOptions { packagingOptions {
doNotStrip '**/*.so' doNotStrip '**/*.so'
jniLibs {
// Unpack native libs from the APK rather than using them in-place. We package the
// Tor binaries as native libs and need them to be unpacked so we can execute them
useLegacyPackaging = true
}
} }
defaultConfig { defaultConfig {
// FIXME: sqlite-jdbc-crypt uses __register_atfork which is only available on API >= 23.
// We might be able to solve this by recompiling (or asking upstream to recompile)
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 33
versionCode 10502 versionCode 10506
versionName "1.5.2" versionName "1.5.6"
applicationId "org.briarproject.briar.android" applicationId "org.briarproject.briar.android"
buildConfigField "String", "TorVersion", "\"$tor_version\""
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
buildConfigField "String", "TorVersion", "\"$tor_version\""
buildConfigField "String", "GitHash", buildConfigField "String", "GitHash",
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\"" "\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
def now = (long) (System.currentTimeMillis() / 1000) def now = (long) (System.currentTimeMillis() / 1000)
buildConfigField "Long", "BuildTimestamp", buildConfigField "Long", "BuildTimestamp",
"${getStdout(['git', 'log', '-n', '1', '--format=%ct'], now)}000L" "${getStdout(['git', 'log', '-n', '1', '--format=%ct'], now)}000L"
testInstrumentationRunner 'org.briarproject.briar.android.BriarTestRunner' testInstrumentationRunner 'org.briarproject.briar.android.BriarTestRunner'
testInstrumentationRunnerArguments disableAnalytics: 'true' testInstrumentationRunnerArguments disableAnalytics: 'true'
} }
@@ -78,6 +87,7 @@ android {
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
coreLibraryDesugaringEnabled true
} }
testOptions { testOptions {
@@ -97,6 +107,10 @@ android {
} }
} }
// Workaround for https://github.com/gradle/gradle/issues/20330 to make gradle-witness work
// with Android Gradle Plugin 7.4
project.evaluationDependsOn(project.getRootProject().findProject("bramble-android").getPath())
dependencies { dependencies {
// In theory this dependency shouldn't be needed, but without it Android Studio's linter will // In theory this dependency shouldn't be needed, but without it Android Studio's linter will
// complain about unresolved symbols for bramble-api test classes in briar-android tests, // complain about unresolved symbols for bramble-api test classes in briar-android tests,
@@ -143,6 +157,8 @@ dependencies {
compileOnly 'javax.annotation:jsr250-api:1.0' compileOnly 'javax.annotation:jsr250-api:1.0'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
testImplementation project(path: ':bramble-api', configuration: 'testOutput') testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation project(path: ':bramble-core', configuration: 'testOutput') testImplementation project(path: ':bramble-core', configuration: 'testOutput')

View File

@@ -1,5 +1,11 @@
Briar е приложение за обменяне на съобщения, предназначено за активисти, журналисти и всички други, които имат нужда от безопасен, лесен и стабилен начин за общуване. За разлика от другите подобни приложения, Briar може да използва Bluetooth или Wi-Fi, за да поддържа потока на информация по време на криза. При наличие на интернет, Briar използва мрежата на Tor и така предпазва потребителите и техните взаимоотношения от наблюдение. Briar е приложение за обменяне на съобщения, предназначено за активисти, журналисти и всички други, които имат нужда от безопасен, лесен и стабилен начин за общуване. За разлика от другите подобни приложения, Briar не използва централен сървър - съобщенията се обменят между устройствата не потребителите. За да поддържа потока на информация по време на криза Briar използва Bluetooth, Wi-Fi или карти с памет. При наличие на интернет, Briar използва мрежата на Tor и така предпазва потребителите и техните взаимоотношения от наблюдение.
Приложението предлага лични съобщения, групи, форуми, а също и блогове. Вградена поддръжка на мрежата на Tor. Всичко, което правите в Briar се съхранява само на устройството ви, освен ако не решите да го споделите с други потребители. Приложението предлага лични съобщения, групи, форуми, а също и блогове. Вградена поддръжка на мрежата на Tor. Всичко, което правите в Briar се съхранява само на устройството ви, освен ако не решите да го споделите с други потребители.
Няма реклами и проследяване. Изходният код на приложението е достъпен за преглед от всеки и е преминал професионален одит. Всички издания на Briar могат да бъдат пресъздадени и така може да бъде проверено, че публикувания изходен код отговаря на публикуваното тук приложение. Разработката се извършва от малък екип с нестопанска цел. Няма реклами и проследяване. Изходният код на приложението е достъпен за преглед от всеки и е преминал професионален одит. Всички издания на Briar могат да бъдат пресъздадени и така може да бъде проверено, че публикувания изходен код точно отговаря на публикуваното тук приложение. Разработката се извършва от малък екип с нестопанска цел.
Политика за лични данни: https://briarproject.org/privacy
Ръководство: https://briarproject.org/manual
Изходен код: https://code.briarproject.org/briar/briar

View File

@@ -1,5 +1,11 @@
Briar ist eine Messaging-App für Aktivisten, Journalisten und jeden, der eine sichere, einfache und robuste Art der Kommunikation benötigt. Im Gegensatz zu herkömmlichen Messaging-Apps benötigt Briar keinen zentralen Server. Nachrichten werden direkt zwischen den Geräten der Benutzer ausgetauscht. Wenn das Internet ausfällt, kann Briar diese auch über Bluetooth oder WLAN austauschen, um den Informationsaustausch in einer Krise aufrecht zu erhalten. Mit einer Internetverbindung kann Briar sich über das Tor-Netzwerk synchronisieren und schützt so die Nutzer und ihre Kontakte vor Überwachung. Briar ist eine Messaging-App, die für Aktivisten, Journalisten und alle anderen entwickelt wurde, die eine sichere, einfache und robuste Möglichkeit zur Kommunikation benötigen. Im Gegensatz zu herkömmlichen Messaging-Apps verlässt sich Briar nicht auf einen zentralen Server - Nachrichten werden direkt zwischen den Geräten der Benutzer synchronisiert. Wenn das Internet nicht funktioniert, kann Briar über Bluetooth, WLAN oder Speicherkarten synchronisieren und so den Informationsaustausch in Krisenzeiten aufrechterhalten. Wenn das Internet verfügbar ist, kann Briar über das Tor-Netzwerk synchronisieren und Nutzer sowie ihre Kontakte vor Überwachung schützen.
Die App bietet private Nachrichten, Gruppen und Foren sowie Blogs. Die Unterstützung für das Tor-Netzwerk ist in die App integriert. Alles, was du in Briar machst, wird nur auf deinem Gerät gespeichert, es sei denn, du entscheidest dich, es mit anderen Benutzern zu teilen. Die App bietet private Nachrichten, Gruppen und Foren sowie Blogs. Unterstützung für das Tor-Netzwerk ist in die App eingebaut. Alles, was du in Briar tust, wird nur auf deinem Gerät gespeichert, es sei denn, du entscheidest dich, es mit anderen Nutzern zu teilen.
Es gibt keine Werbung und kein Tracking. Der Quellcode der App ist komplett offen für jeden einsehbar und wurde bereits professionell auditiert. Alle Versionen von Briar sind reproduzierbar, so dass überprüft werden kann, ob der veröffentlichte Quellcode genau mit der hier veröffentlichten App übereinstimmt. Die Entwicklung wird von einem kleinen Non-Profit-Team durchgeführt. Es gibt keine Werbung und kein Tracking. Der Quellcode der App ist vollständig offen und für jeden zur Inspektion zugänglich und wurde bereits professionell überprüft. Alle Versionen von Briar sind reproduzierbar, was es möglich macht, zu überprüfen, dass der veröffentlichte Quellcode genau mit der hier veröffentlichten App übereinstimmt. Die Entwicklung erfolgt durch ein kleines gemeinnütziges Team.
Datenschutzrichtlinien: https://briarproject.org/privacy
Benutzeranleitung: https://briarproject.org/manual
Quellcode: https://code.briarproject.org/briar/briar

View File

@@ -1,5 +1,11 @@
Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate. Unlike traditional messaging apps, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices. If the internet's down, Briar can sync via Bluetooth or Wi-Fi, keeping the information flowing in a crisis. If the internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance. Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate. Unlike traditional messaging apps, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices. If the Internet's down, Briar can sync via Bluetooth, Wi-Fi or memory cards, keeping the information flowing in a crisis. If the Internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance.
The app features private messages, groups and forums as well as blogs. Support for Tor network is built into the app. Everything you do in Briar is only stored on your device unless you decide to share it with other users. The app features private messages, groups and forums as well as blogs. Support for the Tor network is built into the app. Everything you do in Briar is only stored on your device unless you decide to share it with other users.
There are no advertisements and no tracking. The source code of the app is completely open for anyone to inspect and has already been professionally audited. All releases of Briar are reproducible, making it possible to verify that the published source code matches exactly the app published here. Development is done by a small non-profit team. There are no advertisements and no tracking. The source code of the app is completely open for anyone to inspect and has already been professionally audited. All releases of Briar are reproducible, making it possible to verify that the published source code exactly matches the app published here. Development is done by a small non-profit team.
Privacy policy: https://briarproject.org/privacy
User manual: https://briarproject.org/manual
Source code: https://code.briarproject.org/briar/briar

View File

@@ -1,5 +1,11 @@
Briar este o aplicație de mesagerie concepută pentru activiști, jurnaliști și oricine altcineva care are nevoie de o modalitate sigură, ușoară și robustă de a comunica. Spre deosebire de aplicațiile de mesagerie tradiționale, Briar nu se bazează pe un server central, ci mesajele sunt sincronizate direct între dispozitivele utilizatorilor. Dacă nu este disponibilă o conexiune la internet, Briar poate realiza sincronizarea mesajelor prin Bluetooth sau Wi-Fi, permițând schimbul de informații într-o situație de criză. Când se reface conexiunea la internet, Briar poate realiza sincronizarea prin intermediul rețelei Tor, asigurând protecția utilizatorilor și a relațiilor lor împotriva supravegherii. Briar este o aplicație de mesagerie concepută pentru activiști, jurnaliști și oricine altcineva care are nevoie de o modalitate sigură, ușoară și robustă de a comunica. Spre deosebire de aplicațiile de mesagerie tradiționale, Briar nu se bazează pe un server central, ci mesajele sunt sincronizate direct între dispozitivele utilizatorilor. Dacă nu este disponibilă o conexiune la internet, Briar poate realiza sincronizarea mesajelor prin Bluetooth, Wi-Fi sau carduri de memorie, permițând schimbul de informații într-o situație de criză. Când se reface conexiunea la internet, Briar poate realiza sincronizarea prin intermediul rețelei Tor, asigurând protecția utilizatorilor și a relațiilor lor împotriva supravegherii.
Aplicația oferă mesaje private, grupuri și forumuri, precum și bloguri. Suportul pentru rețeaua Tor este integrat în aplicație. Tot ceea ce faceți în Briar se stochează doar pe dispozitivul dvs., cu excepția cazului în care decideți partajarea cu alți utilizatori. Aplicația oferă mesaje private, grupuri și forumuri, precum și bloguri. Suportul pentru rețeaua Tor este integrat în aplicație. Tot ceea ce faceți în Briar se stochează doar pe dispozitivul dvs., cu excepția cazului în care decideți partajarea cu alți utilizatori.
Nu există reclame și nici urmărire. Codul sursă al aplicației este complet deschis pentru a fi inspectat de oricine și a fost deja auditat de specialiști. Toate versiunile Briar sunt reproductibile, ceea ce face posibilă verificarea potrivirii exacte a codului sursă publicat cu aplicația publicată aici. Dezvoltarea este realizată de o mică echipă non-profit. Nu există reclame și nici urmărire. Codul sursă al aplicației este complet deschis pentru a fi inspectat de oricine și a fost deja auditat de specialiști. Toate versiunile Briar sunt reproductibile, ceea ce face posibilă verificarea potrivirii exacte a codului sursă publicat cu aplicația publicată aici. Dezvoltarea este realizată de o mică echipă non-profit.
Politica de intimitate: https://briarproject.org/privacy
Manualul de utilizare: https://briarproject.org/manual
Codul sursă: https://code.briarproject.org/briar/briar

View File

@@ -1,5 +1,11 @@
Briar je aplikácia na zasielanie správ určená pre aktivistov, novinárov a všetkých, ktorí potrebujú bezpečný, jednoduchý a spoľahlivý spôsob komunikácie. Na rozdiel od tradičných aplikácií na zasielanie správ, Briar sa nespolieha na centrálny server - správy sa synchronizujú priamo medzi zariadeniami používateľov. Ak vypadne internet, Briar sa dokáže synchronizovať cez Bluetooth alebo Wi-Fi, čím udržiava tok informácií aj v krízových situáciách. Ak internet funguje, Briar sa môže synchronizovať cez sieť Tor, čím chráni používateľov a ich vzťahy pred sledovaním. Briar je aplikácia na zasielanie správ určená pre aktivistov, novinárov a všetkých, ktorí potrebujú bezpečný, jednoduchý a spoľahlivý spôsob komunikácie. Na rozdiel od tradičných aplikácií na zasielanie správ, Briar sa nespolieha na centrálny server - správy sa synchronizujú priamo medzi zariadeniami používateľov. V prípade výpadku internetu sa Briar dokáže synchronizovať cez Bluetooth, Wi-Fi alebo pamäťové karty, čím udržiava tok informácií aj v krízových situáciách. Ak je internet v prevádzke, Briar sa môže synchronizovať prostredníctvom siete Tor, čím chráni používateľov a ich vzťahy pred sledovaním.
Aplikácia poskytuje súkromné správy, skupiny a fóra, ako aj blogy. V aplikácii je zabudovaná podpora siete Tor. Všetko, čo robíte v aplikácii Briar, sa ukladá len vo vašom zariadení, pokiaľ sa to nerozhodnete zdieľať s ostatnými používateľmi. Aplikácia poskytuje súkromné správy, skupiny a fóra, ako aj blogy. V aplikácii je zabudovaná podpora siete Tor. Všetko, čo robíte v aplikácii Briar, sa ukladá len vo vašom zariadení, pokiaľ sa to nerozhodnete zdieľať s ostatnými používateľmi.
Nie sú tu žiadne reklamy ani sledovanie. Zdrojový kód aplikácie je úplne otvorený, aby si ho mohol ktokoľvek pozrieť a už bol profesionálne skontrolovaný. Všetky vydania aplikácie Briar sú reprodukovateľné, čo umožňuje overiť, či sa zverejnený zdrojový kód presne zhoduje s tu zverejnenou aplikáciou. Vývoj aplikácie vykonáva malý neziskový tím. Nie sú tu žiadne reklamy ani sledovanie. Zdrojový kód aplikácie je úplne otvorený, aby si ho mohol ktokoľvek pozrieť, a už bol profesionálne skontrolovaný. Všetky vydania aplikácie Briar sú reprodukovateľné, čo umožňuje overiť, či sa zverejnený zdrojový kód presne zhoduje s touto zverejnenou aplikáciou. Vývoj vykonáva malý neziskový tím.
Zásady ochrany osobných údajov: https://briarproject.org/privacy
Používateľská príručka: https://briarproject.org/manual
Zdrojový kód: https://code.briarproject.org/briar/briar

View File

@@ -1,5 +1,11 @@
Briar është një aplikacion për shkëmbim mesazhesh, i konceptuar për veprimtarë, gazetarë, dhe cilido tjetër që ka nevojë për një mënyrë të sigurt, të lehtë dhe të fuqishme komunikimi. Ndryshe nga aplikacionet tradicionale, Briar-i nuk bazohet në një shërbyes qendror - mesazhet njëkohësohen drejtpërdrejt mes pajisjeve të përdoruesve. Nëse ska internet, Briar-i mund të bëjë njëkohësimin me Bluetooth ose Wi-Fi, duke mbajtur kështu rrjedhën e informacioneve në rast krizash. Nëse ka Internet, Briar-i mund të bëjë njëkohësimet përmes rrjetit Tor, duke i mbrojtur përdoruesit dhe marrëdhëniet e tyre nga survejimi. Briar është një aplikacion për shkëmbim mesazhesh, i konceptuar për veprimtarë, gazetarë, dhe cilido tjetër që ka nevojë për një mënyrë të sigurt, të lehtë dhe të fuqishme komunikimi. Ndryshe nga aplikacionet tradicionale, Briar-i nuk bazohet në një shërbyes qendror - mesazhet njëkohësohen drejtpërdrejt mes pajisjeve të përdoruesve. Nëse ska Internet, Briar-i mund të bëjë njëkohësimin me Bluetooth, Wi-Fi, ose karta kujtese, duke mbajtur kështu rrjedhën e informacioneve në rast krizash. Nëse ka Internet, Briar-i mund të bëjë njëkohësimet përmes rrjetit Tor, duke i mbrojtur përdoruesit dhe marrëdhëniet e tyre nga survejimi.
Aplikacioni lejon mesazhe, grupe dhe forume private, si edhe blogje. Rrjeti Tor mbulohet së brendshmi nga aplikacioni. Gjithçka që bëni në Briar depozitohet vetëm në pajisjen tuaj, veç në vendosshi ta ndani me përdorues të tjerë. Aplikacioni lejon mesazhe, grupe dhe forume private, si edhe blogje. Rrjeti Tor mbulohet së brendshmi nga aplikacioni. Gjithçka që bëni në Briar depozitohet vetëm në pajisjen tuaj, veç në vendosshi ta ndani me përdorues të tjerë.
Ska reklama dhe as gjurmim. Kodi burim i aplikacionit është plotësisht i lirë që ta inspektojë cilido dhe është shqyrtuar tashmë profesionalisht. Krejt hedhjet në qarkullim të Briar-it janë të riprodhueshme, duke bërë të mundur të verifikohet se kodi burim i bërë publik përputhet plotësisht me aplikacionin e publikuar këtu. Zhvillimi bëhet nga një ekip i vogël jofitimprurës. Ska reklama dhe as gjurmim. Kodi burim i aplikacionit është plotësisht i lirë që ta inspektojë cilido dhe është shqyrtuar tashmë profesionalisht. Krejt hedhjet në qarkullim të Briar-it janë të riprodhueshme, duke bërë të mundur të verifikohet se kodi burim i bërë publik përputhet saktësisht me aplikacionin e publikuar këtu. Zhvillimi bëhet nga një ekip i vogël jofitimprurës.
Rregulla privatësie: https://briarproject.org/privacy
Doracak përdoruesi: https://briarproject.org/manual
Kod burim: https://code.briarproject.org/briar/briar

View File

@@ -1,5 +1,11 @@
Briar aktivistler ve gazeteciler başta olmak üzere güvenli, kolay ve sağlam bir iletişim isteyen herkes için tasarlanmış bir ileti sistemidir. Geleneksel ileti sistemlerinin aksine Briar merkezi bir sunucu kullanmaz, iletiler doğrudan kullanıcıların aygıtları arasında eşleştirilir. Briar, eğer İnternet yoksa Bluetooth veya Wi-Fi aracılığıyla da iletileri iletebilir, böylece kriz durumlarında bilgi akışını sürdürür. İnternet varken Briar Tor ağı aracılığıyla iletim sağlar, böylece kullanıcıları ve ilkilerini gözetimden korumuş olur. Briar aktivistler ve gazeteciler başta olmak üzere güvenli, kolay ve sağlam bir iletişim isteyen herkes için tasarlanmış bir ileti aktarımı sistemidir. Geleneksel ileti aktarımı sistemlerinin aksine Briar merkezi bir sunucu kullanmaz. İletiler doğrudan kullanıcıların aygıtları arasında eşleştirilir. İnternet erişimi yoksa Briar iletileri Bluetooth veya Wi-Fi üzerinden de aktarabilir ve kriz durumlarında bilgi akışını sürdürür. İnternet erişimi varken Briar iletileri Tor ağı üzerinden aktarır. Böylece kullanıcılar ve yazıştıkları kişiler izlenmekten korunmuş olur.
Bu uygulama özel iletiler, gruplar ve forumlarla birlikte blog özelliklerine sahiptir. Tor ağı desteği uygulamada gömülüdür. Briar üzerinde yaptığınız her şey, siz başka kullanıcılarla paylaşmayı seçmediğiniz sürece, sadece kendi aygıtınızda saklanır. Bu uygulamada özel iletiler, gruplar, forumlar ve günlük özellikleri bulunur. Tor ağı desteği uygulama ile bütünleştirilmiştir. Briar üzerinde yaptığınız her şey, siz başka kullanıcılarla paylaşmayı seçmediğiniz sürece, yalnızca kendi aygıtınızda tutulur.
Ne reklam var, ne de sizi izleme. Uygulamanın kaynak kodu, incelemek isteyen herkese tamamen ıktır ve zaten profesyonel olarak da denetlenmiştir. Briar'ın tüm sürümleri yeniden üretilebilir, böylece yayınlanan kaynak kodun, burada yayınlanan uygulamayla tam olarak eşlendiğini doğrulamak mümkündür. Uygulama kâr amacı gütmeyen küçük bir ekip tarafından geliştirilmektedir. Reklam yok, izleme yok. Uygulamanın kaynak kodu, incelemek isteyen herkese açıktır ve profesyonel uzmanlar tarafından da denetlenmiştir. Tüm Briar sürümleri yeniden üretilebilir. Böylece yayınlanmış kaynak kodunun, burada yayınlanan uygulamayla bire bir aynı olduğu doğrulanabilir. Uygulama kâr amacı gütmeyen küçük bir ekip tarafından geliştirilmektedir.
Gizlilik ilkesi: https://briarproject.org/privacy
Kullanım rehberi: https://briarproject.org/manual
Kaynak kodu: https://code.briarproject.org/briar/briar

View File

@@ -1 +1 @@
Güvenli mesajlaşma, nerede olursa olsun. Güvenli ileti gönderimi, her yerde.

View File

@@ -25,8 +25,9 @@
-dontnote com.android.org.conscrypt.SSLParametersImpl -dontnote com.android.org.conscrypt.SSLParametersImpl
-dontnote org.apache.harmony.xnet.provider.jsse.SSLParametersImpl -dontnote org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
-dontnote sun.security.ssl.SSLContextImpl -dontnote sun.security.ssl.SSLContextImpl
-dontwarn org.conscrypt.OpenSSLProvider -dontwarn org.bouncycastle.jsse.**
-dontwarn org.conscrypt.Conscrypt -dontwarn org.conscrypt.**
-dontwarn org.openjsse.**
# HTML sanitiser # HTML sanitiser
-keep class org.jsoup.safety.Whitelist -keep class org.jsoup.safety.Whitelist

View File

@@ -12,13 +12,14 @@
<uses-feature <uses-feature
android:name="android.hardware.touchscreen" android:name="android.hardware.touchscreen"
android:required="false" /> android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission
android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="31" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
@@ -30,8 +31,10 @@
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" android:maxSdkVersion="18"
tools:ignore="ScopedStorage" /> tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="32" />
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> <uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC" /> <uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC" />
<uses-permission-sdk-23 android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission-sdk-23 android:name="android.permission.FOREGROUND_SERVICE" />
@@ -39,7 +42,6 @@
<application <application
android:name="org.briarproject.briar.android.BriarApplicationImpl" android:name="org.briarproject.briar.android.BriarApplicationImpl"
android:allowBackup="false" android:allowBackup="false"
android:banner="@mipmap/tv_banner"
android:dataExtractionRules="@xml/backup_extraction_rules" android:dataExtractionRules="@xml/backup_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher_round" android:icon="@mipmap/ic_launcher_round"
@@ -118,7 +120,6 @@
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>

View File

@@ -150,7 +150,8 @@ public class AppModule {
//FIXME: StrictMode //FIXME: StrictMode
StrictMode.ThreadPolicy tp = StrictMode.allowThreadDiskReads(); StrictMode.ThreadPolicy tp = StrictMode.allowThreadDiskReads();
StrictMode.allowThreadDiskWrites(); StrictMode.allowThreadDiskWrites();
File dbDir = app.getApplicationContext().getDir("db", MODE_PRIVATE); File dbDir = app.getApplicationContext().getDir("db_sqlite",
MODE_PRIVATE);
File keyDir = app.getApplicationContext().getDir("key", MODE_PRIVATE); File keyDir = app.getApplicationContext().getDir("key", MODE_PRIVATE);
StrictMode.setThreadPolicy(tp); StrictMode.setThreadPolicy(tp);
KeyStrengthener keyStrengthener = SDK_INT >= 23 KeyStrengthener keyStrengthener = SDK_INT >= 23

View File

@@ -7,17 +7,26 @@ import android.content.IntentFilter;
import android.os.PowerManager; import android.os.PowerManager;
import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.DozeWatchdog;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import androidx.annotation.RequiresApi;
import static android.content.Context.POWER_SERVICE; import static android.content.Context.POWER_SERVICE;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
import static android.os.PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
class DozeWatchdogImpl implements DozeWatchdog, Service { class DozeWatchdogImpl implements DozeWatchdog, Service {
private static final Logger LOG =
getLogger(DozeWatchdogImpl.class.getName());
private final Context appContext; private final Context appContext;
private final AtomicBoolean dozed = new AtomicBoolean(false); private final AtomicBoolean dozed = new AtomicBoolean(false);
private final BroadcastReceiver receiver = new DozeBroadcastReceiver(); private final BroadcastReceiver receiver = new DozeBroadcastReceiver();
@@ -32,14 +41,18 @@ class DozeWatchdogImpl implements DozeWatchdog, Service {
} }
@Override @Override
public void startService() throws ServiceException { public void startService() {
if (SDK_INT < 23) return; if (SDK_INT < 23) return;
IntentFilter filter = new IntentFilter(ACTION_DEVICE_IDLE_MODE_CHANGED); IntentFilter filter = new IntentFilter(ACTION_DEVICE_IDLE_MODE_CHANGED);
if (SDK_INT >= 33) {
filter.addAction(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED);
filter.addAction(ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
}
appContext.registerReceiver(receiver, filter); appContext.registerReceiver(receiver, filter);
} }
@Override @Override
public void stopService() throws ServiceException { public void stopService() {
if (SDK_INT < 23) return; if (SDK_INT < 23) return;
appContext.unregisterReceiver(receiver); appContext.unregisterReceiver(receiver);
} }
@@ -49,9 +62,33 @@ class DozeWatchdogImpl implements DozeWatchdog, Service {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (SDK_INT < 23) return; if (SDK_INT < 23) return;
String action = intent.getAction();
PowerManager pm = PowerManager pm =
(PowerManager) appContext.getSystemService(POWER_SERVICE); (PowerManager) appContext.getSystemService(POWER_SERVICE);
if (ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
if (pm.isDeviceIdleMode()) dozed.set(true); if (pm.isDeviceIdleMode()) dozed.set(true);
} else if (SDK_INT >= 33) {
onReceive33(action, pm);
}
}
@RequiresApi(33)
private void onReceive33(String action, PowerManager pm) {
if (ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED.equals(action)) {
if (pm.isLowPowerStandbyEnabled()) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("System is in low power standby mode");
}
dozed.set(true);
}
} else if (ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED.equals(action)) {
if (pm.isDeviceLightIdleMode()) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("System is in light idle mode");
}
dozed.set(true);
}
}
} }
} }
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.android.account; package org.briarproject.briar.android.account;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -8,6 +9,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.Toast;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.account.PowerView.OnCheckedChangedListener; import org.briarproject.briar.android.account.PowerView.OnCheckedChangedListener;
@@ -18,6 +20,7 @@ import androidx.annotation.Nullable;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG;
import static org.briarproject.android.dontkillmelib.DozeUtils.getDozeWhitelistingIntent; import static org.briarproject.android.dontkillmelib.DozeUtils.getDozeWhitelistingIntent;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog; import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
@@ -113,7 +116,12 @@ public class DozeFragment extends SetupFragment
private void askForDozeWhitelisting() { private void askForDozeWhitelisting() {
if (getContext() == null) return; if (getContext() == null) return;
Intent i = getDozeWhitelistingIntent(getContext()); Intent i = getDozeWhitelistingIntent(getContext());
try {
startActivityForResult(i, REQUEST_DOZE_WHITELISTING); startActivityForResult(i, REQUEST_DOZE_WHITELISTING);
} catch (ActivityNotFoundException e) {
Toast.makeText(requireContext(),
R.string.error_start_activity, LENGTH_LONG).show();
}
} }
@Override @Override

View File

@@ -14,6 +14,7 @@ import androidx.annotation.UiThread;
import static org.briarproject.android.dontkillmelib.HuaweiUtils.getHuaweiProtectedAppsIntent; import static org.briarproject.android.dontkillmelib.HuaweiUtils.getHuaweiProtectedAppsIntent;
import static org.briarproject.android.dontkillmelib.HuaweiUtils.protectedAppsNeedsToBeShown; import static org.briarproject.android.dontkillmelib.HuaweiUtils.protectedAppsNeedsToBeShown;
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
@@ -49,7 +50,7 @@ class HuaweiProtectedAppsView extends PowerView {
@Override @Override
protected void onButtonClick() { protected void onButtonClick() {
getContext().startActivity(getHuaweiProtectedAppsIntent()); tryToStartActivity(getContext(), getHuaweiProtectedAppsIntent());
setChecked(true); setChecked(true);
} }
} }

View File

@@ -19,10 +19,17 @@ import org.briarproject.nullsafety.ParametersNotNullByDefault;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.content.Context.INPUT_METHOD_SERVICE; import static android.content.Context.INPUT_METHOD_SERVICE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static androidx.core.content.ContextCompat.checkSelfPermission;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK; import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
import static org.briarproject.briar.android.util.UiUtils.setError; import static org.briarproject.briar.android.util.UiUtils.setError;
@@ -38,6 +45,10 @@ public class SetPasswordFragment extends SetupFragment {
private StrengthMeter strengthMeter; private StrengthMeter strengthMeter;
private Button nextButton; private Button nextButton;
private final ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new RequestPermission(), isGranted ->
setPassword());
public static SetPasswordFragment newInstance() { public static SetPasswordFragment newInstance() {
return new SetPasswordFragment(); return new SetPasswordFragment();
} }
@@ -121,6 +132,18 @@ public class SetPasswordFragment extends SetupFragment {
IBinder token = passwordEntry.getWindowToken(); IBinder token = passwordEntry.getWindowToken();
Object o = requireContext().getSystemService(INPUT_METHOD_SERVICE); Object o = requireContext().getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).hideSoftInputFromWindow(token, 0); ((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
if (SDK_INT >= 33 &&
checkSelfPermission(requireContext(), POST_NOTIFICATIONS) !=
PERMISSION_GRANTED) {
// this calls setPassword() when it returns
requestPermissionLauncher.launch(POST_NOTIFICATIONS);
} else {
setPassword();
}
}
private void setPassword() {
viewModel.setPassword(passwordEntry.getText().toString()); viewModel.setPassword(passwordEntry.getText().toString());
} }
} }

View File

@@ -36,7 +36,7 @@ public class SetupActivity extends BaseActivity
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
SetupViewModel viewModel; private SetupViewModel viewModel;
@Override @Override
public void injectActivity(ActivityComponent component) { public void injectActivity(ActivityComponent component) {
@@ -71,16 +71,16 @@ public class SetupActivity extends BaseActivity
} }
} }
void showPasswordFragment() { private void showPasswordFragment() {
showNextFragment(SetPasswordFragment.newInstance()); showNextFragment(SetPasswordFragment.newInstance());
} }
@TargetApi(23) @TargetApi(23)
void showDozeFragment() { private void showDozeFragment() {
showNextFragment(DozeFragment.newInstance()); showNextFragment(DozeFragment.newInstance());
} }
void showApp() { private void showApp() {
Intent i = new Intent(this, ENTRY_ACTIVITY); Intent i = new Intent(this, ENTRY_ACTIVITY);
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME | i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME |
FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_CLEAR_TOP); FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_CLEAR_TOP);

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.android.account; package org.briarproject.briar.android.account;
import android.app.KeyguardManager; import android.app.KeyguardManager;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback; import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
@@ -28,6 +29,7 @@ import static android.hardware.biometrics.BiometricPrompt.BIOMETRIC_ERROR_CANCEL
import static android.hardware.biometrics.BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED; import static android.hardware.biometrics.BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.widget.Toast.LENGTH_LONG;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_KEYGUARD_UNLOCK; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_KEYGUARD_UNLOCK;
import static org.briarproject.briar.android.util.UiUtils.hasKeyguardLock; import static org.briarproject.briar.android.util.UiUtils.hasKeyguardLock;
import static org.briarproject.briar.android.util.UiUtils.hasUsableFingerprint; import static org.briarproject.briar.android.util.UiUtils.hasUsableFingerprint;
@@ -191,7 +193,12 @@ public class UnlockActivity extends BaseActivity {
unlock(); unlock();
} else { } else {
keyguardShown = true; keyguardShown = true;
try {
startActivityForResult(intent, REQUEST_KEYGUARD_UNLOCK); startActivityForResult(intent, REQUEST_KEYGUARD_UNLOCK);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.error_start_activity, LENGTH_LONG)
.show();
}
overridePendingTransition(0, 0); overridePendingTransition(0, 0);
} }
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.android.account; package org.briarproject.briar.android.account;
import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.Toast; import android.widget.Toast;
@@ -60,12 +61,12 @@ class XiaomiLockAppsView extends PowerView {
getContext().startActivity(getXiaomiLockAppsIntent()); getContext().startActivity(getXiaomiLockAppsIntent());
setChecked(true); setChecked(true);
return; return;
} catch (SecurityException e) { } catch (SecurityException | ActivityNotFoundException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
}
Toast.makeText(getContext(), Toast.makeText(getContext(),
R.string.dnkm_xiaomi_lock_apps_error_toast, R.string.dnkm_xiaomi_lock_apps_error_toast,
LENGTH_LONG).show(); LENGTH_LONG).show();
}
// Let the user continue with setup // Let the user continue with setup
setChecked(true); setChecked(true);
} }

View File

@@ -1,9 +1,11 @@
package org.briarproject.briar.android.activity; package org.briarproject.briar.android.activity;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.transition.Transition; import android.transition.Transition;
import android.view.Window; import android.view.Window;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.Toast;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager; import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Wakeful; import org.briarproject.bramble.api.system.Wakeful;
@@ -34,9 +36,12 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION; import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.widget.Toast.LENGTH_LONG;
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.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.android.dontkillmelib.DozeUtils.getDozeWhitelistingIntent; import static org.briarproject.android.dontkillmelib.DozeUtils.getDozeWhitelistingIntent;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_UNLOCK; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_UNLOCK;
@@ -179,7 +184,13 @@ public abstract class BriarActivity extends BaseActivity {
b.setPositiveButton(R.string.fix, b.setPositiveButton(R.string.fix,
(dialog, which) -> { (dialog, which) -> {
Intent i = getDozeWhitelistingIntent(BriarActivity.this); Intent i = getDozeWhitelistingIntent(BriarActivity.this);
try {
startActivityForResult(i, REQUEST_DOZE_WHITELISTING); startActivityForResult(i, REQUEST_DOZE_WHITELISTING);
} catch (ActivityNotFoundException e) {
logException(LOG, WARNING, e);
Toast.makeText(this, R.string.error_start_activity,
LENGTH_LONG).show();
}
dialog.dismiss(); dialog.dismiss();
}); });
b.setNegativeButton(R.string.cancel, b.setNegativeButton(R.string.cancel,

View File

@@ -19,6 +19,7 @@ import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails; import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails;
import org.briarproject.nullsafety.MethodsNotNullByDefault; import org.briarproject.nullsafety.MethodsNotNullByDefault;
import org.briarproject.nullsafety.ParametersNotNullByDefault; import org.briarproject.nullsafety.ParametersNotNullByDefault;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -32,6 +33,7 @@ import androidx.fragment.app.DialogFragment;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION; import static android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION;
import static android.view.View.GONE; import static android.view.View.GONE;
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -68,6 +70,7 @@ public class ScreenFilterDialogFragment extends DialogFragment {
((BaseActivity) requireActivity()).getActivityComponent().inject(this); ((BaseActivity) requireActivity()).getActivityComponent().inject(this);
} }
@NotNull
@Override @Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Activity activity = getActivity(); Activity activity = getActivity();
@@ -98,7 +101,7 @@ public class ScreenFilterDialogFragment extends DialogFragment {
builder.setNeutralButton(R.string.screen_filter_review_apps, builder.setNeutralButton(R.string.screen_filter_review_apps,
(dialog, which) -> { (dialog, which) -> {
Intent i = new Intent(ACTION_MANAGE_OVERLAY_PERMISSION); Intent i = new Intent(ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(i); tryToStartActivity(requireActivity(), i);
}); });
} }
builder.setPositiveButton(R.string.continue_button, (dialog, which) -> { builder.setPositiveButton(R.string.continue_button, (dialog, which) -> {

View File

@@ -1,21 +1,27 @@
package org.briarproject.briar.android.hotspot; package org.briarproject.briar.android.hotspot;
import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.widget.Toast;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.core.util.Consumer; import androidx.core.util.Consumer;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import static android.content.Context.WIFI_SERVICE; import static android.content.Context.WIFI_SERVICE;
import static android.widget.Toast.LENGTH_LONG;
/** /**
* Abstract base class for the ConditionManagers that ensure that the conditions * Abstract base class for the ConditionManagers that ensure that the conditions
* to open a hotspot are fulfilled. There are different extensions of this for * to open a hotspot are fulfilled. There are different extensions of this for
* API levels lower than 29 and 29+. * API levels lower than 29, 29+ and 33+.
*/ */
abstract class AbstractConditionManager { abstract class AbstractConditionManager {
@@ -28,6 +34,7 @@ abstract class AbstractConditionManager {
final Consumer<Boolean> permissionUpdateCallback; final Consumer<Boolean> permissionUpdateCallback;
protected FragmentActivity ctx; protected FragmentActivity ctx;
WifiManager wifiManager; WifiManager wifiManager;
private ActivityResultLauncher<Intent> wifiRequest;
AbstractConditionManager(Consumer<Boolean> permissionUpdateCallback) { AbstractConditionManager(Consumer<Boolean> permissionUpdateCallback) {
this.permissionUpdateCallback = permissionUpdateCallback; this.permissionUpdateCallback = permissionUpdateCallback;
@@ -38,8 +45,12 @@ abstract class AbstractConditionManager {
*/ */
void init(FragmentActivity ctx) { void init(FragmentActivity ctx) {
this.ctx = ctx; this.ctx = ctx;
this.wifiManager = (WifiManager) ctx.getApplicationContext() wifiManager = (WifiManager) ctx.getApplicationContext()
.getSystemService(WIFI_SERVICE); .getSystemService(WIFI_SERVICE);
wifiRequest = ctx.registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> permissionUpdateCallback
.accept(wifiManager.isWifiEnabled()));
} }
/** /**
@@ -57,6 +68,8 @@ abstract class AbstractConditionManager {
*/ */
abstract boolean checkAndRequestConditions(); abstract boolean checkAndRequestConditions();
abstract String getWifiSettingsAction();
void showRationale(Context ctx, @StringRes int title, void showRationale(Context ctx, @StringRes int title,
@StringRes int body, Runnable onContinueClicked, @StringRes int body, Runnable onContinueClicked,
Runnable onDismiss) { Runnable onDismiss) {
@@ -69,4 +82,13 @@ abstract class AbstractConditionManager {
builder.show(); builder.show();
} }
void requestEnableWiFi() {
try {
wifiRequest.launch(new Intent(getWifiSettingsAction()));
} catch (ActivityNotFoundException e) {
Toast.makeText(ctx, R.string.error_start_activity, LENGTH_LONG)
.show();
}
}
} }

View File

@@ -1,15 +1,12 @@
package org.briarproject.briar.android.hotspot; package org.briarproject.briar.android.hotspot;
import android.content.Intent;
import android.provider.Settings; import android.provider.Settings;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.logging.Logger; import java.util.logging.Logger;
import androidx.activity.result.ActivityResultCaller;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.core.util.Consumer; import androidx.core.util.Consumer;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
@@ -22,20 +19,14 @@ import static java.util.logging.Logger.getLogger;
* As soon as {@link #checkAndRequestConditions()} returns true, * As soon as {@link #checkAndRequestConditions()} returns true,
* all conditions are fulfilled. * all conditions are fulfilled.
*/ */
@NotNullByDefault
class ConditionManager extends AbstractConditionManager { class ConditionManager extends AbstractConditionManager {
private static final Logger LOG = private static final Logger LOG =
getLogger(ConditionManager.class.getName()); getLogger(ConditionManager.class.getName());
private final ActivityResultLauncher<Intent> wifiRequest; ConditionManager(Consumer<Boolean> permissionUpdateCallback) {
ConditionManager(ActivityResultCaller arc,
Consumer<Boolean> permissionUpdateCallback) {
super( permissionUpdateCallback); super( permissionUpdateCallback);
wifiRequest = arc.registerForActivityResult(
new StartActivityForResult(),
result -> permissionUpdateCallback
.accept(wifiManager.isWifiEnabled()));
} }
@Override @Override
@@ -76,8 +67,9 @@ class ConditionManager extends AbstractConditionManager {
return false; return false;
} }
private void requestEnableWiFi() { @Override
wifiRequest.launch(new Intent(Settings.ACTION_WIFI_SETTINGS)); String getWifiSettingsAction() {
return Settings.ACTION_WIFI_SETTINGS;
} }
} }

View File

@@ -1,18 +1,17 @@
package org.briarproject.briar.android.hotspot; package org.briarproject.briar.android.hotspot;
import android.content.Intent;
import android.provider.Settings; import android.provider.Settings;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.util.Permission; import org.briarproject.briar.android.util.Permission;
import org.briarproject.briar.android.util.PermissionUtils; import org.briarproject.briar.android.util.PermissionUtils;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.logging.Logger; import java.util.logging.Logger;
import androidx.activity.result.ActivityResultCaller; import androidx.activity.result.ActivityResultCaller;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission; import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.core.util.Consumer; import androidx.core.util.Consumer;
@@ -28,12 +27,13 @@ import static org.briarproject.briar.android.util.PermissionUtils.showLocationDi
/** /**
* This class ensures that the conditions to open a hotspot are fulfilled on * This class ensures that the conditions to open a hotspot are fulfilled on
* API levels >= 29. * API levels >= 29 and < 33.
* <p> * <p>
* As soon as {@link #checkAndRequestConditions()} returns true, * As soon as {@link #checkAndRequestConditions()} returns true,
* all conditions are fulfilled. * all conditions are fulfilled.
*/ */
@RequiresApi(29) @RequiresApi(29)
@NotNullByDefault
class ConditionManager29 extends AbstractConditionManager { class ConditionManager29 extends AbstractConditionManager {
private static final Logger LOG = private static final Logger LOG =
@@ -42,7 +42,6 @@ class ConditionManager29 extends AbstractConditionManager {
private Permission locationPermission = Permission.UNKNOWN; private Permission locationPermission = Permission.UNKNOWN;
private final ActivityResultLauncher<String> locationRequest; private final ActivityResultLauncher<String> locationRequest;
private final ActivityResultLauncher<Intent> wifiRequest;
ConditionManager29(ActivityResultCaller arc, ConditionManager29(ActivityResultCaller arc,
Consumer<Boolean> permissionUpdateCallback) { Consumer<Boolean> permissionUpdateCallback) {
@@ -53,11 +52,6 @@ class ConditionManager29 extends AbstractConditionManager {
onRequestPermissionResult(granted); onRequestPermissionResult(granted);
permissionUpdateCallback.accept(TRUE.equals(granted)); permissionUpdateCallback.accept(TRUE.equals(granted));
}); });
wifiRequest = arc.registerForActivityResult(
new StartActivityForResult(),
result -> permissionUpdateCallback
.accept(wifiManager.isWifiEnabled())
);
} }
@Override @Override
@@ -131,6 +125,11 @@ class ConditionManager29 extends AbstractConditionManager {
return false; return false;
} }
@Override
String getWifiSettingsAction() {
return Settings.Panel.ACTION_WIFI;
}
private void onRequestPermissionResult(@Nullable Boolean granted) { private void onRequestPermissionResult(@Nullable Boolean granted) {
if (granted != null && granted) { if (granted != null && granted) {
locationPermission = Permission.GRANTED; locationPermission = Permission.GRANTED;
@@ -146,8 +145,4 @@ class ConditionManager29 extends AbstractConditionManager {
locationRequest.launch(ACCESS_FINE_LOCATION); locationRequest.launch(ACCESS_FINE_LOCATION);
} }
private void requestEnableWiFi() {
wifiRequest.launch(new Intent(Settings.Panel.ACTION_WIFI));
}
} }

View File

@@ -0,0 +1,134 @@
package org.briarproject.briar.android.hotspot;
import android.provider.Settings;
import org.briarproject.briar.R;
import org.briarproject.briar.android.util.Permission;
import org.briarproject.briar.android.util.PermissionUtils;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.logging.Logger;
import androidx.activity.result.ActivityResultCaller;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.util.Consumer;
import static android.Manifest.permission.NEARBY_WIFI_DEVICES;
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
import static java.lang.Boolean.TRUE;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
/**
* This class ensures that the conditions to open a hotspot are fulfilled on
* API levels >= 33.
* <p>
* As soon as {@link #checkAndRequestConditions()} returns true,
* all conditions are fulfilled.
*/
@RequiresApi(33)
@NotNullByDefault
class ConditionManager33 extends AbstractConditionManager {
private static final Logger LOG =
getLogger(ConditionManager33.class.getName());
private Permission nearbyWifiPermission = Permission.UNKNOWN;
private final ActivityResultLauncher<String> nearbyWifiRequest;
ConditionManager33(ActivityResultCaller arc,
Consumer<Boolean> permissionUpdateCallback) {
super(permissionUpdateCallback);
// permissionUpdateCallback receives false if permissions were denied
nearbyWifiRequest = arc.registerForActivityResult(
new RequestPermission(), granted -> {
onRequestPermissionResult(granted);
permissionUpdateCallback.accept(TRUE.equals(granted));
});
}
@Override
void onStart() {
nearbyWifiPermission = Permission.UNKNOWN;
}
private boolean areEssentialPermissionsGranted() {
boolean isWifiEnabled = wifiManager.isWifiEnabled();
if (LOG.isLoggable(INFO)) {
LOG.info(String.format("areEssentialPermissionsGranted(): " +
"nearbyWifiPermission? %s, " +
"wifiManager.isWifiEnabled()? %b",
nearbyWifiPermission, isWifiEnabled));
}
return nearbyWifiPermission == Permission.GRANTED && isWifiEnabled;
}
@Override
boolean checkAndRequestConditions() {
if (areEssentialPermissionsGranted()) return true;
if (nearbyWifiPermission == Permission.UNKNOWN) {
requestPermissions();
return false;
}
// If the location permission has been permanently denied, ask the
// user to change the setting
if (nearbyWifiPermission == Permission.PERMANENTLY_DENIED) {
PermissionUtils.showDenialDialog(ctx,
R.string.permission_nearby_devices_title,
R.string.permission_hotspot_nearby_wifi_denied_body,
() -> permissionUpdateCallback.accept(false));
return false;
}
// Should we show the rationale for location permission?
if (nearbyWifiPermission == Permission.SHOW_RATIONALE) {
showRationale(ctx,
R.string.permission_location_title,
R.string.permission_hotspot_nearby_wifi_request_body,
this::requestPermissions,
() -> permissionUpdateCallback.accept(false));
return false;
}
// If Wifi is not enabled, we show the rationale for enabling Wifi?
if (!wifiManager.isWifiEnabled()) {
showRationale(ctx, R.string.wifi_settings_title,
R.string.wifi_settings_request_enable_body,
this::requestEnableWiFi,
() -> permissionUpdateCallback.accept(false));
return false;
}
// we shouldn't usually reach this point, but if we do, return false
// anyway to force a recheck. Maybe some condition changed in the
// meantime.
return false;
}
@Override
String getWifiSettingsAction() {
return Settings.Panel.ACTION_WIFI;
}
private void onRequestPermissionResult(@Nullable Boolean granted) {
if (granted != null && granted) {
nearbyWifiPermission = Permission.GRANTED;
} else if (shouldShowRequestPermissionRationale(ctx,
NEARBY_WIFI_DEVICES)) {
nearbyWifiPermission = Permission.SHOW_RATIONALE;
} else {
nearbyWifiPermission = Permission.PERMANENTLY_DENIED;
}
}
private void requestPermissions() {
nearbyWifiRequest.launch(NEARBY_WIFI_DEVICES);
}
}

View File

@@ -31,6 +31,7 @@ import static android.view.View.VISIBLE;
import static androidx.transition.TransitionManager.beginDelayedTransition; import static androidx.transition.TransitionManager.beginDelayedTransition;
import static org.briarproject.briar.android.AppModule.getAndroidComponent; import static org.briarproject.briar.android.AppModule.getAndroidComponent;
import static org.briarproject.briar.android.hotspot.HotspotViewModel.getApkFileName; import static org.briarproject.briar.android.hotspot.HotspotViewModel.getApkFileName;
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -102,7 +103,7 @@ public class FallbackFragment extends BaseFragment {
i.putExtra(EXTRA_STREAM, uri); i.putExtra(EXTRA_STREAM, uri);
i.setType("*/*"); // gives us all sharing options i.setType("*/*"); // gives us all sharing options
i.addFlags(FLAG_GRANT_READ_URI_PERMISSION); i.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(i, null)); tryToStartActivity(requireActivity(), Intent.createChooser(i, null));
} }
} }

View File

@@ -49,7 +49,9 @@ public class HotspotIntroFragment extends Fragment {
private TextView progressTextView; private TextView progressTextView;
private final AbstractConditionManager conditionManager = SDK_INT < 29 ? private final AbstractConditionManager conditionManager = SDK_INT < 29 ?
new ConditionManager(this, this::onPermissionUpdate) : new ConditionManager(this::onPermissionUpdate) :
SDK_INT >= 33 ?
new ConditionManager33(this, this::onPermissionUpdate) :
new ConditionManager29(this, this::onPermissionUpdate); new ConditionManager29(this, this::onPermissionUpdate);
@Override @Override
@@ -87,7 +89,6 @@ public class HotspotIntroFragment extends Fragment {
} }
private void onButtonClick(View view) { private void onButtonClick(View view) {
startButton.setEnabled(false);
startHotspotIfConditionsFulfilled(); startHotspotIfConditionsFulfilled();
} }

View File

@@ -22,13 +22,19 @@ import org.briarproject.nullsafety.ParametersNotNullByDefault;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static androidx.core.content.ContextCompat.checkSelfPermission;
import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHENER_ERROR; import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHENER_ERROR;
import static org.briarproject.bramble.api.crypto.DecryptionResult.SUCCESS; import static org.briarproject.bramble.api.crypto.DecryptionResult.SUCCESS;
import static org.briarproject.briar.android.login.LoginUtils.createKeyStrengthenerErrorDialog; import static org.briarproject.briar.android.login.LoginUtils.createKeyStrengthenerErrorDialog;
@@ -52,6 +58,10 @@ public class PasswordFragment extends BaseFragment implements TextWatcher {
private TextInputLayout input; private TextInputLayout input;
private TextInputEditText password; private TextInputEditText password;
private final ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new RequestPermission(), isGranted ->
validatePassword());
@Override @Override
public void injectFragment(ActivityComponent component) { public void injectFragment(ActivityComponent component) {
component.inject(this); component.inject(this);
@@ -109,6 +119,17 @@ public class PasswordFragment extends BaseFragment implements TextWatcher {
hideSoftKeyboard(password); hideSoftKeyboard(password);
signInButton.setVisibility(INVISIBLE); signInButton.setVisibility(INVISIBLE);
progress.setVisibility(VISIBLE); progress.setVisibility(VISIBLE);
if (SDK_INT >= 33 &&
checkSelfPermission(requireContext(), POST_NOTIFICATIONS) !=
PERMISSION_GRANTED) {
// this calls validatePassword() when it returns
requestPermissionLauncher.launch(POST_NOTIFICATIONS);
} else {
validatePassword();
}
}
private void validatePassword() {
viewModel.validatePassword(password.getText().toString()); viewModel.validatePassword(password.getText().toString());
} }

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.android.reporting; package org.briarproject.briar.android.reporting;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@@ -33,13 +32,11 @@ import androidx.recyclerview.widget.RecyclerView;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
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.util.LogUtils.logException;
import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick; import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick;
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -180,13 +177,7 @@ public class ReportFormFragment extends BaseFragment {
private void triggerPrivacyPolicy() { private void triggerPrivacyPolicy() {
Intent i = new Intent(Intent.ACTION_VIEW); Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://briarproject.org/privacy-policy/\\")); i.setData(Uri.parse("https://briarproject.org/privacy-policy/\\"));
try { tryToStartActivity(requireActivity(), i);
startActivity(i);
} catch (ActivityNotFoundException e) {
logException(LOG, WARNING, e);
Toast.makeText(requireContext(),
R.string.error_start_activity, LENGTH_LONG).show();
}
} }
} }

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.android.settings; package org.briarproject.briar.android.settings;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@@ -8,7 +7,6 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.briar.BuildConfig; import org.briarproject.briar.BuildConfig;
import org.briarproject.briar.R; import org.briarproject.briar.R;
@@ -21,10 +19,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import static android.widget.Toast.LENGTH_LONG; import static android.content.Intent.ACTION_VIEW;
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.util.LogUtils.logException; import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -85,16 +82,9 @@ public class AboutFragment extends Fragment {
} }
private void goToUrl(String url) { private void goToUrl(String url) {
Intent i = new Intent(Intent.ACTION_VIEW); Intent i = new Intent(ACTION_VIEW);
i.setData(Uri.parse(url)); i.setData(Uri.parse(url));
try { tryToStartActivity(requireActivity(), i);
startActivity(i);
} catch (ActivityNotFoundException e) {
logException(LOG, WARNING, e);
Toast.makeText(requireContext(),
R.string.error_start_activity, LENGTH_LONG).show();
}
} }
} }

View File

@@ -24,11 +24,14 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceGroup;
import static android.content.Intent.ACTION_SEND;
import static android.content.Intent.EXTRA_TEXT;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.AppModule.getAndroidComponent; import static org.briarproject.briar.android.AppModule.getAndroidComponent;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
import static org.briarproject.briar.android.util.UiUtils.launchActivityToOpenFile; import static org.briarproject.briar.android.util.UiUtils.launchActivityToOpenFile;
import static org.briarproject.briar.android.util.UiUtils.triggerFeedback; import static org.briarproject.briar.android.util.UiUtils.triggerFeedback;
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -37,11 +40,14 @@ public class SettingsFragment extends PreferenceFragmentCompat {
public static final String SETTINGS_NAMESPACE = "android-ui"; public static final String SETTINGS_NAMESPACE = "android-ui";
private static final String PREF_KEY_AVATAR = "pref_key_avatar"; private static final String PREF_KEY_AVATAR = "pref_key_avatar";
private static final String PREF_KEY_SHARE_LINK = "pref_key_share_app_link";
private static final String PREF_KEY_FEEDBACK = "pref_key_send_feedback"; private static final String PREF_KEY_FEEDBACK = "pref_key_send_feedback";
private static final String PREF_KEY_DEV = "pref_key_dev"; private static final String PREF_KEY_DEV = "pref_key_dev";
private static final String PREF_KEY_EXPLODE = "pref_key_explode"; private static final String PREF_KEY_EXPLODE = "pref_key_explode";
private static final String PREF_KEY_MAILBOX = "pref_key_mailbox"; private static final String PREF_KEY_MAILBOX = "pref_key_mailbox";
private static final String DOWNLOAD_URL = "https://briarproject.org/download/";
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
@@ -86,6 +92,17 @@ public class SettingsFragment extends PreferenceFragmentCompat {
return true; return true;
}); });
Preference prefShareLink =
requireNonNull(findPreference(PREF_KEY_SHARE_LINK));
prefShareLink.setOnPreferenceClickListener(preference -> {
String text = getString(R.string.share_app_link_text, DOWNLOAD_URL);
Intent sendIntent = new Intent(ACTION_SEND);
sendIntent.putExtra(EXTRA_TEXT, text);
sendIntent.setType("text/plain");
tryToStartActivity(requireActivity(),
Intent.createChooser(sendIntent, null));
return true;
});
Preference prefFeedback = Preference prefFeedback =
requireNonNull(findPreference(PREF_KEY_FEEDBACK)); requireNonNull(findPreference(PREF_KEY_FEEDBACK));
prefFeedback.setOnPreferenceClickListener(preference -> { prefFeedback.setOnPreferenceClickListener(preference -> {

View File

@@ -1,12 +1,10 @@
package org.briarproject.briar.android.util; package org.briarproject.briar.android.util;
import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.location.LocationManager; import android.location.LocationManager;
import android.net.Uri; import android.net.Uri;
import android.widget.Toast;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.nullsafety.MethodsNotNullByDefault; import org.briarproject.nullsafety.MethodsNotNullByDefault;
@@ -29,10 +27,10 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS; import static android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS;
import static android.widget.Toast.LENGTH_LONG;
import static androidx.core.content.ContextCompat.checkSelfPermission; import static androidx.core.content.ContextCompat.checkSelfPermission;
import static java.lang.Boolean.TRUE; import static java.lang.Boolean.TRUE;
import static org.briarproject.briar.BuildConfig.APPLICATION_ID; import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -47,7 +45,7 @@ public class PermissionUtils {
} }
} }
public static boolean isPermissionGranted(Context ctx, String permission) { private static boolean isPermissionGranted(Context ctx, String permission) {
return checkSelfPermission(ctx, permission) == return checkSelfPermission(ctx, permission) ==
PERMISSION_GRANTED; PERMISSION_GRANTED;
} }
@@ -68,7 +66,7 @@ public class PermissionUtils {
gotPermission(ctx, grantedMap, BLUETOOTH_SCAN); gotPermission(ctx, grantedMap, BLUETOOTH_SCAN);
} }
public static DialogInterface.OnClickListener getGoToSettingsListener( private static DialogInterface.OnClickListener getGoToSettingsListener(
Context context) { Context context) {
return (dialog, which) -> { return (dialog, which) -> {
Intent i = new Intent(); Intent i = new Intent();
@@ -76,7 +74,7 @@ public class PermissionUtils {
i.addCategory(CATEGORY_DEFAULT); i.addCategory(CATEGORY_DEFAULT);
i.setData(Uri.parse("package:" + APPLICATION_ID)); i.setData(Uri.parse("package:" + APPLICATION_ID));
i.addFlags(FLAG_ACTIVITY_NEW_TASK); i.addFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i); tryToStartActivity(context, i);
}; };
} }
@@ -123,12 +121,7 @@ public class PermissionUtils {
builder.setPositiveButton(R.string.permission_location_setting_button, builder.setPositiveButton(R.string.permission_location_setting_button,
(dialog, which) -> { (dialog, which) -> {
Intent i = new Intent(ACTION_LOCATION_SOURCE_SETTINGS); Intent i = new Intent(ACTION_LOCATION_SOURCE_SETTINGS);
try { tryToStartActivity(ctx, i);
ctx.startActivity(i);
} catch (ActivityNotFoundException e) {
Toast.makeText(ctx, R.string.error_start_activity,
LENGTH_LONG).show();
}
}); });
builder.show(); builder.show();
} }

View File

@@ -157,6 +157,15 @@ public class UiUtils {
ta.commit(); ta.commit();
} }
public static void tryToStartActivity(Context ctx, Intent intent) {
try {
ctx.startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(ctx, R.string.error_start_activity, LENGTH_LONG)
.show();
}
}
public static String getContactDisplayName(Author author, public static String getContactDisplayName(Author author,
@Nullable String alias) { @Nullable String alias) {
String name = author.getName(); String name = author.getName();

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/textColorPrimary"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M21,12L14,5V9C7,10 4,15 3,20C5.5,16.5 9,14.9 14,14.9V19L21,12Z" />
</vector>

View File

@@ -13,10 +13,10 @@
android:layout_margin="@dimen/margin_medium" android:layout_margin="@dimen/margin_medium"
android:contentDescription="@string/info" android:contentDescription="@string/info"
android:drawablePadding="@dimen/margin_medium" android:drawablePadding="@dimen/margin_medium"
android:drawableTint="?attr/colorControlNormal"
android:gravity="center_vertical" android:gravity="center_vertical"
app:drawableLeftCompat="@drawable/ic_info_dark" app:drawableLeftCompat="@drawable/ic_info_dark"
app:drawableStartCompat="@drawable/ic_info_dark" app:drawableStartCompat="@drawable/ic_info_dark"
app:drawableTint="?attr/colorControlNormal"
tools:text="Did you know that if you took all the veins out of your body and laid them out end to end, you would die?" /> tools:text="Did you know that if you took all the veins out of your body and laid them out end to end, you would die?" />
</merge> </merge>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

View File

@@ -27,8 +27,6 @@
<string name="dnkm_xiaomi_help">Ако Briar не е заключен в списъка с последно използваните приложения, няма да работи на заден план.</string> <string name="dnkm_xiaomi_help">Ако Briar не е заключен в списъка с последно използваните приложения, няма да работи на заден план.</string>
<string name="dnkm_xiaomi_dialog_body_old">1. Отворете списъка с отворени приложения (списък за превключване на приложения)\n\n2. Плъзнете надолу върху изображението на Briar докато се покаже икона на катинар\n\n3. Ако катинарът е отключен го докоснете, за да го заключите</string> <string name="dnkm_xiaomi_dialog_body_old">1. Отворете списъка с отворени приложения (списък за превключване на приложения)\n\n2. Плъзнете надолу върху изображението на Briar докато се покаже икона на катинар\n\n3. Ако катинарът е отключен го докоснете, за да го заключите</string>
<string name="dnkm_xiaomi_dialog_body_new">1. Отворете списъка с последните приложения\n\n2. Ако до името на Briar има значка на катинарче, не е необходимо да правите нищо\n\n3. Ако няма натиснете и задръжте изображението на Briar, докато се появи бутон за катинарче, след което го докоснете</string> <string name="dnkm_xiaomi_dialog_body_new">1. Отворете списъка с последните приложения\n\n2. Ако до името на Briar има значка на катинарче, не е необходимо да правите нищо\n\n3. Ако няма натиснете и задръжте изображението на Briar, докато се появи бутон за катинарче, след което го докоснете</string>
<string name="dnkm_xiaomi_lock_apps_text">Моля, докоснете бутона по-долу, за да отворите настройките за сигурност. Натиснете бутона \"Ускори\", след това натиснете \"Заключени приложения\" и се уверете, че Briar е \"Заключен\"</string>
<string name="dnkm_xiaomi_lock_apps_help">Ако Briar не е \"Заключен\" в менюто \"Заключени приложения\", няма да може да работи на заден план.</string>
<string name="dnkm_warning_dozed_1">Briar не може да работи във фонов режим</string> <string name="dnkm_warning_dozed_1">Briar не може да работи във фонов режим</string>
<!--Login--> <!--Login-->
<string name="enter_password">Парола</string> <string name="enter_password">Парола</string>
@@ -591,7 +589,7 @@
<!--Mailbox--> <!--Mailbox-->
<string name="mailbox_settings_title">Пощенска кутия</string> <string name="mailbox_settings_title">Пощенска кутия</string>
<string name="mailbox_setup_title">Настройка като пощенска кутия</string> <string name="mailbox_setup_title">Настройка като пощенска кутия</string>
<string name="mailbox_setup_intro">Пощенската кутия ви дава възможност да получавате съобщения от вашите контакти докато сте извън мрежа. Кутията ще получава съобщенията и ще ги пази докато дойдете на линия.\n\nИнсталирайте приложението Birar Mailbox на резервно устройство. Снабдете го със захранване и постоянен достъп до безжична мрежа, така че да е винаги на линия.</string> <string name="mailbox_setup_intro">Пощенската кутия дава възможност да получавате съобщения от вашите контакти докато сте без достъп до мрежа. Кутията ще получава съобщенията и ще ги пази докато дойдете на линия.\n\nИнсталирайте приложението Birar Mailbox на резервно устройство. Снабдете го със захранване и постоянен достъп до безжична мрежа, така че да е винаги на линия.</string>
<string name="mailbox_setup_download">Първо инсталирайте приложението Mailbox на друго устройство като потърсите „Briar Mailbox“ в Google Play или от където сте инсталирали Briar.\n <string name="mailbox_setup_download">Първо инсталирайте приложението Mailbox на друго устройство като потърсите „Briar Mailbox“ в Google Play или от където сте инсталирали Briar.\n
\nСлед това свържете Mailbox с Briar чрез сканиране на кода за QR от приложението Mailbox.</string> \nСлед това свържете Mailbox с Briar чрез сканиране на кода за QR от приложението Mailbox.</string>
<string name="mailbox_setup_download_link">Споделяне на препратка за изтегляне</string> <string name="mailbox_setup_download_link">Споделяне на препратка за изтегляне</string>
@@ -633,7 +631,7 @@
<string name="mailbox_status_unlink_button">Прекъсване на връзката</string> <string name="mailbox_status_unlink_button">Прекъсване на връзката</string>
<string name="mailbox_status_unlink_dialog_title">Желаете ли да прекъснете връзката с пощенската кутия?</string> <string name="mailbox_status_unlink_dialog_title">Желаете ли да прекъснете връзката с пощенската кутия?</string>
<string name="mailbox_status_unlink_dialog_question">Сигурни ли сте, че желаете да прекъснете връзката с пощенската кутия?</string> <string name="mailbox_status_unlink_dialog_question">Сигурни ли сте, че желаете да прекъснете връзката с пощенската кутия?</string>
<string name="mailbox_status_unlink_dialog_warning">Ако прекъснете връзката с пощенската кутия, няма да получавате съобщения докато Briar е без мрежа.</string> <string name="mailbox_status_unlink_dialog_warning">Ако прекъснете връзката с пощенската кутия, няма да получавате съобщения докато Briar е без достъп до мрежа.</string>
<string name="mailbox_status_unlink_no_wipe_title">Връзката с пощенската кутия е прекъсната</string> <string name="mailbox_status_unlink_no_wipe_title">Връзката с пощенската кутия е прекъсната</string>
<string name="mailbox_status_unlink_no_wipe_message">За да завършите процеса, следващия път, когато имате достъп до устройството с пощенската кутия, отворете приложението на пощенската кутия и докоснете бутона „Прекъсване на връзката“.\n\nДаже и вече нямате достъп до устройството с пощенската кутия - не се притеснявайте. Вашата информация е шифрована, така че ще остане защитена, дори и да не завършите този процес.</string> <string name="mailbox_status_unlink_no_wipe_message">За да завършите процеса, следващия път, когато имате достъп до устройството с пощенската кутия, отворете приложението на пощенската кутия и докоснете бутона „Прекъсване на връзката“.\n\nДаже и вече нямате достъп до устройството с пощенската кутия - не се притеснявайте. Вашата информация е шифрована, така че ще остане защитена, дори и да не завършите този процес.</string>
<string name="mailbox_status_unlink_success">Връзката с пощенската кутия е прекъсната</string> <string name="mailbox_status_unlink_success">Връзката с пощенската кутия е прекъсната</string>
@@ -683,6 +681,8 @@
<string name="disappearing_messages_summary">Бъдещите съобщения в разговора изчезват след 7\u00A0дни</string> <string name="disappearing_messages_summary">Бъдещите съобщения в разговора изчезват след 7\u00A0дни</string>
<!--Settings Actions--> <!--Settings Actions-->
<string name="pref_category_actions">Действия</string> <string name="pref_category_actions">Действия</string>
<string name="share_app_link">Споделяне на препратка за изтегляне</string>
<string name="share_app_link_text">Изтеглете Брайар от %s</string>
<string name="send_feedback">Изпращане на обратна връзка</string> <string name="send_feedback">Изпращане на обратна връзка</string>
<!--Link Warning--> <!--Link Warning-->
<string name="link_warning_title">Предупреждение за препратка</string> <string name="link_warning_title">Предупреждение за препратка</string>
@@ -732,6 +732,7 @@
<string name="permission_camera_title">Разрешение за камера</string> <string name="permission_camera_title">Разрешение за камера</string>
<string name="permission_camera_request_body">За да сканира кода за QR, Briar трябва да използва камерата.</string> <string name="permission_camera_request_body">За да сканира кода за QR, Briar трябва да използва камерата.</string>
<string name="permission_location_title">Разрешение за местоположение</string> <string name="permission_location_title">Разрешение за местоположение</string>
<string name="permission_nearby_devices_title">Разрешение за устройства наблизо</string>
<string name="permission_location_request_body">За да открива устройства чрез Bluetooth, Briar се нуждае от разрешение за достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string> <string name="permission_location_request_body">За да открива устройства чрез Bluetooth, Briar се нуждае от разрешение за достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string>
<string name="permission_camera_location_title">Камера и местоположение</string> <string name="permission_camera_location_title">Камера и местоположение</string>
<string name="permission_camera_location_request_body">За да сканира кода за QR, Briar трябва да използва камерата.\n\nЗа да открива устройства чрез Bluetooth, Briar трябва да има достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string> <string name="permission_camera_location_request_body">За да сканира кода за QR, Briar трябва да използва камерата.\n\nЗа да открива устройства чрез Bluetooth, Briar трябва да има достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string>
@@ -758,19 +759,21 @@
<!--Connections Screen--> <!--Connections Screen-->
<string name="transports_help_text">Briar може да се свърже с контактите ви през интернет, Wi-Fi или Bluetooth.\n\nЗа повече поверителност цялата връзка към интернет се пренасочва през мрежата на Tor.\n\nАко даден контакт може да бъде достъпен чрез няколко метода Briar ги използва успоредно.</string> <string name="transports_help_text">Briar може да се свърже с контактите ви през интернет, Wi-Fi или Bluetooth.\n\nЗа повече поверителност цялата връзка към интернет се пренасочва през мрежата на Tor.\n\nАко даден контакт може да бъде достъпен чрез няколко метода Briar ги използва успоредно.</string>
<!--Share app offline--> <!--Share app offline-->
<string name="hotspot_title">Споделяне на приложението извън мрежа</string> <string name="hotspot_title">Споделяне на приложението без достъп до мрежа</string>
<string name="hotspot_intro">Споделете приложението с някого наблизо през Wi-Fi на устройствата, без използване на връзка с интернет. <string name="hotspot_intro">Споделете приложението с някого наблизо през Wi-Fi на устройствата, без използване на връзка с интернет.
\n\nВашето устройство ще създаде безжична точка за достъп. Хората наблизо могат да се свържат към нея и да изтеглят Briar от вашето устройство.</string> \n\nВашето устройство ще създаде безжична точка за достъп. Хората наблизо могат да се свържат към нея и да изтеглят Briar от вашето устройство.</string>
<string name="hotspot_button_start_sharing">Включване на безжична точка</string> <string name="hotspot_button_start_sharing">Включване на безжична точка</string>
<string name="hotspot_button_stop_sharing">Спиране на безжична точка</string> <string name="hotspot_button_stop_sharing">Спиране на безжична точка</string>
<string name="hotspot_progress_text_start">Настройване на безжична точка…</string> <string name="hotspot_progress_text_start">Настройване на безжична точка…</string>
<string name="hotspot_notification_channel_title">Безжична точка за достъп</string> <string name="hotspot_notification_channel_title">Безжична точка за достъп</string>
<string name="hotspot_notification_title">Споделяне на Briar извън мрежа</string> <string name="hotspot_notification_title">Споделяне на Briar без достъп до мрежа</string>
<string name="hotspot_button_connected">Напред</string> <string name="hotspot_button_connected">Напред</string>
<string name="permission_hotspot_location_request_body">За да създаде безжична точка за достъп, Briar се нуждае от разрешение за достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string> <string name="permission_hotspot_location_request_body">За да създаде безжична точка за достъп, Briar се нуждае от разрешение за достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string>
<string name="permission_hotspot_location_request_precise_body">За да създаде безжична точка за достъп, Briar се нуждае от разрешение за достъп до точното местоположение.\n\nBriar не го пази и не го споделя с никого.</string> <string name="permission_hotspot_location_request_precise_body">За да създаде безжична точка за достъп, Briar се нуждае от разрешение за достъп до точното местоположение.\n\nBriar не го пази и не го споделя с никого.</string>
<string name="permission_hotspot_location_denied_body">Отказахте достъп до местоположението, но достъп е необходим за създаване на безжична точка за достъп.\n\nОбмислете дали да не дадете разрешение.</string> <string name="permission_hotspot_location_denied_body">Отказахте достъп до местоположението, но достъп е необходим за създаване на безжична точка за достъп.\n\nОбмислете дали да не дадете разрешение.</string>
<string name="permission_hotspot_location_denied_precise_body">Отказахте достъп до точното местоположение, но достъп е необходим за създаване на безжична точка за достъп.\n\nОбмислете дали да не дадете разрешение.</string> <string name="permission_hotspot_location_denied_precise_body">Отказахте достъп до точното местоположение, но достъп е необходим за създаване на безжична точка за достъп.\n\nОбмислете дали да не дадете разрешение.</string>
<string name="permission_hotspot_nearby_wifi_request_body">За да създаде безжична точка за достъп, Briar се нуждае от права за достъп до околните устройства.</string>
<string name="permission_hotspot_nearby_wifi_denied_body">Отказахте достъп до околните устройства, но той е необходим за създаване на безжична точка за достъп.\n\nОбмислете дали да не дадете разрешение.</string>
<string name="wifi_settings_title">Настройки на Wi-Fi</string> <string name="wifi_settings_title">Настройки на Wi-Fi</string>
<string name="wifi_settings_request_enable_body">За да създаде безжична точка за достъп, Briar се нуждае от безжична мрежа. Включете Wi-Fi.</string> <string name="wifi_settings_request_enable_body">За да създаде безжична точка за достъп, Briar се нуждае от безжична мрежа. Включете Wi-Fi.</string>
<string name="hotspot_tab_manual">Ръчно</string> <string name="hotspot_tab_manual">Ръчно</string>

View File

@@ -51,12 +51,6 @@
<item quantity="many">Toto je testovací verze Briar. Váš účet a jeho platnost vyprší po %d dnech a není možné ho obnovit.</item> <item quantity="many">Toto je testovací verze Briar. Váš účet a jeho platnost vyprší po %d dnech a není možné ho obnovit.</item>
<item quantity="other">Toto je testovací verze Briar. Váš účet a jeho platnost vyprší po %d dnech a není možné ho obnovit.</item> <item quantity="other">Toto je testovací verze Briar. Váš účet a jeho platnost vyprší po %d dnech a není možné ho obnovit.</item>
</plurals> </plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Systém Android 4 již není podporován. Briar přestane fungovat %s (za %d den). Nainstalujte si prosím Briar na novější zařízení a vytvořte si nový účet.</item>
<item quantity="few">Systém Android 4 již není podporován. Briar přestane fungovat %s (za %d dny). Nainstalujte si prosím Briar na novější zařízení a vytvořte si nový účet.</item>
<item quantity="many">Systém Android 4 již není podporován. Briar přestane fungovat %s (za %d dní). Nainstalujte si prosím Briar na novější zařízení a vytvořte si nový účet.</item>
<item quantity="other">Systém Android 4 již není podporován. Briar přestane fungovat %s (za %d dní). Nainstalujte si prosím Briar na novější zařízení a vytvořte si nový účet.</item>
</plurals>
<string name="expiry_date_reached">Platnost tohoto software vypršela.\nDěkujeme za jeho otestování!</string> <string name="expiry_date_reached">Platnost tohoto software vypršela.\nDěkujeme za jeho otestování!</string>
<string name="download_briar">Pro pokračování v používání aplikace Briar si prosím stáhněte její nejnovější verzi.</string> <string name="download_briar">Pro pokračování v používání aplikace Briar si prosím stáhněte její nejnovější verzi.</string>
<string name="create_new_account">Budete potřebovat vytvořit nový účet, ale můžete použít stejné uživatelské jméno.</string> <string name="create_new_account">Budete potřebovat vytvořit nový účet, ale můžete použít stejné uživatelské jméno.</string>
@@ -721,6 +715,8 @@
<string name="disappearing_messages_summary">Nastavte automatické zmizení zpráv této konverzace po 7\u00A0dnech.</string> <string name="disappearing_messages_summary">Nastavte automatické zmizení zpráv této konverzace po 7\u00A0dnech.</string>
<!--Settings Actions--> <!--Settings Actions-->
<string name="pref_category_actions">Akce</string> <string name="pref_category_actions">Akce</string>
<string name="share_app_link">Sdílet odkaz ke stažení</string>
<string name="share_app_link_text">Stáhnout Briar z %s</string>
<string name="send_feedback">Poslat zpětnou vazbu</string> <string name="send_feedback">Poslat zpětnou vazbu</string>
<!--Link Warning--> <!--Link Warning-->
<string name="link_warning_title">Odkaz varování</string> <string name="link_warning_title">Odkaz varování</string>
@@ -770,6 +766,7 @@
<string name="permission_camera_title">Oprávnění pro přístup k fotoaparátu</string> <string name="permission_camera_title">Oprávnění pro přístup k fotoaparátu</string>
<string name="permission_camera_request_body">Pro scan QR kódu, Briar vyžaduje přístup k fotoaparátu.</string> <string name="permission_camera_request_body">Pro scan QR kódu, Briar vyžaduje přístup k fotoaparátu.</string>
<string name="permission_location_title">Místní povolení</string> <string name="permission_location_title">Místní povolení</string>
<string name="permission_nearby_devices_title">Povolení pro zařízení v okolí</string>
<string name="permission_location_request_body">Aby mohl Briar najít zařízení Bluetooth, potřebuje Briar povolení zjistit vaše umístění.\n\nBriar neukládá vaše umístění ani ho s nikým nesdílí.</string> <string name="permission_location_request_body">Aby mohl Briar najít zařízení Bluetooth, potřebuje Briar povolení zjistit vaše umístění.\n\nBriar neukládá vaše umístění ani ho s nikým nesdílí.</string>
<string name="permission_camera_location_title">Fotoaparát a umístění</string> <string name="permission_camera_location_title">Fotoaparát a umístění</string>
<string name="permission_camera_location_request_body">Pro oskenování QR kódu potřebuje Briar přístup k fotoaparátu.\n\nPro nalezení zařízení Bluetooth potřebuje Briar povolení zjistit vaše umístění.\n\nBriar neukládá vaše umístění ani ho s nikým nesdílí.</string> <string name="permission_camera_location_request_body">Pro oskenování QR kódu potřebuje Briar přístup k fotoaparátu.\n\nPro nalezení zařízení Bluetooth potřebuje Briar povolení zjistit vaše umístění.\n\nBriar neukládá vaše umístění ani ho s nikým nesdílí.</string>
@@ -809,6 +806,8 @@
<string name="permission_hotspot_location_request_precise_body">Pro vytvoření Wi-Fi hotspotu potřebuje Briar povolení zjistit vaše přesné umístění.\n\nBriar neukládá vaše umístění ani ho s nikým nesdílí.</string> <string name="permission_hotspot_location_request_precise_body">Pro vytvoření Wi-Fi hotspotu potřebuje Briar povolení zjistit vaše přesné umístění.\n\nBriar neukládá vaše umístění ani ho s nikým nesdílí.</string>
<string name="permission_hotspot_location_denied_body">Zamítli jste přístup k vašemu umístění. Briar přístup potřebuje, aby mohl vytvořit Wi-Fi hotspot.\n\nProsím zvažte povolení přístupu.</string> <string name="permission_hotspot_location_denied_body">Zamítli jste přístup k vašemu umístění. Briar přístup potřebuje, aby mohl vytvořit Wi-Fi hotspot.\n\nProsím zvažte povolení přístupu.</string>
<string name="permission_hotspot_location_denied_precise_body">Zamítli jste přístup k vašemu přesnému umístění. Briar přístup potřebuje, aby mohl vytvořit Wi-Fi hotspot.\n\nProsím zvažte povolení přístupu.</string> <string name="permission_hotspot_location_denied_precise_body">Zamítli jste přístup k vašemu přesnému umístění. Briar přístup potřebuje, aby mohl vytvořit Wi-Fi hotspot.\n\nProsím zvažte povolení přístupu.</string>
<string name="permission_hotspot_nearby_wifi_request_body">Pro vytvoření Wi-Fi hotspotu, potřebuje Briar povolení přístupu k zařízením v okolí.</string>
<string name="permission_hotspot_nearby_wifi_denied_body">Odmítli jste přístup k zařízením v okolí i když Briar potřebuje povolení, aby mohl vytvořit Wi-Fi hotspot.\n\nProsím zvažte udělení přístupu.</string>
<string name="wifi_settings_title">Wi-Fi volba</string> <string name="wifi_settings_title">Wi-Fi volba</string>
<string name="wifi_settings_request_enable_body">Pro vytvoření Wi-Fi hotspotu potřebuje Briar využít Wi-Fi. Prosím zapněte ji.</string> <string name="wifi_settings_request_enable_body">Pro vytvoření Wi-Fi hotspotu potřebuje Briar využít Wi-Fi. Prosím zapněte ji.</string>
<string name="hotspot_tab_manual">Manuálně</string> <string name="hotspot_tab_manual">Manuálně</string>

View File

@@ -689,6 +689,8 @@
<string name="disappearing_messages_summary">Zukünftige Nachrichten in dieser Unterhaltung werden automatisch nach 7\u00A0Tagen gelöscht.</string> <string name="disappearing_messages_summary">Zukünftige Nachrichten in dieser Unterhaltung werden automatisch nach 7\u00A0Tagen gelöscht.</string>
<!--Settings Actions--> <!--Settings Actions-->
<string name="pref_category_actions">Aktionen</string> <string name="pref_category_actions">Aktionen</string>
<string name="share_app_link">Downloadlink teilen</string>
<string name="share_app_link_text">Briar bei %s herunterladen</string>
<string name="send_feedback">Feedback senden</string> <string name="send_feedback">Feedback senden</string>
<!--Link Warning--> <!--Link Warning-->
<string name="link_warning_title">Link-Warnung</string> <string name="link_warning_title">Link-Warnung</string>
@@ -738,6 +740,7 @@
<string name="permission_camera_title">Berechtigung Kamera</string> <string name="permission_camera_title">Berechtigung Kamera</string>
<string name="permission_camera_request_body">Um den QR-Code zu scannen, benötigt Briar Zugriff auf die Kamera.</string> <string name="permission_camera_request_body">Um den QR-Code zu scannen, benötigt Briar Zugriff auf die Kamera.</string>
<string name="permission_location_title">Berechtigung Standort</string> <string name="permission_location_title">Berechtigung Standort</string>
<string name="permission_nearby_devices_title">Berechtigung für Geräte in der Nähe</string>
<string name="permission_location_request_body">Um Bluetooth-Geräte zu finden, braucht Briar Zugriff auf deinen Standort.\n\nBriar speichert weder deinen Standort noch gibt es ihn an andere weiter.</string> <string name="permission_location_request_body">Um Bluetooth-Geräte zu finden, braucht Briar Zugriff auf deinen Standort.\n\nBriar speichert weder deinen Standort noch gibt es ihn an andere weiter.</string>
<string name="permission_camera_location_title">Kamera und Standort</string> <string name="permission_camera_location_title">Kamera und Standort</string>
<string name="permission_camera_location_request_body">Um den QR-Code zu scannen, braucht Briar Zugriff auf die Kamera.\n\nUm Bluetooth-Geräte zu finden, braucht Briar Zugriff auf deinen Standort.\n\nBriar speichert weder deinen Standort noch gibt es ihn an andere weiter.</string> <string name="permission_camera_location_request_body">Um den QR-Code zu scannen, braucht Briar Zugriff auf die Kamera.\n\nUm Bluetooth-Geräte zu finden, braucht Briar Zugriff auf deinen Standort.\n\nBriar speichert weder deinen Standort noch gibt es ihn an andere weiter.</string>
@@ -777,6 +780,8 @@
<string name="permission_hotspot_location_request_precise_body">Um einen WLAN-Hotspot zu erstellen, benötigt Briar die Berechtigung, auf deinen genauen Standort zuzugreifen.\n\nBriar speichert weder deinen Standort noch gibt es ihn an andere weiter.</string> <string name="permission_hotspot_location_request_precise_body">Um einen WLAN-Hotspot zu erstellen, benötigt Briar die Berechtigung, auf deinen genauen Standort zuzugreifen.\n\nBriar speichert weder deinen Standort noch gibt es ihn an andere weiter.</string>
<string name="permission_hotspot_location_denied_body">Du hast den Zugriff auf deinen Standort verweigert, aber Briar benötigt diese Berechtigung, um einen WLAN-Hotspot zu erstellen.\n\nBitte überlege, ob du Zugriff gewährst.</string> <string name="permission_hotspot_location_denied_body">Du hast den Zugriff auf deinen Standort verweigert, aber Briar benötigt diese Berechtigung, um einen WLAN-Hotspot zu erstellen.\n\nBitte überlege, ob du Zugriff gewährst.</string>
<string name="permission_hotspot_location_denied_precise_body">Du hast den Zugriff auf deinen genauen Standort verweigert, aber Briar benötigt diese Berechtigung, um einen WLAN-Hotspot zu erstellen.\n\nBitte überlege, ob du Zugriff gewährst.</string> <string name="permission_hotspot_location_denied_precise_body">Du hast den Zugriff auf deinen genauen Standort verweigert, aber Briar benötigt diese Berechtigung, um einen WLAN-Hotspot zu erstellen.\n\nBitte überlege, ob du Zugriff gewährst.</string>
<string name="permission_hotspot_nearby_wifi_request_body">Um einen WLAN-Hotspot zu erstellen, benötigt Briar die Erlaubnis, auf Geräte in der Nähe zuzugreifen.</string>
<string name="permission_hotspot_nearby_wifi_denied_body">Du hast den Zugriff auf Geräte in der Nähe verweigert, aber Briar benötigt diese Berechtigung, um einen WLAN-Hotspot zu erstellen.\n\nBitte erwäge, den Zugriff zu gewähren.</string>
<string name="wifi_settings_title">WLAN-Einstellungen</string> <string name="wifi_settings_title">WLAN-Einstellungen</string>
<string name="wifi_settings_request_enable_body">Um einen WLAN-Hotspot zu erstellen, benötigt Briar das WLAN. Bitte aktiviere es.</string> <string name="wifi_settings_request_enable_body">Um einen WLAN-Hotspot zu erstellen, benötigt Briar das WLAN. Bitte aktiviere es.</string>
<string name="hotspot_tab_manual">Manuell</string> <string name="hotspot_tab_manual">Manuell</string>

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