mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-17 05:09:53 +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);
|
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);
|
byte[] deriveNextSecret(byte[] secret, int index, long connection);
|
||||||
|
|
||||||
KeyPair generateKeyPair();
|
KeyPair generateKeyPair();
|
||||||
@@ -25,6 +30,8 @@ public interface CryptoComponent {
|
|||||||
|
|
||||||
MessageDigest getMessageDigest();
|
MessageDigest getMessageDigest();
|
||||||
|
|
||||||
|
PseudoRandom getPseudoRandom(int seed);
|
||||||
|
|
||||||
SecureRandom getSecureRandom();
|
SecureRandom getSecureRandom();
|
||||||
|
|
||||||
Cipher getTagCipher();
|
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;
|
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 {
|
public interface PluginManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,4 +17,10 @@ public interface PluginManager {
|
|||||||
* Stops the plugins and returns the number of plugins successfully stopped.
|
* Stops the plugins and returns the number of plugins successfully stopped.
|
||||||
*/
|
*/
|
||||||
int stop();
|
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;
|
package net.sf.briar.api.plugins.duplex;
|
||||||
|
|
||||||
import net.sf.briar.api.ContactId;
|
import net.sf.briar.api.ContactId;
|
||||||
|
import net.sf.briar.api.crypto.PseudoRandom;
|
||||||
import net.sf.briar.api.plugins.Plugin;
|
import net.sf.briar.api.plugins.Plugin;
|
||||||
|
|
||||||
/** An interface for transport plugins that support duplex communication. */
|
/** 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
|
* Starts the invitation process from the inviter's side. Returns null if
|
||||||
* no connection can be established within the given timeout.
|
* 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
|
* Starts the invitation process from the invitee's side. Returns null if
|
||||||
* no connection can be established within the given timeout.
|
* 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;
|
package net.sf.briar.api.plugins.simplex;
|
||||||
|
|
||||||
import net.sf.briar.api.ContactId;
|
import net.sf.briar.api.ContactId;
|
||||||
|
import net.sf.briar.api.crypto.PseudoRandom;
|
||||||
import net.sf.briar.api.plugins.Plugin;
|
import net.sf.briar.api.plugins.Plugin;
|
||||||
|
|
||||||
/** An interface for transport plugins that support simplex communication. */
|
/** 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
|
* Starts the invitation process from the inviter's side. Returns null if
|
||||||
* no connection can be established within the given timeout.
|
* 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
|
* Starts the invitation process from the invitee's side. Returns null if
|
||||||
* no connection can be established within the given timeout.
|
* 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
|
* Continues the invitation process from the invitee's side. Returns null
|
||||||
* if no connection can be established within the given timeout.
|
* 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
|
* Continues the invitation process from the inviter's side. Returns null
|
||||||
* if no connection can be established within the given timeout.
|
* 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;
|
package net.sf.briar.crypto;
|
||||||
|
|
||||||
|
import static net.sf.briar.api.plugins.InvitationConstants.CODE_BITS;
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.PublicKey;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.KeyAgreement;
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
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.ErasableKey;
|
||||||
import net.sf.briar.api.crypto.KeyParser;
|
import net.sf.briar.api.crypto.KeyParser;
|
||||||
import net.sf.briar.api.crypto.MessageDigest;
|
import net.sf.briar.api.crypto.MessageDigest;
|
||||||
|
import net.sf.briar.api.crypto.PseudoRandom;
|
||||||
import net.sf.briar.util.ByteUtils;
|
import net.sf.briar.util.ByteUtils;
|
||||||
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
@@ -26,6 +31,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
private static final String PROVIDER = "BC";
|
private static final String PROVIDER = "BC";
|
||||||
private static final String KEY_PAIR_ALGO = "ECDSA";
|
private static final String KEY_PAIR_ALGO = "ECDSA";
|
||||||
private static final int KEY_PAIR_BITS = 384;
|
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 String SECRET_KEY_ALGO = "AES";
|
||||||
private static final int SECRET_KEY_BYTES = 32; // 256 bits
|
private static final int SECRET_KEY_BYTES = 32; // 256 bits
|
||||||
private static final int KEY_DERIVATION_IV_BYTES = 16; // 128 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[] TAG = { 'T', 'A', 'G', 0 };
|
||||||
private static final byte[] FRAME = { 'F', 'R', 'A', 'M', 'E', 0 };
|
private static final byte[] FRAME = { 'F', 'R', 'A', 'M', 'E', 0 };
|
||||||
private static final byte[] MAC = { 'M', 'A', 'C', 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 };
|
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[] INITIATOR = { 'I' };
|
||||||
private static final byte[] RESPONDER = { 'R' };
|
private static final byte[] RESPONDER = { 'R' };
|
||||||
// Blank plaintext for key derivation
|
// Blank plaintext for key derivation
|
||||||
private static final byte[] KEY_DERIVATION_INPUT =
|
private static final byte[] KEY_DERIVATION_INPUT =
|
||||||
new byte[SECRET_KEY_BYTES];
|
new byte[SECRET_KEY_BYTES];
|
||||||
|
|
||||||
private final KeyParser keyParser;
|
private final KeyParser keyParser;
|
||||||
private final KeyPairGenerator keyPairGenerator;
|
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) {
|
public byte[] deriveNextSecret(byte[] secret, int index, long connection) {
|
||||||
if(index < 0 || index > ByteUtils.MAX_16_BIT_UNSIGNED)
|
if(index < 0 || index > ByteUtils.MAX_16_BIT_UNSIGNED)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
@@ -125,6 +201,19 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
return counterModeKdf(secret, NEXT, context);
|
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() {
|
public KeyPair generateKeyPair() {
|
||||||
return keyPairGenerator.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() {
|
public SecureRandom getSecureRandom() {
|
||||||
return secureRandom;
|
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.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -40,7 +41,7 @@ import com.google.inject.Inject;
|
|||||||
class PluginManagerImpl implements PluginManager {
|
class PluginManagerImpl implements PluginManager {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(PluginManagerImpl.class.getName());
|
Logger.getLogger(PluginManagerImpl.class.getName());
|
||||||
|
|
||||||
private static final String[] SIMPLEX_PLUGIN_FACTORIES = new String[] {
|
private static final String[] SIMPLEX_PLUGIN_FACTORIES = new String[] {
|
||||||
"net.sf.briar.plugins.file.RemovableDrivePluginFactory"
|
"net.sf.briar.plugins.file.RemovableDrivePluginFactory"
|
||||||
@@ -84,7 +85,7 @@ class PluginManagerImpl implements PluginManager {
|
|||||||
try {
|
try {
|
||||||
Class<?> c = Class.forName(s);
|
Class<?> c = Class.forName(s);
|
||||||
SimplexPluginFactory factory =
|
SimplexPluginFactory factory =
|
||||||
(SimplexPluginFactory) c.newInstance();
|
(SimplexPluginFactory) c.newInstance();
|
||||||
SimplexCallback callback = new SimplexCallback();
|
SimplexCallback callback = new SimplexCallback();
|
||||||
SimplexPlugin plugin = factory.createPlugin(pluginExecutor,
|
SimplexPlugin plugin = factory.createPlugin(pluginExecutor,
|
||||||
callback);
|
callback);
|
||||||
@@ -124,7 +125,7 @@ class PluginManagerImpl implements PluginManager {
|
|||||||
try {
|
try {
|
||||||
Class<?> c = Class.forName(s);
|
Class<?> c = Class.forName(s);
|
||||||
DuplexPluginFactory factory =
|
DuplexPluginFactory factory =
|
||||||
(DuplexPluginFactory) c.newInstance();
|
(DuplexPluginFactory) c.newInstance();
|
||||||
DuplexCallback callback = new DuplexCallback();
|
DuplexCallback callback = new DuplexCallback();
|
||||||
DuplexPlugin plugin = factory.createPlugin(pluginExecutor,
|
DuplexPlugin plugin = factory.createPlugin(pluginExecutor,
|
||||||
callback);
|
callback);
|
||||||
@@ -198,6 +199,26 @@ class PluginManagerImpl implements PluginManager {
|
|||||||
return stopped;
|
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 {
|
private abstract class PluginCallbackImpl implements PluginCallback {
|
||||||
|
|
||||||
protected volatile TransportId id = null;
|
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.ContactId;
|
||||||
import net.sf.briar.api.TransportProperties;
|
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.PluginExecutor;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||||
@@ -304,22 +305,25 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DuplexTransportConnection sendInvitation(int code, long timeout) {
|
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
||||||
return createInvitationConnection(code, timeout);
|
long timeout) {
|
||||||
|
return createInvitationConnection(r, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DuplexTransportConnection acceptInvitation(int code, long timeout) {
|
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
||||||
return createInvitationConnection(code, timeout);
|
long timeout) {
|
||||||
|
return createInvitationConnection(r, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DuplexTransportConnection createInvitationConnection(int code,
|
private DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||||
long timeout) {
|
long timeout) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
if(!running) return null;
|
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
|
// The invitee's device may not be discoverable, so both parties must
|
||||||
// try to initiate connections
|
// try to initiate connections
|
||||||
String uuid = convertInvitationCodeToUuid(code);
|
|
||||||
final ConnectionCallback c = new ConnectionCallback(uuid, timeout);
|
final ConnectionCallback c = new ConnectionCallback(uuid, timeout);
|
||||||
pluginExecutor.execute(new Runnable() {
|
pluginExecutor.execute(new Runnable() {
|
||||||
public void run() {
|
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) {
|
private void createInvitationConnection(ConnectionCallback c) {
|
||||||
LocalDevice localDevice;
|
LocalDevice localDevice;
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
|
|||||||
@@ -11,19 +11,21 @@ import java.util.logging.Level;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import net.sf.briar.api.ContactId;
|
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.PluginExecutor;
|
||||||
import net.sf.briar.api.plugins.simplex.SimplexPlugin;
|
import net.sf.briar.api.plugins.simplex.SimplexPlugin;
|
||||||
import net.sf.briar.api.plugins.simplex.SimplexPluginCallback;
|
import net.sf.briar.api.plugins.simplex.SimplexPluginCallback;
|
||||||
import net.sf.briar.api.plugins.simplex.SimplexTransportReader;
|
import net.sf.briar.api.plugins.simplex.SimplexTransportReader;
|
||||||
import net.sf.briar.api.plugins.simplex.SimplexTransportWriter;
|
import net.sf.briar.api.plugins.simplex.SimplexTransportWriter;
|
||||||
import net.sf.briar.api.transport.TransportConstants;
|
import net.sf.briar.api.transport.TransportConstants;
|
||||||
|
import net.sf.briar.util.StringUtils;
|
||||||
|
|
||||||
import org.apache.commons.io.FileSystemUtils;
|
import org.apache.commons.io.FileSystemUtils;
|
||||||
|
|
||||||
abstract class FilePlugin implements SimplexPlugin {
|
abstract class FilePlugin implements SimplexPlugin {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(FilePlugin.class.getName());
|
Logger.getLogger(FilePlugin.class.getName());
|
||||||
|
|
||||||
protected final Executor pluginExecutor;
|
protected final Executor pluginExecutor;
|
||||||
protected final SimplexPluginCallback callback;
|
protected final SimplexPluginCallback callback;
|
||||||
@@ -92,26 +94,28 @@ abstract class FilePlugin implements SimplexPlugin {
|
|||||||
pluginExecutor.execute(new ReaderCreator(f));
|
pluginExecutor.execute(new ReaderCreator(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
public SimplexTransportWriter sendInvitation(int code, long timeout) {
|
public SimplexTransportWriter sendInvitation(PseudoRandom r, long timeout) {
|
||||||
if(!running) return null;
|
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;
|
if(!running) return null;
|
||||||
String filename = createInvitationFilename(code, false);
|
String filename = createInvitationFilename(r, false);
|
||||||
return createInvitationReader(filename, timeout);
|
return createInvitationReader(filename, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SimplexTransportWriter sendInvitationResponse(int code, long timeout) {
|
public SimplexTransportWriter sendInvitationResponse(PseudoRandom r,
|
||||||
if(!running) return null;
|
|
||||||
return createWriter(createInvitationFilename(code, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
public SimplexTransportReader acceptInvitationResponse(int code,
|
|
||||||
long timeout) {
|
long timeout) {
|
||||||
if(!running) return null;
|
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);
|
return createInvitationReader(filename, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,15 +153,14 @@ abstract class FilePlugin implements SimplexPlugin {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createInvitationFilename(int code, boolean response) {
|
private String createInvitationFilename(PseudoRandom r, boolean response) {
|
||||||
assert code >= 0;
|
String digits = StringUtils.toHexString(r.nextBytes(3));
|
||||||
assert code < 10 * 1000 * 1000;
|
return String.format("%c%s.dat", response ? 'b' : 'a', digits);
|
||||||
return String.format("%c%7d.dat", response ? 'b' : 'a', code);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Package access for testing
|
// Package access for testing
|
||||||
boolean isPossibleInvitationFilename(String filename) {
|
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 {
|
private class ReaderCreator implements Runnable {
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import java.net.ServerSocket;
|
|||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.Random;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
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.PluginExecutor;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||||
@@ -23,7 +23,7 @@ import net.sf.briar.util.ByteUtils;
|
|||||||
class LanSocketPlugin extends SimpleSocketPlugin {
|
class LanSocketPlugin extends SimpleSocketPlugin {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(LanSocketPlugin.class.getName());
|
Logger.getLogger(LanSocketPlugin.class.getName());
|
||||||
|
|
||||||
LanSocketPlugin(@PluginExecutor Executor pluginExecutor,
|
LanSocketPlugin(@PluginExecutor Executor pluginExecutor,
|
||||||
DuplexPluginCallback callback, long pollingInterval) {
|
DuplexPluginCallback callback, long pollingInterval) {
|
||||||
@@ -36,12 +36,13 @@ class LanSocketPlugin extends SimpleSocketPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection sendInvitation(int code, long timeout) {
|
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
||||||
|
long timeout) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
if(!running) return null;
|
if(!running) return null;
|
||||||
}
|
}
|
||||||
// Calculate the group address and port from the invitation code
|
// Use the invitation code to choose the group address and port
|
||||||
InetSocketAddress mcast = convertInvitationCodeToMulticastGroup(code);
|
InetSocketAddress mcast = chooseMulticastGroup(r);
|
||||||
// Bind a multicast socket for receiving packets
|
// Bind a multicast socket for receiving packets
|
||||||
MulticastSocket ms = null;
|
MulticastSocket ms = null;
|
||||||
try {
|
try {
|
||||||
@@ -105,10 +106,8 @@ class LanSocketPlugin extends SimpleSocketPlugin {
|
|||||||
ms.close();
|
ms.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private InetSocketAddress convertInvitationCodeToMulticastGroup(int code) {
|
private InetSocketAddress chooseMulticastGroup(PseudoRandom r) {
|
||||||
Random r = new Random(code);
|
byte[] b = r.nextBytes(5);
|
||||||
byte[] b = new byte[5];
|
|
||||||
r.nextBytes(b);
|
|
||||||
// The group address is 239.random.random.random, excluding 0 and 255
|
// The group address is 239.random.random.random, excluding 0 and 255
|
||||||
byte[] group = new byte[4];
|
byte[] group = new byte[4];
|
||||||
group[0] = (byte) 239;
|
group[0] = (byte) 239;
|
||||||
@@ -139,12 +138,13 @@ class LanSocketPlugin extends SimpleSocketPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection acceptInvitation(int code, long timeout) {
|
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
||||||
|
long timeout) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
if(!running) return null;
|
if(!running) return null;
|
||||||
}
|
}
|
||||||
// Calculate the group address and port from the invitation code
|
// Use the invitation code to choose the group address and port
|
||||||
InetSocketAddress mcast = convertInvitationCodeToMulticastGroup(code);
|
InetSocketAddress mcast = chooseMulticastGroup(r);
|
||||||
// Bind a TCP socket for receiving connections
|
// Bind a TCP socket for receiving connections
|
||||||
ServerSocket ss = null;
|
ServerSocket ss = null;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import net.sf.briar.api.ContactId;
|
import net.sf.briar.api.ContactId;
|
||||||
import net.sf.briar.api.TransportProperties;
|
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.PluginExecutor;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||||
@@ -24,13 +25,13 @@ import net.sf.briar.util.StringUtils;
|
|||||||
class SimpleSocketPlugin extends SocketPlugin {
|
class SimpleSocketPlugin extends SocketPlugin {
|
||||||
|
|
||||||
public static final byte[] TRANSPORT_ID =
|
public static final byte[] TRANSPORT_ID =
|
||||||
StringUtils.fromHexString("58c66d999e492b85065924acfd739d80"
|
StringUtils.fromHexString("58c66d999e492b85065924acfd739d80"
|
||||||
+ "c65a62f87e5a4fc6c284f95908b9007d"
|
+ "c65a62f87e5a4fc6c284f95908b9007d"
|
||||||
+ "512a93ebf89bf68f50a29e96eebf97b6");
|
+ "512a93ebf89bf68f50a29e96eebf97b6");
|
||||||
|
|
||||||
private static final TransportId ID = new TransportId(TRANSPORT_ID);
|
private static final TransportId ID = new TransportId(TRANSPORT_ID);
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(SimpleSocketPlugin.class.getName());
|
Logger.getLogger(SimpleSocketPlugin.class.getName());
|
||||||
|
|
||||||
SimpleSocketPlugin(@PluginExecutor Executor pluginExecutor,
|
SimpleSocketPlugin(@PluginExecutor Executor pluginExecutor,
|
||||||
DuplexPluginCallback callback, long pollingInterval) {
|
DuplexPluginCallback callback, long pollingInterval) {
|
||||||
@@ -68,7 +69,7 @@ class SimpleSocketPlugin extends SocketPlugin {
|
|||||||
|
|
||||||
protected InetAddress chooseInterface(boolean lan) throws IOException {
|
protected InetAddress chooseInterface(boolean lan) throws IOException {
|
||||||
List<NetworkInterface> ifaces =
|
List<NetworkInterface> ifaces =
|
||||||
Collections.list(NetworkInterface.getNetworkInterfaces());
|
Collections.list(NetworkInterface.getNetworkInterfaces());
|
||||||
// Try to find an interface of the preferred type (LAN or WAN)
|
// Try to find an interface of the preferred type (LAN or WAN)
|
||||||
for(NetworkInterface iface : ifaces) {
|
for(NetworkInterface iface : ifaces) {
|
||||||
for(InetAddress addr : Collections.list(iface.getInetAddresses())) {
|
for(InetAddress addr : Collections.list(iface.getInetAddresses())) {
|
||||||
@@ -139,11 +140,13 @@ class SimpleSocketPlugin extends SocketPlugin {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DuplexTransportConnection sendInvitation(int code, long timeout) {
|
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
||||||
|
long timeout) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DuplexTransportConnection acceptInvitation(int code, long timeout) {
|
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
||||||
|
long timeout) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import java.util.logging.Logger;
|
|||||||
import net.sf.briar.api.ContactId;
|
import net.sf.briar.api.ContactId;
|
||||||
import net.sf.briar.api.TransportConfig;
|
import net.sf.briar.api.TransportConfig;
|
||||||
import net.sf.briar.api.TransportProperties;
|
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.PluginExecutor;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||||
@@ -33,13 +34,13 @@ import org.silvertunnel.netlib.layer.tor.util.RSAKeyPair;
|
|||||||
class TorPlugin implements DuplexPlugin {
|
class TorPlugin implements DuplexPlugin {
|
||||||
|
|
||||||
public static final byte[] TRANSPORT_ID =
|
public static final byte[] TRANSPORT_ID =
|
||||||
StringUtils.fromHexString("f264721575cb7ee710772f35abeb3db4"
|
StringUtils.fromHexString("f264721575cb7ee710772f35abeb3db4"
|
||||||
+ "a91f474e14de346be296c2efc99effdd"
|
+ "a91f474e14de346be296c2efc99effdd"
|
||||||
+ "f35921e6ed87a25c201f044da4767981");
|
+ "f35921e6ed87a25c201f044da4767981");
|
||||||
|
|
||||||
private static final TransportId ID = new TransportId(TRANSPORT_ID);
|
private static final TransportId ID = new TransportId(TRANSPORT_ID);
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(TorPlugin.class.getName());
|
Logger.getLogger(TorPlugin.class.getName());
|
||||||
|
|
||||||
private final Executor pluginExecutor;
|
private final Executor pluginExecutor;
|
||||||
private final DuplexPluginCallback callback;
|
private final DuplexPluginCallback callback;
|
||||||
@@ -89,7 +90,7 @@ class TorPlugin implements DuplexPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TorHiddenServicePortPrivateNetAddress addrPort =
|
TorHiddenServicePortPrivateNetAddress addrPort =
|
||||||
new TorHiddenServicePortPrivateNetAddress(addr, 80);
|
new TorHiddenServicePortPrivateNetAddress(addr, 80);
|
||||||
// Connect to Tor
|
// Connect to Tor
|
||||||
NetFactory netFactory = NetFactory.getInstance();
|
NetFactory netFactory = NetFactory.getInstance();
|
||||||
NetLayer nl = netFactory.getNetLayerById(NetLayerIDs.TOR);
|
NetLayer nl = netFactory.getNetLayerById(NetLayerIDs.TOR);
|
||||||
@@ -129,7 +130,7 @@ class TorPlugin implements DuplexPlugin {
|
|||||||
private TorHiddenServicePrivateNetAddress createHiddenServiceAddress(
|
private TorHiddenServicePrivateNetAddress createHiddenServiceAddress(
|
||||||
TorNetLayerUtil util, TransportConfig c) {
|
TorNetLayerUtil util, TransportConfig c) {
|
||||||
TorHiddenServicePrivateNetAddress addr =
|
TorHiddenServicePrivateNetAddress addr =
|
||||||
util.createNewTorHiddenServicePrivateNetAddress();
|
util.createNewTorHiddenServicePrivateNetAddress();
|
||||||
RSAKeyPair keyPair = addr.getKeyPair();
|
RSAKeyPair keyPair = addr.getKeyPair();
|
||||||
String privateKey = Encryption.getPEMStringFromRSAKeyPair(keyPair);
|
String privateKey = Encryption.getPEMStringFromRSAKeyPair(keyPair);
|
||||||
c.put("privateKey", privateKey);
|
c.put("privateKey", privateKey);
|
||||||
@@ -199,7 +200,7 @@ class TorPlugin implements DuplexPlugin {
|
|||||||
if(!running) return;
|
if(!running) return;
|
||||||
}
|
}
|
||||||
Map<ContactId, TransportProperties> remote =
|
Map<ContactId, TransportProperties> remote =
|
||||||
callback.getRemoteProperties();
|
callback.getRemoteProperties();
|
||||||
for(final ContactId c : remote.keySet()) {
|
for(final ContactId c : remote.keySet()) {
|
||||||
if(connected.contains(c)) continue;
|
if(connected.contains(c)) continue;
|
||||||
pluginExecutor.execute(new Runnable() {
|
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();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DuplexTransportConnection acceptInvitation(int code, long timeout) {
|
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
||||||
|
long timeout) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public abstract class DuplexClientTest extends DuplexTest {
|
|||||||
}
|
}
|
||||||
// Try to send an invitation
|
// Try to send an invitation
|
||||||
System.out.println("Sending invitation");
|
System.out.println("Sending invitation");
|
||||||
d = plugin.sendInvitation(123, INVITATION_TIMEOUT);
|
d = plugin.sendInvitation(getPseudoRandom(123), INVITATION_TIMEOUT);
|
||||||
if(d == null) {
|
if(d == null) {
|
||||||
System.out.println("Connection failed");
|
System.out.println("Connection failed");
|
||||||
} else {
|
} else {
|
||||||
@@ -38,7 +38,7 @@ public abstract class DuplexClientTest extends DuplexTest {
|
|||||||
}
|
}
|
||||||
// Try to accept an invitation
|
// Try to accept an invitation
|
||||||
System.out.println("Accepting invitation");
|
System.out.println("Accepting invitation");
|
||||||
d = plugin.acceptInvitation(456, INVITATION_TIMEOUT);
|
d = plugin.acceptInvitation(getPseudoRandom(456), INVITATION_TIMEOUT);
|
||||||
if(d == null) {
|
if(d == null) {
|
||||||
System.out.println("Connection failed");
|
System.out.println("Connection failed");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ public abstract class DuplexServerTest extends DuplexTest {
|
|||||||
callback.latch.await();
|
callback.latch.await();
|
||||||
// Try to accept an invitation
|
// Try to accept an invitation
|
||||||
System.out.println("Accepting invitation");
|
System.out.println("Accepting invitation");
|
||||||
DuplexTransportConnection d = plugin.acceptInvitation(123,
|
DuplexTransportConnection d = plugin.acceptInvitation(
|
||||||
INVITATION_TIMEOUT);
|
getPseudoRandom(123), INVITATION_TIMEOUT);
|
||||||
if(d == null) {
|
if(d == null) {
|
||||||
System.out.println("Connection failed");
|
System.out.println("Connection failed");
|
||||||
} else {
|
} else {
|
||||||
@@ -34,7 +34,7 @@ public abstract class DuplexServerTest extends DuplexTest {
|
|||||||
}
|
}
|
||||||
// Try to send an invitation
|
// Try to send an invitation
|
||||||
System.out.println("Sending invitation");
|
System.out.println("Sending invitation");
|
||||||
d = plugin.sendInvitation(456, INVITATION_TIMEOUT);
|
d = plugin.sendInvitation(getPseudoRandom(456), INVITATION_TIMEOUT);
|
||||||
if(d == null) {
|
if(d == null) {
|
||||||
System.out.println("Connection failed");
|
System.out.println("Connection failed");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package net.sf.briar.plugins;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
|
||||||
import net.sf.briar.api.ContactId;
|
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.DuplexPlugin;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||||
|
|
||||||
@@ -66,4 +68,23 @@ abstract class DuplexTest {
|
|||||||
d.dispose(true, true);
|
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);
|
ByteUtils.writeUint32(4294967295L, b, 1);
|
||||||
assertEquals("00FFFFFFFF", StringUtils.toHexString(b));
|
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) {
|
public static long readUint32(byte[] b, int offset) {
|
||||||
if(b.length < offset + 4) throw new IllegalArgumentException();
|
if(b.length < offset + 4) throw new IllegalArgumentException();
|
||||||
return ((b[offset] & 0xFFL) << 24) | ((b[offset + 1] & 0xFFL) << 16)
|
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) {
|
public static void erase(byte[] b) {
|
||||||
for(int i = 0; i < b.length; i++) b[i] = 0;
|
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