mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Merge branch '914-simpler-secure-random' into 'master'
Remove Fortuna generator, fix Android SecureRandom bug Closes #914 See merge request !500
This commit is contained in:
@@ -1,62 +0,0 @@
|
||||
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;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
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.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
@@ -29,7 +29,10 @@ import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -101,16 +104,26 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
private final MessageEncrypter messageEncrypter;
|
||||
|
||||
@Inject
|
||||
CryptoComponentImpl(SeedProvider seedProvider) {
|
||||
if (!FortunaSecureRandom.selfTest()) throw new RuntimeException();
|
||||
SecureRandom platformSecureRandom = new SecureRandom();
|
||||
CryptoComponentImpl(SecureRandomProvider secureRandomProvider) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
String provider = platformSecureRandom.getProvider().getName();
|
||||
String algorithm = platformSecureRandom.getAlgorithm();
|
||||
LOG.info("Default SecureRandom: " + provider + " " + algorithm);
|
||||
SecureRandom defaultSecureRandom = new SecureRandom();
|
||||
String name = defaultSecureRandom.getProvider().getName();
|
||||
String algorithm = defaultSecureRandom.getAlgorithm();
|
||||
LOG.info("Default SecureRandom: " + name + " " + algorithm);
|
||||
}
|
||||
SecureRandom fortuna = new FortunaSecureRandom(seedProvider.getSeed());
|
||||
secureRandom = new CombinedSecureRandom(platformSecureRandom, fortuna);
|
||||
Provider provider = secureRandomProvider.getProvider();
|
||||
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(
|
||||
PARAMETERS, secureRandom);
|
||||
agreementKeyPairGenerator = new ECKeyPairGenerator();
|
||||
@@ -124,6 +137,31 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
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
|
||||
public SecretKey generateSecretKey() {
|
||||
byte[] b = new byte[SecretKey.LENGTH];
|
||||
@@ -133,7 +171,10 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
|
||||
@Override
|
||||
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
|
||||
@@ -296,7 +337,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
public SecretKey deriveMasterSecret(byte[] theirPublicKey,
|
||||
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
|
||||
return deriveMasterSecret(deriveSharedSecret(
|
||||
theirPublicKey,ourKeyPair, alice));
|
||||
theirPublicKey, ourKeyPair, alice));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -607,7 +648,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
|
||||
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];
|
||||
int keyLengthInBits = SecretKey.LENGTH * 8;
|
||||
long start = System.nanoTime();
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
||||
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
|
||||
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
|
||||
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.util.concurrent.BlockingQueue;
|
||||
@@ -60,8 +60,9 @@ public class CryptoModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
CryptoComponent provideCryptoComponent(SeedProvider seedProvider) {
|
||||
return new CryptoComponentImpl(seedProvider);
|
||||
CryptoComponent provideCryptoComponent(
|
||||
SecureRandomProvider secureRandomProvider) {
|
||||
return new CryptoComponentImpl(secureRandomProvider);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
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;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
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 static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
||||
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
class PseudoRandomImpl implements PseudoRandom {
|
||||
|
||||
private final FortunaGenerator generator;
|
||||
private final Salsa20Engine cipher = new Salsa20Engine();
|
||||
|
||||
PseudoRandomImpl(int seed1, int seed2) {
|
||||
byte[] seed = new byte[INT_32_BYTES * 2];
|
||||
ByteUtils.writeUint32(seed1, seed, 0);
|
||||
ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
|
||||
generator = new FortunaGenerator(seed);
|
||||
PseudoRandomImpl(byte[] seed) {
|
||||
// Hash the seed to produce a 32-byte key
|
||||
byte[] key = new byte[32];
|
||||
Digest digest = new Blake2sDigest();
|
||||
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
|
||||
public byte[] nextBytes(int length) {
|
||||
byte[] b = new byte[length];
|
||||
int offset = 0;
|
||||
while (offset < length) offset += generator.nextBytes(b, offset, length);
|
||||
return b;
|
||||
byte[] in = new byte[length], out = new byte[length];
|
||||
cipher.processBytes(in, 0, length, out, 0);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandomSpi;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
public class LinuxSecureRandomSpi extends SecureRandomSpi {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(LinuxSecureRandomSpi.class.getName());
|
||||
|
||||
private static final File RANDOM_DEVICE = new File("/dev/urandom");
|
||||
|
||||
private final File inputDevice, outputDevice;
|
||||
|
||||
public LinuxSecureRandomSpi() {
|
||||
this(RANDOM_DEVICE, RANDOM_DEVICE);
|
||||
}
|
||||
|
||||
LinuxSecureRandomSpi(File inputDevice, File outputDevice) {
|
||||
this.inputDevice = inputDevice;
|
||||
this.outputDevice = outputDevice;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineSetSeed(byte[] seed) {
|
||||
try {
|
||||
DataOutputStream out = new DataOutputStream(
|
||||
new FileOutputStream(outputDevice));
|
||||
out.write(seed);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineNextBytes(byte[] bytes) {
|
||||
try {
|
||||
DataInputStream in = new DataInputStream(
|
||||
new FileInputStream(inputDevice));
|
||||
in.readFully(bytes);
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] engineGenerateSeed(int len) {
|
||||
byte[] seed = new byte[len];
|
||||
engineNextBytes(seed);
|
||||
return seed;
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
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.DataOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class LinuxSeedProvider implements SeedProvider {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(LinuxSeedProvider.class.getName());
|
||||
|
||||
private final String outputFile, inputFile;
|
||||
|
||||
LinuxSeedProvider() {
|
||||
this("/dev/urandom", "/dev/urandom");
|
||||
}
|
||||
|
||||
LinuxSeedProvider(String outputFile, String inputFile) {
|
||||
this.outputFile = outputFile;
|
||||
this.inputFile = inputFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSeed() {
|
||||
byte[] seed = new byte[SEED_BYTES];
|
||||
// Contribute whatever slightly unpredictable info we have to the pool
|
||||
try {
|
||||
DataOutputStream out = new DataOutputStream(
|
||||
new FileOutputStream(outputFile));
|
||||
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);
|
||||
}
|
||||
// Read the seed from the pool
|
||||
try {
|
||||
DataInputStream in = new DataInputStream(
|
||||
new FileInputStream(inputFile));
|
||||
in.readFully(seed);
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
|
||||
void writeToEntropyPool(DataOutputStream out) throws IOException {
|
||||
out.writeLong(System.currentTimeMillis());
|
||||
out.writeLong(System.nanoTime());
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
|
||||
byte[] seed = new byte[32];
|
||||
new SecureRandom().nextBytes(seed);
|
||||
// Montgomery ladder multiplier
|
||||
SecureRandom random = new FortunaSecureRandom(seed);
|
||||
SecureRandom random = new PseudoSecureRandom(seed);
|
||||
ECKeyGenerationParameters montgomeryGeneratorParams =
|
||||
new ECKeyGenerationParameters(PARAMETERS, random);
|
||||
ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator();
|
||||
@@ -63,7 +63,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
|
||||
ECPublicKeyParameters montgomeryPublic2 =
|
||||
(ECPublicKeyParameters) montgomeryKeyPair2.getPublic();
|
||||
// Default multiplier
|
||||
random = new FortunaSecureRandom(seed);
|
||||
random = new PseudoSecureRandom(seed);
|
||||
ECKeyGenerationParameters defaultGeneratorParams =
|
||||
new ECKeyGenerationParameters(defaultParameters, random);
|
||||
ECKeyPairGenerator defaultGenerator = new ECKeyPairGenerator();
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
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;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
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.junit.Test;
|
||||
|
||||
@@ -21,7 +21,7 @@ public class HashTest extends BrambleTestCase {
|
||||
private final byte[] inputBytes2 = new byte[0];
|
||||
|
||||
public HashTest() {
|
||||
crypto = new CryptoComponentImpl(new TestSeedProvider());
|
||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -3,9 +3,9 @@ package org.briarproject.bramble.crypto;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
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.TestSeedProvider;
|
||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
@@ -14,8 +14,9 @@ public class KeyAgreementTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testDeriveMasterSecret() throws Exception {
|
||||
SeedProvider seedProvider = new TestSeedProvider();
|
||||
CryptoComponent crypto = new CryptoComponentImpl(seedProvider);
|
||||
SecureRandomProvider
|
||||
secureRandomProvider = new TestSecureRandomProvider();
|
||||
CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
|
||||
KeyPair aPair = crypto.generateAgreementKeyPair();
|
||||
byte[] aPub = aPair.getPublic().getEncoded();
|
||||
KeyPair bPair = crypto.generateAgreementKeyPair();
|
||||
@@ -27,8 +28,9 @@ public class KeyAgreementTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testDeriveSharedSecret() throws Exception {
|
||||
SeedProvider seedProvider = new TestSeedProvider();
|
||||
CryptoComponent crypto = new CryptoComponentImpl(seedProvider);
|
||||
SecureRandomProvider
|
||||
secureRandomProvider = new TestSecureRandomProvider();
|
||||
CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
|
||||
KeyPair aPair = crypto.generateAgreementKeyPair();
|
||||
byte[] aPub = aPair.getPublic().getEncoded();
|
||||
KeyPair bPair = crypto.generateAgreementKeyPair();
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
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.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class KeyDerivationTest extends BrambleTestCase {
|
||||
|
||||
@@ -23,7 +27,7 @@ public class KeyDerivationTest extends BrambleTestCase {
|
||||
private final SecretKey master;
|
||||
|
||||
public KeyDerivationTest() {
|
||||
crypto = new CryptoComponentImpl(new TestSeedProvider());
|
||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||
master = TestUtils.getSecretKey();
|
||||
}
|
||||
|
||||
@@ -156,11 +160,7 @@ public class KeyDerivationTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
private void assertAllDifferent(List<SecretKey> keys) {
|
||||
for (SecretKey ki : keys) {
|
||||
for (SecretKey kj : keys) {
|
||||
if (ki == kj) assertArrayEquals(ki.getBytes(), kj.getBytes());
|
||||
else assertFalse(Arrays.equals(ki.getBytes(), kj.getBytes()));
|
||||
}
|
||||
}
|
||||
Set<Bytes> set = new HashSet<Bytes>();
|
||||
for (SecretKey k : keys) assertTrue(set.add(new Bytes(k.getBytes())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
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.junit.Test;
|
||||
|
||||
@@ -19,7 +19,7 @@ import static org.junit.Assert.assertTrue;
|
||||
public class KeyEncodingAndParsingTest extends BrambleTestCase {
|
||||
|
||||
private final CryptoComponentImpl crypto =
|
||||
new CryptoComponentImpl(new TestSeedProvider());
|
||||
new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||
|
||||
@Test
|
||||
public void testAgreementPublicKeyLength() throws Exception {
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.briarproject.bramble.crypto;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
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.junit.Test;
|
||||
|
||||
@@ -22,7 +22,7 @@ public class MacTest extends BrambleTestCase {
|
||||
private final byte[] inputBytes2 = new byte[0];
|
||||
|
||||
public MacTest() {
|
||||
crypto = new CryptoComponentImpl(new TestSeedProvider());
|
||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
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.junit.Test;
|
||||
|
||||
@@ -15,7 +15,7 @@ import static org.junit.Assert.assertTrue;
|
||||
public class PasswordBasedKdfTest extends BrambleTestCase {
|
||||
|
||||
private final CryptoComponentImpl crypto =
|
||||
new CryptoComponentImpl(new TestSeedProvider());
|
||||
new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||
|
||||
@Test
|
||||
public void testEncryptionAndDecryption() {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SecureRandomSpi;
|
||||
|
||||
class PseudoSecureRandom extends SecureRandom {
|
||||
|
||||
private static final Provider PROVIDER = new PseudoSecureRandomProvider();
|
||||
|
||||
PseudoSecureRandom(byte[] seed) {
|
||||
super(new PseudoSecureRandomSpi(seed), PROVIDER);
|
||||
}
|
||||
|
||||
private static class PseudoSecureRandomSpi extends SecureRandomSpi {
|
||||
|
||||
private final PseudoRandom pseudoRandom;
|
||||
|
||||
private PseudoSecureRandomSpi(byte[] seed) {
|
||||
pseudoRandom = new PseudoRandomImpl(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] engineGenerateSeed(int length) {
|
||||
return pseudoRandom.nextBytes(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineNextBytes(byte[] b) {
|
||||
byte[] random = pseudoRandom.nextBytes(b.length);
|
||||
System.arraycopy(random, 0, b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineSetSeed(byte[] seed) {
|
||||
// Thank you for your input
|
||||
}
|
||||
}
|
||||
|
||||
private static class PseudoSecureRandomProvider extends Provider {
|
||||
|
||||
private PseudoSecureRandomProvider() {
|
||||
super("PseudoSecureRandom", 1.0, "Only for testing");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package org.briarproject.bramble.crypto;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
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.junit.Test;
|
||||
|
||||
@@ -22,7 +22,7 @@ public class SignatureTest extends BrambleTestCase {
|
||||
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
|
||||
|
||||
public SignatureTest() {
|
||||
crypto = new CryptoComponentImpl(new TestSeedProvider());
|
||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||
KeyPair k = crypto.generateSignatureKeyPair();
|
||||
publicKey = k.getPublic().getEncoded();
|
||||
privateKey = k.getPrivate().getEncoded();
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.OsUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.Provider;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class LinuxSecureRandomProviderTest extends BrambleTestCase {
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testDir.mkdirs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetProviderWritesToRandomDeviceOnFirstCall()
|
||||
throws Exception {
|
||||
if (!(OsUtils.isLinux())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
// Redirect the provider's output to a file
|
||||
File urandom = new File(testDir, "urandom");
|
||||
urandom.delete();
|
||||
assertTrue(urandom.createNewFile());
|
||||
assertEquals(0, urandom.length());
|
||||
LinuxSecureRandomProvider p = new LinuxSecureRandomProvider(urandom);
|
||||
// Getting a provider should write entropy to the file
|
||||
Provider provider = p.getProvider();
|
||||
assertNotNull(provider);
|
||||
assertEquals("LinuxPRNG", provider.getName());
|
||||
// There should be at least 16 bytes from the clock, 8 from the runtime
|
||||
long length = urandom.length();
|
||||
assertTrue(length >= 24);
|
||||
// Getting another provider should not write to the file again
|
||||
provider = p.getProvider();
|
||||
assertNotNull(provider);
|
||||
assertEquals("LinuxPRNG", provider.getName());
|
||||
assertEquals(length, urandom.length());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.IoUtils;
|
||||
import org.briarproject.bramble.util.OsUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
||||
public class LinuxSecureRandomSpiTest extends BrambleTestCase {
|
||||
|
||||
private static final File RANDOM_DEVICE = new File("/dev/urandom");
|
||||
private static final int SEED_BYTES = 32;
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testDir.mkdirs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeedsAreDistinct() {
|
||||
if (!(OsUtils.isLinux())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
Set<Bytes> seeds = new HashSet<Bytes>();
|
||||
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
byte[] seed = engine.engineGenerateSeed(SEED_BYTES);
|
||||
assertEquals(SEED_BYTES, seed.length);
|
||||
assertTrue(seeds.add(new Bytes(seed)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEngineSetSeedWritesToRandomDevice() throws Exception {
|
||||
if (!(OsUtils.isLinux())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
// Redirect the engine's output to a file
|
||||
File urandom = new File(testDir, "urandom");
|
||||
urandom.delete();
|
||||
assertTrue(urandom.createNewFile());
|
||||
assertEquals(0, urandom.length());
|
||||
// Generate a seed
|
||||
byte[] seed = TestUtils.getRandomBytes(SEED_BYTES);
|
||||
// Check that the engine writes the seed to the file
|
||||
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(RANDOM_DEVICE,
|
||||
urandom);
|
||||
engine.engineSetSeed(seed);
|
||||
assertEquals(SEED_BYTES, urandom.length());
|
||||
byte[] written = new byte[SEED_BYTES];
|
||||
FileInputStream in = new FileInputStream(urandom);
|
||||
IoUtils.read(in, written);
|
||||
in.close();
|
||||
assertArrayEquals(seed, written);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEngineNextBytesReadsFromRandomDevice() throws Exception {
|
||||
if (!(OsUtils.isLinux())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
// Generate some entropy
|
||||
byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
|
||||
// Write the entropy to a file
|
||||
File urandom = new File(testDir, "urandom");
|
||||
urandom.delete();
|
||||
FileOutputStream out = new FileOutputStream(urandom);
|
||||
out.write(entropy);
|
||||
out.flush();
|
||||
out.close();
|
||||
assertTrue(urandom.exists());
|
||||
assertEquals(SEED_BYTES, urandom.length());
|
||||
// Check that the engine reads from the file
|
||||
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(urandom,
|
||||
RANDOM_DEVICE);
|
||||
byte[] b = new byte[SEED_BYTES];
|
||||
engine.engineNextBytes(b);
|
||||
assertArrayEquals(entropy, b);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEngineGenerateSeedReadsFromRandomDevice() throws Exception {
|
||||
if (!(OsUtils.isLinux())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
// Generate some entropy
|
||||
byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
|
||||
// Write the entropy to a file
|
||||
File urandom = new File(testDir, "urandom");
|
||||
urandom.delete();
|
||||
FileOutputStream out = new FileOutputStream(urandom);
|
||||
out.write(entropy);
|
||||
out.flush();
|
||||
out.close();
|
||||
assertTrue(urandom.exists());
|
||||
assertEquals(SEED_BYTES, urandom.length());
|
||||
// Check that the engine reads from the file
|
||||
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(urandom,
|
||||
RANDOM_DEVICE);
|
||||
byte[] b = engine.engineGenerateSeed(SEED_BYTES);
|
||||
assertArrayEquals(entropy, b);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.OsUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.briarproject.bramble.api.system.SeedProvider.SEED_BYTES;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class LinuxSeedProviderTest extends BrambleTestCase {
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testDir.mkdirs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeedAppearsSane() {
|
||||
if (!(OsUtils.isLinux())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
Set<Bytes> seeds = new HashSet<Bytes>();
|
||||
LinuxSeedProvider p = new LinuxSeedProvider();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
byte[] seed = p.getSeed();
|
||||
assertEquals(SEED_BYTES, seed.length);
|
||||
assertTrue(seeds.add(new Bytes(seed)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntropyIsWrittenToPool() throws Exception {
|
||||
if (!(OsUtils.isLinux())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
// Redirect the provider's entropy to a file
|
||||
File urandom = new File(testDir, "urandom");
|
||||
urandom.delete();
|
||||
assertTrue(urandom.createNewFile());
|
||||
assertEquals(0, urandom.length());
|
||||
String path = urandom.getAbsolutePath();
|
||||
LinuxSeedProvider p = new LinuxSeedProvider(path, "/dev/urandom");
|
||||
p.getSeed();
|
||||
// There should be 16 bytes from the clock, plus network interfaces
|
||||
assertTrue(urandom.length() > 20);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeedIsReadFromPool() throws Exception {
|
||||
if (!(OsUtils.isLinux())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
// Generate a seed
|
||||
byte[] seed = TestUtils.getRandomBytes(SEED_BYTES);
|
||||
// Write the seed to a file
|
||||
File urandom = new File(testDir, "urandom");
|
||||
urandom.delete();
|
||||
FileOutputStream out = new FileOutputStream(urandom);
|
||||
out.write(seed);
|
||||
out.flush();
|
||||
out.close();
|
||||
assertTrue(urandom.exists());
|
||||
assertEquals(SEED_BYTES, urandom.length());
|
||||
// Check that the provider reads the seed from the file
|
||||
String path = urandom.getAbsolutePath();
|
||||
LinuxSeedProvider p = new LinuxSeedProvider("/dev/urandom", path);
|
||||
assertArrayEquals(seed, p.getSeed());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
|
||||
import java.security.Provider;
|
||||
|
||||
@NotNullByDefault
|
||||
public class TestSecureRandomProvider implements SecureRandomProvider {
|
||||
|
||||
@Override
|
||||
public Provider getProvider() {
|
||||
// Use the default provider
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.SeedProvider;
|
||||
|
||||
@NotNullByDefault
|
||||
public class TestSeedProvider implements SeedProvider {
|
||||
|
||||
@Override
|
||||
public byte[] getSeed() {
|
||||
return TestUtils.getRandomBytes(32);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.api.system.SeedProvider;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@@ -12,7 +12,7 @@ public class TestSeedProviderModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
SeedProvider provideSeedProvider() {
|
||||
return new TestSeedProvider();
|
||||
SecureRandomProvider provideSeedProvider() {
|
||||
return new TestSecureRandomProvider();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user