mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
12 Commits
alpha-1.5.
...
beta-1.5.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f1a97f62d | ||
|
|
7a33d26533 | ||
|
|
f2c85f37be | ||
|
|
8e3fa872fd | ||
|
|
0d1e81ebdb | ||
|
|
bded4e7bc8 | ||
|
|
bf1a5cf218 | ||
|
|
dd7a638984 | ||
|
|
942222131e | ||
|
|
4a4b04bec3 | ||
|
|
462f57c966 | ||
|
|
8d20c5d8b8 |
@@ -13,8 +13,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 31
|
targetSdkVersion 31
|
||||||
versionCode 10502
|
versionCode 10503
|
||||||
versionName "1.5.2"
|
versionName "1.5.3"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|||||||
@@ -54,6 +54,38 @@ public interface CryptoComponent {
|
|||||||
KeyPair ourKeyPair, byte[]... inputs)
|
KeyPair ourKeyPair, byte[]... inputs)
|
||||||
throws GeneralSecurityException;
|
throws GeneralSecurityException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives a shared secret from two static and two ephemeral key pairs.
|
||||||
|
* <p>
|
||||||
|
* Do not use this method for new protocols. The shared secret can be
|
||||||
|
* re-derived using the ephemeral public keys and both static private
|
||||||
|
* keys, so keys derived from the shared secret should not be used if
|
||||||
|
* forward secrecy is required. Use {@link #deriveSharedSecret(String,
|
||||||
|
* PublicKey, PublicKey, KeyPair, KeyPair, boolean, byte[]...)} instead.
|
||||||
|
* <p>
|
||||||
|
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* @param label A namespaced label indicating the purpose of this shared
|
||||||
|
* secret, to prevent it from being repurposed or colliding with a shared
|
||||||
|
* secret derived for another purpose
|
||||||
|
* @param theirStaticPublicKey The static public key of the remote party
|
||||||
|
* @param theirEphemeralPublicKey The ephemeral public key of the remote
|
||||||
|
* party
|
||||||
|
* @param ourStaticKeyPair The static key pair of the local party
|
||||||
|
* @param ourEphemeralKeyPair The ephemeral key pair of the local party
|
||||||
|
* @param alice True if the local party is Alice
|
||||||
|
* @param inputs Additional inputs that will be included in the
|
||||||
|
* derivation of the shared secret
|
||||||
|
* @return The shared secret
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
SecretKey deriveSharedSecretBadly(String label,
|
||||||
|
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
|
||||||
|
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
|
||||||
|
boolean alice, byte[]... inputs)
|
||||||
|
throws GeneralSecurityException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives a shared secret from two static and two ephemeral key pairs.
|
* Derives a shared secret from two static and two ephemeral key pairs.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -32,8 +32,15 @@ public interface RecordReader {
|
|||||||
* 'accept' or 'ignore' predicates
|
* 'accept' or 'ignore' predicates
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
Record readRecord(Predicate<Record> accept, Predicate<Record> ignore)
|
Record readRecord(RecordPredicate accept, RecordPredicate ignore)
|
||||||
throws IOException;
|
throws IOException;
|
||||||
|
|
||||||
void close() throws IOException;
|
void close() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that reifies the generic interface {@code Predicate<Record>}
|
||||||
|
* for easier testing.
|
||||||
|
*/
|
||||||
|
interface RecordPredicate extends Predicate<Record> {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.bramble.contact;
|
package org.briarproject.bramble.contact;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.Predicate;
|
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
||||||
@@ -24,6 +23,7 @@ import org.briarproject.bramble.api.properties.TransportProperties;
|
|||||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||||
import org.briarproject.bramble.api.record.Record;
|
import org.briarproject.bramble.api.record.Record;
|
||||||
import org.briarproject.bramble.api.record.RecordReader;
|
import org.briarproject.bramble.api.record.RecordReader;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||||
import org.briarproject.bramble.api.record.RecordWriter;
|
import org.briarproject.bramble.api.record.RecordWriter;
|
||||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||||
@@ -61,12 +61,12 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
|
|||||||
getLogger(ContactExchangeManagerImpl.class.getName());
|
getLogger(ContactExchangeManagerImpl.class.getName());
|
||||||
|
|
||||||
// Accept records with current protocol version, known record type
|
// Accept records with current protocol version, known record type
|
||||||
private static final Predicate<Record> ACCEPT = r ->
|
private static final RecordPredicate ACCEPT = r ->
|
||||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||||
isKnownRecordType(r.getRecordType());
|
isKnownRecordType(r.getRecordType());
|
||||||
|
|
||||||
// Ignore records with current protocol version, unknown record type
|
// Ignore records with current protocol version, unknown record type
|
||||||
private static final Predicate<Record> IGNORE = r ->
|
private static final RecordPredicate IGNORE = r ->
|
||||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||||
!isKnownRecordType(r.getRecordType());
|
!isKnownRecordType(r.getRecordType());
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,31 @@ import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
|
|||||||
interface HandshakeConstants {
|
interface HandshakeConstants {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current version of the handshake protocol.
|
* The current major version of the handshake protocol.
|
||||||
*/
|
*/
|
||||||
byte PROTOCOL_VERSION = 0;
|
byte PROTOCOL_MAJOR_VERSION = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Label for deriving the master key.
|
* The current minor version of the handshake protocol.
|
||||||
*/
|
*/
|
||||||
String MASTER_KEY_LABEL = "org.briarproject.bramble.handshake/MASTER_KEY";
|
byte PROTOCOL_MINOR_VERSION = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for deriving the master key when using the deprecated v0.0 key
|
||||||
|
* derivation method.
|
||||||
|
* <p>
|
||||||
|
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
String MASTER_KEY_LABEL_0_0 =
|
||||||
|
"org.briarproject.bramble.handshake/MASTER_KEY";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for deriving the master key when using the v0.1 key derivation
|
||||||
|
* method.
|
||||||
|
*/
|
||||||
|
String MASTER_KEY_LABEL_0_1 =
|
||||||
|
"org.briarproject.bramble.handshake/MASTER_KEY_0_1";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Label for deriving Alice's proof of ownership from the master key.
|
* Label for deriving Alice's proof of ownership from the master key.
|
||||||
|
|||||||
@@ -13,11 +13,26 @@ interface HandshakeCrypto {
|
|||||||
KeyPair generateEphemeralKeyPair();
|
KeyPair generateEphemeralKeyPair();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives the master key from the given static and ephemeral keys.
|
* Derives the master key from the given static and ephemeral keys using
|
||||||
|
* the deprecated v0.0 key derivation method.
|
||||||
|
* <p>
|
||||||
|
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
|
||||||
*
|
*
|
||||||
* @param alice Whether the local peer is Alice
|
* @param alice Whether the local peer is Alice
|
||||||
*/
|
*/
|
||||||
SecretKey deriveMasterKey(PublicKey theirStaticPublicKey,
|
@Deprecated
|
||||||
|
SecretKey deriveMasterKey_0_0(PublicKey theirStaticPublicKey,
|
||||||
|
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||||
|
KeyPair ourEphemeralKeyPair, boolean alice)
|
||||||
|
throws GeneralSecurityException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives the master key from the given static and ephemeral keys using
|
||||||
|
* the v0.1 key derivation method.
|
||||||
|
*
|
||||||
|
* @param alice Whether the local peer is Alice
|
||||||
|
*/
|
||||||
|
SecretKey deriveMasterKey_0_1(PublicKey theirStaticPublicKey,
|
||||||
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||||
KeyPair ourEphemeralKeyPair, boolean alice)
|
KeyPair ourEphemeralKeyPair, boolean alice)
|
||||||
throws GeneralSecurityException;
|
throws GeneralSecurityException;
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import static org.briarproject.bramble.contact.HandshakeConstants.ALICE_PROOF_LABEL;
|
import static org.briarproject.bramble.contact.HandshakeConstants.ALICE_PROOF_LABEL;
|
||||||
import static org.briarproject.bramble.contact.HandshakeConstants.BOB_PROOF_LABEL;
|
import static org.briarproject.bramble.contact.HandshakeConstants.BOB_PROOF_LABEL;
|
||||||
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL;
|
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL_0_0;
|
||||||
|
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL_0_1;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -32,7 +33,8 @@ class HandshakeCryptoImpl implements HandshakeCrypto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SecretKey deriveMasterKey(PublicKey theirStaticPublicKey,
|
@Deprecated
|
||||||
|
public SecretKey deriveMasterKey_0_0(PublicKey theirStaticPublicKey,
|
||||||
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||||
KeyPair ourEphemeralKeyPair, boolean alice) throws
|
KeyPair ourEphemeralKeyPair, boolean alice) throws
|
||||||
GeneralSecurityException {
|
GeneralSecurityException {
|
||||||
@@ -46,9 +48,29 @@ class HandshakeCryptoImpl implements HandshakeCrypto {
|
|||||||
alice ? ourEphemeral : theirEphemeral,
|
alice ? ourEphemeral : theirEphemeral,
|
||||||
alice ? theirEphemeral : ourEphemeral
|
alice ? theirEphemeral : ourEphemeral
|
||||||
};
|
};
|
||||||
return crypto.deriveSharedSecret(MASTER_KEY_LABEL, theirStaticPublicKey,
|
return crypto.deriveSharedSecretBadly(MASTER_KEY_LABEL_0_0,
|
||||||
theirEphemeralPublicKey, ourStaticKeyPair, ourEphemeralKeyPair,
|
theirStaticPublicKey, theirEphemeralPublicKey,
|
||||||
alice, inputs);
|
ourStaticKeyPair, ourEphemeralKeyPair, alice, inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretKey deriveMasterKey_0_1(PublicKey theirStaticPublicKey,
|
||||||
|
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||||
|
KeyPair ourEphemeralKeyPair, boolean alice) throws
|
||||||
|
GeneralSecurityException {
|
||||||
|
byte[] theirStatic = theirStaticPublicKey.getEncoded();
|
||||||
|
byte[] theirEphemeral = theirEphemeralPublicKey.getEncoded();
|
||||||
|
byte[] ourStatic = ourStaticKeyPair.getPublic().getEncoded();
|
||||||
|
byte[] ourEphemeral = ourEphemeralKeyPair.getPublic().getEncoded();
|
||||||
|
byte[][] inputs = {
|
||||||
|
alice ? ourStatic : theirStatic,
|
||||||
|
alice ? theirStatic : ourStatic,
|
||||||
|
alice ? ourEphemeral : theirEphemeral,
|
||||||
|
alice ? theirEphemeral : ourEphemeral
|
||||||
|
};
|
||||||
|
return crypto.deriveSharedSecret(MASTER_KEY_LABEL_0_1,
|
||||||
|
theirStaticPublicKey, theirEphemeralPublicKey,
|
||||||
|
ourStaticKeyPair, ourEphemeralKeyPair, alice, inputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.briarproject.bramble.contact;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.Pair;
|
import org.briarproject.bramble.api.Pair;
|
||||||
import org.briarproject.bramble.api.Predicate;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.contact.HandshakeManager;
|
import org.briarproject.bramble.api.contact.HandshakeManager;
|
||||||
import org.briarproject.bramble.api.contact.PendingContact;
|
import org.briarproject.bramble.api.contact.PendingContact;
|
||||||
@@ -12,12 +11,12 @@ import org.briarproject.bramble.api.crypto.KeyPair;
|
|||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.TransactionManager;
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.record.Record;
|
import org.briarproject.bramble.api.record.Record;
|
||||||
import org.briarproject.bramble.api.record.RecordReader;
|
import org.briarproject.bramble.api.record.RecordReader;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||||
import org.briarproject.bramble.api.record.RecordWriter;
|
import org.briarproject.bramble.api.record.RecordWriter;
|
||||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||||
@@ -28,15 +27,20 @@ import java.io.EOFException;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES;
|
import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES;
|
||||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_VERSION;
|
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MAJOR_VERSION;
|
||||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.EPHEMERAL_PUBLIC_KEY;
|
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MINOR_VERSION;
|
||||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.PROOF_OF_OWNERSHIP;
|
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_EPHEMERAL_PUBLIC_KEY;
|
||||||
|
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_MINOR_VERSION;
|
||||||
|
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_PROOF_OF_OWNERSHIP;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -44,12 +48,14 @@ import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
|||||||
class HandshakeManagerImpl implements HandshakeManager {
|
class HandshakeManagerImpl implements HandshakeManager {
|
||||||
|
|
||||||
// Ignore records with current protocol version, unknown record type
|
// Ignore records with current protocol version, unknown record type
|
||||||
private static final Predicate<Record> IGNORE = r ->
|
private static final RecordPredicate IGNORE = r ->
|
||||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
r.getProtocolVersion() == PROTOCOL_MAJOR_VERSION &&
|
||||||
!isKnownRecordType(r.getRecordType());
|
!isKnownRecordType(r.getRecordType());
|
||||||
|
|
||||||
private static boolean isKnownRecordType(byte type) {
|
private static boolean isKnownRecordType(byte type) {
|
||||||
return type == EPHEMERAL_PUBLIC_KEY || type == PROOF_OF_OWNERSHIP;
|
return type == RECORD_TYPE_EPHEMERAL_PUBLIC_KEY ||
|
||||||
|
type == RECORD_TYPE_PROOF_OF_OWNERSHIP ||
|
||||||
|
type == RECORD_TYPE_MINOR_VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final TransactionManager db;
|
private final TransactionManager db;
|
||||||
@@ -61,7 +67,7 @@ class HandshakeManagerImpl implements HandshakeManager {
|
|||||||
private final RecordWriterFactory recordWriterFactory;
|
private final RecordWriterFactory recordWriterFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
HandshakeManagerImpl(DatabaseComponent db,
|
HandshakeManagerImpl(TransactionManager db,
|
||||||
IdentityManager identityManager,
|
IdentityManager identityManager,
|
||||||
ContactManager contactManager,
|
ContactManager contactManager,
|
||||||
TransportCrypto transportCrypto,
|
TransportCrypto transportCrypto,
|
||||||
@@ -95,19 +101,31 @@ class HandshakeManagerImpl implements HandshakeManager {
|
|||||||
.createRecordWriter(out.getOutputStream());
|
.createRecordWriter(out.getOutputStream());
|
||||||
KeyPair ourEphemeralKeyPair =
|
KeyPair ourEphemeralKeyPair =
|
||||||
handshakeCrypto.generateEphemeralKeyPair();
|
handshakeCrypto.generateEphemeralKeyPair();
|
||||||
PublicKey theirEphemeralPublicKey;
|
Pair<Byte, PublicKey> theirMinorVersionAndKey;
|
||||||
if (alice) {
|
if (alice) {
|
||||||
|
sendMinorVersion(recordWriter);
|
||||||
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
|
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
|
||||||
theirEphemeralPublicKey = receivePublicKey(recordReader);
|
theirMinorVersionAndKey = receiveMinorVersionAndKey(recordReader);
|
||||||
} else {
|
} else {
|
||||||
theirEphemeralPublicKey = receivePublicKey(recordReader);
|
theirMinorVersionAndKey = receiveMinorVersionAndKey(recordReader);
|
||||||
|
sendMinorVersion(recordWriter);
|
||||||
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
|
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
|
||||||
}
|
}
|
||||||
|
byte theirMinorVersion = theirMinorVersionAndKey.getFirst();
|
||||||
|
PublicKey theirEphemeralPublicKey = theirMinorVersionAndKey.getSecond();
|
||||||
SecretKey masterKey;
|
SecretKey masterKey;
|
||||||
try {
|
try {
|
||||||
masterKey = handshakeCrypto.deriveMasterKey(theirStaticPublicKey,
|
if (theirMinorVersion > 0) {
|
||||||
theirEphemeralPublicKey, ourStaticKeyPair,
|
masterKey = handshakeCrypto.deriveMasterKey_0_1(
|
||||||
ourEphemeralKeyPair, alice);
|
theirStaticPublicKey, theirEphemeralPublicKey,
|
||||||
|
ourStaticKeyPair, ourEphemeralKeyPair, alice);
|
||||||
|
} else {
|
||||||
|
// TODO: Remove this branch after a reasonable migration
|
||||||
|
// period (added 2023-03-10).
|
||||||
|
masterKey = handshakeCrypto.deriveMasterKey_0_0(
|
||||||
|
theirStaticPublicKey, theirEphemeralPublicKey,
|
||||||
|
ourStaticKeyPair, ourEphemeralKeyPair, alice);
|
||||||
|
}
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
throw new FormatException();
|
throw new FormatException();
|
||||||
}
|
}
|
||||||
@@ -128,34 +146,91 @@ class HandshakeManagerImpl implements HandshakeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void sendPublicKey(RecordWriter w, PublicKey k) throws IOException {
|
private void sendPublicKey(RecordWriter w, PublicKey k) throws IOException {
|
||||||
w.writeRecord(new Record(PROTOCOL_VERSION, EPHEMERAL_PUBLIC_KEY,
|
w.writeRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||||
k.getEncoded()));
|
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY, k.getEncoded()));
|
||||||
w.flush();
|
w.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
private PublicKey receivePublicKey(RecordReader r) throws IOException {
|
/**
|
||||||
byte[] key = readRecord(r, EPHEMERAL_PUBLIC_KEY).getPayload();
|
* Receives the remote peer's protocol minor version and ephemeral public
|
||||||
|
* key.
|
||||||
|
* <p>
|
||||||
|
* In version 0.1 of the protocol, each peer sends a minor version record
|
||||||
|
* followed by an ephemeral public key record.
|
||||||
|
* <p>
|
||||||
|
* In version 0.0 of the protocol, each peer sends an ephemeral public key
|
||||||
|
* record without a preceding minor version record.
|
||||||
|
* <p>
|
||||||
|
* Therefore the remote peer's minor version must be non-zero if a minor
|
||||||
|
* version record is received, and is assumed to be zero if no minor
|
||||||
|
* version record is received.
|
||||||
|
*/
|
||||||
|
private Pair<Byte, PublicKey> receiveMinorVersionAndKey(RecordReader r)
|
||||||
|
throws IOException {
|
||||||
|
byte theirMinorVersion;
|
||||||
|
PublicKey theirEphemeralPublicKey;
|
||||||
|
// The first record can be either a minor version record or an
|
||||||
|
// ephemeral public key record
|
||||||
|
Record first = readRecord(r, asList(RECORD_TYPE_MINOR_VERSION,
|
||||||
|
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY));
|
||||||
|
if (first.getRecordType() == RECORD_TYPE_MINOR_VERSION) {
|
||||||
|
// The payload must be a single byte giving the remote peer's
|
||||||
|
// protocol minor version, which must be non-zero
|
||||||
|
byte[] payload = first.getPayload();
|
||||||
|
checkLength(payload, 1);
|
||||||
|
theirMinorVersion = payload[0];
|
||||||
|
if (theirMinorVersion == 0) throw new FormatException();
|
||||||
|
// The second record must be an ephemeral public key record
|
||||||
|
Record second = readRecord(r,
|
||||||
|
singletonList(RECORD_TYPE_EPHEMERAL_PUBLIC_KEY));
|
||||||
|
theirEphemeralPublicKey = parsePublicKey(second);
|
||||||
|
} else {
|
||||||
|
// The remote peer did not send a minor version record, so the
|
||||||
|
// remote peer's protocol minor version is assumed to be zero
|
||||||
|
// TODO: Remove this branch after a reasonable migration period
|
||||||
|
// (added 2023-03-10).
|
||||||
|
theirMinorVersion = 0;
|
||||||
|
theirEphemeralPublicKey = parsePublicKey(first);
|
||||||
|
}
|
||||||
|
return new Pair<>(theirMinorVersion, theirEphemeralPublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PublicKey parsePublicKey(Record rec) throws IOException {
|
||||||
|
if (rec.getRecordType() != RECORD_TYPE_EPHEMERAL_PUBLIC_KEY) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
byte[] key = rec.getPayload();
|
||||||
checkLength(key, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES);
|
checkLength(key, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES);
|
||||||
return new AgreementPublicKey(key);
|
return new AgreementPublicKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendProof(RecordWriter w, byte[] proof) throws IOException {
|
private void sendProof(RecordWriter w, byte[] proof) throws IOException {
|
||||||
w.writeRecord(new Record(PROTOCOL_VERSION, PROOF_OF_OWNERSHIP, proof));
|
w.writeRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||||
|
RECORD_TYPE_PROOF_OF_OWNERSHIP, proof));
|
||||||
w.flush();
|
w.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] receiveProof(RecordReader r) throws IOException {
|
private byte[] receiveProof(RecordReader r) throws IOException {
|
||||||
byte[] proof = readRecord(r, PROOF_OF_OWNERSHIP).getPayload();
|
Record rec = readRecord(r,
|
||||||
|
singletonList(RECORD_TYPE_PROOF_OF_OWNERSHIP));
|
||||||
|
byte[] proof = rec.getPayload();
|
||||||
checkLength(proof, PROOF_BYTES, PROOF_BYTES);
|
checkLength(proof, PROOF_BYTES, PROOF_BYTES);
|
||||||
return proof;
|
return proof;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Record readRecord(RecordReader r, byte expectedType)
|
private void sendMinorVersion(RecordWriter w) throws IOException {
|
||||||
|
w.writeRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||||
|
RECORD_TYPE_MINOR_VERSION,
|
||||||
|
new byte[] {PROTOCOL_MINOR_VERSION}));
|
||||||
|
w.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Record readRecord(RecordReader r, List<Byte> expectedTypes)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
// Accept records with current protocol version, expected type only
|
// Accept records with current protocol version, expected types only
|
||||||
Predicate<Record> accept = rec ->
|
RecordPredicate accept = rec ->
|
||||||
rec.getProtocolVersion() == PROTOCOL_VERSION &&
|
rec.getProtocolVersion() == PROTOCOL_MAJOR_VERSION &&
|
||||||
rec.getRecordType() == expectedType;
|
expectedTypes.contains(rec.getRecordType());
|
||||||
Record rec = r.readRecord(accept, IGNORE);
|
Record rec = r.readRecord(accept, IGNORE);
|
||||||
if (rec == null) throw new EOFException();
|
if (rec == null) throw new EOFException();
|
||||||
return rec;
|
return rec;
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ package org.briarproject.bramble.contact;
|
|||||||
*/
|
*/
|
||||||
interface HandshakeRecordTypes {
|
interface HandshakeRecordTypes {
|
||||||
|
|
||||||
byte EPHEMERAL_PUBLIC_KEY = 0;
|
byte RECORD_TYPE_EPHEMERAL_PUBLIC_KEY = 0;
|
||||||
|
|
||||||
byte PROOF_OF_OWNERSHIP = 1;
|
byte RECORD_TYPE_PROOF_OF_OWNERSHIP = 1;
|
||||||
|
|
||||||
|
byte RECORD_TYPE_MINOR_VERSION = 2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,7 +222,8 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SecretKey deriveSharedSecret(String label,
|
@Deprecated
|
||||||
|
public SecretKey deriveSharedSecretBadly(String label,
|
||||||
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
|
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
|
||||||
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
|
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
|
||||||
boolean alice, byte[]... inputs) throws GeneralSecurityException {
|
boolean alice, byte[]... inputs) throws GeneralSecurityException {
|
||||||
@@ -250,6 +251,35 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
return new SecretKey(hash);
|
return new SecretKey(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretKey deriveSharedSecret(String label,
|
||||||
|
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
|
||||||
|
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
|
||||||
|
boolean alice, byte[]... inputs) throws GeneralSecurityException {
|
||||||
|
PrivateKey ourStaticPrivateKey = ourStaticKeyPair.getPrivate();
|
||||||
|
PrivateKey ourEphemeralPrivateKey = ourEphemeralKeyPair.getPrivate();
|
||||||
|
byte[][] hashInputs = new byte[inputs.length + 3][];
|
||||||
|
// Alice ephemeral/Bob ephemeral
|
||||||
|
hashInputs[0] = performRawKeyAgreement(ourEphemeralPrivateKey,
|
||||||
|
theirEphemeralPublicKey);
|
||||||
|
// Alice static/Bob ephemeral, Bob static/Alice ephemeral
|
||||||
|
if (alice) {
|
||||||
|
hashInputs[1] = performRawKeyAgreement(ourStaticPrivateKey,
|
||||||
|
theirEphemeralPublicKey);
|
||||||
|
hashInputs[2] = performRawKeyAgreement(ourEphemeralPrivateKey,
|
||||||
|
theirStaticPublicKey);
|
||||||
|
} else {
|
||||||
|
hashInputs[1] = performRawKeyAgreement(ourEphemeralPrivateKey,
|
||||||
|
theirStaticPublicKey);
|
||||||
|
hashInputs[2] = performRawKeyAgreement(ourStaticPrivateKey,
|
||||||
|
theirEphemeralPublicKey);
|
||||||
|
}
|
||||||
|
arraycopy(inputs, 0, hashInputs, 3, inputs.length);
|
||||||
|
byte[] hash = hash(label, hashInputs);
|
||||||
|
if (hash.length != SecretKey.LENGTH) throw new IllegalStateException();
|
||||||
|
return new SecretKey(hash);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] sign(String label, byte[] toSign, PrivateKey privateKey)
|
public byte[] sign(String label, byte[] toSign, PrivateKey privateKey)
|
||||||
throws GeneralSecurityException {
|
throws GeneralSecurityException {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Predicate;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.record.Record;
|
import org.briarproject.bramble.api.record.Record;
|
||||||
import org.briarproject.bramble.api.record.RecordReader;
|
import org.briarproject.bramble.api.record.RecordReader;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||||
import org.briarproject.bramble.api.record.RecordWriter;
|
import org.briarproject.bramble.api.record.RecordWriter;
|
||||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||||
@@ -34,12 +34,12 @@ class KeyAgreementTransport {
|
|||||||
Logger.getLogger(KeyAgreementTransport.class.getName());
|
Logger.getLogger(KeyAgreementTransport.class.getName());
|
||||||
|
|
||||||
// Accept records with current protocol version, known record type
|
// Accept records with current protocol version, known record type
|
||||||
private static final Predicate<Record> ACCEPT = r ->
|
private static final RecordPredicate ACCEPT = r ->
|
||||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||||
isKnownRecordType(r.getRecordType());
|
isKnownRecordType(r.getRecordType());
|
||||||
|
|
||||||
// Ignore records with current protocol version, unknown record type
|
// Ignore records with current protocol version, unknown record type
|
||||||
private static final Predicate<Record> IGNORE = r ->
|
private static final RecordPredicate IGNORE = r ->
|
||||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||||
!isKnownRecordType(r.getRecordType());
|
!isKnownRecordType(r.getRecordType());
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.bramble.record;
|
package org.briarproject.bramble.record;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.Predicate;
|
|
||||||
import org.briarproject.bramble.api.record.Record;
|
import org.briarproject.bramble.api.record.Record;
|
||||||
import org.briarproject.bramble.api.record.RecordReader;
|
import org.briarproject.bramble.api.record.RecordReader;
|
||||||
import org.briarproject.bramble.util.ByteUtils;
|
import org.briarproject.bramble.util.ByteUtils;
|
||||||
@@ -45,7 +44,7 @@ class RecordReaderImpl implements RecordReader {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Record readRecord(Predicate<Record> accept, Predicate<Record> ignore)
|
public Record readRecord(RecordPredicate accept, RecordPredicate ignore)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (eof()) return null;
|
if (eof()) return null;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package org.briarproject.bramble.sync;
|
package org.briarproject.bramble.sync;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.Predicate;
|
|
||||||
import org.briarproject.bramble.api.UniqueId;
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
import org.briarproject.bramble.api.record.Record;
|
import org.briarproject.bramble.api.record.Record;
|
||||||
import org.briarproject.bramble.api.record.RecordReader;
|
import org.briarproject.bramble.api.record.RecordReader;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||||
@@ -41,12 +41,12 @@ import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
|||||||
class SyncRecordReaderImpl implements SyncRecordReader {
|
class SyncRecordReaderImpl implements SyncRecordReader {
|
||||||
|
|
||||||
// Accept records with current protocol version, known record type
|
// Accept records with current protocol version, known record type
|
||||||
private static final Predicate<Record> ACCEPT = r ->
|
private static final RecordPredicate ACCEPT = r ->
|
||||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||||
isKnownRecordType(r.getRecordType());
|
isKnownRecordType(r.getRecordType());
|
||||||
|
|
||||||
// Ignore records with current protocol version, unknown record type
|
// Ignore records with current protocol version, unknown record type
|
||||||
private static final Predicate<Record> IGNORE = r ->
|
private static final RecordPredicate IGNORE = r ->
|
||||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||||
!isKnownRecordType(r.getRecordType());
|
!isKnownRecordType(r.getRecordType());
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,316 @@
|
|||||||
|
package org.briarproject.bramble.contact;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
|
import org.briarproject.bramble.api.contact.HandshakeManager.HandshakeResult;
|
||||||
|
import org.briarproject.bramble.api.contact.PendingContact;
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
|
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
|
import org.briarproject.bramble.api.record.Record;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReader;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriter;
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||||
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.briarproject.bramble.test.DbExpectations;
|
||||||
|
import org.briarproject.bramble.test.PredicateMatcher;
|
||||||
|
import org.jmock.Expectations;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES;
|
||||||
|
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MAJOR_VERSION;
|
||||||
|
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MINOR_VERSION;
|
||||||
|
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_EPHEMERAL_PUBLIC_KEY;
|
||||||
|
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_MINOR_VERSION;
|
||||||
|
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_PROOF_OF_OWNERSHIP;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getPendingContact;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class HandshakeManagerImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
private final TransactionManager db =
|
||||||
|
context.mock(TransactionManager.class);
|
||||||
|
private final IdentityManager identityManager =
|
||||||
|
context.mock(IdentityManager.class);
|
||||||
|
private final ContactManager contactManager =
|
||||||
|
context.mock(ContactManager.class);
|
||||||
|
private final TransportCrypto transportCrypto =
|
||||||
|
context.mock(TransportCrypto.class);
|
||||||
|
private final HandshakeCrypto handshakeCrypto =
|
||||||
|
context.mock(HandshakeCrypto.class);
|
||||||
|
private final RecordReaderFactory recordReaderFactory =
|
||||||
|
context.mock(RecordReaderFactory.class);
|
||||||
|
private final RecordWriterFactory recordWriterFactory =
|
||||||
|
context.mock(RecordWriterFactory.class);
|
||||||
|
private final RecordReader recordReader = context.mock(RecordReader.class);
|
||||||
|
private final RecordWriter recordWriter = context.mock(RecordWriter.class);
|
||||||
|
private final StreamWriter streamWriter = context.mock(StreamWriter.class);
|
||||||
|
|
||||||
|
private final PendingContact pendingContact = getPendingContact();
|
||||||
|
private final PublicKey theirStaticPublicKey =
|
||||||
|
pendingContact.getPublicKey();
|
||||||
|
private final PublicKey ourStaticPublicKey = getAgreementPublicKey();
|
||||||
|
private final PrivateKey ourStaticPrivateKey = getAgreementPrivateKey();
|
||||||
|
private final KeyPair ourStaticKeyPair =
|
||||||
|
new KeyPair(ourStaticPublicKey, ourStaticPrivateKey);
|
||||||
|
private final PublicKey theirEphemeralPublicKey = getAgreementPublicKey();
|
||||||
|
private final PublicKey ourEphemeralPublicKey = getAgreementPublicKey();
|
||||||
|
private final PrivateKey ourEphemeralPrivateKey = getAgreementPrivateKey();
|
||||||
|
private final KeyPair ourEphemeralKeyPair =
|
||||||
|
new KeyPair(ourEphemeralPublicKey, ourEphemeralPrivateKey);
|
||||||
|
private final SecretKey masterKey = getSecretKey();
|
||||||
|
private final byte[] ourProof = getRandomBytes(PROOF_BYTES);
|
||||||
|
private final byte[] theirProof = getRandomBytes(PROOF_BYTES);
|
||||||
|
|
||||||
|
private final InputStream in = new ByteArrayInputStream(new byte[0]);
|
||||||
|
private final OutputStream out = new ByteArrayOutputStream(0);
|
||||||
|
|
||||||
|
private final HandshakeManagerImpl handshakeManager =
|
||||||
|
new HandshakeManagerImpl(db, identityManager, contactManager,
|
||||||
|
transportCrypto, handshakeCrypto, recordReaderFactory,
|
||||||
|
recordWriterFactory);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHandshakeAsAliceWithPeerVersion_0_1() throws Exception {
|
||||||
|
testHandshakeWithPeerVersion_0_1(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHandshakeAsBobWithPeerVersion_0_1() throws Exception {
|
||||||
|
testHandshakeWithPeerVersion_0_1(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testHandshakeWithPeerVersion_0_1(boolean alice)
|
||||||
|
throws Exception {
|
||||||
|
expectPrepareForHandshake(alice);
|
||||||
|
expectSendMinorVersion();
|
||||||
|
expectSendKey();
|
||||||
|
// Remote peer sends minor version, so use new key derivation
|
||||||
|
expectReceiveMinorVersion();
|
||||||
|
expectReceiveKey();
|
||||||
|
expectDeriveMasterKey_0_1(alice);
|
||||||
|
expectDeriveProof(alice);
|
||||||
|
expectSendProof();
|
||||||
|
expectReceiveProof();
|
||||||
|
expectSendEof();
|
||||||
|
expectReceiveEof();
|
||||||
|
expectVerifyOwnership(alice, true);
|
||||||
|
|
||||||
|
HandshakeResult result = handshakeManager.handshake(
|
||||||
|
pendingContact.getId(), in, streamWriter);
|
||||||
|
|
||||||
|
assertArrayEquals(masterKey.getBytes(),
|
||||||
|
result.getMasterKey().getBytes());
|
||||||
|
assertEquals(alice, result.isAlice());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHandshakeAsAliceWithPeerVersion_0_0() throws Exception {
|
||||||
|
testHandshakeWithPeerVersion_0_0(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHandshakeAsBobWithPeerVersion_0_0() throws Exception {
|
||||||
|
testHandshakeWithPeerVersion_0_0(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testHandshakeWithPeerVersion_0_0(boolean alice)
|
||||||
|
throws Exception {
|
||||||
|
expectPrepareForHandshake(alice);
|
||||||
|
expectSendMinorVersion();
|
||||||
|
expectSendKey();
|
||||||
|
// Remote peer does not send minor version, so use old key derivation
|
||||||
|
expectReceiveKey();
|
||||||
|
expectDeriveMasterKey_0_0(alice);
|
||||||
|
expectDeriveProof(alice);
|
||||||
|
expectSendProof();
|
||||||
|
expectReceiveProof();
|
||||||
|
expectSendEof();
|
||||||
|
expectReceiveEof();
|
||||||
|
expectVerifyOwnership(alice, true);
|
||||||
|
|
||||||
|
HandshakeResult result = handshakeManager.handshake(
|
||||||
|
pendingContact.getId(), in, streamWriter);
|
||||||
|
|
||||||
|
assertArrayEquals(masterKey.getBytes(),
|
||||||
|
result.getMasterKey().getBytes());
|
||||||
|
assertEquals(alice, result.isAlice());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testProofOfOwnershipNotVerifiedAsAlice() throws Exception {
|
||||||
|
testProofOfOwnershipNotVerified(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testProofOfOwnershipNotVerifiedAsBob() throws Exception {
|
||||||
|
testProofOfOwnershipNotVerified(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testProofOfOwnershipNotVerified(boolean alice)
|
||||||
|
throws Exception {
|
||||||
|
expectPrepareForHandshake(alice);
|
||||||
|
expectSendMinorVersion();
|
||||||
|
expectSendKey();
|
||||||
|
expectReceiveMinorVersion();
|
||||||
|
expectReceiveKey();
|
||||||
|
expectDeriveMasterKey_0_1(alice);
|
||||||
|
expectDeriveProof(alice);
|
||||||
|
expectSendProof();
|
||||||
|
expectReceiveProof();
|
||||||
|
expectSendEof();
|
||||||
|
expectReceiveEof();
|
||||||
|
expectVerifyOwnership(alice, false);
|
||||||
|
|
||||||
|
handshakeManager.handshake(pendingContact.getId(), in, streamWriter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectPrepareForHandshake(boolean alice) throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, true);
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||||
|
oneOf(contactManager).getPendingContact(txn,
|
||||||
|
pendingContact.getId());
|
||||||
|
will(returnValue(pendingContact));
|
||||||
|
oneOf(identityManager).getHandshakeKeys(txn);
|
||||||
|
will(returnValue(ourStaticKeyPair));
|
||||||
|
oneOf(transportCrypto).isAlice(theirStaticPublicKey,
|
||||||
|
ourStaticKeyPair);
|
||||||
|
will(returnValue(alice));
|
||||||
|
oneOf(recordReaderFactory).createRecordReader(in);
|
||||||
|
will(returnValue(recordReader));
|
||||||
|
oneOf(streamWriter).getOutputStream();
|
||||||
|
will(returnValue(out));
|
||||||
|
oneOf(recordWriterFactory).createRecordWriter(out);
|
||||||
|
will(returnValue(recordWriter));
|
||||||
|
oneOf(handshakeCrypto).generateEphemeralKeyPair();
|
||||||
|
will(returnValue(ourEphemeralKeyPair));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectSendMinorVersion() throws Exception {
|
||||||
|
expectWriteRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||||
|
RECORD_TYPE_MINOR_VERSION,
|
||||||
|
new byte[] {PROTOCOL_MINOR_VERSION}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectReceiveMinorVersion() throws Exception {
|
||||||
|
expectReadRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||||
|
RECORD_TYPE_MINOR_VERSION,
|
||||||
|
new byte[] {PROTOCOL_MINOR_VERSION}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectSendKey() throws Exception {
|
||||||
|
expectWriteRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||||
|
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY,
|
||||||
|
ourEphemeralPublicKey.getEncoded()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectReceiveKey() throws Exception {
|
||||||
|
expectReadRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||||
|
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY,
|
||||||
|
theirEphemeralPublicKey.getEncoded()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectDeriveMasterKey_0_1(boolean alice) throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(handshakeCrypto).deriveMasterKey_0_1(theirStaticPublicKey,
|
||||||
|
theirEphemeralPublicKey, ourStaticKeyPair,
|
||||||
|
ourEphemeralKeyPair, alice);
|
||||||
|
will(returnValue(masterKey));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectDeriveMasterKey_0_0(boolean alice) throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(handshakeCrypto).deriveMasterKey_0_0(theirStaticPublicKey,
|
||||||
|
theirEphemeralPublicKey, ourStaticKeyPair,
|
||||||
|
ourEphemeralKeyPair, alice);
|
||||||
|
will(returnValue(masterKey));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectDeriveProof(boolean alice) {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(handshakeCrypto).proveOwnership(masterKey, alice);
|
||||||
|
will(returnValue(ourProof));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectSendProof() throws Exception {
|
||||||
|
expectWriteRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||||
|
RECORD_TYPE_PROOF_OF_OWNERSHIP, ourProof));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectReceiveProof() throws Exception {
|
||||||
|
expectReadRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||||
|
RECORD_TYPE_PROOF_OF_OWNERSHIP, theirProof));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectSendEof() throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(streamWriter).sendEndOfStream();
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectReceiveEof() throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(recordReader).readRecord(with(any(RecordPredicate.class)),
|
||||||
|
with(any(RecordPredicate.class)));
|
||||||
|
will(returnValue(null));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectVerifyOwnership(boolean alice, boolean verified) {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(handshakeCrypto).verifyOwnership(masterKey, !alice,
|
||||||
|
theirProof);
|
||||||
|
will(returnValue(verified));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectWriteRecord(Record record) throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(recordWriter).writeRecord(with(new PredicateMatcher<>(
|
||||||
|
Record.class, r -> recordEquals(record, r))));
|
||||||
|
oneOf(recordWriter).flush();
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean recordEquals(Record expected, Record actual) {
|
||||||
|
return expected.getProtocolVersion() == actual.getProtocolVersion() &&
|
||||||
|
expected.getRecordType() == actual.getRecordType() &&
|
||||||
|
Arrays.equals(expected.getPayload(), actual.getPayload());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectReadRecord(Record record) throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
// Test that the `accept` predicate passed to the reader would
|
||||||
|
// accept the expected record
|
||||||
|
oneOf(recordReader).readRecord(with(new PredicateMatcher<>(
|
||||||
|
RecordPredicate.class, rp -> rp.test(record))),
|
||||||
|
with(any(RecordPredicate.class)));
|
||||||
|
will(returnValue(record));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,6 +60,22 @@ public class KeyAgreementTest extends BrambleTestCase {
|
|||||||
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
|
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDerivesStaticEphemeralSharedSecretBadly() throws Exception {
|
||||||
|
String label = getRandomString(123);
|
||||||
|
KeyPair aStatic = crypto.generateAgreementKeyPair();
|
||||||
|
KeyPair aEphemeral = crypto.generateAgreementKeyPair();
|
||||||
|
KeyPair bStatic = crypto.generateAgreementKeyPair();
|
||||||
|
KeyPair bEphemeral = crypto.generateAgreementKeyPair();
|
||||||
|
SecretKey aShared = crypto.deriveSharedSecretBadly(label,
|
||||||
|
bStatic.getPublic(), bEphemeral.getPublic(), aStatic,
|
||||||
|
aEphemeral, true, inputs);
|
||||||
|
SecretKey bShared = crypto.deriveSharedSecretBadly(label,
|
||||||
|
aStatic.getPublic(), aEphemeral.getPublic(), bStatic,
|
||||||
|
bEphemeral, false, inputs);
|
||||||
|
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDerivesStaticEphemeralSharedSecret() throws Exception {
|
public void testDerivesStaticEphemeralSharedSecret() throws Exception {
|
||||||
String label = getRandomString(123);
|
String label = getRandomString(123);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Predicate;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||||
@@ -8,11 +7,13 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.record.Record;
|
import org.briarproject.bramble.api.record.Record;
|
||||||
import org.briarproject.bramble.api.record.RecordReader;
|
import org.briarproject.bramble.api.record.RecordReader;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||||
import org.briarproject.bramble.api.record.RecordWriter;
|
import org.briarproject.bramble.api.record.RecordWriter;
|
||||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||||
|
import org.briarproject.bramble.test.PredicateMatcher;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.imposters.ByteBuddyClassImposteriser;
|
import org.jmock.imposters.ByteBuddyClassImposteriser;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -21,8 +22,6 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
||||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT;
|
import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT;
|
||||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM;
|
import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM;
|
||||||
@@ -119,7 +118,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
|
|||||||
public void testReceiveKeyThrowsExceptionIfAtEndOfStream()
|
public void testReceiveKeyThrowsExceptionIfAtEndOfStream()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
setup();
|
setup();
|
||||||
expectReadRecord(null);
|
expectReadEof();
|
||||||
|
|
||||||
kat.receiveKey();
|
kat.receiveKey();
|
||||||
}
|
}
|
||||||
@@ -148,7 +147,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
|
|||||||
public void testReceiveConfirmThrowsExceptionIfAtEndOfStream()
|
public void testReceiveConfirmThrowsExceptionIfAtEndOfStream()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
setup();
|
setup();
|
||||||
expectReadRecord(null);
|
expectReadEof();
|
||||||
|
|
||||||
kat.receiveConfirm();
|
kat.receiveConfirm();
|
||||||
}
|
}
|
||||||
@@ -209,12 +208,22 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
|
|||||||
assertArrayEquals(expectedPayload, actual.getPayload());
|
assertArrayEquals(expectedPayload, actual.getPayload());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expectReadRecord(@Nullable Record record) throws Exception {
|
private void expectReadRecord(Record record) throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
//noinspection unchecked
|
// Test that the `accept` predicate passed to the reader would
|
||||||
oneOf(recordReader).readRecord(with(any(Predicate.class)),
|
// accept the expected record
|
||||||
with(any(Predicate.class)));
|
oneOf(recordReader).readRecord(with(new PredicateMatcher<>(
|
||||||
|
RecordPredicate.class, rp -> rp.test(record))),
|
||||||
|
with(any(RecordPredicate.class)));
|
||||||
will(returnValue(record));
|
will(returnValue(record));
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void expectReadEof() throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(recordReader).readRecord(with(any(RecordPredicate.class)),
|
||||||
|
with(any(RecordPredicate.class)));
|
||||||
|
will(returnValue(null));
|
||||||
|
}});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package org.briarproject.bramble.record;
|
package org.briarproject.bramble.record;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.Predicate;
|
|
||||||
import org.briarproject.bramble.api.record.Record;
|
import org.briarproject.bramble.api.record.Record;
|
||||||
import org.briarproject.bramble.api.record.RecordReader;
|
import org.briarproject.bramble.api.record.RecordReader;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.util.ByteUtils;
|
import org.briarproject.bramble.util.ByteUtils;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -128,12 +128,12 @@ public class RecordReaderImplTest extends BrambleTestCase {
|
|||||||
RecordReader reader = new RecordReaderImpl(in);
|
RecordReader reader = new RecordReaderImpl(in);
|
||||||
|
|
||||||
// Accept records with version 0, type 0 or 1
|
// Accept records with version 0, type 0 or 1
|
||||||
Predicate<Record> accept = r -> {
|
RecordPredicate accept = r -> {
|
||||||
byte version = r.getProtocolVersion(), type = r.getRecordType();
|
byte version = r.getProtocolVersion(), type = r.getRecordType();
|
||||||
return version == 0 && (type == 0 || type == 1);
|
return version == 0 && (type == 0 || type == 1);
|
||||||
};
|
};
|
||||||
// Ignore records with version 0, any other type
|
// Ignore records with version 0, any other type
|
||||||
Predicate<Record> ignore = r -> {
|
RecordPredicate ignore = r -> {
|
||||||
byte version = r.getProtocolVersion(), type = r.getRecordType();
|
byte version = r.getProtocolVersion(), type = r.getRecordType();
|
||||||
return version == 0 && !(type == 0 || type == 1);
|
return version == 0 && !(type == 0 || type == 1);
|
||||||
};
|
};
|
||||||
@@ -183,12 +183,12 @@ public class RecordReaderImplTest extends BrambleTestCase {
|
|||||||
RecordReader reader = new RecordReaderImpl(in);
|
RecordReader reader = new RecordReaderImpl(in);
|
||||||
|
|
||||||
// Accept records with version 0, type 0 or 1
|
// Accept records with version 0, type 0 or 1
|
||||||
Predicate<Record> accept = r -> {
|
RecordPredicate accept = r -> {
|
||||||
byte version = r.getProtocolVersion(), type = r.getRecordType();
|
byte version = r.getProtocolVersion(), type = r.getRecordType();
|
||||||
return version == 0 && (type == 0 || type == 1);
|
return version == 0 && (type == 0 || type == 1);
|
||||||
};
|
};
|
||||||
// Ignore records with version 0, any other type
|
// Ignore records with version 0, any other type
|
||||||
Predicate<Record> ignore = r -> {
|
RecordPredicate ignore = r -> {
|
||||||
byte version = r.getProtocolVersion(), type = r.getRecordType();
|
byte version = r.getProtocolVersion(), type = r.getRecordType();
|
||||||
return version == 0 && !(type == 0 || type == 1);
|
return version == 0 && !(type == 0 || type == 1);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package org.briarproject.bramble.sync;
|
package org.briarproject.bramble.sync;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.Predicate;
|
|
||||||
import org.briarproject.bramble.api.UniqueId;
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
import org.briarproject.bramble.api.record.Record;
|
import org.briarproject.bramble.api.record.Record;
|
||||||
import org.briarproject.bramble.api.record.RecordReader;
|
import org.briarproject.bramble.api.record.RecordReader;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
@@ -24,8 +24,6 @@ import org.junit.Test;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||||
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
|
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
|
||||||
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
|
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
|
||||||
@@ -186,7 +184,7 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testEofReturnsTrueWhenAtEndOfStream() throws Exception {
|
public void testEofReturnsTrueWhenAtEndOfStream() throws Exception {
|
||||||
expectReadRecord(createAck());
|
expectReadRecord(createAck());
|
||||||
expectReadRecord(null);
|
expectReadEof();
|
||||||
|
|
||||||
SyncRecordReader reader =
|
SyncRecordReader reader =
|
||||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
new SyncRecordReaderImpl(messageFactory, recordReader);
|
||||||
@@ -212,15 +210,25 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
|
|||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expectReadRecord(@Nullable Record record) throws Exception {
|
private void expectReadRecord(Record record) throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
//noinspection unchecked
|
// Test that the `accept` predicate passed to the reader would
|
||||||
oneOf(recordReader).readRecord(with(any(Predicate.class)),
|
// accept the expected record
|
||||||
with(any(Predicate.class)));
|
oneOf(recordReader).readRecord(with(new PredicateMatcher<>(
|
||||||
|
RecordPredicate.class, rp -> rp.test(record))),
|
||||||
|
with(any(RecordPredicate.class)));
|
||||||
will(returnValue(record));
|
will(returnValue(record));
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void expectReadEof() throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(recordReader).readRecord(with(any(RecordPredicate.class)),
|
||||||
|
with(any(RecordPredicate.class)));
|
||||||
|
will(returnValue(null));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
private Record createMessage(int payloadLength) {
|
private Record createMessage(int payloadLength) {
|
||||||
return new Record(PROTOCOL_VERSION, MESSAGE, new byte[payloadLength]);
|
return new Record(PROTOCOL_VERSION, MESSAGE, new byte[payloadLength]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class DesktopSecureRandomModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
SecureRandomProvider provideSecureRandomProvider() {
|
SecureRandomProvider provideSecureRandomProvider() {
|
||||||
if (isLinux() || isMac()) return new UnixSecureRandomProvider();
|
if (isLinux()) return new UnixSecureRandomProvider();
|
||||||
return () -> null; // Use system default
|
return () -> null; // Use system default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 31
|
targetSdkVersion 31
|
||||||
versionCode 10502
|
versionCode 10503
|
||||||
versionName "1.5.2"
|
versionName "1.5.3"
|
||||||
applicationId "org.briarproject.briar.android"
|
applicationId "org.briarproject.briar.android"
|
||||||
buildConfigField "String", "TorVersion", "\"$tor_version\""
|
buildConfigField "String", "TorVersion", "\"$tor_version\""
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package org.briarproject.briar.android.settings;
|
package org.briarproject.briar.android.settings;
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.mailbox.MailboxActivity;
|
import org.briarproject.briar.android.mailbox.MailboxActivity;
|
||||||
@@ -24,6 +26,9 @@ import androidx.preference.Preference;
|
|||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
import androidx.preference.PreferenceGroup;
|
import androidx.preference.PreferenceGroup;
|
||||||
|
|
||||||
|
import static android.content.Intent.ACTION_SEND;
|
||||||
|
import static android.content.Intent.EXTRA_TEXT;
|
||||||
|
import static android.widget.Toast.LENGTH_LONG;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
|
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
|
||||||
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||||
@@ -37,11 +42,14 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
public static final String SETTINGS_NAMESPACE = "android-ui";
|
public static final String SETTINGS_NAMESPACE = "android-ui";
|
||||||
|
|
||||||
private static final String PREF_KEY_AVATAR = "pref_key_avatar";
|
private static final String PREF_KEY_AVATAR = "pref_key_avatar";
|
||||||
|
private static final String PREF_KEY_SHARE_LINK = "pref_key_share_app_link";
|
||||||
private static final String PREF_KEY_FEEDBACK = "pref_key_send_feedback";
|
private static final String PREF_KEY_FEEDBACK = "pref_key_send_feedback";
|
||||||
private static final String PREF_KEY_DEV = "pref_key_dev";
|
private static final String PREF_KEY_DEV = "pref_key_dev";
|
||||||
private static final String PREF_KEY_EXPLODE = "pref_key_explode";
|
private static final String PREF_KEY_EXPLODE = "pref_key_explode";
|
||||||
private static final String PREF_KEY_MAILBOX = "pref_key_mailbox";
|
private static final String PREF_KEY_MAILBOX = "pref_key_mailbox";
|
||||||
|
|
||||||
|
private static final String DOWNLOAD_URL = "https://briarproject.org/download/";
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
@@ -86,6 +94,21 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Preference prefShareLink =
|
||||||
|
requireNonNull(findPreference(PREF_KEY_SHARE_LINK));
|
||||||
|
prefShareLink.setOnPreferenceClickListener(preference -> {
|
||||||
|
String text = getString(R.string.share_app_link_text, DOWNLOAD_URL);
|
||||||
|
Intent sendIntent = new Intent(ACTION_SEND);
|
||||||
|
sendIntent.putExtra(EXTRA_TEXT, text);
|
||||||
|
sendIntent.setType("text/plain");
|
||||||
|
try {
|
||||||
|
startActivity(Intent.createChooser(sendIntent, null));
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Toast.makeText(requireContext(),
|
||||||
|
R.string.error_start_activity, LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
Preference prefFeedback =
|
Preference prefFeedback =
|
||||||
requireNonNull(findPreference(PREF_KEY_FEEDBACK));
|
requireNonNull(findPreference(PREF_KEY_FEEDBACK));
|
||||||
prefFeedback.setOnPreferenceClickListener(preference -> {
|
prefFeedback.setOnPreferenceClickListener(preference -> {
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?android:attr/textColorPrimary"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M21,12L14,5V9C7,10 4,15 3,20C5.5,16.5 9,14.9 14,14.9V19L21,12Z" />
|
||||||
|
</vector>
|
||||||
@@ -696,7 +696,7 @@
|
|||||||
<string name="mailbox_error_wizard_info2">لطفا هنگامی که به دستگاه دسترسی دارید به این صفحه بازگردید.</string>
|
<string name="mailbox_error_wizard_info2">لطفا هنگامی که به دستگاه دسترسی دارید به این صفحه بازگردید.</string>
|
||||||
<string name="mailbox_error_wizard_info3">لطفا با استفاده از دکمه زیر، پیوند Mailbox خود را لغو کنید.\n\nپس از لغو پیوند Mailbox قدیمی، میتوانید هر زمان که خواستید یک Mailbox جدید راهاندازی کنید.</string>
|
<string name="mailbox_error_wizard_info3">لطفا با استفاده از دکمه زیر، پیوند Mailbox خود را لغو کنید.\n\nپس از لغو پیوند Mailbox قدیمی، میتوانید هر زمان که خواستید یک Mailbox جدید راهاندازی کنید.</string>
|
||||||
<!--About-->
|
<!--About-->
|
||||||
<string name="about_title">دربارهی Psiphon</string>
|
<string name="about_title">دربارهی Briar</string>
|
||||||
<string name="briar_version">نسخه Briar: %s</string>
|
<string name="briar_version">نسخه Briar: %s</string>
|
||||||
<string name="tor_version">نسخه Tor: %s</string>
|
<string name="tor_version">نسخه Tor: %s</string>
|
||||||
<string name="links">لینک ها</string>
|
<string name="links">لینک ها</string>
|
||||||
|
|||||||
@@ -733,6 +733,8 @@
|
|||||||
|
|
||||||
<!-- Settings Actions -->
|
<!-- Settings Actions -->
|
||||||
<string name="pref_category_actions">Actions</string>
|
<string name="pref_category_actions">Actions</string>
|
||||||
|
<string name="share_app_link">Share download link</string>
|
||||||
|
<string name="share_app_link_text">Download Briar at %s</string>
|
||||||
<string name="send_feedback">Send feedback</string>
|
<string name="send_feedback">Send feedback</string>
|
||||||
|
|
||||||
<!-- Link Warning -->
|
<!-- Link Warning -->
|
||||||
|
|||||||
@@ -29,11 +29,6 @@
|
|||||||
android:title="@string/mailbox_settings_title"
|
android:title="@string/mailbox_settings_title"
|
||||||
app:icon="@drawable/ic_mailbox" />
|
app:icon="@drawable/ic_mailbox" />
|
||||||
|
|
||||||
<Preference
|
|
||||||
android:title="@string/about_title"
|
|
||||||
app:fragment="org.briarproject.briar.android.settings.AboutFragment"
|
|
||||||
app:icon="@drawable/ic_info_dark" />
|
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:key="pref_key_actions"
|
android:key="pref_key_actions"
|
||||||
android:layout="@layout/preferences_category"
|
android:layout="@layout/preferences_category"
|
||||||
@@ -47,6 +42,11 @@
|
|||||||
android:targetClass="org.briarproject.briar.android.hotspot.HotspotActivity"
|
android:targetClass="org.briarproject.briar.android.hotspot.HotspotActivity"
|
||||||
android:targetPackage="@string/app_package" />
|
android:targetPackage="@string/app_package" />
|
||||||
</Preference>
|
</Preference>
|
||||||
|
<Preference
|
||||||
|
android:key="pref_key_share_app_link"
|
||||||
|
android:title="@string/share_app_link"
|
||||||
|
app:icon="@drawable/ic_settings_share_link">
|
||||||
|
</Preference>
|
||||||
<Preference
|
<Preference
|
||||||
android:key="pref_key_send_feedback"
|
android:key="pref_key_send_feedback"
|
||||||
android:title="@string/send_feedback"
|
android:title="@string/send_feedback"
|
||||||
@@ -74,4 +74,10 @@
|
|||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:title="@string/about_title"
|
||||||
|
app:allowDividerAbove="true"
|
||||||
|
app:fragment="org.briarproject.briar.android.settings.AboutFragment"
|
||||||
|
app:icon="@drawable/ic_info_dark" />
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|||||||
Reference in New Issue
Block a user