mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-15 12:19:54 +01:00
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
57
briar-core/src/net/sf/briar/crypto/Sec1PrivateKey.java
Normal file
57
briar-core/src/net/sf/briar/crypto/Sec1PrivateKey.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user