Skip to content
Snippets Groups Projects
Unverified Commit d9c63bbc authored by akwizgran's avatar akwizgran
Browse files

Remove Fortuna generator, fix Android SecureRandom bug.

parent adc9bdeb
No related branches found
No related tags found
No related merge requests found
Showing
with 384 additions and 566 deletions
package org.briarproject.bramble.system; package org.briarproject.bramble.system;
import android.app.Application; import android.app.Application;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Build; import android.os.Build;
import android.os.Parcel;
import android.provider.Settings; import android.provider.Settings;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
...@@ -14,21 +19,25 @@ import java.io.IOException; ...@@ -14,21 +19,25 @@ import java.io.IOException;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static android.content.Context.WIFI_SERVICE;
import static android.provider.Settings.Secure.ANDROID_ID; import static android.provider.Settings.Secure.ANDROID_ID;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class AndroidSeedProvider extends LinuxSeedProvider { class AndroidSecureRandomProvider extends LinuxSecureRandomProvider {
private static final int SEED_LENGTH = 32;
private final Context appContext; private final Context appContext;
@Inject @Inject
AndroidSeedProvider(Application app) { AndroidSecureRandomProvider(Application app) {
appContext = app.getApplicationContext(); appContext = app.getApplicationContext();
} }
@Override @Override
void writeToEntropyPool(DataOutputStream out) throws IOException { protected void writeToEntropyPool(DataOutputStream out) throws IOException {
super.writeToEntropyPool(out);
out.writeInt(android.os.Process.myPid()); out.writeInt(android.os.Process.myPid());
out.writeInt(android.os.Process.myTid()); out.writeInt(android.os.Process.myTid());
out.writeInt(android.os.Process.myUid()); out.writeInt(android.os.Process.myUid());
...@@ -37,6 +46,44 @@ class AndroidSeedProvider extends LinuxSeedProvider { ...@@ -37,6 +46,44 @@ class AndroidSeedProvider extends LinuxSeedProvider {
ContentResolver contentResolver = appContext.getContentResolver(); ContentResolver contentResolver = appContext.getContentResolver();
String id = Settings.Secure.getString(contentResolver, ANDROID_ID); String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
if (id != null) out.writeUTF(id); if (id != null) out.writeUTF(id);
super.writeToEntropyPool(out); Parcel parcel = Parcel.obtain();
WifiManager wm =
(WifiManager) appContext.getSystemService(WIFI_SERVICE);
for (WifiConfiguration config : wm.getConfiguredNetworks())
parcel.writeParcelable(config, 0);
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt != null) {
for (BluetoothDevice device : bt.getBondedDevices())
parcel.writeParcelable(device, 0);
}
out.write(parcel.marshall());
parcel.recycle();
}
@Override
protected void writeSeed() {
super.writeSeed();
if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT <= 18)
applyOpenSslFix();
}
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
private void applyOpenSslFix() {
byte[] seed = new LinuxSecureRandomSpi().engineGenerateSeed(
SEED_LENGTH);
try {
// Seed the OpenSSL PRNG
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_seed", byte[].class)
.invoke(null, seed);
// Mix the output of the Linux PRNG into the OpenSSL PRNG
int bytesRead = (Integer) Class.forName(
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_load_file", String.class, long.class)
.invoke(null, "/dev/urandom", 1024);
if (bytesRead != 1024) throw new IOException();
} catch (Exception e) {
throw new SecurityException(e);
}
} }
} }
...@@ -4,7 +4,7 @@ import android.app.Application; ...@@ -4,7 +4,7 @@ import android.app.Application;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.SeedProvider; import org.briarproject.bramble.api.system.SecureRandomProvider;
import javax.inject.Singleton; import javax.inject.Singleton;
...@@ -16,8 +16,8 @@ public class AndroidSystemModule { ...@@ -16,8 +16,8 @@ public class AndroidSystemModule {
@Provides @Provides
@Singleton @Singleton
SeedProvider provideSeedProvider(Application app) { SecureRandomProvider provideSecureRandomProvider(Application app) {
return new AndroidSeedProvider(app); return new AndroidSecureRandomProvider(app);
} }
@Provides @Provides
......
package org.briarproject.bramble.api.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.Provider;
import java.security.SecureRandom;
import javax.annotation.Nullable;
/**
* Wrapper for a platform-specific secure random number generator.
*/
@NotNullByDefault
public interface SecureRandomProvider {
/**
* Returns a {@link Provider} that provides a strong {@link SecureRandom}
* implementation, or null if the platform's default implementation should
* be used.
*/
@Nullable
Provider getProvider();
}
package org.briarproject.bramble.api.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* Uses a platform-specific source to provide a seed for a pseudo-random
* number generator.
*/
@NotNullByDefault
public interface SeedProvider {
/**
* The length of the seed in bytes.
*/
int SEED_BYTES = 32;
byte[] getSeed();
}
package org.briarproject.bramble.crypto;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
/**
* A {@link SecureRandom} implementation that combines the outputs of two or
* more other implementations using XOR.
*/
class CombinedSecureRandom extends SecureRandom {
private static final Provider PROVIDER = new CombinedProvider();
CombinedSecureRandom(SecureRandom... randoms) {
super(new CombinedSecureRandomSpi(randoms), PROVIDER);
}
private static class CombinedSecureRandomSpi extends SecureRandomSpi {
private final SecureRandom[] randoms;
private CombinedSecureRandomSpi(SecureRandom... randoms) {
if (randoms.length < 2) throw new IllegalArgumentException();
this.randoms = randoms;
}
@Override
protected byte[] engineGenerateSeed(int numBytes) {
byte[] combined = new byte[numBytes];
for (SecureRandom random : randoms) {
byte[] b = random.generateSeed(numBytes);
int length = Math.min(numBytes, b.length);
for (int i = 0; i < length; i++)
combined[i] = (byte) (combined[i] ^ b[i]);
}
return combined;
}
@Override
protected void engineNextBytes(byte[] b) {
byte[] temp = new byte[b.length];
for (SecureRandom random : randoms) {
random.nextBytes(temp);
for (int i = 0; i < b.length; i++)
b[i] = (byte) (b[i] ^ temp[i]);
}
}
@Override
protected void engineSetSeed(byte[] seed) {
for (SecureRandom random : randoms) random.setSeed(seed);
}
}
private static class CombinedProvider extends Provider {
private CombinedProvider() {
super("Combined", 1.0, "");
}
}
}
...@@ -8,7 +8,7 @@ import org.briarproject.bramble.api.crypto.PseudoRandom; ...@@ -8,7 +8,7 @@ import org.briarproject.bramble.api.crypto.PseudoRandom;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.system.SeedProvider; import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.api.transport.IncomingKeys; import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.OutgoingKeys; import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
...@@ -29,7 +29,10 @@ import org.spongycastle.crypto.params.KeyParameter; ...@@ -29,7 +29,10 @@ import org.spongycastle.crypto.params.KeyParameter;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Security;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
...@@ -101,16 +104,26 @@ class CryptoComponentImpl implements CryptoComponent { ...@@ -101,16 +104,26 @@ class CryptoComponentImpl implements CryptoComponent {
private final MessageEncrypter messageEncrypter; private final MessageEncrypter messageEncrypter;
@Inject @Inject
CryptoComponentImpl(SeedProvider seedProvider) { CryptoComponentImpl(SecureRandomProvider secureRandomProvider) {
if (!FortunaSecureRandom.selfTest()) throw new RuntimeException();
SecureRandom platformSecureRandom = new SecureRandom();
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
String provider = platformSecureRandom.getProvider().getName(); SecureRandom defaultSecureRandom = new SecureRandom();
String algorithm = platformSecureRandom.getAlgorithm(); String name = defaultSecureRandom.getProvider().getName();
LOG.info("Default SecureRandom: " + provider + " " + algorithm); String algorithm = defaultSecureRandom.getAlgorithm();
LOG.info("Default SecureRandom: " + name + " " + algorithm);
} }
SecureRandom fortuna = new FortunaSecureRandom(seedProvider.getSeed()); Provider provider = secureRandomProvider.getProvider();
secureRandom = new CombinedSecureRandom(platformSecureRandom, fortuna); if (provider == null) {
LOG.info("Using default");
} else {
installSecureRandomProvider(provider);
if (LOG.isLoggable(INFO)) {
SecureRandom installedSecureRandom = new SecureRandom();
String name = installedSecureRandom.getProvider().getName();
String algorithm = installedSecureRandom.getAlgorithm();
LOG.info("Installed SecureRandom: " + name + " " + algorithm);
}
}
secureRandom = new SecureRandom();
ECKeyGenerationParameters params = new ECKeyGenerationParameters( ECKeyGenerationParameters params = new ECKeyGenerationParameters(
PARAMETERS, secureRandom); PARAMETERS, secureRandom);
agreementKeyPairGenerator = new ECKeyPairGenerator(); agreementKeyPairGenerator = new ECKeyPairGenerator();
...@@ -124,6 +137,31 @@ class CryptoComponentImpl implements CryptoComponent { ...@@ -124,6 +137,31 @@ class CryptoComponentImpl implements CryptoComponent {
messageEncrypter = new MessageEncrypter(secureRandom); messageEncrypter = new MessageEncrypter(secureRandom);
} }
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
private void installSecureRandomProvider(Provider provider) {
Provider[] providers = Security.getProviders("SecureRandom.SHA1PRNG");
if (providers == null || providers.length == 0
|| !provider.getClass().equals(providers[0].getClass())) {
Security.insertProviderAt(provider, 1);
}
// Check the new provider is the default when no algorithm is specified
SecureRandom random = new SecureRandom();
if (!provider.getClass().equals(random.getProvider().getClass())) {
throw new SecurityException("Wrong SecureRandom provider: "
+ random.getProvider().getClass());
}
// Check the new provider is the default when SHA1PRNG is specified
try {
random = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new SecurityException(e);
}
if (!provider.getClass().equals(random.getProvider().getClass())) {
throw new SecurityException("Wrong SHA1PRNG provider: "
+ random.getProvider().getClass());
}
}
@Override @Override
public SecretKey generateSecretKey() { public SecretKey generateSecretKey() {
byte[] b = new byte[SecretKey.LENGTH]; byte[] b = new byte[SecretKey.LENGTH];
...@@ -133,7 +171,10 @@ class CryptoComponentImpl implements CryptoComponent { ...@@ -133,7 +171,10 @@ class CryptoComponentImpl implements CryptoComponent {
@Override @Override
public PseudoRandom getPseudoRandom(int seed1, int seed2) { public PseudoRandom getPseudoRandom(int seed1, int seed2) {
return new PseudoRandomImpl(seed1, seed2); byte[] seed = new byte[INT_32_BYTES * 2];
ByteUtils.writeUint32(seed1, seed, 0);
ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
return new PseudoRandomImpl(seed);
} }
@Override @Override
...@@ -296,7 +337,7 @@ class CryptoComponentImpl implements CryptoComponent { ...@@ -296,7 +337,7 @@ class CryptoComponentImpl implements CryptoComponent {
public SecretKey deriveMasterSecret(byte[] theirPublicKey, public SecretKey deriveMasterSecret(byte[] theirPublicKey,
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException { KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
return deriveMasterSecret(deriveSharedSecret( return deriveMasterSecret(deriveSharedSecret(
theirPublicKey,ourKeyPair, alice)); theirPublicKey, ourKeyPair, alice));
} }
@Override @Override
...@@ -607,7 +648,7 @@ class CryptoComponentImpl implements CryptoComponent { ...@@ -607,7 +648,7 @@ class CryptoComponentImpl implements CryptoComponent {
} }
private long sampleRunningTime(int iterations) { private long sampleRunningTime(int iterations) {
byte[] password = { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' }; byte[] password = {'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
byte[] salt = new byte[PBKDF_SALT_BYTES]; byte[] salt = new byte[PBKDF_SALT_BYTES];
int keyLengthInBits = SecretKey.LENGTH * 8; int keyLengthInBits = SecretKey.LENGTH * 8;
long start = System.nanoTime(); long start = System.nanoTime();
......
...@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; ...@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory; import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory; import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.SeedProvider; import org.briarproject.bramble.api.system.SecureRandomProvider;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
...@@ -60,8 +60,9 @@ public class CryptoModule { ...@@ -60,8 +60,9 @@ public class CryptoModule {
@Provides @Provides
@Singleton @Singleton
CryptoComponent provideCryptoComponent(SeedProvider seedProvider) { CryptoComponent provideCryptoComponent(
return new CryptoComponentImpl(seedProvider); SecureRandomProvider secureRandomProvider) {
return new CryptoComponentImpl(secureRandomProvider);
} }
@Provides @Provides
......
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.Digest;
import javax.annotation.concurrent.NotThreadSafe;
/**
* A message digest that prevents length extension attacks - see Ferguson and
* Schneier, <i>Practical Cryptography</i>, chapter 6.
* <p>
* "Let h be an interative hash function. The hash function h<sub>d</sub> is
* defined by h<sub>d</sub> := h(h(m)), and has a claimed security level of
* min(k, n/2) where k is the security level of h and n is the size of the hash
* result."
*/
@NotThreadSafe
@NotNullByDefault
class DoubleDigest implements Digest {
private final Digest delegate;
DoubleDigest(Digest delegate) {
this.delegate = delegate;
}
private byte[] digest() {
byte[] digest = new byte[delegate.getDigestSize()];
delegate.doFinal(digest, 0); // h(m)
delegate.update(digest, 0, digest.length);
delegate.doFinal(digest, 0); // h(h(m))
return digest;
}
public int digest(byte[] buf, int offset, int len) {
byte[] digest = digest();
len = Math.min(len, digest.length);
System.arraycopy(digest, 0, buf, offset, len);
return len;
}
@Override
public int getDigestSize() {
return delegate.getDigestSize();
}
@Override
public String getAlgorithmName() {
return "Double " + delegate.getAlgorithmName();
}
@Override
public void reset() {
delegate.reset();
}
@Override
public void update(byte input) {
delegate.update(input);
}
public void update(byte[] input) {
delegate.update(input, 0, input.length);
}
@Override
public void update(byte[] input, int offset, int len) {
delegate.update(input, offset, len);
}
@Override
public int doFinal(byte[] out, int outOff) {
return digest(out, outOff, delegate.getDigestSize());
}
}
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.engines.AESLightEngine;
import org.spongycastle.crypto.params.KeyParameter;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.concurrent.ThreadSafe;
/**
* Implements the Fortuna pseudo-random number generator, as described in
* Ferguson and Schneier, <i>Practical Cryptography</i>, chapter 9.
*/
@ThreadSafe
@NotNullByDefault
class FortunaGenerator {
private static final int MAX_BYTES_PER_REQUEST = 1024 * 1024;
private static final int KEY_BYTES = 32;
private static final int BLOCK_BYTES = 16;
private final Lock lock = new ReentrantLock();
// The following are locking: lock
private final DoubleDigest digest = new DoubleDigest(new SHA256Digest());
private final BlockCipher cipher = new AESLightEngine();
private final byte[] key = new byte[KEY_BYTES];
private final byte[] counter = new byte[BLOCK_BYTES];
private final byte[] buffer = new byte[BLOCK_BYTES];
private final byte[] newKey = new byte[KEY_BYTES];
FortunaGenerator(byte[] seed) {
reseed(seed);
}
void reseed(byte[] seed) {
lock.lock();
try {
digest.update(key);
digest.update(seed);
digest.digest(key, 0, KEY_BYTES);
incrementCounter();
} finally {
lock.unlock();
}
}
// Package access for testing
void incrementCounter() {
lock.lock();
try {
counter[0]++;
for (int i = 0; counter[i] == 0; i++) {
if (i + 1 == BLOCK_BYTES)
throw new RuntimeException("Counter exhausted");
counter[i + 1]++;
}
} finally {
lock.unlock();
}
}
// Package access for testing
byte[] getCounter() {
lock.lock();
try {
return counter;
} finally {
lock.unlock();
}
}
int nextBytes(byte[] dest, int off, int len) {
lock.lock();
try {
// Don't write more than the maximum number of bytes in one request
if (len > MAX_BYTES_PER_REQUEST) len = MAX_BYTES_PER_REQUEST;
cipher.init(true, new KeyParameter(key));
// Generate full blocks directly into the output buffer
int fullBlocks = len / BLOCK_BYTES;
for (int i = 0; i < fullBlocks; i++) {
cipher.processBlock(counter, 0, dest, off + i * BLOCK_BYTES);
incrementCounter();
}
// Generate a partial block if needed
int done = fullBlocks * BLOCK_BYTES, remaining = len - done;
if (remaining >= BLOCK_BYTES) throw new AssertionError();
if (remaining > 0) {
cipher.processBlock(counter, 0, buffer, 0);
incrementCounter();
// Copy the partial block to the output buffer and erase our copy
System.arraycopy(buffer, 0, dest, off + done, remaining);
for (int i = 0; i < BLOCK_BYTES; i++) buffer[i] = 0;
}
// Generate a new key
for (int i = 0; i < KEY_BYTES / BLOCK_BYTES; i++) {
cipher.processBlock(counter, 0, newKey, i * BLOCK_BYTES);
incrementCounter();
}
System.arraycopy(newKey, 0, key, 0, KEY_BYTES);
for (int i = 0; i < KEY_BYTES; i++) newKey[i] = 0;
// Return the number of bytes written
return len;
} finally {
lock.unlock();
}
}
}
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.util.StringUtils;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.util.Arrays;
/**
* A {@link java.security.SecureRandom SecureRandom} implementation based on a
* {@link FortunaGenerator}.
*/
class FortunaSecureRandom extends SecureRandom {
// Package access for testing
static final byte[] SELF_TEST_VECTOR_1 =
StringUtils.fromHexString("4BD6EA599D47E3EE9DD911833C29CA22");
static final byte[] SELF_TEST_VECTOR_2 =
StringUtils.fromHexString("10984D576E6850E505CA9F42A9BFD88A");
static final byte[] SELF_TEST_VECTOR_3 =
StringUtils.fromHexString("1E12DA166BD86DCECDE50A8296018DE2");
private static final Provider PROVIDER = new FortunaProvider();
FortunaSecureRandom(byte[] seed) {
super(new FortunaSecureRandomSpi(seed), PROVIDER);
}
/**
* Tests that the {@link #nextBytes(byte[])} and {@link #setSeed(byte[])}
* methods are passed through to the generator in the expected way.
*/
static boolean selfTest() {
byte[] seed = new byte[32];
SecureRandom r = new FortunaSecureRandom(seed);
byte[] output = new byte[16];
r.nextBytes(output);
if (!Arrays.equals(SELF_TEST_VECTOR_1, output)) return false;
r.nextBytes(output);
if (!Arrays.equals(SELF_TEST_VECTOR_2, output)) return false;
r.setSeed(seed);
r.nextBytes(output);
return Arrays.equals(SELF_TEST_VECTOR_3, output);
}
private static class FortunaSecureRandomSpi extends SecureRandomSpi {
private final FortunaGenerator generator;
private FortunaSecureRandomSpi(byte[] seed) {
generator = new FortunaGenerator(seed);
}
@Override
protected byte[] engineGenerateSeed(int numBytes) {
byte[] b = new byte[numBytes];
engineNextBytes(b);
return b;
}
@Override
protected void engineNextBytes(byte[] b) {
int offset = 0;
while (offset < b.length)
offset += generator.nextBytes(b, offset, b.length - offset);
}
@Override
protected void engineSetSeed(byte[] seed) {
generator.reseed(seed);
}
}
private static class FortunaProvider extends Provider {
private FortunaProvider() {
super("Fortuna", 1.0, "");
}
}
}
...@@ -2,30 +2,34 @@ package org.briarproject.bramble.crypto; ...@@ -2,30 +2,34 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.PseudoRandom; import org.briarproject.bramble.api.crypto.PseudoRandom;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.ByteUtils; import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.engines.Salsa20Engine;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
@NotThreadSafe @NotThreadSafe
@NotNullByDefault @NotNullByDefault
class PseudoRandomImpl implements PseudoRandom { class PseudoRandomImpl implements PseudoRandom {
private final FortunaGenerator generator; private final Salsa20Engine cipher = new Salsa20Engine();
PseudoRandomImpl(int seed1, int seed2) { PseudoRandomImpl(byte[] seed) {
byte[] seed = new byte[INT_32_BYTES * 2]; // Hash the seed to produce a 32-byte key
ByteUtils.writeUint32(seed1, seed, 0); byte[] key = new byte[32];
ByteUtils.writeUint32(seed2, seed, INT_32_BYTES); Digest digest = new Blake2sDigest();
generator = new FortunaGenerator(seed); digest.update(seed, 0, seed.length);
digest.doFinal(key, 0);
// Initialise the stream cipher with an all-zero nonce
byte[] nonce = new byte[8];
cipher.init(true, new ParametersWithIV(new KeyParameter(key), nonce));
} }
@Override @Override
public byte[] nextBytes(int length) { public byte[] nextBytes(int length) {
byte[] b = new byte[length]; byte[] in = new byte[length], out = new byte[length];
int offset = 0; cipher.processBytes(in, 0, length, out, 0);
while (offset < length) offset += generator.nextBytes(b, offset, length); return out;
return b;
} }
} }
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
abstract class AbstractSecureRandomProvider implements SecureRandomProvider {
// Contribute whatever slightly unpredictable info we have to the pool
protected void writeToEntropyPool(DataOutputStream out) throws IOException {
out.writeLong(System.currentTimeMillis());
out.writeLong(System.nanoTime());
out.writeLong(Runtime.getRuntime().freeMemory());
List<NetworkInterface> ifaces =
Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface i : ifaces) {
List<InetAddress> addrs = Collections.list(i.getInetAddresses());
for (InetAddress a : addrs) out.write(a.getAddress());
byte[] hardware = i.getHardwareAddress();
if (hardware != null) out.write(hardware);
}
for (Entry<String, String> e : System.getenv().entrySet()) {
out.writeUTF(e.getKey());
out.writeUTF(e.getValue());
}
Properties properties = System.getProperties();
for (String key : properties.stringPropertyNames())
out.writeUTF(properties.getProperty(key));
}
}
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.Provider;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import static java.util.logging.Level.WARNING;
@Immutable
@NotNullByDefault
class LinuxSecureRandomProvider extends AbstractSecureRandomProvider {
private static final Logger LOG =
Logger.getLogger(LinuxSecureRandomProvider.class.getName());
private static final File RANDOM_DEVICE = new File("/dev/urandom");
private final AtomicBoolean seeded = new AtomicBoolean(false);
private final File outputDevice;
LinuxSecureRandomProvider() {
this(RANDOM_DEVICE);
}
LinuxSecureRandomProvider(File outputDevice) {
this.outputDevice = outputDevice;
}
@Override
public Provider getProvider() {
if (!seeded.getAndSet(true)) writeSeed();
return new LinuxProvider();
}
protected void writeSeed() {
try {
DataOutputStream out = new DataOutputStream(
new FileOutputStream(outputDevice));
writeToEntropyPool(out);
out.flush();
out.close();
} catch (IOException e) {
// On some devices /dev/urandom isn't writable - this isn't fatal
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
private static class LinuxProvider extends Provider {
private LinuxProvider() {
super("LinuxPRNG", 1.1, "A Linux-specific PRNG using /dev/urandom");
// Although /dev/urandom is not a SHA-1 PRNG, some callers
// explicitly request a SHA1PRNG SecureRandom and we need to
// prevent them from getting the default implementation whose
// output may have low entropy.
put("SecureRandom.SHA1PRNG", LinuxSecureRandomSpi.class.getName());
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
}
}
}
\ No newline at end of file
package org.briarproject.bramble.system; package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.SeedProvider;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.security.SecureRandomSpi;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@Immutable public class LinuxSecureRandomSpi extends SecureRandomSpi {
@NotNullByDefault
class LinuxSeedProvider implements SeedProvider {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(LinuxSeedProvider.class.getName()); Logger.getLogger(LinuxSecureRandomSpi.class.getName());
private static final File RANDOM_DEVICE = new File("/dev/urandom");
private final String outputFile, inputFile; private final File inputDevice, outputDevice;
LinuxSeedProvider() { public LinuxSecureRandomSpi() {
this("/dev/urandom", "/dev/urandom"); this(RANDOM_DEVICE, RANDOM_DEVICE);
} }
LinuxSeedProvider(String outputFile, String inputFile) { LinuxSecureRandomSpi(File inputDevice, File outputDevice) {
this.outputFile = outputFile; this.inputDevice = inputDevice;
this.inputFile = inputFile; this.outputDevice = outputDevice;
} }
@Override @Override
public byte[] getSeed() { protected void engineSetSeed(byte[] seed) {
byte[] seed = new byte[SEED_BYTES];
// Contribute whatever slightly unpredictable info we have to the pool
try { try {
DataOutputStream out = new DataOutputStream( DataOutputStream out = new DataOutputStream(
new FileOutputStream(outputFile)); new FileOutputStream(outputDevice));
writeToEntropyPool(out); out.write(seed);
out.flush(); out.flush();
out.close(); out.close();
} catch (IOException e) { } catch (IOException e) {
// On some devices /dev/urandom isn't writable - this isn't fatal // On some devices /dev/urandom isn't writable - this isn't fatal
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} }
// Read the seed from the pool }
@Override
protected void engineNextBytes(byte[] bytes) {
try { try {
DataInputStream in = new DataInputStream( DataInputStream in = new DataInputStream(
new FileInputStream(inputFile)); new FileInputStream(inputDevice));
in.readFully(seed); in.readFully(bytes);
in.close(); in.close();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
return seed;
} }
void writeToEntropyPool(DataOutputStream out) throws IOException { @Override
out.writeLong(System.currentTimeMillis()); protected byte[] engineGenerateSeed(int len) {
out.writeLong(System.nanoTime()); byte[] seed = new byte[len];
List<NetworkInterface> ifaces = engineNextBytes(seed);
Collections.list(NetworkInterface.getNetworkInterfaces()); return seed;
for (NetworkInterface i : ifaces) {
List<InetAddress> addrs = Collections.list(i.getInetAddresses());
for (InetAddress a : addrs) out.write(a.getAddress());
}
} }
} }
...@@ -45,7 +45,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase { ...@@ -45,7 +45,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
byte[] seed = new byte[32]; byte[] seed = new byte[32];
new SecureRandom().nextBytes(seed); new SecureRandom().nextBytes(seed);
// Montgomery ladder multiplier // Montgomery ladder multiplier
SecureRandom random = new FortunaSecureRandom(seed); SecureRandom random = new PseudoSecureRandom(seed);
ECKeyGenerationParameters montgomeryGeneratorParams = ECKeyGenerationParameters montgomeryGeneratorParams =
new ECKeyGenerationParameters(PARAMETERS, random); new ECKeyGenerationParameters(PARAMETERS, random);
ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator(); ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator();
...@@ -63,7 +63,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase { ...@@ -63,7 +63,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
ECPublicKeyParameters montgomeryPublic2 = ECPublicKeyParameters montgomeryPublic2 =
(ECPublicKeyParameters) montgomeryKeyPair2.getPublic(); (ECPublicKeyParameters) montgomeryKeyPair2.getPublic();
// Default multiplier // Default multiplier
random = new FortunaSecureRandom(seed); random = new PseudoSecureRandom(seed);
ECKeyGenerationParameters defaultGeneratorParams = ECKeyGenerationParameters defaultGeneratorParams =
new ECKeyGenerationParameters(defaultParameters, random); new ECKeyGenerationParameters(defaultParameters, random);
ECKeyPairGenerator defaultGenerator = new ECKeyPairGenerator(); ECKeyPairGenerator defaultGenerator = new ECKeyPairGenerator();
......
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.engines.AESLightEngine;
import org.spongycastle.crypto.params.KeyParameter;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class FortunaGeneratorTest extends BrambleTestCase {
@Test
public void testCounterInitialisedToOne() {
FortunaGenerator f = new FortunaGenerator(new byte[32]);
// The counter is little-endian
byte[] expected = new byte[16];
expected[0] = 1;
assertArrayEquals(expected, f.getCounter());
}
@Test
public void testIncrementCounter() {
FortunaGenerator f = new FortunaGenerator(new byte[32]);
// Increment the counter until it reaches 255
for (int i = 1; i < 255; i++) f.incrementCounter();
byte[] expected = new byte[16];
expected[0] = (byte) 255;
assertArrayEquals(expected, f.getCounter());
// Increment the counter again - it should carry into the next byte
f.incrementCounter();
expected[0] = 0;
expected[1] = 1;
assertArrayEquals(expected, f.getCounter());
// Increment the counter until it carries into the next byte
for (int i = 256; i < 65536; i++) f.incrementCounter();
expected[0] = 0;
expected[1] = 0;
expected[2] = 1;
assertArrayEquals(expected, f.getCounter());
}
@Test
public void testNextBytes() {
// Generate several outputs with the same seed - they should all match
byte[] seed = new byte[32];
byte[] out1 = new byte[48];
new FortunaGenerator(seed).nextBytes(out1, 0, 48);
// One byte longer than a block, with an offset of one
byte[] out2 = new byte[49];
new FortunaGenerator(seed).nextBytes(out2, 1, 48);
for (int i = 0; i < 48; i++) assertEquals(out1[i], out2[i + 1]);
// One byte shorter than a block
byte[] out3 = new byte[47];
new FortunaGenerator(seed).nextBytes(out3, 0, 47);
for (int i = 0; i < 47; i++) assertEquals(out1[i], out3[i]);
// Less than a block, with an offset greater than a block
byte[] out4 = new byte[32];
new FortunaGenerator(seed).nextBytes(out4, 17, 15);
for (int i = 0; i < 15; i++) assertEquals(out1[i], out4[i + 17]);
}
@Test
public void testRekeying() {
byte[] seed = new byte[32];
FortunaGenerator f = new FortunaGenerator(seed);
// Generate three blocks of output
byte[] out1 = new byte[48];
f.nextBytes(out1, 0, 48);
// Create another generator with the same seed and generate one block
f = new FortunaGenerator(seed);
byte[] out2 = new byte[16];
f.nextBytes(out2, 0, 16);
// The generator should have rekeyed with the 2nd and 3rd blocks
byte[] expectedKey = new byte[32];
System.arraycopy(out1, 16, expectedKey, 0, 32);
// The generator's counter should have been incremented 3 times
byte[] expectedCounter = new byte[16];
expectedCounter[0] = 4;
// The next expected output block is the counter encrypted with the key
byte[] expectedOutput = new byte[16];
BlockCipher c = new AESLightEngine();
c.init(true, new KeyParameter(expectedKey));
c.processBlock(expectedCounter, 0, expectedOutput, 0);
// Check that the generator produces the expected output block
byte[] out3 = new byte[16];
f.nextBytes(out3, 0, 16);
assertArrayEquals(expectedOutput, out3);
}
@Test
public void testMaximumRequestLength() {
int expectedMax = 1024 * 1024;
byte[] output = new byte[expectedMax + 123];
FortunaGenerator f = new FortunaGenerator(new byte[32]);
assertEquals(expectedMax, f.nextBytes(output, 0, output.length));
}
}
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.engines.AESLightEngine;
import org.spongycastle.crypto.params.KeyParameter;
import static org.briarproject.bramble.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_1;
import static org.briarproject.bramble.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_2;
import static org.briarproject.bramble.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_3;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
public class FortunaSecureRandomTest extends BrambleTestCase {
@Test
public void testClassPassesSelfTest() {
assertTrue(FortunaSecureRandom.selfTest());
}
@Test
public void testSelfTestVectorsAreReproducible() {
byte[] key = new byte[32], seed = new byte[32];
byte[] counter = new byte[16], output = new byte[16];
byte[] newKey = new byte[32];
// Calculate the initial key
DoubleDigest digest = new DoubleDigest(new SHA256Digest());
digest.update(key);
digest.update(seed);
digest.digest(key, 0, 32);
// Calculate the first output block and the new key
BlockCipher c = new AESLightEngine();
c.init(true, new KeyParameter(key));
counter[0] = 1;
c.processBlock(counter, 0, output, 0);
counter[0] = 2;
c.processBlock(counter, 0, newKey, 0);
counter[0] = 3;
c.processBlock(counter, 0, newKey, 16);
System.arraycopy(newKey, 0, key, 0, 32);
// The first self-test vector should match the first output block
assertArrayEquals(SELF_TEST_VECTOR_1, output);
// Calculate the second output block and the new key before reseeding
c.init(true, new KeyParameter(key));
counter[0] = 4;
c.processBlock(counter, 0, output, 0);
counter[0] = 5;
c.processBlock(counter, 0, newKey, 0);
counter[0] = 6;
c.processBlock(counter, 0, newKey, 16);
System.arraycopy(newKey, 0, key, 0, 32);
// The second self-test vector should match the second output block
assertArrayEquals(SELF_TEST_VECTOR_2, output);
// Calculate the new key after reseeding
digest.update(key);
digest.update(seed);
digest.digest(key, 0, 32);
// Calculate the third output block
c.init(true, new KeyParameter(key));
counter[0] = 8;
c.processBlock(counter, 0, output, 0);
// The third self-test vector should match the third output block
assertArrayEquals(SELF_TEST_VECTOR_3, output);
}
}
...@@ -2,7 +2,7 @@ package org.briarproject.bramble.crypto; ...@@ -2,7 +2,7 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSeedProvider; import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils; import org.briarproject.bramble.test.TestUtils;
import org.junit.Test; import org.junit.Test;
...@@ -21,7 +21,7 @@ public class HashTest extends BrambleTestCase { ...@@ -21,7 +21,7 @@ public class HashTest extends BrambleTestCase {
private final byte[] inputBytes2 = new byte[0]; private final byte[] inputBytes2 = new byte[0];
public HashTest() { public HashTest() {
crypto = new CryptoComponentImpl(new TestSeedProvider()); crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
} }
@Test @Test
......
...@@ -3,9 +3,9 @@ package org.briarproject.bramble.crypto; ...@@ -3,9 +3,9 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.system.SeedProvider; import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSeedProvider; import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
...@@ -14,8 +14,9 @@ public class KeyAgreementTest extends BrambleTestCase { ...@@ -14,8 +14,9 @@ public class KeyAgreementTest extends BrambleTestCase {
@Test @Test
public void testDeriveMasterSecret() throws Exception { public void testDeriveMasterSecret() throws Exception {
SeedProvider seedProvider = new TestSeedProvider(); SecureRandomProvider
CryptoComponent crypto = new CryptoComponentImpl(seedProvider); secureRandomProvider = new TestSecureRandomProvider();
CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
KeyPair aPair = crypto.generateAgreementKeyPair(); KeyPair aPair = crypto.generateAgreementKeyPair();
byte[] aPub = aPair.getPublic().getEncoded(); byte[] aPub = aPair.getPublic().getEncoded();
KeyPair bPair = crypto.generateAgreementKeyPair(); KeyPair bPair = crypto.generateAgreementKeyPair();
...@@ -27,8 +28,9 @@ public class KeyAgreementTest extends BrambleTestCase { ...@@ -27,8 +28,9 @@ public class KeyAgreementTest extends BrambleTestCase {
@Test @Test
public void testDeriveSharedSecret() throws Exception { public void testDeriveSharedSecret() throws Exception {
SeedProvider seedProvider = new TestSeedProvider(); SecureRandomProvider
CryptoComponent crypto = new CryptoComponentImpl(seedProvider); secureRandomProvider = new TestSecureRandomProvider();
CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
KeyPair aPair = crypto.generateAgreementKeyPair(); KeyPair aPair = crypto.generateAgreementKeyPair();
byte[] aPub = aPair.getPublic().getEncoded(); byte[] aPub = aPair.getPublic().getEncoded();
KeyPair bPair = crypto.generateAgreementKeyPair(); KeyPair bPair = crypto.generateAgreementKeyPair();
......
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSeedProvider; import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils; import org.briarproject.bramble.test.TestUtils;
import org.junit.Test; import org.junit.Test;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class KeyDerivationTest extends BrambleTestCase { public class KeyDerivationTest extends BrambleTestCase {
...@@ -23,7 +27,7 @@ public class KeyDerivationTest extends BrambleTestCase { ...@@ -23,7 +27,7 @@ public class KeyDerivationTest extends BrambleTestCase {
private final SecretKey master; private final SecretKey master;
public KeyDerivationTest() { public KeyDerivationTest() {
crypto = new CryptoComponentImpl(new TestSeedProvider()); crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
master = TestUtils.getSecretKey(); master = TestUtils.getSecretKey();
} }
...@@ -156,11 +160,7 @@ public class KeyDerivationTest extends BrambleTestCase { ...@@ -156,11 +160,7 @@ public class KeyDerivationTest extends BrambleTestCase {
} }
private void assertAllDifferent(List<SecretKey> keys) { private void assertAllDifferent(List<SecretKey> keys) {
for (SecretKey ki : keys) { Set<Bytes> set = new HashSet<Bytes>();
for (SecretKey kj : keys) { for (SecretKey k : keys) assertTrue(set.add(new Bytes(k.getBytes())));
if (ki == kj) assertArrayEquals(ki.getBytes(), kj.getBytes());
else assertFalse(Arrays.equals(ki.getBytes(), kj.getBytes()));
}
}
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment