mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-22 07:39:53 +01:00
Removed double-encryption of shared secrets.
This commit is contained in:
@@ -1,18 +0,0 @@
|
|||||||
package net.sf.briar.api.crypto;
|
|
||||||
|
|
||||||
import static java.lang.annotation.ElementType.PARAMETER;
|
|
||||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
import com.google.inject.BindingAnnotation;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation for injecting the key that is used for encrypting and decrypting
|
|
||||||
* secrets stored in the database.
|
|
||||||
*/
|
|
||||||
@BindingAnnotation
|
|
||||||
@Target({ PARAMETER })
|
|
||||||
@Retention(RUNTIME)
|
|
||||||
public @interface SecretStorageKey {}
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package net.sf.briar.crypto;
|
package net.sf.briar.crypto;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
@@ -10,22 +8,17 @@ import java.security.NoSuchProviderException;
|
|||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
|
||||||
import javax.crypto.KeyGenerator;
|
import javax.crypto.KeyGenerator;
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
import net.sf.briar.api.crypto.CryptoComponent;
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
import net.sf.briar.api.crypto.KeyParser;
|
import net.sf.briar.api.crypto.KeyParser;
|
||||||
import net.sf.briar.api.crypto.MessageDigest;
|
import net.sf.briar.api.crypto.MessageDigest;
|
||||||
import net.sf.briar.api.crypto.SecretStorageKey;
|
|
||||||
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
|
||||||
@@ -37,7 +30,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
private static final String DIGEST_ALGO = "SHA-256";
|
private static final String DIGEST_ALGO = "SHA-256";
|
||||||
private static final String KEY_PAIR_ALGO = "ECDSA";
|
private static final String KEY_PAIR_ALGO = "ECDSA";
|
||||||
private static final int KEY_PAIR_BITS = 256;
|
private static final int KEY_PAIR_BITS = 256;
|
||||||
private static final String SECRET_STORAGE_ALGO = "AES/CTR/NoPadding";
|
|
||||||
private static final String FRAME_CIPHER_ALGO = "AES/CTR/NoPadding";
|
private static final String FRAME_CIPHER_ALGO = "AES/CTR/NoPadding";
|
||||||
private static final String SECRET_KEY_ALGO = "AES";
|
private static final String SECRET_KEY_ALGO = "AES";
|
||||||
private static final int SECRET_KEY_BITS = 256;
|
private static final int SECRET_KEY_BITS = 256;
|
||||||
@@ -45,15 +37,13 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
private static final String MAC_ALGO = "HMacSHA256";
|
private static final String MAC_ALGO = "HMacSHA256";
|
||||||
private static final String SIGNATURE_ALGO = "ECDSA";
|
private static final String SIGNATURE_ALGO = "ECDSA";
|
||||||
|
|
||||||
private final SecretKey secretStorageKey;
|
|
||||||
private final KeyParser keyParser;
|
private final KeyParser keyParser;
|
||||||
private final KeyPairGenerator keyPairGenerator;
|
private final KeyPairGenerator keyPairGenerator;
|
||||||
private final KeyGenerator keyGenerator;
|
private final KeyGenerator keyGenerator;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CryptoComponentImpl(@SecretStorageKey SecretKey secretStorageKey) {
|
CryptoComponentImpl() {
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
this.secretStorageKey = secretStorageKey;
|
|
||||||
try {
|
try {
|
||||||
keyParser = new KeyParserImpl(KEY_PAIR_ALGO, PROVIDER);
|
keyParser = new KeyParserImpl(KEY_PAIR_ALGO, PROVIDER);
|
||||||
keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGO,
|
keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGO,
|
||||||
@@ -75,54 +65,29 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private SecretKey deriveFrameKey(SharedSecret s, boolean alice) {
|
private SecretKey deriveFrameKey(SharedSecret s, boolean alice) {
|
||||||
if(alice) return deriveKey("F_A", s.getIv(), s.getCiphertext());
|
if(alice) return deriveKey("F_A", s.getSecret());
|
||||||
else return deriveKey("F_B", s.getIv(), s.getCiphertext());
|
else return deriveKey("F_B", s.getSecret());
|
||||||
}
|
}
|
||||||
|
|
||||||
private SecretKey deriveKey(String name, IvParameterSpec iv,
|
private SecretKey deriveKey(String name, byte[] secret) {
|
||||||
byte[] ciphertext) {
|
|
||||||
MessageDigest digest = getMessageDigest();
|
MessageDigest digest = getMessageDigest();
|
||||||
try {
|
try {
|
||||||
digest.update(name.getBytes("UTF-8"));
|
digest.update(name.getBytes("UTF-8"));
|
||||||
} catch(UnsupportedEncodingException e) {
|
} catch(UnsupportedEncodingException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
byte[] decrypted = decryptSharedSecret(iv, ciphertext);
|
digest.update(secret);
|
||||||
digest.update(decrypted);
|
|
||||||
Arrays.fill(decrypted, (byte) 0); // Destroy the plaintext secret
|
|
||||||
return new SecretKeySpec(digest.digest(), SECRET_KEY_ALGO);
|
return new SecretKeySpec(digest.digest(), SECRET_KEY_ALGO);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] decryptSharedSecret(IvParameterSpec iv, byte[] ciphertext) {
|
|
||||||
try {
|
|
||||||
Cipher c = Cipher.getInstance(SECRET_STORAGE_ALGO, PROVIDER);
|
|
||||||
c.init(Cipher.DECRYPT_MODE, secretStorageKey, iv);
|
|
||||||
return c.doFinal(ciphertext);
|
|
||||||
} catch(BadPaddingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch(IllegalBlockSizeException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch(InvalidAlgorithmParameterException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch(InvalidKeyException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch(NoSuchAlgorithmException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch(NoSuchPaddingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch(NoSuchProviderException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SecretKey deriveIncomingIvKey(byte[] secret) {
|
public SecretKey deriveIncomingIvKey(byte[] secret) {
|
||||||
SharedSecret s = new SharedSecret(secret);
|
SharedSecret s = new SharedSecret(secret);
|
||||||
return deriveIvKey(s, !s.getAlice());
|
return deriveIvKey(s, !s.getAlice());
|
||||||
}
|
}
|
||||||
|
|
||||||
private SecretKey deriveIvKey(SharedSecret s, boolean alice) {
|
private SecretKey deriveIvKey(SharedSecret s, boolean alice) {
|
||||||
if(alice) return deriveKey("I_A", s.getIv(), s.getCiphertext());
|
if(alice) return deriveKey("I_A", s.getSecret());
|
||||||
else return deriveKey("I_B", s.getIv(), s.getCiphertext());
|
else return deriveKey("I_B", s.getSecret());
|
||||||
}
|
}
|
||||||
|
|
||||||
public SecretKey deriveIncomingMacKey(byte[] secret) {
|
public SecretKey deriveIncomingMacKey(byte[] secret) {
|
||||||
@@ -131,8 +96,8 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private SecretKey deriveMacKey(SharedSecret s, boolean alice) {
|
private SecretKey deriveMacKey(SharedSecret s, boolean alice) {
|
||||||
if(alice) return deriveKey("M_A", s.getIv(), s.getCiphertext());
|
if(alice) return deriveKey("M_A", s.getSecret());
|
||||||
else return deriveKey("M_B", s.getIv(), s.getCiphertext());
|
else return deriveKey("M_B", s.getSecret());
|
||||||
}
|
}
|
||||||
|
|
||||||
public SecretKey deriveOutgoingFrameKey(byte[] secret) {
|
public SecretKey deriveOutgoingFrameKey(byte[] secret) {
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
package net.sf.briar.crypto;
|
package net.sf.briar.crypto;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
import net.sf.briar.api.crypto.CryptoComponent;
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
import net.sf.briar.api.crypto.SecretStorageKey;
|
|
||||||
|
|
||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
@@ -15,8 +11,5 @@ public class CryptoModule extends AbstractModule {
|
|||||||
protected void configure() {
|
protected void configure() {
|
||||||
bind(CryptoComponent.class).to(
|
bind(CryptoComponent.class).to(
|
||||||
CryptoComponentImpl.class).in(Singleton.class);
|
CryptoComponentImpl.class).in(Singleton.class);
|
||||||
// FIXME: Use a real key
|
|
||||||
bind(SecretKey.class).annotatedWith(SecretStorageKey.class).toInstance(
|
|
||||||
new SecretKeySpec(new byte[32], "AES"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,19 @@
|
|||||||
package net.sf.briar.crypto;
|
package net.sf.briar.crypto;
|
||||||
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An encrypted shared secret from which authentication and encryption keys can
|
* A shared secret from which authentication and encryption keys can be derived.
|
||||||
* be derived. The encrypted secret carries an IV for encrypting and decrypting
|
* The secret carries a flag indicating whether Alice's keys or Bob's keys
|
||||||
* it and a flag indicating whether Alice's keys or Bob's keys should be
|
* should be derived from the secret. When two parties agree on a shared secret,
|
||||||
* derived from the secret.
|
* they must decide which of them will derive Alice's keys and which Bob's.
|
||||||
* <p>
|
|
||||||
* When two parties agree on a shared secret, they must determine which of them
|
|
||||||
* will derive Alice's keys and which Bob's. Each party then encrypts the
|
|
||||||
* secret with an independent key and IV.
|
|
||||||
*/
|
*/
|
||||||
class SharedSecret {
|
class SharedSecret {
|
||||||
|
|
||||||
static final int IV_BYTES = 16;
|
|
||||||
|
|
||||||
private final IvParameterSpec iv;
|
|
||||||
private final boolean alice;
|
private final boolean alice;
|
||||||
private final byte[] ciphertext;
|
private final byte[] secret;
|
||||||
|
|
||||||
SharedSecret(byte[] secret) {
|
SharedSecret(byte[] b) {
|
||||||
if(secret.length < IV_BYTES + 2) throw new IllegalArgumentException();
|
if(b.length < 2) throw new IllegalArgumentException();
|
||||||
iv = new IvParameterSpec(secret, 0, IV_BYTES);
|
switch(b[0]) {
|
||||||
switch(secret[IV_BYTES]) {
|
|
||||||
case 0:
|
case 0:
|
||||||
alice = false;
|
alice = false;
|
||||||
break;
|
break;
|
||||||
@@ -33,21 +23,13 @@ class SharedSecret {
|
|||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
ciphertext = new byte[secret.length - IV_BYTES - 1];
|
secret = new byte[b.length - 1];
|
||||||
System.arraycopy(secret, IV_BYTES + 1, ciphertext, 0,
|
System.arraycopy(b, 1, secret, 0, secret.length);
|
||||||
ciphertext.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedSecret(IvParameterSpec iv, boolean alice, byte[] ciphertext) {
|
SharedSecret(boolean alice, byte[] secret) {
|
||||||
if(iv.getIV().length != IV_BYTES) throw new IllegalArgumentException();
|
|
||||||
this.iv = iv;
|
|
||||||
this.alice = alice;
|
this.alice = alice;
|
||||||
this.ciphertext = ciphertext;
|
this.secret = secret;
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the IV used for encrypting and decrypting the secret. */
|
|
||||||
IvParameterSpec getIv() {
|
|
||||||
return iv;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,22 +40,19 @@ class SharedSecret {
|
|||||||
return alice;
|
return alice;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the encrypted shared secret. */
|
/** Returns the shared secret. */
|
||||||
byte[] getCiphertext() {
|
byte[] getSecret() {
|
||||||
return ciphertext;
|
return secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a raw representation of the encrypted shared secret, suitable
|
* Returns a raw representation of this object, suitable for storing in the
|
||||||
* for storing in the database.
|
* database.
|
||||||
*/
|
*/
|
||||||
byte[] getBytes() {
|
byte[] getBytes() {
|
||||||
byte[] b = new byte[IV_BYTES + 1 + ciphertext.length];
|
byte[] b = new byte[1 + secret.length];
|
||||||
byte[] ivBytes = iv.getIV();
|
if(alice) b[0] = (byte) 1;
|
||||||
assert ivBytes.length == IV_BYTES;
|
System.arraycopy(secret, 0, b, 1, secret.length);
|
||||||
System.arraycopy(ivBytes, 0, b, 0, IV_BYTES);
|
|
||||||
if(alice) b[IV_BYTES] = (byte) 1;
|
|
||||||
System.arraycopy(ciphertext, 0, b, IV_BYTES + 1, ciphertext.length);
|
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,9 +97,9 @@ public class ProtocolIntegrationTest extends TestCase {
|
|||||||
assertEquals(crypto.getMessageDigest().getDigestLength(),
|
assertEquals(crypto.getMessageDigest().getDigestLength(),
|
||||||
UniqueId.LENGTH);
|
UniqueId.LENGTH);
|
||||||
// Create matching secrets: one for Alice, one for Bob
|
// Create matching secrets: one for Alice, one for Bob
|
||||||
aliceSecret = new byte[45];
|
aliceSecret = new byte[123];
|
||||||
aliceSecret[16] = (byte) 1;
|
aliceSecret[0] = (byte) 1;
|
||||||
bobSecret = new byte[45];
|
bobSecret = new byte[123];
|
||||||
// Create two groups: one restricted, one unrestricted
|
// Create two groups: one restricted, one unrestricted
|
||||||
GroupFactory groupFactory = i.getInstance(GroupFactory.class);
|
GroupFactory groupFactory = i.getInstance(GroupFactory.class);
|
||||||
group = groupFactory.createGroup("Unrestricted group", null);
|
group = groupFactory.createGroup("Unrestricted group", null);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class CryptoComponentTest extends TestCase {
|
|||||||
public void testKeyDerivation() {
|
public void testKeyDerivation() {
|
||||||
// Create matching secrets: one for Alice, one for Bob
|
// Create matching secrets: one for Alice, one for Bob
|
||||||
byte[] aliceSecret = new byte[123];
|
byte[] aliceSecret = new byte[123];
|
||||||
aliceSecret[SharedSecret.IV_BYTES] = (byte) 1;
|
aliceSecret[0] = (byte) 1;
|
||||||
byte[] bobSecret = new byte[123];
|
byte[] bobSecret = new byte[123];
|
||||||
// Check that Alice's incoming keys match Bob's outgoing keys
|
// Check that Alice's incoming keys match Bob's outgoing keys
|
||||||
assertEquals(crypto.deriveIncomingMacKey(aliceSecret),
|
assertEquals(crypto.deriveIncomingMacKey(aliceSecret),
|
||||||
|
|||||||
@@ -15,22 +15,22 @@ public class SharedSecretTest extends TestCase {
|
|||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
byte[] secret = new byte[40];
|
byte[] secret = new byte[40];
|
||||||
random.nextBytes(secret);
|
random.nextBytes(secret);
|
||||||
secret[SharedSecret.IV_BYTES] = (byte) 0;
|
secret[0] = (byte) 0;
|
||||||
SharedSecret s = new SharedSecret(secret);
|
SharedSecret s = new SharedSecret(secret);
|
||||||
assertArrayEquals(secret, s.getBytes());
|
assertArrayEquals(secret, s.getBytes());
|
||||||
secret[SharedSecret.IV_BYTES] = (byte) 1;
|
secret[0] = (byte) 1;
|
||||||
s = new SharedSecret(secret);
|
s = new SharedSecret(secret);
|
||||||
assertArrayEquals(secret, s.getBytes());
|
assertArrayEquals(secret, s.getBytes());
|
||||||
// The Alice flag must be either 0 or 1
|
// The Alice flag must be either 0 or 1
|
||||||
secret[SharedSecret.IV_BYTES] = (byte) 2;
|
secret[0] = (byte) 2;
|
||||||
try {
|
try {
|
||||||
s = new SharedSecret(secret);
|
s = new SharedSecret(secret);
|
||||||
fail();
|
fail();
|
||||||
} catch(IllegalArgumentException expected) {}
|
} catch(IllegalArgumentException expected) {}
|
||||||
// The ciphertext must be at least 1 byte long
|
// The secret must be at least 1 byte long
|
||||||
secret = new byte[SharedSecret.IV_BYTES + 1];
|
secret = new byte[1];
|
||||||
random.nextBytes(secret);
|
random.nextBytes(secret);
|
||||||
secret[SharedSecret.IV_BYTES] = (byte) 0;
|
secret[0] = (byte) 0;
|
||||||
try {
|
try {
|
||||||
s = new SharedSecret(secret);
|
s = new SharedSecret(secret);
|
||||||
fail();
|
fail();
|
||||||
|
|||||||
@@ -63,9 +63,9 @@ public class BatchConnectionReadWriteTest extends TestCase {
|
|||||||
transportId = new TransportId(TestUtils.getRandomId());
|
transportId = new TransportId(TestUtils.getRandomId());
|
||||||
transportIndex = new TransportIndex(1);
|
transportIndex = new TransportIndex(1);
|
||||||
// Create matching secrets for Alice and Bob
|
// Create matching secrets for Alice and Bob
|
||||||
aliceSecret = new byte[100];
|
aliceSecret = new byte[123];
|
||||||
aliceSecret[16] = (byte) 1;
|
aliceSecret[0] = (byte) 1;
|
||||||
bobSecret = new byte[100];
|
bobSecret = new byte[123];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
|||||||
Reference in New Issue
Block a user