Massive refactoring to use pseudonyms instead of nicknames for contacts.

The invitation and private messaging UIs are currently broken. Some key
rotation bugs were fixed; others may have been created (unit tests
needed). An encoding for private keys was added. Pseudonyms were moved
out of the messaging package and ratings were moved in.
This commit is contained in:
akwizgran
2013-03-29 19:48:23 +00:00
parent 4a40de957c
commit 3309938467
131 changed files with 2094 additions and 1398 deletions

View File

@@ -11,10 +11,12 @@ import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECField;
import java.security.spec.ECFieldFp;
@@ -39,27 +41,33 @@ import org.spongycastle.crypto.modes.AEADBlockCipher;
import org.spongycastle.crypto.modes.GCMBlockCipher;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import com.google.inject.Inject;
class CryptoComponentImpl implements CryptoComponent {
private static final String PROVIDER = "SC"; // Spongy Castle
private static final String AGREEMENT_KEY_PAIR_ALGO = "ECDH";
private static final int AGREEMENT_KEY_PAIR_BITS = 384;
private static final String AGREEMENT_ALGO = "ECDHC";
private static final String SECRET_KEY_ALGO = "AES";
private static final int SECRET_KEY_BYTES = 32; // 256 bits
private static final String KEY_DERIVATION_ALGO = "AES/CTR/NoPadding";
private static final int KEY_DERIVATION_IV_BYTES = 16; // 128 bits
private static final String DIGEST_ALGO = "SHA-384";
private static final String AGREEMENT_ALGO = "ECDHC";
private static final String AGREEMENT_KEY_PAIR_ALGO = "ECDH";
private static final int AGREEMENT_KEY_PAIR_BITS = 384;
private static final String SIGNATURE_ALGO = "ECDSA";
private static final String SIGNATURE_KEY_PAIR_ALGO = "ECDSA";
private static final int SIGNATURE_KEY_PAIR_BITS = 384;
private static final String SIGNATURE_ALGO = "ECDSA";
private static final String TAG_CIPHER_ALGO = "AES/ECB/NoPadding";
private static final int GCM_MAC_LENGTH = 16; // 128 bits
private static final String STORAGE_CIPHER_ALGO = "AES/GCM/NoPadding";
private static final int STORAGE_IV_LENGTH = 32; // 256 bits
private static final String KEY_DERIVATION_ALGO = "AES/CTR/NoPadding";
private static final int KEY_DERIVATION_IV_BYTES = 16; // 128 bits
// Labels for secret derivation
private static final byte[] MASTER = { 'M', 'A', 'S', 'T', 'E', 'R', '\0' };
private static final byte[] FIRST = { 'F', 'I', 'R', 'S', 'T', '\0' };
private static final byte[] ROTATE = { 'R', 'O', 'T', 'A', 'T', 'E', '\0' };
// Label for confirmation code derivation
private static final byte[] CODE = { 'C', 'O', 'D', 'E', '\0' };
// Label for invitation nonce derivation
private static final byte[] NONCE = { 'N', 'O', 'N', 'C', 'E', '\0' };
// Labels for key derivation
private static final byte[] A_TAG = { 'A', '_', 'T', 'A', 'G', '\0' };
private static final byte[] B_TAG = { 'B', '_', 'T', 'A', 'G', '\0' };
@@ -71,11 +79,6 @@ class CryptoComponentImpl implements CryptoComponent {
{ 'B', '_', 'F', 'R', 'A', 'M', 'E', '_', 'A', '\0' };
private static final byte[] B_FRAME_B =
{ 'B', '_', 'F', 'R', 'A', 'M', 'E', '_', 'B', '\0' };
// Labels for secret derivation
private static final byte[] FIRST = { 'F', 'I', 'R', 'S', 'T', '\0' };
private static final byte[] ROTATE = { 'R', 'O', 'T', 'A', 'T', 'E', '\0' };
// Label for confirmation code derivation
private static final byte[] CODE = { 'C', 'O', 'D', 'E', '\0' };
// Blank plaintext for key derivation
private static final byte[] KEY_DERIVATION_BLANK_PLAINTEXT =
new byte[SECRET_KEY_BYTES];
@@ -121,7 +124,6 @@ class CryptoComponentImpl implements CryptoComponent {
private final SecureRandom secureRandom;
private final ErasableKey temporaryStorageKey;
@Inject
CryptoComponentImpl() {
Security.addProvider(new BouncyCastleProvider());
try {
@@ -146,197 +148,6 @@ class CryptoComponentImpl implements CryptoComponent {
temporaryStorageKey = generateSecretKey();
}
public ErasableKey deriveTagKey(byte[] secret, boolean alice) {
if(alice) return deriveKey(secret, A_TAG, 0);
else return deriveKey(secret, B_TAG, 0);
}
public ErasableKey deriveFrameKey(byte[] secret, long connection,
boolean alice, boolean initiator) {
if(alice) {
if(initiator) return deriveKey(secret, A_FRAME_A, connection);
else return deriveKey(secret, A_FRAME_B, connection);
} else {
if(initiator) return deriveKey(secret, B_FRAME_A, connection);
else return deriveKey(secret, B_FRAME_B, connection);
}
}
private ErasableKey deriveKey(byte[] secret, byte[] label, long context) {
byte[] key = counterModeKdf(secret, label, context);
return new ErasableKeyImpl(key, SECRET_KEY_ALGO);
}
// Key derivation function based on a block cipher in CTR mode - see
// NIST SP 800-108, section 5.1
private byte[] counterModeKdf(byte[] secret, byte[] label, long context) {
// The secret must be usable as a key
if(secret.length != SECRET_KEY_BYTES)
throw new IllegalArgumentException();
// The label and context must leave a byte free for the counter
if(label.length + 4 >= KEY_DERIVATION_IV_BYTES)
throw new IllegalArgumentException();
byte[] ivBytes = new byte[KEY_DERIVATION_IV_BYTES];
System.arraycopy(label, 0, ivBytes, 0, label.length);
ByteUtils.writeUint32(context, ivBytes, label.length);
// Use the secret and the IV to encrypt a blank plaintext
IvParameterSpec iv = new IvParameterSpec(ivBytes);
ErasableKey key = new ErasableKeyImpl(secret, SECRET_KEY_ALGO);
try {
Cipher cipher = Cipher.getInstance(KEY_DERIVATION_ALGO, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] output = cipher.doFinal(KEY_DERIVATION_BLANK_PLAINTEXT);
assert output.length == SECRET_KEY_BYTES;
return output;
} catch(GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
public byte[] deriveInitialSecret(byte[] theirPublicKey,
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
PublicKey theirPublic = agreementKeyParser.parsePublicKey(
theirPublicKey);
MessageDigest messageDigest = getMessageDigest();
byte[] ourPublicKey = ourKeyPair.getPublic().getEncoded();
byte[] ourHash = messageDigest.digest(ourPublicKey);
byte[] theirHash = messageDigest.digest(theirPublicKey);
byte[] aliceInfo, bobInfo;
if(alice) {
aliceInfo = ourHash;
bobInfo = theirHash;
} else {
aliceInfo = theirHash;
bobInfo = ourHash;
}
// The raw secret comes from the key agreement algorithm
KeyAgreement keyAgreement = KeyAgreement.getInstance(AGREEMENT_ALGO,
PROVIDER);
keyAgreement.init(ourKeyPair.getPrivate());
keyAgreement.doPhase(theirPublic, true);
byte[] rawSecret = keyAgreement.generateSecret();
// Derive the cooked secret from the raw secret using the
// concatenation KDF
byte[] cookedSecret = concatenationKdf(rawSecret, FIRST, aliceInfo,
bobInfo);
ByteUtils.erase(rawSecret);
return cookedSecret;
}
// Key derivation function based on a hash function - see NIST SP 800-56A,
// section 5.8
private byte[] concatenationKdf(byte[] rawSecret, byte[] label,
byte[] initiatorInfo, byte[] responderInfo) {
// 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();
// All fields are length-prefixed
byte[] length = new byte[1];
ByteUtils.writeUint8(rawSecret.length, length, 0);
messageDigest.update(length);
messageDigest.update(rawSecret);
ByteUtils.writeUint8(label.length, length, 0);
messageDigest.update(length);
messageDigest.update(label);
ByteUtils.writeUint8(initiatorInfo.length, length, 0);
messageDigest.update(length);
messageDigest.update(initiatorInfo);
ByteUtils.writeUint8(responderInfo.length, length, 0);
messageDigest.update(length);
messageDigest.update(responderInfo);
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, long period) {
if(period < 0 || period > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
return counterModeKdf(secret, ROTATE, period);
}
public void encodeTag(byte[] tag, Cipher tagCipher, ErasableKey tagKey,
long connection) {
if(tag.length < TAG_LENGTH) throw new IllegalArgumentException();
if(connection < 0 || connection > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
for(int i = 0; i < TAG_LENGTH; i++) tag[i] = 0;
ByteUtils.writeUint32(connection, tag, 0);
try {
tagCipher.init(ENCRYPT_MODE, tagKey);
int encrypted = tagCipher.doFinal(tag, 0, TAG_LENGTH, tag);
if(encrypted != TAG_LENGTH) throw new IllegalArgumentException();
} catch(GeneralSecurityException e) {
// Unsuitable cipher or key
throw new IllegalArgumentException(e);
}
}
public int generateInvitationCode() {
int codeBytes = (int) Math.ceil(CODE_BITS / 8.0);
byte[] random = new byte[codeBytes];
secureRandom.nextBytes(random);
return ByteUtils.readUint(random, CODE_BITS);
}
public int[] deriveConfirmationCodes(byte[] secret) {
byte[] alice = counterModeKdf(secret, CODE, 0);
byte[] bob = counterModeKdf(secret, CODE, 1);
int[] codes = new int[2];
codes[0] = ByteUtils.readUint(alice, CODE_BITS);
codes[1] = ByteUtils.readUint(bob, CODE_BITS);
ByteUtils.erase(alice);
ByteUtils.erase(bob);
return codes;
}
public KeyPair generateAgreementKeyPair() {
KeyPair keyPair = agreementKeyPairGenerator.generateKeyPair();
// Check that the key pair uses NIST curve P-384
ECPublicKey ecPublicKey = checkP384Params(keyPair.getPublic());
// Return a public key that uses the SEC 1 encoding
ecPublicKey = new Sec1PublicKey(ecPublicKey, AGREEMENT_KEY_PAIR_BITS);
return new KeyPair(ecPublicKey, keyPair.getPrivate());
}
private ECPublicKey checkP384Params(PublicKey publicKey) {
if(!(publicKey instanceof ECPublicKey)) throw new RuntimeException();
ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
ECParameterSpec params = ecPublicKey.getParams();
EllipticCurve curve = params.getCurve();
ECField field = curve.getField();
if(!(field instanceof ECFieldFp)) throw new RuntimeException();
BigInteger q = ((ECFieldFp) field).getP();
if(!q.equals(P_384_Q)) throw new RuntimeException();
if(!curve.getA().equals(P_384_A)) throw new RuntimeException();
if(!curve.getB().equals(P_384_B)) throw new RuntimeException();
if(!params.getGenerator().equals(P_384_G)) throw new RuntimeException();
if(!params.getOrder().equals(P_384_N)) throw new RuntimeException();
if(!(params.getCofactor() == P_384_H)) throw new RuntimeException();
return ecPublicKey;
}
public KeyParser getAgreementKeyParser() {
return agreementKeyParser;
}
public KeyPair generateSignatureKeyPair() {
KeyPair keyPair = signatureKeyPairGenerator.generateKeyPair();
// Check that the key pair uses NIST curve P-384
ECPublicKey ecPublicKey = checkP384Params(keyPair.getPublic());
// Return a public key that uses the SEC 1 encoding
ecPublicKey = new Sec1PublicKey(ecPublicKey, SIGNATURE_KEY_PAIR_BITS);
return new KeyPair(ecPublicKey, keyPair.getPrivate());
}
public KeyParser getSignatureKeyParser() {
return signatureKeyParser;
}
public ErasableKey generateSecretKey() {
byte[] b = new byte[SECRET_KEY_BYTES];
secureRandom.nextBytes(b);
@@ -368,6 +179,127 @@ class CryptoComponentImpl implements CryptoComponent {
}
}
public KeyPair generateAgreementKeyPair() {
KeyPair keyPair = agreementKeyPairGenerator.generateKeyPair();
// Check that the key pair uses NIST curve P-384
ECPublicKey publicKey = checkP384Params(keyPair.getPublic());
// Return a wrapper that uses the SEC 1 encoding
publicKey = new Sec1PublicKey(publicKey, AGREEMENT_KEY_PAIR_BITS);
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
privateKey = new Sec1PrivateKey(privateKey, AGREEMENT_KEY_PAIR_BITS);
return new KeyPair(publicKey, privateKey);
}
public KeyParser getAgreementKeyParser() {
return agreementKeyParser;
}
public KeyPair generateSignatureKeyPair() {
KeyPair keyPair = signatureKeyPairGenerator.generateKeyPair();
// Check that the key pair uses NIST curve P-384
ECPublicKey publicKey = checkP384Params(keyPair.getPublic());
// Return a wrapper that uses the SEC 1 encoding
publicKey = new Sec1PublicKey(publicKey, SIGNATURE_KEY_PAIR_BITS);
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
privateKey = new Sec1PrivateKey(privateKey, SIGNATURE_KEY_PAIR_BITS);
return new KeyPair(publicKey, privateKey);
}
public KeyParser getSignatureKeyParser() {
return signatureKeyParser;
}
public int generateInvitationCode() {
int codeBytes = (int) Math.ceil(CODE_BITS / 8.0);
byte[] random = new byte[codeBytes];
secureRandom.nextBytes(random);
return ByteUtils.readUint(random, CODE_BITS);
}
public int[] deriveConfirmationCodes(byte[] secret) {
byte[] alice = counterModeKdf(secret, CODE, 0);
byte[] bob = counterModeKdf(secret, CODE, 1);
int[] codes = new int[2];
codes[0] = ByteUtils.readUint(alice, CODE_BITS);
codes[1] = ByteUtils.readUint(bob, CODE_BITS);
ByteUtils.erase(alice);
ByteUtils.erase(bob);
return codes;
}
public byte[][] deriveInvitationNonces(byte[] secret) {
byte[] alice = counterModeKdf(secret, NONCE, 0);
byte[] bob = counterModeKdf(secret, NONCE, 1);
return new byte[][] { alice, bob };
}
public byte[] deriveMasterSecret(byte[] theirPublicKey,
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
PublicKey theirPub = agreementKeyParser.parsePublicKey(theirPublicKey);
MessageDigest messageDigest = getMessageDigest();
byte[] ourPublicKey = ourKeyPair.getPublic().getEncoded();
byte[] ourHash = messageDigest.digest(ourPublicKey);
byte[] theirHash = messageDigest.digest(theirPublicKey);
byte[] aliceInfo, bobInfo;
if(alice) {
aliceInfo = ourHash;
bobInfo = theirHash;
} else {
aliceInfo = theirHash;
bobInfo = ourHash;
}
PrivateKey ourPriv = ourKeyPair.getPrivate();
// The raw secret comes from the key agreement algorithm
byte[] raw = deriveSharedSecret(ourPriv, theirPub);
// Derive the cooked secret from the raw secret using the
// concatenation KDF
byte[] cooked = concatenationKdf(raw, MASTER, aliceInfo, bobInfo);
ByteUtils.erase(raw);
return cooked;
}
// Package access for testing
byte[] deriveSharedSecret(PrivateKey priv, PublicKey pub)
throws GeneralSecurityException {
KeyAgreement keyAgreement = KeyAgreement.getInstance(AGREEMENT_ALGO,
PROVIDER);
keyAgreement.init(priv);
keyAgreement.doPhase(pub, true);
return keyAgreement.generateSecret();
}
public byte[] deriveInitialSecret(byte[] secret, int transportIndex) {
if(transportIndex < 0) throw new IllegalArgumentException();
return counterModeKdf(secret, FIRST, transportIndex);
}
public byte[] deriveNextSecret(byte[] secret, long period) {
if(period < 0 || period > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
return counterModeKdf(secret, ROTATE, period);
}
public ErasableKey deriveTagKey(byte[] secret, boolean alice) {
if(alice) return deriveKey(secret, A_TAG, 0);
else return deriveKey(secret, B_TAG, 0);
}
public ErasableKey deriveFrameKey(byte[] secret, long connection,
boolean alice, boolean initiator) {
if(alice) {
if(initiator) return deriveKey(secret, A_FRAME_A, connection);
else return deriveKey(secret, A_FRAME_B, connection);
} else {
if(initiator) return deriveKey(secret, B_FRAME_A, connection);
else return deriveKey(secret, B_FRAME_B, connection);
}
}
private ErasableKey deriveKey(byte[] secret, byte[] label, long context) {
byte[] key = counterModeKdf(secret, label, context);
return new ErasableKeyImpl(key, SECRET_KEY_ALGO);
}
public Cipher getTagCipher() {
try {
return Cipher.getInstance(TAG_CIPHER_ALGO, PROVIDER);
@@ -377,12 +309,28 @@ class CryptoComponentImpl implements CryptoComponent {
}
public AuthenticatedCipher getFrameCipher() {
// This code is specific to BouncyCastle because javax.crypto.Cipher
// This code is specific to Spongy Castle because javax.crypto.Cipher
// doesn't support additional authenticated data until Java 7
AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
return new AuthenticatedCipherImpl(cipher, GCM_MAC_LENGTH);
}
public void encodeTag(byte[] tag, Cipher tagCipher, ErasableKey tagKey,
long connection) {
if(tag.length < TAG_LENGTH) throw new IllegalArgumentException();
if(connection < 0 || connection > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
for(int i = 0; i < TAG_LENGTH; i++) tag[i] = 0;
ByteUtils.writeUint32(connection, tag, 0);
try {
tagCipher.init(ENCRYPT_MODE, tagKey);
int encrypted = tagCipher.doFinal(tag, 0, TAG_LENGTH, tag);
if(encrypted != TAG_LENGTH) throw new IllegalArgumentException();
} catch(GeneralSecurityException e) {
throw new IllegalArgumentException(e); // Unsuitable cipher or key
}
}
public byte[] encryptTemporaryStorage(byte[] input) {
// Generate a random IV
byte[] ivBytes = new byte[STORAGE_IV_LENGTH];
@@ -425,4 +373,77 @@ class CryptoComponentImpl implements CryptoComponent {
return null; // Invalid
}
}
private ECPublicKey checkP384Params(PublicKey publicKey) {
if(!(publicKey instanceof ECPublicKey)) throw new RuntimeException();
ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
ECParameterSpec params = ecPublicKey.getParams();
EllipticCurve curve = params.getCurve();
ECField field = curve.getField();
if(!(field instanceof ECFieldFp)) throw new RuntimeException();
BigInteger q = ((ECFieldFp) field).getP();
if(!q.equals(P_384_Q)) throw new RuntimeException();
if(!curve.getA().equals(P_384_A)) throw new RuntimeException();
if(!curve.getB().equals(P_384_B)) throw new RuntimeException();
if(!params.getGenerator().equals(P_384_G)) throw new RuntimeException();
if(!params.getOrder().equals(P_384_N)) throw new RuntimeException();
if(!(params.getCofactor() == P_384_H)) throw new RuntimeException();
return ecPublicKey;
}
// Key derivation function based on a hash function - see NIST SP 800-56A,
// section 5.8
private byte[] concatenationKdf(byte[] rawSecret, byte[] label,
byte[] initiatorInfo, byte[] responderInfo) {
// 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();
// All fields are length-prefixed
byte[] length = new byte[1];
ByteUtils.writeUint8(rawSecret.length, length, 0);
messageDigest.update(length);
messageDigest.update(rawSecret);
ByteUtils.writeUint8(label.length, length, 0);
messageDigest.update(length);
messageDigest.update(label);
ByteUtils.writeUint8(initiatorInfo.length, length, 0);
messageDigest.update(length);
messageDigest.update(initiatorInfo);
ByteUtils.writeUint8(responderInfo.length, length, 0);
messageDigest.update(length);
messageDigest.update(responderInfo);
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;
}
// Key derivation function based on a block cipher in CTR mode - see
// NIST SP 800-108, section 5.1
private byte[] counterModeKdf(byte[] secret, byte[] label, long context) {
// The secret must be usable as a key
if(secret.length != SECRET_KEY_BYTES)
throw new IllegalArgumentException();
// The label and context must leave a byte free for the counter
if(label.length + 4 >= KEY_DERIVATION_IV_BYTES)
throw new IllegalArgumentException();
byte[] ivBytes = new byte[KEY_DERIVATION_IV_BYTES];
System.arraycopy(label, 0, ivBytes, 0, label.length);
ByteUtils.writeUint32(context, ivBytes, label.length);
// Use the secret and the IV to encrypt a blank plaintext
IvParameterSpec iv = new IvParameterSpec(ivBytes);
ErasableKey key = new ErasableKeyImpl(secret, SECRET_KEY_ALGO);
try {
Cipher cipher = Cipher.getInstance(KEY_DERIVATION_ALGO, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] output = cipher.doFinal(KEY_DERIVATION_BLANK_PLAINTEXT);
assert output.length == SECRET_KEY_BYTES;
return output;
} catch(GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -2,9 +2,13 @@ package net.sf.briar.crypto;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
@@ -20,20 +24,22 @@ class Sec1KeyParser implements KeyParser {
private final KeyFactory keyFactory;
private final ECParameterSpec params;
private final BigInteger modulus;
private final int bytesPerInt, encodedKeyLength;
private final int keyBits, bytesPerInt, publicKeyBytes, privateKeyBytes;
Sec1KeyParser(KeyFactory keyFactory, ECParameterSpec params,
BigInteger modulus, int keyBits) {
this.keyFactory = keyFactory;
this.params = params;
this.modulus = modulus;
this.keyBits = keyBits;
bytesPerInt = (int) Math.ceil(keyBits / 8.0);
encodedKeyLength = 1 + 2 * bytesPerInt;
publicKeyBytes = 1 + 2 * bytesPerInt;
privateKeyBytes = bytesPerInt;
}
public PublicKey parsePublicKey(byte[] encodedKey)
throws InvalidKeySpecException {
if(encodedKey.length != encodedKeyLength)
if(encodedKey.length != publicKeyBytes)
throw new InvalidKeySpecException();
// The first byte must be 0x04
if(encodedKey[0] != 4) throw new InvalidKeySpecException();
@@ -52,9 +58,23 @@ class Sec1KeyParser implements KeyParser {
BigInteger lhs = y.multiply(y).mod(modulus);
BigInteger rhs = x.multiply(x).add(a).multiply(x).add(b).mod(modulus);
if(!lhs.equals(rhs)) throw new InvalidKeySpecException();
// FIXME: Verify that n times the point (x, y) = the point at infinity
// Construct a public key from the point (x, y) and the params
ECPoint pub = new ECPoint(x, y);
ECPublicKeySpec keySpec = new ECPublicKeySpec(pub, params);
return keyFactory.generatePublic(keySpec);
ECPublicKey k = (ECPublicKey) keyFactory.generatePublic(keySpec);
return new Sec1PublicKey(k, keyBits);
}
public PrivateKey parsePrivateKey(byte[] encodedKey)
throws InvalidKeySpecException {
if(encodedKey.length != privateKeyBytes)
throw new InvalidKeySpecException();
BigInteger s = new BigInteger(1, encodedKey); // Positive signum
if(s.compareTo(params.getOrder()) >= 0)
throw new InvalidKeySpecException();
ECPrivateKeySpec keySpec = new ECPrivateKeySpec(s, params);
ECPrivateKey k = (ECPrivateKey) keyFactory.generatePrivate(keySpec);
return new Sec1PrivateKey(k, keyBits);
}
}

View File

@@ -0,0 +1,57 @@
package net.sf.briar.crypto;
import java.math.BigInteger;
import java.security.interfaces.ECPrivateKey;
import java.security.spec.ECParameterSpec;
class Sec1PrivateKey implements ECPrivateKey,
org.spongycastle.jce.interfaces.ECPrivateKey {
private static final long serialVersionUID = -493100835871466670L;
private final ECPrivateKey key;
private final int privateKeyBytes;
Sec1PrivateKey(ECPrivateKey key, int keyBits) {
// Spongy Castle only accepts instances of its own interface, so we
// have to wrap an instance of that interface and delegate to it
if(!(key instanceof org.spongycastle.jce.interfaces.ECPrivateKey))
throw new IllegalArgumentException();
this.key = key;
privateKeyBytes = (int) Math.ceil(keyBits / 8.0);
}
public String getAlgorithm() {
return key.getAlgorithm();
}
public byte[] getEncoded() {
byte[] encodedKey = new byte[privateKeyBytes];
BigInteger s = key.getS();
// Copy up to privateKeyBytes bytes into exactly privateKeyBytes bytes
byte[] sBytes = s.toByteArray();
for(int i = 0; i < sBytes.length && i < privateKeyBytes; i++)
encodedKey[privateKeyBytes - 1 - i] = sBytes[sBytes.length - 1 - i];
return encodedKey;
}
public String getFormat() {
return "SEC1";
}
public ECParameterSpec getParams() {
return key.getParams();
}
public BigInteger getS() {
return key.getS();
}
public org.spongycastle.jce.spec.ECParameterSpec getParameters() {
return ((org.spongycastle.jce.interfaces.ECPrivateKey) key).getParameters();
}
public BigInteger getD() {
return ((org.spongycastle.jce.interfaces.ECPrivateKey) key).getD();
}
}

View File

@@ -10,17 +10,22 @@ import java.security.spec.ECPoint;
* Elliptic Curve Cryptography", section 2.3 (Certicom Corporation, May 2009).
* Point compression is not used.
*/
class Sec1PublicKey implements ECPublicKey {
class Sec1PublicKey implements ECPublicKey,
org.spongycastle.jce.interfaces.ECPublicKey {
private static final long serialVersionUID = -2722797033851423987L;
private final ECPublicKey key;
private final int bytesPerInt, encodedKeyLength;
private final int bytesPerInt, publicKeyBytes;
Sec1PublicKey(ECPublicKey key, int keyBits) {
// Spongy Castle only accepts instances of its own interface, so we
// have to wrap an instance of that interface and delegate to it
if(!(key instanceof org.spongycastle.jce.interfaces.ECPublicKey))
throw new IllegalArgumentException();
this.key = key;
bytesPerInt = (int) Math.ceil(keyBits / 8.0);
encodedKeyLength = 1 + 2 * bytesPerInt;
publicKeyBytes = 1 + 2 * bytesPerInt;
}
public String getAlgorithm() {
@@ -28,9 +33,10 @@ class Sec1PublicKey implements ECPublicKey {
}
public byte[] getEncoded() {
byte[] encodedKey = new byte[encodedKeyLength];
byte[] encodedKey = new byte[publicKeyBytes];
encodedKey[0] = 4;
BigInteger x = key.getW().getAffineX(), y = key.getW().getAffineY();
BigInteger x = key.getW().getAffineX();
BigInteger y = key.getW().getAffineY();
// Copy up to bytesPerInt bytes into exactly bytesPerInt bytes
byte[] xBytes = x.toByteArray();
for(int i = 0; i < xBytes.length && i < bytesPerInt; i++)
@@ -52,4 +58,12 @@ class Sec1PublicKey implements ECPublicKey {
public ECPoint getW() {
return key.getW();
}
public org.spongycastle.jce.spec.ECParameterSpec getParameters() {
return ((org.spongycastle.jce.interfaces.ECPublicKey) key).getParameters();
}
public org.spongycastle.math.ec.ECPoint getQ() {
return ((org.spongycastle.jce.interfaces.ECPublicKey) key).getQ();
}
}

View File

@@ -4,27 +4,28 @@ import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.Contact;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.Rating;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.GroupMessageHeader;
import net.sf.briar.api.db.PrivateMessageHeader;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.LocalAuthor;
import net.sf.briar.api.messaging.LocalGroup;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.Rating;
import net.sf.briar.api.messaging.RetentionAck;
import net.sf.briar.api.messaging.RetentionUpdate;
import net.sf.briar.api.messaging.SubscriptionAck;
import net.sf.briar.api.messaging.SubscriptionUpdate;
import net.sf.briar.api.messaging.TransportAck;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.TransportUpdate;
import net.sf.briar.api.transport.Endpoint;
import net.sf.briar.api.transport.TemporarySecret;
@@ -82,16 +83,17 @@ interface Database<T> {
void commitTransaction(T txn) throws DbException;
/**
* Adds a contact with the given name to the database and returns an ID for
* the contact.
* Stores a contact with the given pseudonym, associated with the given
* local pseudonym, and returns an ID for the contact.
* <p>
* Locking: contact write, retention write, subscription write, transport
* write, window write.
*/
ContactId addContact(T txn, String name) throws DbException;
ContactId addContact(T txn, Author remote, AuthorId local)
throws DbException;
/**
* Adds an endpoint to the database.
* Stores an endpoint.
* <p>
* Locking: window write.
*/
@@ -108,7 +110,7 @@ interface Database<T> {
/**
* Stores a pseudonym that the user can use to sign messages.
* <p>
* Locking: identity write.
* Locking: contact write, identity write.
*/
void addLocalAuthor(T txn, LocalAuthor a) throws DbException;
@@ -162,12 +164,13 @@ interface Database<T> {
boolean addSubscription(T txn, Group g) throws DbException;
/**
* Adds a new transport to the database and returns true if the transport
* was not previously in the database.
* Stores a transport and returns true if the transport was not previously
* in the database.
* <p>
* Locking: transport write, window write.
*/
boolean addTransport(T txn, TransportId t) throws DbException;
boolean addTransport(T txn, TransportId t, long maxLatency)
throws DbException;
/**
* Makes the given group visible to the given contact.
@@ -176,6 +179,13 @@ interface Database<T> {
*/
void addVisibility(T txn, ContactId c, GroupId g) throws DbException;
/**
* Returns true if the database contains the given contact.
* <p>
* Locking: contact read.
*/
boolean containsContact(T txn, AuthorId a) throws DbException;
/**
* Returns true if the database contains the given contact.
* <p>
@@ -277,6 +287,13 @@ interface Database<T> {
*/
long getLastConnected(T txn, ContactId c) throws DbException;
/**
* Returns the pseudonym with the given ID.
* <p>
* Locking: identitiy read.
*/
LocalAuthor getLocalAuthor(T txn, AuthorId a) throws DbException;
/**
* Returns all pseudonyms that the user can use to sign messages.
* <p>
@@ -291,6 +308,14 @@ interface Database<T> {
*/
Collection<LocalGroup> getLocalGroups(T txn) throws DbException;
/**
* Returns the local transport properties for all transports.
* <p>
* Locking: transport read.
*/
Map<TransportId, TransportProperties> getLocalProperties(T txn)
throws DbException;
/**
* Returns the local transport properties for the given transport.
* <p>
@@ -509,6 +534,13 @@ interface Database<T> {
Collection<TransportAck> getTransportAcks(T txn, ContactId c)
throws DbException;
/**
* Returns the maximum latencies of all local transports.
* <p>
* Locking: transport read.
*/
Map<TransportId, Long> getTransportLatencies(T txn) throws DbException;
/**
* Returns a collection of transport updates for the given contact and
* updates their expiry times using the given latency. Returns null if no
@@ -669,6 +701,15 @@ interface Database<T> {
*/
boolean setReadFlag(T txn, MessageId m, boolean read) throws DbException;
/**
* Sets the remote transport properties for the given contact, replacing
* any existing properties.
* <p>
* Locking: transport write.
*/
void setRemoteProperties(T txn, ContactId c,
Map<TransportId, TransportProperties> p) throws DbException;
/**
* Updates the remote transport properties for the given contact and the
* given transport, replacing any existing properties, unless an update

View File

@@ -2,7 +2,7 @@ package net.sf.briar.db;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.api.Rating.GOOD;
import static net.sf.briar.api.messaging.Rating.GOOD;
import static net.sf.briar.db.DatabaseConstants.BYTES_PER_SWEEP;
import static net.sf.briar.db.DatabaseConstants.CRITICAL_FREE_SPACE;
import static net.sf.briar.db.DatabaseConstants.MAX_BYTES_BETWEEN_SPACE_CHECKS;
@@ -23,12 +23,16 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.Contact;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.Rating;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.db.ContactExistsException;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.GroupMessageHeader;
@@ -57,22 +61,19 @@ import net.sf.briar.api.db.event.TransportAddedEvent;
import net.sf.briar.api.db.event.TransportRemovedEvent;
import net.sf.briar.api.lifecycle.ShutdownManager;
import net.sf.briar.api.messaging.Ack;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.LocalAuthor;
import net.sf.briar.api.messaging.LocalGroup;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.Offer;
import net.sf.briar.api.messaging.Rating;
import net.sf.briar.api.messaging.Request;
import net.sf.briar.api.messaging.RetentionAck;
import net.sf.briar.api.messaging.RetentionUpdate;
import net.sf.briar.api.messaging.SubscriptionAck;
import net.sf.briar.api.messaging.SubscriptionUpdate;
import net.sf.briar.api.messaging.TransportAck;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.TransportUpdate;
import net.sf.briar.api.transport.Endpoint;
import net.sf.briar.api.transport.TemporarySecret;
@@ -183,7 +184,8 @@ DatabaseCleaner.Callback {
listeners.remove(d);
}
public ContactId addContact(String name) throws DbException {
public ContactId addContact(Author remote, AuthorId local)
throws DbException {
ContactId c;
contactLock.writeLock().lock();
try {
@@ -197,7 +199,9 @@ DatabaseCleaner.Callback {
try {
T txn = db.startTransaction();
try {
c = db.addContact(txn, name);
if(db.containsContact(txn, remote.getId()))
throw new ContactExistsException();
c = db.addContact(txn, remote, local);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
@@ -257,6 +261,43 @@ DatabaseCleaner.Callback {
}
}
public void addLocalAuthor(LocalAuthor a) throws DbException {
contactLock.writeLock().lock();
try {
identityLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
db.addLocalAuthor(txn, a);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
identityLock.writeLock().unlock();
}
} finally {
contactLock.writeLock().unlock();
}
}
public void addLocalGroup(LocalGroup g) throws DbException {
identityLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
db.addLocalGroup(txn, g);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
identityLock.writeLock().unlock();
}
}
public void addLocalGroupMessage(Message m) throws DbException {
boolean added = false;
contactLock.readLock().lock();
@@ -430,38 +471,6 @@ DatabaseCleaner.Callback {
return true;
}
public void addLocalAuthor(LocalAuthor a) throws DbException {
identityLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
db.addLocalAuthor(txn, a);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
identityLock.writeLock().unlock();
}
}
public void addLocalGroup(LocalGroup g) throws DbException {
identityLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
db.addLocalGroup(txn, g);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
identityLock.writeLock().unlock();
}
}
public void addSecrets(Collection<TemporarySecret> secrets)
throws DbException {
contactLock.readLock().lock();
@@ -498,7 +507,8 @@ DatabaseCleaner.Callback {
}
}
public boolean addTransport(TransportId t) throws DbException {
public boolean addTransport(TransportId t, long maxLatency)
throws DbException {
boolean added;
transportLock.writeLock().lock();
try {
@@ -506,7 +516,7 @@ DatabaseCleaner.Callback {
try {
T txn = db.startTransaction();
try {
added = db.addTransport(txn, t);
added = db.addTransport(txn, t, maxLatency);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
@@ -518,7 +528,7 @@ DatabaseCleaner.Callback {
} finally {
transportLock.writeLock().unlock();
}
if(added) callListeners(new TransportAddedEvent(t));
if(added) callListeners(new TransportAddedEvent(t, maxLatency));
return added;
}
@@ -947,6 +957,23 @@ DatabaseCleaner.Callback {
}
}
public LocalAuthor getLocalAuthor(AuthorId a) throws DbException {
identityLock.readLock().lock();
try {
T txn = db.startTransaction();
try {
LocalAuthor localAuthor = db.getLocalAuthor(txn, a);
db.commitTransaction(txn);
return localAuthor;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
identityLock.readLock().unlock();
}
}
public Collection<LocalAuthor> getLocalAuthors() throws DbException {
identityLock.readLock().lock();
try {
@@ -981,6 +1008,25 @@ DatabaseCleaner.Callback {
}
}
public Map<TransportId, TransportProperties> getLocalProperties()
throws DbException {
transportLock.readLock().lock();
try {
T txn = db.startTransaction();
try {
Map<TransportId, TransportProperties> properties =
db.getLocalProperties(txn);
db.commitTransaction(txn);
return properties;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
transportLock.readLock().unlock();
}
}
public TransportProperties getLocalProperties(TransportId t)
throws DbException {
transportLock.readLock().lock();
@@ -1197,6 +1243,24 @@ DatabaseCleaner.Callback {
}
}
public Map<TransportId, Long> getTransportLatencies() throws DbException {
transportLock.readLock().lock();
try {
T txn = db.startTransaction();
try {
Map<TransportId, Long> latencies =
db.getTransportLatencies(txn);
db.commitTransaction(txn);
return latencies;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
transportLock.readLock().unlock();
}
}
public Map<GroupId, Integer> getUnreadMessageCounts() throws DbException {
messageLock.readLock().lock();
try {
@@ -1785,6 +1849,30 @@ DatabaseCleaner.Callback {
}
}
public void setRemoteProperties(ContactId c,
Map<TransportId, TransportProperties> p) throws DbException {
contactLock.readLock().lock();
try {
transportLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
if(!db.containsContact(txn, c))
throw new NoSuchContactException();
db.setRemoteProperties(txn, c, p);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
transportLock.writeLock().unlock();
}
} finally {
contactLock.readLock().unlock();
}
}
public void setSeen(ContactId c, Collection<MessageId> seen)
throws DbException {
contactLock.readLock().lock();

View File

@@ -9,7 +9,6 @@ import java.util.Arrays;
import java.util.Properties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.Password;
import net.sf.briar.api.db.DatabaseConfig;
import net.sf.briar.api.db.DbException;
import net.sf.briar.util.FileUtils;
@@ -26,7 +25,7 @@ class H2Database extends JdbcDatabase {
private final File home;
private final String url;
private final Password password;
private final char[] password;
private final long maxSize;
@Inject
@@ -76,13 +75,12 @@ class H2Database extends JdbcDatabase {
@Override
protected Connection createConnection() throws SQLException {
Properties props = new Properties();
props.setProperty("user", "b");
char[] passwordArray = password.getPassword();
props.put("password", passwordArray);
props.setProperty("user", "user");
props.put("password", password);
try {
return DriverManager.getConnection(url, props);
} finally {
Arrays.fill(passwordArray, (char) 0);
Arrays.fill(password, (char) 0);
}
}
}

View File

@@ -4,9 +4,9 @@ import static java.sql.Types.BINARY;
import static java.sql.Types.VARCHAR;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.api.Rating.UNRATED;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
import static net.sf.briar.api.messaging.MessagingConstants.RETENTION_MODULUS;
import static net.sf.briar.api.messaging.Rating.UNRATED;
import static net.sf.briar.db.ExponentialBackoff.calculateExpiry;
import java.io.File;
@@ -27,30 +27,30 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.Contact;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.Rating;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.db.DbClosedException;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.GroupMessageHeader;
import net.sf.briar.api.db.PrivateMessageHeader;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.LocalAuthor;
import net.sf.briar.api.messaging.LocalGroup;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.Rating;
import net.sf.briar.api.messaging.RetentionAck;
import net.sf.briar.api.messaging.RetentionUpdate;
import net.sf.briar.api.messaging.SubscriptionAck;
import net.sf.briar.api.messaging.SubscriptionUpdate;
import net.sf.briar.api.messaging.TransportAck;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.TransportUpdate;
import net.sf.briar.api.transport.Endpoint;
import net.sf.briar.api.transport.TemporarySecret;
@@ -85,8 +85,18 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_CONTACTS =
"CREATE TABLE contacts"
+ " (contactId COUNTER,"
+ " authorId HASH NOT NULL,"
+ " name VARCHAR NOT NULL,"
+ " PRIMARY KEY (contactId))";
+ " publicKey BINARY NOT NULL,"
+ " localAuthorId HASH NOT NULL,"
+ " PRIMARY KEY (contactId),"
+ " UNIQUE (authorId),"
+ " FOREIGN KEY (localAuthorId)"
+ " REFERENCES localAuthors (authorId)"
+ " ON DELETE RESTRICT)"; // Deletion not allowed
private static final String INDEX_CONTACTS_BY_AUTHOR =
"CREATE INDEX contactsByAuthor ON contacts (authorId)";
// Locking: subscription
// Dependents: message
@@ -234,7 +244,10 @@ abstract class JdbcDatabase implements Database<Connection> {
// Locking: transport
// Dependents: window
private static final String CREATE_TRANSPORTS =
"CREATE TABLE transports (transportId HASH NOT NULL)";
"CREATE TABLE transports"
+ " (transportId HASH NOT NULL,"
+ " maxLatency BIGINT NOT NULL,"
+ " PRIMARY KEY (transportId))";
// Locking: transport
private static final String CREATE_TRANSPORT_CONFIGS =
@@ -305,8 +318,6 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " (contactId INT NOT NULL,"
+ " transportId HASH NOT NULL,"
+ " epoch BIGINT NOT NULL,"
+ " clockDiff BIGINT NOT NULL,"
+ " latency BIGINT NOT NULL,"
+ " alice BOOLEAN NOT NULL,"
+ " PRIMARY KEY (contactId, transportId),"
+ " FOREIGN KEY (contactId)"
@@ -401,6 +412,7 @@ abstract class JdbcDatabase implements Database<Connection> {
s.executeUpdate(insertTypeNames(CREATE_LOCAL_AUTHORS));
s.executeUpdate(insertTypeNames(CREATE_LOCAL_GROUPS));
s.executeUpdate(insertTypeNames(CREATE_CONTACTS));
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR);
s.executeUpdate(insertTypeNames(CREATE_GROUPS));
s.executeUpdate(insertTypeNames(CREATE_GROUP_VISIBILITIES));
s.executeUpdate(insertTypeNames(CREATE_CONTACT_GROUPS));
@@ -538,15 +550,20 @@ abstract class JdbcDatabase implements Database<Connection> {
if(interrupted) Thread.currentThread().interrupt();
}
public ContactId addContact(Connection txn, String name)
public ContactId addContact(Connection txn, Author remote, AuthorId local)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
// Create a contact row
String sql = "INSERT INTO contacts (name) VALUES (?)";
String sql = "INSERT INTO contacts"
+ " (authorId, name, publicKey, localAuthorId)"
+ " VALUES (?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setString(1, name);
ps.setBytes(1, remote.getId().getBytes());
ps.setString(2, remote.getName());
ps.setBytes(3, remote.getPublicKey());
ps.setBytes(4, local.getBytes());
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
@@ -612,11 +629,11 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setBytes(2, t);
ps.addBatch();
}
int[] affectedBatch = ps.executeBatch();
if(affectedBatch.length != transports.size())
int[] batchAffected = ps.executeBatch();
if(batchAffected.length != transports.size())
throw new DbStateException();
for(int i = 0; i < affectedBatch.length; i++) {
if(affectedBatch[i] != 1) throw new DbStateException();
for(int i = 0; i < batchAffected.length; i++) {
if(batchAffected[i] != 1) throw new DbStateException();
}
ps.close();
return c;
@@ -630,16 +647,14 @@ abstract class JdbcDatabase implements Database<Connection> {
public void addEndpoint(Connection txn, Endpoint ep) throws DbException {
PreparedStatement ps = null;
try {
String sql = "INSERT INTO endpoints (contactId, transportId,"
+ " epoch, clockDiff, latency, alice)"
+ " VALUES (?, ?, ?, ?, ?, ?)";
String sql = "INSERT INTO endpoints"
+ " (contactId, transportId, epoch, alice)"
+ " VALUES (?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, ep.getContactId().getInt());
ps.setBytes(2, ep.getTransportId().getBytes());
ps.setLong(3, ep.getEpoch());
ps.setLong(4, ep.getClockDifference());
ps.setLong(5, ep.getLatency());
ps.setBoolean(6, ep.getAlice());
ps.setBoolean(4, ep.getAlice());
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
@@ -900,7 +915,7 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public boolean addTransport(Connection txn, TransportId t)
public boolean addTransport(Connection txn, TransportId t, long maxLatency)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
@@ -916,9 +931,11 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
if(found) return false;
// Create a transport row
sql = "INSERT INTO transports (transportId) VALUES (?)";
sql = "INSERT INTO transports (transportId, maxLatency)"
+ " VALUES (?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, t.getBytes());
ps.setLong(2, maxLatency);
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
@@ -984,6 +1001,27 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public boolean containsContact(Connection txn, AuthorId a)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT NULL FROM contacts WHERE authorId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, a.getBytes());
rs = ps.executeQuery();
boolean found = rs.next();
if(rs.next()) throw new DbStateException();
rs.close();
ps.close();
return found;
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public boolean containsContact(Connection txn, ContactId c)
throws DbException {
PreparedStatement ps = null;
@@ -1117,7 +1155,7 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT name, lastConnected"
String sql = "SELECT authorId, name, publicKey, lastConnected"
+ " FROM contacts AS c"
+ " JOIN connectionTimes AS ct"
+ " ON c.contactId = ct.contactId"
@@ -1126,11 +1164,14 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setInt(1, c.getInt());
rs = ps.executeQuery();
if(!rs.next()) throw new DbStateException();
String name = rs.getString(1);
long lastConnected = rs.getLong(2);
AuthorId authorId = new AuthorId(rs.getBytes(1));
String name = rs.getString(2);
byte[] publicKey = rs.getBytes(3);
long lastConnected = rs.getLong(4);
rs.close();
ps.close();
return new Contact(c, name, lastConnected);
Author author = new Author(authorId, name, publicKey);
return new Contact(c, author, lastConnected);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
@@ -1163,7 +1204,8 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT c.contactId, name, lastConnected"
String sql = "SELECT c.contactId, authorId, name, publicKey,"
+ " lastConnected"
+ " FROM contacts AS c"
+ " JOIN connectionTimes AS ct"
+ " ON c.contactId = ct.contactId";
@@ -1171,10 +1213,13 @@ abstract class JdbcDatabase implements Database<Connection> {
rs = ps.executeQuery();
List<Contact> contacts = new ArrayList<Contact>();
while(rs.next()) {
ContactId id = new ContactId(rs.getInt(1));
String name = rs.getString(2);
long lastConnected = rs.getLong(3);
contacts.add(new Contact(id, name, lastConnected));
ContactId contactId = new ContactId(rs.getInt(1));
AuthorId authorId = new AuthorId(rs.getBytes(2));
String name = rs.getString(3);
byte[] publicKey = rs.getBytes(4);
long lastConnected = rs.getLong(5);
Author author = new Author(authorId, name, publicKey);
contacts.add(new Contact(contactId, author, lastConnected));
}
rs.close();
ps.close();
@@ -1191,8 +1236,7 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT contactId, transportId, epoch, clockDiff,"
+ " latency, alice"
String sql = "SELECT contactId, transportId, epoch, alice"
+ " FROM endpoints";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
@@ -1201,11 +1245,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ContactId c = new ContactId(rs.getInt(1));
TransportId t = new TransportId(rs.getBytes(2));
long epoch = rs.getLong(3);
long clockDiff = rs.getLong(4);
long latency = rs.getLong(5);
boolean alice = rs.getBoolean(6);
endpoints.add(new Endpoint(c, t, epoch, clockDiff, latency,
alice));
boolean alice = rs.getBoolean(4);
endpoints.add(new Endpoint(c, t, epoch, alice));
}
return Collections.unmodifiableList(endpoints);
} catch(SQLException e) {
@@ -1287,6 +1328,30 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public LocalAuthor getLocalAuthor(Connection txn, AuthorId a)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT name, publicKey, privateKey FROM localAuthors"
+ " WHERE authorId = ?";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
if(!rs.next()) throw new DbStateException();
AuthorId id = new AuthorId(rs.getBytes(1));
LocalAuthor localAuthor = new LocalAuthor(id, rs.getString(2),
rs.getBytes(3), rs.getBytes(4));
if(rs.next()) throw new DbStateException();
rs.close();
ps.close();
return localAuthor;
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Collection<LocalAuthor> getLocalAuthors(Connection txn)
throws DbException {
PreparedStatement ps = null;
@@ -1337,6 +1402,40 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public Map<TransportId, TransportProperties> getLocalProperties(
Connection txn) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT transportId, key, value"
+ " FROM transportProperties"
+ " ORDER BY transportId";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
Map<TransportId, TransportProperties> properties =
new HashMap<TransportId, TransportProperties>();
TransportId lastId = null;
TransportProperties p = null;
while(rs.next()) {
TransportId id = new TransportId(rs.getBytes(1));
String key = rs.getString(2), value = rs.getString(3);
if(!id.equals(lastId)) {
p = new TransportProperties();
properties.put(id, p);
lastId = id;
}
p.put(key, value);
}
rs.close();
ps.close();
return Collections.unmodifiableMap(properties);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public TransportProperties getLocalProperties(Connection txn, TransportId t)
throws DbException {
PreparedStatement ps = null;
@@ -1437,86 +1536,6 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public Collection<PrivateMessageHeader> getPrivateMessageHeaders(
Connection txn) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT m.messageId, parentId, contentType, subject,"
+ " timestamp, m.contactId, read, starred, seen"
+ " FROM messages AS m"
+ " JOIN statuses AS s"
+ " ON m.messageId = s.messageId"
+ " AND m.contactId = s.contactId"
+ " WHERE groupId IS NULL";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
List<PrivateMessageHeader> headers =
new ArrayList<PrivateMessageHeader>();
while(rs.next()) {
MessageId id = new MessageId(rs.getBytes(1));
byte[] b = rs.getBytes(2);
MessageId parent = b == null ? null : new MessageId(b);
String contentType = rs.getString(3);
String subject = rs.getString(4);
long timestamp = rs.getLong(5);
ContactId contactId = new ContactId(rs.getInt(6));
boolean read = rs.getBoolean(7);
boolean starred = rs.getBoolean(8);
boolean seen = rs.getBoolean(9);
headers.add(new PrivateMessageHeader(id, parent, contentType,
subject, timestamp, read, starred, contactId, seen));
}
rs.close();
ps.close();
return Collections.unmodifiableList(headers);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Collection<PrivateMessageHeader> getPrivateMessageHeaders(
Connection txn, ContactId c) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT m.messageId, parentId, contentType, subject,"
+ " timestamp, read, starred, seen"
+ " FROM messages AS m"
+ " JOIN statuses AS s"
+ " ON m.messageId = s.messageId"
+ " AND m.contactId = s.contactId"
+ " WHERE m.contactId = ? AND groupId IS NULL";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
rs = ps.executeQuery();
List<PrivateMessageHeader> headers =
new ArrayList<PrivateMessageHeader>();
while(rs.next()) {
MessageId id = new MessageId(rs.getBytes(1));
byte[] b = rs.getBytes(2);
MessageId parent = b == null ? null : new MessageId(b);
String contentType = rs.getString(3);
String subject = rs.getString(4);
long timestamp = rs.getLong(5);
boolean read = rs.getBoolean(6);
boolean starred = rs.getBoolean(7);
boolean seen = rs.getBoolean(8);
headers.add(new PrivateMessageHeader(id, parent, contentType,
subject, timestamp, read, starred, c, seen));
}
rs.close();
ps.close();
return Collections.unmodifiableList(headers);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Collection<MessageId> getMessagesByAuthor(Connection txn, AuthorId a)
throws DbException {
PreparedStatement ps = null;
@@ -1680,6 +1699,86 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public Collection<PrivateMessageHeader> getPrivateMessageHeaders(
Connection txn) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT m.messageId, parentId, contentType, subject,"
+ " timestamp, m.contactId, read, starred, seen"
+ " FROM messages AS m"
+ " JOIN statuses AS s"
+ " ON m.messageId = s.messageId"
+ " AND m.contactId = s.contactId"
+ " WHERE groupId IS NULL";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
List<PrivateMessageHeader> headers =
new ArrayList<PrivateMessageHeader>();
while(rs.next()) {
MessageId id = new MessageId(rs.getBytes(1));
byte[] b = rs.getBytes(2);
MessageId parent = b == null ? null : new MessageId(b);
String contentType = rs.getString(3);
String subject = rs.getString(4);
long timestamp = rs.getLong(5);
ContactId contactId = new ContactId(rs.getInt(6));
boolean read = rs.getBoolean(7);
boolean starred = rs.getBoolean(8);
boolean seen = rs.getBoolean(9);
headers.add(new PrivateMessageHeader(id, parent, contentType,
subject, timestamp, read, starred, contactId, seen));
}
rs.close();
ps.close();
return Collections.unmodifiableList(headers);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Collection<PrivateMessageHeader> getPrivateMessageHeaders(
Connection txn, ContactId c) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT m.messageId, parentId, contentType, subject,"
+ " timestamp, read, starred, seen"
+ " FROM messages AS m"
+ " JOIN statuses AS s"
+ " ON m.messageId = s.messageId"
+ " AND m.contactId = s.contactId"
+ " WHERE m.contactId = ? AND groupId IS NULL";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
rs = ps.executeQuery();
List<PrivateMessageHeader> headers =
new ArrayList<PrivateMessageHeader>();
while(rs.next()) {
MessageId id = new MessageId(rs.getBytes(1));
byte[] b = rs.getBytes(2);
MessageId parent = b == null ? null : new MessageId(b);
String contentType = rs.getString(3);
String subject = rs.getString(4);
long timestamp = rs.getLong(5);
boolean read = rs.getBoolean(6);
boolean starred = rs.getBoolean(7);
boolean seen = rs.getBoolean(8);
headers.add(new PrivateMessageHeader(id, parent, contentType,
subject, timestamp, read, starred, c, seen));
}
rs.close();
ps.close();
return Collections.unmodifiableList(headers);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Rating getRating(Connection txn, AuthorId a) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
@@ -1834,6 +1933,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if(!id.equals(lastId)) {
p = new TransportProperties();
properties.put(id, p);
lastId = id;
}
p.put(key, value);
}
@@ -1934,9 +2034,8 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT e.contactId, e.transportId, epoch,"
+ " clockDiff, latency, alice, period, secret, outgoing,"
+ " centre, bitmap"
String sql = "SELECT e.contactId, e.transportId, epoch, alice,"
+ " period, secret, outgoing, centre, bitmap"
+ " FROM endpoints AS e"
+ " JOIN secrets AS s"
+ " ON e.contactId = s.contactId"
@@ -1948,16 +2047,14 @@ abstract class JdbcDatabase implements Database<Connection> {
ContactId c = new ContactId(rs.getInt(1));
TransportId t = new TransportId(rs.getBytes(2));
long epoch = rs.getLong(3);
long clockDiff = rs.getLong(4);
long latency = rs.getLong(5);
boolean alice = rs.getBoolean(6);
long period = rs.getLong(7);
byte[] secret = rs.getBytes(8);
long outgoing = rs.getLong(9);
long centre = rs.getLong(10);
byte[] bitmap = rs.getBytes(11);
secrets.add(new TemporarySecret(c, t, epoch, clockDiff, latency,
alice, period, secret, outgoing, centre, bitmap));
boolean alice = rs.getBoolean(4);
long period = rs.getLong(5);
byte[] secret = rs.getBytes(6);
long outgoing = rs.getLong(7);
long centre = rs.getLong(8);
byte[] bitmap = rs.getBytes(9);
secrets.add(new TemporarySecret(c, t, epoch, alice, period,
secret, outgoing, centre, bitmap));
}
rs.close();
ps.close();
@@ -2265,11 +2362,11 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setBytes(2, a.getId().getBytes());
ps.addBatch();
}
int[] affectedBatch = ps.executeBatch();
if(affectedBatch.length != acks.size())
int[] batchAffected = ps.executeBatch();
if(batchAffected.length != acks.size())
throw new DbStateException();
for(int i = 0; i < affectedBatch.length; i++) {
if(affectedBatch[i] < 1) throw new DbStateException();
for(int i = 0; i < batchAffected.length; i++) {
if(batchAffected[i] < 1) throw new DbStateException();
}
ps.close();
return Collections.unmodifiableList(acks);
@@ -2280,6 +2377,29 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public Map<TransportId, Long> getTransportLatencies(Connection txn)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT transportId, maxLatency FROM transports";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
Map<TransportId, Long> latencies = new HashMap<TransportId, Long>();
while(rs.next()){
TransportId id = new TransportId(rs.getBytes(1));
latencies.put(id, rs.getLong(2));
}
rs.close();
ps.close();
return Collections.unmodifiableMap(latencies);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Collection<TransportUpdate> getTransportUpdates(Connection txn,
ContactId c, long maxLatency) throws DbException {
long now = clock.currentTimeMillis();
@@ -2642,11 +2762,11 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setInt(2, c);
ps.addBatch();
}
int[] affectedBatch = ps.executeBatch();
if(affectedBatch.length != visible.size())
int[] batchAffected = ps.executeBatch();
if(batchAffected.length != visible.size())
throw new DbStateException();
for(int i = 0; i < affectedBatch.length; i++) {
if(affectedBatch[i] != 1) throw new DbStateException();
for(int i = 0; i < batchAffected.length; i++) {
if(batchAffected[i] != 1) throw new DbStateException();
}
ps.close();
} catch(SQLException e) {
@@ -2891,6 +3011,45 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public void setRemoteProperties(Connection txn, ContactId c,
Map<TransportId, TransportProperties> p) throws DbException {
PreparedStatement ps = null;
try {
// Delete the existing properties, if any
String sql = "DELETE FROM contactTransportProperties"
+ " WHERE contactId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.executeUpdate();
ps.close();
// Store the new properties
sql = "INSERT INTO contactTransportProperties"
+ " (contactId, transportId, key, value)"
+ " VALUES (?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
int batchSize = 0;
for(Entry<TransportId, TransportProperties> e : p.entrySet()) {
ps.setBytes(2, e.getKey().getBytes());
for(Entry<String, String> e1 : e.getValue().entrySet()) {
ps.setString(3, e1.getKey());
ps.setString(4, e1.getValue());
ps.addBatch();
batchSize++;
}
}
int[] batchAffected = ps.executeBatch();
if(batchAffected.length != batchSize) throw new DbStateException();
for(int i = 0; i < batchAffected.length; i++) {
if(batchAffected[i] != 1) throw new DbStateException();
}
ps.close();
} catch(SQLException e) {
tryToClose(ps);
throw new DbException(e);
}
}
public void setRemoteProperties(Connection txn, ContactId c, TransportId t,
TransportProperties p, long version) throws DbException {
PreparedStatement ps = null;
@@ -3151,11 +3310,11 @@ abstract class JdbcDatabase implements Database<Connection> {
else ps.setNull(4, BINARY);
ps.addBatch();
}
int[] affectedBatch = ps.executeBatch();
if(affectedBatch.length != subs.size())
int[] batchAffected = ps.executeBatch();
if(batchAffected.length != subs.size())
throw new DbStateException();
for(int i = 0; i < affectedBatch.length; i++) {
if(affectedBatch[i] != 1) throw new DbStateException();
for(int i = 0; i < batchAffected.length; i++) {
if(batchAffected[i] != 1) throw new DbStateException();
}
ps.close();
} catch(SQLException e) {

View File

@@ -8,16 +8,30 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.util.Map;
import java.util.logging.Logger;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorFactory;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.KeyManager;
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.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.api.transport.ConnectionReader;
import net.sf.briar.api.transport.ConnectionReaderFactory;
import net.sf.briar.api.transport.ConnectionWriter;
import net.sf.briar.api.transport.ConnectionWriterFactory;
/** A connection thread for the peer being Alice in the invitation protocol. */
class AliceConnector extends Connector {
@@ -25,11 +39,17 @@ class AliceConnector extends Connector {
private static final Logger LOG =
Logger.getLogger(AliceConnector.class.getName());
AliceConnector(CryptoComponent crypto, ReaderFactory readerFactory,
WriterFactory writerFactory, Clock clock, ConnectorGroup group,
DuplexPlugin plugin, int localCode, int remoteCode) {
super(crypto, readerFactory, writerFactory, clock, group, plugin,
crypto.getPseudoRandom(localCode, remoteCode));
AliceConnector(CryptoComponent crypto, DatabaseComponent db,
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, KeyManager keyManager, Clock clock,
ConnectorGroup group, DuplexPlugin plugin, LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps,
PseudoRandom random) {
super(crypto, db, readerFactory, writerFactory, connectionReaderFactory,
connectionWriterFactory, authorFactory, keyManager, clock,
group, plugin, localAuthor, localProps, random);
}
@Override
@@ -65,7 +85,7 @@ class AliceConnector extends Connector {
byte[] hash = receivePublicKeyHash(r);
sendPublicKey(w);
byte[] key = receivePublicKey(r);
secret = deriveSharedSecret(hash, key, true);
secret = deriveMasterSecret(hash, key, true);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
@@ -76,9 +96,10 @@ class AliceConnector extends Connector {
return;
}
// The key agreement succeeded - derive the confirmation codes
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " succeeded");
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
int[] codes = crypto.deriveConfirmationCodes(secret);
group.connectionSucceeded(codes[0], codes[1]);
int aliceCode = codes[0], bobCode = codes[1];
group.connectionSucceeded(aliceCode, bobCode);
// Exchange confirmation results
try {
sendConfirmation(w);
@@ -97,7 +118,59 @@ class AliceConnector extends Connector {
Thread.currentThread().interrupt();
return;
}
// That's all, folks!
// The timestamp is taken after exhanging confirmation results
long localTimestamp = clock.currentTimeMillis();
// Confirmation succeeded - upgrade to a secure connection
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " confirmation succeeded");
ConnectionReader connectionReader =
connectionReaderFactory.createInvitationConnectionReader(in,
secret, false);
r = readerFactory.createReader(connectionReader.getInputStream());
ConnectionWriter connectionWriter =
connectionWriterFactory.createInvitationConnectionWriter(out,
secret, true);
w = writerFactory.createWriter(connectionWriter.getOutputStream());
// Derive the invitation nonces
byte[][] nonces = crypto.deriveInvitationNonces(secret);
byte[] aliceNonce = nonces[0], bobNonce = nonces[1];
// Exchange pseudonyms, signed nonces, timestamps and transports
Author remoteAuthor;
long remoteTimestamp;
Map<TransportId, TransportProperties> remoteProps;
try {
sendPseudonym(w, aliceNonce);
sendTimestamp(w, localTimestamp);
sendTransportProperties(w);
remoteAuthor = receivePseudonym(r, bobNonce);
remoteTimestamp = receiveTimestamp(r);
remoteProps = receiveTransportProperties(r);
} catch(GeneralSecurityException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
group.pseudonymExchangeFailed();
return;
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
group.pseudonymExchangeFailed();
return;
}
// The epoch is the minimum of the peers' timestamps
long epoch = Math.min(localTimestamp, remoteTimestamp);
// Add the contact and store the transports
try {
addContact(remoteAuthor, remoteProps, secret, epoch, true);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
group.pseudonymExchangeFailed();
return;
}
// Pseudonym exchange succeeded
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " pseudonym exchange succeeded");
group.pseudonymExchangeSucceeded(remoteAuthor);
tryToClose(conn, false);
}
}

View File

@@ -8,16 +8,30 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.util.Map;
import java.util.logging.Logger;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorFactory;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.KeyManager;
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.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.api.transport.ConnectionReader;
import net.sf.briar.api.transport.ConnectionReaderFactory;
import net.sf.briar.api.transport.ConnectionWriter;
import net.sf.briar.api.transport.ConnectionWriterFactory;
/** A connection thread for the peer being Bob in the invitation protocol. */
class BobConnector extends Connector {
@@ -25,11 +39,17 @@ class BobConnector extends Connector {
private static final Logger LOG =
Logger.getLogger(BobConnector.class.getName());
BobConnector(CryptoComponent crypto, ReaderFactory readerFactory,
WriterFactory writerFactory, Clock clock, ConnectorGroup group,
DuplexPlugin plugin, int localCode, int remoteCode) {
super(crypto, readerFactory, writerFactory, clock, group, plugin,
crypto.getPseudoRandom(remoteCode, localCode));
BobConnector(CryptoComponent crypto, DatabaseComponent db,
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, KeyManager keyManager, Clock clock,
ConnectorGroup group, DuplexPlugin plugin, LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps,
PseudoRandom random) {
super(crypto, db, readerFactory, writerFactory, connectionReaderFactory,
connectionWriterFactory, authorFactory, keyManager, clock,
group, plugin, localAuthor, localProps, random);
}
@Override
@@ -65,7 +85,7 @@ class BobConnector extends Connector {
sendPublicKeyHash(w);
byte[] key = receivePublicKey(r);
sendPublicKey(w);
secret = deriveSharedSecret(hash, key, false);
secret = deriveMasterSecret(hash, key, false);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
@@ -76,14 +96,15 @@ class BobConnector extends Connector {
return;
}
// The key agreement succeeded - derive the confirmation codes
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " succeeded");
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
int[] codes = crypto.deriveConfirmationCodes(secret);
group.connectionSucceeded(codes[1], codes[0]);
int aliceCode = codes[0], bobCode = codes[1];
group.connectionSucceeded(bobCode, aliceCode);
// Exchange confirmation results
try {
sendConfirmation(w);
if(receiveConfirmation(r)) group.remoteConfirmationSucceeded();
else group.remoteConfirmationFailed();
sendConfirmation(w);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
@@ -97,7 +118,59 @@ class BobConnector extends Connector {
Thread.currentThread().interrupt();
return;
}
// That's all, folks!
// The timestamp is taken after exhanging confirmation results
long localTimestamp = clock.currentTimeMillis();
// Confirmation succeeded - upgrade to a secure connection
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " confirmation succeeded");
ConnectionReader connectionReader =
connectionReaderFactory.createInvitationConnectionReader(in,
secret, true);
r = readerFactory.createReader(connectionReader.getInputStream());
ConnectionWriter connectionWriter =
connectionWriterFactory.createInvitationConnectionWriter(out,
secret, false);
w = writerFactory.createWriter(connectionWriter.getOutputStream());
// Derive the nonces
byte[][] nonces = crypto.deriveInvitationNonces(secret);
byte[] aliceNonce = nonces[0], bobNonce = nonces[1];
// Exchange pseudonyms, signed nonces, timestamps and transports
Author remoteAuthor;
long remoteTimestamp;
Map<TransportId, TransportProperties> remoteProps;
try {
remoteAuthor = receivePseudonym(r, aliceNonce);
remoteTimestamp = receiveTimestamp(r);
remoteProps = receiveTransportProperties(r);
sendPseudonym(w, bobNonce);
sendTimestamp(w, localTimestamp);
sendTransportProperties(w);
} catch(GeneralSecurityException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
group.pseudonymExchangeFailed();
return;
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
group.pseudonymExchangeFailed();
return;
}
// The epoch is the minimum of the peers' timestamps
long epoch = Math.min(localTimestamp, remoteTimestamp);
// Add the contact and store the transports
try {
addContact(remoteAuthor, remoteProps, secret, epoch, true);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
group.pseudonymExchangeFailed();
return;
}
// Pseudonym exchange succeeded
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " pseudonym exchange succeeded");
group.pseudonymExchangeSucceeded(remoteAuthor);
tryToClose(conn, false);
}
}

View File

@@ -2,28 +2,53 @@ package net.sf.briar.invitation;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_AUTHOR_NAME_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_PROPERTY_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_PUBLIC_KEY_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_SIGNATURE_LENGTH;
import static net.sf.briar.api.plugins.InvitationConstants.CONNECTION_TIMEOUT;
import static net.sf.briar.api.plugins.InvitationConstants.HASH_LENGTH;
import static net.sf.briar.api.plugins.InvitationConstants.MAX_PUBLIC_KEY_LENGTH;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.Signature;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorFactory;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.UniqueId;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.KeyManager;
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.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.NoSuchTransportException;
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.api.transport.ConnectionReaderFactory;
import net.sf.briar.api.transport.ConnectionWriterFactory;
import net.sf.briar.api.transport.Endpoint;
abstract class Connector extends Thread {
@@ -31,11 +56,18 @@ abstract class Connector extends Thread {
Logger.getLogger(Connector.class.getName());
protected final CryptoComponent crypto;
protected final DatabaseComponent db;
protected final ReaderFactory readerFactory;
protected final WriterFactory writerFactory;
protected final ConnectionReaderFactory connectionReaderFactory;
protected final ConnectionWriterFactory connectionWriterFactory;
protected final AuthorFactory authorFactory;
protected final KeyManager keyManager;
protected final Clock clock;
protected final ConnectorGroup group;
protected final DuplexPlugin plugin;
protected final LocalAuthor localAuthor;
protected final Map<TransportId, TransportProperties> localProps;
protected final PseudoRandom random;
protected final String pluginName;
@@ -43,16 +75,28 @@ abstract class Connector extends Thread {
private final KeyParser keyParser;
private final MessageDigest messageDigest;
Connector(CryptoComponent crypto, ReaderFactory readerFactory,
WriterFactory writerFactory, Clock clock, ConnectorGroup group,
DuplexPlugin plugin, PseudoRandom random) {
Connector(CryptoComponent crypto, DatabaseComponent db,
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, KeyManager keyManager, Clock clock,
ConnectorGroup group, DuplexPlugin plugin, LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps,
PseudoRandom random) {
super("Connector");
this.crypto = crypto;
this.db = db;
this.readerFactory = readerFactory;
this.writerFactory = writerFactory;
this.connectionReaderFactory = connectionReaderFactory;
this.connectionWriterFactory = connectionWriterFactory;
this.authorFactory = authorFactory;
this.keyManager = keyManager;
this.clock = clock;
this.group = group;
this.plugin = plugin;
this.localAuthor = localAuthor;
this.localProps = localProps;
this.random = random;
pluginName = plugin.getClass().getName();
keyPair = crypto.generateAgreementKeyPair();
@@ -87,16 +131,6 @@ abstract class Connector extends Thread {
}
}
protected void tryToClose(DuplexTransportConnection conn,
boolean exception) {
try {
if(LOG.isLoggable(INFO)) LOG.info("Closing connection");
conn.dispose(exception, true);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
protected void sendPublicKeyHash(Writer w) throws IOException {
w.writeBytes(messageDigest.digest(keyPair.getPublic().getEncoded()));
w.flush();
@@ -116,18 +150,15 @@ abstract class Connector extends Thread {
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent key");
}
protected byte[] receivePublicKey(Reader r) throws IOException {
protected byte[] receivePublicKey(Reader r) throws GeneralSecurityException,
IOException {
byte[] b = r.readBytes(MAX_PUBLIC_KEY_LENGTH);
try {
keyParser.parsePublicKey(b);
} catch(GeneralSecurityException e) {
throw new FormatException();
}
keyParser.parsePublicKey(b);
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received key");
return b;
}
protected byte[] deriveSharedSecret(byte[] hash, byte[] key, boolean alice)
protected byte[] deriveMasterSecret(byte[] hash, byte[] key, boolean alice)
throws GeneralSecurityException {
// Check that the hash matches the key
if(!Arrays.equals(hash, messageDigest.digest(key))) {
@@ -135,16 +166,16 @@ abstract class Connector extends Thread {
LOG.info(pluginName + " hash does not match key");
throw new GeneralSecurityException();
}
// Derive the shared secret
return crypto.deriveInitialSecret(key, keyPair, alice);
// Derive the master secret
return crypto.deriveMasterSecret(key, keyPair, alice);
}
protected void sendConfirmation(Writer w) throws IOException,
InterruptedException {
boolean matched = group.waitForLocalConfirmationResult();
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent confirmation");
w.writeBoolean(matched);
w.flush();
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent confirmation");
}
protected boolean receiveConfirmation(Reader r) throws IOException {
@@ -153,4 +184,137 @@ abstract class Connector extends Thread {
LOG.info(pluginName + " received confirmation");
return matched;
}
protected void sendPseudonym(Writer w, byte[] nonce)
throws GeneralSecurityException, IOException {
// Sign the nonce
Signature signature = crypto.getSignature();
KeyParser keyParser = crypto.getSignatureKeyParser();
byte[] privateKey = localAuthor.getPrivateKey();
signature.initSign(keyParser.parsePrivateKey(privateKey));
signature.update(nonce);
byte[] sig = signature.sign();
// Write the name, public key and signature
w.writeString(localAuthor.getName());
w.writeBytes(localAuthor.getPublicKey());
w.writeBytes(sig);
w.flush();
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent pseudonym");
}
protected Author receivePseudonym(Reader r, byte[] nonce)
throws GeneralSecurityException, IOException {
// Read the name, public key and signature
String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
byte[] publicKey = r.readBytes(MAX_PUBLIC_KEY_LENGTH);
byte[] sig = r.readBytes(MAX_SIGNATURE_LENGTH);
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received pseudonym");
// Verify the signature
Signature signature = crypto.getSignature();
KeyParser keyParser = crypto.getSignatureKeyParser();
signature.initVerify(keyParser.parsePublicKey(publicKey));
signature.update(nonce);
if(!signature.verify(sig)) {
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " invalid signature");
throw new GeneralSecurityException();
}
return authorFactory.createAuthor(name, publicKey);
}
protected void sendTimestamp(Writer w, long timestamp) throws IOException {
w.writeInt64(timestamp);
w.flush();
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent timestamp");
}
protected long receiveTimestamp(Reader r) throws IOException {
long timestamp = r.readInt64();
if(timestamp < 0) throw new FormatException();
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received timestamp");
return timestamp;
}
protected void sendTransportProperties(Writer w) throws IOException {
w.writeListStart();
for(Entry<TransportId, TransportProperties> e : localProps.entrySet()) {
w.writeBytes(e.getKey().getBytes());
w.writeMap(e.getValue());
}
w.writeListEnd();
w.flush();
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " sent transport properties");
}
protected Map<TransportId, TransportProperties> receiveTransportProperties(
Reader r) throws IOException {
Map<TransportId, TransportProperties> remoteProps =
new HashMap<TransportId, TransportProperties>();
r.readListStart();
while(!r.hasListEnd()) {
byte[] b = r.readBytes(UniqueId.LENGTH);
if(b.length != UniqueId.LENGTH) throw new FormatException();
TransportId id = new TransportId(b);
r.setMaxStringLength(MAX_PROPERTY_LENGTH);
Map<String, String> p = r.readMap(String.class, String.class);
r.resetMaxStringLength();
remoteProps.put(id, new TransportProperties(p));
}
r.readListEnd();
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " received transport properties");
return remoteProps;
}
protected void addContact(Author remoteAuthor,
Map<TransportId, TransportProperties> remoteProps, byte[] secret,
long epoch, boolean alice) throws DbException {
// Add the contact to the database
ContactId c = db.addContact(remoteAuthor, localAuthor.getId());
// Store the remote transport properties
db.setRemoteProperties(c, remoteProps);
// Create an endpoint for each transport shared with the contact
List<TransportId> ids = new ArrayList<TransportId>();
for(TransportId id : localProps.keySet())
if(remoteProps.containsKey(id)) ids.add(id);
// Assign indices to the transports in a deterministic way
Collections.sort(ids, TransportIdComparator.INSTANCE);
int size = ids.size();
for(int i = 0; i < size; i++) {
Endpoint ep = new Endpoint(c, ids.get(i), epoch, alice);
try {
db.addEndpoint(ep);
} catch(NoSuchTransportException e) {
continue;
}
keyManager.endpointAdded(ep, crypto.deriveInitialSecret(secret, i));
}
}
protected void tryToClose(DuplexTransportConnection conn,
boolean exception) {
try {
if(LOG.isLoggable(INFO)) LOG.info("Closing connection");
conn.dispose(exception, true);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private static class TransportIdComparator
implements Comparator<TransportId> {
private static final TransportIdComparator INSTANCE =
new TransportIdComparator();
public int compare(TransportId t1, TransportId t2) {
byte[] b1 = t1.getBytes(), b2 = t2.getBytes();
for(int i = 0; i < UniqueId.LENGTH; i++) {
if((b1[i] & 0xff) < (b2[i] & 0xff)) return -1;
if((b1[i] & 0xff) > (b2[i] & 0xff)) return 1;
}
return 0;
}
}
}

View File

@@ -6,13 +6,24 @@ import static net.sf.briar.api.plugins.InvitationConstants.CONFIRMATION_TIMEOUT;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorFactory;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.KeyManager;
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.invitation.InvitationListener;
import net.sf.briar.api.invitation.InvitationState;
import net.sf.briar.api.invitation.InvitationTask;
@@ -20,6 +31,8 @@ import net.sf.briar.api.plugins.PluginManager;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.serial.ReaderFactory;
import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.api.transport.ConnectionReaderFactory;
import net.sf.briar.api.transport.ConnectionWriterFactory;
/** A task consisting of one or more parallel connection attempts. */
class ConnectorGroup extends Thread implements InvitationTask {
@@ -28,10 +41,16 @@ class ConnectorGroup extends Thread implements InvitationTask {
Logger.getLogger(ConnectorGroup.class.getName());
private final CryptoComponent crypto;
private final DatabaseComponent db;
private final ReaderFactory readerFactory;
private final WriterFactory writerFactory;
private final ConnectionReaderFactory connectionReaderFactory;
private final ConnectionWriterFactory connectionWriterFactory;
private final AuthorFactory authorFactory;
private final KeyManager keyManager;
private final Clock clock;
private final PluginManager pluginManager;
private final AuthorId localAuthorId;
private final int localInvitationCode, remoteInvitationCode;
private final Collection<InvitationListener> listeners;
private final AtomicBoolean connected;
@@ -47,17 +66,27 @@ class ConnectorGroup extends Thread implements InvitationTask {
private boolean connectionFailed = false;
private boolean localCompared = false, remoteCompared = false;
private boolean localMatched = false, remoteMatched = false;
private String remoteName = null;
ConnectorGroup(CryptoComponent crypto, ReaderFactory readerFactory,
WriterFactory writerFactory, Clock clock,
PluginManager pluginManager, int localInvitationCode,
int remoteInvitationCode) {
ConnectorGroup(CryptoComponent crypto, DatabaseComponent db,
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, KeyManager keyManager, Clock clock,
PluginManager pluginManager, AuthorId localAuthorId,
int localInvitationCode, int remoteInvitationCode) {
super("ConnectorGroup");
this.crypto = crypto;
this.db = db;
this.readerFactory = readerFactory;
this.writerFactory = writerFactory;
this.connectionReaderFactory = connectionReaderFactory;
this.connectionWriterFactory = connectionWriterFactory;
this.authorFactory = authorFactory;
this.keyManager = keyManager;
this.clock = clock;
this.pluginManager = pluginManager;
this.localAuthorId = localAuthorId;
this.localInvitationCode = localInvitationCode;
this.remoteInvitationCode = remoteInvitationCode;
listeners = new CopyOnWriteArrayList<InvitationListener>();
@@ -68,8 +97,9 @@ class ConnectorGroup extends Thread implements InvitationTask {
public synchronized InvitationState addListener(InvitationListener l) {
listeners.add(l);
return new InvitationState(localInvitationCode, remoteInvitationCode,
localConfirmationCode, remoteConfirmationCode, connectionFailed,
localCompared, remoteCompared, localMatched, remoteMatched);
localConfirmationCode, remoteConfirmationCode,
connectionFailed, localCompared, remoteCompared, localMatched,
remoteMatched, remoteName);
}
public void removeListener(InvitationListener l) {
@@ -82,22 +112,34 @@ class ConnectorGroup extends Thread implements InvitationTask {
@Override
public void run() {
LocalAuthor localAuthor;
Map<TransportId, TransportProperties> localProps;
// Load the local pseudonym and transport properties
try {
localAuthor = db.getLocalAuthor(localAuthorId);
localProps = db.getLocalProperties();
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
synchronized(this) {
connectionFailed = true;
}
for(InvitationListener l : listeners) l.connectionFailed();
return;
}
// Start the connection threads
Collection<Connector> connectors = new ArrayList<Connector>();
// Alice is the party with the smaller invitation code
if(localInvitationCode < remoteInvitationCode) {
for(DuplexPlugin plugin : pluginManager.getInvitationPlugins()) {
Connector c = new AliceConnector(crypto, readerFactory,
writerFactory, clock, this, plugin, localInvitationCode,
remoteInvitationCode);
Connector c = createAliceConnector(plugin, localAuthor,
localProps);
connectors.add(c);
c.start();
}
} else {
for(DuplexPlugin plugin: pluginManager.getInvitationPlugins()) {
Connector c = (new BobConnector(crypto, readerFactory,
writerFactory, clock, this, plugin, localInvitationCode,
remoteInvitationCode));
Connector c = createBobConnector(plugin, localAuthor,
localProps);
connectors.add(c);
c.start();
}
@@ -118,6 +160,28 @@ class ConnectorGroup extends Thread implements InvitationTask {
}
}
private Connector createAliceConnector(DuplexPlugin plugin,
LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps) {
PseudoRandom random = crypto.getPseudoRandom(localInvitationCode,
remoteInvitationCode);
return new AliceConnector(crypto, db, readerFactory, writerFactory,
connectionReaderFactory, connectionWriterFactory, authorFactory,
keyManager, clock, this, plugin, localAuthor, localProps,
random);
}
private Connector createBobConnector(DuplexPlugin plugin,
LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps) {
PseudoRandom random = crypto.getPseudoRandom(remoteInvitationCode,
localInvitationCode);
return new BobConnector(crypto, db, readerFactory, writerFactory,
connectionReaderFactory, connectionWriterFactory, authorFactory,
keyManager, clock, this, plugin, localAuthor, localProps,
random);
}
public void localConfirmationSucceeded() {
synchronized(this) {
localCompared = true;
@@ -167,4 +231,17 @@ class ConnectorGroup extends Thread implements InvitationTask {
return localMatched;
}
}
void pseudonymExchangeSucceeded(Author remoteAuthor) {
String name = remoteAuthor.getName();
synchronized(this) {
remoteName = name;
}
for(InvitationListener l : listeners)
l.pseudonymExchangeSucceeded(name);
}
void pseudonymExchangeFailed() {
for(InvitationListener l : listeners) l.pseudonymExchangeFailed();
}
}

View File

@@ -1,36 +1,58 @@
package net.sf.briar.invitation;
import net.sf.briar.api.AuthorFactory;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.KeyManager;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.invitation.InvitationTask;
import net.sf.briar.api.invitation.InvitationTaskFactory;
import net.sf.briar.api.plugins.PluginManager;
import net.sf.briar.api.serial.ReaderFactory;
import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.api.transport.ConnectionReaderFactory;
import net.sf.briar.api.transport.ConnectionWriterFactory;
import com.google.inject.Inject;
class InvitationTaskFactoryImpl implements InvitationTaskFactory {
private final CryptoComponent crypto;
private final DatabaseComponent db;
private final ReaderFactory readerFactory;
private final WriterFactory writerFactory;
private final ConnectionReaderFactory connectionReaderFactory;
private final ConnectionWriterFactory connectionWriterFactory;
private final AuthorFactory authorFactory;
private final KeyManager keyManager;
private final Clock clock;
private final PluginManager pluginManager;
@Inject
InvitationTaskFactoryImpl(CryptoComponent crypto,
InvitationTaskFactoryImpl(CryptoComponent crypto, DatabaseComponent db,
ReaderFactory readerFactory, WriterFactory writerFactory,
Clock clock, PluginManager pluginManager) {
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, KeyManager keyManager, Clock clock,
PluginManager pluginManager) {
this.crypto = crypto;
this.db = db;
this.readerFactory = readerFactory;
this.writerFactory = writerFactory;
this.connectionReaderFactory = connectionReaderFactory;
this.connectionWriterFactory = connectionWriterFactory;
this.authorFactory = authorFactory;
this.keyManager = keyManager;
this.clock = clock;
this.pluginManager = pluginManager;
}
public InvitationTask createTask(int localCode, int remoteCode) {
return new ConnectorGroup(crypto, readerFactory, writerFactory, clock,
pluginManager, localCode, remoteCode);
public InvitationTask createTask(AuthorId localAuthorId, int localCode,
int remoteCode) {
return new ConnectorGroup(crypto, db, readerFactory, writerFactory,
connectionReaderFactory, connectionWriterFactory,
authorFactory, keyManager, clock, pluginManager, localAuthorId,
localCode, remoteCode);
}
}

View File

@@ -5,11 +5,11 @@ import static net.sf.briar.api.messaging.Types.AUTHOR;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorFactory;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.AuthorFactory;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.serial.Writer;
import net.sf.briar.api.serial.WriterFactory;

View File

@@ -6,10 +6,10 @@ import static net.sf.briar.api.messaging.Types.AUTHOR;
import java.io.IOException;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.serial.DigestingConsumer;
import net.sf.briar.api.serial.Reader;
import net.sf.briar.api.serial.StructReader;

View File

@@ -20,10 +20,10 @@ import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.Signature;
import net.sf.briar.api.Author;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageFactory;

View File

@@ -1,7 +1,7 @@
package net.sf.briar.messaging;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.Author;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId;

View File

@@ -13,11 +13,11 @@ import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import net.sf.briar.api.Author;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.UniqueId;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.UniqueId;
import net.sf.briar.api.messaging.UnverifiedMessage;
import net.sf.briar.api.serial.CopyingConsumer;
import net.sf.briar.api.serial.CountingConsumer;

View File

@@ -4,10 +4,10 @@ import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.security.Signature;
import net.sf.briar.api.Author;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.KeyParser;
import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId;

View File

@@ -1,8 +1,8 @@
package net.sf.briar.messaging;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorFactory;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.AuthorFactory;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupFactory;
import net.sf.briar.api.messaging.MessageFactory;

View File

@@ -24,7 +24,9 @@ import java.util.Map;
import net.sf.briar.api.Bytes;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.UniqueId;
import net.sf.briar.api.messaging.Ack;
import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.Offer;
@@ -35,9 +37,7 @@ import net.sf.briar.api.messaging.RetentionUpdate;
import net.sf.briar.api.messaging.SubscriptionAck;
import net.sf.briar.api.messaging.SubscriptionUpdate;
import net.sf.briar.api.messaging.TransportAck;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.TransportUpdate;
import net.sf.briar.api.messaging.UniqueId;
import net.sf.briar.api.messaging.UnverifiedMessage;
import net.sf.briar.api.serial.Consumer;
import net.sf.briar.api.serial.CountingConsumer;

View File

@@ -2,8 +2,8 @@ package net.sf.briar.messaging.duplex;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.api.Rating.GOOD;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
import static net.sf.briar.api.messaging.Rating.GOOD;
import java.io.IOException;
import java.io.InputStream;
@@ -23,6 +23,7 @@ import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.crypto.CryptoExecutor;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DatabaseExecutor;
@@ -55,7 +56,6 @@ import net.sf.briar.api.messaging.RetentionUpdate;
import net.sf.briar.api.messaging.SubscriptionAck;
import net.sf.briar.api.messaging.SubscriptionUpdate;
import net.sf.briar.api.messaging.TransportAck;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.TransportUpdate;
import net.sf.briar.api.messaging.UnverifiedMessage;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;

View File

@@ -6,6 +6,7 @@ import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.crypto.CryptoExecutor;
import net.sf.briar.api.crypto.KeyManager;
import net.sf.briar.api.db.DatabaseComponent;
@@ -13,7 +14,6 @@ import net.sf.briar.api.db.DatabaseExecutor;
import net.sf.briar.api.messaging.MessageVerifier;
import net.sf.briar.api.messaging.PacketReaderFactory;
import net.sf.briar.api.messaging.PacketWriterFactory;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.duplex.DuplexConnectionFactory;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
import net.sf.briar.api.transport.ConnectionContext;

View File

@@ -10,6 +10,7 @@ import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.crypto.CryptoExecutor;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DatabaseExecutor;
@@ -24,7 +25,6 @@ import net.sf.briar.api.messaging.RetentionUpdate;
import net.sf.briar.api.messaging.SubscriptionAck;
import net.sf.briar.api.messaging.SubscriptionUpdate;
import net.sf.briar.api.messaging.TransportAck;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.TransportUpdate;
import net.sf.briar.api.messaging.UnverifiedMessage;
import net.sf.briar.api.plugins.simplex.SimplexTransportReader;

View File

@@ -10,6 +10,7 @@ import java.util.Collection;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.messaging.Ack;
@@ -20,7 +21,6 @@ import net.sf.briar.api.messaging.RetentionUpdate;
import net.sf.briar.api.messaging.SubscriptionAck;
import net.sf.briar.api.messaging.SubscriptionUpdate;
import net.sf.briar.api.messaging.TransportAck;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.TransportUpdate;
import net.sf.briar.api.plugins.simplex.SimplexTransportWriter;
import net.sf.briar.api.transport.ConnectionContext;

View File

@@ -6,6 +6,7 @@ import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.crypto.CryptoExecutor;
import net.sf.briar.api.crypto.KeyManager;
import net.sf.briar.api.db.DatabaseComponent;
@@ -13,7 +14,6 @@ import net.sf.briar.api.db.DatabaseExecutor;
import net.sf.briar.api.messaging.MessageVerifier;
import net.sf.briar.api.messaging.PacketReaderFactory;
import net.sf.briar.api.messaging.PacketWriterFactory;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.simplex.SimplexConnectionFactory;
import net.sf.briar.api.plugins.simplex.SimplexTransportReader;
import net.sf.briar.api.plugins.simplex.SimplexTransportWriter;

View File

@@ -16,11 +16,11 @@ import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.android.AndroidExecutor;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.Plugin;
import net.sf.briar.api.plugins.PluginCallback;
import net.sf.briar.api.plugins.PluginExecutor;
@@ -88,12 +88,6 @@ class PluginManagerImpl implements PluginManager {
LOG.warning("Duplicate transport ID: " + id);
continue;
}
try {
db.addTransport(id);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
continue;
}
SimplexCallback callback = new SimplexCallback(id);
SimplexPlugin plugin = factory.createPlugin(callback);
if(plugin == null) {
@@ -103,6 +97,12 @@ class PluginManagerImpl implements PluginManager {
}
continue;
}
try {
db.addTransport(id, plugin.getMaxLatency());
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
continue;
}
try {
if(plugin.start()) {
simplexPlugins.add(plugin);
@@ -124,12 +124,6 @@ class PluginManagerImpl implements PluginManager {
LOG.warning("Duplicate transport ID: " + id);
continue;
}
try {
db.addTransport(id);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
continue;
}
DuplexCallback callback = new DuplexCallback(id);
DuplexPlugin plugin = factory.createPlugin(callback);
if(plugin == null) {
@@ -139,6 +133,12 @@ class PluginManagerImpl implements PluginManager {
}
continue;
}
try {
db.addTransport(id, plugin.getMaxLatency());
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
continue;
}
try {
if(plugin.start()) {
duplexPlugins.add(plugin);
@@ -197,10 +197,10 @@ class PluginManagerImpl implements PluginManager {
}
public synchronized Collection<DuplexPlugin> getInvitationPlugins() {
Collection<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
for(DuplexPlugin d : duplexPlugins)
if(d.supportsInvitations()) supported.add(d);
return supported;
return Collections.unmodifiableList(supported);
}
private abstract class PluginCallbackImpl implements PluginCallback {

View File

@@ -26,10 +26,10 @@ import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;

View File

@@ -3,9 +3,9 @@ package net.sf.briar.plugins.bluetooth;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.clock.SystemClock;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;

View File

@@ -24,10 +24,10 @@ import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.android.AndroidExecutor;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;

View File

@@ -3,8 +3,8 @@ package net.sf.briar.plugins.droidtooth;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.android.AndroidExecutor;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;

View File

@@ -12,7 +12,7 @@ import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.simplex.SimplexPluginCallback;
import net.sf.briar.util.StringUtils;

View File

@@ -2,7 +2,7 @@ package net.sf.briar.plugins.file;
import java.util.concurrent.Executor;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.simplex.SimplexPlugin;
import net.sf.briar.api.plugins.simplex.SimplexPluginCallback;

View File

@@ -17,9 +17,9 @@ import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;

View File

@@ -2,7 +2,7 @@ package net.sf.briar.plugins.modem;
import java.util.concurrent.Executor;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;

View File

@@ -21,10 +21,10 @@ import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;

View File

@@ -2,9 +2,9 @@ package net.sf.briar.plugins.tcp;
import java.util.concurrent.Executor;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.clock.SystemClock;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;

View File

@@ -14,9 +14,9 @@ import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;

View File

@@ -2,8 +2,8 @@ package net.sf.briar.plugins.tcp;
import java.util.concurrent.Executor;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.lifecycle.ShutdownManager;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;

View File

@@ -1,6 +1,6 @@
package net.sf.briar.serial;
import net.sf.briar.api.messaging.UniqueId;
import net.sf.briar.api.UniqueId;
import net.sf.briar.api.serial.SerialComponent;
class SerialComponentImpl implements SerialComponent {

View File

@@ -10,8 +10,8 @@ import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.duplex.DuplexConnectionFactory;
import net.sf.briar.api.messaging.simplex.SimplexConnectionFactory;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;

View File

@@ -33,4 +33,12 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
crypto.getFrameCipher(), frameKey, MAX_FRAME_LENGTH);
return new ConnectionReaderImpl(encryption, MAX_FRAME_LENGTH);
}
public ConnectionReader createInvitationConnectionReader(InputStream in,
byte[] secret, boolean alice) {
ErasableKey frameKey = crypto.deriveFrameKey(secret, 0, true, alice);
FrameReader encryption = new IncomingEncryptionLayer(in,
crypto.getFrameCipher(), frameKey, MAX_FRAME_LENGTH);
return new ConnectionReaderImpl(encryption, MAX_FRAME_LENGTH);
}
}

View File

@@ -4,10 +4,10 @@ import java.util.HashMap;
import java.util.Map;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.ConnectionRecogniser;
import net.sf.briar.api.transport.TemporarySecret;

View File

@@ -9,7 +9,7 @@ import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.transport.ConnectionListener;
import net.sf.briar.api.transport.ConnectionRegistry;

View File

@@ -13,7 +13,6 @@ import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.ConnectionWriter;
import net.sf.briar.api.transport.ConnectionWriterFactory;
import com.google.inject.Inject;
class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
@@ -48,4 +47,13 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
}
return new ConnectionWriterImpl(encryption, MAX_FRAME_LENGTH);
}
public ConnectionWriter createInvitationConnectionWriter(OutputStream out,
byte[] secret, boolean alice) {
ErasableKey frameKey = crypto.deriveFrameKey(secret, 0, true, alice);
FrameWriter encryption = new OutgoingEncryptionLayer(out,
Long.MAX_VALUE, crypto.getFrameCipher(), frameKey,
MAX_FRAME_LENGTH);
return new ConnectionWriterImpl(encryption, MAX_FRAME_LENGTH);
}
}

View File

@@ -6,7 +6,6 @@ import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import java.io.IOException;
import java.io.OutputStream;
import net.sf.briar.api.transport.ConnectionWriter;
/**

View File

@@ -1,6 +1,7 @@
package net.sf.briar.transport;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
import java.util.ArrayList;
import java.util.Arrays;
@@ -12,6 +13,7 @@ import java.util.TimerTask;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.clock.Timer;
import net.sf.briar.api.crypto.CryptoComponent;
@@ -21,8 +23,8 @@ import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.event.ContactRemovedEvent;
import net.sf.briar.api.db.event.DatabaseEvent;
import net.sf.briar.api.db.event.DatabaseListener;
import net.sf.briar.api.db.event.TransportAddedEvent;
import net.sf.briar.api.db.event.TransportRemovedEvent;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.ConnectionRecogniser;
import net.sf.briar.api.transport.Endpoint;
@@ -31,6 +33,7 @@ import net.sf.briar.util.ByteUtils;
import com.google.inject.Inject;
// FIXME: Don't make alien calls with a lock held
class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
private static final int MS_BETWEEN_CHECKS = 60 * 1000;
@@ -40,34 +43,38 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
private final CryptoComponent crypto;
private final DatabaseComponent db;
private final ConnectionRecogniser recogniser;
private final ConnectionRecogniser connectionRecogniser;
private final Clock clock;
private final Timer timer;
// Locking: this
// All of the following are locking: this
private final Map<TransportId, Long> maxLatencies;
private final Map<EndpointKey, TemporarySecret> outgoing;
// Locking: this
private final Map<EndpointKey, TemporarySecret> incomingOld;
// Locking: this
private final Map<EndpointKey, TemporarySecret> incomingNew;
@Inject
KeyManagerImpl(CryptoComponent crypto, DatabaseComponent db,
ConnectionRecogniser recogniser, Clock clock, Timer timer) {
ConnectionRecogniser connectionRecogniser, Clock clock,
Timer timer) {
this.crypto = crypto;
this.db = db;
this.recogniser = recogniser;
this.connectionRecogniser = connectionRecogniser;
this.clock = clock;
this.timer = timer;
maxLatencies = new HashMap<TransportId, Long>();
outgoing = new HashMap<EndpointKey, TemporarySecret>();
incomingOld = new HashMap<EndpointKey, TemporarySecret>();
incomingNew = new HashMap<EndpointKey, TemporarySecret>();
}
public synchronized boolean start() {
// Load the temporary secrets and the storage key from the database
db.addListener(this);
// Load the temporary secrets and transport latencies from the database
Collection<TemporarySecret> secrets;
try {
secrets = db.getSecrets();
maxLatencies.putAll(db.getTransportLatencies());
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return false;
@@ -87,129 +94,111 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
}
}
// Pass the current incoming secrets to the recogniser
for(TemporarySecret s : incomingOld.values()) recogniser.addSecret(s);
for(TemporarySecret s : incomingNew.values()) recogniser.addSecret(s);
for(TemporarySecret s : incomingOld.values())
connectionRecogniser.addSecret(s);
for(TemporarySecret s : incomingNew.values())
connectionRecogniser.addSecret(s);
// Schedule periodic key rotation
timer.scheduleAtFixedRate(this, MS_BETWEEN_CHECKS, MS_BETWEEN_CHECKS);
return true;
}
// Assigns secrets to the appropriate maps and returns any dead secrets
// Locking: this
private Collection<TemporarySecret> assignSecretsToMaps(long now,
Collection<TemporarySecret> secrets) {
Collection<TemporarySecret> dead = new ArrayList<TemporarySecret>();
for(TemporarySecret s : secrets) {
// Discard the secret if the transport has been removed
if(!maxLatencies.containsKey(s.getTransportId())) {
ByteUtils.erase(s.getSecret());
continue;
}
EndpointKey k = new EndpointKey(s);
long rotationPeriod = getRotationPeriod(s);
long creationTime = getCreationTime(s);
long activationTime = creationTime + s.getClockDifference();
long activationTime = creationTime + MAX_CLOCK_DIFFERENCE;
long successorCreationTime = creationTime + rotationPeriod;
long deactivationTime = activationTime + rotationPeriod;
long destructionTime = successorCreationTime + rotationPeriod;
TemporarySecret dupe; // There should not be any duplicate keys
if(now >= destructionTime) {
dead.add(s);
} else if(now >= deactivationTime) {
dupe = incomingOld.put(k, s);
if(dupe != null) throw new IllegalStateException();
incomingOld.put(k, s);
} else if(now >= successorCreationTime) {
dupe = incomingOld.put(k, s);
if(dupe != null) throw new IllegalStateException();
dupe = outgoing.put(k, s);
if(dupe != null) throw new IllegalStateException();
incomingOld.put(k, s);
outgoing.put(k, s);
} else if(now >= activationTime) {
dupe = incomingNew.put(k, s);
if(dupe != null) throw new IllegalStateException();
dupe = outgoing.put(k, s);
if(dupe != null) throw new IllegalStateException();
incomingNew.put(k, s);
outgoing.put(k, s);
} else if(now >= creationTime) {
dupe = incomingNew.put(k, s);
if(dupe != null) throw new IllegalStateException();
incomingNew.put(k, s);
} else {
// FIXME: What should we do if the clock moves backwards?
throw new IllegalStateException();
throw new Error("Clock has moved backwards");
}
}
return dead;
}
// Replaces and erases the given secrets and returns any secrets created
// Locking: this
private Collection<TemporarySecret> replaceDeadSecrets(long now,
Collection<TemporarySecret> dead) {
Collection<TemporarySecret> created = new ArrayList<TemporarySecret>();
for(TemporarySecret s : dead) {
EndpointKey k = new EndpointKey(s);
if(incomingNew.containsKey(k)) throw new IllegalStateException();
byte[] secret = s.getSecret();
long period = s.getPeriod();
TemporarySecret dupe; // There should not be any duplicate keys
if(incomingOld.containsKey(k)) {
// The dead secret's successor is still alive
byte[] secret1 = crypto.deriveNextSecret(secret, period + 1);
TemporarySecret s1 = new TemporarySecret(s, period + 1,
secret1);
created.add(s1);
dupe = incomingNew.put(k, s1);
if(dupe != null) throw new IllegalStateException();
long creationTime = getCreationTime(s1);
long activationTime = creationTime + s1.getClockDifference();
if(now >= activationTime) {
dupe = outgoing.put(k, s1);
if(dupe != null) throw new IllegalStateException();
}
} else {
// The dead secret has no living successor
long rotationPeriod = getRotationPeriod(s);
long elapsed = now - s.getEpoch();
long currentPeriod = elapsed / rotationPeriod;
if(currentPeriod <= period) throw new IllegalStateException();
// Derive the two current incoming secrets
byte[] secret1, secret2;
secret1 = secret;
for(long p = period; p < currentPeriod; p++) {
byte[] temp = crypto.deriveNextSecret(secret1, p);
ByteUtils.erase(secret1);
secret1 = temp;
}
secret2 = crypto.deriveNextSecret(secret1, currentPeriod);
// One of the incoming secrets is the current outgoing secret
TemporarySecret s1, s2;
s1 = new TemporarySecret(s, currentPeriod - 1, secret1);
created.add(s1);
dupe = incomingOld.put(k, s1);
if(dupe != null) throw new IllegalStateException();
s2 = new TemporarySecret(s, currentPeriod, secret2);
created.add(s2);
dupe = incomingNew.put(k, s2);
if(dupe != null) throw new IllegalStateException();
if(elapsed % rotationPeriod < s.getClockDifference()) {
// The outgoing secret is the newer incoming secret
dupe = outgoing.put(k, s2);
if(dupe != null) throw new IllegalStateException();
} else {
// The outgoing secret is the older incoming secret
dupe = outgoing.put(k, s1);
if(dupe != null) throw new IllegalStateException();
}
// Work out which rotation period we're in
long rotationPeriod = getRotationPeriod(s);
long elapsed = now - s.getEpoch();
long period = (elapsed / rotationPeriod) + 1;
if(period <= s.getPeriod()) throw new IllegalStateException();
// Derive the two current incoming secrets
byte[] secret1 = s.getSecret();
for(long p = s.getPeriod(); p < period; p++) {
byte[] temp = crypto.deriveNextSecret(secret1, p);
ByteUtils.erase(secret1);
secret1 = temp;
}
byte[] secret2 = crypto.deriveNextSecret(secret1, period);
// Add the incoming secrets to their respective maps - the older
// may already exist if the dead secret has a living successor
EndpointKey k = new EndpointKey(s);
TemporarySecret s1 = new TemporarySecret(s, period - 1, secret1);
created.add(s1);
TemporarySecret exists = incomingOld.put(k, s1);
if(exists != null) ByteUtils.erase(exists.getSecret());
TemporarySecret s2 = new TemporarySecret(s, period, secret2);
created.add(s2);
incomingNew.put(k, s2);
// One of the incoming secrets is the current outgoing secret
if(elapsed % rotationPeriod < MAX_CLOCK_DIFFERENCE) {
// The outgoing secret is the older incoming secret
outgoing.put(k, s1);
} else {
// The outgoing secret is the newer incoming secret
outgoing.put(k, s2);
}
// Erase the dead secret
ByteUtils.erase(secret);
}
return created;
}
// Locking: this
private long getRotationPeriod(Endpoint ep) {
return 2 * ep.getClockDifference() + ep.getLatency();
Long maxLatency = maxLatencies.get(ep.getTransportId());
if(maxLatency == null) throw new IllegalStateException();
return 2 * MAX_CLOCK_DIFFERENCE + maxLatency;
}
// Locking: this
private long getCreationTime(TemporarySecret s) {
long rotationPeriod = getRotationPeriod(s);
return s.getEpoch() + rotationPeriod * s.getPeriod();
}
public synchronized void stop() {
db.removeListener(this);
timer.cancel();
recogniser.removeSecrets();
connectionRecogniser.removeSecrets();
maxLatencies.clear();
removeAndEraseSecrets(outgoing);
removeAndEraseSecrets(incomingOld);
removeAndEraseSecrets(incomingNew);
@@ -236,50 +225,49 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
return new ConnectionContext(c, t, secret, connection, s.getAlice());
}
public synchronized void endpointAdded(Endpoint ep, byte[] initialSecret) {
public synchronized void endpointAdded(Endpoint ep, byte[] initialSecret) {
if(!maxLatencies.containsKey(ep.getTransportId())) {
if(LOG.isLoggable(WARNING)) LOG.warning("No such transport");
return;
}
// Work out which rotation period we're in
long now = clock.currentTimeMillis();
long rotationPeriod = getRotationPeriod(ep);
long elapsed = now - ep.getEpoch();
long currentPeriod = elapsed / rotationPeriod;
if(currentPeriod < 1) throw new IllegalArgumentException();
long period = (elapsed / rotationPeriod) + 1;
if(period < 1) throw new IllegalStateException();
// Derive the two current incoming secrets
byte[] secret1, secret2;
secret1 = initialSecret;
for(long p = 0; p < currentPeriod; p++) {
byte[] secret1 = initialSecret;
for(long p = 0; p < period; p++) {
byte[] temp = crypto.deriveNextSecret(secret1, p);
ByteUtils.erase(secret1);
secret1 = temp;
}
secret2 = crypto.deriveNextSecret(secret1, currentPeriod);
// One of the incoming secrets is the current outgoing secret
byte[] secret2 = crypto.deriveNextSecret(secret1, period);
// Add the incoming secrets to their respective maps
EndpointKey k = new EndpointKey(ep);
TemporarySecret s1, s2, dupe;
s1 = new TemporarySecret(ep, currentPeriod - 1, secret1);
dupe = incomingOld.put(k, s1);
if(dupe != null) throw new IllegalStateException();
s2 = new TemporarySecret(ep, currentPeriod, secret2);
dupe = incomingNew.put(k, s2);
if(dupe != null) throw new IllegalStateException();
if(elapsed % rotationPeriod < ep.getClockDifference()) {
// The outgoing secret is the newer incoming secret
dupe = outgoing.put(k, s2);
if(dupe != null) throw new IllegalStateException();
} else {
TemporarySecret s1 = new TemporarySecret(ep, period - 1, secret1);
incomingOld.put(k, s1);
TemporarySecret s2 = new TemporarySecret(ep, period, secret2);
incomingNew.put(k, s2);
// One of the incoming secrets is the current outgoing secret
if(elapsed % rotationPeriod < MAX_CLOCK_DIFFERENCE) {
// The outgoing secret is the older incoming secret
dupe = outgoing.put(k, s1);
if(dupe != null) throw new IllegalStateException();
outgoing.put(k, s1);
} else {
// The outgoing secret is the newer incoming secret
outgoing.put(k, s2);
}
// Store the new secrets
try {
db.addSecrets(Arrays.asList(s1, s2));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return;
}
// Pass the new secrets to the recogniser
recogniser.addSecret(s1);
recogniser.addSecret(s2);
// Erase the initial secret
ByteUtils.erase(initialSecret);
connectionRecogniser.addSecret(s1);
connectionRecogniser.addSecret(s2);
}
@Override
@@ -299,7 +287,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
ContactId c = s.getContactId();
TransportId t = s.getTransportId();
long period = s.getPeriod();
recogniser.removeSecret(c, t, period);
connectionRecogniser.removeSecret(c, t, period);
}
// Replace any dead secrets
Collection<TemporarySecret> created = replaceDeadSecrets(now, dead);
@@ -311,23 +299,29 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
// Pass any secrets that have been created to the recogniser
for(TemporarySecret s : created) recogniser.addSecret(s);
for(TemporarySecret s : created) connectionRecogniser.addSecret(s);
}
}
public void eventOccurred(DatabaseEvent e) {
if(e instanceof ContactRemovedEvent) {
ContactId c = ((ContactRemovedEvent) e).getContactId();
recogniser.removeSecrets(c);
connectionRecogniser.removeSecrets(c);
synchronized(this) {
removeAndEraseSecrets(c, outgoing);
removeAndEraseSecrets(c, incomingOld);
removeAndEraseSecrets(c, incomingNew);
}
} else if(e instanceof TransportAddedEvent) {
TransportAddedEvent t = (TransportAddedEvent) e;
synchronized(this) {
maxLatencies.put(t.getTransportId(), t.getMaxLatency());
}
} else if(e instanceof TransportRemovedEvent) {
TransportId t = ((TransportRemovedEvent) e).getTransportId();
recogniser.removeSecrets(t);
connectionRecogniser.removeSecrets(t);
synchronized(this) {
maxLatencies.remove(t);
removeAndEraseSecrets(t, outgoing);
removeAndEraseSecrets(t, incomingOld);
removeAndEraseSecrets(t, incomingNew);

View File

@@ -11,7 +11,6 @@ import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import net.sf.briar.api.crypto.AuthenticatedCipher;
import net.sf.briar.api.crypto.ErasableKey;

View File

@@ -11,11 +11,11 @@ import javax.crypto.Cipher;
import net.sf.briar.api.Bytes;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.TemporarySecret;
import net.sf.briar.util.ByteUtils;