Compare commits

..

51 Commits

Author SHA1 Message Date
akwizgran
b38c33bf58 Use WhisperSystems Curve25519 library. 2017-12-07 17:06:58 +00:00
akwizgran
5bb00597ef Add Curve25519 and Ed25519 to performance tests.
Note: Curve25519 is tested using standard ECDH and ECDHC over the Curve25519 curve.
2017-12-07 16:48:02 +00:00
akwizgran
5d528fce74 Merge branch '1112-screen-filter-crash' into 'master'
Don't show screen filter dialog after onSaveInstanceState()

Closes #1112

See merge request !642
2017-12-07 13:06:37 +00:00
Torsten Grote
c80edc99b2 Merge branch '617-protocol-versioning' into 'master'
Protocol versioning

See merge request !646
2017-12-07 12:17:50 +00:00
akwizgran
33378d9920 Merge branch '1088-huawei-whitelisting' into 'master'
Add button for Huawei's power manager to setup wizard

Closes #1088

See merge request !633
2017-12-05 17:22:44 +00:00
akwizgran
85a6e394b9 Merge branch '1127-notification-channels' into 'master'
Use channels for all notifications

Closes #1127

See merge request !643
2017-12-05 16:48:16 +00:00
akwizgran
f2f98f28a3 Include client version in group ID derivation. 2017-12-05 16:07:17 +00:00
akwizgran
d92e042971 Include protocol version in message ID derivation. 2017-12-05 16:07:17 +00:00
akwizgran
6d6e47409f Include protocol version in group ID derivation. 2017-12-05 16:07:17 +00:00
akwizgran
0084e51263 Include protocol version in key derivation. 2017-12-05 16:07:17 +00:00
akwizgran
32e0b39771 Include protocol version in shared secret derivation. 2017-12-05 16:07:17 +00:00
akwizgran
7bb51f77ec Merge branch '545-hyper-sql' into 'master'
Add HyperSQL as an alternative DB library for testing

See merge request !619
2017-12-05 16:05:42 +00:00
akwizgran
c777a57a7d Merge branch '617-crypto-labels' into 'master'
Use namespaced labels for all crypto operations

See merge request !632
2017-12-05 16:04:35 +00:00
akwizgran
def5966767 Sort order of channel IDs affects UI of Settings app. 2017-12-05 15:41:32 +00:00
akwizgran
14b18e9d42 Merge branch '1120-crash-removing-shutdown-hook' into 'master'
Don't remove shutdown hook when closing DB

Closes #1120

See merge request !644
2017-12-05 14:43:36 +00:00
akwizgran
fcff8d92f3 Don't remove shutdown hook when closing DB. 2017-12-05 12:27:41 +00:00
akwizgran
ea0e00f4ac Use channels for all notifications. 2017-12-05 12:09:22 +00:00
Torsten Grote
f199105f6c Add button for Huawei's power manager to setup wizard 2017-12-04 17:26:19 -02:00
akwizgran
b23c0b599b Don't show screen filter dialog after onSaveInstanceState(). 2017-12-04 15:25:12 +00:00
akwizgran
0327d4f38a Merge branch '1007-samsung-transition-npe' into 'master'
Don't set scene transition for Samsung devices running Android 7.0

Closes #1007

See merge request !640
2017-12-04 14:20:28 +00:00
akwizgran
4397a45519 Add links to protocol specs (which are out of date). 2017-12-04 14:16:49 +00:00
Torsten Grote
365e159539 Don't set scene transition for Samsung devices running Android 7.0 2017-12-04 10:51:32 -02:00
akwizgran
8171dd8bc9 Merge branch 'more-lambdas' into 'master'
Replace a few runnables with lambdas

See merge request !638
2017-12-01 17:42:58 +00:00
akwizgran
c4beb60c22 Add dependency hash for HyperSQL. 2017-12-01 17:41:45 +00:00
Torsten Grote
4b88f0d9f1 Merge branch 'package-name-briar-android' into 'master'
Change package name, bump expiry date

See merge request !637
2017-12-01 16:36:47 +00:00
akwizgran
116419f505 Don't show expiry warning for release builds. 2017-12-01 16:18:47 +00:00
akwizgran
87b2624aa8 Set IS_BETA_BUILD to false. 2017-12-01 16:16:37 +00:00
akwizgran
71fe6f3148 Bump expiry date to 31 December 2018. 2017-12-01 16:11:06 +00:00
akwizgran
21df6cb809 Change package name, version number for release branch. 2017-12-01 15:59:04 +00:00
akwizgran
1f0c385a5c Merge branch '1124-notification-channel-crash' into 'master'
Use NotificationChannel for foreground service to avoid crash on Android 8.1

Closes #1124

See merge request !634
2017-12-01 15:53:05 +00:00
Torsten Grote
986ea05fb2 Use NotificationChannel for foreground service to avoid crash on Android 8.1
This also seems to address #1075 at least on an emulator
2017-12-01 13:44:51 -02:00
akwizgran
90e395506f Remove unnecessary DB_CLOSE_ON_EXIT parameter. 2017-12-01 14:13:37 +00:00
akwizgran
cf54360a93 Rename columns whose names are SQL keywords. 2017-12-01 14:13:33 +00:00
akwizgran
a5d4ea4477 Add HSQLDB as an alternative DB library. 2017-12-01 14:13:26 +00:00
akwizgran
030b52261d Replace a few runnables with lambdas. 2017-12-01 14:01:32 +00:00
akwizgran
a50e13c2e3 Merge branch 'transport-property-manager-cleanup' into 'master'
Simplify management of old transport property updates

See merge request !629
2017-11-30 17:46:15 +00:00
akwizgran
c8326103b4 Merge branch 'git-rev-parse-workaround' 2017-11-30 17:39:33 +00:00
akwizgran
0f2beee813 Use namespaced labels for transport key derivation. 2017-11-30 17:36:04 +00:00
akwizgran
d2348a4e7d Remove method that just wraps a MAC call. 2017-11-30 17:08:59 +00:00
akwizgran
cc87e6fd1f Factor out key agreement crypto from CryptoComponent. 2017-11-30 17:08:59 +00:00
akwizgran
1843aea2a7 Factor out transport crypto from CryptoComponent. 2017-11-30 17:08:59 +00:00
akwizgran
9f7021acd3 Include namespaced labels in crypto operations. 2017-11-30 17:08:56 +00:00
Torsten Grote
ddea031cbf Merge branch '1110-signature-labels' into 'master'
Don't use ClientId.toString() for signature labels

Closes #1110

See merge request !631
2017-11-30 17:03:07 +00:00
akwizgran
f0d8532f71 Specify 7 characters for Git revision. 2017-11-30 16:55:41 +00:00
akwizgran
4883d157dc Simplify management of old transport property updates. 2017-11-30 16:43:33 +00:00
akwizgran
a1bec1e927 Merge branch 'ed25519' into 'master'
Add support for Ed25519 signatures

See merge request !627
2017-11-30 16:22:04 +00:00
akwizgran
37d4d79c64 Don't rethrow SignatureException as RuntimeException. 2017-11-29 17:29:32 +00:00
akwizgran
05bc3f6a71 Don't use ClientId.toString() for signature labels. 2017-11-29 16:57:00 +00:00
akwizgran
8b3960781a Fix a typo. 2017-11-23 17:34:40 +00:00
akwizgran
f3de4f53c5 Add ProGuard rule to keep EdDSA classes. 2017-11-23 16:18:30 +00:00
akwizgran
166fc2948c Add support for Ed25519 signatures. 2017-11-23 16:17:41 +00:00
146 changed files with 4780 additions and 5697 deletions

View File

@@ -12,8 +12,8 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 26
versionCode 1617
versionName "0.16.17"
versionCode 1700
versionName "0.17.0"
consumerProguardFiles 'proguard-rules.txt'
}
@@ -43,6 +43,7 @@ dependencyVerification {
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128-runtime.jar:e357a0f1d573c2f702a273992b1b6cb661734f66311854efb3778a888515c5b5',
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128.jar:47b4bec6df11a1118da3953da8b9fa1e7079d6fec857faa1a3cf912e53a6fd4e',
@@ -54,16 +55,16 @@ dependencyVerification {
}
ext.torBinaryDir = 'src/main/res/raw'
ext.torVersion = '0.2.9.14'
ext.geoipVersion = '2017-11-06'
ext.torVersion = '0.2.9.12'
ext.geoipVersion = '2017-09-06'
ext.torDownloadUrl = 'https://briarproject.org/build/'
def torBinaries = [
"tor_arm" : '1710ea6c47b7f4c1a88bdf4858c7893837635db10e8866854eed8d61629f50e8',
"tor_arm_pie": '974e6949507db8fa2ea45231817c2c3677ed4ccf5488a2252317d744b0be1917',
"tor_x86" : '3a5e45b3f051fcda9353b098b7086e762ffe7ba9242f7d7c8bf6523faaa8b1e9',
"tor_x86_pie": 'd1d96d8ce1a4b68accf04850185780d10cd5563d3552f7e1f040f8ca32cb4e51',
"geoip" : '8239b98374493529a29096e45fc5877d4d6fdad0146ad8380b291f90d61484ea'
"tor_arm" : '8ed0b347ffed1d6a4d2fd14495118eb92be83e9cc06e057e15220dc288b31688',
"tor_arm_pie": '64403262511c29f462ca5e7c7621bfc3c944898364d1d5ad35a016bb8a034283',
"tor_x86" : '61e014607a2079bcf1646289c67bff6372b1aded6e1d8d83d7791efda9a4d5ab',
"tor_x86_pie": '18fbc98356697dd0895836ab46d5c9877d1c539193464f7db1e82a65adaaf288',
"geoip" : 'fe49d3adb86d3c512373101422a017dbb86c85a570524663f09dd8ce143a24f3'
]
def downloadBinary(name) {

View File

@@ -8,6 +8,8 @@
-dontwarn dagger.**
-dontnote dagger.**
-keep class net.i2p.crypto.eddsa.** { *; }
-dontwarn sun.misc.Unsafe
-dontnote com.google.common.**

View File

@@ -13,7 +13,6 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.reporting.DevReporter;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.Scheduler;
import org.briarproject.bramble.plugin.droidtooth.DroidtoothPluginFactory;
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
@@ -23,7 +22,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.net.SocketFactory;
@@ -35,7 +33,6 @@ public class AndroidPluginModule {
@Provides
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
@Scheduler ScheduledExecutorService scheduler,
AndroidExecutor androidExecutor, SecureRandom random,
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
Application app, LocationUtils locationUtils, DevReporter reporter,
@@ -43,9 +40,9 @@ public class AndroidPluginModule {
Context appContext = app.getApplicationContext();
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor,
androidExecutor, appContext, random, eventBus, backoffFactory);
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
appContext, locationUtils, reporter, eventBus,
torSocketFactory, backoffFactory);
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext,
locationUtils, reporter, eventBus, torSocketFactory,
backoffFactory);
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
backoffFactory, appContext);
Collection<DuplexPluginFactory> duplex =

View File

@@ -59,12 +59,7 @@ import java.util.Map.Entry;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.zip.ZipInputStream;
@@ -75,15 +70,10 @@ import javax.net.SocketFactory;
import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.MODE_PRIVATE;
import static android.content.Context.POWER_SERVICE;
import static android.content.Intent.ACTION_SCREEN_OFF;
import static android.content.Intent.ACTION_SCREEN_ON;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
@@ -112,7 +102,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
Logger.getLogger(TorPlugin.class.getName());
private final Executor ioExecutor;
private final ScheduledExecutorService scheduler;
private final Context appContext;
private final LocationUtils locationUtils;
private final DevReporter reporter;
@@ -125,9 +114,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final File torDirectory, torFile, geoIpFile, configFile;
private final File doneFile, cookieFile;
private final PowerManager.WakeLock wakeLock;
private final Lock connectionStatusLock;
private final AtomicReference<Future<?>> connectivityCheck =
new AtomicReference<>();
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false;
@@ -136,13 +122,12 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private volatile TorControlConnection controlConnection = null;
private volatile BroadcastReceiver networkStateReceiver = null;
TorPlugin(Executor ioExecutor, ScheduledExecutorService scheduler,
Context appContext, LocationUtils locationUtils,
DevReporter reporter, SocketFactory torSocketFactory,
Backoff backoff, DuplexPluginCallback callback,
String architecture, int maxLatency, int maxIdleTime) {
TorPlugin(Executor ioExecutor, Context appContext,
LocationUtils locationUtils, DevReporter reporter,
SocketFactory torSocketFactory, Backoff backoff,
DuplexPluginCallback callback, String architecture, int maxLatency,
int maxIdleTime) {
this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.appContext = appContext;
this.locationUtils = locationUtils;
this.reporter = reporter;
@@ -167,7 +152,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// This tag will prevent Huawei's powermanager from killing us.
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
wakeLock.setReferenceCounted(false);
connectionStatusLock = new ReentrantLock();
}
@Override
@@ -220,11 +204,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (LOG.isLoggable(INFO)) {
Scanner stdout = new Scanner(torProcess.getInputStream());
Scanner stderr = new Scanner(torProcess.getErrorStream());
while (stdout.hasNextLine() || stderr.hasNextLine()) {
if (stdout.hasNextLine()) {
while (stdout.hasNextLine() || stderr.hasNextLine()){
if(stdout.hasNextLine()) {
LOG.info(stdout.nextLine());
}
if (stderr.hasNextLine()) {
if(stderr.hasNextLine()){
LOG.info(stderr.nextLine());
}
}
@@ -273,11 +257,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
// Register to receive network status events
networkStateReceiver = new NetworkStateReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(CONNECTIVITY_ACTION);
filter.addAction(ACTION_SCREEN_ON);
filter.addAction(ACTION_SCREEN_OFF);
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION);
appContext.registerReceiver(networkStateReceiver, filter);
// Bind a server socket to receive incoming hidden service connections
bind();
@@ -638,8 +618,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void orConnStatus(String status, String orName) {
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
if (status.equals("CLOSED") || status.equals("FAILED"))
updateConnectionStatus(); // Check whether we've lost connectivity
}
@Override
@@ -679,7 +657,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
@Override
public void onEvent(int event, @Nullable String path) {
public void onEvent(int event, String path) {
stopWatching();
latch.countDown();
}
@@ -699,73 +677,53 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void updateConnectionStatus() {
ioExecutor.execute(() -> {
if (!running) return;
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
ConnectivityManager cm = (ConnectivityManager) o;
NetworkInfo net = cm.getActiveNetworkInfo();
boolean online = net != null && net.isConnected();
boolean wifi = online && net.getType() == TYPE_WIFI;
String country = locationUtils.getCurrentCountry();
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
country);
Settings s = callback.getSettings();
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
if (LOG.isLoggable(INFO)) {
LOG.info("Online: " + online + ", wifi: " + wifi);
if ("".equals(country)) LOG.info("Country code unknown");
else LOG.info("Country code: " + country);
}
try {
connectionStatusLock.lock();
updateConnectionStatusLocked();
} finally {
connectionStatusLock.unlock();
if (!online) {
LOG.info("Disabling network, device is offline");
enableNetwork(false);
} else if (blocked) {
LOG.info("Disabling network, country is blocked");
enableNetwork(false);
} else if (network == PREF_TOR_NETWORK_NEVER
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
LOG.info("Disabling network due to data setting");
enableNetwork(false);
} else {
LOG.info("Enabling network");
enableNetwork(true);
}
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
});
}
// Locking: connectionStatusLock
private void updateConnectionStatusLocked() {
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
ConnectivityManager cm = (ConnectivityManager) o;
NetworkInfo net = cm.getActiveNetworkInfo();
boolean online = net != null && net.isConnected();
boolean wifi = online && net.getType() == TYPE_WIFI;
String country = locationUtils.getCurrentCountry();
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
country);
Settings s = callback.getSettings();
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
if (LOG.isLoggable(INFO)) {
LOG.info("Online: " + online + ", wifi: " + wifi);
if ("".equals(country)) LOG.info("Country code unknown");
else LOG.info("Country code: " + country);
}
try {
if (!online) {
LOG.info("Disabling network, device is offline");
enableNetwork(false);
} else if (blocked) {
LOG.info("Disabling network, country is blocked");
enableNetwork(false);
} else if (network == PREF_TOR_NETWORK_NEVER
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
LOG.info("Disabling network due to data setting");
enableNetwork(false);
} else {
LOG.info("Enabling network");
enableNetwork(true);
}
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void scheduleConnectionStatusUpdate() {
Future<?> newConnectivityCheck =
scheduler.schedule(this::updateConnectionStatus, 1, MINUTES);
Future<?> oldConnectivityCheck =
connectivityCheck.getAndSet(newConnectivityCheck);
if (oldConnectivityCheck != null) oldConnectivityCheck.cancel(false);
}
private class NetworkStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context ctx, Intent i) {
if (!running) return;
String action = i.getAction();
if (LOG.isLoggable(INFO)) LOG.info("Received broadcast " + action);
updateConnectionStatus();
if (ACTION_SCREEN_ON.equals(action)
|| ACTION_SCREEN_OFF.equals(action)) {
scheduleConnectionStatusUpdate();
if (CONNECTIVITY_ACTION.equals(i.getAction())) {
LOG.info("Detected connectivity change");
updateConnectionStatus();
}
}
}

View File

@@ -17,7 +17,6 @@ import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.util.AndroidUtils;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
@@ -37,7 +36,6 @@ public class TorPluginFactory implements DuplexPluginFactory {
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor;
private final ScheduledExecutorService scheduler;
private final Context appContext;
private final LocationUtils locationUtils;
private final DevReporter reporter;
@@ -45,13 +43,11 @@ public class TorPluginFactory implements DuplexPluginFactory {
private final SocketFactory torSocketFactory;
private final BackoffFactory backoffFactory;
public TorPluginFactory(Executor ioExecutor,
ScheduledExecutorService scheduler, Context appContext,
public TorPluginFactory(Executor ioExecutor, Context appContext,
LocationUtils locationUtils, DevReporter reporter,
EventBus eventBus, SocketFactory torSocketFactory,
BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.appContext = appContext;
this.locationUtils = locationUtils;
this.reporter = reporter;
@@ -93,9 +89,9 @@ public class TorPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorPlugin plugin = new TorPlugin(ioExecutor, scheduler, appContext,
locationUtils, reporter, torSocketFactory, backoff, callback,
architecture, MAX_LATENCY, MAX_IDLE_TIME);
TorPlugin plugin = new TorPlugin(ioExecutor, appContext, locationUtils,
reporter, torSocketFactory, backoff, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME);
eventBus.addListener(plugin);
return plugin;
}

View File

@@ -12,18 +12,19 @@ public interface ContactGroupFactory {
/**
* Creates a group that is not shared with any contacts.
*/
Group createLocalGroup(ClientId clientId);
Group createLocalGroup(ClientId clientId, int clientVersion);
/**
* Creates a group for the given client to share with the given contact.
*/
Group createContactGroup(ClientId clientId, Contact contact);
Group createContactGroup(ClientId clientId, int clientVersion,
Contact contact);
/**
* Creates a group for the given client to share between the given authors
* identified by their AuthorIds.
*/
Group createContactGroup(ClientId clientId, AuthorId authorId1,
AuthorId authorId2);
Group createContactGroup(ClientId clientId, int clientVersion,
AuthorId authorId1, AuthorId authorId2);
}

View File

@@ -12,6 +12,32 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
@NotNullByDefault
public interface ContactExchangeTask {
/**
* The current version of the contact exchange protocol
*/
int PROTOCOL_VERSION = 0;
/**
* Label for deriving Alice's header key from the master secret.
*/
String ALICE_KEY_LABEL =
"org.briarproject.bramble.contact/ALICE_HEADER_KEY";
/**
* Label for deriving Bob's header key from the master secret.
*/
String BOB_KEY_LABEL = "org.briarproject.bramble.contact/BOB_HEADER_KEY";
/**
* Label for deriving Alice's key binding nonce from the master secret.
*/
String ALICE_NONCE_LABEL = "org.briarproject.bramble.contact/ALICE_NONCE";
/**
* Label for deriving Bob's key binding nonce from the master secret.
*/
String BOB_NONCE_LABEL = "org.briarproject.bramble.contact/BOB_NONCE";
/**
* Exchanges contact information with a remote peer.
*/

View File

@@ -1,8 +1,5 @@
package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.TransportKeys;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
@@ -20,156 +17,98 @@ public interface CryptoComponent {
KeyParser getSignatureKeyParser();
KeyPair generateEdKeyPair();
KeyParser getEdKeyParser();
KeyParser getMessageKeyParser();
/**
* Derives a stream header key from the given master secret.
* @param alice whether the key is for use by Alice or Bob.
*/
SecretKey deriveHeaderKey(SecretKey master, boolean alice);
/**
* Derives a message authentication code key from the given master secret.
* @param alice whether the key is for use by Alice or Bob.
*/
SecretKey deriveMacKey(SecretKey master, boolean alice);
/**
* Derives a nonce from the given master secret for one of the parties to
* sign.
* @param alice whether the nonce is for use by Alice or Bob.
*/
byte[] deriveSignatureNonce(SecretKey master, boolean alice);
/**
* Derives a commitment to the provided public key.
* <p/>
* Part of BQP.
* Derives another secret key from the given secret key.
*
* @param publicKey the public key
* @return the commitment to the provided public key.
* @param label a namespaced label indicating the purpose of the derived
* key, to prevent it from being repurposed or colliding with a key derived
* for another purpose
*/
byte[] deriveKeyCommitment(byte[] publicKey);
SecretKey deriveKey(String label, SecretKey k, byte[]... inputs);
/**
* Derives a common shared secret from two public keys and one of the
* corresponding private keys.
* <p/>
* Part of BQP.
*
* @param theirPublicKey the ephemeral public key of the remote party
* @param ourKeyPair our ephemeral keypair
* @param alice true if ourKeyPair belongs to Alice
* @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 theirPublicKey the public key of the remote party
* @param ourKeyPair the key pair of the local party
* @return the shared secret
* @throws GeneralSecurityException
*/
SecretKey deriveSharedSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
boolean alice) throws GeneralSecurityException;
SecretKey deriveSharedSecret(String label, PublicKey theirPublicKey,
KeyPair ourKeyPair, byte[]... inputs)
throws GeneralSecurityException;
/**
* Derives the content of a confirmation record.
* <p/>
* Part of BQP.
* Signs the given byte[] with the given ECDSA private key.
*
* @param sharedSecret the common shared secret
* @param theirPayload the commit payload from the remote party
* @param ourPayload the commit payload we sent
* @param theirPublicKey the ephemeral public key of the remote party
* @param ourKeyPair our ephemeral keypair
* @param alice true if ourKeyPair belongs to Alice
* @param aliceRecord true if the confirmation record is for use by Alice
* @return the confirmation record
*/
byte[] deriveConfirmationRecord(SecretKey sharedSecret,
byte[] theirPayload, byte[] ourPayload,
byte[] theirPublicKey, KeyPair ourKeyPair,
boolean alice, boolean aliceRecord);
/**
* Derives a master secret from the given shared secret.
* <p/>
* Part of BQP.
*
* @param sharedSecret the common shared secret
* @return the master secret
*/
SecretKey deriveMasterSecret(SecretKey sharedSecret);
/**
* Derives a master secret from two public keys and one of the corresponding
* private keys.
* <p/>
* This is a helper method that calls
* deriveMasterSecret(deriveSharedSecret(theirPublicKey, ourKeyPair, alice))
*
* @param theirPublicKey the ephemeral public key of the remote party
* @param ourKeyPair our ephemeral keypair
* @param alice true if ourKeyPair belongs to Alice
* @return the shared secret
* @throws GeneralSecurityException
*/
SecretKey deriveMasterSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
boolean alice) throws GeneralSecurityException;
/**
* Derives initial transport keys for the given transport in the given
* rotation period from the given master secret.
* @param alice whether the keys are for use by Alice or Bob.
*/
TransportKeys deriveTransportKeys(TransportId t, SecretKey master,
long rotationPeriod, boolean alice);
/**
* Rotates the given transport keys to the given rotation period. If the
* keys are for a future rotation period they are not rotated.
*/
TransportKeys rotateTransportKeys(TransportKeys k, long rotationPeriod);
/** Encodes the pseudo-random tag that is used to recognise a stream. */
void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
long streamNumber);
/**
* Signs the given byte[] with the given PrivateKey.
*
* @param label A label specific to this signature
* to ensure that the signature cannot be repurposed
* @param label a namespaced label indicating the purpose of this
* signature, to prevent it from being repurposed or colliding with a
* signature created for another purpose
*/
byte[] sign(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException;
/**
* Verifies that the given signature is valid for the signedData
* and the given publicKey.
* Signs the given byte[] with the given Ed25519 private key.
*
* @param label A label that was specific to this signature
* @param label A label specific to this signature
* to ensure that the signature cannot be repurposed
*/
byte[] signEd(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException;
/**
* Verifies that the given signature is valid for the signed data
* and the given ECDSA public key.
*
* @param label a namespaced label indicating the purpose of this
* signature, to prevent it from being repurposed or colliding with a
* signature created for another purpose
* @return true if the signature was valid, false otherwise.
*/
boolean verify(String label, byte[] signedData, byte[] publicKey,
byte[] signature) throws GeneralSecurityException;
/**
* Verifies that the given signature is valid for the signed data
* and the given Ed25519 public key.
*
* @param label A label that was specific to this signature
* to ensure that the signature cannot be repurposed
* @return true if the signature was valid, false otherwise.
*/
boolean verifyEd(String label, byte[] signedData, byte[] publicKey,
byte[] signature) throws GeneralSecurityException;
/**
* Returns the hash of the given inputs. The inputs are unambiguously
* combined by prefixing each input with its length.
*
* @param label A label specific to this hash to ensure that hashes
* calculated for distinct purposes don't collide.
* @param label a namespaced label indicating the purpose of this hash, to
* prevent it from being repurposed or colliding with a hash created for
* another purpose
*/
byte[] hash(String label, byte[]... inputs);
/**
* Returns the length of hashes produced by
* the {@link CryptoComponent#hash(String, byte[]...)} method.
*/
int getHashLength();
/**
* Returns a message authentication code with the given key over the
* given inputs. The inputs are unambiguously combined by prefixing each
* input with its length.
*
* @param label a namespaced label indicating the purpose of this MAC, to
* prevent it from being repurposed or colliding with a MAC created for
* another purpose
*/
byte[] mac(SecretKey macKey, byte[]... inputs);
byte[] mac(String label, SecretKey macKey, byte[]... inputs);
/**
* Encrypts and authenticates the given plaintext so it can be written to

View File

@@ -0,0 +1,50 @@
package org.briarproject.bramble.api.crypto;
/**
* Crypto operations for the key agreement protocol - see
* https://code.briarproject.org/akwizgran/briar-spec/blob/master/protocols/BQP.md
*/
public interface KeyAgreementCrypto {
/**
* Hash label for public key commitment.
*/
String COMMIT_LABEL = "org.briarproject.bramble.keyagreement/COMMIT";
/**
* Key derivation label for confirmation record.
*/
String CONFIRMATION_KEY_LABEL =
"org.briarproject.bramble.keyagreement/CONFIRMATION_KEY";
/**
* MAC label for confirmation record.
*/
String CONFIRMATION_MAC_LABEL =
"org.briarproject.bramble.keyagreement/CONFIRMATION_MAC";
/**
* Derives a commitment to the provided public key.
*
* @param publicKey the public key
* @return the commitment to the provided public key.
*/
byte[] deriveKeyCommitment(PublicKey publicKey);
/**
* Derives the content of a confirmation record.
*
* @param sharedSecret the common shared secret
* @param theirPayload the key exchange payload of the remote party
* @param ourPayload the key exchange payload of the local party
* @param theirPublicKey the ephemeral public key of the remote party
* @param ourKeyPair our ephemeral key pair of the local party
* @param alice true if the local party is Alice
* @param aliceRecord true if the confirmation record is for use by Alice
* @return the confirmation record
*/
byte[] deriveConfirmationRecord(SecretKey sharedSecret,
byte[] theirPayload, byte[] ourPayload,
PublicKey theirPublicKey, KeyPair ourKeyPair,
boolean alice, boolean aliceRecord);
}

View File

@@ -0,0 +1,32 @@
package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.TransportKeys;
/**
* Crypto operations for the transport security protocol - see
* https://code.briarproject.org/akwizgran/briar-spec/blob/master/protocols/BTP.md
*/
public interface TransportCrypto {
/**
* Derives initial transport keys for the given transport in the given
* rotation period from the given master secret.
*
* @param alice whether the keys are for use by Alice or Bob.
*/
TransportKeys deriveTransportKeys(TransportId t, SecretKey master,
long rotationPeriod, boolean alice);
/**
* Rotates the given transport keys to the given rotation period. If the
* keys are for the given period or any later period they are not rotated.
*/
TransportKeys rotateTransportKeys(TransportKeys k, long rotationPeriod);
/**
* Encodes the pseudo-random tag that is used to recognise a stream.
*/
void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
long streamNumber);
}

View File

@@ -16,7 +16,7 @@ public class AuthorId extends UniqueId {
/**
* Label for hashing authors to calculate their identities.
*/
public static final String LABEL = "org.briarproject.bramble.AUTHOR_ID";
public static final String LABEL = "org.briarproject.bramble/AUTHOR_ID";
public AuthorId(byte[] id) {
super(id);

View File

@@ -5,7 +5,7 @@ public interface KeyAgreementConstants {
/**
* The current version of the BQP protocol.
*/
byte PROTOCOL_VERSION = 2;
byte PROTOCOL_VERSION = 3;
/**
* The length of the record header in bytes.
@@ -22,7 +22,10 @@ public interface KeyAgreementConstants {
*/
int COMMIT_LENGTH = 16;
long CONNECTION_TIMEOUT = 20 * 1000; // Milliseconds
/**
* The connection timeout in milliseconds.
*/
long CONNECTION_TIMEOUT = 20 * 1000;
/**
* The transport identifier for Bluetooth.
@@ -33,4 +36,16 @@ public interface KeyAgreementConstants {
* The transport identifier for LAN.
*/
int TRANSPORT_ID_LAN = 1;
/**
* Label for deriving the shared secret.
*/
String SHARED_SECRET_LABEL =
"org.briarproject.bramble.keyagreement/SHARED_SECRET";
/**
* Label for deriving the master secret.
*/
String MASTER_SECRET_LABEL =
"org.briarproject.bramble.keyagreement/MASTER_SECRET";
}

View File

@@ -1,15 +0,0 @@
package org.briarproject.bramble.api.keyagreement;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* Manages tasks for conducting key agreements with remote peers.
*/
@NotNullByDefault
public interface KeyAgreementTaskFactory {
/**
* Gets the current key agreement task.
*/
KeyAgreementTask createTask();
}

View File

@@ -17,6 +17,11 @@ public interface TransportPropertyManager {
*/
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.properties");
/**
* The current version of the transport property client.
*/
int CLIENT_VERSION = 0;
/**
* Stores the given properties received while adding a contact - they will
* be superseded by any properties synced from the contact.

View File

@@ -36,4 +36,8 @@ public class ClientId implements Comparable<ClientId> {
return id.hashCode();
}
@Override
public String toString() {
return id;
}
}

View File

@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
public interface GroupFactory {
/**
* Creates a group with the given client ID and descriptor.
* Creates a group with the given client ID, client version and descriptor.
*/
Group createGroup(ClientId c, byte[] descriptor);
Group createGroup(ClientId c, int clientVersion, byte[] descriptor);
}

View File

@@ -15,7 +15,7 @@ public class GroupId extends UniqueId {
/**
* Label for hashing groups to calculate their identifiers.
*/
public static final String LABEL = "org.briarproject.bramble.GROUP_ID";
public static final String LABEL = "org.briarproject.bramble/GROUP_ID";
public GroupId(byte[] id) {
super(id);

View File

@@ -16,7 +16,7 @@ public class MessageId extends UniqueId {
/**
* Label for hashing messages to calculate their identifiers.
*/
public static final String LABEL = "org.briarproject.bramble.MESSAGE_ID";
public static final String LABEL = "org.briarproject.bramble/MESSAGE_ID";
public MessageId(byte[] id) {
super(id);

View File

@@ -7,7 +7,7 @@ public interface TransportConstants {
/**
* The current version of the transport protocol.
*/
int PROTOCOL_VERSION = 3;
int PROTOCOL_VERSION = 4;
/**
* The length of the pseudo-random tag in bytes.
@@ -80,4 +80,32 @@ public interface TransportConstants {
* The size of the reordering window.
*/
int REORDERING_WINDOW_SIZE = 32;
/**
* Label for deriving Alice's initial tag key from the master secret.
*/
String ALICE_TAG_LABEL = "org.briarproject.bramble.transport/ALICE_TAG_KEY";
/**
* Label for deriving Bob's initial tag key from the master secret.
*/
String BOB_TAG_LABEL = "org.briarproject.bramble.transport/BOB_TAG_KEY";
/**
* Label for deriving Alice's initial header key from the master secret.
*/
String ALICE_HEADER_LABEL =
"org.briarproject.bramble.transport/ALICE_HEADER_KEY";
/**
* Label for deriving Bob's initial header key from the master secret.
*/
String BOB_HEADER_LABEL =
"org.briarproject.bramble.transport/BOB_HEADER_KEY";
/**
* Label for deriving the next period's key in key rotation.
*/
String ROTATE_LABEL = "org.briarproject.bramble.transport/ROTATE";
}

View File

@@ -2,27 +2,12 @@ package org.briarproject.bramble.test;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.util.IoUtils;
import java.io.File;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
public class TestUtils {
private static final AtomicInteger nextTestDir =
@@ -53,50 +38,4 @@ public class TestUtils {
return new SecretKey(getRandomBytes(SecretKey.LENGTH));
}
public static LocalAuthor getLocalAuthor() {
return getLocalAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
}
public static LocalAuthor getLocalAuthor(int nameLength) {
AuthorId id = new AuthorId(getRandomId());
String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
long created = System.currentTimeMillis();
return new LocalAuthor(id, name, publicKey, privateKey, created);
}
public static Author getAuthor() {
return getAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
}
public static Author getAuthor(int nameLength) {
AuthorId id = new AuthorId(getRandomId());
String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
return new Author(id, name, publicKey);
}
public static Group getGroup(ClientId clientId) {
int descriptorLength = 1 + random.nextInt(MAX_GROUP_DESCRIPTOR_LENGTH);
return getGroup(clientId, descriptorLength);
}
public static Group getGroup(ClientId clientId, int descriptorLength) {
GroupId groupId = new GroupId(getRandomId());
byte[] descriptor = getRandomBytes(descriptorLength);
return new Group(groupId, clientId, descriptor);
}
public static Message getMessage(GroupId groupId) {
int bodyLength = 1 + random.nextInt(MAX_MESSAGE_BODY_LENGTH);
return getMessage(groupId, MESSAGE_HEADER_LENGTH + bodyLength);
}
public static Message getMessage(GroupId groupId, int rawLength) {
MessageId id = new MessageId(getRandomId());
byte[] raw = getRandomBytes(rawLength);
long timestamp = System.currentTimeMillis();
return new Message(id, groupId, timestamp, raw);
}
}

View File

@@ -9,18 +9,21 @@ apply plugin: 'witness'
dependencies {
implementation project(path: ':bramble-api', configuration: 'default')
implementation 'com.madgag.spongycastle:core:1.58.0.0'
implementation 'com.h2database:h2:1.4.192' // This is the last version that supports Java 1.6
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
implementation 'org.bitlet:weupnp:0.1.4'
implementation 'net.i2p.crypto:eddsa:0.2.0'
apt 'com.google.dagger:dagger-compiler:2.0.2'
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6
testImplementation 'junit:junit:4.12'
testImplementation "org.jmock:jmock:2.8.2"
testImplementation "org.jmock:jmock-junit4:2.8.2"
testImplementation "org.jmock:jmock-legacy:2.8.2"
testImplementation "org.hamcrest:hamcrest-library:1.3"
testImplementation "org.hamcrest:hamcrest-core:1.3"
testImplementation "org.whispersystems:curve25519-java:0.4.1"
testApt 'com.google.dagger:dagger-compiler:2.0.2'
}
@@ -37,18 +40,21 @@ dependencyVerification {
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.hsqldb:hsqldb:2.3.5:hsqldb-2.3.5.jar:6676a6977ac98997a80f827ddbd3fe8ca1e0853dad1492512135fd1a222ccfad',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
'org.whispersystems:curve25519-java:0.4.1:curve25519-java-0.4.1.jar:7dd659d8822c06c3aea1a47f18fac9e5761e29cab8100030b877db445005f03e',
]
}

View File

@@ -32,23 +32,25 @@ class ContactGroupFactoryImpl implements ContactGroupFactory {
}
@Override
public Group createLocalGroup(ClientId clientId) {
return groupFactory.createGroup(clientId, LOCAL_GROUP_DESCRIPTOR);
public Group createLocalGroup(ClientId clientId, int clientVersion) {
return groupFactory.createGroup(clientId, clientVersion,
LOCAL_GROUP_DESCRIPTOR);
}
@Override
public Group createContactGroup(ClientId clientId, Contact contact) {
public Group createContactGroup(ClientId clientId, int clientVersion,
Contact contact) {
AuthorId local = contact.getLocalAuthorId();
AuthorId remote = contact.getAuthor().getId();
byte[] descriptor = createGroupDescriptor(local, remote);
return groupFactory.createGroup(clientId, descriptor);
return groupFactory.createGroup(clientId, clientVersion, descriptor);
}
@Override
public Group createContactGroup(ClientId clientId, AuthorId authorId1,
AuthorId authorId2) {
public Group createContactGroup(ClientId clientId, int clientVersion,
AuthorId authorId1, AuthorId authorId2) {
byte[] descriptor = createGroupDescriptor(authorId1, authorId2);
return groupFactory.createGroup(clientId, descriptor);
return groupFactory.createGroup(clientId, clientVersion, descriptor);
}
private byte[] createGroupDescriptor(AuthorId local, AuthorId remote) {

View File

@@ -141,8 +141,10 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
}
// Derive the header keys for the transport streams
SecretKey aliceHeaderKey = crypto.deriveHeaderKey(masterSecret, true);
SecretKey bobHeaderKey = crypto.deriveHeaderKey(masterSecret, false);
SecretKey aliceHeaderKey = crypto.deriveKey(ALICE_KEY_LABEL,
masterSecret, new byte[] {PROTOCOL_VERSION});
SecretKey bobHeaderKey = crypto.deriveKey(BOB_KEY_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION});
// Create the readers
InputStream streamReader =
@@ -156,8 +158,10 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
BdfWriter w = bdfWriterFactory.createWriter(streamWriter);
// Derive the nonces to be signed
byte[] aliceNonce = crypto.deriveSignatureNonce(masterSecret, true);
byte[] bobNonce = crypto.deriveSignatureNonce(masterSecret, false);
byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION});
byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION});
// Exchange pseudonyms, signed nonces, and timestamps
long localTimestamp = clock.currentTimeMillis();
@@ -196,8 +200,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
try {
// Add the contact
ContactId contactId = addContact(remoteAuthor, masterSecret,
timestamp, alice, remoteProperties);
ContactId contactId = addContact(remoteAuthor, timestamp,
remoteProperties);
// Reuse the connection as a transport connection
connectionManager.manageOutgoingConnection(contactId, transportId,
conn);
@@ -294,15 +298,15 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
return remote;
}
private ContactId addContact(Author remoteAuthor, SecretKey master,
long timestamp, boolean alice,
private ContactId addContact(Author remoteAuthor, long timestamp,
Map<TransportId, TransportProperties> remoteProperties)
throws DbException {
ContactId contactId;
Transaction txn = db.startTransaction(false);
try {
contactId = contactManager.addContact(txn, remoteAuthor,
localAuthor.getId(), master, timestamp, alice, true, true);
localAuthor.getId(), masterSecret, timestamp, alice,
true, true);
transportPropertyManager.addRemoteProperties(txn, contactId,
remoteProperties);
db.commitTransaction(txn);
@@ -312,8 +316,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
return contactId;
}
private void tryToClose(DuplexTransportConnection conn,
boolean exception) {
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
try {
LOG.info("Closing connection");
conn.getReader().dispose(exception, true);

View File

@@ -1,16 +1,16 @@
package org.briarproject.bramble.crypto;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.KeyPairGenerator;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.bramble.util.StringUtils;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
@@ -26,7 +26,6 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.params.KeyParameter;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
@@ -40,14 +39,8 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
class CryptoComponentImpl implements CryptoComponent {
@@ -56,49 +49,19 @@ class CryptoComponentImpl implements CryptoComponent {
private static final int AGREEMENT_KEY_PAIR_BITS = 256;
private static final int SIGNATURE_KEY_PAIR_BITS = 256;
private static final int ED_KEY_PAIR_BITS = 256;
private static final int STORAGE_IV_BYTES = 24; // 196 bits
private static final int PBKDF_SALT_BYTES = 32; // 256 bits
private static final int PBKDF_TARGET_MILLIS = 500;
private static final int PBKDF_SAMPLES = 30;
private static final int HASH_SIZE = 256 / 8;
private static byte[] ascii(String s) {
return s.getBytes(Charset.forName("US-ASCII"));
}
// KDF labels for contact exchange stream header key derivation
private static final byte[] A_INVITE = ascii("ALICE_INVITATION_KEY");
private static final byte[] B_INVITE = ascii("BOB_INVITATION_KEY");
// KDF labels for contact exchange signature nonce derivation
private static final byte[] A_SIG_NONCE = ascii("ALICE_SIGNATURE_NONCE");
private static final byte[] B_SIG_NONCE = ascii("BOB_SIGNATURE_NONCE");
// Hash label for BQP public key commitment derivation
private static final String COMMIT =
"org.briarproject.bramble.COMMIT";
// Hash label for shared secret derivation
private static final String SHARED_SECRET =
"org.briarproject.bramble.SHARED_SECRET";
// KDF label for BQP confirmation key derivation
private static final byte[] CONFIRMATION_KEY = ascii("CONFIRMATION_KEY");
// KDF label for master key derivation
private static final byte[] MASTER_KEY = ascii("MASTER_KEY");
// KDF labels for tag key derivation
private static final byte[] A_TAG = ascii("ALICE_TAG_KEY");
private static final byte[] B_TAG = ascii("BOB_TAG_KEY");
// KDF labels for header key derivation
private static final byte[] A_HEADER = ascii("ALICE_HEADER_KEY");
private static final byte[] B_HEADER = ascii("BOB_HEADER_KEY");
// KDF labels for MAC key derivation
private static final byte[] A_MAC = ascii("ALICE_MAC_KEY");
private static final byte[] B_MAC = ascii("BOB_MAC_KEY");
// KDF label for key rotation
private static final byte[] ROTATE = ascii("ROTATE");
private final SecureRandom secureRandom;
private final ECKeyPairGenerator agreementKeyPairGenerator;
private final ECKeyPairGenerator signatureKeyPairGenerator;
private final KeyParser agreementKeyParser, signatureKeyParser;
private final MessageEncrypter messageEncrypter;
private final KeyPairGenerator edKeyPairGenerator;
private final KeyParser edKeyParser;
@Inject
CryptoComponentImpl(SecureRandomProvider secureRandomProvider) {
@@ -132,6 +95,9 @@ class CryptoComponentImpl implements CryptoComponent {
signatureKeyParser = new Sec1KeyParser(PARAMETERS,
SIGNATURE_KEY_PAIR_BITS);
messageEncrypter = new MessageEncrypter(secureRandom);
edKeyPairGenerator = new KeyPairGenerator();
edKeyPairGenerator.initialize(ED_KEY_PAIR_BITS, secureRandom);
edKeyParser = new EdKeyParser();
}
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
@@ -190,6 +156,21 @@ class CryptoComponentImpl implements CryptoComponent {
return secret;
}
@Override
public KeyPair generateEdKeyPair() {
java.security.KeyPair keyPair = edKeyPairGenerator.generateKeyPair();
EdDSAPublicKey edPublicKey = (EdDSAPublicKey) keyPair.getPublic();
PublicKey publicKey = new EdPublicKey(edPublicKey.getAbyte());
EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) keyPair.getPrivate();
PrivateKey privateKey = new EdPrivateKey(edPrivateKey.getSeed());
return new KeyPair(publicKey, privateKey);
}
@Override
public KeyParser getEdKeyParser() {
return edKeyParser;
}
@Override
public KeyPair generateAgreementKeyPair() {
AsymmetricCipherKeyPair keyPair =
@@ -238,197 +219,63 @@ class CryptoComponentImpl implements CryptoComponent {
}
@Override
public SecretKey deriveHeaderKey(SecretKey master,
boolean alice) {
return new SecretKey(macKdf(master, alice ? A_INVITE : B_INVITE));
public SecretKey deriveKey(String label, SecretKey k, byte[]... inputs) {
byte[] mac = mac(label, k, inputs);
if (mac.length != SecretKey.LENGTH) throw new IllegalStateException();
return new SecretKey(mac);
}
@Override
public SecretKey deriveMacKey(SecretKey master, boolean alice) {
return new SecretKey(macKdf(master, alice ? A_MAC : B_MAC));
}
@Override
public byte[] deriveSignatureNonce(SecretKey master,
boolean alice) {
return macKdf(master, alice ? A_SIG_NONCE : B_SIG_NONCE);
}
@Override
public byte[] deriveKeyCommitment(byte[] publicKey) {
byte[] hash = hash(COMMIT, publicKey);
// The output is the first COMMIT_LENGTH bytes of the hash
byte[] commitment = new byte[COMMIT_LENGTH];
System.arraycopy(hash, 0, commitment, 0, COMMIT_LENGTH);
return commitment;
}
@Override
public SecretKey deriveSharedSecret(byte[] theirPublicKey,
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
public SecretKey deriveSharedSecret(String label, PublicKey theirPublicKey,
KeyPair ourKeyPair, byte[]... inputs)
throws GeneralSecurityException {
PrivateKey ourPriv = ourKeyPair.getPrivate();
PublicKey theirPub = agreementKeyParser.parsePublicKey(theirPublicKey);
byte[] raw = performRawKeyAgreement(ourPriv, theirPub);
byte[] alicePub, bobPub;
if (alice) {
alicePub = ourKeyPair.getPublic().getEncoded();
bobPub = theirPublicKey;
} else {
alicePub = theirPublicKey;
bobPub = ourKeyPair.getPublic().getEncoded();
}
return new SecretKey(hash(SHARED_SECRET, raw, alicePub, bobPub));
}
@Override
public byte[] deriveConfirmationRecord(SecretKey sharedSecret,
byte[] theirPayload, byte[] ourPayload, byte[] theirPublicKey,
KeyPair ourKeyPair, boolean alice, boolean aliceRecord) {
SecretKey ck = new SecretKey(macKdf(sharedSecret, CONFIRMATION_KEY));
byte[] alicePayload, alicePub, bobPayload, bobPub;
if (alice) {
alicePayload = ourPayload;
alicePub = ourKeyPair.getPublic().getEncoded();
bobPayload = theirPayload;
bobPub = theirPublicKey;
} else {
alicePayload = theirPayload;
alicePub = theirPublicKey;
bobPayload = ourPayload;
bobPub = ourKeyPair.getPublic().getEncoded();
}
if (aliceRecord)
return macKdf(ck, alicePayload, alicePub, bobPayload, bobPub);
else
return macKdf(ck, bobPayload, bobPub, alicePayload, alicePub);
}
@Override
public SecretKey deriveMasterSecret(SecretKey sharedSecret) {
return new SecretKey(macKdf(sharedSecret, MASTER_KEY));
}
@Override
public SecretKey deriveMasterSecret(byte[] theirPublicKey,
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
return deriveMasterSecret(deriveSharedSecret(
theirPublicKey, ourKeyPair, alice));
}
@Override
public TransportKeys deriveTransportKeys(TransportId t,
SecretKey master, long rotationPeriod, boolean alice) {
// Keys for the previous period are derived from the master secret
SecretKey inTagPrev = deriveTagKey(master, t, !alice);
SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice);
SecretKey outTagPrev = deriveTagKey(master, t, alice);
SecretKey outHeaderPrev = deriveHeaderKey(master, t, alice);
// Derive the keys for the current and next periods
SecretKey inTagCurr = rotateKey(inTagPrev, rotationPeriod);
SecretKey inHeaderCurr = rotateKey(inHeaderPrev, rotationPeriod);
SecretKey inTagNext = rotateKey(inTagCurr, rotationPeriod + 1);
SecretKey inHeaderNext = rotateKey(inHeaderCurr, rotationPeriod + 1);
SecretKey outTagCurr = rotateKey(outTagPrev, rotationPeriod);
SecretKey outHeaderCurr = rotateKey(outHeaderPrev, rotationPeriod);
// Initialise the reordering windows and stream counters
IncomingKeys inPrev = new IncomingKeys(inTagPrev, inHeaderPrev,
rotationPeriod - 1);
IncomingKeys inCurr = new IncomingKeys(inTagCurr, inHeaderCurr,
rotationPeriod);
IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext,
rotationPeriod + 1);
OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr,
rotationPeriod);
// Collect and return the keys
return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
}
@Override
public TransportKeys rotateTransportKeys(TransportKeys k,
long rotationPeriod) {
if (k.getRotationPeriod() >= rotationPeriod) return k;
IncomingKeys inPrev = k.getPreviousIncomingKeys();
IncomingKeys inCurr = k.getCurrentIncomingKeys();
IncomingKeys inNext = k.getNextIncomingKeys();
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
long startPeriod = outCurr.getRotationPeriod();
// Rotate the keys
for (long p = startPeriod + 1; p <= rotationPeriod; p++) {
inPrev = inCurr;
inCurr = inNext;
SecretKey inNextTag = rotateKey(inNext.getTagKey(), p + 1);
SecretKey inNextHeader = rotateKey(inNext.getHeaderKey(), p + 1);
inNext = new IncomingKeys(inNextTag, inNextHeader, p + 1);
SecretKey outCurrTag = rotateKey(outCurr.getTagKey(), p);
SecretKey outCurrHeader = rotateKey(outCurr.getHeaderKey(), p);
outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p);
}
// Collect and return the keys
return new TransportKeys(k.getTransportId(), inPrev, inCurr, inNext,
outCurr);
}
private SecretKey rotateKey(SecretKey k, long rotationPeriod) {
byte[] period = new byte[INT_64_BYTES];
ByteUtils.writeUint64(rotationPeriod, period, 0);
return new SecretKey(macKdf(k, ROTATE, period));
}
private SecretKey deriveTagKey(SecretKey master, TransportId t,
boolean alice) {
byte[] id = StringUtils.toUtf8(t.getString());
return new SecretKey(macKdf(master, alice ? A_TAG : B_TAG, id));
}
private SecretKey deriveHeaderKey(SecretKey master, TransportId t,
boolean alice) {
byte[] id = StringUtils.toUtf8(t.getString());
return new SecretKey(macKdf(master, alice ? A_HEADER : B_HEADER, id));
}
@Override
public void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
long streamNumber) {
if (tag.length < TAG_LENGTH) throw new IllegalArgumentException();
if (protocolVersion < 0 || protocolVersion > MAX_16_BIT_UNSIGNED)
throw new IllegalArgumentException();
if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
// Initialise the PRF
Digest prf = new Blake2sDigest(tagKey.getBytes());
// The output of the PRF must be long enough to use as a tag
int macLength = prf.getDigestSize();
if (macLength < TAG_LENGTH) throw new IllegalStateException();
// The input is the protocol version as a 16-bit integer, followed by
// the stream number as a 64-bit integer
byte[] protocolVersionBytes = new byte[INT_16_BYTES];
ByteUtils.writeUint16(protocolVersion, protocolVersionBytes, 0);
prf.update(protocolVersionBytes, 0, protocolVersionBytes.length);
byte[] streamNumberBytes = new byte[INT_64_BYTES];
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
prf.update(streamNumberBytes, 0, streamNumberBytes.length);
byte[] mac = new byte[macLength];
prf.doFinal(mac, 0);
// The output is the first TAG_LENGTH bytes of the MAC
System.arraycopy(mac, 0, tag, 0, TAG_LENGTH);
byte[][] hashInputs = new byte[inputs.length + 1][];
hashInputs[0] = performRawKeyAgreement(ourPriv, theirPublicKey);
System.arraycopy(inputs, 0, hashInputs, 1, inputs.length);
byte[] hash = hash(label, hashInputs);
if (hash.length != SecretKey.LENGTH) throw new IllegalStateException();
return new SecretKey(hash);
}
@Override
public byte[] sign(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException {
Signature signature = new SignatureImpl(secureRandom);
KeyParser keyParser = getSignatureKeyParser();
return sign(new SignatureImpl(secureRandom), signatureKeyParser, label,
toSign, privateKey);
}
@Override
public byte[] signEd(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException {
return sign(new EdSignature(), edKeyParser, label, toSign, privateKey);
}
private byte[] sign(Signature sig, KeyParser keyParser, String label,
byte[] toSign, byte[] privateKey) throws GeneralSecurityException {
PrivateKey key = keyParser.parsePrivateKey(privateKey);
signature.initSign(key);
updateSignature(signature, label, toSign);
return signature.sign();
sig.initSign(key);
updateSignature(sig, label, toSign);
return sig.sign();
}
@Override
public boolean verify(String label, byte[] signedData, byte[] publicKey,
byte[] signature) throws GeneralSecurityException {
Signature sig = new SignatureImpl(secureRandom);
KeyParser keyParser = getSignatureKeyParser();
return verify(new SignatureImpl(secureRandom), signatureKeyParser,
label, signedData, publicKey, signature);
}
@Override
public boolean verifyEd(String label, byte[] signedData, byte[] publicKey,
byte[] signature) throws GeneralSecurityException {
return verify(new EdSignature(), edKeyParser, label, signedData,
publicKey, signature);
}
private boolean verify(Signature sig, KeyParser keyParser, String label,
byte[] signedData, byte[] publicKey, byte[] signature)
throws GeneralSecurityException {
PublicKey key = keyParser.parsePublicKey(publicKey);
sig.initVerify(key);
updateSignature(sig, label, signedData);
@@ -436,7 +283,7 @@ class CryptoComponentImpl implements CryptoComponent {
}
private void updateSignature(Signature signature, String label,
byte[] toSign) {
byte[] toSign) throws GeneralSecurityException {
byte[] labelBytes = StringUtils.toUtf8(label);
byte[] length = new byte[INT_32_BYTES];
ByteUtils.writeUint32(labelBytes.length, length, 0);
@@ -466,14 +313,13 @@ class CryptoComponentImpl implements CryptoComponent {
}
@Override
public int getHashLength() {
return HASH_SIZE;
}
@Override
public byte[] mac(SecretKey macKey, byte[]... inputs) {
public byte[] mac(String label, SecretKey macKey, byte[]... inputs) {
byte[] labelBytes = StringUtils.toUtf8(label);
Digest mac = new Blake2sDigest(macKey.getBytes());
byte[] length = new byte[INT_32_BYTES];
ByteUtils.writeUint32(labelBytes.length, length, 0);
mac.update(length, 0, length.length);
mac.update(labelBytes, 0, labelBytes.length);
for (byte[] input : inputs) {
ByteUtils.writeUint32(input.length, length, 0);
mac.update(length, 0, length.length);
@@ -565,30 +411,6 @@ class CryptoComponentImpl implements CryptoComponent {
return AsciiArmour.wrap(b, lineLength);
}
// Key derivation function based on a pseudo-random function - see
// NIST SP 800-108, section 5.1
private byte[] macKdf(SecretKey key, byte[]... inputs) {
// Initialise the PRF
Digest prf = new Blake2sDigest(key.getBytes());
// The output of the PRF must be long enough to use as a key
int macLength = prf.getDigestSize();
if (macLength < SecretKey.LENGTH) throw new IllegalStateException();
// Calculate the PRF over the concatenated length-prefixed inputs
byte[] length = new byte[INT_32_BYTES];
for (byte[] input : inputs) {
ByteUtils.writeUint32(input.length, length, 0);
prf.update(length, 0, length.length);
prf.update(input, 0, input.length);
}
byte[] mac = new byte[macLength];
prf.doFinal(mac, 0);
// The output is the first SecretKey.LENGTH bytes of the MAC
if (mac.length == SecretKey.LENGTH) return mac;
byte[] truncated = new byte[SecretKey.LENGTH];
System.arraycopy(mac, 0, truncated, 0, truncated.length);
return truncated;
}
// Password-based key derivation function - see PKCS#5 v2.1, section 5.2
private byte[] pbkdf2(String password, byte[] salt, int iterations) {
byte[] utf8 = StringUtils.toUtf8(password);

View File

@@ -3,9 +3,11 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.TimeLoggingExecutor;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.SecureRandomProvider;
@@ -74,6 +76,12 @@ public class CryptoModule {
return new PasswordStrengthEstimatorImpl();
}
@Provides
TransportCrypto provideTransportCrypto(
TransportCryptoImpl transportCrypto) {
return transportCrypto;
}
@Provides
StreamDecrypterFactory provideStreamDecrypterFactory(
Provider<AuthenticatedCipher> cipherProvider) {
@@ -81,9 +89,17 @@ public class CryptoModule {
}
@Provides
StreamEncrypterFactory provideStreamEncrypterFactory(CryptoComponent crypto,
StreamEncrypterFactory provideStreamEncrypterFactory(
CryptoComponent crypto, TransportCrypto transportCrypto,
Provider<AuthenticatedCipher> cipherProvider) {
return new StreamEncrypterFactoryImpl(crypto, cipherProvider);
return new StreamEncrypterFactoryImpl(crypto, transportCrypto,
cipherProvider);
}
@Provides
KeyAgreementCrypto provideKeyAgreementCrypto(
KeyAgreementCryptoImpl keyAgreementCrypto) {
return keyAgreementCrypto;
}
@Provides

View File

@@ -0,0 +1,26 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException;
@NotNullByDefault
class EdKeyParser implements KeyParser {
@Override
public PublicKey parsePublicKey(byte[] encodedKey)
throws GeneralSecurityException {
if (encodedKey.length != 32) throw new GeneralSecurityException();
return new EdPublicKey(encodedKey);
}
@Override
public PrivateKey parsePrivateKey(byte[] encodedKey)
throws GeneralSecurityException {
if (encodedKey.length != 32) throw new GeneralSecurityException();
return new EdPrivateKey(encodedKey);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,83 @@
package org.briarproject.bramble.crypto;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.EdDSASecurityProvider;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import static net.i2p.crypto.eddsa.EdDSAEngine.SIGNATURE_ALGORITHM;
@NotNullByDefault
class EdSignature implements Signature {
private static final Provider PROVIDER = new EdDSASecurityProvider();
private static final EdDSANamedCurveSpec CURVE_SPEC =
EdDSANamedCurveTable.getByName("Ed25519");
private final java.security.Signature signature;
EdSignature() {
try {
signature = java.security.Signature
.getInstance(SIGNATURE_ALGORITHM, PROVIDER);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
@Override
public void initSign(PrivateKey k) throws GeneralSecurityException {
if (!(k instanceof EdPrivateKey))
throw new IllegalArgumentException();
EdDSAPrivateKey privateKey = new EdDSAPrivateKey(
new EdDSAPrivateKeySpec(k.getEncoded(), CURVE_SPEC));
signature.initSign(privateKey);
}
@Override
public void initVerify(PublicKey k) throws GeneralSecurityException {
if (!(k instanceof EdPublicKey))
throw new IllegalArgumentException();
EdDSAPublicKey publicKey = new EdDSAPublicKey(
new EdDSAPublicKeySpec(k.getEncoded(), CURVE_SPEC));
signature.initVerify(publicKey);
}
@Override
public void update(byte b) throws GeneralSecurityException {
signature.update(b);
}
@Override
public void update(byte[] b) throws GeneralSecurityException {
signature.update(b);
}
@Override
public void update(byte[] b, int off, int len)
throws GeneralSecurityException {
signature.update(b, off, len);
}
@Override
public byte[] sign() throws GeneralSecurityException {
return signature.sign();
}
@Override
public boolean verify(byte[] sig) throws GeneralSecurityException {
return signature.verify(sig);
}
}

View File

@@ -0,0 +1,56 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import javax.inject.Inject;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
class KeyAgreementCryptoImpl implements KeyAgreementCrypto {
private final CryptoComponent crypto;
@Inject
KeyAgreementCryptoImpl(CryptoComponent crypto) {
this.crypto = crypto;
}
@Override
public byte[] deriveKeyCommitment(PublicKey publicKey) {
byte[] hash = crypto.hash(COMMIT_LABEL, publicKey.getEncoded());
// The output is the first COMMIT_LENGTH bytes of the hash
byte[] commitment = new byte[COMMIT_LENGTH];
System.arraycopy(hash, 0, commitment, 0, COMMIT_LENGTH);
return commitment;
}
@Override
public byte[] deriveConfirmationRecord(SecretKey sharedSecret,
byte[] theirPayload, byte[] ourPayload, PublicKey theirPublicKey,
KeyPair ourKeyPair, boolean alice, boolean aliceRecord) {
SecretKey ck = crypto.deriveKey(CONFIRMATION_KEY_LABEL, sharedSecret);
byte[] alicePayload, alicePub, bobPayload, bobPub;
if (alice) {
alicePayload = ourPayload;
alicePub = ourKeyPair.getPublic().getEncoded();
bobPayload = theirPayload;
bobPub = theirPublicKey.getEncoded();
} else {
alicePayload = theirPayload;
alicePub = theirPublicKey.getEncoded();
bobPayload = ourPayload;
bobPub = ourKeyPair.getPublic().getEncoded();
}
if (aliceRecord) {
return crypto.mac(CONFIRMATION_MAC_LABEL, ck, alicePayload,
alicePub, bobPayload, bobPub);
} else {
return crypto.mac(CONFIRMATION_MAC_LABEL, ck, bobPayload, bobPub,
alicePayload, alicePub);
}
}
}

View File

@@ -22,25 +22,25 @@ interface Signature {
/**
* @see {@link java.security.Signature#update(byte)}
*/
void update(byte b);
void update(byte b) throws GeneralSecurityException;
/**
* @see {@link java.security.Signature#update(byte[])}
*/
void update(byte[] b);
void update(byte[] b) throws GeneralSecurityException;
/**
* @see {@link java.security.Signature#update(byte[], int, int)}
*/
void update(byte[] b, int off, int len);
void update(byte[] b, int off, int len) throws GeneralSecurityException;
/**
* @see {@link java.security.Signature#sign()}
*/
byte[] sign();
byte[] sign() throws GeneralSecurityException;
/**
* @see {@link java.security.Signature#verify(byte[])}
*/
boolean verify(byte[] signature);
boolean verify(byte[] signature) throws GeneralSecurityException;
}

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.StreamEncrypter;
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.transport.StreamContext;
@@ -22,12 +23,15 @@ import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENG
class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
private final CryptoComponent crypto;
private final TransportCrypto transportCrypto;
private final Provider<AuthenticatedCipher> cipherProvider;
@Inject
StreamEncrypterFactoryImpl(CryptoComponent crypto,
TransportCrypto transportCrypto,
Provider<AuthenticatedCipher> cipherProvider) {
this.crypto = crypto;
this.transportCrypto = transportCrypto;
this.cipherProvider = cipherProvider;
}
@@ -37,7 +41,8 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
AuthenticatedCipher cipher = cipherProvider.get();
long streamNumber = ctx.getStreamNumber();
byte[] tag = new byte[TAG_LENGTH];
crypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION, streamNumber);
transportCrypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION,
streamNumber);
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
crypto.getSecureRandom().nextBytes(streamHeaderNonce);
SecretKey frameKey = crypto.generateSecretKey();

View File

@@ -0,0 +1,135 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.bramble.util.StringUtils;
import org.spongycastle.crypto.Digest;
import javax.inject.Inject;
import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_HEADER_LABEL;
import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_TAG_LABEL;
import static org.briarproject.bramble.api.transport.TransportConstants.BOB_HEADER_LABEL;
import static org.briarproject.bramble.api.transport.TransportConstants.BOB_TAG_LABEL;
import static org.briarproject.bramble.api.transport.TransportConstants.ROTATE_LABEL;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
class TransportCryptoImpl implements TransportCrypto {
private final CryptoComponent crypto;
@Inject
TransportCryptoImpl(CryptoComponent crypto) {
this.crypto = crypto;
}
@Override
public TransportKeys deriveTransportKeys(TransportId t,
SecretKey master, long rotationPeriod, boolean alice) {
// Keys for the previous period are derived from the master secret
SecretKey inTagPrev = deriveTagKey(master, t, !alice);
SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice);
SecretKey outTagPrev = deriveTagKey(master, t, alice);
SecretKey outHeaderPrev = deriveHeaderKey(master, t, alice);
// Derive the keys for the current and next periods
SecretKey inTagCurr = rotateKey(inTagPrev, rotationPeriod);
SecretKey inHeaderCurr = rotateKey(inHeaderPrev, rotationPeriod);
SecretKey inTagNext = rotateKey(inTagCurr, rotationPeriod + 1);
SecretKey inHeaderNext = rotateKey(inHeaderCurr, rotationPeriod + 1);
SecretKey outTagCurr = rotateKey(outTagPrev, rotationPeriod);
SecretKey outHeaderCurr = rotateKey(outHeaderPrev, rotationPeriod);
// Initialise the reordering windows and stream counters
IncomingKeys inPrev = new IncomingKeys(inTagPrev, inHeaderPrev,
rotationPeriod - 1);
IncomingKeys inCurr = new IncomingKeys(inTagCurr, inHeaderCurr,
rotationPeriod);
IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext,
rotationPeriod + 1);
OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr,
rotationPeriod);
// Collect and return the keys
return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
}
@Override
public TransportKeys rotateTransportKeys(TransportKeys k,
long rotationPeriod) {
if (k.getRotationPeriod() >= rotationPeriod) return k;
IncomingKeys inPrev = k.getPreviousIncomingKeys();
IncomingKeys inCurr = k.getCurrentIncomingKeys();
IncomingKeys inNext = k.getNextIncomingKeys();
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
long startPeriod = outCurr.getRotationPeriod();
// Rotate the keys
for (long p = startPeriod + 1; p <= rotationPeriod; p++) {
inPrev = inCurr;
inCurr = inNext;
SecretKey inNextTag = rotateKey(inNext.getTagKey(), p + 1);
SecretKey inNextHeader = rotateKey(inNext.getHeaderKey(), p + 1);
inNext = new IncomingKeys(inNextTag, inNextHeader, p + 1);
SecretKey outCurrTag = rotateKey(outCurr.getTagKey(), p);
SecretKey outCurrHeader = rotateKey(outCurr.getHeaderKey(), p);
outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p);
}
// Collect and return the keys
return new TransportKeys(k.getTransportId(), inPrev, inCurr, inNext,
outCurr);
}
private SecretKey rotateKey(SecretKey k, long rotationPeriod) {
byte[] period = new byte[INT_64_BYTES];
ByteUtils.writeUint64(rotationPeriod, period, 0);
return crypto.deriveKey(ROTATE_LABEL, k, period);
}
private SecretKey deriveTagKey(SecretKey master, TransportId t,
boolean alice) {
String label = alice ? ALICE_TAG_LABEL : BOB_TAG_LABEL;
byte[] id = StringUtils.toUtf8(t.getString());
return crypto.deriveKey(label, master, id);
}
private SecretKey deriveHeaderKey(SecretKey master, TransportId t,
boolean alice) {
String label = alice ? ALICE_HEADER_LABEL : BOB_HEADER_LABEL;
byte[] id = StringUtils.toUtf8(t.getString());
return crypto.deriveKey(label, master, id);
}
@Override
public void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
long streamNumber) {
if (tag.length < TAG_LENGTH) throw new IllegalArgumentException();
if (protocolVersion < 0 || protocolVersion > MAX_16_BIT_UNSIGNED)
throw new IllegalArgumentException();
if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
// Initialise the PRF
Digest prf = new Blake2sDigest(tagKey.getBytes());
// The output of the PRF must be long enough to use as a tag
int macLength = prf.getDigestSize();
if (macLength < TAG_LENGTH) throw new IllegalStateException();
// The input is the protocol version as a 16-bit integer, followed by
// the stream number as a 64-bit integer
byte[] protocolVersionBytes = new byte[INT_16_BYTES];
ByteUtils.writeUint16(protocolVersion, protocolVersionBytes, 0);
prf.update(protocolVersionBytes, 0, protocolVersionBytes.length);
byte[] streamNumberBytes = new byte[INT_64_BYTES];
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
prf.update(streamNumberBytes, 0, streamNumberBytes.length);
byte[] mac = new byte[macLength];
prf.doFinal(mac, 0);
// The output is the first TAG_LENGTH bytes of the MAC
System.arraycopy(mac, 0, tag, 0, TAG_LENGTH);
}
}

View File

@@ -22,21 +22,23 @@ import javax.inject.Inject;
class H2Database extends JdbcDatabase {
private static final String HASH_TYPE = "BINARY(32)";
private static final String SECRET_TYPE = "BINARY(32)";
private static final String BINARY_TYPE = "BINARY";
private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT";
private static final String SECRET_TYPE = "BINARY(32)";
private static final String STRING_TYPE = "VARCHAR";
private final DatabaseConfig config;
private final String url;
@Inject
H2Database(DatabaseConfig config, Clock clock) {
super(HASH_TYPE, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE, clock);
super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
clock);
this.config = config;
File dir = config.getDatabaseDirectory();
String path = new File(dir, "db").getAbsolutePath();
url = "jdbc:h2:split:" + path + ";CIPHER=AES;MULTI_THREADED=1"
+ ";WRITE_DELAY=0;DB_CLOSE_ON_EXIT=false";
+ ";WRITE_DELAY=0";
}
@Override

View File

@@ -0,0 +1,99 @@
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.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.StringUtils;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import javax.inject.Inject;
/**
* Contains all the HSQLDB-specific code for the database.
*/
@NotNullByDefault
class HyperSqlDatabase extends JdbcDatabase {
private static final String HASH_TYPE = "BINARY(32)";
private static final String SECRET_TYPE = "BINARY(32)";
private static final String BINARY_TYPE = "BINARY";
private static final String COUNTER_TYPE =
"INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY(START WITH 1)";
private static final String STRING_TYPE = "VARCHAR";
private final DatabaseConfig config;
private final String url;
@Inject
HyperSqlDatabase(DatabaseConfig config, Clock clock) {
super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
clock);
this.config = config;
File dir = config.getDatabaseDirectory();
String path = new File(dir, "db").getAbsolutePath();
url = "jdbc:hsqldb:file:" + path
+ ";sql.enforce_size=false;allow_empty_batch=true"
+ ";encrypt_lobs=true;crypt_type=AES";
}
@Override
public boolean open() throws DbException {
boolean reopen = config.databaseExists();
if (!reopen) config.getDatabaseDirectory().mkdirs();
super.open("org.hsqldb.jdbc.JDBCDriver", reopen);
return reopen;
}
@Override
public void close() throws DbException {
try {
super.closeAllConnections();
Connection c = createConnection();
Statement s = c.createStatement();
s.executeQuery("SHUTDOWN");
s.close();
c.close();
} catch (SQLException e) {
throw new DbException(e);
}
}
@Override
public long getFreeSpace() throws DbException {
File dir = config.getDatabaseDirectory();
long maxSize = config.getMaxSize();
long free = dir.getFreeSpace();
long used = getDiskSpace(dir);
long quota = maxSize - used;
return Math.min(free, quota);
}
private long getDiskSpace(File f) {
if (f.isDirectory()) {
long total = 0;
File[] children = f.listFiles();
if (children != null)
for (File child : children) total += getDiskSpace(child);
return total;
} else if (f.isFile()) {
return f.length();
} else {
return 0;
}
}
@Override
protected Connection createConnection() throws SQLException {
SecretKey key = config.getEncryptionKey();
if (key == null) throw new IllegalStateException();
String hex = StringUtils.toHexString(key.getBytes());
return DriverManager.getConnection(url + ";crypt_key=" + hex);
}
}

View File

@@ -68,32 +68,32 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
@NotNullByDefault
abstract class JdbcDatabase implements Database<Connection> {
private static final int SCHEMA_VERSION = 30;
private static final int MIN_SCHEMA_VERSION = 30;
private static final int SCHEMA_VERSION = 31;
private static final int MIN_SCHEMA_VERSION = 31;
private static final String CREATE_SETTINGS =
"CREATE TABLE settings"
+ " (namespace VARCHAR NOT NULL,"
+ " key VARCHAR NOT NULL,"
+ " value VARCHAR NOT NULL,"
+ " PRIMARY KEY (namespace, key))";
+ " (namespace _STRING NOT NULL,"
+ " settingKey _STRING NOT NULL,"
+ " value _STRING NOT NULL,"
+ " PRIMARY KEY (namespace, settingKey))";
private static final String CREATE_LOCAL_AUTHORS =
"CREATE TABLE localAuthors"
+ " (authorId HASH NOT NULL,"
+ " name VARCHAR NOT NULL,"
+ " publicKey BINARY NOT NULL,"
+ " privateKey BINARY NOT NULL,"
+ " (authorId _HASH NOT NULL,"
+ " name _STRING NOT NULL,"
+ " publicKey _BINARY NOT NULL,"
+ " privateKey _BINARY NOT NULL,"
+ " created BIGINT NOT NULL,"
+ " PRIMARY KEY (authorId))";
private static final String CREATE_CONTACTS =
"CREATE TABLE contacts"
+ " (contactId COUNTER,"
+ " authorId HASH NOT NULL,"
+ " name VARCHAR NOT NULL,"
+ " publicKey BINARY NOT NULL,"
+ " localAuthorId HASH NOT NULL,"
+ " (contactId _COUNTER,"
+ " authorId _HASH NOT NULL,"
+ " name _STRING NOT NULL,"
+ " publicKey _BINARY NOT NULL,"
+ " localAuthorId _HASH NOT NULL,"
+ " verified BOOLEAN NOT NULL,"
+ " active BOOLEAN NOT NULL,"
+ " PRIMARY KEY (contactId),"
@@ -103,17 +103,17 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_GROUPS =
"CREATE TABLE groups"
+ " (groupId HASH NOT NULL,"
+ " clientId VARCHAR NOT NULL,"
+ " descriptor BINARY NOT NULL,"
+ " (groupId _HASH NOT NULL,"
+ " clientId _STRING NOT NULL,"
+ " descriptor _BINARY NOT NULL,"
+ " PRIMARY KEY (groupId))";
private static final String CREATE_GROUP_METADATA =
"CREATE TABLE groupMetadata"
+ " (groupId HASH NOT NULL,"
+ " key VARCHAR NOT NULL,"
+ " value BINARY NOT NULL,"
+ " PRIMARY KEY (groupId, key),"
+ " (groupId _HASH NOT NULL,"
+ " metaKey _STRING NOT NULL,"
+ " value _BINARY NOT NULL,"
+ " PRIMARY KEY (groupId, metaKey),"
+ " FOREIGN KEY (groupId)"
+ " REFERENCES groups (groupId)"
+ " ON DELETE CASCADE)";
@@ -121,7 +121,7 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_GROUP_VISIBILITIES =
"CREATE TABLE groupVisibilities"
+ " (contactId INT NOT NULL,"
+ " groupId HASH NOT NULL,"
+ " groupId _HASH NOT NULL,"
+ " shared BOOLEAN NOT NULL,"
+ " PRIMARY KEY (contactId, groupId),"
+ " FOREIGN KEY (contactId)"
@@ -133,8 +133,8 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_MESSAGES =
"CREATE TABLE messages"
+ " (messageId HASH NOT NULL,"
+ " groupId HASH NOT NULL,"
+ " (messageId _HASH NOT NULL,"
+ " groupId _HASH NOT NULL,"
+ " timestamp BIGINT NOT NULL,"
+ " state INT NOT NULL,"
+ " shared BOOLEAN NOT NULL,"
@@ -147,19 +147,19 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_MESSAGE_METADATA =
"CREATE TABLE messageMetadata"
+ " (messageId HASH NOT NULL,"
+ " key VARCHAR NOT NULL,"
+ " value BINARY NOT NULL,"
+ " PRIMARY KEY (messageId, key),"
+ " (messageId _HASH NOT NULL,"
+ " metaKey _STRING NOT NULL,"
+ " value _BINARY NOT NULL,"
+ " PRIMARY KEY (messageId, metaKey),"
+ " FOREIGN KEY (messageId)"
+ " REFERENCES messages (messageId)"
+ " ON DELETE CASCADE)";
private static final String CREATE_MESSAGE_DEPENDENCIES =
"CREATE TABLE messageDependencies"
+ " (groupId HASH NOT NULL,"
+ " messageId HASH NOT NULL,"
+ " dependencyId HASH NOT NULL," // Not a foreign key
+ " (groupId _HASH NOT NULL,"
+ " messageId _HASH NOT NULL,"
+ " dependencyId _HASH NOT NULL," // Not a foreign key
+ " FOREIGN KEY (groupId)"
+ " REFERENCES groups (groupId)"
+ " ON DELETE CASCADE,"
@@ -169,7 +169,7 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_OFFERS =
"CREATE TABLE offers"
+ " (messageId HASH NOT NULL," // Not a foreign key
+ " (messageId _HASH NOT NULL," // Not a foreign key
+ " contactId INT NOT NULL,"
+ " PRIMARY KEY (messageId, contactId),"
+ " FOREIGN KEY (contactId)"
@@ -178,7 +178,7 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_STATUSES =
"CREATE TABLE statuses"
+ " (messageId HASH NOT NULL,"
+ " (messageId _HASH NOT NULL,"
+ " contactId INT NOT NULL,"
+ " ack BOOLEAN NOT NULL,"
+ " seen BOOLEAN NOT NULL,"
@@ -195,20 +195,20 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_TRANSPORTS =
"CREATE TABLE transports"
+ " (transportId VARCHAR NOT NULL,"
+ " (transportId _STRING NOT NULL,"
+ " maxLatency INT NOT NULL,"
+ " PRIMARY KEY (transportId))";
private static final String CREATE_INCOMING_KEYS =
"CREATE TABLE incomingKeys"
+ " (contactId INT NOT NULL,"
+ " transportId VARCHAR NOT NULL,"
+ " period BIGINT NOT NULL,"
+ " tagKey SECRET NOT NULL,"
+ " headerKey SECRET NOT NULL,"
+ " transportId _STRING NOT NULL,"
+ " rotationPeriod BIGINT NOT NULL,"
+ " tagKey _SECRET NOT NULL,"
+ " headerKey _SECRET NOT NULL,"
+ " base BIGINT NOT NULL,"
+ " bitmap BINARY NOT NULL,"
+ " PRIMARY KEY (contactId, transportId, period),"
+ " bitmap _BINARY NOT NULL,"
+ " PRIMARY KEY (contactId, transportId, rotationPeriod),"
+ " FOREIGN KEY (contactId)"
+ " REFERENCES contacts (contactId)"
+ " ON DELETE CASCADE,"
@@ -219,10 +219,10 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_OUTGOING_KEYS =
"CREATE TABLE outgoingKeys"
+ " (contactId INT NOT NULL,"
+ " transportId VARCHAR NOT NULL,"
+ " period BIGINT NOT NULL,"
+ " tagKey SECRET NOT NULL,"
+ " headerKey SECRET NOT NULL,"
+ " transportId _STRING NOT NULL,"
+ " rotationPeriod BIGINT NOT NULL,"
+ " tagKey _SECRET NOT NULL,"
+ " headerKey _SECRET NOT NULL,"
+ " stream BIGINT NOT NULL,"
+ " PRIMARY KEY (contactId, transportId),"
+ " FOREIGN KEY (contactId)"
@@ -260,7 +260,8 @@ abstract class JdbcDatabase implements Database<Connection> {
Logger.getLogger(JdbcDatabase.class.getName());
// Different database libraries use different names for certain types
private final String hashType, binaryType, counterType, secretType;
private final String hashType, secretType, binaryType;
private final String counterType, stringType;
private final Clock clock;
// Locking: connectionsLock
@@ -275,12 +276,13 @@ abstract class JdbcDatabase implements Database<Connection> {
private final Lock connectionsLock = new ReentrantLock();
private final Condition connectionsChanged = connectionsLock.newCondition();
JdbcDatabase(String hashType, String binaryType, String counterType,
String secretType, Clock clock) {
JdbcDatabase(String hashType, String secretType, String binaryType,
String counterType, String stringType, Clock clock) {
this.hashType = hashType;
this.secretType = secretType;
this.binaryType = binaryType;
this.counterType = counterType;
this.secretType = secretType;
this.stringType = stringType;
this.clock = clock;
}
@@ -383,16 +385,17 @@ abstract class JdbcDatabase implements Database<Connection> {
}
private String insertTypeNames(String s) {
s = s.replaceAll("HASH", hashType);
s = s.replaceAll("BINARY", binaryType);
s = s.replaceAll("COUNTER", counterType);
s = s.replaceAll("SECRET", secretType);
s = s.replaceAll("_HASH", hashType);
s = s.replaceAll("_SECRET", secretType);
s = s.replaceAll("_BINARY", binaryType);
s = s.replaceAll("_COUNTER", counterType);
s = s.replaceAll("_STRING", stringType);
return s;
}
@Override
public Connection startTransaction() throws DbException {
Connection txn = null;
Connection txn;
connectionsLock.lock();
try {
if (closed) throw new DbClosedException();
@@ -500,7 +503,8 @@ abstract class JdbcDatabase implements Database<Connection> {
try {
// Create a contact row
String sql = "INSERT INTO contacts"
+ " (authorId, name, publicKey, localAuthorId, verified, active)"
+ " (authorId, name, publicKey, localAuthorId,"
+ " verified, active)"
+ " VALUES (?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, remote.getId().getBytes());
@@ -719,7 +723,7 @@ abstract class JdbcDatabase implements Database<Connection> {
try {
// Store the incoming keys
String sql = "INSERT INTO incomingKeys (contactId, transportId,"
+ " period, tagKey, headerKey, base, bitmap)"
+ " rotationPeriod, tagKey, headerKey, base, bitmap)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
@@ -754,8 +758,8 @@ abstract class JdbcDatabase implements Database<Connection> {
if (rows != 1) throw new DbStateException();
ps.close();
// Store the outgoing keys
sql = "INSERT INTO outgoingKeys (contactId, transportId, period,"
+ " tagKey, headerKey, stream)"
sql = "INSERT INTO outgoingKeys (contactId, transportId,"
+ " rotationPeriod, tagKey, headerKey, stream)"
+ " VALUES (?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
@@ -1335,7 +1339,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " JOIN messageMetadata AS md"
+ " ON m.messageId = md.messageId"
+ " WHERE state = ? AND groupId = ?"
+ " AND key = ? AND value = ?";
+ " AND metaKey = ? AND value = ?";
for (Entry<String, byte[]> e : query.entrySet()) {
ps = txn.prepareStatement(sql);
ps.setInt(1, DELIVERED.getValue());
@@ -1367,7 +1371,7 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT m.messageId, key, value"
String sql = "SELECT m.messageId, metaKey, value"
+ " FROM messages AS m"
+ " JOIN messageMetadata AS md"
+ " ON m.messageId = md.messageId"
@@ -1417,7 +1421,7 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT key, value FROM groupMetadata"
String sql = "SELECT metaKey, value FROM groupMetadata"
+ " WHERE groupId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes());
@@ -1440,7 +1444,7 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT key, value FROM messageMetadata AS md"
String sql = "SELECT metaKey, value FROM messageMetadata AS md"
+ " JOIN messages AS m"
+ " ON m.messageId = md.messageId"
+ " WHERE m.state = ? AND md.messageId = ?";
@@ -1466,7 +1470,7 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT key, value FROM messageMetadata AS md"
String sql = "SELECT metaKey, value FROM messageMetadata AS md"
+ " JOIN messages AS m"
+ " ON m.messageId = md.messageId"
+ " WHERE (m.state = ? OR m.state = ?)"
@@ -1904,7 +1908,8 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT key, value FROM settings WHERE namespace = ?";
String sql = "SELECT settingKey, value FROM settings"
+ " WHERE namespace = ?";
ps = txn.prepareStatement(sql);
ps.setString(1, namespace);
rs = ps.executeQuery();
@@ -1927,10 +1932,11 @@ abstract class JdbcDatabase implements Database<Connection> {
ResultSet rs = null;
try {
// Retrieve the incoming keys
String sql = "SELECT period, tagKey, headerKey, base, bitmap"
String sql = "SELECT rotationPeriod, tagKey, headerKey,"
+ " base, bitmap"
+ " FROM incomingKeys"
+ " WHERE transportId = ?"
+ " ORDER BY contactId, period";
+ " ORDER BY contactId, rotationPeriod";
ps = txn.prepareStatement(sql);
ps.setString(1, t.getString());
rs = ps.executeQuery();
@@ -1947,10 +1953,10 @@ abstract class JdbcDatabase implements Database<Connection> {
rs.close();
ps.close();
// Retrieve the outgoing keys in the same order
sql = "SELECT contactId, period, tagKey, headerKey, stream"
sql = "SELECT contactId, rotationPeriod, tagKey, headerKey, stream"
+ " FROM outgoingKeys"
+ " WHERE transportId = ?"
+ " ORDER BY contactId, period";
+ " ORDER BY contactId, rotationPeriod";
ps = txn.prepareStatement(sql);
ps.setString(1, t.getString());
rs = ps.executeQuery();
@@ -1987,7 +1993,8 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
try {
String sql = "UPDATE outgoingKeys SET stream = stream + 1"
+ " WHERE contactId = ? AND transportId = ? AND period = ?";
+ " WHERE contactId = ? AND transportId = ?"
+ " AND rotationPeriod = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setString(2, t.getString());
@@ -2081,7 +2088,7 @@ abstract class JdbcDatabase implements Database<Connection> {
// Delete any keys that are being removed
if (!removed.isEmpty()) {
String sql = "DELETE FROM " + tableName
+ " WHERE " + columnName + " = ? AND key = ?";
+ " WHERE " + columnName + " = ? AND metaKey = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, id);
for (String key : removed) {
@@ -2100,7 +2107,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (retained.isEmpty()) return;
// Update any keys that already exist
String sql = "UPDATE " + tableName + " SET value = ?"
+ " WHERE " + columnName + " = ? AND key = ?";
+ " WHERE " + columnName + " = ? AND metaKey = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(2, id);
for (Entry<String, byte[]> e : retained.entrySet()) {
@@ -2117,7 +2124,7 @@ abstract class JdbcDatabase implements Database<Connection> {
}
// Insert any keys that don't already exist
sql = "INSERT INTO " + tableName
+ " (" + columnName + ", key, value)"
+ " (" + columnName + ", metaKey, value)"
+ " VALUES (?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, id);
@@ -2149,7 +2156,7 @@ abstract class JdbcDatabase implements Database<Connection> {
try {
// Update any settings that already exist
String sql = "UPDATE settings SET value = ?"
+ " WHERE namespace = ? AND key = ?";
+ " WHERE namespace = ? AND settingKey = ?";
ps = txn.prepareStatement(sql);
for (Entry<String, String> e : s.entrySet()) {
ps.setString(1, e.getValue());
@@ -2164,7 +2171,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (rows > 1) throw new DbStateException();
}
// Insert any settings that don't already exist
sql = "INSERT INTO settings (namespace, key, value)"
sql = "INSERT INTO settings (namespace, settingKey, value)"
+ " VALUES (?, ?, ?)";
ps = txn.prepareStatement(sql);
int updateIndex = 0, inserted = 0;
@@ -2528,7 +2535,8 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
try {
String sql = "UPDATE incomingKeys SET base = ?, bitmap = ?"
+ " WHERE contactId = ? AND transportId = ? AND period = ?";
+ " WHERE contactId = ? AND transportId = ?"
+ " AND rotationPeriod = ?";
ps = txn.prepareStatement(sql);
ps.setLong(1, base);
ps.setBytes(2, bitmap);

View File

@@ -1,6 +1,6 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
@@ -46,7 +46,7 @@ class KeyAgreementConnector {
private final Callbacks callbacks;
private final Clock clock;
private final CryptoComponent crypto;
private final KeyAgreementCrypto keyAgreementCrypto;
private final PluginManager pluginManager;
private final CompletionService<KeyAgreementConnection> connect;
@@ -58,11 +58,11 @@ class KeyAgreementConnector {
private volatile boolean alice = false;
KeyAgreementConnector(Callbacks callbacks, Clock clock,
CryptoComponent crypto, PluginManager pluginManager,
KeyAgreementCrypto keyAgreementCrypto, PluginManager pluginManager,
Executor ioExecutor) {
this.callbacks = callbacks;
this.clock = clock;
this.crypto = crypto;
this.keyAgreementCrypto = keyAgreementCrypto;
this.pluginManager = pluginManager;
connect = new ExecutorCompletionService<>(ioExecutor);
}
@@ -70,8 +70,8 @@ class KeyAgreementConnector {
public Payload listen(KeyPair localKeyPair) {
LOG.info("Starting BQP listeners");
// Derive commitment
byte[] commitment = crypto.deriveKeyCommitment(
localKeyPair.getPublic().getEncoded());
byte[] commitment = keyAgreementCrypto.deriveKeyCommitment(
localKeyPair.getPublic());
// Start all listeners and collect their descriptors
List<TransportDescriptor> descriptors = new ArrayList<>();
for (DuplexPlugin plugin : pluginManager.getKeyAgreementPlugins()) {

View File

@@ -1,19 +1,10 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
import org.briarproject.bramble.api.keyagreement.PayloadParser;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.system.Clock;
import java.util.concurrent.Executor;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@@ -22,13 +13,9 @@ import dagger.Provides;
public class KeyAgreementModule {
@Provides
@Singleton
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(Clock clock,
CryptoComponent crypto, EventBus eventBus,
@IoExecutor Executor ioExecutor, PayloadEncoder payloadEncoder,
PluginManager pluginManager) {
return new KeyAgreementTaskFactoryImpl(clock, crypto, eventBus,
ioExecutor, payloadEncoder, pluginManager);
KeyAgreementTask provideKeyAgreementTask(
KeyAgreementTaskImpl keyAgreementTask) {
return keyAgreementTask;
}
@Provides

View File

@@ -1,7 +1,10 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.keyagreement.Payload;
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
@@ -11,6 +14,10 @@ import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_SECRET_LABEL;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
/**
* Implementation of the BQP protocol.
* <p/>
@@ -57,6 +64,7 @@ class KeyAgreementProtocol {
private final Callbacks callbacks;
private final CryptoComponent crypto;
private final KeyAgreementCrypto keyAgreementCrypto;
private final PayloadEncoder payloadEncoder;
private final KeyAgreementTransport transport;
private final Payload theirPayload, ourPayload;
@@ -64,11 +72,13 @@ class KeyAgreementProtocol {
private final boolean alice;
KeyAgreementProtocol(Callbacks callbacks, CryptoComponent crypto,
KeyAgreementCrypto keyAgreementCrypto,
PayloadEncoder payloadEncoder, KeyAgreementTransport transport,
Payload theirPayload, Payload ourPayload, KeyPair ourKeyPair,
boolean alice) {
this.callbacks = callbacks;
this.crypto = crypto;
this.keyAgreementCrypto = keyAgreementCrypto;
this.payloadEncoder = payloadEncoder;
this.transport = transport;
this.theirPayload = theirPayload;
@@ -86,7 +96,7 @@ class KeyAgreementProtocol {
*/
SecretKey perform() throws AbortException, IOException {
try {
byte[] theirPublicKey;
PublicKey theirPublicKey;
if (alice) {
sendKey();
// Alice waits here until Bob obtains her payload.
@@ -104,7 +114,7 @@ class KeyAgreementProtocol {
receiveConfirm(s, theirPublicKey);
sendConfirm(s, theirPublicKey);
}
return crypto.deriveMasterSecret(s);
return crypto.deriveKey(MASTER_SECRET_LABEL, s);
} catch (AbortException e) {
sendAbort(e.getCause() != null);
throw e;
@@ -115,27 +125,41 @@ class KeyAgreementProtocol {
transport.sendKey(ourKeyPair.getPublic().getEncoded());
}
private byte[] receiveKey() throws AbortException {
byte[] publicKey = transport.receiveKey();
private PublicKey receiveKey() throws AbortException {
byte[] publicKeyBytes = transport.receiveKey();
callbacks.initialRecordReceived();
byte[] expected = crypto.deriveKeyCommitment(publicKey);
if (!Arrays.equals(expected, theirPayload.getCommitment()))
KeyParser keyParser = crypto.getAgreementKeyParser();
try {
PublicKey publicKey = keyParser.parsePublicKey(publicKeyBytes);
byte[] expected = keyAgreementCrypto.deriveKeyCommitment(publicKey);
if (!Arrays.equals(expected, theirPayload.getCommitment()))
throw new AbortException();
return publicKey;
} catch (GeneralSecurityException e) {
throw new AbortException();
return publicKey;
}
}
private SecretKey deriveSharedSecret(byte[] theirPublicKey)
private SecretKey deriveSharedSecret(PublicKey theirPublicKey)
throws AbortException {
try {
return crypto.deriveSharedSecret(theirPublicKey, ourKeyPair, alice);
byte[] ourPublicKeyBytes = ourKeyPair.getPublic().getEncoded();
byte[] theirPublicKeyBytes = theirPublicKey.getEncoded();
byte[][] inputs = {
new byte[] {PROTOCOL_VERSION},
alice ? ourPublicKeyBytes : theirPublicKeyBytes,
alice ? theirPublicKeyBytes : ourPublicKeyBytes
};
return crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
theirPublicKey, ourKeyPair, inputs);
} catch (GeneralSecurityException e) {
throw new AbortException(e);
}
}
private void sendConfirm(SecretKey s, byte[] theirPublicKey)
private void sendConfirm(SecretKey s, PublicKey theirPublicKey)
throws IOException {
byte[] confirm = crypto.deriveConfirmationRecord(s,
byte[] confirm = keyAgreementCrypto.deriveConfirmationRecord(s,
payloadEncoder.encode(theirPayload),
payloadEncoder.encode(ourPayload),
theirPublicKey, ourKeyPair,
@@ -143,10 +167,10 @@ class KeyAgreementProtocol {
transport.sendConfirm(confirm);
}
private void receiveConfirm(SecretKey s, byte[] theirPublicKey)
private void receiveConfirm(SecretKey s, PublicKey theirPublicKey)
throws AbortException {
byte[] confirm = transport.receiveConfirm();
byte[] expected = crypto.deriveConfirmationRecord(s,
byte[] expected = keyAgreementCrypto.deriveConfirmationRecord(s,
payloadEncoder.encode(theirPayload),
payloadEncoder.encode(ourPayload),
theirPublicKey, ourKeyPair,

View File

@@ -1,46 +0,0 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.system.Clock;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class KeyAgreementTaskFactoryImpl implements KeyAgreementTaskFactory {
private final Clock clock;
private final CryptoComponent crypto;
private final EventBus eventBus;
private final Executor ioExecutor;
private final PayloadEncoder payloadEncoder;
private final PluginManager pluginManager;
@Inject
KeyAgreementTaskFactoryImpl(Clock clock, CryptoComponent crypto,
EventBus eventBus, @IoExecutor Executor ioExecutor,
PayloadEncoder payloadEncoder, PluginManager pluginManager) {
this.clock = clock;
this.crypto = crypto;
this.eventBus = eventBus;
this.ioExecutor = ioExecutor;
this.payloadEncoder = payloadEncoder;
this.pluginManager = pluginManager;
}
@Override
public KeyAgreementTask createTask() {
return new KeyAgreementTaskImpl(clock, crypto, eventBus, payloadEncoder,
pluginManager, ioExecutor);
}
}

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.event.EventBus;
@@ -14,6 +15,7 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
@@ -23,6 +25,8 @@ import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
@MethodsNotNullByDefault
@@ -35,6 +39,7 @@ class KeyAgreementTaskImpl extends Thread implements
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
private final CryptoComponent crypto;
private final KeyAgreementCrypto keyAgreementCrypto;
private final EventBus eventBus;
private final PayloadEncoder payloadEncoder;
private final KeyPair localKeyPair;
@@ -43,14 +48,17 @@ class KeyAgreementTaskImpl extends Thread implements
private Payload localPayload;
private Payload remotePayload;
@Inject
KeyAgreementTaskImpl(Clock clock, CryptoComponent crypto,
EventBus eventBus, PayloadEncoder payloadEncoder,
PluginManager pluginManager, Executor ioExecutor) {
KeyAgreementCrypto keyAgreementCrypto, EventBus eventBus,
PayloadEncoder payloadEncoder, PluginManager pluginManager,
@IoExecutor Executor ioExecutor) {
this.crypto = crypto;
this.keyAgreementCrypto = keyAgreementCrypto;
this.eventBus = eventBus;
this.payloadEncoder = payloadEncoder;
localKeyPair = crypto.generateAgreementKeyPair();
connector = new KeyAgreementConnector(this, clock, crypto,
connector = new KeyAgreementConnector(this, clock, keyAgreementCrypto,
pluginManager, ioExecutor);
}
@@ -100,8 +108,8 @@ class KeyAgreementTaskImpl extends Thread implements
// Run BQP protocol over the connection
LOG.info("Starting BQP protocol");
KeyAgreementProtocol protocol = new KeyAgreementProtocol(this, crypto,
payloadEncoder, transport, remotePayload, localPayload,
localKeyPair, alice);
keyAgreementCrypto, payloadEncoder, transport, remotePayload,
localPayload, localKeyPair, alice);
try {
SecretKey master = protocol.perform();
KeyAgreementResult result =

View File

@@ -58,12 +58,12 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
this.metadataParser = metadataParser;
this.contactGroupFactory = contactGroupFactory;
this.clock = clock;
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID);
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
CLIENT_VERSION);
}
@Override
public void createLocalState(Transaction txn) throws DbException {
if (db.containsGroup(txn, localGroup.getId())) return;
db.addGroup(txn, localGroup);
// Ensure we've set things up for any pre-existing contacts
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
@@ -130,8 +130,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
public Map<TransportId, TransportProperties> getLocalProperties()
throws DbException {
Map<TransportId, TransportProperties> local;
// TODO: Transaction can be read-only when code is simplified
Transaction txn = db.startTransaction(false);
Transaction txn = db.startTransaction(true);
try {
local = getLocalProperties(txn);
db.commitTransaction(txn);
@@ -166,8 +165,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
throws DbException {
try {
TransportProperties p = null;
// TODO: Transaction can be read-only when code is simplified
Transaction txn = db.startTransaction(false);
Transaction txn = db.startTransaction(true);
try {
// Find the latest local update
LatestUpdate latest = findLatest(txn, localGroup.getId(), t,
@@ -193,8 +191,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
public Map<ContactId, TransportProperties> getRemoteProperties(
TransportId t) throws DbException {
Map<ContactId, TransportProperties> remote = new HashMap<>();
// TODO: Transaction can be read-only when code is simplified
Transaction txn = db.startTransaction(false);
Transaction txn = db.startTransaction(true);
try {
for (Contact c : db.getContacts(txn))
remote.put(c.getId(), getRemoteProperties(txn, c, t));
@@ -228,8 +225,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
public TransportProperties getRemoteProperties(ContactId c, TransportId t)
throws DbException {
TransportProperties p;
// TODO: Transaction can be read-only when code is simplified
Transaction txn = db.startTransaction(false);
Transaction txn = db.startTransaction(true);
try {
p = getRemoteProperties(txn, db.getContact(txn, c), t);
db.commitTransaction(txn);
@@ -292,7 +288,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
}
private Group getContactGroup(Contact c) {
return contactGroupFactory.createContactGroup(CLIENT_ID, c);
return contactGroupFactory.createContactGroup(CLIENT_ID,
CLIENT_VERSION, c);
}
private void storeMessage(Transaction txn, GroupId g, TransportId t,
@@ -319,7 +316,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
private Map<TransportId, LatestUpdate> findLatestLocal(Transaction txn)
throws DbException, FormatException {
// TODO: This can be simplified before 1.0
Map<TransportId, LatestUpdate> latestUpdates = new HashMap<>();
Map<MessageId, BdfDictionary> metadata = clientHelper
.getMessageMetadataAsDictionary(txn, localGroup.getId());
@@ -327,17 +323,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
BdfDictionary meta = e.getValue();
TransportId t = new TransportId(meta.getString("transportId"));
long version = meta.getLong("version");
LatestUpdate latest = latestUpdates.get(t);
if (latest == null) {
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
} else if (version > latest.version) {
// This update is newer - delete the previous one
db.removeMessage(txn, latest.messageId);
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
} else {
// We've already found a newer update - delete this one
db.removeMessage(txn, e.getKey());
}
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
}
return latestUpdates;
}
@@ -345,38 +331,16 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Nullable
private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t,
boolean local) throws DbException, FormatException {
// TODO: This can be simplified before 1.0
LatestUpdate latest = null;
Map<MessageId, BdfDictionary> metadata =
clientHelper.getMessageMetadataAsDictionary(txn, g);
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
BdfDictionary meta = e.getValue();
if (meta.getString("transportId").equals(t.getString())
&& meta.getBoolean("local") == local) {
long version = meta.getLong("version");
if (latest == null) {
latest = new LatestUpdate(e.getKey(), version);
} else if (version > latest.version) {
// This update is newer - delete the previous one
if (local) {
db.removeMessage(txn, latest.messageId);
} else {
db.deleteMessage(txn, latest.messageId);
db.deleteMessageMetadata(txn, latest.messageId);
}
latest = new LatestUpdate(e.getKey(), version);
} else {
// We've already found a newer update - delete this one
if (local) {
db.removeMessage(txn, e.getKey());
} else {
db.deleteMessage(txn, e.getKey());
db.deleteMessageMetadata(txn, e.getKey());
}
}
return new LatestUpdate(e.getKey(), meta.getLong("version"));
}
}
return latest;
return null;
}
private TransportProperties parseProperties(BdfList message)

View File

@@ -6,11 +6,16 @@ import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupFactory;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.bramble.util.StringUtils;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.GroupId.LABEL;
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
@Immutable
@NotNullByDefault
class GroupFactoryImpl implements GroupFactory {
@@ -23,9 +28,12 @@ class GroupFactoryImpl implements GroupFactory {
}
@Override
public Group createGroup(ClientId c, byte[] descriptor) {
byte[] hash = crypto.hash(GroupId.LABEL,
StringUtils.toUtf8(c.getString()), descriptor);
public Group createGroup(ClientId c, int clientVersion, byte[] descriptor) {
byte[] clientVersionBytes = new byte[INT_32_BYTES];
ByteUtils.writeUint32(clientVersion, clientVersionBytes, 0);
byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
StringUtils.toUtf8(c.getString()), clientVersionBytes,
descriptor);
return new Group(new GroupId(hash), c, descriptor);
}
}

View File

@@ -12,8 +12,10 @@ import org.briarproject.bramble.util.ByteUtils;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.MessageId.LABEL;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
@Immutable
@NotNullByDefault
@@ -32,9 +34,9 @@ class MessageFactoryImpl implements MessageFactory {
throw new IllegalArgumentException();
byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
ByteUtils.writeUint64(timestamp, timeBytes, 0);
byte[] idHash =
crypto.hash(MessageId.LABEL, g.getBytes(), timeBytes, body);
MessageId id = new MessageId(idHash);
byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
g.getBytes(), timeBytes, body);
MessageId id = new MessageId(hash);
byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);

View File

@@ -1,6 +1,6 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -20,17 +20,18 @@ class TransportKeyManagerFactoryImpl implements
TransportKeyManagerFactory {
private final DatabaseComponent db;
private final CryptoComponent crypto;
private final TransportCrypto transportCrypto;
private final Executor dbExecutor;
private final ScheduledExecutorService scheduler;
private final Clock clock;
@Inject
TransportKeyManagerFactoryImpl(DatabaseComponent db, CryptoComponent crypto,
TransportKeyManagerFactoryImpl(DatabaseComponent db,
TransportCrypto transportCrypto,
@DatabaseExecutor Executor dbExecutor,
@Scheduler ScheduledExecutorService scheduler, Clock clock) {
this.db = db;
this.crypto = crypto;
this.transportCrypto = transportCrypto;
this.dbExecutor = dbExecutor;
this.scheduler = scheduler;
this.clock = clock;
@@ -39,8 +40,8 @@ class TransportKeyManagerFactoryImpl implements
@Override
public TransportKeyManager createTransportKeyManager(
TransportId transportId, long maxLatency) {
return new TransportKeyManagerImpl(db, crypto, dbExecutor, scheduler,
clock, transportId, maxLatency);
return new TransportKeyManagerImpl(db, transportCrypto, dbExecutor,
scheduler, clock, transportId, maxLatency);
}
}

View File

@@ -2,8 +2,8 @@ package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
@@ -41,7 +41,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
Logger.getLogger(TransportKeyManagerImpl.class.getName());
private final DatabaseComponent db;
private final CryptoComponent crypto;
private final TransportCrypto transportCrypto;
private final Executor dbExecutor;
private final ScheduledExecutorService scheduler;
private final Clock clock;
@@ -54,11 +54,12 @@ class TransportKeyManagerImpl implements TransportKeyManager {
private final Map<ContactId, MutableOutgoingKeys> outContexts;
private final Map<ContactId, MutableTransportKeys> keys;
TransportKeyManagerImpl(DatabaseComponent db, CryptoComponent crypto,
Executor dbExecutor, @Scheduler ScheduledExecutorService scheduler,
Clock clock, TransportId transportId, long maxLatency) {
TransportKeyManagerImpl(DatabaseComponent db,
TransportCrypto transportCrypto, Executor dbExecutor,
@Scheduler ScheduledExecutorService scheduler, Clock clock,
TransportId transportId, long maxLatency) {
this.db = db;
this.crypto = crypto;
this.transportCrypto = transportCrypto;
this.dbExecutor = dbExecutor;
this.scheduler = scheduler;
this.clock = clock;
@@ -99,7 +100,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
ContactId c = e.getKey();
TransportKeys k = e.getValue();
TransportKeys k1 = crypto.rotateTransportKeys(k, rotationPeriod);
TransportKeys k1 =
transportCrypto.rotateTransportKeys(k, rotationPeriod);
if (k1.getRotationPeriod() > k.getRotationPeriod())
rotationResult.rotated.put(c, k1);
rotationResult.current.put(c, k1);
@@ -127,7 +129,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
for (long streamNumber : inKeys.getWindow().getUnseen()) {
TagContext tagCtx = new TagContext(c, inKeys, streamNumber);
byte[] tag = new byte[TAG_LENGTH];
crypto.encodeTag(tag, inKeys.getTagKey(), PROTOCOL_VERSION,
transportCrypto.encodeTag(tag, inKeys.getTagKey(), PROTOCOL_VERSION,
streamNumber);
inContexts.put(new Bytes(tag), tagCtx);
}
@@ -162,11 +164,11 @@ class TransportKeyManagerImpl implements TransportKeyManager {
// Work out what rotation period the timestamp belongs to
long rotationPeriod = timestamp / rotationPeriodLength;
// Derive the transport keys
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
rotationPeriod, alice);
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
master, rotationPeriod, alice);
// Rotate the keys to the current rotation period if necessary
rotationPeriod = clock.currentTimeMillis() / rotationPeriodLength;
k = crypto.rotateTransportKeys(k, rotationPeriod);
k = transportCrypto.rotateTransportKeys(k, rotationPeriod);
// Initialise mutable state for the contact
addKeys(c, new MutableTransportKeys(k));
// Write the keys back to the DB
@@ -234,8 +236,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
// Add tags for any stream numbers added to the window
for (long streamNumber : change.getAdded()) {
byte[] addTag = new byte[TAG_LENGTH];
crypto.encodeTag(addTag, inKeys.getTagKey(), PROTOCOL_VERSION,
streamNumber);
transportCrypto.encodeTag(addTag, inKeys.getTagKey(),
PROTOCOL_VERSION, streamNumber);
inContexts.put(new Bytes(addTag), new TagContext(
tagCtx.contactId, inKeys, streamNumber));
}
@@ -243,7 +245,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
for (long streamNumber : change.getRemoved()) {
if (streamNumber == tagCtx.streamNumber) continue;
byte[] removeTag = new byte[TAG_LENGTH];
crypto.encodeTag(removeTag, inKeys.getTagKey(),
transportCrypto.encodeTag(removeTag, inKeys.getTagKey(),
PROTOCOL_VERSION, streamNumber);
inContexts.remove(new Bytes(removeTag));
}

View File

@@ -0,0 +1,25 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.KeyPair;
import java.security.GeneralSecurityException;
public class EcdsaSignatureTest extends SignatureTest {
@Override
protected KeyPair generateKeyPair() {
return crypto.generateSignatureKeyPair();
}
@Override
protected byte[] sign(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException {
return crypto.sign(label, toSign, privateKey);
}
@Override
protected boolean verify(String label, byte[] signedData, byte[] publicKey,
byte[] signature) throws GeneralSecurityException {
return crypto.verify(label, signedData, publicKey, signature);
}
}

View File

@@ -0,0 +1,25 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.KeyPair;
import java.security.GeneralSecurityException;
public class EdSignatureTest extends SignatureTest {
@Override
protected KeyPair generateKeyPair() {
return crypto.generateEdKeyPair();
}
@Override
protected byte[] sign(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException {
return crypto.signEd(label, toSign, privateKey);
}
@Override
protected boolean verify(String label, byte[] signedData, byte[] publicKey,
byte[] signature) throws GeneralSecurityException {
return crypto.verifyEd(label, signedData, publicKey, signature);
}
}

View File

@@ -1,16 +1,19 @@
package org.briarproject.bramble.crypto;
import net.i2p.crypto.eddsa.EdDSASecurityProvider;
import net.i2p.crypto.eddsa.KeyPairGenerator;
import org.spongycastle.asn1.sec.SECNamedCurves;
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.BasicAgreement;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.params.ParametersWithRandom;
import org.spongycastle.crypto.signers.DSADigestSigner;
import org.spongycastle.crypto.signers.DSAKCalculator;
@@ -19,14 +22,23 @@ import org.spongycastle.crypto.signers.HMacDSAKCalculator;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECPoint;
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
import org.whispersystems.curve25519.Curve25519;
import org.whispersystems.curve25519.Curve25519KeyPair;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Signature;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static net.i2p.crypto.eddsa.EdDSAEngine.SIGNATURE_ALGORITHM;
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
// Not a JUnit test
public class EllipticCurvePerformanceTest {
@@ -37,8 +49,9 @@ public class EllipticCurvePerformanceTest {
"secp256k1", "secp256r1", "secp384r1", "secp521r1");
private static final List<String> BRAINPOOL_NAMES = Arrays.asList(
"brainpoolp256r1", "brainpoolp384r1", "brainpoolp512r1");
private static final Provider ED_PROVIDER = new EdDSASecurityProvider();
public static void main(String[] args) {
public static void main(String[] args) throws GeneralSecurityException {
for (String name : SEC_NAMES) {
ECDomainParameters params =
convertParams(SECNamedCurves.getByName(name));
@@ -51,43 +64,32 @@ public class EllipticCurvePerformanceTest {
runTest(name + " default", params);
runTest(name + " constant", constantTime(params));
}
runTest("ours", EllipticCurveConstants.PARAMETERS);
runTest("ours", PARAMETERS);
runCurve25519Test();
runEd25519Test();
}
private static void runTest(String name, ECDomainParameters params) {
// Generate two key pairs using the given parameters
ECKeyGenerationParameters generatorParams =
new ECKeyGenerationParameters(params, random);
ECKeyPairGenerator generator = new ECKeyPairGenerator();
generator.init(generatorParams);
generator.init(new ECKeyGenerationParameters(params, random));
AsymmetricCipherKeyPair keyPair1 = generator.generateKeyPair();
ECPublicKeyParameters public1 =
(ECPublicKeyParameters) keyPair1.getPublic();
ECPrivateKeyParameters private1 =
(ECPrivateKeyParameters) keyPair1.getPrivate();
AsymmetricCipherKeyPair keyPair2 = generator.generateKeyPair();
ECPublicKeyParameters public2 =
(ECPublicKeyParameters) keyPair2.getPublic();
// Time some ECDH key agreements
List<Long> samples = new ArrayList<>();
for (int i = 0; i < SAMPLES; i++) {
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
long start = System.nanoTime();
agreement.init(private1);
agreement.calculateAgreement(public2);
samples.add(System.nanoTime() - start);
}
long agreementMedian = median(samples);
// Time some ECDH and ECDHC key agreements
long agreementMedian = runAgreementTest(keyPair1, keyPair2, false);
long agreementWithCofactorMedian =
runAgreementTest(keyPair1, keyPair2, true);
// Time some signatures
List<Long> samples = new ArrayList<>();
List<byte[]> signatures = new ArrayList<>();
samples.clear();
for (int i = 0; i < SAMPLES; i++) {
Digest digest = new Blake2sDigest();
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
calculator), digest);
long start = System.nanoTime();
signer.init(true, new ParametersWithRandom(private1, random));
signer.init(true,
new ParametersWithRandom(keyPair1.getPrivate(), random));
signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
signatures.add(signer.generateSignature());
samples.add(System.nanoTime() - start);
@@ -101,17 +103,83 @@ public class EllipticCurvePerformanceTest {
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
calculator), digest);
long start = System.nanoTime();
signer.init(false, public1);
signer.init(false, keyPair1.getPublic());
signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
if (!signer.verifySignature(signatures.get(i)))
throw new AssertionError();
samples.add(System.nanoTime() - start);
}
long verificationMedian = median(samples);
System.out.println(name + ": "
+ agreementMedian + " "
+ signatureMedian + " "
+ verificationMedian);
System.out.println(String.format("%s: %,d %,d %,d %,d", name,
agreementMedian, agreementWithCofactorMedian,
signatureMedian, verificationMedian));
}
private static long runAgreementTest(AsymmetricCipherKeyPair keyPair1,
AsymmetricCipherKeyPair keyPair2, boolean withCofactor) {
List<Long> samples = new ArrayList<>();
for (int i = 0; i < SAMPLES; i++) {
BasicAgreement agreement = createAgreement(withCofactor);
long start = System.nanoTime();
agreement.init(keyPair1.getPrivate());
agreement.calculateAgreement(keyPair2.getPublic());
samples.add(System.nanoTime() - start);
}
return median(samples);
}
private static BasicAgreement createAgreement(boolean withCofactor) {
if (withCofactor) return new ECDHCBasicAgreement();
else return new ECDHBasicAgreement();
}
private static void runCurve25519Test() {
Curve25519 curve25519 = Curve25519.getInstance("java");
Curve25519KeyPair keyPair1 = curve25519.generateKeyPair();
Curve25519KeyPair keyPair2 = curve25519.generateKeyPair();
// Time some key agreements
List<Long> samples = new ArrayList<>();
for (int i = 0; i < SAMPLES; i++) {
long start = System.nanoTime();
curve25519.calculateAgreement(keyPair1.getPublicKey(),
keyPair2.getPrivateKey());
samples.add(System.nanoTime() - start);
}
long agreementMedian = median(samples);
System.out.println(String.format("Curve25519: %,d - - -",
agreementMedian));
}
private static void runEd25519Test() throws GeneralSecurityException {
KeyPair keyPair = new KeyPairGenerator().generateKeyPair();
// Time some signatures
List<Long> samples = new ArrayList<>();
List<byte[]> signatures = new ArrayList<>();
for (int i = 0; i < SAMPLES; i++) {
Signature signature =
Signature.getInstance(SIGNATURE_ALGORITHM, ED_PROVIDER);
long start = System.nanoTime();
signature.initSign(keyPair.getPrivate(), random);
signature.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
signatures.add(signature.sign());
samples.add(System.nanoTime() - start);
}
long signatureMedian = median(samples);
// Time some signature verifications
samples.clear();
for (int i = 0; i < SAMPLES; i++) {
Signature signature =
Signature.getInstance(SIGNATURE_ALGORITHM, ED_PROVIDER);
long start = System.nanoTime();
signature.initVerify(keyPair.getPublic());
signature.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
if (!signature.verify(signatures.get(i)))
throw new AssertionError();
samples.add(System.nanoTime() - start);
}
long verificationMedian = median(samples);
System.out.println(String.format("Ed25519: - - %,d %,d",
signatureMedian, verificationMedian));
}
private static long median(List<Long> list) {

View File

@@ -3,40 +3,32 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.junit.Test;
import java.util.Random;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.junit.Assert.assertArrayEquals;
public class KeyAgreementTest extends BrambleTestCase {
@Test
public void testDeriveMasterSecret() throws Exception {
SecureRandomProvider
secureRandomProvider = new TestSecureRandomProvider();
CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
KeyPair aPair = crypto.generateAgreementKeyPair();
byte[] aPub = aPair.getPublic().getEncoded();
KeyPair bPair = crypto.generateAgreementKeyPair();
byte[] bPub = bPair.getPublic().getEncoded();
SecretKey aMaster = crypto.deriveMasterSecret(aPub, bPair, true);
SecretKey bMaster = crypto.deriveMasterSecret(bPub, aPair, false);
assertArrayEquals(aMaster.getBytes(), bMaster.getBytes());
}
@Test
public void testDeriveSharedSecret() throws Exception {
SecureRandomProvider
secureRandomProvider = new TestSecureRandomProvider();
CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
CryptoComponent crypto =
new CryptoComponentImpl(new TestSecureRandomProvider());
KeyPair aPair = crypto.generateAgreementKeyPair();
byte[] aPub = aPair.getPublic().getEncoded();
KeyPair bPair = crypto.generateAgreementKeyPair();
byte[] bPub = bPair.getPublic().getEncoded();
SecretKey aShared = crypto.deriveSharedSecret(bPub, aPair, true);
SecretKey bShared = crypto.deriveSharedSecret(aPub, bPair, false);
Random random = new Random();
byte[][] inputs = new byte[random.nextInt(10) + 1][];
for (int i = 0; i < inputs.length; i++)
inputs[i] = getRandomBytes(random.nextInt(256));
SecretKey aShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
bPair.getPublic(), aPair, inputs);
SecretKey bShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
aPair.getPublic(), bPair, inputs);
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
}
}

View File

@@ -3,11 +3,11 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.junit.Test;
import java.util.ArrayList;
@@ -16,35 +16,34 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class KeyDerivationTest extends BrambleTestCase {
private final CryptoComponent crypto =
new CryptoComponentImpl(new TestSecureRandomProvider());
private final TransportCrypto transportCrypto =
new TransportCryptoImpl(crypto);
private final TransportId transportId = new TransportId("id");
private final CryptoComponent crypto;
private final SecretKey master;
public KeyDerivationTest() {
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
master = TestUtils.getSecretKey();
}
private final SecretKey master = getSecretKey();
@Test
public void testKeysAreDistinct() {
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
assertAllDifferent(k);
}
@Test
public void testCurrentKeysMatchCurrentKeysOfContact() {
// Start in rotation period 123
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
123, false);
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
master, 123, false);
// Alice's incoming keys should equal Bob's outgoing keys
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
@@ -56,8 +55,8 @@ public class KeyDerivationTest extends BrambleTestCase {
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
kB.getCurrentIncomingKeys().getHeaderKey().getBytes());
// Rotate into the future
kA = crypto.rotateTransportKeys(kA, 456);
kB = crypto.rotateTransportKeys(kB, 456);
kA = transportCrypto.rotateTransportKeys(kA, 456);
kB = transportCrypto.rotateTransportKeys(kB, 456);
// Alice's incoming keys should equal Bob's outgoing keys
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
@@ -73,22 +72,23 @@ public class KeyDerivationTest extends BrambleTestCase {
@Test
public void testPreviousKeysMatchPreviousKeysOfContact() {
// Start in rotation period 123
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
123, false);
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
master, 123, false);
// Compare Alice's previous keys in period 456 with Bob's current keys
// in period 455
kA = crypto.rotateTransportKeys(kA, 456);
kB = crypto.rotateTransportKeys(kB, 455);
kA = transportCrypto.rotateTransportKeys(kA, 456);
kB = transportCrypto.rotateTransportKeys(kB, 455);
// Alice's previous incoming keys should equal Bob's outgoing keys
assertArrayEquals(kA.getPreviousIncomingKeys().getTagKey().getBytes(),
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
assertArrayEquals(kA.getPreviousIncomingKeys().getHeaderKey().getBytes(),
assertArrayEquals(
kA.getPreviousIncomingKeys().getHeaderKey().getBytes(),
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
// Compare Alice's current keys in period 456 with Bob's previous keys
// in period 457
kB = crypto.rotateTransportKeys(kB, 457);
kB = transportCrypto.rotateTransportKeys(kB, 457);
// Alice's outgoing keys should equal Bob's previous incoming keys
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
kB.getPreviousIncomingKeys().getTagKey().getBytes());
@@ -99,14 +99,14 @@ public class KeyDerivationTest extends BrambleTestCase {
@Test
public void testNextKeysMatchNextKeysOfContact() {
// Start in rotation period 123
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
123, false);
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
master, 123, false);
// Compare Alice's current keys in period 456 with Bob's next keys in
// period 455
kA = crypto.rotateTransportKeys(kA, 456);
kB = crypto.rotateTransportKeys(kB, 455);
kA = transportCrypto.rotateTransportKeys(kA, 456);
kB = transportCrypto.rotateTransportKeys(kB, 455);
// Alice's outgoing keys should equal Bob's next incoming keys
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
kB.getNextIncomingKeys().getTagKey().getBytes());
@@ -114,7 +114,7 @@ public class KeyDerivationTest extends BrambleTestCase {
kB.getNextIncomingKeys().getHeaderKey().getBytes());
// Compare Alice's next keys in period 456 with Bob's current keys
// in period 457
kB = crypto.rotateTransportKeys(kB, 457);
kB = transportCrypto.rotateTransportKeys(kB, 457);
// Alice's next incoming keys should equal Bob's outgoing keys
assertArrayEquals(kA.getNextIncomingKeys().getTagKey().getBytes(),
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
@@ -124,12 +124,12 @@ public class KeyDerivationTest extends BrambleTestCase {
@Test
public void testMasterKeyAffectsOutput() {
SecretKey master1 = TestUtils.getSecretKey();
SecretKey master1 = getSecretKey();
assertFalse(Arrays.equals(master.getBytes(), master1.getBytes()));
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys k1 = crypto.deriveTransportKeys(transportId, master1,
123, true);
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId,
master1, 123, true);
assertAllDifferent(k, k1);
}
@@ -137,10 +137,10 @@ public class KeyDerivationTest extends BrambleTestCase {
public void testTransportIdAffectsOutput() {
TransportId transportId1 = new TransportId("id1");
assertFalse(transportId.getString().equals(transportId1.getString()));
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys k1 = crypto.deriveTransportKeys(transportId1, master,
123, true);
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId1,
master, 123, true);
assertAllDifferent(k, k1);
}

View File

@@ -4,42 +4,49 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.junit.Test;
import java.util.Arrays;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
public class MacTest extends BrambleTestCase {
private final CryptoComponent crypto;
private final CryptoComponent crypto =
new CryptoComponentImpl(new TestSecureRandomProvider());
private final SecretKey k = TestUtils.getSecretKey();
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
private final byte[] inputBytes1 = TestUtils.getRandomBytes(234);
private final byte[] inputBytes2 = new byte[0];
public MacTest() {
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
}
private final SecretKey key1 = getSecretKey(), key2 = getSecretKey();
private final String label1 = getRandomString(123);
private final String label2 = getRandomString(123);
private final byte[] input1 = getRandomBytes(123);
private final byte[] input2 = getRandomBytes(234);
private final byte[] input3 = new byte[0];
@Test
public void testIdenticalKeysAndInputsProduceIdenticalMacs() {
// Calculate the MAC twice - the results should be identical
byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
byte[] mac1 = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
byte[] mac1 = crypto.mac(label1, key1, input1, input2, input3);
assertArrayEquals(mac, mac1);
}
@Test
public void testDifferentLabelsProduceDifferentMacs() {
// Calculate the MAC with each label - the results should be different
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
byte[] mac1 = crypto.mac(label2, key1, input1, input2, input3);
assertFalse(Arrays.equals(mac, mac1));
}
@Test
public void testDifferentKeysProduceDifferentMacs() {
// Generate second random key
SecretKey k1 = TestUtils.getSecretKey();
// Calculate the MAC with each key - the results should be different
byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
byte[] mac1 = crypto.mac(k1, inputBytes, inputBytes1, inputBytes2);
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
byte[] mac1 = crypto.mac(label1, key2, input1, input2, input3);
assertFalse(Arrays.equals(mac, mac1));
}
@@ -47,8 +54,8 @@ public class MacTest extends BrambleTestCase {
public void testDifferentInputsProduceDifferentMacs() {
// Calculate the MAC with the inputs in different orders - the results
// should be different
byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
byte[] mac1 = crypto.mac(k, inputBytes2, inputBytes1, inputBytes);
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
byte[] mac1 = crypto.mac(label1, key1, input3, input2, input1);
assertFalse(Arrays.equals(mac, mac1));
}

View File

@@ -8,23 +8,32 @@ import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.StringUtils;
import org.junit.Test;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class SignatureTest extends BrambleTestCase {
public abstract class SignatureTest extends BrambleTestCase {
private final CryptoComponent crypto;
protected final CryptoComponent crypto;
private final byte[] publicKey, privateKey;
private final String label = StringUtils.getRandomString(42);
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
public SignatureTest() {
protected abstract KeyPair generateKeyPair();
protected abstract byte[] sign(String label, byte[] toSign,
byte[] privateKey) throws GeneralSecurityException;
protected abstract boolean verify(String label, byte[] signedData,
byte[] publicKey, byte[] signature) throws GeneralSecurityException;
SignatureTest() {
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
KeyPair k = crypto.generateSignatureKeyPair();
KeyPair k = generateKeyPair();
publicKey = k.getPublic().getEncoded();
privateKey = k.getPrivate().getEncoded();
}
@@ -33,19 +42,19 @@ public class SignatureTest extends BrambleTestCase {
public void testIdenticalKeysAndInputsProduceIdenticalSignatures()
throws Exception {
// Calculate the Signature twice - the results should be identical
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
byte[] sig2 = crypto.sign(label, inputBytes, privateKey);
byte[] sig1 = sign(label, inputBytes, privateKey);
byte[] sig2 = sign(label, inputBytes, privateKey);
assertArrayEquals(sig1, sig2);
}
@Test
public void testDifferentKeysProduceDifferentSignatures() throws Exception {
// Generate second private key
KeyPair k2 = crypto.generateSignatureKeyPair();
KeyPair k2 = generateKeyPair();
byte[] privateKey2 = k2.getPrivate().getEncoded();
// Calculate the signature with each key
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
byte[] sig2 = crypto.sign(label, inputBytes, privateKey2);
byte[] sig1 = sign(label, inputBytes, privateKey);
byte[] sig2 = sign(label, inputBytes, privateKey2);
assertFalse(Arrays.equals(sig1, sig2));
}
@@ -56,8 +65,8 @@ public class SignatureTest extends BrambleTestCase {
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
// Calculate the signature with different inputs
// the results should be different
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
byte[] sig2 = crypto.sign(label, inputBytes2, privateKey);
byte[] sig1 = sign(label, inputBytes, privateKey);
byte[] sig2 = sign(label, inputBytes2, privateKey);
assertFalse(Arrays.equals(sig1, sig2));
}
@@ -68,25 +77,25 @@ public class SignatureTest extends BrambleTestCase {
String label2 = StringUtils.getRandomString(42);
// Calculate the signature with different inputs
// the results should be different
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
byte[] sig2 = crypto.sign(label2, inputBytes, privateKey);
byte[] sig1 = sign(label, inputBytes, privateKey);
byte[] sig2 = sign(label2, inputBytes, privateKey);
assertFalse(Arrays.equals(sig1, sig2));
}
@Test
public void testSignatureVerification() throws Exception {
byte[] sig = crypto.sign(label, inputBytes, privateKey);
assertTrue(crypto.verify(label, inputBytes, publicKey, sig));
byte[] sig = sign(label, inputBytes, privateKey);
assertTrue(verify(label, inputBytes, publicKey, sig));
}
@Test
public void testDifferentKeyFailsVerification() throws Exception {
// Generate second private key
KeyPair k2 = crypto.generateSignatureKeyPair();
KeyPair k2 = generateKeyPair();
byte[] privateKey2 = k2.getPrivate().getEncoded();
// calculate the signature with different key, should fail to verify
byte[] sig = crypto.sign(label, inputBytes, privateKey2);
assertFalse(crypto.verify(label, inputBytes, publicKey, sig));
byte[] sig = sign(label, inputBytes, privateKey2);
assertFalse(verify(label, inputBytes, publicKey, sig));
}
@Test
@@ -94,8 +103,8 @@ public class SignatureTest extends BrambleTestCase {
// Generate a second input
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
// calculate the signature with different input, should fail to verify
byte[] sig = crypto.sign(label, inputBytes, privateKey);
assertFalse(crypto.verify(label, inputBytes2, publicKey, sig));
byte[] sig = sign(label, inputBytes, privateKey);
assertFalse(verify(label, inputBytes2, publicKey, sig));
}
@Test
@@ -103,8 +112,8 @@ public class SignatureTest extends BrambleTestCase {
// Generate a second label
String label2 = StringUtils.getRandomString(42);
// calculate the signature with different label, should fail to verify
byte[] sig = crypto.sign(label, inputBytes, privateKey);
assertFalse(crypto.verify(label2, inputBytes, publicKey, sig));
byte[] sig = sign(label, inputBytes, privateKey);
assertFalse(verify(label2, inputBytes, publicKey, sig));
}
}

View File

@@ -3,9 +3,8 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.junit.Test;
import java.util.HashSet;
@@ -14,25 +13,25 @@ import java.util.Set;
import static junit.framework.TestCase.assertTrue;
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
public class TagEncodingTest extends BrambleTestCase {
public class TagEncodingTest extends BrambleMockTestCase {
private final CryptoComponent crypto;
private final SecretKey tagKey;
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final TransportCrypto transportCrypto =
new TransportCryptoImpl(crypto);
private final SecretKey tagKey = getSecretKey();
private final long streamNumber = 1234567890;
public TagEncodingTest() {
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
tagKey = TestUtils.getSecretKey();
}
@Test
public void testKeyAffectsTag() throws Exception {
Set<Bytes> set = new HashSet<>();
for (int i = 0; i < 100; i++) {
byte[] tag = new byte[TAG_LENGTH];
SecretKey tagKey = TestUtils.getSecretKey();
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber);
SecretKey tagKey = getSecretKey();
transportCrypto.encodeTag(tag, tagKey, PROTOCOL_VERSION,
streamNumber);
assertTrue(set.add(new Bytes(tag)));
}
}
@@ -42,7 +41,8 @@ public class TagEncodingTest extends BrambleTestCase {
Set<Bytes> set = new HashSet<>();
for (int i = 0; i < 100; i++) {
byte[] tag = new byte[TAG_LENGTH];
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION + i, streamNumber);
transportCrypto.encodeTag(tag, tagKey, PROTOCOL_VERSION + i,
streamNumber);
assertTrue(set.add(new Bytes(tag)));
}
}
@@ -52,7 +52,8 @@ public class TagEncodingTest extends BrambleTestCase {
Set<Bytes> set = new HashSet<>();
for (int i = 0; i < 100; i++) {
byte[] tag = new byte[TAG_LENGTH];
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber + i);
transportCrypto.encodeTag(tag, tagKey, PROTOCOL_VERSION,
streamNumber + i);
assertTrue(set.add(new Bytes(tag)));
}
}

View File

@@ -0,0 +1,380 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.StringUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import static java.sql.Types.BINARY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public abstract class BasicDatabaseTest extends BrambleTestCase {
private static final int BATCH_SIZE = 100;
private final File testDir = TestUtils.getTestDirectory();
private final File db = new File(testDir, "db");
protected abstract String getBinaryType();
protected abstract String getDriverName();
protected abstract Connection openConnection(File db, boolean encrypt)
throws SQLException;
protected abstract void shutdownDatabase(File db, boolean encrypt)
throws SQLException;
@Before
public void setUp() throws Exception {
testDir.mkdirs();
Class.forName(getDriverName());
}
@Test
public void testInsertUpdateAndDelete() throws Exception {
Connection connection = openConnection(db, false);
try {
// Create the table
createTable(connection);
// Generate an ID and two names
byte[] id = TestUtils.getRandomId();
String oldName = StringUtils.getRandomString(50);
String newName = StringUtils.getRandomString(50);
// Insert the ID and old name into the table
insertRow(connection, id, oldName);
// Check that the old name can be retrieved using the ID
assertTrue(rowExists(connection, id));
assertEquals(oldName, getName(connection, id));
// Update the name
updateRow(connection, id, newName);
// Check that the new name can be retrieved using the ID
assertTrue(rowExists(connection, id));
assertEquals(newName, getName(connection, id));
// Delete the row from the table
assertTrue(deleteRow(connection, id));
// Check that the row no longer exists
assertFalse(rowExists(connection, id));
// Deleting the row again should have no effect
assertFalse(deleteRow(connection, id));
} finally {
connection.close();
shutdownDatabase(db, false);
}
}
@Test
public void testBatchInsertUpdateAndDelete() throws Exception {
Connection connection = openConnection(db, false);
try {
// Create the table
createTable(connection);
// Generate some IDs and two sets of names
byte[][] ids = new byte[BATCH_SIZE][];
String[] oldNames = new String[BATCH_SIZE];
String[] newNames = new String[BATCH_SIZE];
for (int i = 0; i < BATCH_SIZE; i++) {
ids[i] = TestUtils.getRandomId();
oldNames[i] = StringUtils.getRandomString(50);
newNames[i] = StringUtils.getRandomString(50);
}
// Insert the IDs and old names into the table as a batch
insertBatch(connection, ids, oldNames);
// Update the names as a batch
updateBatch(connection, ids, newNames);
// Check that the new names can be retrieved using the IDs
for (int i = 0; i < BATCH_SIZE; i++) {
assertTrue(rowExists(connection, ids[i]));
assertEquals(newNames[i], getName(connection, ids[i]));
}
// Delete the rows as a batch
boolean[] deleted = deleteBatch(connection, ids);
// Check that the rows no longer exist
for (int i = 0; i < BATCH_SIZE; i++) {
assertTrue(deleted[i]);
assertFalse(rowExists(connection, ids[i]));
}
// Deleting the rows again should have no effect
deleted = deleteBatch(connection, ids);
for (int i = 0; i < BATCH_SIZE; i++) assertFalse(deleted[i]);
} finally {
connection.close();
shutdownDatabase(db, false);
}
}
@Test
public void testSortOrder() throws Exception {
byte[] first = new byte[] {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
byte[] second = new byte[] {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 127
};
byte[] third = new byte[] {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, (byte) 255
};
Connection connection = openConnection(db, false);
try {
// Create the table
createTable(connection);
// Insert the rows
insertRow(connection, first, "first");
insertRow(connection, second, "second");
insertRow(connection, third, "third");
insertRow(connection, null, "null");
// Check the ordering of the < operator: null is not comparable
assertNull(getPredecessor(connection, first));
assertEquals("first", getPredecessor(connection, second));
assertEquals("second", getPredecessor(connection, third));
assertNull(getPredecessor(connection, null));
// Check the ordering of ORDER BY: nulls come first
List<String> names = getNames(connection);
assertEquals(4, names.size());
assertEquals("null", names.get(0));
assertEquals("first", names.get(1));
assertEquals("second", names.get(2));
assertEquals("third", names.get(3));
} finally {
connection.close();
shutdownDatabase(db, false);
}
}
@Test
public void testDataIsFoundWithoutEncryption() throws Exception {
testEncryption(false);
}
@Test
public void testDataIsNotFoundWithEncryption() throws Exception {
testEncryption(true);
}
private void testEncryption(boolean encrypt) throws Exception {
byte[] sequence = new byte[] {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
Connection connection = openConnection(db, encrypt);
try {
createTable(connection);
insertRow(connection, sequence, "abcdefg");
} finally {
connection.close();
shutdownDatabase(db, encrypt);
}
try {
if (findSequence(testDir, sequence) == encrypt) fail();
} finally {
shutdownDatabase(db, encrypt);
}
}
private void createTable(Connection connection) throws SQLException {
Statement s = connection.createStatement();
String sql = "CREATE TABLE foo (uniqueId " + getBinaryType() + ","
+ " name VARCHAR(100) NOT NULL)";
s.executeUpdate(sql);
s.close();
}
private void insertRow(Connection connection, byte[] id, String name)
throws SQLException {
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
PreparedStatement ps = connection.prepareStatement(sql);
if (id == null) ps.setNull(1, BINARY);
else ps.setBytes(1, id);
ps.setString(2, name);
int affected = ps.executeUpdate();
assertEquals(1, affected);
ps.close();
}
private boolean rowExists(Connection connection, byte[] id)
throws SQLException {
assertNotNull(id);
String sql = "SELECT * FROM foo WHERE uniqueID = ?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setBytes(1, id);
ResultSet rs = ps.executeQuery();
boolean found = rs.next();
assertFalse(rs.next());
rs.close();
ps.close();
return found;
}
private String getName(Connection connection, byte[] id)
throws SQLException {
assertNotNull(id);
String sql = "SELECT name FROM foo WHERE uniqueID = ?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setBytes(1, id);
ResultSet rs = ps.executeQuery();
assertTrue(rs.next());
String name = rs.getString(1);
assertFalse(rs.next());
rs.close();
ps.close();
return name;
}
private void updateRow(Connection connection, byte[] id, String name)
throws SQLException {
String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?";
PreparedStatement ps = connection.prepareStatement(sql);
if (id == null) ps.setNull(2, BINARY);
else ps.setBytes(2, id);
ps.setString(1, name);
assertEquals(1, ps.executeUpdate());
ps.close();
}
private boolean deleteRow(Connection connection, byte[] id)
throws SQLException {
String sql = "DELETE FROM foo WHERE uniqueId = ?";
PreparedStatement ps = connection.prepareStatement(sql);
if (id == null) ps.setNull(1, BINARY);
else ps.setBytes(1, id);
int affected = ps.executeUpdate();
ps.close();
return affected == 1;
}
private void insertBatch(Connection connection, byte[][] ids,
String[] names) throws SQLException {
assertEquals(ids.length, names.length);
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
PreparedStatement ps = connection.prepareStatement(sql);
for (int i = 0; i < ids.length; i++) {
if (ids[i] == null) ps.setNull(1, BINARY);
else ps.setBytes(1, ids[i]);
ps.setString(2, names[i]);
ps.addBatch();
}
int[] batchAffected = ps.executeBatch();
assertEquals(ids.length, batchAffected.length);
for (int affected : batchAffected) assertEquals(1, affected);
ps.close();
}
private void updateBatch(Connection connection, byte[][] ids,
String[] names) throws SQLException {
assertEquals(ids.length, names.length);
String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?";
PreparedStatement ps = connection.prepareStatement(sql);
for (int i = 0; i < ids.length; i++) {
if (ids[i] == null) ps.setNull(2, BINARY);
else ps.setBytes(2, ids[i]);
ps.setString(1, names[i]);
ps.addBatch();
}
int[] batchAffected = ps.executeBatch();
assertEquals(ids.length, batchAffected.length);
for (int affected : batchAffected) assertEquals(1, affected);
ps.close();
}
private boolean[] deleteBatch(Connection connection, byte[][] ids)
throws SQLException {
String sql = "DELETE FROM foo WHERE uniqueId = ?";
PreparedStatement ps = connection.prepareStatement(sql);
for (byte[] id : ids) {
if (id == null) ps.setNull(1, BINARY);
else ps.setBytes(1, id);
ps.addBatch();
}
int[] batchAffected = ps.executeBatch();
assertEquals(ids.length, batchAffected.length);
boolean[] ret = new boolean[ids.length];
for (int i = 0; i < batchAffected.length; i++)
ret[i] = batchAffected[i] == 1;
ps.close();
return ret;
}
private String getPredecessor(Connection connection, byte[] id)
throws SQLException {
String sql = "SELECT name FROM foo WHERE uniqueId < ?"
+ " ORDER BY uniqueId DESC";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setBytes(1, id);
ps.setMaxRows(1);
ResultSet rs = ps.executeQuery();
String name = rs.next() ? rs.getString(1) : null;
assertFalse(rs.next());
rs.close();
ps.close();
return name;
}
private List<String> getNames(Connection connection) throws SQLException {
String sql = "SELECT name FROM foo ORDER BY uniqueId NULLS FIRST";
List<String> names = new ArrayList<>();
PreparedStatement ps = connection.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
while (rs.next()) names.add(rs.getString(1));
rs.close();
ps.close();
return names;
}
private boolean findSequence(File f, byte[] sequence) throws IOException {
if (f.isDirectory()) {
File[] children = f.listFiles();
if (children != null)
for (File child : children)
if (findSequence(child, sequence)) return true;
return false;
} else if (f.isFile()) {
FileInputStream in = new FileInputStream(f);
try {
int offset = 0;
while (true) {
int read = in.read();
if (read == -1) return false;
if (((byte) read) == sequence[offset]) {
offset++;
if (offset == sequence.length) return true;
} else {
offset = 0;
}
}
} finally {
in.close();
}
} else {
return false;
}
}
@After
public void tearDown() throws Exception {
TestUtils.deleteTestDirectory(testDir);
}
}

View File

@@ -1,345 +1,46 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.StringUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import static java.sql.Types.BINARY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class BasicH2Test extends BasicDatabaseTest {
public class BasicH2Test extends BrambleTestCase {
private final SecretKey key = TestUtils.getSecretKey();
private static final String CREATE_TABLE =
"CREATE TABLE foo (uniqueId BINARY(32), name VARCHAR NOT NULL)";
private static final int BATCH_SIZE = 100;
private final File testDir = TestUtils.getTestDirectory();
private final File db = new File(testDir, "db");
private final String url = "jdbc:h2:" + db.getAbsolutePath();
private Connection connection = null;
@Before
public void setUp() throws Exception {
testDir.mkdirs();
Class.forName("org.h2.Driver");
connection = DriverManager.getConnection(url);
@Override
protected String getBinaryType() {
return "BINARY(32)";
}
@Test
public void testInsertUpdateAndDelete() throws Exception {
// Create the table
createTable(connection);
// Generate an ID and two names
byte[] id = TestUtils.getRandomId();
String oldName = StringUtils.getRandomString(50);
String newName = StringUtils.getRandomString(50);
// Insert the ID and old name into the table
insertRow(id, oldName);
// Check that the old name can be retrieved using the ID
assertTrue(rowExists(id));
assertEquals(oldName, getName(id));
// Update the name
updateRow(id, newName);
// Check that the new name can be retrieved using the ID
assertTrue(rowExists(id));
assertEquals(newName, getName(id));
// Delete the row from the table
assertTrue(deleteRow(id));
// Check that the row no longer exists
assertFalse(rowExists(id));
// Deleting the row again should have no effect
assertFalse(deleteRow(id));
@Override
protected String getDriverName() {
return "org.h2.Driver";
}
@Test
public void testBatchInsertUpdateAndDelete() throws Exception {
// Create the table
createTable(connection);
// Generate some IDs and two sets of names
byte[][] ids = new byte[BATCH_SIZE][];
String[] oldNames = new String[BATCH_SIZE];
String[] newNames = new String[BATCH_SIZE];
for (int i = 0; i < BATCH_SIZE; i++) {
ids[i] = TestUtils.getRandomId();
oldNames[i] = StringUtils.getRandomString(50);
newNames[i] = StringUtils.getRandomString(50);
@Override
protected Connection openConnection(File db, boolean encrypt)
throws SQLException {
String url = "jdbc:h2:split:" + db.getAbsolutePath();
Properties props = new Properties();
props.setProperty("user", "user");
if (encrypt) {
url += ";CIPHER=AES";
String hex = StringUtils.toHexString(key.getBytes());
props.setProperty("password", hex + " password");
}
// Insert the IDs and old names into the table as a batch
insertBatch(ids, oldNames);
// Update the names as a batch
updateBatch(ids, newNames);
// Check that the new names can be retrieved using the IDs
for (int i = 0; i < BATCH_SIZE; i++) {
assertTrue(rowExists(ids[i]));
assertEquals(newNames[i], getName(ids[i]));
}
// Delete the rows as a batch
boolean[] deleted = deleteBatch(ids);
// Check that the rows no longer exist
for (int i = 0; i < BATCH_SIZE; i++) {
assertTrue(deleted[i]);
assertFalse(rowExists(ids[i]));
}
// Deleting the rows again should have no effect
deleted = deleteBatch(ids);
for (int i = 0; i < BATCH_SIZE; i++) assertFalse(deleted[i]);
return DriverManager.getConnection(url, props);
}
@Test
public void testSortOrder() throws Exception {
byte[] first = new byte[] {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
byte[] second = new byte[] {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 127
};
byte[] third = new byte[] {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, (byte) 255
};
// Create the table
createTable(connection);
// Insert the rows
insertRow(first, "first");
insertRow(second, "second");
insertRow(third, "third");
insertRow(null, "null");
// Check the ordering of the < operator: the null ID is not comparable
assertNull(getPredecessor(first));
assertEquals("first", getPredecessor(second));
assertEquals("second", getPredecessor(third));
assertNull(getPredecessor(null));
// Check the ordering of ORDER BY: nulls come first
List<String> names = getNames();
assertEquals(4, names.size());
assertEquals("null", names.get(0));
assertEquals("first", names.get(1));
assertEquals("second", names.get(2));
assertEquals("third", names.get(3));
}
private void createTable(Connection connection) throws SQLException {
try {
Statement s = connection.createStatement();
s.executeUpdate(CREATE_TABLE);
s.close();
} catch (SQLException e) {
connection.close();
throw e;
}
}
private void insertRow(byte[] id, String name) throws SQLException {
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
try {
PreparedStatement ps = connection.prepareStatement(sql);
if (id == null) ps.setNull(1, BINARY);
else ps.setBytes(1, id);
ps.setString(2, name);
int affected = ps.executeUpdate();
assertEquals(1, affected);
ps.close();
} catch (SQLException e) {
connection.close();
throw e;
}
}
private boolean rowExists(byte[] id) throws SQLException {
assertNotNull(id);
String sql = "SELECT NULL FROM foo WHERE uniqueID = ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
ps.setBytes(1, id);
ResultSet rs = ps.executeQuery();
boolean found = rs.next();
assertFalse(rs.next());
rs.close();
ps.close();
return found;
} catch (SQLException e) {
connection.close();
throw e;
}
}
private String getName(byte[] id) throws SQLException {
assertNotNull(id);
String sql = "SELECT name FROM foo WHERE uniqueID = ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
ps.setBytes(1, id);
ResultSet rs = ps.executeQuery();
assertTrue(rs.next());
String name = rs.getString(1);
assertFalse(rs.next());
rs.close();
ps.close();
return name;
} catch (SQLException e) {
connection.close();
throw e;
}
}
private void updateRow(byte[] id, String name) throws SQLException {
String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
if (id == null) ps.setNull(2, BINARY);
else ps.setBytes(2, id);
ps.setString(1, name);
assertEquals(1, ps.executeUpdate());
ps.close();
} catch (SQLException e) {
connection.close();
throw e;
}
}
private boolean deleteRow(byte[] id) throws SQLException {
String sql = "DELETE FROM foo WHERE uniqueId = ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
if (id == null) ps.setNull(1, BINARY);
else ps.setBytes(1, id);
int affected = ps.executeUpdate();
ps.close();
return affected == 1;
} catch (SQLException e) {
connection.close();
throw e;
}
}
private void insertBatch(byte[][] ids, String[] names) throws SQLException {
assertEquals(ids.length, names.length);
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
try {
PreparedStatement ps = connection.prepareStatement(sql);
for (int i = 0; i < ids.length; i++) {
if (ids[i] == null) ps.setNull(1, BINARY);
else ps.setBytes(1, ids[i]);
ps.setString(2, names[i]);
ps.addBatch();
}
int[] batchAffected = ps.executeBatch();
assertEquals(ids.length, batchAffected.length);
for (int affected : batchAffected) assertEquals(1, affected);
ps.close();
} catch (SQLException e) {
connection.close();
throw e;
}
}
private void updateBatch(byte[][] ids, String[] names) throws SQLException {
assertEquals(ids.length, names.length);
String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
for (int i = 0; i < ids.length; i++) {
if (ids[i] == null) ps.setNull(2, BINARY);
else ps.setBytes(2, ids[i]);
ps.setString(1, names[i]);
ps.addBatch();
}
int[] batchAffected = ps.executeBatch();
assertEquals(ids.length, batchAffected.length);
for (int affected : batchAffected) assertEquals(1, affected);
ps.close();
} catch (SQLException e) {
connection.close();
throw e;
}
}
private boolean[] deleteBatch(byte[][] ids) throws SQLException {
String sql = "DELETE FROM foo WHERE uniqueId = ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
for (byte[] id : ids) {
if (id == null) ps.setNull(1, BINARY);
else ps.setBytes(1, id);
ps.addBatch();
}
int[] batchAffected = ps.executeBatch();
assertEquals(ids.length, batchAffected.length);
boolean[] ret = new boolean[ids.length];
for (int i = 0; i < batchAffected.length; i++)
ret[i] = batchAffected[i] == 1;
ps.close();
return ret;
} catch (SQLException e) {
connection.close();
throw e;
}
}
private String getPredecessor(byte[] id) throws SQLException {
String sql = "SELECT name FROM foo WHERE uniqueId < ?"
+ " ORDER BY uniqueId DESC LIMIT ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
ps.setBytes(1, id);
ps.setInt(2, 1);
ResultSet rs = ps.executeQuery();
String name = rs.next() ? rs.getString(1) : null;
assertFalse(rs.next());
rs.close();
ps.close();
return name;
} catch (SQLException e) {
connection.close();
throw e;
}
}
private List<String> getNames() throws SQLException {
String sql = "SELECT name FROM foo ORDER BY uniqueId";
List<String> names = new ArrayList<>();
try {
PreparedStatement ps = connection.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
while (rs.next()) names.add(rs.getString(1));
rs.close();
ps.close();
return names;
} catch (SQLException e) {
connection.close();
throw e;
}
}
@After
public void tearDown() throws Exception {
if (connection != null) connection.close();
TestUtils.deleteTestDirectory(testDir);
@Override
protected void shutdownDatabase(File db, boolean encrypt)
throws SQLException {
// The DB is closed automatically when the connection is closed
}
}

View File

@@ -0,0 +1,48 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.StringUtils;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class BasicHyperSqlTest extends BasicDatabaseTest {
private final SecretKey key = TestUtils.getSecretKey();
@Override
protected String getBinaryType() {
return "BINARY(32)";
}
@Override
protected String getDriverName() {
return "org.hsqldb.jdbc.JDBCDriver";
}
@Override
protected Connection openConnection(File db, boolean encrypt)
throws SQLException {
String url = "jdbc:hsqldb:file:" + db.getAbsolutePath() +
";sql.enforce_size=false;allow_empty_batch=true";
if (encrypt) {
String hex = StringUtils.toHexString(key.getBytes());
url += ";encrypt_lobs=true;crypt_type=AES;crypt_key=" + hex;
}
return DriverManager.getConnection(url);
}
@Override
protected void shutdownDatabase(File db, boolean encrypt)
throws SQLException {
Connection c = openConnection(db, encrypt);
Statement s = c.createStatement();
s.executeQuery("SHUTDOWN");
s.close();
c.close();
}
}

View File

@@ -19,7 +19,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class TransactionIsolationTest extends BrambleTestCase {
public class H2TransactionIsolationTest extends BrambleTestCase {
private static final String DROP_TABLE = "DROP TABLE foo IF EXISTS";
private static final String CREATE_TABLE = "CREATE TABLE foo"
@@ -34,9 +34,9 @@ public class TransactionIsolationTest extends BrambleTestCase {
private final File testDir = TestUtils.getTestDirectory();
private final File db = new File(testDir, "db");
private final String withMvcc = "jdbc:h2:" + db.getAbsolutePath()
private final String withMvcc = "jdbc:h2:split:" + db.getAbsolutePath()
+ ";MV_STORE=TRUE;MVCC=TRUE";
private final String withoutMvcc = "jdbc:h2:" + db.getAbsolutePath()
private final String withoutMvcc = "jdbc:h2:split:" + db.getAbsolutePath()
+ ";MV_STORE=FALSE;MVCC=FALSE;LOCK_MODE=1";
@Before

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.system.Clock;
public class HyperSqlDatabaseTest extends JdbcDatabaseTest {
public HyperSqlDatabaseTest() throws Exception {
super();
}
@Override
protected JdbcDatabase createDatabase(DatabaseConfig config, Clock clock) {
return new HyperSqlDatabase(config, clock);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,14 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.keyagreement.Payload;
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestUtils;
import org.jmock.Expectations;
import org.jmock.auto.Mock;
import org.jmock.integration.junit4.JUnitRuleMockery;
@@ -16,6 +17,11 @@ import org.junit.Rule;
import org.junit.Test;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_SECRET_LABEL;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
@@ -28,34 +34,33 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
setImposteriser(ClassImposteriser.INSTANCE);
}};
private static final byte[] ALICE_PUBKEY = TestUtils.getRandomBytes(32);
private static final byte[] ALICE_COMMIT =
TestUtils.getRandomBytes(COMMIT_LENGTH);
private static final byte[] ALICE_PAYLOAD =
TestUtils.getRandomBytes(COMMIT_LENGTH + 8);
private final PublicKey alicePubKey =
context.mock(PublicKey.class, "alice");
private final byte[] alicePubKeyBytes = getRandomBytes(32);
private final byte[] aliceCommit = getRandomBytes(COMMIT_LENGTH);
private final byte[] alicePayload = getRandomBytes(COMMIT_LENGTH + 8);
private final byte[] aliceConfirm = getRandomBytes(SecretKey.LENGTH);
private static final byte[] BOB_PUBKEY = TestUtils.getRandomBytes(32);
private static final byte[] BOB_COMMIT =
TestUtils.getRandomBytes(COMMIT_LENGTH);
private static final byte[] BOB_PAYLOAD =
TestUtils.getRandomBytes(COMMIT_LENGTH + 19);
private final PublicKey bobPubKey = context.mock(PublicKey.class, "bob");
private final byte[] bobPubKeyBytes = getRandomBytes(32);
private final byte[] bobCommit = getRandomBytes(COMMIT_LENGTH);
private final byte[] bobPayload = getRandomBytes(COMMIT_LENGTH + 19);
private final byte[] bobConfirm = getRandomBytes(SecretKey.LENGTH);
private static final byte[] ALICE_CONFIRM =
TestUtils.getRandomBytes(SecretKey.LENGTH);
private static final byte[] BOB_CONFIRM =
TestUtils.getRandomBytes(SecretKey.LENGTH);
private static final byte[] BAD_PUBKEY = TestUtils.getRandomBytes(32);
private static final byte[] BAD_COMMIT =
TestUtils.getRandomBytes(COMMIT_LENGTH);
private static final byte[] BAD_CONFIRM =
TestUtils.getRandomBytes(SecretKey.LENGTH);
private final PublicKey badPubKey = context.mock(PublicKey.class, "bad");
private final byte[] badPubKeyBytes = getRandomBytes(32);
private final byte[] badCommit = getRandomBytes(COMMIT_LENGTH);
private final byte[] badConfirm = getRandomBytes(SecretKey.LENGTH);
@Mock
KeyAgreementProtocol.Callbacks callbacks;
@Mock
CryptoComponent crypto;
@Mock
KeyAgreementCrypto keyAgreementCrypto;
@Mock
KeyParser keyParser;
@Mock
PayloadEncoder payloadEncoder;
@Mock
KeyAgreementTransport transport;
@@ -65,60 +70,72 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
@Test
public void testAliceProtocol() throws Exception {
// set up
Payload theirPayload = new Payload(BOB_COMMIT, null);
Payload ourPayload = new Payload(ALICE_COMMIT, null);
Payload theirPayload = new Payload(bobCommit, null);
Payload ourPayload = new Payload(aliceCommit, null);
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
SecretKey sharedSecret = TestUtils.getSecretKey();
SecretKey masterSecret = TestUtils.getSecretKey();
SecretKey sharedSecret = getSecretKey();
SecretKey masterSecret = getSecretKey();
KeyAgreementProtocol protocol =
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
transport, theirPayload, ourPayload, ourKeyPair, true);
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
crypto, keyAgreementCrypto, payloadEncoder, transport,
theirPayload, ourPayload, ourKeyPair, true);
// expectations
context.checking(new Expectations() {{
// Helpers
allowing(payloadEncoder).encode(ourPayload);
will(returnValue(ALICE_PAYLOAD));
will(returnValue(alicePayload));
allowing(payloadEncoder).encode(theirPayload);
will(returnValue(BOB_PAYLOAD));
will(returnValue(bobPayload));
allowing(ourPubKey).getEncoded();
will(returnValue(ALICE_PUBKEY));
will(returnValue(alicePubKeyBytes));
allowing(crypto).getAgreementKeyParser();
will(returnValue(keyParser));
allowing(alicePubKey).getEncoded();
will(returnValue(alicePubKeyBytes));
allowing(bobPubKey).getEncoded();
will(returnValue(bobPubKeyBytes));
// Alice sends her public key
oneOf(transport).sendKey(ALICE_PUBKEY);
oneOf(transport).sendKey(alicePubKeyBytes);
// Alice receives Bob's public key
oneOf(callbacks).connectionWaiting();
oneOf(transport).receiveKey();
will(returnValue(BOB_PUBKEY));
will(returnValue(bobPubKeyBytes));
oneOf(callbacks).initialRecordReceived();
oneOf(keyParser).parsePublicKey(bobPubKeyBytes);
will(returnValue(bobPubKey));
// Alice verifies Bob's public key
oneOf(crypto).deriveKeyCommitment(BOB_PUBKEY);
will(returnValue(BOB_COMMIT));
oneOf(keyAgreementCrypto).deriveKeyCommitment(bobPubKey);
will(returnValue(bobCommit));
// Alice computes shared secret
oneOf(crypto).deriveSharedSecret(BOB_PUBKEY, ourKeyPair, true);
oneOf(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, bobPubKey,
ourKeyPair, new byte[] {PROTOCOL_VERSION},
alicePubKeyBytes, bobPubKeyBytes);
will(returnValue(sharedSecret));
// Alice sends her confirmation record
oneOf(crypto).deriveConfirmationRecord(sharedSecret, BOB_PAYLOAD,
ALICE_PAYLOAD, BOB_PUBKEY, ourKeyPair, true, true);
will(returnValue(ALICE_CONFIRM));
oneOf(transport).sendConfirm(ALICE_CONFIRM);
oneOf(keyAgreementCrypto).deriveConfirmationRecord(sharedSecret,
bobPayload, alicePayload, bobPubKey, ourKeyPair,
true, true);
will(returnValue(aliceConfirm));
oneOf(transport).sendConfirm(aliceConfirm);
// Alice receives Bob's confirmation record
oneOf(transport).receiveConfirm();
will(returnValue(BOB_CONFIRM));
will(returnValue(bobConfirm));
// Alice verifies Bob's confirmation record
oneOf(crypto).deriveConfirmationRecord(sharedSecret, BOB_PAYLOAD,
ALICE_PAYLOAD, BOB_PUBKEY, ourKeyPair, true, false);
will(returnValue(BOB_CONFIRM));
oneOf(keyAgreementCrypto).deriveConfirmationRecord(sharedSecret,
bobPayload, alicePayload, bobPubKey, ourKeyPair,
true, false);
will(returnValue(bobConfirm));
// Alice computes master secret
oneOf(crypto).deriveMasterSecret(sharedSecret);
oneOf(crypto).deriveKey(MASTER_SECRET_LABEL, sharedSecret);
will(returnValue(masterSecret));
}});
@@ -129,59 +146,71 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
@Test
public void testBobProtocol() throws Exception {
// set up
Payload theirPayload = new Payload(ALICE_COMMIT, null);
Payload ourPayload = new Payload(BOB_COMMIT, null);
Payload theirPayload = new Payload(aliceCommit, null);
Payload ourPayload = new Payload(bobCommit, null);
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
SecretKey sharedSecret = TestUtils.getSecretKey();
SecretKey masterSecret = TestUtils.getSecretKey();
SecretKey sharedSecret = getSecretKey();
SecretKey masterSecret = getSecretKey();
KeyAgreementProtocol protocol =
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
transport, theirPayload, ourPayload, ourKeyPair, false);
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
crypto, keyAgreementCrypto, payloadEncoder, transport,
theirPayload, ourPayload, ourKeyPair, false);
// expectations
context.checking(new Expectations() {{
// Helpers
allowing(payloadEncoder).encode(ourPayload);
will(returnValue(BOB_PAYLOAD));
will(returnValue(bobPayload));
allowing(payloadEncoder).encode(theirPayload);
will(returnValue(ALICE_PAYLOAD));
will(returnValue(alicePayload));
allowing(ourPubKey).getEncoded();
will(returnValue(BOB_PUBKEY));
will(returnValue(bobPubKeyBytes));
allowing(crypto).getAgreementKeyParser();
will(returnValue(keyParser));
allowing(alicePubKey).getEncoded();
will(returnValue(alicePubKeyBytes));
allowing(bobPubKey).getEncoded();
will(returnValue(bobPubKeyBytes));
// Bob receives Alice's public key
oneOf(transport).receiveKey();
will(returnValue(ALICE_PUBKEY));
will(returnValue(alicePubKeyBytes));
oneOf(callbacks).initialRecordReceived();
oneOf(keyParser).parsePublicKey(alicePubKeyBytes);
will(returnValue(alicePubKey));
// Bob verifies Alice's public key
oneOf(crypto).deriveKeyCommitment(ALICE_PUBKEY);
will(returnValue(ALICE_COMMIT));
oneOf(keyAgreementCrypto).deriveKeyCommitment(alicePubKey);
will(returnValue(aliceCommit));
// Bob sends his public key
oneOf(transport).sendKey(BOB_PUBKEY);
oneOf(transport).sendKey(bobPubKeyBytes);
// Bob computes shared secret
oneOf(crypto).deriveSharedSecret(ALICE_PUBKEY, ourKeyPair, false);
oneOf(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, alicePubKey,
ourKeyPair, new byte[] {PROTOCOL_VERSION},
alicePubKeyBytes, bobPubKeyBytes);
will(returnValue(sharedSecret));
// Bob receives Alices's confirmation record
oneOf(transport).receiveConfirm();
will(returnValue(ALICE_CONFIRM));
will(returnValue(aliceConfirm));
// Bob verifies Alice's confirmation record
oneOf(crypto).deriveConfirmationRecord(sharedSecret, ALICE_PAYLOAD,
BOB_PAYLOAD, ALICE_PUBKEY, ourKeyPair, false, true);
will(returnValue(ALICE_CONFIRM));
oneOf(keyAgreementCrypto).deriveConfirmationRecord(sharedSecret,
alicePayload, bobPayload, alicePubKey, ourKeyPair,
false, true);
will(returnValue(aliceConfirm));
// Bob sends his confirmation record
oneOf(crypto).deriveConfirmationRecord(sharedSecret, ALICE_PAYLOAD,
BOB_PAYLOAD, ALICE_PUBKEY, ourKeyPair, false, false);
will(returnValue(BOB_CONFIRM));
oneOf(transport).sendConfirm(BOB_CONFIRM);
oneOf(keyAgreementCrypto).deriveConfirmationRecord(sharedSecret,
alicePayload, bobPayload, alicePubKey, ourKeyPair,
false, false);
will(returnValue(bobConfirm));
oneOf(transport).sendConfirm(bobConfirm);
// Bob computes master secret
oneOf(crypto).deriveMasterSecret(sharedSecret);
oneOf(crypto).deriveKey(MASTER_SECRET_LABEL, sharedSecret);
will(returnValue(masterSecret));
}});
@@ -192,38 +221,44 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
@Test(expected = AbortException.class)
public void testAliceProtocolAbortOnBadKey() throws Exception {
// set up
Payload theirPayload = new Payload(BOB_COMMIT, null);
Payload ourPayload = new Payload(ALICE_COMMIT, null);
Payload theirPayload = new Payload(bobCommit, null);
Payload ourPayload = new Payload(aliceCommit, null);
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
KeyAgreementProtocol protocol =
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
transport, theirPayload, ourPayload, ourKeyPair, true);
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
crypto, keyAgreementCrypto, payloadEncoder, transport,
theirPayload, ourPayload, ourKeyPair, true);
// expectations
context.checking(new Expectations() {{
// Helpers
allowing(ourPubKey).getEncoded();
will(returnValue(ALICE_PUBKEY));
will(returnValue(alicePubKeyBytes));
allowing(crypto).getAgreementKeyParser();
will(returnValue(keyParser));
// Alice sends her public key
oneOf(transport).sendKey(ALICE_PUBKEY);
oneOf(transport).sendKey(alicePubKeyBytes);
// Alice receives a bad public key
oneOf(callbacks).connectionWaiting();
oneOf(transport).receiveKey();
will(returnValue(BAD_PUBKEY));
will(returnValue(badPubKeyBytes));
oneOf(callbacks).initialRecordReceived();
oneOf(keyParser).parsePublicKey(badPubKeyBytes);
will(returnValue(badPubKey));
// Alice verifies Bob's public key
oneOf(crypto).deriveKeyCommitment(BAD_PUBKEY);
will(returnValue(BAD_COMMIT));
oneOf(keyAgreementCrypto).deriveKeyCommitment(badPubKey);
will(returnValue(badCommit));
// Alice aborts
oneOf(transport).sendAbort(false);
// Alice never computes shared secret
never(crypto).deriveSharedSecret(BAD_PUBKEY, ourKeyPair, true);
never(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, badPubKey,
ourKeyPair, new byte[] {PROTOCOL_VERSION},
alicePubKeyBytes, bobPubKeyBytes);
}});
// execute
@@ -233,34 +268,38 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
@Test(expected = AbortException.class)
public void testBobProtocolAbortOnBadKey() throws Exception {
// set up
Payload theirPayload = new Payload(ALICE_COMMIT, null);
Payload ourPayload = new Payload(BOB_COMMIT, null);
Payload theirPayload = new Payload(aliceCommit, null);
Payload ourPayload = new Payload(bobCommit, null);
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
KeyAgreementProtocol protocol =
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
transport, theirPayload, ourPayload, ourKeyPair, false);
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
crypto, keyAgreementCrypto, payloadEncoder, transport,
theirPayload, ourPayload, ourKeyPair, false);
// expectations
context.checking(new Expectations() {{
// Helpers
allowing(ourPubKey).getEncoded();
will(returnValue(BOB_PUBKEY));
will(returnValue(bobPubKeyBytes));
allowing(crypto).getAgreementKeyParser();
will(returnValue(keyParser));
// Bob receives a bad public key
oneOf(transport).receiveKey();
will(returnValue(BAD_PUBKEY));
will(returnValue(badPubKeyBytes));
oneOf(callbacks).initialRecordReceived();
oneOf(keyParser).parsePublicKey(badPubKeyBytes);
will(returnValue(badPubKey));
// Bob verifies Alice's public key
oneOf(crypto).deriveKeyCommitment(BAD_PUBKEY);
will(returnValue(BAD_COMMIT));
oneOf(keyAgreementCrypto).deriveKeyCommitment(badPubKey);
will(returnValue(badCommit));
// Bob aborts
oneOf(transport).sendAbort(false);
// Bob never sends his public key
never(transport).sendKey(BOB_PUBKEY);
never(transport).sendKey(bobPubKeyBytes);
}});
// execute
@@ -270,62 +309,72 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
@Test(expected = AbortException.class)
public void testAliceProtocolAbortOnBadConfirm() throws Exception {
// set up
Payload theirPayload = new Payload(BOB_COMMIT, null);
Payload ourPayload = new Payload(ALICE_COMMIT, null);
Payload theirPayload = new Payload(bobCommit, null);
Payload ourPayload = new Payload(aliceCommit, null);
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
SecretKey sharedSecret = TestUtils.getSecretKey();
SecretKey sharedSecret = getSecretKey();
KeyAgreementProtocol protocol =
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
transport, theirPayload, ourPayload, ourKeyPair, true);
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
crypto, keyAgreementCrypto, payloadEncoder, transport,
theirPayload, ourPayload, ourKeyPair, true);
// expectations
context.checking(new Expectations() {{
// Helpers
allowing(payloadEncoder).encode(ourPayload);
will(returnValue(ALICE_PAYLOAD));
will(returnValue(alicePayload));
allowing(payloadEncoder).encode(theirPayload);
will(returnValue(BOB_PAYLOAD));
will(returnValue(bobPayload));
allowing(ourPubKey).getEncoded();
will(returnValue(ALICE_PUBKEY));
will(returnValue(alicePubKeyBytes));
allowing(crypto).getAgreementKeyParser();
will(returnValue(keyParser));
allowing(bobPubKey).getEncoded();
will(returnValue(bobPubKeyBytes));
// Alice sends her public key
oneOf(transport).sendKey(ALICE_PUBKEY);
oneOf(transport).sendKey(alicePubKeyBytes);
// Alice receives Bob's public key
oneOf(callbacks).connectionWaiting();
oneOf(transport).receiveKey();
will(returnValue(BOB_PUBKEY));
will(returnValue(bobPubKeyBytes));
oneOf(callbacks).initialRecordReceived();
oneOf(keyParser).parsePublicKey(bobPubKeyBytes);
will(returnValue(bobPubKey));
// Alice verifies Bob's public key
oneOf(crypto).deriveKeyCommitment(BOB_PUBKEY);
will(returnValue(BOB_COMMIT));
oneOf(keyAgreementCrypto).deriveKeyCommitment(bobPubKey);
will(returnValue(bobCommit));
// Alice computes shared secret
oneOf(crypto).deriveSharedSecret(BOB_PUBKEY, ourKeyPair, true);
oneOf(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, bobPubKey,
ourKeyPair, new byte[] {PROTOCOL_VERSION},
alicePubKeyBytes, bobPubKeyBytes);
will(returnValue(sharedSecret));
// Alice sends her confirmation record
oneOf(crypto).deriveConfirmationRecord(sharedSecret, BOB_PAYLOAD,
ALICE_PAYLOAD, BOB_PUBKEY, ourKeyPair, true, true);
will(returnValue(ALICE_CONFIRM));
oneOf(transport).sendConfirm(ALICE_CONFIRM);
oneOf(keyAgreementCrypto).deriveConfirmationRecord(sharedSecret,
bobPayload, alicePayload, bobPubKey, ourKeyPair,
true, true);
will(returnValue(aliceConfirm));
oneOf(transport).sendConfirm(aliceConfirm);
// Alice receives a bad confirmation record
oneOf(transport).receiveConfirm();
will(returnValue(BAD_CONFIRM));
will(returnValue(badConfirm));
// Alice verifies Bob's confirmation record
oneOf(crypto).deriveConfirmationRecord(sharedSecret, BOB_PAYLOAD,
ALICE_PAYLOAD, BOB_PUBKEY, ourKeyPair, true, false);
will(returnValue(BOB_CONFIRM));
oneOf(keyAgreementCrypto).deriveConfirmationRecord(sharedSecret,
bobPayload, alicePayload, bobPubKey, ourKeyPair,
true, false);
will(returnValue(bobConfirm));
// Alice aborts
oneOf(transport).sendAbort(false);
// Alice never computes master secret
never(crypto).deriveMasterSecret(sharedSecret);
never(crypto).deriveKey(MASTER_SECRET_LABEL, sharedSecret);
}});
// execute
@@ -335,56 +384,66 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
@Test(expected = AbortException.class)
public void testBobProtocolAbortOnBadConfirm() throws Exception {
// set up
Payload theirPayload = new Payload(ALICE_COMMIT, null);
Payload ourPayload = new Payload(BOB_COMMIT, null);
Payload theirPayload = new Payload(aliceCommit, null);
Payload ourPayload = new Payload(bobCommit, null);
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
SecretKey sharedSecret = TestUtils.getSecretKey();
SecretKey sharedSecret = getSecretKey();
KeyAgreementProtocol protocol =
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
transport, theirPayload, ourPayload, ourKeyPair, false);
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
crypto, keyAgreementCrypto, payloadEncoder, transport,
theirPayload, ourPayload, ourKeyPair, false);
// expectations
context.checking(new Expectations() {{
// Helpers
allowing(payloadEncoder).encode(ourPayload);
will(returnValue(BOB_PAYLOAD));
will(returnValue(bobPayload));
allowing(payloadEncoder).encode(theirPayload);
will(returnValue(ALICE_PAYLOAD));
will(returnValue(alicePayload));
allowing(ourPubKey).getEncoded();
will(returnValue(BOB_PUBKEY));
will(returnValue(bobPubKeyBytes));
allowing(crypto).getAgreementKeyParser();
will(returnValue(keyParser));
allowing(alicePubKey).getEncoded();
will(returnValue(alicePubKeyBytes));
// Bob receives Alice's public key
oneOf(transport).receiveKey();
will(returnValue(ALICE_PUBKEY));
will(returnValue(alicePubKeyBytes));
oneOf(callbacks).initialRecordReceived();
oneOf(keyParser).parsePublicKey(alicePubKeyBytes);
will(returnValue(alicePubKey));
// Bob verifies Alice's public key
oneOf(crypto).deriveKeyCommitment(ALICE_PUBKEY);
will(returnValue(ALICE_COMMIT));
oneOf(keyAgreementCrypto).deriveKeyCommitment(alicePubKey);
will(returnValue(aliceCommit));
// Bob sends his public key
oneOf(transport).sendKey(BOB_PUBKEY);
oneOf(transport).sendKey(bobPubKeyBytes);
// Bob computes shared secret
oneOf(crypto).deriveSharedSecret(ALICE_PUBKEY, ourKeyPair, false);
oneOf(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, alicePubKey,
ourKeyPair, new byte[] {PROTOCOL_VERSION},
alicePubKeyBytes, bobPubKeyBytes);
will(returnValue(sharedSecret));
// Bob receives a bad confirmation record
oneOf(transport).receiveConfirm();
will(returnValue(BAD_CONFIRM));
will(returnValue(badConfirm));
// Bob verifies Alice's confirmation record
oneOf(crypto).deriveConfirmationRecord(sharedSecret, ALICE_PAYLOAD,
BOB_PAYLOAD, ALICE_PUBKEY, ourKeyPair, false, true);
will(returnValue(ALICE_CONFIRM));
oneOf(keyAgreementCrypto).deriveConfirmationRecord(sharedSecret,
alicePayload, bobPayload, alicePubKey, ourKeyPair,
false, true);
will(returnValue(aliceConfirm));
// Bob aborts
oneOf(transport).sendAbort(false);
// Bob never sends his confirmation record
never(crypto).deriveConfirmationRecord(sharedSecret, ALICE_PAYLOAD,
BOB_PAYLOAD, ALICE_PUBKEY, ourKeyPair, false, false);
never(keyAgreementCrypto).deriveConfirmationRecord(sharedSecret,
alicePayload, bobPayload, alicePubKey, ourKeyPair,
false, false);
}});
// execute

View File

@@ -150,17 +150,14 @@ public class LanTcpPluginTest extends BrambleTestCase {
int port = ss.getLocalPort();
CountDownLatch latch = new CountDownLatch(1);
AtomicBoolean error = new AtomicBoolean(false);
new Thread() {
@Override
public void run() {
try {
ss.accept();
latch.countDown();
} catch (IOException e) {
error.set(true);
}
new Thread(() -> {
try {
ss.accept();
latch.countDown();
} catch (IOException e) {
error.set(true);
}
}.start();
}).start();
// Tell the plugin about the port
TransportProperties p = new TransportProperties();
p.put("ipPorts", addrString + ":" + port);
@@ -243,17 +240,14 @@ public class LanTcpPluginTest extends BrambleTestCase {
ss.bind(new InetSocketAddress(addrString, 0), 10);
CountDownLatch latch = new CountDownLatch(1);
AtomicBoolean error = new AtomicBoolean(false);
new Thread() {
@Override
public void run() {
try {
ss.accept();
latch.countDown();
} catch (IOException e) {
error.set(true);
}
new Thread(() -> {
try {
ss.accept();
latch.countDown();
} catch (IOException e) {
error.set(true);
}
}.start();
}).start();
// Tell the plugin about the port
BdfList descriptor = new BdfList();
descriptor.add(TRANSPORT_ID_LAN);

View File

@@ -34,6 +34,7 @@ import java.util.Map;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_VERSION;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
@@ -78,7 +79,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
private TransportPropertyManagerImpl createInstance() {
context.checking(new Expectations() {{
oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID);
oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID,
CLIENT_VERSION);
will(returnValue(localGroup));
}});
return new TransportPropertyManagerImpl(db, clientHelper,
@@ -94,18 +96,18 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
Group contactGroup1 = getGroup(), contactGroup2 = getGroup();
context.checking(new Expectations() {{
oneOf(db).containsGroup(txn, localGroup.getId());
will(returnValue(false));
oneOf(db).addGroup(txn, localGroup);
oneOf(db).getContacts(txn);
will(returnValue(contacts));
// The first contact's group has already been set up
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact1);
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact1);
will(returnValue(contactGroup1));
oneOf(db).containsGroup(txn, contactGroup1.getId());
will(returnValue(true));
// The second contact's group hasn't been set up
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact2);
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact2);
will(returnValue(contactGroup2));
oneOf(db).containsGroup(txn, contactGroup2.getId());
will(returnValue(false));
@@ -125,28 +127,15 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
}
@Test
public void testDoesNotCreateGroupsAtStartupIfAlreadyCreated()
throws Exception {
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).containsGroup(txn, localGroup.getId());
will(returnValue(true));
}});
TransportPropertyManagerImpl t = createInstance();
t.createLocalState(txn);
}
@Test
public void testCreatesContactGroupWhenAddingContact() throws Exception {
public void testCreatesGroupWhenAddingContact() throws Exception {
Transaction txn = new Transaction(null, false);
Contact contact = getContact(true);
Group contactGroup = getGroup();
context.checking(new Expectations() {{
// Create the group and share it with the contact
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact);
will(returnValue(contactGroup));
oneOf(db).containsGroup(txn, contactGroup.getId());
will(returnValue(false));
@@ -172,7 +161,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
Group contactGroup = getGroup();
context.checking(new Expectations() {{
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact);
will(returnValue(contactGroup));
oneOf(db).removeGroup(txn, contactGroup);
}});
@@ -231,6 +221,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
long timestamp = 123456789;
Message message = getMessage(contactGroupId, timestamp);
Metadata meta = new Metadata();
// Version 4 is being delivered
BdfDictionary metaDictionary = BdfDictionary.of(
new BdfEntry("transportId", "foo"),
new BdfEntry("version", 4),
@@ -238,19 +229,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
);
Map<MessageId, BdfDictionary> messageMetadata =
new LinkedHashMap<>();
// Old remote updates for the same transport should be deleted
MessageId fooVersion2 = new MessageId(getRandomId());
messageMetadata.put(fooVersion2, BdfDictionary.of(
new BdfEntry("transportId", "foo"),
new BdfEntry("version", 2),
new BdfEntry("local", false)
));
MessageId fooVersion1 = new MessageId(getRandomId());
messageMetadata.put(fooVersion1, BdfDictionary.of(
new BdfEntry("transportId", "foo"),
new BdfEntry("version", 1),
new BdfEntry("local", false)
));
// An older remote update for the same transport should be deleted
MessageId fooVersion3 = new MessageId(getRandomId());
messageMetadata.put(fooVersion3, BdfDictionary.of(
new BdfEntry("transportId", "foo"),
@@ -264,11 +243,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId);
will(returnValue(messageMetadata));
// Versions 1-3 should be deleted
oneOf(db).deleteMessage(txn, fooVersion1);
oneOf(db).deleteMessageMetadata(txn, fooVersion1);
oneOf(db).deleteMessage(txn, fooVersion2);
oneOf(db).deleteMessageMetadata(txn, fooVersion2);
// The previous update (version 3) should be deleted
oneOf(db).deleteMessage(txn, fooVersion3);
oneOf(db).deleteMessageMetadata(txn, fooVersion3);
}});
@@ -284,6 +259,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
long timestamp = 123456789;
Message message = getMessage(contactGroupId, timestamp);
Metadata meta = new Metadata();
// Version 3 is being delivered
BdfDictionary metaDictionary = BdfDictionary.of(
new BdfEntry("transportId", "foo"),
new BdfEntry("version", 3),
@@ -291,19 +267,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
);
Map<MessageId, BdfDictionary> messageMetadata =
new LinkedHashMap<>();
// Old remote updates for the same transport should be deleted
MessageId fooVersion2 = new MessageId(getRandomId());
messageMetadata.put(fooVersion2, BdfDictionary.of(
new BdfEntry("transportId", "foo"),
new BdfEntry("version", 2),
new BdfEntry("local", false)
));
MessageId fooVersion1 = new MessageId(getRandomId());
messageMetadata.put(fooVersion1, BdfDictionary.of(
new BdfEntry("transportId", "foo"),
new BdfEntry("version", 1),
new BdfEntry("local", false)
));
// A newer remote update for the same transport should not be deleted
MessageId fooVersion4 = new MessageId(getRandomId());
messageMetadata.put(fooVersion4, BdfDictionary.of(
@@ -318,11 +281,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId);
will(returnValue(messageMetadata));
// Versions 1 and 2 should be deleted, version 4 should not
oneOf(db).deleteMessage(txn, fooVersion1);
oneOf(db).deleteMessageMetadata(txn, fooVersion1);
oneOf(db).deleteMessage(txn, fooVersion2);
oneOf(db).deleteMessageMetadata(txn, fooVersion2);
// The update being delivered (version 3) should be deleted
oneOf(db).deleteMessage(txn, message.getId());
oneOf(db).deleteMessageMetadata(txn, message.getId());
@@ -345,7 +303,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{
oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact);
will(returnValue(contactGroup));
}});
expectStoreMessage(txn, contactGroup.getId(), "foo", fooPropertiesDict,
@@ -359,7 +318,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
@Test
public void testReturnsLatestLocalProperties() throws Exception {
Transaction txn = new Transaction(null, false);
Transaction txn = new Transaction(null, true);
expectGetLocalProperties(txn);
@@ -373,7 +332,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
@Test
public void testReturnsEmptyPropertiesIfNoLocalPropertiesAreFound()
throws Exception {
Transaction txn = new Transaction(null, false);
Transaction txn = new Transaction(null, true);
Map<MessageId, BdfDictionary> messageMetadata =
new LinkedHashMap<>();
// A local update for another transport should be ignored
@@ -385,7 +344,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
));
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
localGroup.getId());
@@ -400,7 +359,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
@Test
public void testReturnsLocalProperties() throws Exception {
Transaction txn = new Transaction(null, false);
Transaction txn = new Transaction(null, true);
Map<MessageId, BdfDictionary> messageMetadata =
new LinkedHashMap<>();
// A local update for another transport should be ignored
@@ -420,7 +379,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
BdfList fooUpdate = BdfList.of("foo", 1, fooPropertiesDict);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
localGroup.getId());
@@ -439,7 +398,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
@Test
public void testReturnsRemotePropertiesOrEmptyProperties()
throws Exception {
Transaction txn = new Transaction(null, false);
Transaction txn = new Transaction(null, true);
Contact contact1 = getContact(false);
Contact contact2 = getContact(true);
Contact contact3 = getContact(true);
@@ -473,19 +432,21 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
BdfList fooUpdate = BdfList.of("foo", 1, fooPropertiesDict);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getContacts(txn);
will(returnValue(contacts));
// First contact: skipped because not active
// Second contact: no updates
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact2);
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact2);
will(returnValue(contactGroup2));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup2.getId());
will(returnValue(Collections.emptyMap()));
// Third contact: returns an update
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact3);
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact3);
will(returnValue(contactGroup3));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup3.getId());
@@ -555,7 +516,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
// Store the new properties in each contact's group, version 1
oneOf(db).getContacts(txn);
will(returnValue(Collections.singletonList(contact)));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact);
will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
@@ -607,7 +569,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
// Store the merged properties in each contact's group, version 2
oneOf(db).getContacts(txn);
will(returnValue(Collections.singletonList(contact)));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact);
will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
@@ -654,28 +617,14 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
return new Message(messageId, g, timestamp, raw);
}
private void expectGetLocalProperties(Transaction txn)
throws Exception {
Map<MessageId, BdfDictionary> messageMetadata =
new LinkedHashMap<>();
// The only update for transport "foo" should be returned
private void expectGetLocalProperties(Transaction txn) throws Exception {
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
// The latest update for transport "foo" should be returned
MessageId fooVersion999 = new MessageId(getRandomId());
messageMetadata.put(fooVersion999, BdfDictionary.of(
new BdfEntry("transportId", "foo"),
new BdfEntry("version", 999)
));
// An old update for transport "bar" should be deleted
MessageId barVersion2 = new MessageId(getRandomId());
messageMetadata.put(barVersion2, BdfDictionary.of(
new BdfEntry("transportId", "bar"),
new BdfEntry("version", 2)
));
// An even older update for transport "bar" should be deleted
MessageId barVersion1 = new MessageId(getRandomId());
messageMetadata.put(barVersion1, BdfDictionary.of(
new BdfEntry("transportId", "bar"),
new BdfEntry("version", 1)
));
// The latest update for transport "bar" should be returned
MessageId barVersion3 = new MessageId(getRandomId());
messageMetadata.put(barVersion3, BdfDictionary.of(
@@ -690,8 +639,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
localGroup.getId());
will(returnValue(messageMetadata));
oneOf(db).removeMessage(txn, barVersion1);
oneOf(db).removeMessage(txn, barVersion2);
// Retrieve and parse the latest local properties
oneOf(clientHelper).getMessageAsList(txn, fooVersion999);
will(returnValue(fooUpdate));

View File

@@ -1,8 +1,8 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.ClientId;
@@ -22,7 +22,6 @@ import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.StringUtils;
import org.junit.Test;
import java.io.ByteArrayInputStream;
@@ -37,6 +36,7 @@ import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -57,7 +57,7 @@ public class SyncIntegrationTest extends BrambleTestCase {
@Inject
RecordWriterFactory recordWriterFactory;
@Inject
CryptoComponent crypto;
TransportCrypto transportCrypto;
private final ContactId contactId;
private final TransportId transportId;
@@ -79,9 +79,11 @@ public class SyncIntegrationTest extends BrambleTestCase {
headerKey = TestUtils.getSecretKey();
streamNumber = 123;
// Create a group
ClientId clientId = new ClientId(StringUtils.getRandomString(5));
ClientId clientId = new ClientId(getRandomString(123));
int clientVersion = 1234567890;
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
Group group = groupFactory.createGroup(clientId, descriptor);
Group group = groupFactory.createGroup(clientId, clientVersion,
descriptor);
// Add two messages to the group
long timestamp = System.currentTimeMillis();
byte[] body = "Hello world".getBytes("UTF-8");
@@ -117,7 +119,8 @@ public class SyncIntegrationTest extends BrambleTestCase {
private void read(byte[] connectionData) throws Exception {
// Calculate the expected tag
byte[] expectedTag = new byte[TAG_LENGTH];
crypto.encodeTag(expectedTag, tagKey, PROTOCOL_VERSION, streamNumber);
transportCrypto.encodeTag(expectedTag, tagKey, PROTOCOL_VERSION,
streamNumber);
// Read the tag
InputStream in = new ByteArrayInputStream(connectionData);

View File

@@ -1,8 +1,8 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.plugin.TransportId;
@@ -11,12 +11,11 @@ import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.RunAction;
import org.briarproject.bramble.test.TestUtils;
import org.hamcrest.Description;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.api.Action;
import org.jmock.api.Invocation;
import org.junit.Test;
@@ -41,7 +40,15 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
public class TransportKeyManagerImplTest extends BrambleTestCase {
public class TransportKeyManagerImplTest extends BrambleMockTestCase {
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final TransportCrypto transportCrypto =
context.mock(TransportCrypto.class);
private final Executor dbExecutor = context.mock(Executor.class);
private final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
private final Clock clock = context.mock(Clock.class);
private final TransportId transportId = new TransportId("id");
private final long maxLatency = 30 * 1000; // 30 seconds
@@ -55,14 +62,6 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
@Test
public void testKeysAreRotatedAtStartup() throws Exception {
Mockery context = new Mockery();
DatabaseComponent db = context.mock(DatabaseComponent.class);
CryptoComponent crypto = context.mock(CryptoComponent.class);
Executor dbExecutor = context.mock(Executor.class);
ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
Clock clock = context.mock(Clock.class);
Map<ContactId, TransportKeys> loaded = new LinkedHashMap<>();
TransportKeys shouldRotate = createTransportKeys(900, 0);
TransportKeys shouldNotRotate = createTransportKeys(1000, 0);
@@ -79,14 +78,15 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
oneOf(db).getTransportKeys(txn, transportId);
will(returnValue(loaded));
// Rotate the transport keys
oneOf(crypto).rotateTransportKeys(shouldRotate, 1000);
oneOf(transportCrypto).rotateTransportKeys(shouldRotate, 1000);
will(returnValue(rotated));
oneOf(crypto).rotateTransportKeys(shouldNotRotate, 1000);
oneOf(transportCrypto).rotateTransportKeys(shouldNotRotate, 1000);
will(returnValue(shouldNotRotate));
// Encode the tags (3 sets per contact)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(6).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(PROTOCOL_VERSION), with(i));
exactly(6).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Save the keys that were rotated
@@ -97,161 +97,124 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
with(rotationPeriodLength - 1), with(MILLISECONDS));
}});
TransportKeyManager
transportKeyManager = new TransportKeyManagerImpl(db,
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency);
transportKeyManager.start(txn);
context.assertIsSatisfied();
}
@Test
public void testKeysAreRotatedWhenAddingContact() throws Exception {
Mockery context = new Mockery();
DatabaseComponent db = context.mock(DatabaseComponent.class);
CryptoComponent crypto = context.mock(CryptoComponent.class);
Executor dbExecutor = context.mock(Executor.class);
ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
Clock clock = context.mock(Clock.class);
boolean alice = true;
boolean alice = random.nextBoolean();
TransportKeys transportKeys = createTransportKeys(999, 0);
TransportKeys rotated = createTransportKeys(1000, 0);
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(crypto).deriveTransportKeys(transportId, masterKey, 999,
alice);
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
999, alice);
will(returnValue(transportKeys));
// Get the current time (1 ms after start of rotation period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(rotationPeriodLength * 1000 + 1));
// Rotate the transport keys
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(rotated));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(PROTOCOL_VERSION), with(i));
exactly(3).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Save the keys
oneOf(db).addTransportKeys(txn, contactId, rotated);
}});
TransportKeyManager
transportKeyManager = new TransportKeyManagerImpl(db,
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency);
// The timestamp is 1 ms before the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000 - 1;
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
alice);
context.assertIsSatisfied();
}
@Test
public void testOutgoingStreamContextIsNullIfContactIsNotFound()
throws Exception {
Mockery context = new Mockery();
DatabaseComponent db = context.mock(DatabaseComponent.class);
CryptoComponent crypto = context.mock(CryptoComponent.class);
Executor dbExecutor = context.mock(Executor.class);
ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
Clock clock = context.mock(Clock.class);
Transaction txn = new Transaction(null, false);
TransportKeyManager
transportKeyManager = new TransportKeyManagerImpl(db,
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency);
assertNull(transportKeyManager.getStreamContext(txn, contactId));
context.assertIsSatisfied();
}
@Test
public void testOutgoingStreamContextIsNullIfStreamCounterIsExhausted()
throws Exception {
Mockery context = new Mockery();
DatabaseComponent db = context.mock(DatabaseComponent.class);
CryptoComponent crypto = context.mock(CryptoComponent.class);
Executor dbExecutor = context.mock(Executor.class);
ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
Clock clock = context.mock(Clock.class);
boolean alice = true;
boolean alice = random.nextBoolean();
// The stream counter has been exhausted
TransportKeys transportKeys = createTransportKeys(1000,
MAX_32_BIT_UNSIGNED + 1);
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(crypto).deriveTransportKeys(transportId, masterKey, 1000,
alice);
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
1000, alice);
will(returnValue(transportKeys));
// Get the current time (the start of rotation period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(rotationPeriodLength * 1000));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(PROTOCOL_VERSION), with(i));
exactly(3).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Rotate the transport keys (the keys are unaffected)
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys));
// Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
}});
TransportKeyManager
transportKeyManager = new TransportKeyManagerImpl(db,
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency);
// The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000;
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
alice);
assertNull(transportKeyManager.getStreamContext(txn, contactId));
context.assertIsSatisfied();
}
@Test
public void testOutgoingStreamCounterIsIncremented() throws Exception {
Mockery context = new Mockery();
DatabaseComponent db = context.mock(DatabaseComponent.class);
CryptoComponent crypto = context.mock(CryptoComponent.class);
Executor dbExecutor = context.mock(Executor.class);
ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
Clock clock = context.mock(Clock.class);
boolean alice = true;
boolean alice = random.nextBoolean();
// The stream counter can be used one more time before being exhausted
TransportKeys transportKeys = createTransportKeys(1000,
MAX_32_BIT_UNSIGNED);
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(crypto).deriveTransportKeys(transportId, masterKey, 1000,
alice);
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
1000, alice);
will(returnValue(transportKeys));
// Get the current time (the start of rotation period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(rotationPeriodLength * 1000));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(PROTOCOL_VERSION), with(i));
exactly(3).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Rotate the transport keys (the keys are unaffected)
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys));
// Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
@@ -259,9 +222,9 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
oneOf(db).incrementStreamCounter(txn, contactId, transportId, 1000);
}});
TransportKeyManager
transportKeyManager = new TransportKeyManagerImpl(db,
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency);
// The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000;
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
@@ -277,94 +240,76 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
assertEquals(MAX_32_BIT_UNSIGNED, ctx.getStreamNumber());
// The second request should return null, the counter is exhausted
assertNull(transportKeyManager.getStreamContext(txn, contactId));
context.assertIsSatisfied();
}
@Test
public void testIncomingStreamContextIsNullIfTagIsNotFound()
throws Exception {
Mockery context = new Mockery();
DatabaseComponent db = context.mock(DatabaseComponent.class);
CryptoComponent crypto = context.mock(CryptoComponent.class);
Executor dbExecutor = context.mock(Executor.class);
ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
Clock clock = context.mock(Clock.class);
boolean alice = true;
boolean alice = random.nextBoolean();
TransportKeys transportKeys = createTransportKeys(1000, 0);
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(crypto).deriveTransportKeys(transportId, masterKey, 1000,
alice);
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
1000, alice);
will(returnValue(transportKeys));
// Get the current time (the start of rotation period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(rotationPeriodLength * 1000));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(PROTOCOL_VERSION), with(i));
exactly(3).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Rotate the transport keys (the keys are unaffected)
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys));
// Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
}});
TransportKeyManager
transportKeyManager = new TransportKeyManagerImpl(db,
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency);
// The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000;
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
alice);
assertNull(transportKeyManager.getStreamContext(txn,
new byte[TAG_LENGTH]));
context.assertIsSatisfied();
}
@Test
public void testTagIsNotRecognisedTwice() throws Exception {
Mockery context = new Mockery();
DatabaseComponent db = context.mock(DatabaseComponent.class);
CryptoComponent crypto = context.mock(CryptoComponent.class);
Executor dbExecutor = context.mock(Executor.class);
ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
Clock clock = context.mock(Clock.class);
boolean alice = true;
boolean alice = random.nextBoolean();
TransportKeys transportKeys = createTransportKeys(1000, 0);
// Keep a copy of the tags
List<byte[]> tags = new ArrayList<>();
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(crypto).deriveTransportKeys(transportId, masterKey, 1000,
alice);
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
1000, alice);
will(returnValue(transportKeys));
// Get the current time (the start of rotation period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(rotationPeriodLength * 1000));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(PROTOCOL_VERSION), with(i));
exactly(3).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction(tags));
}
// Rotate the transport keys (the keys are unaffected)
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys));
// Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
// Encode a new tag after sliding the window
oneOf(crypto).encodeTag(with(any(byte[].class)),
oneOf(transportCrypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(PROTOCOL_VERSION),
with((long) REORDERING_WINDOW_SIZE));
will(new EncodeTagAction(tags));
@@ -373,9 +318,9 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
1, new byte[REORDERING_WINDOW_SIZE / 8]);
}});
TransportKeyManager
transportKeyManager = new TransportKeyManagerImpl(db,
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency);
// The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000;
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
@@ -395,20 +340,10 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
assertEquals(REORDERING_WINDOW_SIZE * 3 + 1, tags.size());
// The second request should return null, the tag has already been used
assertNull(transportKeyManager.getStreamContext(txn, tag));
context.assertIsSatisfied();
}
@Test
public void testKeysAreRotatedToCurrentPeriod() throws Exception {
Mockery context = new Mockery();
DatabaseComponent db = context.mock(DatabaseComponent.class);
CryptoComponent crypto = context.mock(CryptoComponent.class);
Executor dbExecutor = context.mock(Executor.class);
ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
Clock clock = context.mock(Clock.class);
TransportKeys transportKeys = createTransportKeys(1000, 0);
Map<ContactId, TransportKeys> loaded =
Collections.singletonMap(contactId, transportKeys);
@@ -424,12 +359,13 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
oneOf(db).getTransportKeys(txn, transportId);
will(returnValue(loaded));
// Rotate the transport keys (the keys are unaffected)
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(PROTOCOL_VERSION), with(i));
exactly(3).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Schedule key rotation at the start of the next rotation period
@@ -445,13 +381,14 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
oneOf(clock).currentTimeMillis();
will(returnValue(rotationPeriodLength * 1001));
// Rotate the transport keys
oneOf(crypto).rotateTransportKeys(with(any(TransportKeys.class)),
with(1001L));
oneOf(transportCrypto).rotateTransportKeys(
with(any(TransportKeys.class)), with(1001L));
will(returnValue(rotated));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(PROTOCOL_VERSION), with(i));
exactly(3).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Save the keys that were rotated
@@ -465,12 +402,10 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn1);
}});
TransportKeyManager
transportKeyManager = new TransportKeyManagerImpl(db,
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency);
transportKeyManager.start(txn);
context.assertIsSatisfied();
}
private TransportKeys createTransportKeys(long rotationPeriod,

View File

@@ -36,6 +36,7 @@ dependencyVerification {
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
'net.java.dev.jna:jna-platform:4.4.0:jna-platform-4.4.0.jar:e9dda9e884fc107eb6367710540789a12dfa8ad28be9326b22ca6e352e325499',
'net.java.dev.jna:jna:4.4.0:jna-4.4.0.jar:c4dadeeecaa90c8847902082aee5eb107fcf59c5d0e63a17fcaf273c0e2d2bd1',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',

View File

@@ -1,10 +1,6 @@
apply plugin: 'com.android.application'
apply plugin: 'witness'
configurations.all {
resolutionStrategy.preferProjectModules()
}
dependencies {
implementation project(path: ':briar-core', configuration: 'default')
implementation project(path: ':bramble-core', configuration: 'default')
@@ -112,6 +108,7 @@ dependencyVerification {
'nekohtml:xercesMinimal:1.9.6.2:xercesMinimal-1.9.6.2.jar:95b8b357d19f63797dd7d67622fd3f18374d64acbc6584faba1c7759a31e8438',
'net.bytebuddy:byte-buddy-agent:1.6.14:byte-buddy-agent-1.6.14.jar:c141a2d6809c3eeff4a43d25992826abccebdd4b793af3e7a5f346e88ae73a33',
'net.bytebuddy:byte-buddy:1.6.14:byte-buddy-1.6.14.jar:917758b3c651e278a15a029ba1d42dbf802d8b0e1fe2aa4b81c5750c64f461c1',
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.apache.maven.wagon:wagon-file:1.0-beta-6:wagon-file-1.0-beta-6.jar:7298feeb36ff14dd933c38e62585fb9973fea32fb3c4bc5379428cb1aac5dd3c',
@@ -189,19 +186,19 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 26
versionCode 1617
versionName "0.16.17"
applicationId "org.briarproject.briar.beta"
resValue "string", "app_package", "org.briarproject.briar.beta"
resValue "string", "app_name", "Briar Beta"
versionCode 1700
versionName "0.17.0"
applicationId "org.briarproject.briar.android"
resValue "string", "app_package", "org.briarproject.briar.android"
resValue "string", "app_name", "Briar"
buildConfigField "String", "GitHash", "\"${getGitHash()}\""
}
buildTypes {
debug {
applicationIdSuffix ".debug"
resValue "string", "app_package", "org.briarproject.briar.beta.debug"
resValue "string", "app_name", "Briar Beta Debug"
resValue "string", "app_package", "org.briarproject.briar.android.debug"
resValue "string", "app_name", "Briar Debug"
shrinkResources false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'

View File

@@ -12,7 +12,7 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
import org.briarproject.bramble.api.keyagreement.PayloadParser;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
@@ -125,7 +125,7 @@ public interface AndroidComponent
ContactExchangeTask contactExchangeTask();
KeyAgreementTaskFactory keyAgreementTaskFactory();
KeyAgreementTask keyAgreementTask();
PayloadEncoder payloadEncoder();

View File

@@ -6,8 +6,8 @@ package org.briarproject.briar.android;
*/
public interface BriarApplication {
// This build expires on 30 April 2018
long EXPIRY_DATE = 1525046400 * 1000L;
// This build expires on 31 December 2018
long EXPIRY_DATE = 1546214400 * 1000L;
AndroidComponent getApplicationComponent();

View File

@@ -124,24 +124,21 @@ public class BriarService extends Service {
b.setPriority(PRIORITY_MIN);
startForeground(ONGOING_NOTIFICATION_ID, b.build());
// Start the services in a background thread
new Thread() {
@Override
public void run() {
String nickname = databaseConfig.getLocalAuthorName();
StartResult result = lifecycleManager.startServices(nickname);
if (result == SUCCESS) {
started = true;
} else if (result == ALREADY_RUNNING) {
LOG.info("Already running");
stopSelf();
} else {
if (LOG.isLoggable(WARNING))
LOG.warning("Startup failed: " + result);
showStartupFailureNotification(result);
stopSelf();
}
new Thread(() -> {
String nickname = databaseConfig.getLocalAuthorName();
StartResult result = lifecycleManager.startServices(nickname);
if (result == SUCCESS) {
started = true;
} else if (result == ALREADY_RUNNING) {
LOG.info("Already running");
stopSelf();
} else {
if (LOG.isLoggable(WARNING))
LOG.warning("Startup failed: " + result);
showStartupFailureNotification(result);
stopSelf();
}
}.start();
}).start();
}
private void showStartupFailureNotification(StartResult result) {
@@ -187,12 +184,9 @@ public class BriarService extends Service {
LOG.info("Destroyed");
stopForeground(true);
// Stop the services in a background thread
new Thread() {
@Override
public void run() {
if (started) lifecycleManager.stopServices();
}
}.start();
new Thread(() -> {
if (started) lifecycleManager.stopServices();
}).start();
}
@Override

View File

@@ -18,7 +18,7 @@ public interface TestingConstants {
* Whether this is a beta build. This should be set to false for final
* release builds.
*/
boolean IS_BETA_BUILD = true;
boolean IS_BETA_BUILD = false;
/**
* Default log level. Disable logging for final release builds.

View File

@@ -27,11 +27,11 @@ import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
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_NO_ANIMATION;
import static android.os.Build.MANUFACTURER;
import static android.os.Build.VERSION.SDK_INT;
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.util.UiUtils.getDozeWhitelistingIntent;
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
@SuppressLint("Registered")
public abstract class BriarActivity extends BaseActivity {
@@ -80,7 +80,7 @@ public abstract class BriarActivity extends BaseActivity {
public void setSceneTransitionAnimation() {
if (SDK_INT < 21) return;
// workaround for #1007
if (isSamsung7(this)) {
if (SDK_INT == 24 && MANUFACTURER.equalsIgnoreCase("Samsung")) {
return;
}
Transition slide = new Slide(Gravity.RIGHT);

View File

@@ -34,7 +34,6 @@ import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID;
import static org.briarproject.briar.android.util.UiUtils.TEASER_LENGTH;
import static org.briarproject.briar.android.util.UiUtils.getSpanned;
import static org.briarproject.briar.android.util.UiUtils.getTeaser;
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
import static org.briarproject.briar.api.blog.MessageType.POST;
@@ -131,7 +130,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
i.putExtra(GROUP_ID, item.getGroupId().getBytes());
i.putExtra(POST_ID, item.getId().getBytes());
if (Build.VERSION.SDK_INT >= 23 && !isSamsung7(ctx)) {
if (Build.VERSION.SDK_INT >= 23) {
ActivityOptionsCompat options =
makeSceneTransitionAnimation((Activity) ctx, layout,
getTransitionName(item.getId()));
@@ -139,7 +138,6 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
options.toBundle());
} else {
// work-around for android bug #224270
// work-around for Samsung Android 7 bug #1007
ctx.startActivity(i);
}
});

View File

@@ -123,25 +123,22 @@ public class BriarControllerImpl implements BriarController {
@Override
public void signOut(ResultHandler<Void> eventHandler) {
new Thread() {
@Override
public void run() {
try {
// Wait for the service to finish starting up
IBinder binder = serviceConnection.waitForBinder();
BriarService service =
((BriarService.BriarBinder) binder).getService();
service.waitForStartup();
// Shut down the service and wait for it to shut down
LOG.info("Shutting down service");
service.shutdown();
service.waitForShutdown();
} catch (InterruptedException e) {
LOG.warning("Interrupted while waiting for service");
}
eventHandler.onResult(null);
new Thread(() -> {
try {
// Wait for the service to finish starting up
IBinder binder = serviceConnection.waitForBinder();
BriarService service =
((BriarService.BriarBinder) binder).getService();
service.waitForStartup();
// Shut down the service and wait for it to shut down
LOG.info("Shutting down service");
service.shutdown();
service.waitForShutdown();
} catch (InterruptedException e) {
LOG.warning("Interrupted while waiting for service");
}
}.start();
eventHandler.onResult(null);
}).start();
}
private void unbindService() {

View File

@@ -24,7 +24,6 @@ import com.google.zxing.Result;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
import org.briarproject.bramble.api.keyagreement.Payload;
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
import org.briarproject.bramble.api.keyagreement.PayloadParser;
@@ -48,6 +47,7 @@ import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Provider;
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
@@ -68,7 +68,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
private static final Logger LOG = Logger.getLogger(TAG);
@Inject
KeyAgreementTaskFactory keyAgreementTaskFactory;
Provider<KeyAgreementTask> keyAgreementTaskProvider;
@Inject
PayloadEncoder payloadEncoder;
@Inject
@@ -187,7 +187,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
@UiThread
private void startListening() {
KeyAgreementTask oldTask = task;
KeyAgreementTask newTask = keyAgreementTaskFactory.createTask();
KeyAgreementTask newTask = keyAgreementTaskProvider.get();
task = newTask;
ioExecutor.execute(() -> {
if (oldTask != null) oldTask.stopListening();

View File

@@ -17,7 +17,7 @@ import android.widget.Toast;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.controller.handler.UiResultHandler;
import org.briarproject.briar.android.util.UiUtils;
@@ -27,7 +27,7 @@ import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
public class ChangePasswordActivity extends BriarActivity
public class ChangePasswordActivity extends BaseActivity
implements OnClickListener, OnEditorActionListener {
@Inject

View File

@@ -29,6 +29,8 @@ import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.briar.android.BriarApplication.EXPIRY_DATE;
import static org.briarproject.briar.android.TestingConstants.IS_BETA_BUILD;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
import static org.briarproject.briar.android.controller.BriarControllerImpl.DOZE_ASK_AGAIN;
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.NO;
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.SHOW;
@@ -44,7 +46,7 @@ public class NavDrawerControllerImpl extends DbControllerImpl
private static final Logger LOG =
Logger.getLogger(NavDrawerControllerImpl.class.getName());
private static final String EXPIRY_DATE_WARNING = "expiryDateWarning";
private static final String EXPIRY_SHOW_UPDATE = "expiryShowUpdateAgain";
private static final String EXPIRY_SHOW_UPDATE = "expiryShowUpdate";
private final PluginManager pluginManager;
private final SettingsManager settingsManager;
@@ -106,6 +108,10 @@ public class NavDrawerControllerImpl extends DbControllerImpl
@Override
public void showExpiryWarning(ResultHandler<ExpiryWarning> handler) {
if (!IS_DEBUG_BUILD && !IS_BETA_BUILD) {
handler.onResult(NO);
return;
}
runOnDbThread(() -> {
try {
Settings settings =

View File

@@ -35,8 +35,6 @@ import javax.annotation.Nullable;
import static android.content.Context.POWER_SERVICE;
import static android.content.Intent.CATEGORY_DEFAULT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.Build.MANUFACTURER;
import static android.os.Build.VERSION.SDK_INT;
import static android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
@@ -178,8 +176,4 @@ public class UiUtils {
return i;
}
public static boolean isSamsung7(Context context) {
return SDK_INT == 24 && MANUFACTURER.equalsIgnoreCase("Samsung");
}
}

View File

@@ -28,7 +28,6 @@ import javax.annotation.Nullable;
import static android.content.Context.INPUT_METHOD_SERVICE;
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
import static android.view.KeyEvent.KEYCODE_BACK;
import static android.view.KeyEvent.KEYCODE_ENTER;
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
@UiThread
@@ -83,22 +82,16 @@ public class TextInputView extends KeyboardAwareLinearLayout
hideEmojiDrawer();
return true;
}
if (keyCode == KEYCODE_ENTER && event.isCtrlPressed()) {
trySendMessage();
return true;
}
return false;
});
ui.sendButton.setOnClickListener(v -> trySendMessage());
ui.sendButton.setOnClickListener(v -> {
if (listener != null) {
listener.onSendClick(ui.editText.getText().toString());
}
});
ui.emojiDrawer.setEmojiEventListener(this);
}
private void trySendMessage() {
if (listener != null) {
listener.onSendClick(ui.editText.getText().toString());
}
}
@Override
public void setVisibility(int visibility) {
if (visibility == GONE && isKeyboardOpen()) {

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:paddingBottom="@dimen/margin_activity_vertical"
android:paddingEnd="@dimen/margin_activity_horizontal"
android:paddingLeft="@dimen/margin_activity_horizontal"
android:paddingRight="@dimen/margin_activity_horizontal"
android:paddingStart="@dimen/margin_activity_horizontal"
android:paddingTop="@dimen/margin_activity_vertical">
<ImageView
android:id="@+id/imageView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/margin_medium"
android:layout_marginRight="@dimen/margin_medium"
android:layout_weight="1"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="@drawable/bluetooth"/>
<ScrollView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
style="@style/BriarTextBody"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/your_nickname"
android:visibility="gone"/>
<Spinner
android:id="@+id/spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_medium"
android:background="@drawable/border_spinner"
android:spinnerMode="dropdown"
android:visibility="gone"/>
<TextView
style="@style/BriarTextBody"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_medium"
android:text="@string/face_to_face"/>
<Button
android:id="@+id/continueButton"
style="@style/BriarButton.Default"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/margin_medium"
android:text="@string/continue_button"/>
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -1,7 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_next">Следващ</string>
<string name="setup_title">Briar регистрация</string>
<string name="setup_explanation">Briar профилът се съхранява криптиран във вашето устройство, а не в облака. Ако деинсталирате Briar или забравите паролата си, няма как да възстановите профила и данните си.</string>
<string name="choose_nickname">Изберете име</string>
<string name="choose_password">Изберете парола</string>
<string name="confirm_password">Потвърдете парола</string>
@@ -19,7 +20,12 @@
<string name="startup_failed_notification_title">Briar не можа да стартира</string>
<string name="startup_failed_notification_text">Може да се наложи да преинсталирате Briar.</string>
<string name="startup_failed_activity_title">Неуспешно стартиране</string>
<string name="startup_failed_db_error">Вашата база данни на Briar е повредена непоправимо. Вашият профил, данни и всички контакти са унищожени. За съжаление се налага да преинсталирате Briar и да създадете нов профил.</string>
<string name="startup_failed_service_error">Briar не успя да стартира задължителен плъгин. Обикновено преинсталирането на Briar решава този проблем. Моля, имайте предвид, че ще изгубите профила си и всички данни, асоциирани с него, тъй като Briar не съхранява данните ви в централни сървъри.</string>
<plurals name="expiry_warning">
<item quantity="one">Това е бета версия на Briar. Профилът ви ще изгуби валидност след %d ден и не може да бъде подновен.</item>
<item quantity="other">Това е бета версия на Briar. Профилът ви ще изгуби валидност след %d дни и не може да бъде подновен.</item>
</plurals>
<string name="expiry_date_reached">Софтуерът е невалиден.\nБлагодарим ви за тестването!</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">Отвори навигационно чекмедже</string>
@@ -63,7 +69,8 @@
<string name="delete">Изтрий</string>
<string name="accept">Приеми</string>
<string name="decline">Откажи</string>
<string name="online">Онлайн</string>
<string name="options">Опции</string>
<string name="online">Онлайн</string>
<string name="offline">Офлайн</string>
<string name="send">Изпрати</string>
<string name="allow">Позволи</string>
@@ -72,7 +79,6 @@
<string name="ellipsis">...</string>
<string name="text_too_long">Въведеният текст е твърде дълъг</string>
<string name="show_onboarding">Показване на помощен диалог</string>
<string name="help">Помощ</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">Няма добавени контакти.\n\nНатиснете плюса и следвайте инструкциите, за да добавите приятели.\n\nВнимание: Трябва да се срещнете лично с човека, когото искате да добавите в Контакти. По този начин никой не може да се представи за вас или да чете съобщенията ви в бъдеще.</string>
<string name="date_no_private_messages">Няма съобщения.</string>
@@ -84,18 +90,31 @@
<string name="contact_deleted_toast">Контактът е изтрит</string>
<!--Adding Contacts-->
<string name="add_contact_title">Добавяне на контакт</string>
<string name="your_nickname">Изберете профил:</string>
<string name="face_to_face">Трябва да се срещнете лично с човека, когото искате да добавите в Контакти.\n\nПо този начин никой не може да се представи за вас или да чете съобщенията ви в бъдеще.</string>
<string name="continue_button">Напред</string>
<string name="your_invitation_code">Кодът ви за покана е</string>
<string name="enter_invitation_code">Моля, въведете кодът за покана на контакта ви:</string>
<string name="searching_format">Търсене на контакт с код за покана %06d\u2026</string>
<string name="connection_failed">Неуспешна връзка</string>
<string name="could_not_find_contact">Briar не можа да открие вашият контакт </string>
<string name="try_again_button">Опитай пак</string>
<string name="connected_to_contact">Свързан с контакт</string>
<string name="calculating_confirmation_code">Изчисляване на код за потвърждение\u2026</string>
<string name="your_confirmation_code">Кодът ви за потвърждение е</string>
<string name="enter_confirmation_code">Моля, въведете кодът за потвърждение на контакта ви</string>
<string name="waiting_for_contact">Изчакване на контакта\u2026</string>
<string name="waiting_for_contact_to_scan">Изчакване контактът да сканира и да се свърже\u2026</string>
<string name="exchanging_contact_details">Обмен на данни за контакт\u2026</string>
<string name="codes_do_not_match">Кодовете не съвпадат</string>
<string name="interfering">Това може да значи, че някой се опитва да попречи на свързването.</string>
<string name="contact_added_toast">Добавен конктакт: %s</string>
<string name="contact_already_exists">Контактът %s вече съществува</string>
<string name="contact_exchange_failed">Неуспех при обмен на контакти</string>
<string name="qr_code_invalid">QR кодът е невалиден</string>
<string name="connecting_to_device">Свързване с устройство\u2026</string>
<string name="authenticating_with_device">Удостоверяване с устройство\u2026</string>
<string name="connection_aborted_local">Връзката е прекъсната от нас! Това може да значи, че някой се опитва да попречи на свързването</string>
<string name="connection_aborted_remote">Връзката е прекъсната от контакта ви! Това може да значи, че някой се опитва да попречи на свързването</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Представете контактите си</string>
@@ -345,5 +364,4 @@
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Открит е овърлей на екрана</string>
<string name="screen_filter_body">Друго приложение чертае върху Briar. За да защити вашата сигурност, Briar няма да реагира на докосване, докато друго приложение чертае отгоре.\n\nОпитайте да изключите следните приложения, когато използвате Briar:\n\n%1$s</string>
<!--Permission Requests-->
</resources>

View File

@@ -1,149 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_title">Degemer mat war Briar</string>
<string name="setup_next">De-heul</string>
<string name="setup_password_intro">Dibabit ur ger-tremen</string>
<string name="setup_doze_title">Kevreadennoù drek-leur</string>
<string name="setup_doze_button">Aotren ar c\'hevreadennoù</string>
<string name="choose_nickname">Dibabit ho lezanv</string>
<string name="choose_password">Dibabit ho ker-tremen</string>
<string name="confirm_password">Kadarnait ho ker-tremen</string>
<string name="name_too_long">Re hir eo an anv</string>
<string name="password_too_weak">Re wan eo ar ger-kuzh</string>
<string name="passwords_do_not_match">Ne glot ket ar gerioù-tremen</string>
<string name="create_account_button">Krouiñ ur gont</string>
<string name="more_info">Muioc\'h a ditouroù</string>
<string name="don_t_ask_again">Arabat goulenn ganin ken</string>
<string name="setup_huawei_button">Gwareziñ Briar</string>
<!--Login-->
<string name="enter_password">Ebarzhit ho ker-tremen:</string>
<string name="try_again">Ger-tremen fall, klaskit adarre</string>
<string name="sign_in_button">Kevreañ</string>
<string name="forgotten_password">Ankoueet em eus ma ger-tremen</string>
<string name="dialog_title_lost_password">Kollet em eus ma ger-tremen</string>
<string name="startup_failed_notification_title">N\'hall ket loc\'hañ Briar</string>
<string name="startup_failed_notification_text">Rankout a rit staliañ a nevez Briar.</string>
<!--Navigation Drawer-->
<string name="contact_list_button">Darempredoù</string>
<string name="groups_button">Strolladoù prevez</string>
<string name="forums_button">Foromoù</string>
<string name="blogs_button">Blogoù</string>
<string name="settings_button">Arventennoù</string>
<string name="sign_out_button">Digevreañ</string>
<!--Transports-->
<string name="transport_tor">Kenrouedad</string>
<string name="transport_bt">Bluetooth</string>
<string name="transport_lan">Wi-Fi</string>
<!--Notifications-->
<string name="ongoing_notification_title">Marilhet e-barzh Briar</string>
<string name="ongoing_notification_text">Touchit da zigeriñ Briar.</string>
<plurals name="private_message_notification_text">
<item quantity="one">Kemennadenn prevez nevez</item>
<item quantity="other">%d a gemennadennoù prevez nevez. </item>
</plurals>
<plurals name="group_message_notification_text">
<item quantity="one">Kemennadenn strollad nevez.</item>
<item quantity="other">%d a gemennadennoù strollad nevez.</item>
</plurals>
<plurals name="forum_post_notification_text">
<item quantity="one">Postadenn forom nevez.</item>
<item quantity="other">%da bostadennoù forom nevez.</item>
</plurals>
<plurals name="blog_post_notification_text">
<item quantity="one">Postadenn blog nevez.</item>
<item quantity="other">%d a bostadennoù blog nevez.</item>
</plurals>
<!--Misc-->
<string name="now">bremañ</string>
<string name="show">Diskouez</string>
<string name="hide">Kuzhaat</string>
<string name="ok">MAT EO</string>
<string name="cancel">Nullañ</string>
<string name="got_it">Deuet eo ganin</string>
<string name="delete">Dilemel</string>
<string name="accept">Asantiñ</string>
<string name="decline">Nac\'hañ</string>
<string name="online">Enlinenn</string>
<string name="offline">Ezlinenn</string>
<string name="send">Kas</string>
<string name="allow">Aotren</string>
<string name="open">Digeriñ</string>
<string name="no_data">Roadenn ebet</string>
<string name="ellipsis"></string>
<string name="text_too_long">An destenn ebarzhet a zo re hir</string>
<string name="show_onboarding">Diskouez an diviz skoazell</string>
<string name="help">Skoazell</string>
<!--Contacts and Private Conversations-->
<string name="date_no_private_messages">Kemennadenn ebet.</string>
<string name="message_hint">Doare kemennadenn</string>
<string name="delete_contact">Dilemel an darempred</string>
<string name="dialog_title_delete_contact">Kadarnat dilemel darempred</string>
<string name="contact_deleted_toast">Darempred dilemet</string>
<!--Adding Contacts-->
<string name="add_contact_title">Ouzhpennañ un darempred</string>
<string name="continue_button">Kenderc\'hel</string>
<string name="connection_failed">Kevereadenn c\'hwitet</string>
<string name="try_again_button">Klask adarre</string>
<string name="contact_added_toast">Darempred ouzhpennet: %s</string>
<string name="contact_already_exists">An darempred %s a zo anezhañ endeo</string>
<string name="camera_error">Fazi Kamera</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Degas e-barzh ho tarempredoù</string>
<string name="introduction_activity_title">Diuziñ darempred</string>
<string name="introduction_message_title">Degas e-barzh darempredoù</string>
<string name="introduction_message_hint">Ouzhpennañ ur gemennadenn (diret)</string>
<string name="introduction_button">Sevel un digoradur</string>
<string name="introduction_sent">Kaset eo bet ho tigoradur.</string>
<string name="introduction_error">Ur fazi a zo c\'hoarvezet en ur ober an digoradur.</string>
<string name="introduction_response_error">Fazi en ur respont d\'an digoradur</string>
<plurals name="introduction_notification_text">
<item quantity="one">Darempred nevez ouzhpennet.</item>
<item quantity="other">%d a zarempredoù nevez ouzhpennet.</item>
</plurals>
<!--Private Groups-->
<string name="groups_created_by">Graet gant %s</string>
<plurals name="messages">
<item quantity="one">%d kemennadenn</item>
<item quantity="other">%d a gemennadennoù</item>
</plurals>
<string name="groups_group_is_empty">Leun eo ar strollad-mañ</string>
<string name="groups_group_is_dissolved">Divodet eo bet ar strollad-mañ</string>
<string name="groups_remove">Dilemel</string>
<string name="groups_create_group_title">Krouiñ ur strollad prevez</string>
<string name="groups_create_group_button">Krouiñ ur strollad</string>
<string name="groups_create_group_invitation_button">Kas pedadennoù</string>
<string name="groups_create_group_hint">Dibabit un anv evit ho strollad prevez</string>
<string name="groups_invitation_sent">Pedadenn ar strollad a zo bet kaset</string>
<string name="groups_message_sent">Kemennadenn kaset</string>
<string name="groups_member_list">Listenn an izili</string>
<string name="groups_invite_members">Pediñ izili</string>
<string name="groups_member_created_you">Krouet ho peus ar strollad</string>
<string name="groups_member_created">%s en deus krouet ar strollad</string>
<string name="groups_member_joined_you">Emezelet ho peus er strollad</string>
<string name="groups_member_joined">%s a zo deuet e-barzh ar strollad</string>
<string name="groups_leave">Kuitaat ar strollad</string>
<string name="groups_leave_dialog_title">Kadarnaat e kuitait ar strollad</string>
<string name="groups_leave_dialog_message">C\'hoant ho peus da guitaat ar strollad-mañ?</string>
<string name="groups_dissolve">Divodañ ar strollad</string>
<string name="groups_dissolve_dialog_title">Kadarnaat e tivodit ar strollad</string>
<string name="groups_dissolve_button">Divodañ</string>
<!--Private Group Invitations-->
<!--Private Groups Revealing Contacts-->
<!--Forums-->
<!--Forum Sharing-->
<string name="forum_share_message">Ouzhpennañ ur gemennadenn (diret)</string>
<!--Blogs-->
<!--Blog Sharing-->
<!--RSS Feeds-->
<!--Settings Network-->
<!--Settings Security and Panic-->
<string name="lock_setting_title">Digevreañ</string>
<!--Settings Notifications-->
<!--Settings Feedback-->
<!--Link Warning-->
<!--Crash Reporter-->
<!--Sign Out-->
<!--Screen Filters & Tapjacking-->
<!--Permission Requests-->
</resources>

View File

@@ -1,15 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_title">Benvingut a Briar</string>
<string name="setup_name_explanation">El vostre sobrenom es mostrarà al costat de qualsevol contingut que publiqueu. No podeu canviar-lo després de crear el compte.</string>
<string name="setup_next">Següent</string>
<string name="setup_password_intro">Trieu una contrasenya</string>
<string name="setup_password_explanation">El vostre compte Briar s\'emmagatzema encriptada al vostre dispositiu, no al núvol. Si oblideu la vostra contrasenya o desinstal·leu Briar, no hi ha forma de recuperar el vostre compte.\n\nTrieu una contrasenya llarga que sigui difícil d\'endevinar, com ara quatre paraules aleatòries o deu lletres, números i símbols aleatoris.</string>
<string name="setup_doze_title">Connexions de fons</string>
<string name="setup_doze_intro">Per rebre missatges, Briar necessita estar connectat en segon pla.</string>
<string name="setup_doze_explanation">Per rebre missatges, Briar necessita estar connectat en segon pla. Desactiveu les optimitzacions de la bateria perquè Briar es mantingui connectat.</string>
<string name="setup_doze_button">Permet connexions</string>
<string name="setup_title">Configuració de Briar</string>
<string name="setup_explanation">El teu compte de Briar està encriptat al teu dispositiu, no al núvol. Si desinstal·les Briar o oblides la contrasenya, no hi ha manera de recuperar el teu compte i les teves dades.</string>
<string name="choose_nickname">Trieu el sobrenom</string>
<string name="choose_password">Trieu la contrasenya</string>
<string name="confirm_password">Confirmeu la contrasenya</string>
@@ -17,12 +10,6 @@
<string name="password_too_weak">La contrasenya és dèbil</string>
<string name="passwords_do_not_match">Les contrasenyes no coincideixen</string>
<string name="create_account_button">Crear compte</string>
<string name="more_info">Més informació</string>
<string name="don_t_ask_again">No ho tornis a preguntar</string>
<string name="setup_huawei_text">Feu clic al botó següent i assegureu-vos que Briar està protegit a la pantalla «Aplicacions protegides».</string>
<string name="setup_huawei_button">Protegeix el Briar</string>
<string name="setup_huawei_help">Si Briar no s\'afegeix a la llista d\'aplicacions protegides, no podrà executar-se en segon pla.</string>
<string name="warning_dozed">%s no ha pogut executar-se en segon pla</string>
<!--Login-->
<string name="enter_password">Introdueix la teva contrasenya:</string>
<string name="try_again">La contrasenya és incorrecta, torna-ho a provar</string>
@@ -33,13 +20,12 @@
<string name="startup_failed_notification_title">Briar no s\'ha pogut iniciar</string>
<string name="startup_failed_notification_text">Potser us caldrà reinstal·lar Briar.</string>
<string name="startup_failed_activity_title">Error iniciant Briar</string>
<string name="startup_failed_db_error">Per alguna raó, la vostra base de dades de Briar està malmesa més enllà de la reparació. El vostre compte, les vostres dades i tots els vostres contactes es perdran. Malauradament, necessiteu tornar a instal·lar Briar i configurar un compte nou.</string>
<string name="startup_failed_db_error">Per algun motiu, la vostra base de dades de Briar s\'ha corromput i no té solució. El vostre compte, les vostres dades i totes les connexions s\'han perdut. Malauradament, haureu de reinstal·lar i configurar un compte nou.</string>
<string name="startup_failed_service_error">Briar no ha pogut engegar un plugin necessari. La sol.lució sol ser reinstal.lar Briar. De tota manera, tingueu en compte que si ho feu perdreu el vostre compte i totes les dades associades, doncs Briar no usa servidors centrals per desar les vostres dades.</string>
<plurals name="expiry_warning">
<item quantity="one">Aquesta és una versió de prova de Briar. El vostre compte expira en %d dia i no es pot renovar.</item>
<item quantity="other">Aquesta és una versió de prova de Briar. El vostre compte expira en %d dies i no es pot renovar.</item>
<item quantity="one">Això és una versió beta de Briar. El teu compte expira en %d dia i no pot ser renovat.</item>
<item quantity="other">Això és una versió beta de Briar. El teu compte expira en %d dies i no pot ser renovat.</item>
</plurals>
<string name="expiry_update">S\'ha ampliat la data de venciment de la prova. El vostre compte caducarà ara en %d dies.</string>
<string name="expiry_date_reached">Aquest programa ha caducat.\nGràcies per haver-lo provat!</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">Obre el calaix de navegació</string>
@@ -65,14 +51,6 @@
<item quantity="one">Missatge de grup nou.</item>
<item quantity="other">1%d missatges de grup nous</item>
</plurals>
<plurals name="forum_post_notification_text">
<item quantity="one"> Un nou missatge del fòrum.</item>
<item quantity="other">%d nous missatges del fòrum.</item>
</plurals>
<plurals name="blog_post_notification_text">
<item quantity="one">Una nova publicacions al bloc.</item>
<item quantity="other">%d noves publicacions al bloc.</item>
</plurals>
<!--Misc-->
<string name="now">Ara</string>
<string name="show">Mostrar</string>
@@ -83,7 +61,8 @@
<string name="delete">Suprimeix</string>
<string name="accept">Accepta</string>
<string name="decline">Rebutja</string>
<string name="online">En línia</string>
<string name="options">Opcions</string>
<string name="online">En línia</string>
<string name="offline">Fora de línia</string>
<string name="send">Envia</string>
<string name="allow">Permet</string>
@@ -92,12 +71,9 @@
<string name="ellipsis">...</string>
<string name="text_too_long">El text introduït és massa llarg</string>
<string name="show_onboarding">Mostra el diàleg d\'ajuda.</string>
<string name="fix">Corregeix</string>
<string name="help">Ajuda</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">Sembla que sou nouvingut i no teniu contactes encara.\n\nToqueu la icona + en la part superior i seguiu les instruccions per a afegir amics a la llista.\n\nRecordeu: afegiu només contactes cara a cara per a evitar que algú es faça passar per vós o pugui llegir els vostres missatges en el futur. </string>
<string name="date_no_private_messages">Sense missatges.</string>
<string name="no_private_messages">Aquesta és la vista de conversa.\n\nSembla que hi manca una part de la conversa.\n\nNomés cal que toqueu el camp d\'entrada a la part inferior per iniciar una conversa.</string>
<string name="message_hint">Escriu el missatge.</string>
<string name="delete_contact">Suprimeix contacte</string>
<string name="dialog_title_delete_contact">Confirma la supressió del contacte</string>
@@ -105,20 +81,31 @@
<string name="contact_deleted_toast">Contacte suprimit</string>
<!--Adding Contacts-->
<string name="add_contact_title">Afegir contacte</string>
<string name="your_nickname">Escull la identitat que vols utilitzar:</string>
<string name="face_to_face">T\'has de trobar amb la persona que vols afegir com a contacte.\n\nAixò evitarà que algú suplanti la teva identitat o llegeixi els teus missatges.</string>
<string name="continue_button">Continua</string>
<string name="your_invitation_code">El teu codi d\'invitació és</string>
<string name="enter_invitation_code">Si us plau, entra el codi d\'invitació del teu contacte:</string>
<string name="searching_format">Cercant el contacte amb codi d\'invitació %06d\u2026</string>
<string name="connection_failed">La connexió ha fallat</string>
<string name="could_not_find_contact">Briar no ha pogut trobar el teu contacte aprop</string>
<string name="try_again_button">Torna-ho a provar</string>
<string name="connected_to_contact">Connectat amb el contacte</string>
<string name="calculating_confirmation_code">Calculant el codi de confirmació\u2026</string>
<string name="your_confirmation_code">El teu codi de confirmació és</string>
<string name="enter_confirmation_code">Si us plau entra el codi de confirmació del teu contacte:</string>
<string name="waiting_for_contact">Esperant el contacte\u2026</string>
<string name="waiting_for_contact_to_scan">Esperant que el teu contacte escanegi i connecti\u2026</string>
<string name="exchanging_contact_details">Intercanviant els detalls del contacte\u2026</string>
<string name="codes_do_not_match">Els codis no coincideixen</string>
<string name="interfering">Això pot significar que algú està intentant interferir la teva connexió</string>
<string name="contact_added_toast">Contacte afegit: %s</string>
<string name="contact_already_exists">El contacte %s ja existeix</string>
<string name="contact_exchange_failed">L\'intercanvi de contactes ha fallat</string>
<string name="qr_code_invalid">El codi QR és invàlid</string>
<string name="camera_error">Error de la càmera</string>
<string name="connecting_to_device">Connectant al dispositiu\u2026</string>
<string name="authenticating_with_device">Autenticant-se amb el dispositiu\u2026</string>
<string name="connection_aborted_local">S\'ha avortat la connexió! Això podria significar que algú intenta interferir amb la vostra connexió</string>
<string name="connection_aborted_local">Hem avortat la connexió! Això podria significar que algú està provant d\'interferir la vostra connexió</string>
<string name="connection_aborted_remote">El teu contacte ha avortat la connexió! Això podria significar que algú està provant d\'interferir la vostra connexió</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Introdueix els teus contactes</string>
@@ -130,19 +117,7 @@
<string name="introduction_sent">S\'ha enviat la teva presentació.</string>
<string name="introduction_error">Hi ha hagut un error al fer la presentació de contactes.</string>
<string name="introduction_response_error">Error en respondre a la presentació</string>
<string name="introduction_request_sent">Heu demanat que introduïu %1$s a %2$s.</string>
<string name="introduction_request_received">%1$s ha demanat que us presenteu a %2$s. Voleu afegir a%2$s a la vostra llista de contactes?</string>
<string name="introduction_request_exists_received">%1$s ha demanat que us presenteu a %2$s, però %2$s ja està a la vostra llista de contactes. Com que %1$s no sap això, encara podeu respondre:</string>
<string name="introduction_request_answered_received">%1$s ha demanat que us presenteu a %2$s.</string>
<string name="introduction_response_accepted_sent">Has acceptat la presentació amb el contacte %1$s.</string>
<string name="introduction_response_declined_sent">Heu rebutjat la presentació a %1$s.</string>
<string name="introduction_response_accepted_received">%1$s va acceptar la presentació de %2$s.</string>
<string name="introduction_response_declined_received">%1$s va rebutjar la presentació a %2$s.</string>
<string name="introduction_response_declined_received_by_introducee">%1$s diu que %2$s va rebutjar la presentació.</string>
<plurals name="introduction_notification_text">
<item quantity="one">S\'ha afegit un nou contacte.</item>
<item quantity="other">S\'ha afegit %d nous contactes.</item>
</plurals>
<!--Private Groups-->
<string name="groups_list_empty">No pertanyeu a cap grup.\n\nToqueu la icona + en la part superior per a crear un grup o demaneu als vostres contactes que us conviden a un grup.</string>
<string name="groups_created_by">Creat per 1%s</string>
@@ -179,26 +154,14 @@
<string name="groups_invitations_invitation_sent">Heu convidat a 1%1$s al grup \"%2$s \".</string>
<string name="groups_invitations_invitation_received">1%1$s us ha convidat a unir-vos al grup \" 2%2$s \".</string>
<string name="groups_invitations_joined">Us heu unit al grup</string>
<string name="groups_invitations_declined">S\'ha rebutjat la invitació al grup</string>
<string name="groups_invitations_response_accepted_sent">Heu acceptat la invitació del grup de %s.</string>
<string name="groups_invitations_response_declined_sent">Heu rebutjat la invitació del grup de %s.</string>
<string name="groups_invitations_response_accepted_received">%s va acceptar la invitació del grup.</string>
<string name="groups_invitations_response_declined_received">%s va rebutjar la invitació del grup.</string>
<string name="sharing_status_groups">Només el creador del grup pot convidar a nous membres. Tot seguit hi ha la llista dels membres del grup.</string>
<!--Private Groups Revealing Contacts-->
<string name="groups_reveal_contacts">Revela els contactes</string>
<string name="groups_reveal_dialog_message">Podeu triar si voleu que es mostrin contactes a tots els membres actuals i futurs d\'aquest grup.\n\nLa revelació dels contactes fa que la vostra connexió al grup sigui més ràpida i més fiable, ja que podeu comunicar-vos amb els contactes revelats, fins i tot quan el creador del grup està fora de línia.</string>
<string name="groups_reveal_visible">La relació del contacte és visible per al grup</string>
<string name="groups_reveal_visible_revealed_by_us">La relació del contacte és visible per al grup (revelat per vós)</string>
<string name="groups_reveal_visible_revealed_by_contact">La relació del contacte és visible per al grup (revelat per %s)</string>
<string name="groups_reveal_invisible">La relació del contacte no és visible per al grup</string>
<!--Forums-->
<string name="no_forums">No teniu cap fòrum encara.\n\nPer què no en creeu un de nou tocant el + que hi ha a la part superior dreta?\n\nTambé podeu demanar els vostres contactes que us conviden a fòrums.</string>
<string name="create_forum_title">Crea un fòrum</string>
<string name="choose_forum_hint">Trieu un nom per al fòrum</string>
<string name="create_forum_button">Crea el fòrum</string>
<string name="forum_created_toast">S\'ha creat el fòrum</string>
<string name="no_forum_posts">Aquest fòrum està buit.\n\nUtilitzeu la icona de la ploma a la part superior per redactar la primera publicació.\n\nEsteu aquí sol? Compartiu aquest fòrum amb els vostres contactes!</string>
<string name="no_posts">No hi ha publicacions</string>
<plurals name="posts">
<item quantity="one">%d publicacio</item>
@@ -211,33 +174,9 @@
<string name="forum_leave">Abandona el fòrum</string>
<string name="dialog_title_leave_forum">Confirmeu la sortida del Forum</string>
<string name="dialog_message_leave_forum">Esteu segur que voleu sotir del fòrum? Els contactes amb qui heu compartit aquest fòrum podrien deixar de rebre\'n actualitzacions</string>
<string name="dialog_button_leave">Abandona</string>
<string name="forum_left_toast">Abandona el fòrum</string>
<!--Forum Sharing-->
<string name="forum_share_button">Comparteix el fòrum</string>
<string name="contacts_selected">Contactes seleccionats</string>
<string name="activity_share_toolbar_header">Tria contactes</string>
<string name="no_contacts_selector">Sembla que sou nou aquí i no teniu cap contacte.\n\nTorneu aquí després d\'afegir el primer contacte.</string>
<string name="forum_shared_snackbar">Fòrum compartit amb els contactes seleccionats</string>
<string name="forum_share_message">Afegiu un missatge (opcional)</string>
<string name="forum_share_error">S\'ha produït un error en compartir aquest fòrum.</string>
<string name="forum_invitation_received">%1$s ha compartit el fòrum «%2$s» amb vós.</string>
<string name="forum_invitation_sent">Heu compartit el fòrum «%1$s» amb %2$s.</string>
<string name="forum_invitations_title">Invitacions al fòrum</string>
<string name="forum_invitation_exists">Ja heu acceptat una invitació a aquest fòrum. L\'acceptació de més invitacions augmentarà i enfortirà la comunicació al fòrum.</string>
<string name="forum_declined_toast">S\'ha rebutjat la invitació al fòrum</string>
<string name="shared_by_format">Compartit per 1%s</string>
<string name="forum_invitation_response_accepted_sent">Heu acceptat la invitació del fòrum de %s.</string>
<string name="forum_invitation_response_declined_sent">Heu rebutjat la invitació del fòrum de %s.</string>
<string name="forum_invitation_response_accepted_received">%s va acceptar la invitació del fòrum.</string>
<string name="forum_invitation_response_declined_received">%s va rebutjar la invitació del fòrum.</string>
<string name="sharing_status_forum">Qualsevol membre d\'un fòrum pot compartir-lo amb els seus contactes. Esteu compartint aquest fòrum amb els següents contactes. També hi pot haver altres membres que no pugueu veure.</string>
<string name="shared_with">Compartit amb %1$d (%2$d en línia)</string>
<plurals name="forums_shared">
<item quantity="one">%d fòrum compartit per contactes</item>
<item quantity="other">%d fòrums compartits per contactes</item>
</plurals>
<string name="nobody">Ningú</string>
<!--Blogs-->
<string name="read_more">llegir més</string>
<!--Blog Sharing-->
@@ -284,5 +223,4 @@
<string name="close">Tanca</string>
<!--Sign Out-->
<!--Screen Filters & Tapjacking-->
<!--Permission Requests-->
</resources>

View File

@@ -1,387 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_title">Vítejte v Briar</string>
<string name="setup_name_explanation">Vaše uživatelské jméno bude zobrazeno u jakéhokoli obsahu, který zveřejníte. Následná změna již nebude možná.</string>
<string name="setup_next">Další</string>
<string name="setup_password_intro">Zvolte heslo</string>
<string name="setup_password_explanation">Váš Briar účet je šifrován a uložen ve vašem zařízení, nikoli v cloudu. Pokud zapomenete své heslo nebo odinstalujete Briar, obnovit Váš účet již nebude možné.\n\nZvolte si dlouhé heslo, které je těžké uhádnout, například čtyři náhodné fráze nebo deset náhodných písmen, čísel a symbolů.</string>
<string name="setup_doze_title">Připojení na pozadí</string>
<string name="setup_doze_intro">Pro příjem zpráv je nutné, aby byl Briar stále spuštěn na pozadí.</string>
<string name="setup_doze_explanation">Pro příjem zpráv je nutné, aby byl Briar spuštěn na pozadí. Prosím, vypněte optimalizaci baterie, jedině tak bude Briar stále připojen.</string>
<string name="setup_doze_button">Povolit připojení</string>
<string name="choose_nickname">Zvolte si uživatelské jméno</string>
<string name="choose_password">Zvolte si heslo</string>
<string name="confirm_password">Potvrďte své heslo</string>
<string name="name_too_long">Jméno je příliš dlouhé</string>
<string name="password_too_weak">Heslo je příliš slabé</string>
<string name="passwords_do_not_match">Zadaná hesla se neshodují</string>
<string name="create_account_button">Vytvořit účet</string>
<string name="more_info">Další informace</string>
<string name="don_t_ask_again">Znovu se již neptat</string>
<string name="setup_huawei_text">Klikněte na níže uvedené tlačítko a ujistěte se, že byl Briar zařazen mezi \"Chráněné aplikace\".</string>
<string name="setup_huawei_button">Chránit Briar</string>
<string name="setup_huawei_help">Pokud nebyl Briar přidán mezi chráněné aplikace, nebude ho možné spustit na pozadí.</string>
<string name="warning_dozed">%s nebylo možné spustit na pozadí</string>
<!--Login-->
<string name="enter_password">Zadejte své heslo:</string>
<string name="try_again">Zadali jste špatné heslo, zkuste to znovu</string>
<string name="sign_in_button">Přihlásit se</string>
<string name="forgotten_password">Nepamatuji si své heslo</string>
<string name="dialog_title_lost_password">Ztracené heslo</string>
<string name="dialog_message_lost_password">Váš Briar účet je šifrován a uložen ve vašem zařízení, nikoli v cloudu, z tohoto důvodu není možné obnovit Vaše heslo. Chcete odstranit svůj účet a začít znovu?\n\nUpozornění: Vaše identita, kontakty a zprávy budou permanentně ztraceny.</string>
<string name="startup_failed_notification_title">Briar nemohl být spuštěn</string>
<string name="startup_failed_notification_text">Můžete zkusit Briar přeinstalovat.</string>
<string name="startup_failed_activity_title">Spuštění Briar selhalo.</string>
<string name="startup_failed_db_error">Z nějakého důvodu je Briar databáze poškozena a není možná její obnova. Váš účet, data a všechny vaše kontakty jsou ztraceny. Bohužel musíte Briar přeinstalovat a nastavit nový účet.</string>
<string name="startup_failed_service_error">Briar nemohl spustit vyžadovaný plugin. Tento problém vyřeší přeinstalování Briar. Mějte prosím na vědomí, že přeinstalováním ztratíte veškerá data pro váš účet, protože Briar nepoužívá centralizované ukládání vašich dat na serverech.</string>
<plurals name="expiry_warning">
<item quantity="one">Toto je testovací verze Briar. Váš účet a jeho platnost vyprší po %d dnech a není možné ho obnovit.</item>
<item quantity="few">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>
<string name="expiry_update">Datum expirace pro testování bylo prodlouženo. Váš účet nyní bude expirovat po %d dnech.</string>
<string name="expiry_date_reached">Platnost tohoto software vypršela.\nDěkujeme za jeho otestování!</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">Otevřít navigační lištu</string>
<string name="nav_drawer_close_description">Zavřít navigační lištu</string>
<string name="contact_list_button">Kontakty</string>
<string name="groups_button">Soukromá skupina</string>
<string name="forums_button">Fóra</string>
<string name="blogs_button">Blogy</string>
<string name="settings_button">Nastavení</string>
<string name="sign_out_button">Odhlásit se</string>
<!--Transports-->
<string name="transport_tor">Internet</string>
<string name="transport_bt">Bluetooth</string>
<string name="transport_lan">Wi-Fi</string>
<!--Notifications-->
<string name="ongoing_notification_title">Přihlásit se do Briar</string>
<string name="ongoing_notification_text">Kliknutím otevřít Briar</string>
<plurals name="private_message_notification_text">
<item quantity="one">Nová soukromá zpráva</item>
<item quantity="few">%d nových soukromých zpráv.</item>
<item quantity="other">%d nových soukromých zpráv.</item>
</plurals>
<plurals name="group_message_notification_text">
<item quantity="one">Nová zpráva ve skupině.</item>
<item quantity="few">%d nových zpráv ve skupině. </item>
<item quantity="other">%d nových zpráv ve skupině.</item>
</plurals>
<plurals name="forum_post_notification_text">
<item quantity="one">Nový příspěvek ve fóru.</item>
<item quantity="few">%dnových příspěvků ve fóru. </item>
<item quantity="other">%d nových příspěvků ve fóru.</item>
</plurals>
<plurals name="blog_post_notification_text">
<item quantity="one">Nový příspěvek v blogu.</item>
<item quantity="few">%dnových příspěvků v blogu. </item>
<item quantity="other">%d nových příspěvků v blogu.</item>
</plurals>
<!--Misc-->
<string name="now">nyní</string>
<string name="show">Ukázat</string>
<string name="hide">Skrýt</string>
<string name="ok">Ok</string>
<string name="cancel">Zrušit</string>
<string name="got_it">Chápu</string>
<string name="delete">Odstranit</string>
<string name="accept">Přijmout</string>
<string name="decline">Odmítnout</string>
<string name="online">Online</string>
<string name="offline">Offline</string>
<string name="send">Odeslat</string>
<string name="allow">Povolit</string>
<string name="open">Otevřít</string>
<string name="no_data">Žádná data</string>
<string name="ellipsis">...</string>
<string name="text_too_long">Zadaný text je příliš dlouhý</string>
<string name="show_onboarding">Zobrazit dialog pro pomoc</string>
<string name="fix">Opravit</string>
<string name="help">Pomoc</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">Zdá se, že tu jste noví a dosud nemáte žádné kontakty.\n\nKliknětě na + v horní části a sledujte instrukce jak přidat kontakty.\n\n Prosím pamatuj: Můžeš přidat pouze nové kontakty, se kterými jsi tváří-tvář. Je to prevence situacím, kdy se za Tebe bude chtít někdo vydávat, či bude chtít číst Tvé zprávy.</string>
<string name="date_no_private_messages">Žádné zprávy</string>
<string name="no_private_messages">Toto je konverzace a její náhled.\n\nZdá se, že postrádáte konverzace.\n\nKliknutím na vstupní pole v dolní části zahajte konverzaci.</string>
<string name="message_hint">Psát zprávu</string>
<string name="delete_contact">Odstranit kontakt</string>
<string name="dialog_title_delete_contact">Potvrdit odstranění kontaktu</string>
<string name="dialog_message_delete_contact">Jste si jisti, že chcete odstranit kontakt a všechny související zprávy s tímto kontaktem?</string>
<string name="contact_deleted_toast">Kontakt odstraněn</string>
<!--Adding Contacts-->
<string name="add_contact_title">Přidat kontakt</string>
<string name="face_to_face">Musíte se osobně setkat s osobou, kterou si chcete přidat jako kontakt.\n\nToto v budoucnu zabrání komukoli, aby se za vás vydával nebo četl Vaše zprávy.</string>
<string name="continue_button">Pokračovat</string>
<string name="connection_failed">Chyba spojení</string>
<string name="try_again_button">Zkusit znovu</string>
<string name="waiting_for_contact_to_scan">Čekání na scan kontaktu a spojení\u2026</string>
<string name="exchanging_contact_details">Výměna detailů kontaktu\u2026</string>
<string name="contact_added_toast">Kontakt byl přidán: %s</string>
<string name="contact_already_exists">Kontakt %s již existuje</string>
<string name="contact_exchange_failed">Chyba výměny kontaktu</string>
<string name="qr_code_invalid">QR kód je neplatný</string>
<string name="camera_error">Vyskytla se chyba fotoaparátu</string>
<string name="connecting_to_device">Připojování k zařízení\u2026</string>
<string name="authenticating_with_device">Ověřování se zařízením\u2026</string>
<string name="connection_aborted_local">Spojení bylo přerušeno! To může znamenat, že se někdo pokouší zasáhnout do vašeho spojení.</string>
<string name="connection_aborted_remote">Připojení bylo přerušeno vaším kontaktem! To může znamenat, že se někdo pokouší zasahovat do vašeho připojení</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Uvést vaše kontakty</string>
<string name="introduction_onboarding_text">Můžete si navzájem představit své kontakty, takže se následně nemusejí setkat osobně, aby se připojili k Briar.</string>
<string name="introduction_activity_title">Vybrat kontakt</string>
<string name="introduction_message_title">Pozvat kontakty</string>
<string name="introduction_message_hint">Přidat zprávu (volitelné)</string>
<string name="introduction_button">Vytvořit pozvání</string>
<string name="introduction_sent">Vaše pozvání bylo odesláno.</string>
<string name="introduction_error">Vyskytla se chyba při tvorbě pozvání.</string>
<string name="introduction_response_error">Chyba při odpovědi na pozvání.</string>
<string name="introduction_request_sent">Požádali jste o pozvání %1$s do %2$s.</string>
<string name="introduction_request_received">%1$s vás požádal o uvedení do %2$s. Chcete přidat %2$s mezi vaše kontakty?</string>
<string name="introduction_request_exists_received">%1$s vás požádal o představení s %2$s, ale %2$s je již ve vašich kontaktech. Pravděpodobně to %1$s neví, ale stále můžete odpovědět:</string>
<string name="introduction_request_answered_received">%1$s vás žádá o pozvání do %2$s.</string>
<string name="introduction_response_accepted_sent">Přijali jste pozvání do %1$s.</string>
<string name="introduction_response_declined_sent">Odmítli jste představení do %1$s.</string>
<string name="introduction_response_accepted_received">%1$s přijal pozvání do %2$s.</string>
<string name="introduction_response_declined_received">%1$s odmítl pozvání do %2$s.</string>
<string name="introduction_response_declined_received_by_introducee">%1$s řekl, že %2$s odmítl pozvání.</string>
<plurals name="introduction_notification_text">
<item quantity="one">Nový kontakt byl přidán.</item>
<item quantity="few">Bylo přidáno %d nových kontaktů.</item>
<item quantity="other">%d nových kontaktů bylo přidáno.</item>
</plurals>
<!--Private Groups-->
<string name="groups_list_empty">Nepodílíte se na žádné skupině.\n\nKlikněte na ikonu + výše pro vytvoření vaší skupiny nebo požádejte některého z vašich kontaktů o pozvání do některých skupin.</string>
<string name="groups_created_by">Vytvořeno %s</string>
<plurals name="messages">
<item quantity="one">%d zpráva</item>
<item quantity="few">%d zpráv</item>
<item quantity="other">%d zpráv</item>
</plurals>
<string name="groups_group_is_empty">Tato skupina je prázdná</string>
<string name="groups_group_is_dissolved">Tato skupina byla rozpuštěna</string>
<string name="groups_remove">Odstranit</string>
<string name="groups_create_group_title">Vytvořit soukromou skupinu</string>
<string name="groups_create_group_button">Vytvořit skupinu</string>
<string name="groups_create_group_invitation_button">Poslat pozvánku</string>
<string name="groups_create_group_hint">Vyberte jméno pro vaši soukromou skupinu</string>
<string name="groups_invitation_sent">Skupinová pozvánka byla odeslána</string>
<string name="groups_message_sent">Zpráva odeslána</string>
<string name="groups_member_list">Seznam členů</string>
<string name="groups_invite_members">Pozvat členy</string>
<string name="groups_member_created_you">Vytvořili jste skupinu</string>
<string name="groups_member_created">%s vytvořil skupinu</string>
<string name="groups_member_joined_you">Vstoupili jste do skupiny</string>
<string name="groups_member_joined">%s se připojil ke skupině</string>
<string name="groups_leave">Opustit skupinu</string>
<string name="groups_leave_dialog_title">Potvrdit opuštění skupiny</string>
<string name="groups_leave_dialog_message">Jste si jisti, že chcete opustit tuto skupinu?</string>
<string name="groups_dissolve">Rozpustit skupinu</string>
<string name="groups_dissolve_dialog_title">Potvrzení rozpuštění skupiny</string>
<string name="groups_dissolve_dialog_message">Jste si jisti, že chcete tuto skupinu rozpustit?\n\nVšichni členové nebudou moci pokračovat v konverzacích a taktéž nebudou schopni přijímat zprávy.</string>
<string name="groups_dissolve_button">Rozpuštění</string>
<string name="groups_dissolved_dialog_title">Skupina byla rozpuštěna</string>
<string name="groups_dissolved_dialog_message">Zakladatel tuto skupinu rozpustil.\n\nV této skupině již nemůžeš psát zprávy a taktéž přijímat příspěvky, které byly napsány.</string>
<!--Private Group Invitations-->
<string name="groups_invitations_title">Skupinové pozvánky</string>
<string name="groups_invitations_invitation_sent">Pozvali jste %1$s k připojení ke skupině \"%2$s\".</string>
<string name="groups_invitations_invitation_received">%1$s vás pozval, abyste se připojili ke skupině \"%2$s\".</string>
<string name="groups_invitations_joined">Připojení ke skupině</string>
<string name="groups_invitations_declined">Pozvání do skupiny bylo odmítnuto</string>
<plurals name="groups_invitations_open">
<item quantity="one">%d otevřená skupinová pozvánka</item>
<item quantity="few">%d otevřených skupinových pozvánek</item>
<item quantity="other">%d otevřených skupinových pozvánek</item>
</plurals>
<string name="groups_invitations_response_accepted_sent">Přijali jste pozvání do skupiny od %s.</string>
<string name="groups_invitations_response_declined_sent">Odmítli jste pozvání do skupiny od %s.</string>
<string name="groups_invitations_response_accepted_received">%s přijatých pozvání ke skupině.</string>
<string name="groups_invitations_response_declined_received">%s odmítnutých pozvání ke skupině.</string>
<string name="sharing_status_groups">Pouze zakladatel může pozvat nové členy do skupiny. Níže jsou všichni současní členové skupiny.</string>
<!--Private Groups Revealing Contacts-->
<string name="groups_reveal_contacts">Odkrýt kontakty</string>
<string name="groups_reveal_dialog_message">Můžete si vybrat, zda chcete své kontakty odhalit všem současným i budoucím členům této skupiny.\n\nPřidáním kontaktů je vaše připojení ke skupině rychlejší a spolehlivější, protože můžete komunikovat s odhalenými kontakty i když je tvůrce skupiny offline.</string>
<string name="groups_reveal_visible">Vztah s kontaktem je viditelný ve skupině</string>
<string name="groups_reveal_visible_revealed_by_us">Vztah s kontaktem je viditelný ve skupině (Tebou odkrytý)</string>
<string name="groups_reveal_visible_revealed_by_contact">Vztah s kontaktem je viditelný ve skupině (byl odkrytý %s)</string>
<string name="groups_reveal_invisible">Vztah s kontaktem není viditelný ve skupině</string>
<!--Forums-->
<string name="no_forums">Dosud nemáte žádné fórum.\n\nProč si nějaké své nevytvoříte kliknutím v horní části na symbol +?\n\nMůžete taktéž požádat své kontakty o sdílení některých fór s vámi.</string>
<string name="create_forum_title">Vytvořit fórum</string>
<string name="choose_forum_hint">Vybrat jméno pro vaše fórum</string>
<string name="create_forum_button">Vytvořit fórum</string>
<string name="forum_created_toast">Fórum vytvořeno</string>
<string name="no_forum_posts">Toto fórum je prázdné.\n\nPoužij ikonu tužky nahoře k vytvoření prvního příspěvku.\n\nCítíš se tu dobře? Sdílej toto fórum s vícero svými kontakty.</string>
<string name="no_posts">Žádné příspěvky</string>
<plurals name="posts">
<item quantity="one">%d příspěvek</item>
<item quantity="few">%d příspěvků</item>
<item quantity="other">%d příspěvků</item>
</plurals>
<string name="forum_new_entry_posted">Záznam byl na fóru zveřejněn</string>
<string name="forum_new_message_hint">Nový záznam</string>
<string name="forum_message_reply_hint">Nová odpověď</string>
<string name="btn_reply">Odpověď</string>
<string name="forum_leave">Opustit fórum</string>
<string name="dialog_title_leave_forum">Potvrdit opuštění fóra</string>
<string name="dialog_message_leave_forum">Opravdu chcete opustit toto fórum? Kontakty, které jste sdíleli s tímto fórem, mohou být odebrány z příjmu aktualizací tohoto fóra.</string>
<string name="dialog_button_leave">Opustit</string>
<string name="forum_left_toast">Opustit fórum</string>
<!--Forum Sharing-->
<string name="forum_share_button">Sdílet fórum</string>
<string name="contacts_selected">Zvolené kontakty</string>
<string name="activity_share_toolbar_header">Zvolit kontakty</string>
<string name="no_contacts_selector">Zdá se, že jste tu noví a doposud nemáte žádné kontakty.\n\nProsím, vraťte se zpět po přidání svého prvního kontaktu.</string>
<string name="forum_shared_snackbar">Fórum sdíleno s vybranými kontakty</string>
<string name="forum_share_message">Přidat zprávu (volitelné)</string>
<string name="forum_share_error">Při sdílení tohoto fóra nastala chyba.</string>
<string name="forum_invitation_received">%1$s sdílel fórum \"%2$s\" s vámi.</string>
<string name="forum_invitation_sent">Sdíleli jste fórum \"%1$s\" s %2$s.</string>
<string name="forum_invitations_title">Pozvání do fóra</string>
<string name="forum_invitation_exists">Již jste přijali pozvání do tohoto fóra. Přijímání dalších pozvánek povede a posílí komunikaci ve fóru.</string>
<string name="forum_joined_toast">Vstoupit do fóra</string>
<string name="forum_declined_toast">Pozvánka do fóra byla zamítnuta</string>
<string name="shared_by_format">Sdíleno %s</string>
<string name="forum_invitation_already_sharing">Již sdílené</string>
<string name="forum_invitation_response_accepted_sent">Přijali jste pozvání do fóra od %s.</string>
<string name="forum_invitation_response_declined_sent">Odmítli jste pozvání do fóra od %s.</string>
<string name="forum_invitation_response_accepted_received">%s přijatých pozvání do fóra.</string>
<string name="forum_invitation_response_declined_received">%s odmítnutých pozvání do fóra.</string>
<string name="sharing_status">Sdílet status</string>
<string name="sharing_status_forum">Každý člen fóra to může sdílet se svými kontakty. Vy sdílíte toto fórum s následujícími kontakty. Mohou existovat i další členové, které nevidíte.</string>
<string name="shared_with">Sdíleno s %1$d (%2$d online)</string>
<plurals name="forums_shared">
<item quantity="one">%d fórum sdíleno kontakty</item>
<item quantity="few">%d fór sdíleno kontakty </item>
<item quantity="other">%d fór sdíleno kontakty</item>
</plurals>
<string name="nobody">Nikdo</string>
<!--Blogs-->
<string name="blogs_other_blog_empty_state">Tento blog je momentálně prázdný.\n\nPokud autor nebo osoba, sdílící s vámi tento blog, ještě nic nenapsali, pro synchronizaci příspěvků je tedy nutné, aby byli online.</string>
<string name="read_more">Číst dál</string>
<string name="blogs_write_blog_post">Napsat příspěvek do Blogu</string>
<string name="blogs_write_blog_post_body_hint">Svůj příspěvek do blogu zadejte zde</string>
<string name="blogs_publish_blog_post">Publikovat</string>
<string name="blogs_blog_post_created">Příspěvek do blogu byl vytvořen</string>
<string name="blogs_blog_post_received">Přijatý nový příspěvek v blogu.</string>
<string name="blogs_blog_post_scroll_to">Přejít na</string>
<string name="blogs_feed_empty_state">Toto je globální kanál blogu.\n\nZdá se, že ještě nikdo neblogoval..\n\nBuďte první, klikněte na ikonu tužky a napište do blogu nový příspěvek.</string>
<string name="blogs_remove_blog">Odstranit blog</string>
<string name="blogs_remove_blog_dialog_message">Jste si jisti, že chcete odstranit tento blog a všechny příspěvky v něm?\nPamatujte, že blog nebude odstraněn ze zařízení ostatních lidí.</string>
<string name="blogs_remove_blog_ok">Odstranit blog</string>
<string name="blogs_blog_removed">Blog byl odstraněn</string>
<string name="blogs_reblog_comment_hint">Přidat komentář (volitelné)</string>
<string name="blogs_reblog_button">Reblog</string>
<!--Blog Sharing-->
<string name="blogs_sharing_share">Sdílet blog</string>
<string name="blogs_sharing_error">Vyskytla se chyba při sdílení tohoto blogu.</string>
<string name="blogs_sharing_button">Sdílet blog</string>
<string name="blogs_sharing_snackbar">Blog sdílen s vybranými kontakty.</string>
<string name="blogs_sharing_response_accepted_sent">Přijali jste pozvání do blogu od %s.</string>
<string name="blogs_sharing_response_declined_sent">Odmítli jste pozvání do blogu od %s.</string>
<string name="blogs_sharing_response_accepted_received">%s schválených pozvání do blogu.</string>
<string name="blogs_sharing_response_declined_received">%s odmítnutých pozvání do blogu.</string>
<string name="blogs_sharing_invitation_received">%1$s sdílel blog \"%2$s\" s vámi.</string>
<string name="blogs_sharing_invitation_sent">Sdíleli jste blog \"%1$s\" s %2$s.</string>
<string name="blogs_sharing_invitations_title">Pozvánky do blogu</string>
<string name="blogs_sharing_joined_toast">Přihlásit se k blogu</string>
<string name="blogs_sharing_declined_toast">Pozvání do blogu bylo zamítnuto</string>
<string name="sharing_status_blog">Kdokoli, kdo odebírá blog ho může sdílet se svými kontakty. Sdílíte tento blog s následujícími kontakty. Mohou existovat i jiní účastníci, které nevidíte.</string>
<!--RSS Feeds-->
<string name="blogs_rss_feeds_import">Import RSS kanálu</string>
<string name="blogs_rss_feeds_import_button">Import</string>
<string name="blogs_rss_feeds_import_hint">Zadejte URL adresu RSS kanálu</string>
<string name="blogs_rss_feeds_import_error">Omlouváme se! Vyskytla se chyba při importu vašeho kanálu.</string>
<string name="blogs_rss_feeds_manage">Správa RSS kanálů</string>
<string name="blogs_rss_feeds_manage_imported">Importováno:</string>
<string name="blogs_rss_feeds_manage_author">Autor:</string>
<string name="blogs_rss_feeds_manage_updated">Naposledy aktualizováno:</string>
<string name="blogs_rss_remove_feed">Odstranit kanál</string>
<string name="blogs_rss_remove_feed_dialog_message">Skutečně chceš smazat tento kanál a s ním spojené příspěvky?\nVeškeré příspěvky, které jste sdíleli, nebudou odstraněny ze zařízení jiných lidí.</string>
<string name="blogs_rss_remove_feed_ok">Odstranit kanál</string>
<string name="blogs_rss_feeds_manage_delete_error">Kanál nemohl být odstraněn !</string>
<string name="blogs_rss_feeds_manage_empty_state">Nemáte naimportovaný žádný RSS kanál.\n\nProč nekliknete na znak plus v horní části obrazovky, kde můžete přidat svůj první zdroj?</string>
<string name="blogs_rss_feeds_manage_error">Vyskytl se problém s načtením vašeho kanálu příspěvků. Zkuste to prosím později.</string>
<!--Settings Network-->
<string name="network_settings_title">Sítě</string>
<string name="bluetooth_setting">Spojení přes Bluetooth</string>
<string name="bluetooth_setting_enabled">Vždy, když jsou kontakty v blízkosti</string>
<string name="bluetooth_setting_disabled">Jen když přidáte kontakty</string>
<string name="tor_network_setting">Spojení přes Tor</string>
<string name="tor_network_setting_never">Nikdy</string>
<string name="tor_network_setting_wifi">Jen když používáte Wi-Fi</string>
<string name="tor_network_setting_always">Když používáte Wi-Fi nebo mobilní data</string>
<!--Settings Security and Panic-->
<string name="security_settings_title">Zabezpečení</string>
<string name="change_password">Změnit heslo</string>
<string name="current_password">Zadejte vaše současné heslo:</string>
<string name="choose_new_password">Zvolte si nové heslo:</string>
<string name="confirm_new_password">Potvrďte nové heslo:</string>
<string name="password_changed">Heslo bylo změněno.</string>
<string name="panic_setting">Nastavení tlačítka Paniky</string>
<string name="panic_setting_title">Tlačítko Paniky</string>
<string name="panic_setting_hint">Nastavte, jak bude Briar reagovat, když použijete aplikaci tlačítka paniky</string>
<string name="panic_app_setting_title">Aplikace Panik tlačítko</string>
<string name="unknown_app">neznámá aplikace</string>
<string name="panic_app_setting_summary">Žádná aplikace nebyla nastavena</string>
<string name="panic_app_setting_none">Žádný</string>
<string name="dialog_title_connect_panic_app">Potvrdit aplikaci Paniky</string>
<string name="dialog_message_connect_panic_app">Jste si jisti, že chcete povolit %1$s spustit akci zneškodnění pomocí tlačítka paniky?</string>
<string name="lock_setting_title">Odhlásit se</string>
<string name="lock_setting_summary">Odhlásit se z Briar, pokud je stisknuto tlačítko paniky</string>
<string name="purge_setting_title">Odstranit účet</string>
<string name="purge_setting_summary">Vymazat váš Briar účet pokud je stlačeno tlačítko paniky. Upozornění: Toto trvale vymaže vaši identitu, kontakty a zprávy</string>
<string name="uninstall_setting_title">Odinstalovat Briar</string>
<string name="uninstall_setting_summary">Toto vyžaduje manuální potvrzení v události paniky</string>
<!--Settings Notifications-->
<string name="notification_settings_title">Oznámení</string>
<string name="notify_private_messages_setting_title">Soukromé zprávy</string>
<string name="notify_private_messages_setting_summary">Zobrazit upozornění pro soukromé zprávy</string>
<string name="notify_group_messages_setting_title">Skupinové zprávy</string>
<string name="notify_group_messages_setting_summary">Zobrazit upozornění pro skupinové zprávy</string>
<string name="notify_forum_posts_setting_title">Příspěvky fóra</string>
<string name="notify_forum_posts_setting_summary">Zobrazit upozornění pro příspěvky fóra</string>
<string name="notify_blog_posts_setting_title">Příspěvky v blogu</string>
<string name="notify_blog_posts_setting_summary">Zobrazit upozornění pro příspěvky v blogu</string>
<string name="notify_vibration_setting">Vibrovat</string>
<string name="notify_lock_screen_setting_title">Zamčená obrazovka</string>
<string name="notify_lock_screen_setting_summary">Zobrazit oznámení na zamčené obrazovce</string>
<string name="notify_sound_setting">Zvuk</string>
<string name="notify_sound_setting_default">Výchozí vyzvánění</string>
<string name="notify_sound_setting_disabled">Žádný</string>
<string name="choose_ringtone_title">Zvolit vyzváněcí tón</string>
<string name="cannot_load_ringtone">Nelze načíst vyzváněcí tón</string>
<!--Settings Feedback-->
<string name="feedback_settings_title">Zpětná vazba</string>
<string name="send_feedback">Poslat zpětnou vazbu</string>
<!--Link Warning-->
<string name="link_warning_title">Odkaz varování</string>
<string name="link_warning_intro">Chystáte se otevřít následující odkaz pomocí externí eplikace.</string>
<string name="link_warning_text">Toto může být užito pro vaší identifikaci. Přemýšlejte o tom, zda důvěřujete osobě, která vám poslala tento odkaz, a zvažte, zda ho otevřete s aplikací Orfox.</string>
<string name="link_warning_open_link">Otevřít odkaz</string>
<!--Crash Reporter-->
<string name="crash_report_title">Hlášení o pádu Briar</string>
<string name="briar_crashed">Promiňte, Briar se zhroutil.</string>
<string name="not_your_fault">Toto není vaše chyba.</string>
<string name="please_send_report">Vaše pomoc je důležitá. Díky ní můžeme postavit lepší Briar tím, že nám pošlete zprávu o pádu aplikace.</string>
<string name="report_is_encrypted">Slibujeme, že hlášení je zašifrováno a bezpečně odesláno.</string>
<string name="feedback_title">Zpětná vazba</string>
<string name="describe_crash">Popište, co se stalo (nepovinné)</string>
<string name="enter_feedback">Vložte svou zpětnou vazbu</string>
<string name="optional_contact_email">Vaše emailová adresa (nepovinné)</string>
<string name="include_debug_report_crash">Zahrnout anonymní data o tomto selhání</string>
<string name="include_debug_report_feedback">Zahrnout anonymní data o tomto zařízení</string>
<string name="could_not_load_report_data">Data hlášení nemohla být načtena.</string>
<string name="send_report">Odeslat hlášení</string>
<string name="close">Zavřít</string>
<string name="dev_report_saved">Hlášení uloženo. Odesláno bude při příštím přihlášení do Briar.</string>
<!--Sign Out-->
<string name="progress_title_logout">Odhlásit se z Briar...</string>
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Bylo zjištěno překrytí obrazovky</string>
<string name="screen_filter_body">Jiná aplikace překrývá Briar. Pro vaši bezpečnost, Briar nebude odpovídat na kliknutí, pokud jej bude jiná aplikace překrývat.\n\nZkuste vypnout následující aplikace, když používáte Briar:\n\n%1$s</string>
<!--Permission Requests-->
<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_denied_body">Odmítli jste udělit oprávnění přístupu k fotoaparátu, avšak pro přidání kontaktů je nutné použití fotoaparátu.\n\nZvažte prosím, opětovné udělení přístupu.</string>
<string name="permission_camera_denied_toast">Oprávnění pro přístup k fotoaparátu nebylo uděleno</string>
</resources>

View File

@@ -1,15 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_title">Willkommen bei Briar</string>
<string name="setup_name_explanation">Dein Benutzername wird neben deinem geposteten Inhalt angezeigt. Du kannst diesen nicht mehr ändern, nachdem du dein Konto erstellt hast.</string>
<string name="setup_next">Weiter</string>
<string name="setup_password_intro">Wähle ein Passwort</string>
<string name="setup_password_explanation">Dein Briar-Konto wird auf deinem Gerät verschlüsselt und nicht in der Cloud gespeichert. Wenn du dein Passwort vergisst oder Briar deinstallierst, gibt es keine Möglichkeit, dein Konto wiederherzustellen.\n\nWähle ein langes Passwort, das schwer zu erraten ist, z.B. vier zufällige Wörter oder zehn zufällige Buchstaben, Zahlen und Symbole.</string>
<string name="setup_doze_title">Hintergrundverbindungen</string>
<string name="setup_doze_intro">Um Nachrichten zu empfangen, muss Briar im Hintergrund verbunden bleiben.</string>
<string name="setup_doze_explanation">Um Nachrichten zu empfangen, muss Briar im Hintergrund verbunden bleiben. Bitte deaktiviere die Batterieoptimierungen, damit Briar in Verbindung bleiben kann.</string>
<string name="setup_doze_button">Erlaube Verbindungen</string>
<string name="setup_title">Briar einrichten</string>
<string name="setup_explanation">Dein Briar-Konto wird verschlüsselt auf deinem Gerät gespeichert und nicht mit der \"Cloud\" synchronisiert. Wenn du Briar deinstallierst oder dein Passwort vergisst, können das Konto und deine Daten nicht wiederhergestellt werden.</string>
<string name="choose_nickname">Wähle deinen Benutzernamen</string>
<string name="choose_password">Wähle dein Passwort</string>
<string name="confirm_password">Passwort bestätigen</string>
@@ -17,29 +10,22 @@
<string name="password_too_weak">Passwort zu schwach</string>
<string name="passwords_do_not_match">Passwörter stimmen nicht überein</string>
<string name="create_account_button">Konto anlegen</string>
<string name="more_info">Weitere Informationen</string>
<string name="don_t_ask_again">Frage nicht noch einmal</string>
<string name="setup_huawei_text">Bitte tippe auf die Schaltfläche unten und stelle sicher, dass Briar unter \"Geschützte Apps\" angezeigt wird.</string>
<string name="setup_huawei_button">Schütze Briar</string>
<string name="setup_huawei_help">Wenn Briar nicht zur Liste der geschützten Apps hinzugefügt wird, kann es nicht im Hintergrund ausgeführt werden.</string>
<string name="warning_dozed">%s konnte nicht im Hintergrund ausgeführt werden</string>
<!--Login-->
<string name="enter_password">Passwort eingeben:</string>
<string name="try_again">Passwort falsch, bitte erneut versuchen</string>
<string name="sign_in_button">Anmelden</string>
<string name="forgotten_password">Ich habe mein Passwort vergessen</string>
<string name="dialog_title_lost_password">Passwort vergessen</string>
<string name="dialog_message_lost_password">Dein Briar-Konto ist auf deinem Gerät verschlüsselt und nicht in der Cloud gespeichert, deshalb kannst du dein Passwort nicht zurücksetzen. Willst du dein Konto löschen und neu beginnen?\n\nAchtung: Deine bestehenden Identitäten, Kontakte und Nachrichten gehen dann für immer verloren.</string>
<string name="dialog_message_lost_password">Dein Briar-Konto ist verschlüsselt auf deinem Gerät gespeichert und nicht mit der \"Cloud\" synchronisiert. Deshalb können wir dein Passwort nicht zurücksetzen. Willst du dein Konto löschen und neu beginnen?\n\nAchtung: Deine bestehenden Identitäten, Kontakte und Nachrichten gehen dann für immer verloren.</string>
<string name="startup_failed_notification_title">Briar konnte nicht gestartet werden</string>
<string name="startup_failed_notification_text">Möglicherweise hilft eine Neuinstallation von Briar.</string>
<string name="startup_failed_activity_title">Fehler beim Starten von Briar</string>
<string name="startup_failed_db_error">Aus irgendeinem Grund ist deine Briar-Datenbank irreparabel beschädigt. Dein Konto, deine Daten und alle deinen Kontakte sind verloren. Leider musst du Briar neu installieren und ein neues Konto einrichten.</string>
<string name="startup_failed_service_error">Briar konnte ein benötigtes Plugin nicht starten. Normalerweise kann das Problem durch eine Neuinstallation von Briar gelöst werden. Eine Neuinstallation führt jedoch zum Verlust deines Kontos und aller dazugehörigen Daten, da Briar deine Daten nicht auf zentralen Servern speichert.</string>
<string name="startup_failed_db_error">Deine Briar-Datenbank ist korrupt. Briar-Konto, Daten und alle Verbindungen zu Kontakten können nicht mehr wiederhergestellt werden. Deinstalliere Briar und erstelle nach Installation der aktuellen Briar-Version ein neues Konto.</string>
<string name="startup_failed_service_error">Briar konnte ein benötigtes Plugin nicht starten. Normalerweise kann das Problem durch eine Neuinstallation von Briar gelöst werden. Eine Neuinstallation führt jedoch zum Verlust des Kontos und aller dazugehörigen Daten, da Briar deine Daten nicht auf zentralen Servern speichert.</string>
<plurals name="expiry_warning">
<item quantity="one">Dies ist eine Testversion von Briar. Dein Konto läuft in %d Tag ab und kann nicht verlängert werden.</item>
<item quantity="other">Dies ist eine Testversion von Briar. Dein Konto läuft in %d Tagen ab und kann nicht verlängert werden.</item>
<item quantity="one">Dies ist eine Beta-Version von Briar. Dein Konto läuft in %d Tag ab und kann nicht verlängert werden.</item>
<item quantity="other">Dies ist eine Beta-Version von Briar. Dein Konto läuft in %d Tagen ab und kann nicht verlängert werden.</item>
</plurals>
<string name="expiry_update">Das Ablaufdatum des Tests wurde verlängert. Dein Konto läuft nun in %d Tagen ab.</string>
<string name="expiry_date_reached">Diese Software ist abgelaufen.\nDanke dass du Briar getestet hast!</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">Navigationsleiste öffnen</string>
@@ -83,7 +69,8 @@
<string name="delete">Löschen</string>
<string name="accept">Annehmen</string>
<string name="decline">Ablehnen</string>
<string name="online">Online</string>
<string name="options">Optionen</string>
<string name="online">Online</string>
<string name="offline">Offline</string>
<string name="send">Senden</string>
<string name="allow">Erlauben</string>
@@ -92,10 +79,8 @@
<string name="ellipsis"></string>
<string name="text_too_long">Der eingegebene Text ist leider zu lang</string>
<string name="show_onboarding">Hilfe anzeigen</string>
<string name="fix">Behoben</string>
<string name="help">Hilfe</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">Du scheinst hier neu zu sein und noch keine Kontakte zu haben.\n\nTippe auf das \"+\"-Symbol am oberen Bildschirmrand und folge den Anweisungen, um Freunde zu deiner Kontaktliste hinzuzufügen.\n\nDenke daran, dass du neue Kontakte nur dann hinzufügen kannst, wenn du ihnen persönlich begegnest. Das hindert andere daran, sich als deine Person auszugeben oder deine Nachrichten zu lesen.</string>
<string name="no_contacts">Du scheinst hier neu zu sein und noch keine Kontakte zu haben.\n\nTippe auf das \"+\"-Symbol am oberen Bildschirmrand an und folge den Anweisungen, um Freunde zu deiner Kontaktliste hinzuzufügen.\n\nDenke daran, dass du neue Kontakte nur dann hinzufügen kannst, wenn du ihnen persönlich begegnest. Das hindert andere daran, sich als deine Person auszugeben oder deine Nachrichten zu lesen.</string>
<string name="date_no_private_messages">Keine Nachrichten.</string>
<string name="no_private_messages">Das ist die Gesprächsansicht.\n\nEs scheint hier einen Mangel an Gesprächsthemen zu geben.\n\nUm ein neues Gespräch zu beginnen, verwende einfach das Eingabefeld am unteren Bildschirmrand.</string>
<string name="message_hint">Nachricht eingeben</string>
@@ -105,20 +90,31 @@
<string name="contact_deleted_toast">Kontakt gelöscht</string>
<!--Adding Contacts-->
<string name="add_contact_title">Kontakt hinzufügen</string>
<string name="your_nickname">Wähle die zu verwendende Identität:</string>
<string name="face_to_face">Um einen neuen Kontakt hinzuzufügen, ist es notwendig, dass sich beide Kontakte an einem Ort treffen.\n\nDadurch wird betrügerische Identitätsvortäuschung und unautorisierter Kommunikationszugriff verhindert.</string>
<string name="continue_button">Weiter</string>
<string name="your_invitation_code">Dein Einladungs-Code ist</string>
<string name="enter_invitation_code">Bitte gib den Einladungs-Code deines Kontakts an:</string>
<string name="searching_format">Suche nach Kontakt mit Einladungs-Code %06d\u2026</string>
<string name="connection_failed">Verbindung fehlgeschlagen</string>
<string name="could_not_find_contact">Briar kann deinen Kontakt nicht innerhalb der Reichweite finden.</string>
<string name="try_again_button">Noch einmal versuchen</string>
<string name="connected_to_contact">Mit Kontakt verbunden</string>
<string name="calculating_confirmation_code">Bestätigungscode wird berechnet\u2026</string>
<string name="your_confirmation_code">Dein Bestätigungscode ist</string>
<string name="enter_confirmation_code">Bitte gib den Bestätigungscode deines Kontakts ein:</string>
<string name="waiting_for_contact">Warte auf Kontakt\u2026</string>
<string name="waiting_for_contact_to_scan">Warte auf Scan und Verbindung mit dem Kontakt\u2026</string>
<string name="exchanging_contact_details">Kontaktdetails werden ausgetauscht\u2026</string>
<string name="codes_do_not_match">Codes stimmen nicht überein</string>
<string name="interfering">Möglicherweise stört jemand deine Verbindung.</string>
<string name="contact_added_toast">Kontakt hinzugefügt: %s</string>
<string name="contact_already_exists">Kontakt %s existiert bereits</string>
<string name="contact_exchange_failed">Kontaktaustausch fehlgeschlagen</string>
<string name="qr_code_invalid">Der QR-Code ist ungültig</string>
<string name="camera_error">Kamerafehler</string>
<string name="connecting_to_device">Verbinde mit Gerät\u2026</string>
<string name="authenticating_with_device">Authentifiziere Gerät\u2026</string>
<string name="connection_aborted_local">Verbindung abgebrochen! Dies könnte bedeuten, dass jemand versucht, deine Verbindung zu stören.</string>
<string name="connection_aborted_local">Verbindung wurde durch uns abgebrochen! Möglicherweise stört jemand deine Verbindung.</string>
<string name="connection_aborted_remote">Verbindung wurde durch deinen Kontakt abgebrochen! Möglicherweise stört jemand deine Verbindung.</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Mache deine Kontakte untereinander bekannt</string>
@@ -127,8 +123,8 @@
<string name="introduction_message_title">Kontakte untereinander bekannt machen</string>
<string name="introduction_message_hint">Nachricht hinzufügen (optional)</string>
<string name="introduction_button">Kontaktempfehlung abgeben</string>
<string name="introduction_sent">Deine Kontaktempfehlung wurde verschickt.</string>
<string name="introduction_error">Es gab einen Fehler beim Versuch, die Kontaktempfehlung zu verschicken.</string>
<string name="introduction_sent">Deine Kontaktempfehlung wurde verschickt</string>
<string name="introduction_error">Es gab einen Fehler beim Versuch, die Kontaktempfehlung zu verschicken</string>
<string name="introduction_response_error">Fehler bei Antwort auf Kontaktempfehlung</string>
<string name="introduction_request_sent">Du wolltest %1$s an %2$s als Kontakt empfehlen</string>
<string name="introduction_request_received">%1$s schlägt vor, dich als Kontakt an %2$s zu empfehlen. Möchtest du %2$s zu deiner Kontaktliste hinzufügen?</string>
@@ -159,7 +155,7 @@
<string name="groups_create_group_hint">Wähle einen Namen für deine private Gruppe</string>
<string name="groups_invitation_sent">Gruppeneinladung versendet</string>
<string name="groups_message_sent">Nachricht gesendet</string>
<string name="groups_member_list">Mitgliederliste</string>
<string name="groups_member_list">Mitglieder</string>
<string name="groups_invite_members">Mitglieder einladen</string>
<string name="groups_member_created_you">Du hast diese Gruppe erstellt</string>
<string name="groups_member_created">%s hat diese Gruppe erstellt</string>
@@ -193,16 +189,16 @@
<string name="groups_reveal_contacts">Kontakte teilen</string>
<string name="groups_reveal_dialog_message">Kontakte können mit allen derzeitigen und zukünftigen Mitgliedern dieser Gruppe geteilt werden.\n\nDas beschleunigt die Verbindung zu der Gruppe und macht sie zusätzlich zuverlässiger, da Kommunikation mit den Mitgliedern auch dann erfolgen kann, wenn der Ersteller der Gruppe offline ist.</string>
<string name="groups_reveal_visible">Verbindung zum Kontakt ist für die Gruppe sichtbar</string>
<string name="groups_reveal_visible_revealed_by_us">Verbindung zum Kontakt ist für diese Gruppe sichtbar (selbst offengelegt)</string>
<string name="groups_reveal_visible_revealed_by_contact">Verbindung zum Kontakt ist für diese Gruppe sichtbar (offengelegt durch %s)</string>
<string name="groups_reveal_invisible">Verbindung zum Kontakt ist für diese Gruppe nicht sichtbar</string>
<string name="groups_reveal_visible_revealed_by_us">Beziehung zum Kontakt ist für diese Gruppe sichtbar (selbst offengelegt)</string>
<string name="groups_reveal_visible_revealed_by_contact">Beziehung zum Kontakt ist für diese Gruppe sichtbar (offengelegt durch %s)</string>
<string name="groups_reveal_invisible">Beziehung zum Kontakt ist für diese Gruppe nicht sichtbar</string>
<!--Forums-->
<string name="no_forums">Du hast noch keine Foren.\n\nWarum erstellst du nicht einfach selbst ein neues Forum, indem du auf das \"+\"-Symbol am oberen Bildschirmrand tippst?\n\nDu kannst auch deine Kontakte auffordern, Foren mit dir zu teilen.</string>
<string name="create_forum_title">Forum erstellen</string>
<string name="choose_forum_hint">Wähle einen Namen für dein Forum</string>
<string name="create_forum_button">Forum erstellen</string>
<string name="forum_created_toast">Forum wurde erstellt</string>
<string name="no_forum_posts">Dieses Forum ist leer.\n\nBenutze das Stift-Symbol am oberen Bildschirmrand, um den ersten Beitrag zu verfassen.\n\nFühlst du dich einsam hier? Dann teile das Forum mit weiteren Kontakten!</string>
<string name="no_forum_posts">Dieses Forum ist leer.\n\nBenutze das Stift-Icon am oberen Bildschirmrand um den ersten Beitrag zu verfassen.\n\nFühlst du dich einsam hier? Dann teile das Forum mit weiteren Kontakten!</string>
<string name="no_posts">Keine Beiträge</string>
<plurals name="posts">
<item quantity="one">%d Beitrag</item>
@@ -237,7 +233,7 @@
<string name="forum_invitation_response_declined_sent">Du hast die Forumeinladung von %s abgelehnt.</string>
<string name="forum_invitation_response_accepted_received">%s hat die Forumeinladung akzeptiert.</string>
<string name="forum_invitation_response_declined_received">%s hat die Forumseinladung abgelehnt.</string>
<string name="sharing_status">Freigabestatus</string>
<string name="sharing_status">Sharing Status</string>
<string name="sharing_status_forum">Jedes Mitglied eines Forums kann dieses mit seinen Kontakten teilen. Du teilst dieses Forum mit den folgenden Kontakten. Möglicherweise gibt es Mitglieder die nicht sichtbar sind.</string>
<string name="shared_with">Geteilt mit %1$d (%2$d online)</string>
<plurals name="forums_shared">
@@ -246,7 +242,7 @@
</plurals>
<string name="nobody">Niemand</string>
<!--Blogs-->
<string name="blogs_other_blog_empty_state">Dieser Blog ist momentan leer.\n\nEntweder hat der Autor noch nichts geschrieben, oder die Person, die diesen Blog mit dir geteilt hat, muss online gehen, sodass Beiträge synchronisiert werden können.</string>
<string name="blogs_other_blog_empty_state">Dieser Blog ist momentan leer.\n\nEntweder hat der Autor noch nichts geschrieben, oder die Person, die diesen Blog mit dir geteilt hat, muss online kommen, sodass Beiträge synchronisiert werden können.</string>
<string name="read_more">weiterlesen</string>
<string name="blogs_write_blog_post">Blogbeitrag erstellen</string>
<string name="blogs_write_blog_post_body_hint">Gib hier deinen Blogbeitrag ein</string>
@@ -254,7 +250,7 @@
<string name="blogs_blog_post_created">Blogbeitrag erstellt</string>
<string name="blogs_blog_post_received">Neuen Blogbeitrag empfangen</string>
<string name="blogs_blog_post_scroll_to">Scrolle zu</string>
<string name="blogs_feed_empty_state">Dies ist die globale Blog-Zeitleiste.\n\nOffensichtlich hat noch niemand etwas veröffentlicht.\n\nSei die oder der Erste und tippe auf das Stift-Symbol, um einen neuen Blogbeitrag zu verfassen.</string>
<string name="blogs_feed_empty_state">Dies ist die globale Blog-Zeitleiste.\n\nOffensichtlich hat noch niemand etwas veröffentlicht.\n\nSei die oder der Erste und tippe auf das Stift-Icon, um einen neuen Blogbeitrag zu verfassen.</string>
<string name="blogs_remove_blog">Blog entfernen</string>
<string name="blogs_remove_blog_dialog_message">Bist du sicher, dass du diesen Blog und alle dazugehörigen Beiträge löschen möchtest?\nBeachte, dass dies nicht den Blog auf Geräten anderer Leute löscht.</string>
<string name="blogs_remove_blog_ok">Blog entfernen</string>
@@ -263,14 +259,14 @@
<string name="blogs_reblog_button">Rebloggen</string>
<!--Blog Sharing-->
<string name="blogs_sharing_share">Blog teilen</string>
<string name="blogs_sharing_error">Es gab einen Fehler beim Versuch, diesen Blog zu teilen.</string>
<string name="blogs_sharing_error">Es gab einen Fehler beim Versuch, dieses Blog zu teilen</string>
<string name="blogs_sharing_button">Blog teilen</string>
<string name="blogs_sharing_snackbar">Blog wurde mit ausgewählten Kontakten geteilt</string>
<string name="blogs_sharing_response_accepted_sent">Du hast die Blogeinladung von %s akzeptiert.</string>
<string name="blogs_sharing_response_declined_sent">Du hast die Blogeinladung von %s abgelehnt.</string>
<string name="blogs_sharing_response_accepted_received">%s hat die Blogeinladung akzeptiert.</string>
<string name="blogs_sharing_response_declined_received">%s hat die Blogeinladung abgelehnt.</string>
<string name="blogs_sharing_invitation_received">%1$shat den Blog \"%2$s\" mit dir geteilt.</string>
<string name="blogs_sharing_invitation_received">%1$shat den Blog \"%2$s\" mit Dir geteilt.</string>
<string name="blogs_sharing_invitation_sent">Du teilst den Blog \"%1$s\" mit %2$s.</string>
<string name="blogs_sharing_invitations_title">Blogeinladungen</string>
<string name="blogs_sharing_joined_toast">Blog abonniert</string>
@@ -289,7 +285,7 @@
<string name="blogs_rss_remove_feed_dialog_message">Soll der Feed mit allen Posts wirklich gelöscht werden?\nGeteilte Posts werden dabei nicht von anderen Geräten gelöscht.</string>
<string name="blogs_rss_remove_feed_ok">Feed entfernen</string>
<string name="blogs_rss_feeds_manage_delete_error">Der Feed konnte nicht gelöscht werden!</string>
<string name="blogs_rss_feeds_manage_empty_state">Du hast bisher noch keine RSS-Feeds importiert. Tippe auf das \"+\"-Symbol am oberen Bildschirmrand, um einen neuen Feed hinzuzufügen.</string>
<string name="blogs_rss_feeds_manage_empty_state">Du hast bisher noch keine RSS-Feeds importiert. Tippe auf das \"+\"-Icon am oberen Bildschirmrand, um einen neuen Feed hinzuzufügen.</string>
<string name="blogs_rss_feeds_manage_error">Es gab ein Problem beim Laden deiner Feeds. Bitte versuche es später erneut.</string>
<!--Settings Network-->
<string name="network_settings_title">Netzwerke</string>
@@ -315,7 +311,7 @@
<string name="panic_app_setting_summary">Keine App eingerichtet</string>
<string name="panic_app_setting_none">Keine</string>
<string name="dialog_title_connect_panic_app">Bestätige Panik-App</string>
<string name="dialog_message_connect_panic_app">Bist du sicher, dass du %1$s erlauben willst, Panik-Button-Ereignisse auszulösen? Dies kann zum Löschen von Daten führen.</string>
<string name="dialog_message_connect_panic_app">Bist Du sicher, dass Du %1$s erlauben willst, Panik-Button-Ereignisse auszulösen? Dies kann zum Löschen von Daten führen.</string>
<string name="lock_setting_title">Abmelden</string>
<string name="lock_setting_summary">Von Briar abmelden, wenn ein Panik-Button aktiviert wird</string>
<string name="purge_setting_title">Konto löschen</string>
@@ -339,7 +335,6 @@
<string name="notify_sound_setting_default">Standardklingelton</string>
<string name="notify_sound_setting_disabled">Keine</string>
<string name="choose_ringtone_title">Klingelton auswählen</string>
<string name="cannot_load_ringtone">Klingelton kann nicht geladen werden</string>
<!--Settings Feedback-->
<string name="feedback_settings_title">Feedback</string>
<string name="send_feedback">Feedback abschicken</string>
@@ -365,13 +360,8 @@
<string name="close">Schließen</string>
<string name="dev_report_saved">Der Bericht wurde gespeichert. Er wird verschickt, wenn du dich das nächste Mal bei Briar anmeldest.</string>
<!--Sign Out-->
<string name="progress_title_logout">Von Briar abmelden...</string>
<string name="progress_title_logout">Von Briar abmelden ...</string>
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Bildschirmüberlagerung erkannt</string>
<string name="screen_filter_body">Eine andere App überlagert Briar. Um deine Sicherheit zu gewährleisten, reagiert Briar in diesem Fall nicht auf deine Eingaben.\n\nBeende deswegen die folgenden Apps während der Verwendung von Briar:\n\n%1$s</string>
<!--Permission Requests-->
<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_denied_body">Du hast den Zugriff auf die Kamera verweigert, aber das Hinzufügen von Kontakten erfordert die Verwendung der Kamera.\n\nBitte gewähre den Zugriff.</string>
<string name="permission_camera_denied_toast">Berechtigung für Kamera wurde nicht gewährt</string>
</resources>

View File

@@ -1,15 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_title">Bienvenido a Briar</string>
<string name="setup_name_explanation">Tu nombre de usuario aparecerá junto a cualquier contenido que publiques. No puedes cambiarlo después de crear tu cuenta.</string>
<string name="setup_next">Siguiente</string>
<string name="setup_password_intro">Elija una contraseña</string>
<string name="setup_password_explanation">Su cuenta Briar se almacena cifrada en su dispositivo, no en la nube. Si olvida su contraseña o desinstala Briar, no hay manera de recuperar su cuenta.\n\nElija una contraseña larga que sea difícil de adivinar, como cuatro palabras aleatorias o diez letras, números y símbolos al azar.</string>
<string name="setup_doze_title">Conexiones de fondo</string>
<string name="setup_doze_intro">Para recibir mensajes, Briar necesita mantenerse conectado en segundo plano.</string>
<string name="setup_doze_explanation">Para recibir mensajes, Briar necesita mantenerse conectado en segundo plano. Desactive las optimizaciones de la batería para que Briar pueda permanecer conectado.</string>
<string name="setup_doze_button">Permitir conexiones</string>
<string name="setup_title">Configuración de Briar</string>
<string name="setup_explanation">Tu cuenta de Briar se almacena de manera cifrada en tu dispositivo y no en ninguna nube. Si desinstalas Briar u olvidas tu contraseña, no podrás recuperar ni tu cuenta ni tus datos.</string>
<string name="choose_nickname">Elige tu nombre de usuario</string>
<string name="choose_password">Elige tu contraseña</string>
<string name="confirm_password">Confirma tu contraseña</string>
@@ -17,12 +10,6 @@
<string name="password_too_weak">La contraseña es demasiado débil</string>
<string name="passwords_do_not_match">Las contraseñas no coinciden</string>
<string name="create_account_button">Registrar una nueva cuenta</string>
<string name="more_info">Más información</string>
<string name="don_t_ask_again">No preguntes de nuevo</string>
<string name="setup_huawei_text">Por favor toque el botón de abajo y asegúrese de que Briar está protegido en la pantalla \"Aplicaciones protegidas\".</string>
<string name="setup_huawei_button">Proteger Briar</string>
<string name="setup_huawei_help">Si Briar no se agrega a la lista de aplicaciones protegidas, no podrá ejecutarse en segundo plano.</string>
<string name="warning_dozed">%s no pudo ejecutarse en proceso de fondo</string>
<!--Login-->
<string name="enter_password">Introduce tu contraseña:</string>
<string name="try_again">Contraseña incorrecta, inténtalo de nuevo</string>
@@ -33,13 +20,12 @@
<string name="startup_failed_notification_title">Briar no pudo iniciarse</string>
<string name="startup_failed_notification_text">Quizá tengas que reinstalar Briar.</string>
<string name="startup_failed_activity_title">Fallo al iniciar Briar</string>
<string name="startup_failed_db_error">Por alguna razón, su base de datos Briar está dañada irreparablemente. Su cuenta, sus datos y todos sus contactos están perdidos. Desafortunadamente, necesita reinstalar Briar y configurar una nueva cuenta.</string>
<string name="startup_failed_db_error">Por alguna razón, la base de datos de Briar ha sufrido daños irreparables. Tu cuenta, tus datos y todos tus contactos se han perdido. Desafortunadamente, tendrás que reinstalar Briar y registrar una nueva cuenta.</string>
<string name="startup_failed_service_error">Briar no pudo iniciar un complemento necesario. Reinstalar Briar suele solucionar el problema. Sin embargo, ten en cuenta que perderás tu cuenta y todos los datos asociados ya que Briar no almacena esta información en ningún servidor central.</string>
<plurals name="expiry_warning">
<item quantity="one">Esta es una versión de prueba de Briar. Su cuenta expirará en %d día y no podrá ser renovada.</item>
<item quantity="other">Esta es una versión de prueba de Briar. Su cuenta expirará en %d días y no podrá ser renovada.</item>
<item quantity="one">Esta es una versión preliminar de Briar. Tu cuenta caducará en %d día y no podrá renovarse.</item>
<item quantity="other">Esta es una versión preliminar de Briar. Tu cuenta caducará en %d días y no podrá renovarse.</item>
</plurals>
<string name="expiry_update">La fecha de caducidad de las pruebas se ha ampliado. Su cuenta expirará en %d días.</string>
<string name="expiry_date_reached">Esta versión ha caducado.\n¡Gracias por probarla!</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">Abrir el panel de navegación</string>
@@ -83,7 +69,8 @@
<string name="delete">Borrar</string>
<string name="accept">Aceptar</string>
<string name="decline">Rechazar</string>
<string name="online">En línea</string>
<string name="options">Configuración</string>
<string name="online">En línea</string>
<string name="offline">Desconectado</string>
<string name="send">Enviar</string>
<string name="allow">Permitir</string>
@@ -92,8 +79,6 @@
<string name="ellipsis"></string>
<string name="text_too_long">El texto es demasiado largo</string>
<string name="show_onboarding">Mostrar diálogo de ayuda</string>
<string name="fix">Reparar</string>
<string name="help">Ayuda</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">Parece que eres nuevo por aquí y no tienes aún contactos.\n\nPulsa el signo + en la parte superior y sigue las instrucciones para añadir amigos a tu lista.\n\nPor favor, recuerda: sólo puedes añadir nuevos contactos cara a cara para evitar que nadie suplante tu identidad o lea tus mensajes en el futuro. </string>
<string name="date_no_private_messages">Sin mensajes.</string>
@@ -105,20 +90,31 @@
<string name="contact_deleted_toast">Contacto eliminado</string>
<!--Adding Contacts-->
<string name="add_contact_title">Añadir un contacto</string>
<string name="your_nickname">Elige la identidad que quieres utilizar:</string>
<string name="face_to_face">Debes reunirte con la persona a la que quieras añadir como contacto.\n\nHaciéndolo así prevendrás que nadie te suplante o pueda leer tus mensajes en el futuro.</string>
<string name="continue_button">Continuar</string>
<string name="your_invitation_code">Tu código de invitación es</string>
<string name="enter_invitation_code">Por favor, introduce el código de invitación de tu contacto:</string>
<string name="searching_format">Buscando el contacto con el código de invitación %06d\u2026</string>
<string name="connection_failed">La conexión falló</string>
<string name="could_not_find_contact">Briar no pudo encontrar a tu contacto cerca</string>
<string name="try_again_button">Prueba de nuevo</string>
<string name="connected_to_contact">Conectado con el contacto</string>
<string name="calculating_confirmation_code">Calculando código de confirmación\u2026</string>
<string name="your_confirmation_code">Tu código de confirmación es</string>
<string name="enter_confirmation_code">Por favor, introduce el código de confirmación de tu contacto:</string>
<string name="waiting_for_contact">Esperando al contacto\u2026</string>
<string name="waiting_for_contact_to_scan">Esperando a que el contacto escanee y conecte contigo\u2026</string>
<string name="exchanging_contact_details">Intercambiando información de contacto\u2026</string>
<string name="codes_do_not_match">Los códigos no coinciden</string>
<string name="interfering">Esto podría significar que alguien está intentando interceptar la conexión</string>
<string name="contact_added_toast">Contacto añadido: %s</string>
<string name="contact_already_exists">El contacto %s ya existe</string>
<string name="contact_exchange_failed">El intercambio del contacto falló</string>
<string name="qr_code_invalid">El código QR no es válido</string>
<string name="camera_error">Error de cámara</string>
<string name="connecting_to_device">Conectando al dispositivo\u2026</string>
<string name="authenticating_with_device">Autentificándose con el dispositivo\u2026</string>
<string name="connection_aborted_local">¡Conexión interrumpida! Esto podría significar que alguien está intentando interferir con su conexión</string>
<string name="connection_aborted_local">¡Hemos interumpido la conexión! Esto podría significar que alguien está intentando interceptar la conexión</string>
<string name="connection_aborted_remote">¡La conexión ha sido interrumpida por tu contacto! Esto podría significar que alguien está intentando interceptar la conexión</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Presenta a tus contactos</string>
@@ -339,7 +335,6 @@
<string name="notify_sound_setting_default">Tono de notificación predeterminado</string>
<string name="notify_sound_setting_disabled">Ninguna</string>
<string name="choose_ringtone_title">Elegir alerta sonora</string>
<string name="cannot_load_ringtone">No se puede cargar el timbre</string>
<!--Settings Feedback-->
<string name="feedback_settings_title">Retroalimentación</string>
<string name="send_feedback">Enviar comentario</string>
@@ -369,9 +364,4 @@
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Superposición de pantalla detectada</string>
<string name="screen_filter_body">Otra aplicación se está mostrando encima de Briar. Por seguridad, Briar no responderá a los toques mientras se muestre otra aplicación encima.\n\nIntenta apagar las siguientes aplicaciones cuando uses Briar:\n\n%1$s</string>
<!--Permission Requests-->
<string name="permission_camera_title">Permiso de cámara</string>
<string name="permission_camera_request_body">Para escanear el código QR, Briar necesita acceso a la cámara.</string>
<string name="permission_camera_denied_body">Ha denegado el acceso a la cámara, pero para añadir contactos se requiere el uso de la cámara.\n\nPor favor considere la posibilidad de conceder el acceso.</string>
<string name="permission_camera_denied_toast">No se concedió el permiso de cámara</string>
</resources>

View File

@@ -1,15 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_title">Ongi etorri Briar-era</string>
<string name="setup_name_explanation">Zure ezizena argitaratzen dituzun eduki guztien ondoan agertuko da. Ezin dezakezu aldatu kontua sortu eta gero.</string>
<string name="setup_next">Hurrengoa</string>
<string name="setup_password_intro">Aukeratu pasahitz bat</string>
<string name="setup_password_explanation">Zure Briar kontua zure gailuan zifratuta gordetzen da, ez hodeian. Briar desinstalatzen baduzu edo pasahitza ahazten baduzu, ez dago zure kontua eta datuak berreskuratzeko modurik.\n\nAukeratu asmatzeko zaila den pasahitz luze bat, adibidez ausazko lau hitz edo ausazko hamar letra, zenbaki eta ikur.</string>
<string name="setup_doze_title">Atzeko planoko konexioak</string>
<string name="setup_doze_intro">Mezuak jasotzeko, Briar bigarren planoak konektatuta mantendu behar da.</string>
<string name="setup_doze_explanation">Mezuak jasotzeko, Briar bigarren planoak konektatuta mantendu behar da. Mesedez desgaitu bateria optimizazioak Briar konektatuta mantendu dadin.</string>
<string name="setup_doze_button">Baimendu konexioak</string>
<string name="setup_title">Briar ezarpena</string>
<string name="setup_explanation">Zure Briar kontua zure gailuan zifratuta gordetzen da, ez hodeian. Briar desinstalatzen baduzu edo pasahitza ahazten baduzu, ez dago zure kontua eta datuak berreskuratzeko modurik.</string>
<string name="choose_nickname">Hautatu zure ezizena</string>
<string name="choose_password">Hautatu zure pasahitza</string>
<string name="confirm_password">Berretsi zure pasahitza</string>
@@ -17,12 +10,6 @@
<string name="password_too_weak">Pasahitza ahulegia da</string>
<string name="passwords_do_not_match">Pasahitzak ez datoz bat</string>
<string name="create_account_button">Sortu kontua</string>
<string name="more_info">Informazio gehiago</string>
<string name="don_t_ask_again">Ez galdetu berriro</string>
<string name="setup_huawei_text">Sakatu beheko botoia eta ziurtatu Briar babestuta dagoela \"Babestutako aplikazioak\" pantailan.</string>
<string name="setup_huawei_button">Babestu Briar</string>
<string name="setup_huawei_help">Briar ez bada babestutako aplikazioen zerrendara gehitu, ezin izango du bigarren planoan ibili.</string>
<string name="warning_dozed">%s ezin izan da bigarren planoan abiatu</string>
<!--Login-->
<string name="enter_password">Sartu zure pasahitza:</string>
<string name="try_again">Pasahitz okerra, saiatu berriro</string>
@@ -33,13 +20,12 @@
<string name="startup_failed_notification_title">Ezin izan da Briar abiatu</string>
<string name="startup_failed_notification_text">Agian Briar berrinstalatu behar duzu.</string>
<string name="startup_failed_activity_title">Briar abio-hutsegitea</string>
<string name="startup_failed_db_error">Dena delakoagatik, zure Briar datu-basea hondatuta dago eta ezin da konpondu. Zure kontua, zure datuak eta zure kontaktuak galdu dira. Zoritxarrez Briar berrinstalatu behar duzu eta kontu berria sortu.</string>
<string name="startup_failed_db_error">Arrazoiren bategatik, zure Briar datubasea hondatuta dago eta ezin da konpondu. Zure kontua, datuak eta kontaktuekin konexioak galdu dira. Zoritxarrez Briar berrinstalatu behar duzu kontu berria sortzeko.</string>
<string name="startup_failed_service_error">Briar aplikazioak ezin izan du ezinbesteko plugin bat abiatu. Briar berrinstalatzeak arazoa konpondu ohi du. Hala ere, jakin zure kontua eta datuak galduko dituzula Briar aplikazioak ez baititu zerbitzari zentralak erabiltzen zure datuak gordetzeko.</string>
<plurals name="expiry_warning">
<item quantity="one">Hau Briar-en probetarako bertsio bat da. Zure kontua egun %d barru iraungituko da eta ezin da berriztu.</item>
<item quantity="other">Hau Briar-en probetarako bertsio bat da. Zure kontua %d egun barru iraungituko da eta ezin da berriztu.</item>
<item quantity="one">Hau Briar aplikazioaren beta bertsio bat da. Zure kontua egun %d barru iraungitu da eta ezin da berritu.</item>
<item quantity="other">Hau Briar aplikazioaren beta bertsio bat da. Zure kontua %d egun barru iraungitu da eta ezin da berritu.</item>
</plurals>
<string name="expiry_update">Probetarako iraungitze data luzatu da. Zure kontua %d egun barru iraungituko da.</string>
<string name="expiry_date_reached">Programa hau iraungitu da.\nEskerrik asko probatzeagatik!</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">Ireki nabigazio tiradera</string>
@@ -83,7 +69,8 @@
<string name="delete">Ezabatu</string>
<string name="accept">Onartu</string>
<string name="decline">Ukatu</string>
<string name="online">Konektatuta</string>
<string name="options">Aukerak</string>
<string name="online">Konektatuta</string>
<string name="offline">Deskonektatuta</string>
<string name="send">Bidali</string>
<string name="allow">Baimendu</string>
@@ -92,8 +79,6 @@
<string name="ellipsis"></string>
<string name="text_too_long">Sartutako testua luzeegia da</string>
<string name="show_onboarding">Erakutsi laguntza elkarrizketa-koadroa</string>
<string name="fix">Konpondu</string>
<string name="help">Laguntza</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">Badirudi heldu berria zarela eta ez duzula kontakturik hemen oraindik.\n\nSakatu goiko + ikonoa eta jarraitu argibideak lagunak zure zerrendara gehitzeko.\nGogoratu: Kontaktu berriak aurrez aurre besterik ezin dira gehitu beste inork zure itxurak ez egiteko edo zure mezuak ez irakurtzeko.</string>
<string name="date_no_private_messages">Mezurik ez.</string>
@@ -105,20 +90,31 @@
<string name="contact_deleted_toast">Kontaktua ezabatuta</string>
<!--Adding Contacts-->
<string name="add_contact_title">Gehitu kontaktua</string>
<string name="your_nickname">Hautatu erabili nahi duzun identitatea:</string>
<string name="face_to_face">Aurrez aurre aurkitu behar zara pertsonarekin kontaktua gehitzeko.\n\nHonek inork zure itxurak egitea edo zure mezuak irakurtzea eragotziko du.</string>
<string name="continue_button">Jarraitu</string>
<string name="your_invitation_code">Zure gonbidapen kodea hau da</string>
<string name="enter_invitation_code">Sartu zure gonbidapen kodea:</string>
<string name="searching_format">%06d gonbidapen kodea duen erabiltzailea bilatzen\u2026</string>
<string name="connection_failed">Konexioak huts egin du</string>
<string name="could_not_find_contact">Briar aplikazioak ezin izan du zure kontaktua inguruan aurkitu</string>
<string name="try_again_button">Saiatu berriro</string>
<string name="connected_to_contact">Kontaktura konektatuta</string>
<string name="calculating_confirmation_code">Berreste kode kalkulatzen\u2026</string>
<string name="your_confirmation_code">Zure berreste kodea hau da:</string>
<string name="enter_confirmation_code">Sartu zure kontaktuaren berreste kodea:</string>
<string name="waiting_for_contact">Kontaktuaren zain\u2026</string>
<string name="waiting_for_contact_to_scan">Kontaktuak eskaneatu dezan eta konektatu dadin zain\u2026</string>
<string name="exchanging_contact_details">Kontaktu-xehetasunak trukatzen\u2016</string>
<string name="codes_do_not_match">Kodeak ez datoz bat</string>
<string name="interfering">Norbait zure konexioan interferentziak sortzen saiatzen ari dela esan nahi lezake</string>
<string name="contact_added_toast">Kontaktua gehituta: %s</string>
<string name="contact_already_exists">%s kontaktua badago aurretik</string>
<string name="contact_exchange_failed">Kontaktuen trukeak huts egin du</string>
<string name="qr_code_invalid">QR kodea baliogabea da</string>
<string name="camera_error">Kameraren errorea</string>
<string name="connecting_to_device">Gailura konektatzen\u2026</string>
<string name="authenticating_with_device">Gailuarekin autentifikatzen\u2026</string>
<string name="connection_aborted_local">Konexioa eten da! Honek norbait konexioan interferentziak sortzen ari dela esan nahi lezake.</string>
<string name="connection_aborted_local">Guk eten dugu konexioa! Norbait zure konexioan interferentziak sortzen saiatzen ari dela esan nahi lezake</string>
<string name="connection_aborted_remote">Zure kontaktuak eten du konexioa! Norbait zure konexioan interferentziak sortzen saiatzen ari dela esan nahi lezake</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Aurkeztu zure kontaktuak</string>
@@ -339,7 +335,6 @@
<string name="notify_sound_setting_default">Lehenetsitako dei-doinua</string>
<string name="notify_sound_setting_disabled">Bat ere ez</string>
<string name="choose_ringtone_title">Hautatu dei-doinua</string>
<string name="cannot_load_ringtone">Ezin izan da dei-doinua kargatu</string>
<!--Settings Feedback-->
<string name="feedback_settings_title">Iruzkinak</string>
<string name="send_feedback">Bidali iruzkinak</string>
@@ -369,9 +364,4 @@
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Pantaila gainjartzea antzeman da</string>
<string name="screen_filter_body">Briar aplikazioaren gainean marrazten ari den beste aplikazio bat dago. Zure segurtasuna babesteko Briar aplikazioak ez dio ukimenari erantzungo beste aplikazio bat gainean marrazten dagoen bitartean.\n\nSaiatu honako aplikazioak ixten Briar erabiltzean:\n\n%1$s</string>
<!--Permission Requests-->
<string name="permission_camera_title">Kamera baimena</string>
<string name="permission_camera_request_body">QR kodea eskaneatzeko Briar-ek kamera atzitu behar du.</string>
<string name="permission_camera_denied_body">Kameraatzitzeko baimena ukatu duzu, baina kontaktuak gehitzeko kamera behar da.\n\nMesedez baimendu sarbidea.</string>
<string name="permission_camera_denied_toast">Eza kameraren baimenik eman</string>
</resources>

View File

@@ -1,15 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_title">Tervetuloa Briariin</string>
<string name="setup_name_explanation">Nimimerkkisi tulee näkymään julkaisemasi sisällön yhteydessä. Et voi muuttaa nimimerkkiä sen jälkeen kun olet luonut tilin.</string>
<string name="setup_next">Seuraava</string>
<string name="setup_password_intro">Valitse salasana</string>
<string name="setup_password_explanation">Sinun Briar tili on tallennettu salattuna sinun laitteelle, ei pilvipalvelimelle. Jos unohdat salasanasi tai poistat Briarin asennuksen, ei ole mitään keinoa jolla tilisi voisi palauttaa.\n\nValitse pitkä salasana, joka on vaikea arvata, esimerkiksi neljä sattumanvaraista sanaa tai kymmenen sattumanvaraista kirjainta, numeroa ja symbolia.</string>
<string name="setup_doze_title">Taustayhteydet</string>
<string name="setup_doze_intro">Vastaanottaakseen viestejä, Briarin täytyy pitää yhteyttä yllä taustalla.</string>
<string name="setup_doze_explanation">Vastaanottaakseen viestejä, Briarin täytyy pitää yhteyttä yllä taustalla. Ole hyvä ja kytke pois akun optimisaatiot, jotta Briar voisi pitää yhteyden yllä.</string>
<string name="setup_doze_button">Salli yhteydet</string>
<string name="setup_title">Briarin asetus</string>
<string name="setup_explanation">Sinun Briar tili on tallennettu salattuna sinun laitteelle, ei pilvipalvelimelle. Jos poistat Briarin asennuksen tai unohdat salasanasi, ei ole mitään keinoa jolla tilisi tai dataasi voisi palauttaa.</string>
<string name="choose_nickname">Valitse nimimerkki</string>
<string name="choose_password">Valitse salasana</string>
<string name="confirm_password">Vahvista salasana</string>
@@ -17,12 +10,6 @@
<string name="password_too_weak">Salasana on liian heikko</string>
<string name="passwords_do_not_match">Salasanat eivät täsmää</string>
<string name="create_account_button">Luo tili</string>
<string name="more_info">Lisätietoja</string>
<string name="don_t_ask_again">Älä kysy uudelleen</string>
<string name="setup_huawei_text">Napsauta alla olevaa nappia varmistaaksesi, että Briar on suojattu \"Suojatut sovellukset\" -näkymässä.</string>
<string name="setup_huawei_button">Suojaa Briar</string>
<string name="setup_huawei_help">Jos Briaria ei lisätä suojattujen sovellusten listalle, se ei voi toimia taustalla.</string>
<string name="warning_dozed">%s ei voinut toimia taustalla</string>
<!--Login-->
<string name="enter_password">Syötä salasana:</string>
<string name="try_again">Väärä salasana, yritä uudelleen</string>
@@ -36,10 +23,9 @@
<string name="startup_failed_db_error">Jostain syystä sinun Briar tietokanta on korruptoitunut korjauskelvottomaksi. Tilisi, tietosi ja kaikki yhteystietosi on menetetty. Valitettavasti, sinun täytyy asentaa Briar uudelleen ja luoda uusi tili.</string>
<string name="startup_failed_service_error">Briar ei kyennyt käynnistämään vaadittua liitännäistä. Briarin uudelleenasennus yleensä korjaa ongelman. Huomaa kuitenkin, että tulet menettämään tilisi ja kaikki siihen liittyvä data koska Briar ei käytä palvelimia sinun tietojesi säilyttämiseen.</string>
<plurals name="expiry_warning">
<item quantity="one">Tämä on Briarin testiversio. Sinun tilisi tulee vanhentumaan %d päivän päästä eikä sitä voi uusia.</item>
<item quantity="other">Tämä on Briarin testiversio. Sinun tilisi tulee vanhentumaan %d päivän päästä eikä sitä voi uusia.</item>
<item quantity="one">Tämä on Briarin beeta versio. Sinun tilisi tulee vanhentumaan %d päivän päästä eikä sitä voi uusia.</item>
<item quantity="other">Tämä on Briarin beeta versio. Sinun tilisi tulee vanhentumaan %d päivän päästä eikä sitä voi uusia.</item>
</plurals>
<string name="expiry_update">Testiaikaa on pidennetty. Tilisi tulee nyt vanhentumaan %d päivän kuluttua.</string>
<string name="expiry_date_reached">Tämä sovellus on vanhentunut.\nKiitos testaamisesta!</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">Avaa navigointilaatikko</string>
@@ -83,7 +69,8 @@
<string name="delete">Poista</string>
<string name="accept">Hyväksy</string>
<string name="decline">Kieltäydy</string>
<string name="online">Verkossa</string>
<string name="options">Valitsimet</string>
<string name="online">Verkossa</string>
<string name="offline">Poissa verkosta</string>
<string name="send">Lähetä</string>
<string name="allow">Salli</string>
@@ -92,8 +79,6 @@
<string name="ellipsis"></string>
<string name="text_too_long">Kirjoitettu teksti on liian pitkä</string>
<string name="show_onboarding">Näytä apudialogi</string>
<string name="fix">Korjaa</string>
<string name="help">Ohje</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">Näyttää siltä, että olet uusi täällä, eikä sinulla vielä ole yhteyshenkilöitä.\n\nNapauta yllä olevaa + -kuvaketta ja seuraa ohjeita lisätäksesi kavereita luetteloon.\n\nMuista: Voit ainoastaan lisätä uusia yhteyshenkilöitä tapaamalla heidät kasvokkain. Tämä estää sen, että joku voisi esittää olevansa sinä tai lukea viestejäsi tulevaisuudessa.</string>
<string name="date_no_private_messages">Ei viestejä.</string>
@@ -105,20 +90,31 @@
<string name="contact_deleted_toast">Yhteystieto poistettu</string>
<!--Adding Contacts-->
<string name="add_contact_title">Lisää yhteystieto</string>
<string name="your_nickname">Valitse tunnus jota haluat käyttää:</string>
<string name="face_to_face">Sinun täytyy tavata käyttäjä, jonka haluat lisätä yhteystietoihisi.\n\nTämä estää sen, että joku voisi esittää olevansa sinä tai lukea viestejäsi tulevaisuudessa</string>
<string name="continue_button">Jatka</string>
<string name="your_invitation_code">Sinun kutsukoodi on</string>
<string name="enter_invitation_code">Syötä yhteyshenkilösi kutsukoodi:</string>
<string name="searching_format">Etsitään yhteyshenkilöä jolla on kutsukoodi %06d\u2026</string>
<string name="connection_failed">Yhteys epäonnistui</string>
<string name="could_not_find_contact">Briar ei löytänyt yhteyshenkilöä lähistöltä</string>
<string name="try_again_button">Yritä uudelleen</string>
<string name="connected_to_contact">Yhdistetty yhteyshenkilöön</string>
<string name="calculating_confirmation_code">Luodaan varmennuskoodi\u2026</string>
<string name="your_confirmation_code">Sinun varmennuskoodi on</string>
<string name="enter_confirmation_code">Syötä yhteyshenkilösi varmennuskoodi:</string>
<string name="waiting_for_contact">Odotetaan yhteyshenkilöä\u2026</string>
<string name="waiting_for_contact_to_scan">Odotetaan, että käyttäjä skannaa ja saa yhteyden\u2026</string>
<string name="exchanging_contact_details">Yhteystietoja vaihdetaan\u2026</string>
<string name="codes_do_not_match">Koodit eivät täsmää</string>
<string name="interfering">Tämä voi tarkoittaa, että joku yrittää häiritä sinun ja toisen käyttäjän välistä yhteyttä</string>
<string name="contact_added_toast">Yhteystieto lisätty: %s</string>
<string name="contact_already_exists">Yhteystieto %s on jo olemassa</string>
<string name="contact_exchange_failed">Yhteystietojen vaihto epäonnistui</string>
<string name="qr_code_invalid">QR koodi on virheellinen</string>
<string name="camera_error">Kameravirhe</string>
<string name="connecting_to_device">Yhdistetään laitteeseen\u2026</string>
<string name="authenticating_with_device">Tunnistaudutaan laitteen kanssa\u2026</string>
<string name="connection_aborted_local">Yhteys katkaistu! Tämä voi tarkoittaa, että joku muu yrittää häiritä sinun ja toisen käyttäjän välistä yhteyttä</string>
<string name="connection_aborted_local">Katkaisimme sinun ja toisen käyttäjän välisen yhteyden! Tämä voi tarkoittaa, että joku muu yrittää häiritä sinun ja toisen käyttäjän välistä yhteyttä</string>
<string name="connection_aborted_remote">Toinen käyttäjä katkaisi yhteyden! Tämä voi tarkoittaa, että joku muu yrittää häiritä sinun ja toisen käyttäjän välistä yhteyttä</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Esittele yhteyshenkilösi</string>
@@ -339,7 +335,6 @@
<string name="notify_sound_setting_default">Oletussoittoääni</string>
<string name="notify_sound_setting_disabled">Ei mikään</string>
<string name="choose_ringtone_title">Valitse soittoääni</string>
<string name="cannot_load_ringtone">Soittoäänen lataaminen epäonnistui</string>
<!--Settings Feedback-->
<string name="feedback_settings_title">Palaute</string>
<string name="send_feedback">Lähetä palautetta</string>
@@ -369,9 +364,4 @@
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Näyttökerros havaittu</string>
<string name="screen_filter_body">Toinen sovellus on piirtämässä Briarin päälle. Suojellakseen sinun turvallisuutta, Briar ei tule vastaamaan kosketuksiin silloin kun toinen sovellus on piirtämässä Briarin päälle.\n\nYritä sulkea seuraavat sovellukset silloin kun käytät Briaria:\n\n%1$s</string>
<!--Permission Requests-->
<string name="permission_camera_title">Kameralupa</string>
<string name="permission_camera_request_body">Skannatakseen QR koodin, Briar tarvitsee luvan käyttää kameraa.</string>
<string name="permission_camera_denied_body">Olet kieltänyt käyttämästä kameraa, mutta yhteyshenkilöiden lisääminen vaatii kameran käyttöä.\n\nOle hyvä ja harkitse kameraluvan myöntämistä.</string>
<string name="permission_camera_denied_toast">Kameralupaa ei myönnetty</string>
</resources>

View File

@@ -2,7 +2,8 @@
<resources>
<!--Setup-->
<string name="setup_title">Configuration de Briar</string>
<string name="choose_nickname">Choisir votre pseudonyme</string>
<string name="setup_explanation">Votre compte Briar est enregistré chiffré sur votre appareil et non sur le nuage. Si vous désinstallez Briar ou oubliez votre mot de passe, votre compte et vos données sont irrécupérables.</string>
<string name="choose_nickname">Choisir votre pseudonyme</string>
<string name="choose_password">Choisir votre mot de passe</string>
<string name="confirm_password">Confirmer votre mot de passe</string>
<string name="name_too_long">Le nom est trop long</string>
@@ -68,7 +69,8 @@
<string name="delete">Supprimer</string>
<string name="accept">Accepter</string>
<string name="decline">Refuser</string>
<string name="online">En ligne</string>
<string name="options">Options</string>
<string name="online">En ligne</string>
<string name="offline">Hors ligne</string>
<string name="send">Envoyer</string>
<string name="allow">Autoriser</string>
@@ -88,13 +90,25 @@
<string name="contact_deleted_toast">Le contact a été supprimé</string>
<!--Adding Contacts-->
<string name="add_contact_title">Ajouter un contact</string>
<string name="face_to_face">Vous devez rencontrer la personne que vous voulez ajouter comme contact, afin déviter que quelquun se fasse passer pour vous et puisse lire vos messages à lavenir.</string>
<string name="your_nickname">Choisir lidentité que vous souhaitez utiliser :</string>
<string name="face_to_face">Vous devez rencontrer la personne que vous voulez ajouter comme contact, afin déviter que quelquun se fasse passer pour vous et puisse lire vos messages à lavenir.</string>
<string name="continue_button">Continuer</string>
<string name="connection_failed">Échec de connexion</string>
<string name="try_again_button">Ressayer</string>
<string name="waiting_for_contact_to_scan">En attente de lecture du code QR par le contact et de sa connexion\u2026</string>
<string name="your_invitation_code">Votre code dinvitation est</string>
<string name="enter_invitation_code">Veuillez saisir le code dinvitation de votre contact :</string>
<string name="searching_format">Recherche de contacts avec le code dinvitation %06d\u2026</string>
<string name="connection_failed">Échec de connexion</string>
<string name="could_not_find_contact">Briar na pas trouvé votre contact à proximité</string>
<string name="try_again_button">Ressayer</string>
<string name="connected_to_contact">Connecté au contact</string>
<string name="calculating_confirmation_code">Calcul du code de confirmation\u2026</string>
<string name="your_confirmation_code">Votre code de confirmation est</string>
<string name="enter_confirmation_code">Veuillez saisir le code de confirmation de votre contact :</string>
<string name="waiting_for_contact">En attente du contact\u2026</string>
<string name="waiting_for_contact_to_scan">En attente de lecture du code QR par le contact et de sa connexion\u2026</string>
<string name="exchanging_contact_details">Échange des renseignements de contact\u2026</string>
<string name="contact_added_toast">Contact ajouté : %s</string>
<string name="codes_do_not_match">Les codes ne correspondent pas</string>
<string name="interfering">Cela pourrait signifier que quelquun tente dinterférer avec votre connexion</string>
<string name="contact_added_toast">Contact ajouté : %s</string>
<string name="contact_already_exists">Le contact %s existe déjà</string>
<string name="contact_exchange_failed">Échec déchange de contacts</string>
<string name="qr_code_invalid">Le code QR est invalide</string>

View File

@@ -1,7 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_next">Seguinte</string>
<string name="setup_title">Configuración de Briar</string>
<string name="setup_explanation">Briar almacena a súa configuración encriptada no dispositivo, non na nube. Se desinstala Briar ou esquece a súa clave, non hai forma de recuperar a súa conta e datos.</string>
<string name="choose_nickname">Escolle o teu alcume</string>
<string name="choose_password">Escolle a túa clave</string>
<string name="confirm_password">Confirma a túa clave</string>
@@ -19,6 +20,7 @@
<string name="startup_failed_notification_title">Briar non puido iniciarse</string>
<string name="startup_failed_notification_text">Pode que precises instalar Briar de novo</string>
<string name="startup_failed_activity_title">Fallo de Inicio de Briar</string>
<string name="startup_failed_db_error">A súa base de datos de Briar está corrompida ate o punto que é irreparábel. Perdeuse a súa conta, os seus datos e todos os contactos conectados. Por desgraza debes instalar de novo Briar e configurar unha nova conta.</string>
<string name="startup_failed_service_error"> Briar non puido iniciar un complemento necesario. Xeralmente reinstalar Briar resolve este problema. Teña en conta que entón perderá a súa conta e todos os datos asociados a esta pois Briar non está a utilizar servidores centrais para almacenar os seus datos.</string>
<string name="expiry_date_reached">Este software caducou.\nGrazas por probalo!</string>
<!--Navigation Drawer-->
@@ -47,7 +49,8 @@
<string name="delete">Eliminar</string>
<string name="accept">Aceptar</string>
<string name="decline">Rexeitar</string>
<string name="online">Conectado</string>
<string name="options">Opcións</string>
<string name="online">Conectado</string>
<string name="offline">Desconectado</string>
<string name="send">Enviar</string>
<string name="allow">Permitir</string>
@@ -56,7 +59,6 @@
<string name="ellipsis">...</string>
<string name="text_too_long">O texto inserido e demasiado longo</string>
<string name="show_onboarding">Amosar xanela de axuda</string>
<string name="help">Axuda</string>
<!--Contacts and Private Conversations-->
<string name="date_no_private_messages">Sen mensaxes</string>
<string name="message_hint">Esciba unha mensaxe</string>
@@ -65,9 +67,15 @@
<string name="contact_deleted_toast">Contacto eliminado</string>
<!--Adding Contacts-->
<string name="add_contact_title">Engada un contacto</string>
<string name="your_nickname">Escolla a identidade que quere usar:</string>
<string name="continue_button">Continuar</string>
<string name="your_invitation_code">O seu código de convite é</string>
<string name="enter_invitation_code">Por favor, insira o código de convite do seu contacto</string>
<string name="connection_failed">Fallou a conexión</string>
<string name="try_again_button">Tenteo de novo</string>
<string name="connected_to_contact">Conectado ao contacto</string>
<string name="your_confirmation_code">O seu código de confirmación é</string>
<string name="codes_do_not_match">Os códigos non coinciden</string>
<string name="qr_code_invalid">O código QR non é válido</string>
<!--Introductions-->
<string name="introduction_activity_title">Escoller contacto</string>
@@ -109,12 +117,10 @@
<string name="blogs_publish_blog_post">Publicar</string>
<!--Blog Sharing-->
<!--RSS Feeds-->
<string name="blogs_rss_feeds_import_button">Importar</string>
<string name="blogs_rss_feeds_manage_author">Autor/a:</string>
<string name="blogs_rss_feeds_manage_updated">Última actualización:</string>
<!--Settings Network-->
<!--Settings Security and Panic-->
<string name="change_password">Cambiar contrasinal</string>
<string name="panic_app_setting_none">Ningún</string>
<string name="lock_setting_title">Finalizar sesión</string>
<!--Settings Notifications-->
@@ -125,5 +131,4 @@
<string name="close">Pechar</string>
<!--Sign Out-->
<!--Screen Filters & Tapjacking-->
<!--Permission Requests-->
</resources>

View File

@@ -1,266 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_next">הבא</string>
<string name="choose_nickname">בחרו כינוי</string>
<string name="choose_password">בחרו סיסמה</string>
<string name="confirm_password">אשרו את הסיסמה</string>
<string name="name_too_long">השם ארוך מדי</string>
<string name="password_too_weak">הסיסמה חלשה מדי</string>
<string name="passwords_do_not_match">הסיסמאות לא תואמות</string>
<string name="create_account_button">יצירת חשבון</string>
<!--Login-->
<string name="enter_password">הזינו סיסמה:</string>
<string name="try_again">הסיסמה שגוייה, נסו שוב</string>
<string name="sign_in_button">הכנסו</string>
<string name="forgotten_password">שכחתי את הסיסמה שלי</string>
<string name="dialog_title_lost_password">הסיסמה נאבדה</string>
<string name="dialog_message_lost_password">חשבון הבריאר שלכם שמור ומוצפן על המכשיר, לא בענן, כך שלא נוכל לשחזר את הסיסמה שלכם. למחוק את החשבון שלכם ולהתחיל מחדש?
זהירות: הזהויות שלכם, אנשי הקשר וההודעות יאבדו לצמיתות.</string>
<string name="startup_failed_notification_title">אפליקציית בריאר נכשלה באיתחול</string>
<string name="startup_failed_notification_text">יכול להיות שתאלצו להתקין מחדש את אפליקציית בריאר.</string>
<string name="startup_failed_activity_title">איתחול בריאר נכשל</string>
<string name="startup_failed_service_error">בריאר לא הצליח לאתחל תוסף הכרחי. התקנת בריאר מחדש לרוב פותרת בעייה זו. שימו לב שאז תאבדו את חשבונכם וכל המידע המשוייך אליו כיוון שבריאר לא משתמש בשרתים מרכזיים לשמירת המידע שלכם עליהם.</string>
<string name="expiry_date_reached">פג תוקפה של תוכנה זו.
תודה לכם שבדקתם.</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">פתחו את סרגל הניווט</string>
<string name="nav_drawer_close_description">סגרו את סרגל הניווט</string>
<string name="contact_list_button">אנשי קשר</string>
<string name="groups_button">קבוצות פרטיות</string>
<string name="forums_button">פורומים</string>
<string name="blogs_button">בלוגים</string>
<string name="settings_button">הגדרות</string>
<string name="sign_out_button">יציאה</string>
<!--Transports-->
<string name="transport_tor">אינטרנט</string>
<string name="transport_bt">בלוטות\'</string>
<string name="transport_lan">Wi-Fi</string>
<!--Notifications-->
<string name="ongoing_notification_title">מחוברים לבריאר</string>
<string name="ongoing_notification_text">לחצו על מנת לפתוח את בריאר</string>
<plurals name="private_message_notification_text">
<item quantity="one">הודעה פרטית חדשה.</item>
<item quantity="other">%d הודעות פריטות חדשות.</item>
</plurals>
<plurals name="group_message_notification_text">
<item quantity="one">הודעה קבוצתית חדשה.</item>
<item quantity="other">%d הודעות קבוצתיות חדשות.</item>
</plurals>
<plurals name="forum_post_notification_text">
<item quantity="one">פוסט חדש בפורומים.</item>
<item quantity="other">%d פוסטים חדשים בפורומים.</item>
</plurals>
<plurals name="blog_post_notification_text">
<item quantity="one">פוסט חדש בבלוגים.</item>
<item quantity="other">%d פוסטים חדשים בבלוגים.</item>
</plurals>
<!--Misc-->
<string name="now">עכשיו</string>
<string name="show">הצג</string>
<string name="hide">הסתר</string>
<string name="ok">אישור</string>
<string name="cancel">ביטול</string>
<string name="got_it">הבנתי</string>
<string name="delete">מחק</string>
<string name="accept">קבל</string>
<string name="decline">סרב</string>
<string name="online">מחובר</string>
<string name="offline">לא מחובר</string>
<string name="send">שלח</string>
<string name="allow">להתיר</string>
<string name="open">פתח</string>
<string name="no_data">לא נמצא מידע</string>
<string name="ellipsis">...</string>
<string name="text_too_long">הטקסט שהזנתם ארוך מדי</string>
<string name="show_onboarding">הצג עזרה</string>
<string name="help">עזרה</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">נראה שאתם חדשים כאן ועדיין אין לכם אנשי קשר.
לחצו על האייקון&amp;#160+ למעלה ועקבו אחר ההוראות להוספת חברים לרשימה.
אנא זכרו: אתם יכולים להוסיף אנשי קשר רק פנים-מול-פנים, זאת על מנת למנוע מאנשים להתחזות לכם או לקרוא את הודעותיכם בעתיד.</string>
<string name="date_no_private_messages">אין הודעות.</string>
<string name="no_private_messages">זהו מסך השיחה.
נראה שאין שיחה.
פשוט לחצו על השדה הטקסט בתחתית בשביל להתחיל שיחה.</string>
<string name="message_hint">כתבו הודעה</string>
<string name="delete_contact">מחיקת איש קשר</string>
<string name="dialog_title_delete_contact">אישור מחיקת איש קשר</string>
<string name="dialog_message_delete_contact">האם אתם בטוחים שאתם רוצים למחוק את איש הקשר וכל ההתכתבות איתו?</string>
<string name="contact_deleted_toast">איש הקשר נמחק</string>
<!--Adding Contacts-->
<string name="add_contact_title">הוספת איש קשר</string>
<string name="face_to_face">אתם חייבים להפגש עם האדם שאותו תרצו להוסיף כאיש קשר.
זאת על מנת למנוע מצב שבו מתחזים אליכם או קוראים הודעות שלכם בעתיד.</string>
<string name="continue_button">המשך</string>
<string name="connection_failed">חיבור נכשל</string>
<string name="try_again_button">נסו שוב</string>
<string name="waiting_for_contact_to_scan">ממתין שאיש הקשר יסרוק ויתחבר\u2026</string>
<string name="exchanging_contact_details">מחליף פרטים של איש קשר\u2026</string>
<string name="contact_added_toast">איש קשר נוסף: %s</string>
<string name="contact_already_exists">איש קשר %s קיים כבר</string>
<string name="contact_exchange_failed">החלפת אנשי קשר נכשלה</string>
<string name="qr_code_invalid">קוד ה- QR אינו תקף</string>
<string name="connecting_to_device">מתחבר למכשיר\u2026</string>
<string name="authenticating_with_device">מזדהה מול המכשיר\u2026</string>
<string name="connection_aborted_remote">החיבור בוטל על ידי איש הקשר! זה עלול לומר שמישהו מנסה להפריע לחיבור שלכם</string>
<!--Introductions-->
<string name="introduction_onboarding_title">הציגו את אנשי קשר שלכם</string>
<string name="introduction_onboarding_text">אתם יכולים להציג את אנשי הקשר שלכם אחד לשני, כך שהם לא צריכים להפגש פנים-מול-פנים על מנת להתחבר בבריאר.</string>
<string name="introduction_activity_title">בחרו איש קשר</string>
<string name="introduction_message_title">הציגו אנשי קשר</string>
<string name="introduction_message_hint">הוסיפו הודעה (לא חובה)</string>
<string name="introduction_button">הציגו איש קשר</string>
<string name="introduction_sent">הצגת איש קשר נשלחה.</string>
<string name="introduction_error">הייתה שגיאה בעת הצגת איש קשר.</string>
<string name="introduction_response_error">שגיאה בעת התגובה להצגת איש קשר</string>
<string name="introduction_request_sent">ביקשתם להציג את %1$s ל- %2$s.</string>
<string name="introduction_request_received">%1$s ביקש להציג אתכם ל- %2$s. האם תרצו להוסיף את %2$s לאנשי הקשר שלכם?</string>
<string name="introduction_request_exists_received">%1$s ביקשו להציג אתכם ל- %2$s, אבל %2$s עדיין ברשימת אנשי הקשר שלכם. מכיוון ש- %1$s עשוי לא לדעת זאת, אתם עדיין מוזמנים להגיב:</string>
<string name="introduction_request_answered_received">%1$s ביקשו להציג אתכם בפני %2$s.</string>
<string name="introduction_response_accepted_sent">הסכמתם להיות מוצגים בפני %1$s.</string>
<string name="introduction_response_declined_sent">סירבתם להיות מוצגים בפני %1$s.</string>
<string name="introduction_response_accepted_received">%1$s הסכימו להיות מוצגים בפני %2$s.</string>
<string name="introduction_response_declined_received">%1$s סירבו להיות מוצגים בפני %2$s.</string>
<string name="introduction_response_declined_received_by_introducee">%1$s אומרים ש- %2$s סירבו להיות מוצגים בפניהם.</string>
<plurals name="introduction_notification_text">
<item quantity="one">נוסף איש קשר חדש.</item>
<item quantity="other">נוספו %d אנשי קשר חדשים.</item>
</plurals>
<!--Private Groups-->
<string name="groups_list_empty">אתם לא משתתפים באף קבוצה.
לחצו על אייקון + בראש המסך על מנת ליצור קבוצה בעצמכם או ביקשו מאנשי הקשר שלכם להוסיף אתכם לאחת הקבוצות שלהם.</string>
<string name="groups_created_by">נוצר על ידי %s</string>
<plurals name="messages">
<item quantity="one">הודעה %d</item>
<item quantity="other">%d הודעות</item>
</plurals>
<string name="groups_group_is_empty">קבוצה זו הינה ריקה</string>
<string name="groups_group_is_dissolved">קבוצה זו התפזרה</string>
<string name="groups_remove">להסיר</string>
<string name="groups_create_group_title">יצירת קבוצה פרטית</string>
<string name="groups_create_group_button">יצירת קבוצה</string>
<string name="groups_create_group_invitation_button">שליחת הזמנה</string>
<string name="groups_create_group_hint">בחרו שם לקבוצה הפרטית שלכם</string>
<string name="groups_invitation_sent">הזמנה קבוצתית נשלחה</string>
<string name="groups_message_sent">ההודעה נשלחה</string>
<string name="groups_member_list">רשימת משתתפים</string>
<string name="groups_invite_members">הזמנת משתתפים</string>
<string name="groups_member_created_you">יצרתם את הקבוצה</string>
<string name="groups_member_created">%s יצר את הקבוצה</string>
<string name="groups_member_joined_you">הצטרפתם לקבוצה</string>
<string name="groups_member_joined">%s הצטרפו לקבוצה</string>
<string name="groups_leave">עזיבת הקבוצה</string>
<string name="groups_leave_dialog_title">אשרו את עזיבת הקבוצה</string>
<string name="groups_leave_dialog_message">האם אתם בטוחים שתרצו לעזוב את הקבוצה?</string>
<string name="groups_dissolve">פיזור הקבוצה</string>
<string name="groups_dissolve_dialog_title">אשרו פיזור הקבוצה</string>
<string name="groups_dissolve_dialog_message">האם אתם בטוחים שתרצו לפזר את הקבוצה?
כל המשתתפים לא יוכלו להמשיך את השיחה ועלולים לא לקבל את ההודעות האחרונות.</string>
<string name="groups_dissolve_button">פיזור</string>
<string name="groups_dissolved_dialog_title">הקבוצה פוזרה</string>
<string name="groups_dissolved_dialog_message">יוצר הקבוצה פיזר אותה.
לא תוכלו יותר לכתוב הודעות לקבוצה ואולי אף לא תקבלו את כל ההודעות שנכתבו.</string>
<!--Private Group Invitations-->
<string name="groups_invitations_title">הזמנות קבוצתיות</string>
<string name="groups_invitations_invitation_sent">הזמנתם את %1$s להצטרף לקבוצה \"%2$s\".</string>
<string name="groups_invitations_invitation_received">%1$s הזמינו אתכם להצטרף לקבוצה \"%2$s\".</string>
<string name="groups_invitations_joined">הצטרפתם לקבוצה</string>
<string name="groups_invitations_declined">הזמנה קבוצתית נדחתה</string>
<plurals name="groups_invitations_open">
<item quantity="one">הזמנה קבוצתית פתוחה %d</item>
<item quantity="other">%dהזמנות קבוצתית פתוחות </item>
</plurals>
<string name="groups_invitations_response_accepted_sent">הסכמתם להזמנה הקבוצתית מ- %s.</string>
<string name="groups_invitations_response_declined_sent">סירבתם להזמנה הקבוצתית מ- %s.</string>
<string name="groups_invitations_response_accepted_received">%s הסכימו להזמנה הקבוצתית.</string>
<string name="groups_invitations_response_declined_received">%s סרבו להזמנה הקבוצתית.</string>
<string name="sharing_status_groups">רק היוצר יכול להזמין משתתפים חדשים לקבוצה. להלן כל המשתתפים הנוכחיים של הקבוצה.</string>
<!--Private Groups Revealing Contacts-->
<string name="groups_reveal_contacts">הצגת אנשי קשר</string>
<string name="groups_reveal_dialog_message">אתם יכולים לבחור אם לחשוף את אנשי הקשר שלכם למשתתפים הנוכחיים והעתידיים של קבוצה זו.
חשיפת אנשי הקשר תגרום לחיבור עם הקבוצה להיות מהיר ואמין יותר, כי תוכלו לתקשר עם אנשי הקשר שנחשפו גם אם יוצר הקבוצה אינו מחובר.</string>
<string name="groups_reveal_visible">היחסים עם האיש קשר גלויים לקבוצה</string>
<string name="groups_reveal_visible_revealed_by_us">היחסים עם האיש קשר גלויים לקבוצה (נחשפו על ידיכם)</string>
<string name="groups_reveal_visible_revealed_by_contact">היחסים עם האיש קשר גלויים לקבוצה (נחשפו על %s)</string>
<string name="groups_reveal_invisible">היחסים עם האיש קשר אינם גלויים לקבוצה</string>
<!--Forums-->
<string name="no_forums">עדיין אין לכם פורומים.
למה שלא תיצרו אחד חדש בעצמכם על ידי לחיצה על האייקון&amp;#160+ למעלה?
ביכולתכם גם לבקש מאנשי הקשר לשתף אתכם בפורומים.</string>
<string name="create_forum_title">יצירת פורום</string>
<string name="choose_forum_hint">בחרו שם לפורום שלכם</string>
<string name="create_forum_button">יצירת פורום</string>
<string name="forum_created_toast">הפורום נוצר</string>
<string name="no_forum_posts">הפורום הזה ריק.
לחצו על אייקון העט למעלה על מנת לכתוב את הפוסט הראשון.
מרגישים בודדים כאן? שתפו את הפורום עם עוד אנשי קשר!</string>
<string name="no_posts">אין פוסטים</string>
<plurals name="posts">
<item quantity="one">פוסט %d</item>
<item quantity="other">%d פוסטים</item>
</plurals>
<string name="forum_new_entry_posted">הפוסט נוצר</string>
<string name="forum_new_message_hint">פוסט חדש</string>
<string name="forum_message_reply_hint">תגובה חדשה</string>
<string name="btn_reply">להשיב</string>
<string name="forum_leave">עזיבת הפורום</string>
<string name="dialog_title_leave_forum">אשרו את עזיבת הפורום</string>
<string name="dialog_message_leave_forum">האם אתם בטוחים שברצונכם לעזוב את הפורום הזה? אנשי הקשר ששיתפתם בפורום עלולים לא לקבל יותר עדכונים מהפורום.</string>
<string name="dialog_button_leave">לעזוב</string>
<string name="forum_left_toast">עזבתם את הפורום</string>
<!--Forum Sharing-->
<string name="forum_share_button">שיתוף הפורום</string>
<string name="contacts_selected">אנשי קשר נבחרו</string>
<string name="activity_share_toolbar_header">בחרו אנשי קשר</string>
<string name="no_contacts_selector">נראה שאתם חדשים כאן ושעדיין אין לכם אנשי קשר.
אנא חזרו הנה אחרי שתוסיפו את איש הקשר הראשון שלכם.</string>
<string name="forum_shared_snackbar">שיתפתם את הפורום עם אנשי הקשר שנבחרו</string>
<string name="forum_share_message">הוסיפו הודעה (לא חובה)</string>
<string name="forum_share_error">הייתה שגיאה בעת שיתוף הפורום.</string>
<string name="forum_invitation_received">%1$s שיתפו את הפורום \"%2$s\" איתכם.</string>
<string name="forum_invitation_sent">שיתפתם את הפורום \"%1$s\" עם %2$s.</string>
<string name="forum_invitations_title">הזמנות לפורום</string>
<string name="forum_invitation_exists">אישרתם כבר את ההזמנה לפורום הזה. אישור הזמנות נוספות יגדיל ויחזק את התקשורת בפורום.</string>
<string name="forum_joined_toast">הצטרפתם לפורום</string>
<string name="forum_declined_toast">הזמנה לפורום נדחתה</string>
<string name="shared_by_format">שותף על ידי %s</string>
<!--Blogs-->
<!--Blog Sharing-->
<!--RSS Feeds-->
<string name="blogs_rss_feeds_import_button">ייבא</string>
<string name="blogs_rss_feeds_manage_author">מחבר:</string>
<string name="blogs_rss_feeds_manage_updated">עודכן לאחרונה:</string>
<!--Settings Network-->
<!--Settings Security and Panic-->
<string name="security_settings_title">אבטחה</string>
<string name="panic_app_setting_none">כלום</string>
<string name="lock_setting_title">יציאה</string>
<string name="purge_setting_title">מחק חשבון</string>
<!--Settings Notifications-->
<string name="notification_settings_title">הודעות מערכת</string>
<string name="notify_private_messages_setting_title">הודעות פרטיות</string>
<string name="notify_sound_setting_disabled">כלום</string>
<!--Settings Feedback-->
<!--Link Warning-->
<!--Crash Reporter-->
<string name="close">סגור</string>
<!--Sign Out-->
<!--Screen Filters & Tapjacking-->
<!--Permission Requests-->
</resources>

View File

@@ -1,7 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_next">अगला</string>
<string name="setup_title">ब्रियर सेटअप</string>
<string name="setup_explanation">आपका ब्रियर खाता आपके डिवाइस पर एन्क्रिप्ट किया गया है, न कि क्लाउड में। यदि आप बिअर की स्थापना रद्द करते हैं या अपना पासवर्ड भूल जाते हैं, तो आपके खाता और आपके डेटा को पुनर्प्राप्त करने का कोई तरीका नहीं है।</string>
<string name="choose_nickname">आपका मुंहबोला नाम चुनें</string>
<string name="choose_password">अपना पासवर्ड चुनें</string>
<string name="confirm_password">अपने पासवर्ड की पुष्टि करें</string>
@@ -9,7 +10,6 @@
<string name="password_too_weak">पासवर्ड बहुत कमजोर है</string>
<string name="passwords_do_not_match">पासवर्ड मेल नहीं खाते</string>
<string name="create_account_button">खाता बनाएं</string>
<string name="more_info">अधिक जानकारी</string>
<!--Login-->
<string name="enter_password">अपना पासवर्ड डालें:</string>
<string name="try_again">गलत पासवर्ड, फिर से प्रयास करें</string>
@@ -20,7 +20,12 @@
<string name="startup_failed_notification_title">बियर शुरू नहीं हो सका</string>
<string name="startup_failed_notification_text">आपको ब्रियर को पुनर्स्थापित करने की आवश्यकता हो सकती है</string>
<string name="startup_failed_activity_title">ब्रियर स्टार्टअप विफलता</string>
<string name="startup_failed_db_error">किसी कारण से, आपके ब्रियर डेटाबेस की मरम्मत से परे भ्रष्ट है। आपका खाता, आपका डेटा और आपके सभी संपर्क कनेक्शन खो जाते हैं। दुर्भाग्य से, आपको ब्रियर को पुनर्स्थापित करना होगा और एक नया खाता सेट अप करना होगा।</string>
<string name="startup_failed_service_error">ब्रियर एक आवश्यक प्लगइन प्रारंभ करने में असमर्थ था बरिअर को पुनः स्थापित करना आमतौर पर इस समस्या को हल करता है हालांकि, कृपया ध्यान दें कि बियर आपके डेटा को स्टोर करने के लिए केंद्रीय सर्वर का उपयोग नहीं कर रहा है, इसके बाद आप अपने खाता और उसके साथ जुड़े सभी डेटा खो देंगे।</string>
<plurals name="expiry_warning">
<item quantity="one">यह बियर का बीटा संस्करण है आपका खाता%d दिनों में समाप्त हो जाएगा और नवीनीकरण नहीं किया जा सकता है।</item>
<item quantity="other">यह बियर का बीटा संस्करण है आपका खाता%d दिनों में समाप्त हो जाएगा और नवीनीकरण नहीं किया जा सकता है।</item>
</plurals>
<string name="expiry_date_reached">यह सॉफ्टवेयर समाप्त हो गया है। \n परीक्षण के लिए धन्यवाद!</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">नेविगेशन ड्रॉवर खोलें</string>
@@ -64,7 +69,8 @@
<string name="delete">हटाना</string>
<string name="accept">स्वीकारें</string>
<string name="decline">पतन</string>
<string name="online">ऑनलाइन</string>
<string name="options">विकल्प</string>
<string name="online">ऑनलाइन</string>
<string name="offline">ऑफलाइन</string>
<string name="send">भेजना</string>
<string name="allow">अनुमति दें</string>
@@ -73,7 +79,6 @@
<string name="ellipsis"></string>
<string name="text_too_long">प्रवेश किया हुआ पाठ बहुत लंबा है</string>
<string name="show_onboarding">सहायता संवाद दिखाएं</string>
<string name="help">सहायता</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">ऐसा लगता है कि आप यहां नए हैं और अभी तक कोई संपर्क नहीं है। \ N \ n शीर्ष पर + आइकन टैप करें और कुछ मित्रों को अपनी सूची में जोड़ने के लिए निर्देशों का पालन करें। \ N \ n कृपया याद रखें: आप केवल नए संपर्कों को आमने-सामने जोड़ सकते हैं किसी भी व्यक्ति को भविष्य में आपके प्रतिरूपण या पढ़ने से रोकने के लिए -फाइल</string>
<string name="date_no_private_messages">कोई संदेश नहीं।</string>
@@ -85,18 +90,31 @@
<string name="contact_deleted_toast">संपर्क हटा दिया गया</string>
<!--Adding Contacts-->
<string name="add_contact_title">संपर्क जोड़ना</string>
<string name="your_nickname">वह पहचान चुनें जिसे आप उपयोग करना चाहते हैं:</string>
<string name="face_to_face">आपको उस व्यक्ति के साथ मिलना चाहिए जिसे आप संपर्क के रूप में जोड़ना चाहते हैं। \ N \ n यह किसी को भविष्य में आपके प्रतिरूपण या पढ़ने के बाद से किसी को भी रोकेगा।</string>
<string name="continue_button">जारी रहना</string>
<string name="your_invitation_code">आपका निमंत्रण कोड है</string>
<string name="enter_invitation_code">कृपया अपने संपर्क के निमंत्रण कोड को दर्ज करें:</string>
<string name="searching_format">निमंत्रण कोड%06d\ u2026 के साथ संपर्क के लिए खोज रहे हैं</string>
<string name="connection_failed">कनेक्शन विफल</string>
<string name="could_not_find_contact">ब्रियर आपके संपर्क के पास नहीं मिल सका</string>
<string name="try_again_button">पुनः प्रयास करें</string>
<string name="connected_to_contact">संपर्क से कनेक्ट किया गया</string>
<string name="calculating_confirmation_code">पुष्टि कोड की गणना \ u2026</string>
<string name="your_confirmation_code">आपका पुष्टिकरण कोड है</string>
<string name="enter_confirmation_code">कृपया अपने संपर्क का पुष्टिकरण कोड दर्ज करें:</string>
<string name="waiting_for_contact">संपर्क की प्रतीक्षा कर रहा है \ u2026</string>
<string name="waiting_for_contact_to_scan">स्कैन करने और कनेक्ट करने के लिए संपर्क की प्रतीक्षा कर रहा है \ u2026</string>
<string name="exchanging_contact_details">संपर्क विवरणों का आदान-प्रदान करें \ u2026</string>
<string name="codes_do_not_match">कोड मेल नहीं खाते हैं</string>
<string name="interfering">इसका मतलब यह हो सकता है कि कोई आपके कनेक्शन में हस्तक्षेप करने का प्रयास कर रहा है</string>
<string name="contact_added_toast">संपर्क जोड़ा गया:%s</string>
<string name="contact_already_exists">संपर्क%s पहले से मौजूद है</string>
<string name="contact_exchange_failed">संपर्क विनिमय विफल</string>
<string name="qr_code_invalid">QR कोड अमान्य है</string>
<string name="connecting_to_device">उपकरण \ u2026 से कनेक्ट हो रहा है</string>
<string name="authenticating_with_device">डिवाइस के साथ प्रमाणीकरण \ u2026</string>
<string name="connection_aborted_local">कनेक्शन हमारे द्वारा निरस्त कर दिया! इसका मतलब यह हो सकता है कि कोई आपके कनेक्शन में हस्तक्षेप करने का प्रयास कर रहा है</string>
<string name="connection_aborted_remote">आपके संपर्क से कनेक्शन निरस्त! इसका मतलब यह हो सकता है कि कोई आपके कनेक्शन में हस्तक्षेप करने का प्रयास कर रहा है</string>
<!--Introductions-->
<string name="introduction_onboarding_title">अपने संपर्कों का परिचय दें</string>
@@ -346,5 +364,4 @@
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">स्क्रीन ओवरले का पता लगाया</string>
<string name="screen_filter_body">एक और ऐप ब्रियर के शीर्ष पर है। आपकी सुरक्षा की सुरक्षा के लिए, ब्रियर किसी अन्य ऐप को शीर्ष पर खींचने पर स्पर्श का जवाब नहीं देगा। \ N \ n बरिआर का उपयोग करते समय निम्नलिखित ऐप्स बंद कर दें: \ n \ n%1$s</string>
<!--Permission Requests-->
</resources>

View File

@@ -1,15 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_title">Benvenuti su Briar</string>
<string name="setup_name_explanation">Il tuo nickname verrà mostrato accanto ad ogni contenuto pubblicato. Non puoi cambiarlo dopo averlo creato.</string>
<string name="setup_next">Avanti</string>
<string name="setup_password_intro">Scegli una password</string>
<string name="setup_password_explanation">Il tuo account Briar resta memorizzato cifrato nel tuo dispositivo, non nel cloud. Se dimentichi la password o disinstalli Briar, non c\'è modo di ripristinare l\'account.\n\nScegli una password lunga difficile da indovinare, come quattro parole casuali o dieci lettere, numeri e simboli a caso.</string>
<string name="setup_doze_title">Connessioni in background</string>
<string name="setup_doze_intro">Per ricevere messaggi, Briar deve restare connesso in background.</string>
<string name="setup_doze_explanation">Per ricevere messaggi, Briar deve restare connesso in background. Disattiva le ottimizzazioni della batteria per mantenere Briar connesso.</string>
<string name="setup_doze_button">Permetti le connessioni</string>
<string name="setup_title">Impostazione Briar</string>
<string name="setup_explanation">Il tuo account Briar si trova criptato sul tuo dispositivo e non nel cloud. Se disinstalli Briar o dimentichi la tua password, non c\'è modo di recuperare il tuo account o i tuoi dati.</string>
<string name="choose_nickname">Scegli il tuo nickname</string>
<string name="choose_password">Scegli la tua password</string>
<string name="confirm_password">Conferma la tua password</string>
@@ -17,12 +10,6 @@
<string name="password_too_weak">La password è troppo debole</string>
<string name="passwords_do_not_match">La password non corrisponde</string>
<string name="create_account_button">Creare Account</string>
<string name="more_info">Ulteriori Informazioni</string>
<string name="don_t_ask_again">Non chiedere più</string>
<string name="setup_huawei_text">Tocca il pulsante qua sotto e assicurati che Briar sia protetto nella schermata \"App protette\"</string>
<string name="setup_huawei_button">Proteggi Briar</string>
<string name="setup_huawei_help">Se Briar non viene aggiunto nell\'elenco di app protette, non potrà funzionare in background.</string>
<string name="warning_dozed">%s non ha potuto funzionare in background</string>
<!--Login-->
<string name="enter_password">Inserisci la tua password:</string>
<string name="try_again">Password sbagliata, riprova</string>
@@ -33,13 +20,12 @@
<string name="startup_failed_notification_title">Briar non è riuscito a partire</string>
<string name="startup_failed_notification_text">Potresti dover reinstallare Briar</string>
<string name="startup_failed_activity_title">Fallimento Avvio Briar</string>
<string name="startup_failed_db_error">Per qualche motivo il tuo database di Briar è danneggiato in modo irreversibile. Il tuo account, i tuoi dati e tutti i contatti sono persi. Sfortunatamente devi reinstallare Briar e creare un nuovo account.</string>
<string name="startup_failed_db_error">Per alcune reagioni, il tuo database Briar si è corrotto in modo irreparabile. Il tuo account, i tuoi dati e tutte le connessioni ai tuoi contatti sono andati persi. Sfortunatamente, devi reinstallare Briar per creare un nuovo account.</string>
<string name="startup_failed_service_error">Briar non è stato in grado di caricare un plugin richiesto. Reinstallare Briar di solito sistema questo problema. Però ricorda che perderai il tuo account e tutti i dati ad esso associati poichè Briar non usa server centralizzati per mantenere i tuoi dati.</string>
<plurals name="expiry_warning">
<item quantity="one">Questa è una versione di prova di Briar. Il tuo account scadrà fra %d giorno e non può essere rinnovato.</item>
<item quantity="other">Questa è una versione di prova di Briar. Il tuo account scadrà fra %d giorni e non può essere rinnovato.</item>
<item quantity="one">Questa è una versione beta di Briar. Il tuo account scadrà in %d giorno e non può essere rinnovato.</item>
<item quantity="other">Questa è una versione beta di Briar. Il tuo account scadrà in %d giorni e non può essere rinnovato.</item>
</plurals>
<string name="expiry_update">La scadenza della versione di prova è stata prorogata. Il tuo account ora scadrà fra %d giorni.</string>
<string name="expiry_date_reached">Questo software è scaduto.\nGrazie per il test!</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">Apri la barra di navigazione</string>
@@ -83,7 +69,8 @@
<string name="delete">Cancella</string>
<string name="accept">Accetta</string>
<string name="decline">Declina</string>
<string name="online">Connesso</string>
<string name="options">Opzioni</string>
<string name="online">Connesso</string>
<string name="offline">Disconnesso</string>
<string name="send">Invia</string>
<string name="allow">Abilita</string>
@@ -92,10 +79,8 @@
<string name="ellipsis">...</string>
<string name="text_too_long">Il testo inserito è troppo lungo</string>
<string name="show_onboarding">Mostra l\'aiuto</string>
<string name="fix">Correggi</string>
<string name="help">Aiuto</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">Sembra che tu sia nuovo qui e non hai ancora contatti.\n\nPremi l\'icona in alto e segui le istruzioni per aggiungere qualche amico alla tua lista.\n\nPer favore ricorda: puoi solo aggiungere nuovi contatti faccia-a-faccia per evitare che altri ti impersonino o leggano i tuoi messaggi in futuro.</string>
<string name="no_contacts">Sembra che tu sia nuovo qui e non hai ancora contatti.\n\nPremi l\' icona in alto e segui le istruzioni per aggiungere qualche amico alla tua lista.\n\nPer favore ricorda: Puoi solo aggiungere nuovi contatti faccia-a-faccia per evitare che altri ti impersonino o leggano i tuoi messaggi in futuro.</string>
<string name="date_no_private_messages">Nessun messaggio.</string>
<string name="no_private_messages">Questa è la vista conversazione\n\n\Sembra che ci sia una mancanza di conversazione.\n\nPremi il campo di immissione in basso per iniziare una conversazione.</string>
<string name="message_hint">Scrivi un messaggio</string>
@@ -105,20 +90,31 @@
<string name="contact_deleted_toast">Contatto cancellato</string>
<!--Adding Contacts-->
<string name="add_contact_title">Aggiungi un Contatto</string>
<string name="your_nickname">Scegli l\'identità che vuoi usare:</string>
<string name="face_to_face">Devi incontrarti con la persona che vuoi aggiungere come contatto.\n\nQuesto evita che qualcuno ti impersoni o legga i tuoi messaggi in futuro.</string>
<string name="continue_button">Continua</string>
<string name="your_invitation_code">Il tuo codice di invito è</string>
<string name="enter_invitation_code">Si prega di inserire il vostro codice di invito di contatto:</string>
<string name="searching_format">Sto cercando i contatti con codice di invito %06d\u2026</string>
<string name="connection_failed">Connessione fallita</string>
<string name="could_not_find_contact">Briar non è stato in grado di trovare il vostro contatto vicino</string>
<string name="try_again_button">Riprova</string>
<string name="connected_to_contact">Connesso al contatto</string>
<string name="calculating_confirmation_code">Calcolazione codice di conferma\u2026</string>
<string name="your_confirmation_code">Il tuo codice di conferma è</string>
<string name="enter_confirmation_code">Si prega di inserire il vostro codice di conferma di contatto:</string>
<string name="waiting_for_contact">In attesa del contatto\u2026</string>
<string name="waiting_for_contact_to_scan">Aspettando il contatto per la scansione ed il collegamento\u2026</string>
<string name="exchanging_contact_details">Scambio dettagli contatto\u2026</string>
<string name="codes_do_not_match">I codici non corrispondono</string>
<string name="interfering">Questo può voler dire che qualcuno sta cercando di interferire con la tua conessione</string>
<string name="contact_added_toast">Contatto aggiunto: %s</string>
<string name="contact_already_exists">Il contatto %s esiste già</string>
<string name="contact_exchange_failed">Scambio di contatto fallito</string>
<string name="qr_code_invalid">Il codice QR non è valido</string>
<string name="camera_error">Errore fotocamera</string>
<string name="connecting_to_device">Connessione al dispositivo\u2026</string>
<string name="authenticating_with_device">Autenticazione con il dispositivo\u2026</string>
<string name="connection_aborted_local">Connessione annullata! Potrebbe significare che qualcuno stia interferendo con la tua connessione</string>
<string name="connection_aborted_local">Connessione abortita da noi!Questo può voler dire che qualcuno sta cercando di interferire con la tua connessione</string>
<string name="connection_aborted_remote">Connessione abortita dal tuo contatto!Questo può voler dire che qualcuno sta cercando di interferire con la tua connessione</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Introduzione tuoi contatti</string>
@@ -144,7 +140,7 @@
<item quantity="other">Aggiunti %d nuovi contatti.</item>
</plurals>
<!--Private Groups-->
<string name="groups_list_empty">Non stai partecipando ad alcun gruppo.\n\nClicca l\'icona + in alto per creare un tuo gruppo o chiedi ai tuoi contatti di essere invitato in uno dei loro gruppi.</string>
<string name="groups_list_empty">Non stai partecipando a nessun gruppo.\n\nClicca l\' icona + in alto per creare un tuo gruppo o chiedi ai tuoi contatti di essere invitato in uno dei loro gruppi.</string>
<string name="groups_created_by">Creato da %s</string>
<plurals name="messages">
<item quantity="one">%d messaggio</item>
@@ -197,12 +193,12 @@
<string name="groups_reveal_visible_revealed_by_contact">La relazione fra i contatti è visibile al gruppo (rivelata da %s)</string>
<string name="groups_reveal_invisible">La relazione fra i contatti non è visibile al gruppo</string>
<!--Forums-->
<string name="no_forums">Non hai ancora alcun forum.\n\nPerchè non ne crei uno nuovo tu premendo l\'icona + in alto?\n\nPuoi anche chiedere ai tuoi contatti di condividere forum con te.</string>
<string name="no_forums">Non hai ancora alcun forum.\n\nPerchè non ne crei uno nuovo tu premendo l\' icona + in alto?\n\nPuoi anche chiedere ai tuoi contatti di condividere forums con te.</string>
<string name="create_forum_title">Crea Forum</string>
<string name="choose_forum_hint">Scegli un nome per il tuo forum</string>
<string name="create_forum_button">Crea Forum</string>
<string name="forum_created_toast">Forum creato</string>
<string name="no_forum_posts">Questo forum è vuoto.\n\nUsa l\'icona penna in alto per comporre il primo post.\n\nTi senti solo qui? Condividi questo forum con i tuoi contatti!</string>
<string name="no_forum_posts">Questo forum è vuoto.\n\nUsa l\' icona penna in alto per comporre il primo post.\n\nTi senti solo qui? Condividi questo forum con i tuoi contatti!</string>
<string name="no_posts">Nessun post.</string>
<plurals name="posts">
<item quantity="one">%d post</item>
@@ -339,7 +335,6 @@
<string name="notify_sound_setting_default">Suoneria di default</string>
<string name="notify_sound_setting_disabled">Nessuno</string>
<string name="choose_ringtone_title">Scegli suoneria</string>
<string name="cannot_load_ringtone">Impossibile caricare la suoneria</string>
<!--Settings Feedback-->
<string name="feedback_settings_title">Feedback</string>
<string name="send_feedback">Invia feedback</string>
@@ -369,9 +364,4 @@
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">È stata rilevata un\'overlay sullo schermo</string>
<string name="screen_filter_body">Un\'altra app sta disegnando sopra a Briar. Per proteggere la tua sicurezza, Briar non risponderà ai tocchi quando un\'altra app vi starà disegnando sopra.\n\nProva a spegnere le seguenti app mentre usi Briar:\n\n%1$s</string>
<!--Permission Requests-->
<string name="permission_camera_title">Autorizzazione fotocamera</string>
<string name="permission_camera_request_body">Per scansionare il codice QR, Briar deve accedere alla fotocamera.</string>
<string name="permission_camera_denied_body">Hai negato l\'accesso alla fotocamera, ma questa serve per aggiungere i contatti.\n\nConsidera la possibilità di concedere l\'accesso.</string>
<string name="permission_camera_denied_toast">Autorizzazione fotocamera non concessa</string>
</resources>

View File

@@ -1,124 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_next">次へ</string>
<string name="choose_nickname">ニックネームを入力</string>
<string name="choose_password">パスワードを入力</string>
<string name="confirm_password">確認のため再度パスワードを入力</string>
<string name="name_too_long">名前が長過ぎます。</string>
<string name="password_too_weak">パスワードが弱すぎます。</string>
<string name="passwords_do_not_match">パスワードが一致しません。</string>
<string name="create_account_button">アカウントを作成</string>
<!--Login-->
<string name="enter_password">パスワード入力</string>
<string name="try_again">パスワードが間違っています。もう一度入力してください。</string>
<string name="sign_in_button">Sign In</string>
<string name="forgotten_password">パスワードを忘れました。</string>
<string name="dialog_title_lost_password">パスワードを紛失</string>
<string name="dialog_message_lost_password">あなたのBriarアカウントはクラウド上ではなく、暗号化さた上であなたのデバイスに保存さています。したがってBriarはパスワードをリセットできません。アカウントを削除しはじめからやりなおしますか注意あなたのID、連絡先、メッセージは永久に削除されます。</string>
<string name="startup_failed_notification_title">Briarを起動できません。</string>
<string name="startup_failed_notification_text">Briarを再インストールする必要があります。</string>
<string name="startup_failed_activity_title">起動に失敗</string>
<string name="startup_failed_service_error">プラグインの起動に失敗しました。Briarを再インストールすることで通常は直ります。Briarは中央サーバにデータを保存していないため、アカウントとそれに関連する情報は全て失われることに注意してください。</string>
<string name="expiry_date_reached">このソフトの有効期限が切れました。テストに参加してくださりありがとうございます!</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">ナビゲーションを開く</string>
<string name="nav_drawer_close_description">ナビゲーションを閉じる</string>
<string name="contact_list_button">連絡先</string>
<string name="groups_button">Private Groups</string>
<string name="forums_button">フォーラム</string>
<string name="blogs_button">Blogs</string>
<string name="settings_button">Settings</string>
<string name="sign_out_button">サインアウト</string>
<!--Transports-->
<string name="transport_tor">インターネット</string>
<string name="transport_bt">Bluetooth</string>
<string name="transport_lan">Wi-Fi</string>
<!--Notifications-->
<string name="ongoing_notification_title">Briarにサインイン</string>
<string name="ongoing_notification_text">Briarを開く</string>
<plurals name="private_message_notification_text">
<item quantity="other">%d件の新規プライベートメッセージ</item>
</plurals>
<plurals name="group_message_notification_text">
<item quantity="other">%d件の新規グループメッセージ</item>
</plurals>
<plurals name="forum_post_notification_text">
<item quantity="other">%d件の新規フォーラム投稿</item>
</plurals>
<plurals name="blog_post_notification_text">
<item quantity="other">%d件の新規ブログポスト</item>
</plurals>
<!--Misc-->
<string name="now">現在</string>
<string name="show">表示</string>
<string name="hide">非表示</string>
<string name="ok">OK</string>
<string name="cancel">キャンセル</string>
<string name="got_it">了解</string>
<string name="delete">削除</string>
<string name="accept">承認</string>
<string name="decline">却下</string>
<string name="online">オプション</string>
<string name="offline">オフライン</string>
<string name="send">送信</string>
<string name="allow">許可</string>
<string name="open">開く</string>
<string name="no_data">データなし</string>
<string name="ellipsis">...</string>
<string name="text_too_long">入力された文章が長すぎます。</string>
<string name="show_onboarding">ヘルプを表示</string>
<string name="help">ヘルプ</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">まだ連絡先がないようです。
追加するには+アイコンをタップし、説明にしたがってください。
なりすましやのぞき見を防ぐため、新しい連絡先は対面でしか追加できません。</string>
<string name="date_no_private_messages">メッセージがありません。</string>
<string name="message_hint">メッセージ入力</string>
<string name="delete_contact">連絡先を削除</string>
<string name="contact_deleted_toast">連絡先の削除完了</string>
<!--Adding Contacts-->
<string name="add_contact_title">連絡先を追加</string>
<string name="continue_button">続ける</string>
<string name="connection_failed">接続失敗</string>
<string name="try_again_button">もう一度やり直してください</string>
<!--Introductions-->
<!--Private Groups-->
<string name="groups_remove">削除</string>
<string name="groups_create_group_title">プライベートグループ作成</string>
<string name="groups_create_group_button">グループ作成</string>
<string name="groups_create_group_invitation_button">招待する</string>
<string name="groups_create_group_hint">プライベートグループに名前をつける</string>
<string name="groups_member_list">メンバー一覧</string>
<!--Private Group Invitations-->
<!--Private Groups Revealing Contacts-->
<!--Forums-->
<string name="btn_reply">返信</string>
<!--Forum Sharing-->
<!--Blogs-->
<string name="blogs_publish_blog_post">公開</string>
<!--Blog Sharing-->
<!--RSS Feeds-->
<string name="blogs_rss_feeds_import_button">インポート</string>
<string name="blogs_rss_feeds_manage_author">著者:</string>
<string name="blogs_rss_feeds_manage_updated">最終更新:</string>
<!--Settings Network-->
<string name="tor_network_setting_never">二度としない</string>
<!--Settings Security and Panic-->
<string name="security_settings_title">セキュリティ</string>
<string name="change_password">パスワードの変更</string>
<string name="panic_app_setting_none">なし</string>
<string name="lock_setting_title">サインアウト</string>
<string name="purge_setting_title">アカウントを削除</string>
<!--Settings Notifications-->
<string name="notification_settings_title">通知</string>
<string name="notify_private_messages_setting_title">プライベート・メッセージ</string>
<string name="notify_sound_setting_disabled">なし</string>
<!--Settings Feedback-->
<!--Link Warning-->
<!--Crash Reporter-->
<string name="close">閉じる</string>
<!--Sign Out-->
<!--Screen Filters & Tapjacking-->
<!--Permission Requests-->
</resources>

View File

@@ -34,16 +34,27 @@
<!--Adding Contacts-->
<string name="add_contact_title">Tambah kenalan</string>
<string name="continue_button">Teruskan</string>
<string name="connection_failed">Sambungan gagal</string>
<string name="exchanging_contact_details">Sedang bertukar butiran kenalan\u2026</string>
<string name="contact_added_toast">Kenalan telah ditambah: %s</string>
<string name="your_invitation_code">Kod jemputan anda adalah</string>
<string name="enter_invitation_code">Sila masukkan kod jemputan kenalan anda:</string>
<string name="connection_failed">Sambungan gagal</string>
<string name="could_not_find_contact">Briar tidak dapat mencari kenalan anda berhampiran</string>
<string name="connected_to_contact">Tersambung dengan kenalan</string>
<string name="calculating_confirmation_code">Sedang mencuba kod pengesahan\u2026</string>
<string name="your_confirmation_code">Kod pengesahan anda adalah</string>
<string name="enter_confirmation_code">Sila masukkan kod pengesahan kenalan anda:</string>
<string name="waiting_for_contact">Sedang menunggu kenalan\u2026</string>
<string name="exchanging_contact_details">Sedang bertukar butiran kenalan\u2026</string>
<string name="codes_do_not_match">Kod tidak sepadan</string>
<string name="interfering">Ini menunjukkan bahawa seseorang sedang mengganggu sambungan anda</string>
<string name="contact_added_toast">Kenalan telah ditambah: %s</string>
<!--Introductions-->
<!--Private Groups-->
<!--Private Group Invitations-->
<!--Private Groups Revealing Contacts-->
<!--Forums-->
<string name="create_forum_title">Cipta forum baru</string>
<string name="forum_created_toast">Forum telah dicipta</string>
<string name="choose_forum_name">Pilih nama untuk forum anda:</string>
<string name="forum_created_toast">Forum telah dicipta</string>
<!--Forum Sharing-->
<!--Blogs-->
<!--Blog Sharing-->
@@ -52,7 +63,8 @@
<string name="bluetooth_setting_disabled">Hanya ketika menambah kenalan</string>
<!--Settings Security and Panic-->
<!--Settings Notifications-->
<string name="notify_vibration_setting">Getar</string>
<string name="notify_private_messages_setting">Papar notifikasi untuk mesej peribadi</string>
<string name="notify_vibration_setting">Getar</string>
<string name="notify_sound_setting">Bunyi</string>
<string name="notify_sound_setting_default">Nada bunyi asal</string>
<string name="notify_sound_setting_disabled">Tiada</string>

View File

@@ -1,15 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_title">Velkommen til Briar</string>
<string name="setup_name_explanation">Ditt kallenavn vil bli vist ved siden av innhold du poster. Du kan endre det etter at du har opprettet kontoen din.</string>
<string name="setup_next">Neste</string>
<string name="setup_password_intro">Velg et passord</string>
<string name="setup_password_explanation">Din Briar-konto er lagret kryptert på din enhet, ikke i skyen. Hvis du avinstallerer Briar eller glemmer passordet ditt, går det ikke an å gjenopprette kontoen din..\n\nVelg et langt passord som er vanskelig å gjette, som fire tilfeldige ord, eller ti tilfeldige bokstaver, tall og symboler.</string>
<string name="setup_doze_title">Bakgrunnstilkoblinger</string>
<string name="setup_doze_intro">For å motta meldinger, må Briar forbli tilkoblet i bakgrunnen.</string>
<string name="setup_doze_explanation">For å motta meldinger, må Briar forbli tilkoblet i bakgrunnen. Skru av batterioptimiseringer slik at Briar kan forbli tilkoblet.</string>
<string name="setup_doze_button">Tillat tilkoblinger</string>
<string name="setup_title">Oppsett av Briar</string>
<string name="setup_explanation">Din Briar-konto er lagret kryptert på din enhet, ikke i skyen. Hvis du avinstallerer Briar eller glemmer passordet ditt, går det ikke an å gjenopprette kontoen og dataen din.</string>
<string name="choose_nickname">Velg kallenavn</string>
<string name="choose_password">Velg passord</string>
<string name="confirm_password">Bekreft passord</string>
@@ -17,12 +10,6 @@
<string name="password_too_weak">Passordet er for svakt</string>
<string name="passwords_do_not_match">Passordene samsvarer ikke</string>
<string name="create_account_button">Opprett konto</string>
<string name="more_info">Ytterligere informasjon</string>
<string name="don_t_ask_again">Ikke spør igjen</string>
<string name="setup_huawei_text">Trykk på knappen nedenfor for å forsikre at Briar er beskyttet i \"Beskyttede programmer\".</string>
<string name="setup_huawei_button">Beskytt Briar</string>
<string name="setup_huawei_help">Hvis Briar ikke er lagt til i listen over beskyttede programmer, vil det ikke kunne kjøre i bakgrunnen.</string>
<string name="warning_dozed">%s klarte ikke å kjøre i bakgrunnen</string>
<!--Login-->
<string name="enter_password">Skriv inn passord:</string>
<string name="try_again">Feil passord, prøv igjen</string>
@@ -33,13 +20,12 @@
<string name="startup_failed_notification_title">Briar kunne ikke starte</string>
<string name="startup_failed_notification_text">Det kan hende du må reinstallere Briar.</string>
<string name="startup_failed_activity_title">Oppstartsfeil med Briar</string>
<string name="startup_failed_db_error">Hvis din Briar-database av noen grunn skulle bli skadet uten mulighet for reparasjon, vil din konto, din data, og alle dine kontakter gå tapt. Uheldigvis, må du da reinstallere Briar og sett opp en ny konto.</string>
<string name="startup_failed_db_error">Av ukjent grunn, er din Briar-database skadet og kan ikke repareres. Kontoen din, alle data, og din kontakter har gått tapt for alltid. Beklageligvis må du reinstallere Briar og sette opp en ny konto.</string>
<string name="startup_failed_service_error">Briar kunne ikke starte det nødvendige programtillegget. Reinstallasjon av Briar fikser vanligvis dette problemet. Merk deg dog at kontoen og all data tilknyttet den vil gå tapt for godt siden Briar ikke bruker sentrale tjenere å lagre dataen din på.</string>
<plurals name="expiry_warning">
<item quantity="one">Dette er en test-versjon av Briar. Din konto vil utløpe om %d dag, og kan ikke fornyes.</item>
<item quantity="other">Dette er en test-versjon av Briar. Din konto vil utløpe om %d dager og kan ikke fornyes.</item>
<item quantity="one">Dette er en betaversjon av Briar. Din konto vil utløpe om %d dag og kan ikke fornyes.</item>
<item quantity="other">Dette er en betaversjon av Briar. Din konto vil utløpe om %d dager og kan ikke fornyes.</item>
</plurals>
<string name="expiry_update">Utløpsdatoen for testing har blitt forskjøvet. Kontoen din vil nå utløpe om %d dager.</string>
<string name="expiry_date_reached">Denne programvaren har utløpt.\nTakk for at du testet den.</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">Åpne navigasjonsskuffen</string>
@@ -83,7 +69,8 @@
<string name="delete">Slett</string>
<string name="accept">Godta</string>
<string name="decline">Avslå</string>
<string name="online">Pålogget</string>
<string name="options">Valg</string>
<string name="online">Pålogget</string>
<string name="offline">Frakoblet</string>
<string name="send">Send</string>
<string name="allow">Tillat</string>
@@ -92,8 +79,6 @@
<string name="ellipsis"></string>
<string name="text_too_long">Innskrevet tekst er for lang</string>
<string name="show_onboarding">Vis hjelpedialogvindu</string>
<string name="fix">Fiks</string>
<string name="help">Hjelp</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">Det later til at du er ny her og ikke har noen kontakter enda.\n\nTrykk på \"+\"-ikonet på toppen og følg instruksjonene for å legge til noen venner på listen din.\n\nHusk: Du kan bare legge til nye kontakter ansikt-til-ansikt for å forhindre noen fra å etterligne deg eller fra å lese meldingene dine i framtiden.</string>
<string name="date_no_private_messages">Ingen meldinger.</string>
@@ -105,20 +90,31 @@
<string name="contact_deleted_toast">Kontakt slettet</string>
<!--Adding Contacts-->
<string name="add_contact_title">Legg til en kontakt</string>
<string name="your_nickname">Velg identiteten du ønsker å bruke:</string>
<string name="face_to_face">Du må møte personen du ønsker å legge til som kontakt.\n\nDette vil forhindre folk fra å etterligne deg eller lese meldingene dine i fremtiden.</string>
<string name="continue_button">Fortsett</string>
<string name="your_invitation_code">Din invitasjonskode er</string>
<string name="enter_invitation_code">Skriv inn invitasjonskoden fra din kontakt:</string>
<string name="searching_format">Søker etter kontakt med invitasjonskode %06d\u2026</string>
<string name="connection_failed">Tilkobling mislyktes</string>
<string name="could_not_find_contact">Briar kunne ikke finne kontakten din nærheten</string>
<string name="try_again_button">Prøv igjen</string>
<string name="connected_to_contact">Tilkoblet kontakt</string>
<string name="calculating_confirmation_code">Regner ut bekreftelseskode\u2026</string>
<string name="your_confirmation_code">Din bekreftelseskode er</string>
<string name="enter_confirmation_code">Skriv inn din kontakts bekreftelseskode:</string>
<string name="waiting_for_contact">Venter på kontakt\u2026</string>
<string name="waiting_for_contact_to_scan">Venter på at kontakten skal skanne og koble til\u2026</string>
<string name="exchanging_contact_details">Utveksler kontaktdetaljer\u2026</string>
<string name="codes_do_not_match">Kodene samsvarer ikke</string>
<string name="interfering">Dette kan bety at noen prøver å tukle med tilkoblingen</string>
<string name="contact_added_toast">Kontakt lagt til: %s</string>
<string name="contact_already_exists">Kontakten %s finnes allerede</string>
<string name="contact_exchange_failed">Kontaktutveksling mislyktes</string>
<string name="qr_code_invalid">QR-koden er ugyldig</string>
<string name="camera_error">Kamerafeil</string>
<string name="connecting_to_device">Kobler til enhet\u2026</string>
<string name="authenticating_with_device">Autentiserer med enhet\u2026</string>
<string name="connection_aborted_local">Tilkobling avbrutt! Dette kan bety at noen prøver å tukle med tilkoblingen din</string>
<string name="connection_aborted_local">Tilkoblingen ble avbrutt på din side! Dette kan bety at noen prøver å tukle med tilkoblingen din</string>
<string name="connection_aborted_remote">Tilkobling avbrutt av din kontakt! Dette kan bety at noen prøver å tukle med vedkommendes tilkobling</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Introduser kontaktene dine</string>
@@ -339,7 +335,6 @@
<string name="notify_sound_setting_default">Forvalgt ringetone</string>
<string name="notify_sound_setting_disabled">Ingen</string>
<string name="choose_ringtone_title">Velg ringetone</string>
<string name="cannot_load_ringtone">Kan ikke laste inn ringetone</string>
<!--Settings Feedback-->
<string name="feedback_settings_title">Tilbakemelding</string>
<string name="send_feedback">Send tilbakemelding</string>
@@ -369,9 +364,4 @@
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Skjermoverlag oppdaget</string>
<string name="screen_filter_body">Et annet program tegner på toppen av Briar. For å beskytte din sikkerhet, vil Briar ikke reagere på trykk når et annet program tegner over det.\n\nPrøv å skru av følgende programmer når du bruker Briar.\n\n%1$s</string>
<!--Permission Requests-->
<string name="permission_camera_title">Kameratilgang</string>
<string name="permission_camera_request_body">For å skanne QR-koden, trenger Briar tilgang til kameraet.</string>
<string name="permission_camera_denied_body">Du har nektet tilgang til kameraet, men tillegg av kontakter krever bruk av kameraet.\n\nOvervei å innvilge tilgang.</string>
<string name="permission_camera_denied_toast">Kameratilgang ble ikke innvilget</string>
</resources>

View File

@@ -1,377 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_title">Welkom bij Briar</string>
<string name="setup_name_explanation">Je bijnaam wordt getoond bij alle content die je post. Nadat je account is aangemaakt is het niet mogelijk je bijnaam te wijzigen.</string>
<string name="setup_next">Volgende</string>
<string name="setup_password_intro">Voer een wachtwoord in</string>
<string name="setup_password_explanation">Je Briar-account is versleuteld opgeslagen op je apparaat, niet in de cloud. Als je je wachtwoord vergeet of Briar deïnstalleert is er geen manier om je account te herstellen.\n\nKies een lang wachtwoord dat moeilijk is om te raden , bijvoorbeeld vier willekeurige woorden of tien willekeurige letters, cijfers en symbolen.</string>
<string name="setup_doze_title">Achtergrondverbindingen</string>
<string name="setup_doze_intro">Om berichten te ontvangen dient Briar in de achtergrond verbonden te blijven.</string>
<string name="setup_doze_explanation">Briar moet in de achtergrond verbonden blijven om berichten te ontvangen. Schakel batterij-optimalisaties a.u.b. uit zodat Briar verbonden kan blijven.</string>
<string name="setup_doze_button">Sta verbindingen toe</string>
<string name="choose_nickname">Voer je bijnaam in</string>
<string name="choose_password">Voer je wachtwoord in</string>
<string name="confirm_password">Bevestig je wachtwoord</string>
<string name="name_too_long">Naam is te lang</string>
<string name="password_too_weak">Wachtwoord is te zwak</string>
<string name="passwords_do_not_match">Wachtwoorden komen niet overeen</string>
<string name="create_account_button">Maak account aan</string>
<string name="more_info">Meer informatie</string>
<string name="don_t_ask_again">Vraag niet nog een keer</string>
<string name="setup_huawei_text">Tap a.u.b. op de knop hieronder en ga na dat Briar is beschermd op het scherm \"Beveiligde apps\".</string>
<string name="setup_huawei_button">Protect Briar</string>
<string name="setup_huawei_help">Als Briar niet is toegevoegd aan de beschermde apps, is het niet mogelijk om het in de achtergrond te runnen.</string>
<string name="warning_dozed">%s kon niet in de achtergrond runnen</string>
<!--Login-->
<string name="enter_password">Voer je wachtwoord in:</string>
<string name="try_again">Verkeerd wachtwoord, probeer het nog een keer</string>
<string name="sign_in_button">Log in</string>
<string name="forgotten_password">Ik ben mijn wachtwoord vergeten</string>
<string name="dialog_title_lost_password">Wachtwoord vergeten</string>
<string name="dialog_message_lost_password">Je Briar-account is versleuteld opgeslagen op je apparaat, niet in de cloud, dus kunnen we je wachtwoord niet resetten. Wil je je account verwijderen en opnieuw beginnen?\n\nLet op: Je identiteiten, contacten en berichten zullen permanent verloren gaan.</string>
<string name="startup_failed_notification_title">Briar kon niet opstarten</string>
<string name="startup_failed_notification_text">Misschien moet je Briar opnieuw installeren</string>
<string name="startup_failed_activity_title">Opstarten Briar mislukt</string>
<string name="startup_failed_db_error">Om de een of andere andere reden is je Briar-database corrupt geworden en niet meer te herstellen. Je account, je gegevens en al je contacten zijn verloren. Helaas dien je Briar opnieuw te installeren en een nieuw account aan te maken.</string>
<string name="startup_failed_service_error">Briar kon de vereiste plug-in niet starten. Herinstalleren van Briar lost dit probleem meestal op. Let op dat je al je je account en alle gegevens die daaraan vast zitten zal verliezen omdat Briar geen centrale servers gebruikt om gegevens op te slaan.</string>
<plurals name="expiry_warning">
<item quantity="one">Dit is een testversie van Briar. Je account verloopt binnen %d dag en kan niet worden vernieuwd.</item>
<item quantity="other">Dit is een testversie van Briar. Je account verloopt binnen %d dagen en kan niet worden vernieuwd.</item>
</plurals>
<string name="expiry_update">De verloopdatum voor de test is vooruitgeschoven. Je account verloopt nu binnen %d dagen.</string>
<string name="expiry_date_reached">Deze software is verlopen.\nBedankt vor het testen!</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">Open de navigatielade</string>
<string name="nav_drawer_close_description">Sluit de navigatielade</string>
<string name="contact_list_button">Contacten</string>
<string name="groups_button">Privégroepen</string>
<string name="forums_button">Fora</string>
<string name="blogs_button">Blogs</string>
<string name="settings_button">Instellingen</string>
<string name="sign_out_button">Log uit</string>
<!--Transports-->
<string name="transport_tor">Internet</string>
<string name="transport_bt">Bluetooth</string>
<string name="transport_lan">Wifi</string>
<!--Notifications-->
<string name="ongoing_notification_title">Ingelogd op Briar</string>
<string name="ongoing_notification_text">Raak aan om Briar te openen.</string>
<plurals name="private_message_notification_text">
<item quantity="one">Nieuwe privéberichten.</item>
<item quantity="other">%d nieuwe privéberichten.</item>
</plurals>
<plurals name="group_message_notification_text">
<item quantity="one">Nieuw groepsbericht.</item>
<item quantity="other">%d nieuwe groepsberichten.</item>
</plurals>
<plurals name="forum_post_notification_text">
<item quantity="one">Nieuwe forumpost.</item>
<item quantity="other">%d nieuww forumposts.</item>
</plurals>
<plurals name="blog_post_notification_text">
<item quantity="one">Nieuwe blogpost.</item>
<item quantity="other">%d nieuwe blogposts.</item>
</plurals>
<!--Misc-->
<string name="now">nu</string>
<string name="show">Toon</string>
<string name="hide">Verberg</string>
<string name="ok">Oké</string>
<string name="cancel">Annuleer</string>
<string name="got_it">Begrepen</string>
<string name="delete">Verwijder</string>
<string name="accept">Accepteer</string>
<string name="decline">Wijs af</string>
<string name="online">Online</string>
<string name="offline">Offline</string>
<string name="send">Verstuur</string>
<string name="allow">Sta toe</string>
<string name="open">Open</string>
<string name="no_data">Geen gegevens</string>
<string name="ellipsis"></string>
<string name="text_too_long">De ingevoerde tekst is te lang</string>
<string name="show_onboarding">Toon helpdialoog</string>
<string name="fix">Fiks</string>
<string name="help">Help</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">Zo te zien ben je nieuw hier en heb je nog geen contacten.\n\nTap op het +-icoon bovenaan en volg de instructies om een aantal vrienden aan je list toe te voegen.\n\nHerinner a.u.b.: Je kan alleen in levenden lijve nieuwe contacten toevoevoegen om te voorkomen dat anderen zich als jou voor kunnen doen of in de toekomst je berichten kunnen lezen.</string>
<string name="date_no_private_messages">Geen berichten.</string>
<string name="no_private_messages">Dit is de het gespreksscherm.\n\nHet ziet ernaar uit dat er nog geen gesprek heeft plaatsgevonden.\n\nEenvoudigweg tap op het invoerveld onderaan om een gesprek te starten.</string>
<string name="message_hint">Typ een bericht</string>
<string name="delete_contact">Verwijder bericht</string>
<string name="dialog_title_delete_contact">Bevestig verwijderen contact</string>
<string name="dialog_message_delete_contact">Weet je zeker dat je dit contact en alle berichten die met dit contact zijn uitgewisseld wil verwijderen?</string>
<string name="contact_deleted_toast">Contact is verwijderd</string>
<!--Adding Contacts-->
<string name="add_contact_title">Voed contact toe</string>
<string name="face_to_face">Je moet een persoon in levenden lijve ontmoeten om die als contact toe te voegen.\n\nDit voortkomt dat anderen zich als jou voor kunnen doen of in de toekomst je berichten kunnen lezen.</string>
<string name="continue_button">Ga verder</string>
<string name="connection_failed">Connectie is mislukt</string>
<string name="try_again_button">Probeer nog een keer</string>
<string name="waiting_for_contact_to_scan">Wacht op contact om te scannen en te verbinden\u2026</string>
<string name="exchanging_contact_details">Contactdetails aan het uitwisselen\u2026</string>
<string name="contact_added_toast">Contact toegevoegd: %s</string>
<string name="contact_already_exists">Contact %s bestaat al</string>
<string name="contact_exchange_failed">Uitwisselen contact is mislukt</string>
<string name="qr_code_invalid">De QR-code is ongeldig</string>
<string name="camera_error">Camerafout</string>
<string name="connecting_to_device">Aan het verbinden met apparaat\u2026</string>
<string name="authenticating_with_device">Aan het authentificeren met apparaat\u2026</string>
<string name="connection_aborted_local">Verbinding is verbroken! Dit kan betekenen dat iemand probeert te met je verbinding probeert te bemoeien</string>
<string name="connection_aborted_remote">Verbinding is door je contact verbroken! Dit kan betekenen dat iemand met je verbinding probeert te bemoeien</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Introduceer je contacten</string>
<string name="introduction_onboarding_text">Je kan je contacten aan elkaar introduceren zodat ze niet in levenden lijve elkaar te hoeven ontmoeten om op Briar in contact te komen.</string>
<string name="introduction_activity_title">Selecteer contact</string>
<string name="introduction_message_title">Intoduceer contacten</string>
<string name="introduction_message_hint">Voeg een bericht toe (optioneel)</string>
<string name="introduction_button">Introduceer</string>
<string name="introduction_sent">Je introductie is verzonden</string>
<string name="introduction_error">Er trad een fout op tijdens het aanmaken van de introductie.</string>
<string name="introduction_response_error">Fout tijdens reageren op een introductie</string>
<string name="introduction_request_sent">Je hebt gevraagd om %1$s te introduceren aan %2$s.</string>
<string name="introduction_request_received">%1$s heeft voorgesteld om je te introduceren aan %2$s. Wil je %2$s toevoegen aan je contactenlijst?</string>
<string name="introduction_request_exists_received">%1$s heeft gevraagd om je te introduceren aan %2$s, maar %2$s staat al in je contactenlijst. Omdat %1$s dit niet mag weten kun je nog steeds antwoorden:</string>
<string name="introduction_request_answered_received">%1$s heeft gevraagd om je te introduceren aan %2$s.</string>
<string name="introduction_response_accepted_sent">Je hebt de introductie aan %1$s geaccepteerd.</string>
<string name="introduction_response_declined_sent">Je hebt de introductie aan %1$s afgewezen.</string>
<string name="introduction_response_accepted_received">%1$s heeft de introductie aan %2$sgeaccepteerd.</string>
<string name="introduction_response_declined_received">%1$s heeft de introductie aan %2$s afgewezen.</string>
<string name="introduction_response_declined_received_by_introducee">%1$s zegt dat %2$s je introductie heeft afgewezen.</string>
<plurals name="introduction_notification_text">
<item quantity="one">Nieuw contact is toegevoegd.</item>
<item quantity="other">%d nieuwe contacten zijn toegevoegd.</item>
</plurals>
<!--Private Groups-->
<string name="groups_list_empty">Je hebt nog geen groepen gebruikt.\n\nTap op het +-icoon om zelf een groep aan te maken of vraag je contacten of ze je voor een van hun groepen willen uitnodigen.</string>
<string name="groups_created_by">Is aangemaakt door %s</string>
<plurals name="messages">
<item quantity="one">%d bericht</item>
<item quantity="other">%d berichten</item>
</plurals>
<string name="groups_group_is_empty">Deze groep is leeg</string>
<string name="groups_group_is_dissolved">Deze groep is opgeheven</string>
<string name="groups_remove">Verwijder</string>
<string name="groups_create_group_title">Maak privégroep aan</string>
<string name="groups_create_group_button">Maak groep aan</string>
<string name="groups_create_group_invitation_button">Verstuur uitnodiging</string>
<string name="groups_create_group_hint">Voer een naam in voor je privégroep</string>
<string name="groups_invitation_sent">Groepsuitnodiging is verstuurd</string>
<string name="groups_message_sent">Bericht is verstuurd</string>
<string name="groups_member_list">Ledenlijst</string>
<string name="groups_invite_members">Nodig leden uit</string>
<string name="groups_member_created_you">Je hebt de groep aangemaakt</string>
<string name="groups_member_created">%s heeft de groep aangemaakt</string>
<string name="groups_member_joined_you">Je hebt lid geworden van de groep</string>
<string name="groups_member_joined">%s is lid geworden van de groep</string>
<string name="groups_leave">Verlaat groep</string>
<string name="groups_leave_dialog_title">Bevestig verlaten groep</string>
<string name="groups_leave_dialog_message">Weet je zeker dat je deze groep wil verlaten? </string>
<string name="groups_dissolve">Hef groep op</string>
<string name="groups_dissolve_dialog_title">Bevestig opheffen groep</string>
<string name="groups_dissolve_dialog_message">Weet je zeker dat je deze groep wil opheffen?\n\nAlle andere leden zullen dan niet meer hun gesprek voort kunnen zetten en het zou kunnen dat ze de laatste berichten niet ontvangen.</string>
<string name="groups_dissolve_button">Hef op</string>
<string name="groups_dissolved_dialog_title">Groep is opgeheven</string>
<string name="groups_dissolved_dialog_message">De maker van deze groep heeft deze opgeheven.\n\nJe kan niet langer berichten aan de groep sturen en het zou kunnen dat je niet alle posts hebt ontvangen die aan deze groep zijn verstuurd.</string>
<!--Private Group Invitations-->
<string name="groups_invitations_title">Groepsuitnodigingen</string>
<string name="groups_invitations_invitation_sent">Je hebt %1$s uitgenodigd om lid te worden van de groep \"%2$s\".</string>
<string name="groups_invitations_invitation_received">%1$s heeft je uitgenodigd om lid te worden van de groep \"%2$s\".</string>
<string name="groups_invitations_joined">Lid geworden van groep</string>
<string name="groups_invitations_declined">Groepsuitnodiging is afgewezen</string>
<plurals name="groups_invitations_open">
<item quantity="one">%d openstaande groepsuitnodiging</item>
<item quantity="other">%d openstaande groepsuitnodigingen</item>
</plurals>
<string name="groups_invitations_response_accepted_sent">Je hebt de groepsuitnodiging van %s geaccepteerd.</string>
<string name="groups_invitations_response_declined_sent">Je hebt de groepsuitnodiging van %s afgewezen.</string>
<string name="groups_invitations_response_accepted_received">%s heeft de groepsuitnodiging geaccepteerd.</string>
<string name="groups_invitations_response_declined_received">%s heeft de groepsuitnodiging afgewezen.</string>
<string name="sharing_status_groups">Alleen degene die een groep heeft aangemaakt kan nieuwe leden voor die groep uitnodigen. Hieronder staan alle huidige leden van de groep.</string>
<!--Private Groups Revealing Contacts-->
<string name="groups_reveal_contacts">Openbaar contacten</string>
<string name="groups_reveal_dialog_message">Je kan ervoor kiezen om contacten te openbaren aan alle huidige en toekomstige leden van deze groep.\n\nOpenbaren van contacten maakt je verbinding met de groep sneller en meer betrouwbaar omdat je kan communiceren met geopenbaarde contacten, zelfs als degene die de groep heeft aangemaakt offline is.</string>
<string name="groups_reveal_visible">Contactrelatie is zichtbaar voor de groep</string>
<string name="groups_reveal_visible_revealed_by_us">Contactrelatie is zichtbaar voor de groep (door jou geopenbaard)</string>
<string name="groups_reveal_visible_revealed_by_contact">Contactrelaties zijn zichtbaar voor de groep (geopenbaard door %s)</string>
<string name="groups_reveal_invisible">Contactrelatie is niet zichtbaar voor de groep</string>
<!--Forums-->
<string name="no_forums">Je hebt nog heen fora.\n\nWaarom maak je er zelf geen aan door te tappen op het +-icoon bovenin?\n\nJe kan ook je contacten vragen om fora met je te delen.</string>
<string name="create_forum_title">Maak forum aan</string>
<string name="choose_forum_hint">Voer een naam in voor je forum</string>
<string name="create_forum_button">Maak forum aan</string>
<string name="forum_created_toast">Forum aangemaakt</string>
<string name="no_forum_posts">Dit forum is leeg.\n\nGebruik het penicoon bovenin om om de eerste post op te stellen.\n\nVoel je je hier eenzaam? Deel dit forum met meerdere van je contacten.</string>
<string name="no_posts">Geen posts</string>
<plurals name="posts">
<item quantity="one">%d post</item>
<item quantity="other">%d posts</item>
</plurals>
<string name="forum_new_entry_posted">Forumonderwerp is gepost</string>
<string name="forum_new_message_hint">Nieuw onderwerp</string>
<string name="forum_message_reply_hint">Nieuwe reactie</string>
<string name="btn_reply">Antwoord</string>
<string name="forum_leave">Verlaat forum</string>
<string name="dialog_title_leave_forum">Bevestig verlaten forum</string>
<string name="dialog_message_leave_forum">Weet je zeker dat je dit forum wil verlaten? Contacten die je via dit forum hebt gedeeld kunnen afgesloten worden van het ontvangen van updates van dit forum.</string>
<string name="dialog_button_leave">Verlaat</string>
<string name="forum_left_toast">Forum verlaten</string>
<!--Forum Sharing-->
<string name="forum_share_button">Deel forum</string>
<string name="contacts_selected">Contacten geselecteerd</string>
<string name="activity_share_toolbar_header">Kies contacten</string>
<string name="no_contacts_selector">Het ziet eruit dat je hier nieuw bent en nog geen contacten hebt.\n\nKom a.u.b. later hier terug om je eerste contact toe te voegen.</string>
<string name="forum_shared_snackbar">Forum is gedeeld met uitgekozen contacten</string>
<string name="forum_share_message">Voeg een bericht toe (optioneel)</string>
<string name="forum_share_error">Er trad een fout op bij het delen van dit forum.</string>
<string name="forum_invitation_received">%1$s heeft het forum \"%2$s\" met je gedeeld.</string>
<string name="forum_invitation_sent">Jij hebt het forum \"%1$s\" gedeeld met %2$s.</string>
<string name="forum_invitations_title">Forumuitnodigingen</string>
<string name="forum_invitation_exists">Je hebt al een uitnodiging van dit forum geaccepteerd. Het accepteren van meer uitnodigingen zal de communicatie in het forum laten groeien en versterken.</string>
<string name="forum_joined_toast">Forum lid geworden</string>
<string name="forum_declined_toast">Forumuitnodiging afgewezen</string>
<string name="shared_by_format">Is gedeeld door %s</string>
<string name="forum_invitation_already_sharing">Reeds gedeeld</string>
<string name="forum_invitation_response_accepted_sent">Je hebt een forumuitnodiging geaccepteerd van %s.</string>
<string name="forum_invitation_response_declined_sent">Je hebt een forumuitnodiging afgewezen van %s.</string>
<string name="forum_invitation_response_accepted_received">%s heeft de forumuitnodiging geaccepteerd.</string>
<string name="forum_invitation_response_declined_received">%s heeft de forumuitnodiging afgewezen.</string>
<string name="sharing_status">Deelstatus</string>
<string name="sharing_status_forum">Alle leden van een forum kunnen dat forum delen met hun contacten. Momenteel deel je dit forum met de volgende contacten. Er kunnen ook andere leden zijn die je niet kan zien.</string>
<string name="shared_with">Is gedeeld met %1$d (%2$d online)</string>
<plurals name="forums_shared">
<item quantity="one">%d forum is gedeeld door contacten.</item>
<item quantity="other">%d fora zijn gedeeld door contacten.</item>
</plurals>
<string name="nobody">Niemand</string>
<!--Blogs-->
<string name="blogs_other_blog_empty_state">Deze blog is momenteel leeg.\n\nNog de auteur heeft iets geschreven, nog de personen met wie deze blog met je hebben gedeeld moeten nog online komen zodat posts gesynchroniseerd kunnen worden.</string>
<string name="read_more">lees meer</string>
<string name="blogs_write_blog_post">Schrijf blogpost</string>
<string name="blogs_write_blog_post_body_hint">Typ hier je blogpost</string>
<string name="blogs_publish_blog_post">Publiceer</string>
<string name="blogs_blog_post_created">Blogpost aangemaakt</string>
<string name="blogs_blog_post_received">Nieuwe blogpost ontvangen</string>
<string name="blogs_blog_post_scroll_to">Scroll naar</string>
<string name="blogs_feed_empty_state">Dit is de globale blogfeed.\n\nHet ziet ernaar uit dat nog niemand iets geblogd heeft.\n\nWees de eerste en tap op de pen om een nieuwe blogpost te schrijven.</string>
<string name="blogs_remove_blog">Verwijder bblog</string>
<string name="blogs_remove_blog_dialog_message">Weet je zeker dat je deze blog en alle bijbehorende posts wil verwijderen?\n\nMerk op dat deze blog niet van apparaten van andere mensen wordt verwijderd?</string>
<string name="blogs_remove_blog_ok">Verwijder blog</string>
<string name="blogs_blog_removed">Blog is verwijderd</string>
<string name="blogs_reblog_comment_hint">Voeg commentaar toe (optioneel)</string>
<string name="blogs_reblog_button">Reblog</string>
<!--Blog Sharing-->
<string name="blogs_sharing_share">Deel blog</string>
<string name="blogs_sharing_error">Er was een fout tijdens het delen van deze blog.</string>
<string name="blogs_sharing_button">Deel blog</string>
<string name="blogs_sharing_snackbar">Blog is gedeeld met gekozen contacten</string>
<string name="blogs_sharing_response_accepted_sent">Je hebt de bloguitnodiging van %s geaccepteerd.</string>
<string name="blogs_sharing_response_declined_sent">Je hebt de bloguitnodiging van %s afgewezen.</string>
<string name="blogs_sharing_response_accepted_received">%s heeft de bloguitnodiging geaccepteerd.</string>
<string name="blogs_sharing_response_declined_received">%s heeft de bloguitnodiging afgewezen.</string>
<string name="blogs_sharing_invitation_received">%1$s heeft de blog \"%2$s\" met je gedeeld.</string>
<string name="blogs_sharing_invitation_sent">Je hebt de blog \"%1$s\" gedeeld met %2$s.</string>
<string name="blogs_sharing_invitations_title">Bloguitnodigingen</string>
<string name="blogs_sharing_joined_toast">Lid geworden van blog</string>
<string name="blogs_sharing_declined_toast">Bloguitnodiging afgewezen</string>
<string name="sharing_status_blog">Alle leden die lid worden van een blog kunnen deze blog delen met hun contacten. Je deelt deze blog al met de volgende contacten. Er kunnen ook andere leden zijn die je niet kan zien.</string>
<!--RSS Feeds-->
<string name="blogs_rss_feeds_import">Importeer RSS-feed</string>
<string name="blogs_rss_feeds_import_button">Importeer</string>
<string name="blogs_rss_feeds_import_hint">Voer de URL van de RSS-feed in</string>
<string name="blogs_rss_feeds_import_error">Excuses! Er trad een fout op bij het importeren van je feed.</string>
<string name="blogs_rss_feeds_manage">Beheer RSS-feeds</string>
<string name="blogs_rss_feeds_manage_imported">Geïmporteerd:</string>
<string name="blogs_rss_feeds_manage_author">Auteur:</string>
<string name="blogs_rss_feeds_manage_updated">Laatst bijgewerkt:</string>
<string name="blogs_rss_remove_feed">Verwijder feed</string>
<string name="blogs_rss_remove_feed_dialog_message">Weet je zeker dat je deze feed en alle bijbehorende posts wil verwijderen?\nPosts die je hebt gedeeld worden niet van apparaten van anderen verwijderd.</string>
<string name="blogs_rss_remove_feed_ok">Verwijder feed</string>
<string name="blogs_rss_feeds_manage_delete_error">De feed kon niet worden verwijderd.</string>
<string name="blogs_rss_feeds_manage_empty_state">Je hebt nog geen RSS-feeds geïmporteerd.\n\nWaarom klik je niet op de plus in de rechterbovenhoek van het scherm om de eerste toe te voegen?</string>
<string name="blogs_rss_feeds_manage_error">Er was een probleem met het laden van je feeds. Probeer het later nog een keer.</string>
<!--Settings Network-->
<string name="network_settings_title">Netwerken</string>
<string name="bluetooth_setting">Verbind via Bluetooth</string>
<string name="bluetooth_setting_enabled">Alleen als contacten dichtbij zijn</string>
<string name="bluetooth_setting_disabled">Alleen tijdens toevoegen van contacten</string>
<string name="tor_network_setting">Verbind via Tor</string>
<string name="tor_network_setting_never">Nooit</string>
<string name="tor_network_setting_wifi">Alleen tijdens gebruik van wifi</string>
<string name="tor_network_setting_always">Tijdens gebruik van wifi of mobiele dataverbinding</string>
<!--Settings Security and Panic-->
<string name="security_settings_title">Beveiliging</string>
<string name="change_password">Wijzig wachtwoord</string>
<string name="current_password">Voer je huidige wachtwoord in:</string>
<string name="choose_new_password">Voer je nieuwe wachtwoord in:</string>
<string name="confirm_new_password">Bevestig je nieuwe wachtwoord:</string>
<string name="password_changed">Wachtwoord is gewijzigd</string>
<string name="panic_setting">Paniekknopinstellingen</string>
<string name="panic_setting_title">Paniekknop</string>
<string name="panic_setting_hint">Configureer hoe Briar reageert als je paniekknopapp gebruikt.</string>
<string name="panic_app_setting_title">Paniekknopapp</string>
<string name="unknown_app">een onbekende app</string>
<string name="panic_app_setting_summary">Er is geen app ingesteld</string>
<string name="panic_app_setting_none">Geen</string>
<string name="dialog_title_connect_panic_app">Bevestig paniekapp</string>
<string name="dialog_message_connect_panic_app">Weet je zeker dat je wil toestaan dat %1$s destructieve paniekknopacties mag veroorzaken?</string>
<string name="lock_setting_title">Log uit</string>
<string name="lock_setting_summary">Log uit op Briar als de paniekknop wordt ingedrukt.</string>
<string name="purge_setting_title">Verwijder account</string>
<string name="purge_setting_summary">Verwijder je Briar-account als een paniekknop wordt ingedrukt. Let op: Dit zal permanent je identiteit, contacten en berichten verwijderen.</string>
<string name="uninstall_setting_title">Deïnstalleer Briar</string>
<string name="uninstall_setting_summary">Dit vereist een handmatige bevestiging op een paniekmoment</string>
<!--Settings Notifications-->
<string name="notification_settings_title">Meldingen</string>
<string name="notify_private_messages_setting_title">Privéberichten</string>
<string name="notify_private_messages_setting_summary">Toon meldingen voor privéberichten</string>
<string name="notify_group_messages_setting_title">Groepsberichten</string>
<string name="notify_group_messages_setting_summary">Toon meldingen voor groepsberichten</string>
<string name="notify_forum_posts_setting_title">Forumposts</string>
<string name="notify_forum_posts_setting_summary">Toon meldingen voor forumposts</string>
<string name="notify_blog_posts_setting_title">Blogposts</string>
<string name="notify_blog_posts_setting_summary">Toon meldingen voor blogposts</string>
<string name="notify_vibration_setting">Tril</string>
<string name="notify_lock_screen_setting_title">Schermvergrendeling</string>
<string name="notify_lock_screen_setting_summary">Toon notificaties over de schermvergrendeling</string>
<string name="notify_sound_setting">Geluid</string>
<string name="notify_sound_setting_default">Standaardringtoon</string>
<string name="notify_sound_setting_disabled">Geen</string>
<string name="choose_ringtone_title">Kies ringtoon</string>
<string name="cannot_load_ringtone">Kan ringtoon niet laden</string>
<!--Settings Feedback-->
<string name="feedback_settings_title">Terugkoppeling</string>
<string name="send_feedback">Verstuur terugkoppeling</string>
<!--Link Warning-->
<string name="link_warning_title">Linkwaarschuwing</string>
<string name="link_warning_intro">Je staat op het punt om de volgende link te open met een externe app.</string>
<string name="link_warning_text">Dit kan gebruikt worden om te achterhalen wie jij bent. Denk na of je de persoon vertrouwd die je deze link heeft gestuurd en overweeg deze link te openen met Orfox.</string>
<string name="link_warning_open_link">Open link</string>
<!--Crash Reporter-->
<string name="crash_report_title">Crashrapport Briar</string>
<string name="briar_crashed">Excuses, Briar is gecrashed.</string>
<string name="not_your_fault">Dit is niet je schuld</string>
<string name="please_send_report">Help ons a.u.b. om Briar te verbeteren door crashrapporten naar ons te sturen.</string>
<string name="report_is_encrypted">We beloven dat het rapport is versleuteld en veilig wordt verzonden.</string>
<string name="feedback_title">Terugkoppeling</string>
<string name="describe_crash">Beschrijf wat er is gebeurd (optioneel)</string>
<string name="enter_feedback">Voer je terugkoppeling in</string>
<string name="optional_contact_email">Je e-mailadres (optioneel)</string>
<string name="include_debug_report_crash">Voeg geanonimiseerde gegevens toe over de crash.</string>
<string name="include_debug_report_feedback">Voeg geanonimiseerde gegevens over dit apparaat toe.</string>
<string name="could_not_load_report_data">Kon rapportgegevens niet laden.</string>
<string name="send_report">Verstuur rapport</string>
<string name="close">Sluit</string>
<string name="dev_report_saved">Rapport is opgeslagen. Het zal worden verstuurd als je de volgende keer inlogt op Briar </string>
<!--Sign Out-->
<string name="progress_title_logout">Uitloggen van Briar…</string>
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Schermoverlay detecteerd</string>
<string name="screen_filter_body">Een andere app ligt boven Briar. Om je veiligheid te beschermen zal Briar niet reageren op aanrakingen als er een andere app boven ligt.\n\nProbeer de volgende apps uit te schakelen als je Briar gebruikt:\n\n%1$s</string>
<!--Permission Requests-->
<string name="permission_camera_title">Cameratoestemming</string>
<string name="permission_camera_request_body">Om de QR-code te scannen moet Briar toegang hebben tot de camera.</string>
<string name="permission_camera_denied_body">Je hebt toegang tot de camera niet vrijgegeven, terwijl het toevoegen van contacten de camera nodig heeft.\n\nOverweeg a.u.b. toegang vrij te geven.</string>
<string name="permission_camera_denied_toast">Cameratoegang niet vrijgegeven</string>
</resources>

View File

@@ -1,6 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_title">Configuracion de Briar</string>
<string name="setup_explanation">Vòstre compte Briar es salvat e chifrat sus vòstre aparelh, non pas sus un servidor alonhat \"cloud\". Se desinstalletz Briar o oblidetz vòstre senhal, vòstre compte e vòstras donadas serà pas possible de los recuperar.</string>
<string name="choose_nickname">Causir un escais-nom</string>
<string name="choose_password">Causir un senhal</string>
<string name="confirm_password">Confirmar lo senhal</string>
@@ -20,7 +22,12 @@ Volètz suprimir vòstre compte e ne crear un nòu?\n
<string name="startup_failed_notification_title">Briar a pas pogut aviar</string>
<string name="startup_failed_notification_text">Benlèu que vos cal tornar installar Briar.</string>
<string name="startup_failed_activity_title">Fracàs de laviada de Briar.</string>
<string name="startup_failed_db_error">Per una rason desconeguda vòstra basa de donadas Briar es pas utilizabla nimai recuperabla. Vòstres comptes, donadas e contactes son perduts. Per malastre vos cal tornar installar Briar e configurar un compte nòu.</string>
<string name="startup_failed_service_error">Briar a pas pogut aviar un modul necessari. Tornar installar Briar pòt resolver aquò. Que aquò siá dich:perdretz vòstre compte e totas las donadas ligadas a aqueste. Briar utiliza pas de servidor centralizat per salvar sas donadas.</string>
<plurals name="expiry_warning">
<item quantity="one">Aquò es una version Beta de Briar. Vòstre compte sacabarà dins %d jorn e poirà pas èsser renovat.</item>
<item quantity="other">Aquò es una version Beta de Briar. Vòstre compte sacabarà dins %d jorns e poirà pas èsser renovat.</item>
</plurals>
<string name="expiry_date_reached">Vòstre logicial sacabèt.\nMercés daver ensajat!</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">Dobrir lo panèl de navigacion</string>
@@ -64,7 +71,8 @@ Volètz suprimir vòstre compte e ne crear un nòu?\n
<string name="delete">Suprimir</string>
<string name="accept">Acceptar</string>
<string name="decline">Refusar</string>
<string name="online">En linha</string>
<string name="options">Opcions</string>
<string name="online">En linha</string>
<string name="offline">Fòra linha</string>
<string name="send">Mandar</string>
<string name="allow">Autorizar</string>
@@ -73,7 +81,6 @@ Volètz suprimir vòstre compte e ne crear un nòu?\n
<string name="ellipsis">...</string>
<string name="text_too_long">Lo tèxte picat es tròp long</string>
<string name="show_onboarding">Mostrar lajuda</string>
<string name="help">Ajuda</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">Sembla que sètz novèl aquí e quavètz pas cap de contacte.\n\nQuichatz licòna ennaut e seguètz las instruccions per napondre. Informacion:podètz pas quapondre lo monde se se tròban en fàcia de vos, aquò per evitar quòm vos raube lidentitat e que vòstres messatges sián legits per dautres.</string>
<string name="date_no_private_messages">Cap messatge</string>
@@ -85,18 +92,31 @@ Volètz suprimir vòstre compte e ne crear un nòu?\n
<string name="contact_deleted_toast">Contacte suprimit</string>
<!--Adding Contacts-->
<string name="add_contact_title">Ajustar un contacte</string>
<string name="your_nickname">Triatz lidentitat que volètz utilizar</string>
<string name="face_to_face">Vos cal vos amassar amb la persona que volètz apondre als contactes.\n\n Aquò es fach per evitar quòm vos raube lidentitat e que vòstres messatges sián legits per dautres.</string>
<string name="continue_button">Contunhar</string>
<string name="your_invitation_code">Vòstre còdi de convidacion es</string>
<string name="enter_invitation_code">Mercés de marcar lo còdi de convidacion de vòstre contacte:</string>
<string name="searching_format">Recercar de contactes amb lo còdi de convidacion %06d\u2026</string>
<string name="connection_failed">Fracàs de la connexion</string>
<string name="could_not_find_contact">Briar a pas trobar degun a proximitat</string>
<string name="try_again_button">Tornar ensajar</string>
<string name="connected_to_contact">Connectat al contacte</string>
<string name="calculating_confirmation_code">Calcul del còdi de confirmacion\u2026</string>
<string name="your_confirmation_code">Vòstre còdi de confirmacion es </string>
<string name="enter_confirmation_code">Mercés de marcar lo còdi de confirmacion de vòstre contacte:</string>
<string name="waiting_for_contact">En espèra del contacte\u2026</string>
<string name="waiting_for_contact_to_scan">En espèra de la numerizacion e de la connexion al contacte\u2026</string>
<string name="exchanging_contact_details">Escambi dels detalhs del contacte\u2026</string>
<string name="codes_do_not_match">Los còdis correspondon pas</string>
<string name="interfering">Poriá arribar que qualquun ensajar dinterferir dins la connexion</string>
<string name="contact_added_toast">Contacte apondut:%s</string>
<string name="contact_already_exists">Lo contacte %s existís ja</string>
<string name="contact_exchange_failed">Lescambi de contacte a fracassat</string>
<string name="qr_code_invalid">Lo QR còdi es invalid</string>
<string name="connecting_to_device">Connexion a laparelh\u2026</string>
<string name="authenticating_with_device">Autentificacion amb laparelh\u2026</string>
<string name="connection_aborted_local">Avèm copat la connexion!Poiriá arribar que qualquun ensage dinterferir amb aquela</string>
<string name="connection_aborted_remote">Vòstre contacte a copat la connexion!Poiriá arribar que qualquun ensage dinterferir amb aquela</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Presentatz vòstres contactes</string>
@@ -346,5 +366,4 @@ Volètz suprimir vòstre compte e ne crear un nòu?\n
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Superposicion detectada</string>
<string name="screen_filter_body">Una autra aplicacion saficha per dessús Briar. Per protegir vòstre seguretat Briar respond pas quand una autra aplicacion saficha par dessús.\n\nEnsajatz de tampar las aplicacions seguentas quand utilizatz Briar:\n\n%1$s</string>
<!--Permission Requests-->
</resources>

View File

@@ -1,15 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_title">Bem-vindo ao Briar</string>
<string name="setup_name_explanation">Seu nome de usuária será mostrado próximo a qualquer conteúdo que você publicar. Você não pode mudá-lo depois que criar sua conta,</string>
<string name="setup_next">Próximo</string>
<string name="setup_password_intro">Escolha uma senha</string>
<string name="setup_password_explanation">A sua conta do Briar está salva de modo criptografado no seu dispositivo, não na nuvem. Se você esquecer sua senha nou desinstalar o Briar não há como recuperar sua conta.\n\nEscolha uma senha longa que seja difícil de ser adivinhada, como cinco palavras aleatórias ou dez letras, números e símbolos aleatórios.</string>
<string name="setup_doze_title">Conexões em segundo plano</string>
<string name="setup_doze_intro">Para receber mensagens, o Briar precisa permanecer conectado em segundo plano.</string>
<string name="setup_doze_explanation">Para receber mensagens, o Briar precisa permanecer conectado em segundo plano. Por favor, desative as otimizações da bateria para que o Briar possa permanecer conectado.</string>
<string name="setup_doze_button">Pemitir conexões</string>
<string name="setup_title">Configuração de Briar</string>
<string name="setup_explanation">Sua conta Briar é armazenada criptografada apenas no seu dispositivo, não na nuvem. Se você Desinstalar o Briar ou esquecer sua senha, não será possível recuperar sua conta e seus dados.</string>
<string name="choose_nickname">Escolha seu apelido</string>
<string name="choose_password">Escolha sua senha</string>
<string name="confirm_password">Confirme sua senha</string>
@@ -17,12 +10,6 @@
<string name="password_too_weak">A senha está muito fraca</string>
<string name="passwords_do_not_match">As senhas não conferem</string>
<string name="create_account_button">Criar conta</string>
<string name="more_info">Informações adicionais</string>
<string name="don_t_ask_again">Não perguntar novamente</string>
<string name="setup_huawei_text">Toque no botão abaixo e verifique se o Briar está protegido na tela \"Aplicativos Protegidos\"</string>
<string name="setup_huawei_button">Proteger o Briar</string>
<string name="setup_huawei_help">Se o Briar não for adicionado à lista de aplicativos protegidos ele não poderá ser executado em segundo plano.</string>
<string name="warning_dozed">%s não pôde ser executado em segundo plano</string>
<!--Login-->
<string name="enter_password">Insira sua senha:</string>
<string name="try_again">Senha Incorreta, tente novamente</string>
@@ -33,13 +20,12 @@
<string name="startup_failed_notification_title">Briar não pode iniciar</string>
<string name="startup_failed_notification_text">Você pode precisar reinstalar o Briar.</string>
<string name="startup_failed_activity_title">Inicialização do Briar falhou</string>
<string name="startup_failed_db_error">Por algum motivo, seu banco de dados do Briar foi corrompido enão pode ser reparado. Sua conta, seus dados e todos seus contatos estão perdidos. Infelizmente, você precisa reinstalar o Briar e criar uma nova conta.</string>
<string name="startup_failed_db_error">Por alguma razão, seus dados do Briar estão corrompidos e não podem ser reparados. Sua conta, seus dados e todas suas conexões com contatos estão perdidas. Infelizmente você terá que resintalar o Briar e criar uma nova conta.</string>
<string name="startup_failed_service_error">O Briar não pode iniciar devido a um plugin. Reinstalar o Briar geralmente resolve esse problema. Porém, note que ao fazer isso você perderá sua conta e todos os dados associados a ela, já que o Briar não usa um servidor central para armazenar seus dados.</string>
<plurals name="expiry_warning">
<item quantity="one">Esta é uma versão de teste do Briar. Sua conta irá expirar em %d dia e não poderá ser renovada.</item>
<item quantity="other">Esta é uma versão de teste do Briar. Sua conta irá expirar em %d dias e não poderá ser renovada.</item>
<item quantity="one">Esta é uma versão beta do Briar. Sua conta irá expirar em %d dias e não poderá ser renovada.</item>
<item quantity="other">Esta é uma versão beta do Briar. Sua conta irá expirar em %d dias e não poderá ser renovada.</item>
</plurals>
<string name="expiry_update">O prazo de validade da versão de teste foi extendido. Agora sua conta irá expirar em %d dias.</string>
<string name="expiry_date_reached">Este software expirou.\nObrigado por testar!</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">Abrir aba de navegação</string>
@@ -83,7 +69,8 @@
<string name="delete">Deletar</string>
<string name="accept">Aceitar</string>
<string name="decline">Recusar</string>
<string name="online">Online</string>
<string name="options">Opções</string>
<string name="online">Online</string>
<string name="offline">Offline</string>
<string name="send">Enviar</string>
<string name="allow">Permitir</string>
@@ -92,8 +79,6 @@
<string name="ellipsis"></string>
<string name="text_too_long">O texto entrado é muito grande</string>
<string name="show_onboarding">Mostrar dialogo de ajuda</string>
<string name="fix">Consertar</string>
<string name="help">Ajuda</string>
<!--Contacts and Private Conversations-->
<string name="no_contacts">Parece que você é novo aqui e ainda não tem contatos.\nClique no ícone + no topo e siga as instruções para adicionar alguns amigos para sua lista.\nPor favor lembre: você só pode adicionar contatos pessoalmente para prevenir alguém se passar por você ou ler suas mensagens no futuro. </string>
<string name="date_no_private_messages">Sem Mensagens</string>
@@ -105,20 +90,31 @@
<string name="contact_deleted_toast">Contato apagado</string>
<!--Adding Contacts-->
<string name="add_contact_title">Adicionar um contato</string>
<string name="your_nickname">Escolha a identidade que você quer usar:</string>
<string name="face_to_face">Você deve estar frente-a-frente com a pessoa que deseja adicionar como contato.\n\nIsso evita que alguém se passe por você ou leia suas mensagens no futuro.</string>
<string name="continue_button">Continuar</string>
<string name="your_invitation_code">Seu código do convite é</string>
<string name="enter_invitation_code">Insira o código do convite do contato:</string>
<string name="searching_format">Procurando por contato com código de convite %06d\u2026</string>
<string name="connection_failed">Conexão incompleta</string>
<string name="could_not_find_contact">Briar não conseguiu achar nenhum contato por perto</string>
<string name="try_again_button">Tente novamente</string>
<string name="connected_to_contact">Conectado ao contato</string>
<string name="calculating_confirmation_code">Calculando código de confirmação\u2026</string>
<string name="your_confirmation_code">Seu código de confirmação é</string>
<string name="enter_confirmation_code">Insira o código de confirmação do contato:</string>
<string name="waiting_for_contact">Esperando pelo contato\u2026</string>
<string name="waiting_for_contact_to_scan">Esperando pelo contato escanear e conectar\u2026</string>
<string name="exchanging_contact_details">Transferindo detalhes do contato\u2026</string>
<string name="codes_do_not_match">Códigos não conferem</string>
<string name="interfering">Isto pode significar que algúem está tentando interferir em sua conexão</string>
<string name="contact_added_toast">Contato adicionado: %s</string>
<string name="contact_already_exists">Contato %s já existe</string>
<string name="contact_exchange_failed">Troca de contatos falhou</string>
<string name="qr_code_invalid">O QR code é inválido</string>
<string name="camera_error">Erro da câmera</string>
<string name="connecting_to_device">Conectando a device\u2026</string>
<string name="authenticating_with_device">Autenticando com o dispositivo\u2026</string>
<string name="connection_aborted_local">Conexão abortada! Isso pode significar que alguém está tentando interferir na sua conexão.</string>
<string name="connection_aborted_local">Conexão interrompida por nós! Isso pode disgnificar que alguém está tentando interferir na sua conexão</string>
<string name="connection_aborted_remote">Conexão interrompida pelo seu contato! Isto pode significar que algúem está tentando interferir em sua conexão</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Apresente seus contatos</string>
@@ -339,7 +335,6 @@
<string name="notify_sound_setting_default">Toque padrão</string>
<string name="notify_sound_setting_disabled">Nenhum</string>
<string name="choose_ringtone_title">Escolher toque</string>
<string name="cannot_load_ringtone">Não foi possível carregar o toque</string>
<!--Settings Feedback-->
<string name="feedback_settings_title">Feedback</string>
<string name="send_feedback">Enviar feedback</string>
@@ -367,11 +362,5 @@
<!--Sign Out-->
<string name="progress_title_logout">Saindo do Briar…</string>
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Sobreposição de tela detectada</string>
<string name="screen_filter_body">Outro aplicativo está rodando por cima do Briar. Para proteger sua segurança, o Briar não irá responder por toques quando outro aplicativo estiver por cima.\n\nTente desligar os seguintes aplicativos quando for usar o Briar:\n\n %1$s</string>
<!--Permission Requests-->
<string name="permission_camera_title">Permissão da câmera</string>
<string name="permission_camera_request_body">O Briar precisa acessar a câmera para pode escanear o QR code.</string>
<string name="permission_camera_denied_body">Você negou acesso à câmera, mas adicionar contatos requer que você use a câmera.\n\nPor favor, considere a concessão de acesso a ela.</string>
<string name="permission_camera_denied_toast">A permissão da câmera não foi concedida</string>
</resources>

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