mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Partial implementation of the invitation protocol (untested).
This commit is contained in:
@@ -15,6 +15,11 @@ public interface CryptoComponent {
|
||||
|
||||
ErasableKey deriveMacKey(byte[] secret, boolean initiator);
|
||||
|
||||
byte[][] deriveInitialSecrets(byte[] theirPublicKey, KeyPair ourKeyPair,
|
||||
int invitationCode, boolean initiator);
|
||||
|
||||
int deriveConfirmationCode(byte[] secret, boolean initiator);
|
||||
|
||||
byte[] deriveNextSecret(byte[] secret, int index, long connection);
|
||||
|
||||
KeyPair generateKeyPair();
|
||||
@@ -25,6 +30,8 @@ public interface CryptoComponent {
|
||||
|
||||
MessageDigest getMessageDigest();
|
||||
|
||||
PseudoRandom getPseudoRandom(int seed);
|
||||
|
||||
SecureRandom getSecureRandom();
|
||||
|
||||
Cipher getTagCipher();
|
||||
|
||||
6
api/net/sf/briar/api/crypto/PseudoRandom.java
Normal file
6
api/net/sf/briar/api/crypto/PseudoRandom.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package net.sf.briar.api.crypto;
|
||||
|
||||
public interface PseudoRandom {
|
||||
|
||||
byte[] nextBytes(int bytes);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package net.sf.briar.api.plugins;
|
||||
|
||||
public interface IncomingInvitationCallback extends InvitationCallback {
|
||||
|
||||
int enterInvitationCode();
|
||||
}
|
||||
14
api/net/sf/briar/api/plugins/InvitationCallback.java
Normal file
14
api/net/sf/briar/api/plugins/InvitationCallback.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package net.sf.briar.api.plugins;
|
||||
|
||||
public interface InvitationCallback {
|
||||
|
||||
boolean isCancelled();
|
||||
|
||||
int enterConfirmationCode(int code);
|
||||
|
||||
void showProgress(String... message);
|
||||
|
||||
void showFailure(String... message);
|
||||
|
||||
void showSuccess();
|
||||
}
|
||||
14
api/net/sf/briar/api/plugins/InvitationConstants.java
Normal file
14
api/net/sf/briar/api/plugins/InvitationConstants.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package net.sf.briar.api.plugins;
|
||||
|
||||
public interface InvitationConstants {
|
||||
|
||||
static final long INVITATION_TIMEOUT = 60 * 1000; // 1 minute
|
||||
|
||||
static final int CODE_BITS = 19; // Codes must fit into six decimal digits
|
||||
|
||||
static final int MAX_CODE = 1 << CODE_BITS - 1;
|
||||
|
||||
static final int HASH_LENGTH = 48;
|
||||
|
||||
static final int MAX_PUBLIC_KEY_LENGTH = 120;
|
||||
}
|
||||
12
api/net/sf/briar/api/plugins/InvitationStarter.java
Normal file
12
api/net/sf/briar/api/plugins/InvitationStarter.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package net.sf.briar.api.plugins;
|
||||
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
|
||||
public interface InvitationStarter {
|
||||
|
||||
void startIncomingInvitation(DuplexPlugin plugin,
|
||||
IncomingInvitationCallback callback);
|
||||
|
||||
void startOutgoingInvitation(DuplexPlugin plugin,
|
||||
OutgoingInvitationCallback callback);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package net.sf.briar.api.plugins;
|
||||
|
||||
public interface OutgoingInvitationCallback extends InvitationCallback {
|
||||
|
||||
void showInvitationCode(int code);
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
package net.sf.briar.api.plugins;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexPlugin;
|
||||
|
||||
public interface PluginManager {
|
||||
|
||||
/**
|
||||
@@ -12,4 +17,10 @@ public interface PluginManager {
|
||||
* Stops the plugins and returns the number of plugins successfully stopped.
|
||||
*/
|
||||
int stop();
|
||||
|
||||
/** Returns any duplex plugins that support invitations. */
|
||||
Collection<DuplexPlugin> getDuplexInvitationPlugins();
|
||||
|
||||
/** Returns any simplex plugins that support invitations. */
|
||||
Collection<SimplexPlugin> getSimplexInvitationPlugins();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.sf.briar.api.plugins.duplex;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.plugins.Plugin;
|
||||
|
||||
/** An interface for transport plugins that support duplex communication. */
|
||||
@@ -17,11 +18,11 @@ public interface DuplexPlugin extends Plugin {
|
||||
* Starts the invitation process from the inviter's side. Returns null if
|
||||
* no connection can be established within the given timeout.
|
||||
*/
|
||||
DuplexTransportConnection sendInvitation(int code, long timeout);
|
||||
DuplexTransportConnection sendInvitation(PseudoRandom r, long timeout);
|
||||
|
||||
/**
|
||||
* Starts the invitation process from the invitee's side. Returns null if
|
||||
* no connection can be established within the given timeout.
|
||||
*/
|
||||
DuplexTransportConnection acceptInvitation(int code, long timeout);
|
||||
DuplexTransportConnection acceptInvitation(PseudoRandom r, long timeout);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.sf.briar.api.plugins.simplex;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.plugins.Plugin;
|
||||
|
||||
/** An interface for transport plugins that support simplex communication. */
|
||||
@@ -24,23 +25,24 @@ public interface SimplexPlugin extends Plugin {
|
||||
* Starts the invitation process from the inviter's side. Returns null if
|
||||
* no connection can be established within the given timeout.
|
||||
*/
|
||||
SimplexTransportWriter sendInvitation(int code, long timeout);
|
||||
SimplexTransportWriter sendInvitation(PseudoRandom r, long timeout);
|
||||
|
||||
/**
|
||||
* Starts the invitation process from the invitee's side. Returns null if
|
||||
* no connection can be established within the given timeout.
|
||||
*/
|
||||
SimplexTransportReader acceptInvitation(int code, long timeout);
|
||||
SimplexTransportReader acceptInvitation(PseudoRandom r, long timeout);
|
||||
|
||||
/**
|
||||
* Continues the invitation process from the invitee's side. Returns null
|
||||
* if no connection can be established within the given timeout.
|
||||
*/
|
||||
SimplexTransportWriter sendInvitationResponse(int code, long timeout);
|
||||
SimplexTransportWriter sendInvitationResponse(PseudoRandom r, long timeout);
|
||||
|
||||
/**
|
||||
* Continues the invitation process from the inviter's side. Returns null
|
||||
* if no connection can be established within the given timeout.
|
||||
*/
|
||||
SimplexTransportReader acceptInvitationResponse(int code, long timeout);
|
||||
SimplexTransportReader acceptInvitationResponse(PseudoRandom r,
|
||||
long timeout);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package net.sf.briar.crypto;
|
||||
|
||||
import static net.sf.briar.api.plugins.InvitationConstants.CODE_BITS;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.security.Signature;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyAgreement;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
@@ -15,6 +19,7 @@ import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.ErasableKey;
|
||||
import net.sf.briar.api.crypto.KeyParser;
|
||||
import net.sf.briar.api.crypto.MessageDigest;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.util.ByteUtils;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
@@ -26,6 +31,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
private static final String PROVIDER = "BC";
|
||||
private static final String KEY_PAIR_ALGO = "ECDSA";
|
||||
private static final int KEY_PAIR_BITS = 384;
|
||||
private static final String KEY_AGREEMENT_ALGO = "ECDHC";
|
||||
private static final String SECRET_KEY_ALGO = "AES";
|
||||
private static final int SECRET_KEY_BYTES = 32; // 256 bits
|
||||
private static final int KEY_DERIVATION_IV_BYTES = 16; // 128 bits
|
||||
@@ -40,13 +46,17 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
private static final byte[] TAG = { 'T', 'A', 'G', 0 };
|
||||
private static final byte[] FRAME = { 'F', 'R', 'A', 'M', 'E', 0 };
|
||||
private static final byte[] MAC = { 'M', 'A', 'C', 0 };
|
||||
// Labels for secret derivation, null-terminated
|
||||
private static final byte[] FIRST = { 'F', 'I', 'R', 'S', 'T', 0 };
|
||||
private static final byte[] NEXT = { 'N', 'E', 'X', 'T', 0 };
|
||||
// Context strings for key derivation
|
||||
// Label for confirmation code derivation, null-terminated
|
||||
private static final byte[] CODE = { 'C', 'O', 'D', 'E', 0 };
|
||||
// Context strings for key and confirmation code derivation
|
||||
private static final byte[] INITIATOR = { 'I' };
|
||||
private static final byte[] RESPONDER = { 'R' };
|
||||
// Blank plaintext for key derivation
|
||||
private static final byte[] KEY_DERIVATION_INPUT =
|
||||
new byte[SECRET_KEY_BYTES];
|
||||
new byte[SECRET_KEY_BYTES];
|
||||
|
||||
private final KeyParser keyParser;
|
||||
private final KeyPairGenerator keyPairGenerator;
|
||||
@@ -114,6 +124,72 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public byte[][] deriveInitialSecrets(byte[] theirPublicKey,
|
||||
KeyPair ourKeyPair, int invitationCode, boolean initiator) {
|
||||
try {
|
||||
PublicKey theirPublic = keyParser.parsePublicKey(theirPublicKey);
|
||||
MessageDigest messageDigest = getMessageDigest();
|
||||
byte[] ourPublicKey = ourKeyPair.getPublic().getEncoded();
|
||||
byte[] ourHash = messageDigest.digest(ourPublicKey);
|
||||
byte[] theirHash = messageDigest.digest(theirPublicKey);
|
||||
// The initiator and responder info for the KDF are the hashes of
|
||||
// the corresponding public keys
|
||||
byte[] initiatorInfo, responderInfo;
|
||||
if(initiator) {
|
||||
initiatorInfo = ourHash;
|
||||
responderInfo = theirHash;
|
||||
} else {
|
||||
initiatorInfo = theirHash;
|
||||
responderInfo = ourHash;
|
||||
}
|
||||
// The public info for the KDF is the invitation code as a uint32
|
||||
byte[] publicInfo = new byte[4];
|
||||
ByteUtils.writeUint32(invitationCode, publicInfo, 0);
|
||||
// The raw secret comes from the key agreement algorithm
|
||||
KeyAgreement keyAgreement = KeyAgreement.getInstance(
|
||||
KEY_AGREEMENT_ALGO, PROVIDER);
|
||||
keyAgreement.init(ourKeyPair.getPrivate());
|
||||
keyAgreement.doPhase(theirPublic, true);
|
||||
byte[] rawSecret = keyAgreement.generateSecret();
|
||||
// Derive the cooked secret from the raw secret
|
||||
byte[] cookedSecret = concatenationKdf(rawSecret, FIRST,
|
||||
initiatorInfo, responderInfo, publicInfo);
|
||||
ByteUtils.erase(rawSecret);
|
||||
// Derive the incoming and outgoing secrets from the cooked secret
|
||||
byte[][] secrets = new byte[2][];
|
||||
secrets[0] = counterModeKdf(cookedSecret, FIRST, INITIATOR);
|
||||
secrets[1] = counterModeKdf(cookedSecret, FIRST, RESPONDER);
|
||||
ByteUtils.erase(cookedSecret);
|
||||
return secrets;
|
||||
} catch(GeneralSecurityException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Key derivation function based on a hash function - see NIST SP 800-65A,
|
||||
// section 5.8
|
||||
private byte[] concatenationKdf(byte[] rawSecret, byte[] label,
|
||||
byte[] initiatorInfo, byte[] responderInfo, byte[] publicInfo) {
|
||||
// The output of the hash function must be long enough to use as a key
|
||||
MessageDigest messageDigest = getMessageDigest();
|
||||
if(messageDigest.getDigestLength() < SECRET_KEY_BYTES)
|
||||
throw new RuntimeException();
|
||||
byte[] rawSecretLength = new byte[4];
|
||||
ByteUtils.writeUint32(rawSecret.length, rawSecretLength, 0);
|
||||
messageDigest.update(rawSecretLength);
|
||||
messageDigest.update(rawSecret);
|
||||
messageDigest.update(label);
|
||||
messageDigest.update(initiatorInfo);
|
||||
messageDigest.update(responderInfo);
|
||||
messageDigest.update(publicInfo);
|
||||
byte[] hash = messageDigest.digest();
|
||||
// The secret is the first SECRET_KEY_BYTES bytes of the hash
|
||||
byte[] output = new byte[SECRET_KEY_BYTES];
|
||||
System.arraycopy(hash, 0, output, 0, SECRET_KEY_BYTES);
|
||||
ByteUtils.erase(hash);
|
||||
return output;
|
||||
}
|
||||
|
||||
public byte[] deriveNextSecret(byte[] secret, int index, long connection) {
|
||||
if(index < 0 || index > ByteUtils.MAX_16_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
@@ -125,6 +201,19 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return counterModeKdf(secret, NEXT, context);
|
||||
}
|
||||
|
||||
public int deriveConfirmationCode(byte[] secret, boolean initiator) {
|
||||
byte[] context = initiator ? INITIATOR : RESPONDER;
|
||||
byte[] output = counterModeKdf(secret, CODE, context);
|
||||
int code = extractCode(output);
|
||||
ByteUtils.erase(output);
|
||||
return code;
|
||||
}
|
||||
|
||||
private int extractCode(byte[] secret) {
|
||||
// Convert the first CODE_BITS bits of the secret into an unsigned int
|
||||
return ByteUtils.readUint(secret, CODE_BITS);
|
||||
}
|
||||
|
||||
public KeyPair generateKeyPair() {
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
@@ -148,6 +237,10 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public PseudoRandom getPseudoRandom(int seed) {
|
||||
return new PseudoRandomImpl(getMessageDigest(), seed);
|
||||
}
|
||||
|
||||
public SecureRandom getSecureRandom() {
|
||||
return secureRandom;
|
||||
}
|
||||
|
||||
40
components/net/sf/briar/crypto/PseudoRandomImpl.java
Normal file
40
components/net/sf/briar/crypto/PseudoRandomImpl.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package net.sf.briar.crypto;
|
||||
|
||||
import net.sf.briar.api.crypto.MessageDigest;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.util.ByteUtils;
|
||||
|
||||
class PseudoRandomImpl implements PseudoRandom {
|
||||
|
||||
private final MessageDigest messageDigest;
|
||||
|
||||
private byte[] state;
|
||||
private int offset;
|
||||
|
||||
PseudoRandomImpl(MessageDigest messageDigest, int seed) {
|
||||
this.messageDigest = messageDigest;
|
||||
byte[] seedBytes = new byte[4];
|
||||
ByteUtils.writeUint32(seed, seedBytes, 0);
|
||||
messageDigest.update(seedBytes);
|
||||
state = messageDigest.digest();
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
public synchronized byte[] nextBytes(int bytes) {
|
||||
byte[] b = new byte[bytes];
|
||||
int half = state.length / 2;
|
||||
int off = 0, len = b.length, available = half - offset;
|
||||
while(available < len) {
|
||||
System.arraycopy(state, offset, b, off, available);
|
||||
off += available;
|
||||
len -= available;
|
||||
messageDigest.update(state, half, half);
|
||||
state = messageDigest.digest();
|
||||
offset = 0;
|
||||
available = half;
|
||||
}
|
||||
System.arraycopy(state, offset, b, off, len);
|
||||
offset += len;
|
||||
return b;
|
||||
}
|
||||
}
|
||||
225
components/net/sf/briar/plugins/InvitationStarterImpl.java
Normal file
225
components/net/sf/briar/plugins/InvitationStarterImpl.java
Normal file
@@ -0,0 +1,225 @@
|
||||
package net.sf.briar.plugins;
|
||||
|
||||
import static net.sf.briar.api.plugins.InvitationConstants.HASH_LENGTH;
|
||||
import static net.sf.briar.api.plugins.InvitationConstants.INVITATION_TIMEOUT;
|
||||
import static net.sf.briar.api.plugins.InvitationConstants.MAX_CODE;
|
||||
import static net.sf.briar.api.plugins.InvitationConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.MessageDigest;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.db.DatabaseComponent;
|
||||
import net.sf.briar.api.db.DbException;
|
||||
import net.sf.briar.api.plugins.IncomingInvitationCallback;
|
||||
import net.sf.briar.api.plugins.InvitationStarter;
|
||||
import net.sf.briar.api.plugins.OutgoingInvitationCallback;
|
||||
import net.sf.briar.api.plugins.PluginExecutor;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||
import net.sf.briar.api.serial.Reader;
|
||||
import net.sf.briar.api.serial.ReaderFactory;
|
||||
import net.sf.briar.api.serial.Writer;
|
||||
import net.sf.briar.api.serial.WriterFactory;
|
||||
import net.sf.briar.util.ByteUtils;
|
||||
|
||||
// FIXME: Refactor this class to remove duplicated code
|
||||
class InvitationStarterImpl implements InvitationStarter {
|
||||
|
||||
private static final String TIMED_OUT = "INVITATION_TIMED_OUT";
|
||||
private static final String IO_EXCEPTION = "INVITATION_IO_EXCEPTION";
|
||||
private static final String INVALID_KEY = "INVITATION_INVALID_KEY";
|
||||
private static final String WRONG_CODE = "INVITATION_WRONG_CODE";
|
||||
private static final String DB_EXCEPTION = "INVITATION_DB_EXCEPTION";
|
||||
|
||||
private final Executor pluginExecutor;
|
||||
private final CryptoComponent crypto;
|
||||
private final DatabaseComponent db;
|
||||
private final ReaderFactory readerFactory;
|
||||
private final WriterFactory writerFactory;
|
||||
|
||||
@Inject
|
||||
InvitationStarterImpl(@PluginExecutor Executor pluginExecutor,
|
||||
CryptoComponent crypto, DatabaseComponent db,
|
||||
ReaderFactory readerFactory, WriterFactory writerFactory) {
|
||||
this.pluginExecutor = pluginExecutor;
|
||||
this.crypto = crypto;
|
||||
this.db = db;
|
||||
this.readerFactory = readerFactory;
|
||||
this.writerFactory = writerFactory;
|
||||
}
|
||||
|
||||
public void startIncomingInvitation(final DuplexPlugin plugin,
|
||||
final IncomingInvitationCallback callback) {
|
||||
pluginExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
long end = System.currentTimeMillis() + INVITATION_TIMEOUT;
|
||||
// Get the invitation code from the inviter
|
||||
int code = callback.enterInvitationCode();
|
||||
if(code == -1) return;
|
||||
long remaining = end - System.currentTimeMillis();
|
||||
if(remaining <= 0) return;
|
||||
// Use the invitation code to seed the PRNG
|
||||
PseudoRandom r = crypto.getPseudoRandom(code);
|
||||
// Connect to the inviter
|
||||
DuplexTransportConnection conn = plugin.acceptInvitation(r,
|
||||
remaining);
|
||||
if(callback.isCancelled()) {
|
||||
if(conn != null) conn.dispose(false, false);
|
||||
return;
|
||||
}
|
||||
if(conn == null) {
|
||||
callback.showFailure(TIMED_OUT);
|
||||
return;
|
||||
}
|
||||
KeyPair ourKeyPair = crypto.generateKeyPair();
|
||||
MessageDigest messageDigest = crypto.getMessageDigest();
|
||||
byte[] ourKey = ourKeyPair.getPublic().getEncoded();
|
||||
byte[] ourHash = messageDigest.digest(ourKey);
|
||||
byte[] theirKey, theirHash;
|
||||
try {
|
||||
// Send the public key hash
|
||||
OutputStream out = conn.getOutputStream();
|
||||
Writer writer = writerFactory.createWriter(out);
|
||||
writer.writeBytes(ourHash);
|
||||
out.flush();
|
||||
// Receive the public key hash
|
||||
InputStream in = conn.getInputStream();
|
||||
Reader reader = readerFactory.createReader(in);
|
||||
theirHash = reader.readBytes(HASH_LENGTH);
|
||||
// Send the public key
|
||||
writer.writeBytes(ourKey);
|
||||
out.flush();
|
||||
// Receive the public key
|
||||
theirKey = reader.readBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
} catch(IOException e) {
|
||||
conn.dispose(true, false);
|
||||
callback.showFailure(IO_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
conn.dispose(false, false);
|
||||
if(callback.isCancelled()) return;
|
||||
// Check that the received hash matches the received key
|
||||
if(!Arrays.equals(theirHash, messageDigest.digest(theirKey))) {
|
||||
callback.showFailure(INVALID_KEY);
|
||||
return;
|
||||
}
|
||||
// Derive the initial shared secrets and the confirmation codes
|
||||
byte[][] secrets = crypto.deriveInitialSecrets(theirKey,
|
||||
ourKeyPair, code, false);
|
||||
if(secrets == null) {
|
||||
callback.showFailure(INVALID_KEY);
|
||||
return;
|
||||
}
|
||||
int theirCode = crypto.deriveConfirmationCode(secrets[0], true);
|
||||
int ourCode = crypto.deriveConfirmationCode(secrets[1], false);
|
||||
// Compare the confirmation codes
|
||||
if(callback.enterConfirmationCode(ourCode) != theirCode) {
|
||||
callback.showFailure(WRONG_CODE);
|
||||
ByteUtils.erase(secrets[0]);
|
||||
ByteUtils.erase(secrets[1]);
|
||||
return;
|
||||
}
|
||||
// Add the contact to the database
|
||||
try {
|
||||
db.addContact(secrets[0], secrets[1]);
|
||||
} catch(DbException e) {
|
||||
callback.showFailure(DB_EXCEPTION);
|
||||
ByteUtils.erase(secrets[0]);
|
||||
ByteUtils.erase(secrets[1]);
|
||||
return;
|
||||
}
|
||||
callback.showSuccess();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void startOutgoingInvitation(final DuplexPlugin plugin,
|
||||
final OutgoingInvitationCallback callback) {
|
||||
pluginExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
// Generate an invitation code and use it to seed the PRNG
|
||||
int code = crypto.getSecureRandom().nextInt(MAX_CODE + 1);
|
||||
PseudoRandom r = crypto.getPseudoRandom(code);
|
||||
// Connect to the invitee
|
||||
DuplexTransportConnection conn = plugin.sendInvitation(r,
|
||||
INVITATION_TIMEOUT);
|
||||
if(callback.isCancelled()) {
|
||||
if(conn != null) conn.dispose(false, false);
|
||||
return;
|
||||
}
|
||||
if(conn == null) {
|
||||
callback.showFailure(TIMED_OUT);
|
||||
return;
|
||||
}
|
||||
KeyPair ourKeyPair = crypto.generateKeyPair();
|
||||
MessageDigest messageDigest = crypto.getMessageDigest();
|
||||
byte[] ourKey = ourKeyPair.getPublic().getEncoded();
|
||||
byte[] ourHash = messageDigest.digest(ourKey);
|
||||
byte[] theirKey, theirHash;
|
||||
try {
|
||||
// Receive the public key hash
|
||||
InputStream in = conn.getInputStream();
|
||||
Reader reader = readerFactory.createReader(in);
|
||||
theirHash = reader.readBytes(HASH_LENGTH);
|
||||
// Send the public key hash
|
||||
OutputStream out = conn.getOutputStream();
|
||||
Writer writer = writerFactory.createWriter(out);
|
||||
writer.writeBytes(ourHash);
|
||||
out.flush();
|
||||
// Receive the public key
|
||||
theirKey = reader.readBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
// Send the public key
|
||||
writer.writeBytes(ourKey);
|
||||
out.flush();
|
||||
} catch(IOException e) {
|
||||
conn.dispose(true, false);
|
||||
callback.showFailure(IO_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
conn.dispose(false, false);
|
||||
if(callback.isCancelled()) return;
|
||||
// Check that the received hash matches the received key
|
||||
if(!Arrays.equals(theirHash, messageDigest.digest(theirKey))) {
|
||||
callback.showFailure(INVALID_KEY);
|
||||
return;
|
||||
}
|
||||
// Derive the shared secret and the confirmation codes
|
||||
byte[][] secrets = crypto.deriveInitialSecrets(theirKey,
|
||||
ourKeyPair, code, true);
|
||||
if(secrets == null) {
|
||||
callback.showFailure(INVALID_KEY);
|
||||
return;
|
||||
}
|
||||
int ourCode = crypto.deriveConfirmationCode(secrets[0], true);
|
||||
int theirCode = crypto.deriveConfirmationCode(secrets[1],
|
||||
false);
|
||||
// Compare the confirmation codes
|
||||
if(callback.enterConfirmationCode(ourCode) != theirCode) {
|
||||
callback.showFailure(WRONG_CODE);
|
||||
ByteUtils.erase(secrets[0]);
|
||||
ByteUtils.erase(secrets[1]);
|
||||
return;
|
||||
}
|
||||
// Add the contact to the database
|
||||
try {
|
||||
db.addContact(secrets[1], secrets[0]);
|
||||
} catch(DbException e) {
|
||||
callback.showFailure(DB_EXCEPTION);
|
||||
ByteUtils.erase(secrets[0]);
|
||||
ByteUtils.erase(secrets[1]);
|
||||
return;
|
||||
}
|
||||
callback.showSuccess();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package net.sf.briar.plugins;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -40,7 +41,7 @@ import com.google.inject.Inject;
|
||||
class PluginManagerImpl implements PluginManager {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(PluginManagerImpl.class.getName());
|
||||
Logger.getLogger(PluginManagerImpl.class.getName());
|
||||
|
||||
private static final String[] SIMPLEX_PLUGIN_FACTORIES = new String[] {
|
||||
"net.sf.briar.plugins.file.RemovableDrivePluginFactory"
|
||||
@@ -84,7 +85,7 @@ class PluginManagerImpl implements PluginManager {
|
||||
try {
|
||||
Class<?> c = Class.forName(s);
|
||||
SimplexPluginFactory factory =
|
||||
(SimplexPluginFactory) c.newInstance();
|
||||
(SimplexPluginFactory) c.newInstance();
|
||||
SimplexCallback callback = new SimplexCallback();
|
||||
SimplexPlugin plugin = factory.createPlugin(pluginExecutor,
|
||||
callback);
|
||||
@@ -124,7 +125,7 @@ class PluginManagerImpl implements PluginManager {
|
||||
try {
|
||||
Class<?> c = Class.forName(s);
|
||||
DuplexPluginFactory factory =
|
||||
(DuplexPluginFactory) c.newInstance();
|
||||
(DuplexPluginFactory) c.newInstance();
|
||||
DuplexCallback callback = new DuplexCallback();
|
||||
DuplexPlugin plugin = factory.createPlugin(pluginExecutor,
|
||||
callback);
|
||||
@@ -198,6 +199,26 @@ class PluginManagerImpl implements PluginManager {
|
||||
return stopped;
|
||||
}
|
||||
|
||||
public Collection<DuplexPlugin> getDuplexInvitationPlugins() {
|
||||
Collection<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
|
||||
synchronized(this) {
|
||||
for(DuplexPlugin d : duplexPlugins) {
|
||||
if(d.supportsInvitations()) supported.add(d);
|
||||
}
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
|
||||
public Collection<SimplexPlugin> getSimplexInvitationPlugins() {
|
||||
Collection<SimplexPlugin> supported = new ArrayList<SimplexPlugin>();
|
||||
synchronized(this) {
|
||||
for(SimplexPlugin s : simplexPlugins) {
|
||||
if(s.supportsInvitations()) supported.add(s);
|
||||
}
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
|
||||
private abstract class PluginCallbackImpl implements PluginCallback {
|
||||
|
||||
protected volatile TransportId id = null;
|
||||
|
||||
@@ -25,6 +25,7 @@ import javax.microedition.io.StreamConnectionNotifier;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.TransportProperties;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.plugins.PluginExecutor;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||
@@ -304,22 +305,25 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
return true;
|
||||
}
|
||||
|
||||
public DuplexTransportConnection sendInvitation(int code, long timeout) {
|
||||
return createInvitationConnection(code, timeout);
|
||||
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
||||
long timeout) {
|
||||
return createInvitationConnection(r, timeout);
|
||||
}
|
||||
|
||||
public DuplexTransportConnection acceptInvitation(int code, long timeout) {
|
||||
return createInvitationConnection(code, timeout);
|
||||
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
||||
long timeout) {
|
||||
return createInvitationConnection(r, timeout);
|
||||
}
|
||||
|
||||
private DuplexTransportConnection createInvitationConnection(int code,
|
||||
private DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout) {
|
||||
synchronized(this) {
|
||||
if(!running) return null;
|
||||
}
|
||||
// Use the invitation code to generate the UUID
|
||||
String uuid = StringUtils.toHexString(r.nextBytes(16));
|
||||
// The invitee's device may not be discoverable, so both parties must
|
||||
// try to initiate connections
|
||||
String uuid = convertInvitationCodeToUuid(code);
|
||||
final ConnectionCallback c = new ConnectionCallback(uuid, timeout);
|
||||
pluginExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
@@ -342,12 +346,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private String convertInvitationCodeToUuid(int code) {
|
||||
byte[] b = new byte[16];
|
||||
new Random(code).nextBytes(b);
|
||||
return StringUtils.toHexString(b);
|
||||
}
|
||||
|
||||
private void createInvitationConnection(ConnectionCallback c) {
|
||||
LocalDevice localDevice;
|
||||
synchronized(this) {
|
||||
|
||||
@@ -11,19 +11,21 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.plugins.PluginExecutor;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexPlugin;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexPluginCallback;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexTransportReader;
|
||||
import net.sf.briar.api.plugins.simplex.SimplexTransportWriter;
|
||||
import net.sf.briar.api.transport.TransportConstants;
|
||||
import net.sf.briar.util.StringUtils;
|
||||
|
||||
import org.apache.commons.io.FileSystemUtils;
|
||||
|
||||
abstract class FilePlugin implements SimplexPlugin {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(FilePlugin.class.getName());
|
||||
Logger.getLogger(FilePlugin.class.getName());
|
||||
|
||||
protected final Executor pluginExecutor;
|
||||
protected final SimplexPluginCallback callback;
|
||||
@@ -92,26 +94,28 @@ abstract class FilePlugin implements SimplexPlugin {
|
||||
pluginExecutor.execute(new ReaderCreator(f));
|
||||
}
|
||||
|
||||
public SimplexTransportWriter sendInvitation(int code, long timeout) {
|
||||
public SimplexTransportWriter sendInvitation(PseudoRandom r, long timeout) {
|
||||
if(!running) return null;
|
||||
return createWriter(createInvitationFilename(code, false));
|
||||
return createWriter(createInvitationFilename(r, false));
|
||||
}
|
||||
|
||||
public SimplexTransportReader acceptInvitation(int code, long timeout) {
|
||||
public SimplexTransportReader acceptInvitation(PseudoRandom r,
|
||||
long timeout) {
|
||||
if(!running) return null;
|
||||
String filename = createInvitationFilename(code, false);
|
||||
String filename = createInvitationFilename(r, false);
|
||||
return createInvitationReader(filename, timeout);
|
||||
}
|
||||
|
||||
public SimplexTransportWriter sendInvitationResponse(int code, long timeout) {
|
||||
if(!running) return null;
|
||||
return createWriter(createInvitationFilename(code, true));
|
||||
}
|
||||
|
||||
public SimplexTransportReader acceptInvitationResponse(int code,
|
||||
public SimplexTransportWriter sendInvitationResponse(PseudoRandom r,
|
||||
long timeout) {
|
||||
if(!running) return null;
|
||||
String filename = createInvitationFilename(code, true);
|
||||
return createWriter(createInvitationFilename(r, true));
|
||||
}
|
||||
|
||||
public SimplexTransportReader acceptInvitationResponse(PseudoRandom r,
|
||||
long timeout) {
|
||||
if(!running) return null;
|
||||
String filename = createInvitationFilename(r, true);
|
||||
return createInvitationReader(filename, timeout);
|
||||
}
|
||||
|
||||
@@ -149,15 +153,14 @@ abstract class FilePlugin implements SimplexPlugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
private String createInvitationFilename(int code, boolean response) {
|
||||
assert code >= 0;
|
||||
assert code < 10 * 1000 * 1000;
|
||||
return String.format("%c%7d.dat", response ? 'b' : 'a', code);
|
||||
private String createInvitationFilename(PseudoRandom r, boolean response) {
|
||||
String digits = StringUtils.toHexString(r.nextBytes(3));
|
||||
return String.format("%c%s.dat", response ? 'b' : 'a', digits);
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
boolean isPossibleInvitationFilename(String filename) {
|
||||
return filename.toLowerCase().matches("[ab][0-9]{7}.dat");
|
||||
return filename.toLowerCase().matches("[ab][0-9a-f]{6}.dat");
|
||||
}
|
||||
|
||||
private class ReaderCreator implements Runnable {
|
||||
|
||||
@@ -9,11 +9,11 @@ import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.plugins.PluginExecutor;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||
@@ -23,7 +23,7 @@ import net.sf.briar.util.ByteUtils;
|
||||
class LanSocketPlugin extends SimpleSocketPlugin {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(LanSocketPlugin.class.getName());
|
||||
Logger.getLogger(LanSocketPlugin.class.getName());
|
||||
|
||||
LanSocketPlugin(@PluginExecutor Executor pluginExecutor,
|
||||
DuplexPluginCallback callback, long pollingInterval) {
|
||||
@@ -36,12 +36,13 @@ class LanSocketPlugin extends SimpleSocketPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection sendInvitation(int code, long timeout) {
|
||||
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
||||
long timeout) {
|
||||
synchronized(this) {
|
||||
if(!running) return null;
|
||||
}
|
||||
// Calculate the group address and port from the invitation code
|
||||
InetSocketAddress mcast = convertInvitationCodeToMulticastGroup(code);
|
||||
// Use the invitation code to choose the group address and port
|
||||
InetSocketAddress mcast = chooseMulticastGroup(r);
|
||||
// Bind a multicast socket for receiving packets
|
||||
MulticastSocket ms = null;
|
||||
try {
|
||||
@@ -105,10 +106,8 @@ class LanSocketPlugin extends SimpleSocketPlugin {
|
||||
ms.close();
|
||||
}
|
||||
|
||||
private InetSocketAddress convertInvitationCodeToMulticastGroup(int code) {
|
||||
Random r = new Random(code);
|
||||
byte[] b = new byte[5];
|
||||
r.nextBytes(b);
|
||||
private InetSocketAddress chooseMulticastGroup(PseudoRandom r) {
|
||||
byte[] b = r.nextBytes(5);
|
||||
// The group address is 239.random.random.random, excluding 0 and 255
|
||||
byte[] group = new byte[4];
|
||||
group[0] = (byte) 239;
|
||||
@@ -139,12 +138,13 @@ class LanSocketPlugin extends SimpleSocketPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection acceptInvitation(int code, long timeout) {
|
||||
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
||||
long timeout) {
|
||||
synchronized(this) {
|
||||
if(!running) return null;
|
||||
}
|
||||
// Calculate the group address and port from the invitation code
|
||||
InetSocketAddress mcast = convertInvitationCodeToMulticastGroup(code);
|
||||
// Use the invitation code to choose the group address and port
|
||||
InetSocketAddress mcast = chooseMulticastGroup(r);
|
||||
// Bind a TCP socket for receiving connections
|
||||
ServerSocket ss = null;
|
||||
try {
|
||||
|
||||
@@ -15,6 +15,7 @@ import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.TransportProperties;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.plugins.PluginExecutor;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||
@@ -24,13 +25,13 @@ import net.sf.briar.util.StringUtils;
|
||||
class SimpleSocketPlugin extends SocketPlugin {
|
||||
|
||||
public static final byte[] TRANSPORT_ID =
|
||||
StringUtils.fromHexString("58c66d999e492b85065924acfd739d80"
|
||||
+ "c65a62f87e5a4fc6c284f95908b9007d"
|
||||
+ "512a93ebf89bf68f50a29e96eebf97b6");
|
||||
StringUtils.fromHexString("58c66d999e492b85065924acfd739d80"
|
||||
+ "c65a62f87e5a4fc6c284f95908b9007d"
|
||||
+ "512a93ebf89bf68f50a29e96eebf97b6");
|
||||
|
||||
private static final TransportId ID = new TransportId(TRANSPORT_ID);
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(SimpleSocketPlugin.class.getName());
|
||||
Logger.getLogger(SimpleSocketPlugin.class.getName());
|
||||
|
||||
SimpleSocketPlugin(@PluginExecutor Executor pluginExecutor,
|
||||
DuplexPluginCallback callback, long pollingInterval) {
|
||||
@@ -68,7 +69,7 @@ class SimpleSocketPlugin extends SocketPlugin {
|
||||
|
||||
protected InetAddress chooseInterface(boolean lan) throws IOException {
|
||||
List<NetworkInterface> ifaces =
|
||||
Collections.list(NetworkInterface.getNetworkInterfaces());
|
||||
Collections.list(NetworkInterface.getNetworkInterfaces());
|
||||
// Try to find an interface of the preferred type (LAN or WAN)
|
||||
for(NetworkInterface iface : ifaces) {
|
||||
for(InetAddress addr : Collections.list(iface.getInetAddresses())) {
|
||||
@@ -139,11 +140,13 @@ class SimpleSocketPlugin extends SocketPlugin {
|
||||
return false;
|
||||
}
|
||||
|
||||
public DuplexTransportConnection sendInvitation(int code, long timeout) {
|
||||
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
||||
long timeout) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public DuplexTransportConnection acceptInvitation(int code, long timeout) {
|
||||
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
||||
long timeout) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.logging.Logger;
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.TransportConfig;
|
||||
import net.sf.briar.api.TransportProperties;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.plugins.PluginExecutor;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||
@@ -33,13 +34,13 @@ import org.silvertunnel.netlib.layer.tor.util.RSAKeyPair;
|
||||
class TorPlugin implements DuplexPlugin {
|
||||
|
||||
public static final byte[] TRANSPORT_ID =
|
||||
StringUtils.fromHexString("f264721575cb7ee710772f35abeb3db4"
|
||||
+ "a91f474e14de346be296c2efc99effdd"
|
||||
+ "f35921e6ed87a25c201f044da4767981");
|
||||
StringUtils.fromHexString("f264721575cb7ee710772f35abeb3db4"
|
||||
+ "a91f474e14de346be296c2efc99effdd"
|
||||
+ "f35921e6ed87a25c201f044da4767981");
|
||||
|
||||
private static final TransportId ID = new TransportId(TRANSPORT_ID);
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(TorPlugin.class.getName());
|
||||
Logger.getLogger(TorPlugin.class.getName());
|
||||
|
||||
private final Executor pluginExecutor;
|
||||
private final DuplexPluginCallback callback;
|
||||
@@ -89,7 +90,7 @@ class TorPlugin implements DuplexPlugin {
|
||||
}
|
||||
}
|
||||
TorHiddenServicePortPrivateNetAddress addrPort =
|
||||
new TorHiddenServicePortPrivateNetAddress(addr, 80);
|
||||
new TorHiddenServicePortPrivateNetAddress(addr, 80);
|
||||
// Connect to Tor
|
||||
NetFactory netFactory = NetFactory.getInstance();
|
||||
NetLayer nl = netFactory.getNetLayerById(NetLayerIDs.TOR);
|
||||
@@ -129,7 +130,7 @@ class TorPlugin implements DuplexPlugin {
|
||||
private TorHiddenServicePrivateNetAddress createHiddenServiceAddress(
|
||||
TorNetLayerUtil util, TransportConfig c) {
|
||||
TorHiddenServicePrivateNetAddress addr =
|
||||
util.createNewTorHiddenServicePrivateNetAddress();
|
||||
util.createNewTorHiddenServicePrivateNetAddress();
|
||||
RSAKeyPair keyPair = addr.getKeyPair();
|
||||
String privateKey = Encryption.getPEMStringFromRSAKeyPair(keyPair);
|
||||
c.put("privateKey", privateKey);
|
||||
@@ -199,7 +200,7 @@ class TorPlugin implements DuplexPlugin {
|
||||
if(!running) return;
|
||||
}
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
callback.getRemoteProperties();
|
||||
for(final ContactId c : remote.keySet()) {
|
||||
if(connected.contains(c)) continue;
|
||||
pluginExecutor.execute(new Runnable() {
|
||||
@@ -249,11 +250,13 @@ class TorPlugin implements DuplexPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
public DuplexTransportConnection sendInvitation(int code, long timeout) {
|
||||
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
||||
long timeout) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public DuplexTransportConnection acceptInvitation(int code, long timeout) {
|
||||
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
||||
long timeout) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public abstract class DuplexClientTest extends DuplexTest {
|
||||
}
|
||||
// Try to send an invitation
|
||||
System.out.println("Sending invitation");
|
||||
d = plugin.sendInvitation(123, INVITATION_TIMEOUT);
|
||||
d = plugin.sendInvitation(getPseudoRandom(123), INVITATION_TIMEOUT);
|
||||
if(d == null) {
|
||||
System.out.println("Connection failed");
|
||||
} else {
|
||||
@@ -38,7 +38,7 @@ public abstract class DuplexClientTest extends DuplexTest {
|
||||
}
|
||||
// Try to accept an invitation
|
||||
System.out.println("Accepting invitation");
|
||||
d = plugin.acceptInvitation(456, INVITATION_TIMEOUT);
|
||||
d = plugin.acceptInvitation(getPseudoRandom(456), INVITATION_TIMEOUT);
|
||||
if(d == null) {
|
||||
System.out.println("Connection failed");
|
||||
} else {
|
||||
|
||||
@@ -24,8 +24,8 @@ public abstract class DuplexServerTest extends DuplexTest {
|
||||
callback.latch.await();
|
||||
// Try to accept an invitation
|
||||
System.out.println("Accepting invitation");
|
||||
DuplexTransportConnection d = plugin.acceptInvitation(123,
|
||||
INVITATION_TIMEOUT);
|
||||
DuplexTransportConnection d = plugin.acceptInvitation(
|
||||
getPseudoRandom(123), INVITATION_TIMEOUT);
|
||||
if(d == null) {
|
||||
System.out.println("Connection failed");
|
||||
} else {
|
||||
@@ -34,7 +34,7 @@ public abstract class DuplexServerTest extends DuplexTest {
|
||||
}
|
||||
// Try to send an invitation
|
||||
System.out.println("Sending invitation");
|
||||
d = plugin.sendInvitation(456, INVITATION_TIMEOUT);
|
||||
d = plugin.sendInvitation(getPseudoRandom(456), INVITATION_TIMEOUT);
|
||||
if(d == null) {
|
||||
System.out.println("Connection failed");
|
||||
} else {
|
||||
|
||||
@@ -2,9 +2,11 @@ package net.sf.briar.plugins;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Random;
|
||||
import java.util.Scanner;
|
||||
|
||||
import net.sf.briar.api.ContactId;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||
|
||||
@@ -66,4 +68,23 @@ abstract class DuplexTest {
|
||||
d.dispose(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected PseudoRandom getPseudoRandom(int seed) {
|
||||
return new TestPseudoRandom(seed);
|
||||
}
|
||||
|
||||
private static class TestPseudoRandom implements PseudoRandom {
|
||||
|
||||
private final Random r;
|
||||
|
||||
private TestPseudoRandom(int seed) {
|
||||
r = new Random(seed);
|
||||
}
|
||||
|
||||
public byte[] nextBytes(int bytes) {
|
||||
byte[] b = new byte[bytes];
|
||||
r.nextBytes(b);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,4 +48,19 @@ public class ByteUtilsTest extends BriarTestCase {
|
||||
ByteUtils.writeUint32(4294967295L, b, 1);
|
||||
assertEquals("00FFFFFFFF", StringUtils.toHexString(b));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadUint() {
|
||||
byte[] b = new byte[1];
|
||||
b[0] = (byte) 128;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
assertEquals(1 << i, ByteUtils.readUint(b, i + 1));
|
||||
}
|
||||
b = new byte[2];
|
||||
for(int i = 0; i < 65535; i++) {
|
||||
ByteUtils.writeUint16(i, b, 0);
|
||||
assertEquals(i, ByteUtils.readUint(b, 16));
|
||||
assertEquals(i >> 1, ByteUtils.readUint(b, 15));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +38,21 @@ public class ByteUtils {
|
||||
public static long readUint32(byte[] b, int offset) {
|
||||
if(b.length < offset + 4) throw new IllegalArgumentException();
|
||||
return ((b[offset] & 0xFFL) << 24) | ((b[offset + 1] & 0xFFL) << 16)
|
||||
| ((b[offset + 2] & 0xFFL) << 8) | (b[offset + 3] & 0xFFL);
|
||||
| ((b[offset + 2] & 0xFFL) << 8) | (b[offset + 3] & 0xFFL);
|
||||
}
|
||||
|
||||
public static void erase(byte[] b) {
|
||||
for(int i = 0; i < b.length; i++) b[i] = 0;
|
||||
}
|
||||
|
||||
public static int readUint(byte[] b, int bits) {
|
||||
if(b.length << 3 < bits) throw new IllegalArgumentException();
|
||||
int result = 0;
|
||||
for(int i = 0; i < bits; i++) {
|
||||
if((b[i >> 3] & 128 >> (i & 7)) != 0) result |= 1 << bits - i - 1;
|
||||
}
|
||||
assert result >= 0;
|
||||
assert result < 1 << bits;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user