Updated PRNGFixes from official blogpost

This commit is contained in:
Dominik Schürmann 2013-09-17 16:53:50 +02:00
parent 0625061018
commit bb657e4add
2 changed files with 120 additions and 97 deletions

View File

@ -350,7 +350,7 @@
android:name=".ui.HelpActivity" android:name=".ui.HelpActivity"
android:label="@string/title_help" /> android:label="@string/title_help" />
<!-- Internal services/content provider (not exported) --> <!-- Internal services/content providers (not exported) -->
<service <service
android:name=".service.PassphraseCacheService" android:name=".service.PassphraseCacheService"
android:exported="false" android:exported="false"
@ -364,7 +364,7 @@
android:authorities="org.sufficientlysecure.keychain.provider" android:authorities="org.sufficientlysecure.keychain.provider"
android:exported="false" /> android:exported="false" />
<!-- Internal classes of OpenPGP (not exported) --> <!-- Internal classes of the remote APIs (not exported) -->
<activity <activity
android:name="org.sufficientlysecure.keychain.service.remote.RemoteServiceActivity" android:name="org.sufficientlysecure.keychain.service.remote.RemoteServiceActivity"
android:exported="false" android:exported="false"
@ -398,7 +398,6 @@
</service> </service>
<!-- Extended Remote API --> <!-- Extended Remote API -->
<service <service
android:name="org.sufficientlysecure.keychain.service.remote.ExtendedApiService" android:name="org.sufficientlysecure.keychain.service.remote.ExtendedApiService"
android:enabled="true" android:enabled="true"
@ -416,7 +415,7 @@
<!-- TODO: authority! Make this API with content provider uris --> <!-- TODO: authority! Make this API with content provider uris -->
<!-- <provider --> <!-- <provider -->
<!-- android:name="org.sufficientlysecure.keychain.provider.KeychainServiceBlobProvider" --> <!-- android:name="org.sufficientlysecure.keychain.provider.KeychainServiceBlobProvider" -->
<!-- android:authorities="org.sufficientlysecure.keychain.provider.apgserviceblobprovider" --> <!-- android:authorities="org.sufficientlysecure.keychain.provider.KeychainServiceBlobProvider" -->
<!-- android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> --> <!-- android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> -->
</application> </application>

View File

@ -12,13 +12,16 @@ package org.sufficientlysecure.keychain.util;
import android.os.Build; import android.os.Build;
import android.os.Process; import android.os.Process;
import android.util.Log;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.Provider; import java.security.Provider;
@ -51,22 +54,27 @@ import java.security.Security;
* random version, now Samsung's SELinux policy also prevents apps from opening * random version, now Samsung's SELinux policy also prevents apps from opening
* /dev/urandom for writing. Since we shouldn't need to write to /dev/urandom anyway * /dev/urandom for writing. Since we shouldn't need to write to /dev/urandom anyway
* we now simply don't. * we now simply don't.
*
*
* Sep 17, 2013:
* Updated from official blogpost:
* Update: the original code sample below crashed on a small fraction of Android
* devices due to /dev/urandom not being writable. We have now updated the code sample to handle this case gracefully.
*/ */
public final class PRNGFixes { public final class PRNGFixes {
private static final int VERSION_CODE_JELLY_BEAN = 16; private static final int VERSION_CODE_JELLY_BEAN = 16;
private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18; private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial(); private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
getBuildFingerprintAndDeviceSerial();
/** Hidden constructor to prevent instantiation. */ /** Hidden constructor to prevent instantiation. */
private PRNGFixes() { private PRNGFixes() {}
}
/** /**
* Applies all fixes. * Applies all fixes.
* *
* @throws SecurityException * @throws SecurityException if a fix is needed but could not be applied.
* if a fix is needed but could not be applied.
*/ */
public static void apply() { public static void apply() {
applyOpenSSLFix(); applyOpenSSLFix();
@ -74,10 +82,10 @@ public final class PRNGFixes {
} }
/** /**
* Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the fix is not needed. * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
* * fix is not needed.
* @throws SecurityException *
* if the fix is needed but could not be applied. * @throws SecurityException if the fix is needed but could not be applied.
*/ */
private static void applyOpenSSLFix() throws SecurityException { private static void applyOpenSSLFix() throws SecurityException {
if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
@ -89,16 +97,18 @@ public final class PRNGFixes {
try { try {
// Mix in the device- and invocation-specific seed. // Mix in the device- and invocation-specific seed.
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_seed", byte[].class).invoke(null, generateSeed()); .getMethod("RAND_seed", byte[].class)
.invoke(null, generateSeed());
// Mix output of Linux PRNG into OpenSSL's PRNG // Mix output of Linux PRNG into OpenSSL's PRNG
int bytesRead = (Integer) Class int bytesRead = (Integer) Class.forName(
.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") "org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_load_file", String.class, long.class) .getMethod("RAND_load_file", String.class, long.class)
.invoke(null, "/dev/urandom", 1024); .invoke(null, "/dev/urandom", 1024);
if (bytesRead != 1024) { if (bytesRead != 1024) {
throw new IOException("Unexpected number of bytes read from Linux PRNG: " throw new IOException(
+ bytesRead); "Unexpected number of bytes read from Linux PRNG: "
+ bytesRead);
} }
} catch (Exception e) { } catch (Exception e) {
throw new SecurityException("Failed to seed OpenSSL PRNG", e); throw new SecurityException("Failed to seed OpenSSL PRNG", e);
@ -106,14 +116,14 @@ public final class PRNGFixes {
} }
/** /**
* Installs a Linux PRNG-backed {@code SecureRandom} implementation as the default. Does nothing * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
* if the implementation is already the default or if there is not need to install the * default. Does nothing if the implementation is already the default or if
* implementation. * there is not need to install the implementation.
* *
* @throws SecurityException * @throws SecurityException if the fix is needed but could not be applied.
* if the fix is needed but could not be applied.
*/ */
private static void installLinuxPRNGSecureRandom() throws SecurityException { private static void installLinuxPRNGSecureRandom()
throws SecurityException {
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) { if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
// No need to apply the fix // No need to apply the fix
return; return;
@ -121,11 +131,12 @@ public final class PRNGFixes {
// Install a Linux PRNG-based SecureRandom implementation as the // Install a Linux PRNG-based SecureRandom implementation as the
// default, if not yet installed. // default, if not yet installed.
Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG"); Provider[] secureRandomProviders =
Security.getProviders("SecureRandom.SHA1PRNG");
if ((secureRandomProviders == null) if ((secureRandomProviders == null)
|| (secureRandomProviders.length < 1) || (secureRandomProviders.length < 1)
|| (!LinuxPRNGSecureRandomProvider.class || (!LinuxPRNGSecureRandomProvider.class.equals(
.equals(secureRandomProviders[0].getClass()))) { secureRandomProviders[0].getClass()))) {
Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1); Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
} }
@ -133,9 +144,11 @@ public final class PRNGFixes {
// SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
// by the Linux PRNG-based SecureRandom implementation. // by the Linux PRNG-based SecureRandom implementation.
SecureRandom rng1 = new SecureRandom(); SecureRandom rng1 = new SecureRandom();
if (!LinuxPRNGSecureRandomProvider.class.equals(rng1.getProvider().getClass())) { if (!LinuxPRNGSecureRandomProvider.class.equals(
throw new SecurityException("new SecureRandom() backed by wrong Provider: " rng1.getProvider().getClass())) {
+ rng1.getProvider().getClass()); throw new SecurityException(
"new SecureRandom() backed by wrong Provider: "
+ rng1.getProvider().getClass());
} }
SecureRandom rng2; SecureRandom rng2;
@ -144,22 +157,25 @@ public final class PRNGFixes {
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
throw new SecurityException("SHA1PRNG not available", e); throw new SecurityException("SHA1PRNG not available", e);
} }
if (!LinuxPRNGSecureRandomProvider.class.equals(rng2.getProvider().getClass())) { if (!LinuxPRNGSecureRandomProvider.class.equals(
throw new SecurityException("SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" rng2.getProvider().getClass())) {
throw new SecurityException(
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
+ " Provider: " + rng2.getProvider().getClass()); + " Provider: " + rng2.getProvider().getClass());
} }
} }
/** /**
* {@code Provider} of {@code SecureRandom} engines which pass through all requests to the Linux * {@code Provider} of {@code SecureRandom} engines which pass through
* PRNG. * all requests to the Linux PRNG.
*/ */
@SuppressWarnings("serial")
private static class LinuxPRNGSecureRandomProvider extends Provider { private static class LinuxPRNGSecureRandomProvider extends Provider {
public LinuxPRNGSecureRandomProvider() { public LinuxPRNGSecureRandomProvider() {
super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses" super("LinuxPRNG",
+ " /dev/urandom"); 1.0,
"A Linux-specific random number provider that uses"
+ " /dev/urandom");
// Although /dev/urandom is not a SHA-1 PRNG, some apps // Although /dev/urandom is not a SHA-1 PRNG, some apps
// explicitly request a SHA1PRNG SecureRandom and we thus need to // explicitly request a SHA1PRNG SecureRandom and we thus need to
// prevent them from getting the default implementation whose output // prevent them from getting the default implementation whose output
@ -170,19 +186,21 @@ public final class PRNGFixes {
} }
/** /**
* {@link SecureRandomSpi} which passes all requests to the Linux PRNG ({@code /dev/urandom}). * {@link SecureRandomSpi} which passes all requests to the Linux PRNG
* ({@code /dev/urandom}).
*/ */
@SuppressWarnings("serial")
public static class LinuxPRNGSecureRandom extends SecureRandomSpi { public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
/* /*
* IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed are passed through * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
* to the Linux PRNG (/dev/urandom). Instances of this class seed themselves by mixing in * are passed through to the Linux PRNG (/dev/urandom). Instances of
* the current time, PID, UID, build fingerprint, and hardware serial number (where * this class seed themselves by mixing in the current time, PID, UID,
* available) into Linux PRNG. * build fingerprint, and hardware serial number (where available) into
* * Linux PRNG.
* Concurrency: Read requests to the underlying Linux PRNG are serialized (on sLock) to *
* ensure that multiple threads do not get duplicated PRNG output. * Concurrency: Read requests to the underlying Linux PRNG are
* serialized (on sLock) to ensure that multiple threads do not get
* duplicated PRNG output.
*/ */
private static final File URANDOM_FILE = new File("/dev/urandom"); private static final File URANDOM_FILE = new File("/dev/urandom");
@ -190,46 +208,53 @@ public final class PRNGFixes {
private static final Object sLock = new Object(); private static final Object sLock = new Object();
/** /**
* Input stream for reading from Linux PRNG or {@code null} if not yet opened. * Input stream for reading from Linux PRNG or {@code null} if not yet
* * opened.
*
* @GuardedBy("sLock") * @GuardedBy("sLock")
*/ */
private static DataInputStream sUrandomIn; private static DataInputStream sUrandomIn;
// /** /**
// * Output stream for writing to Linux PRNG or {@code null} if not yet opened. * Output stream for writing to Linux PRNG or {@code null} if not yet
// * * opened.
// * @GuardedBy("sLock") *
// */ * @GuardedBy("sLock")
// private static OutputStream sUrandomOut; */
// private static OutputStream sUrandomOut;
// /**
// * Whether this engine instance has been seeded. This is needed because each instance needs /**
// * to seed itself if the client does not explicitly seed it. * Whether this engine instance has been seeded. This is needed because
// */ * each instance needs to seed itself if the client does not explicitly
// private boolean mSeeded; * seed it.
*/
private boolean mSeeded;
@Override @Override
protected void engineSetSeed(byte[] bytes) { protected void engineSetSeed(byte[] bytes) {
// try { try {
// OutputStream out; OutputStream out;
// synchronized (sLock) { synchronized (sLock) {
// out = getUrandomOutputStream(); out = getUrandomOutputStream();
// } }
// out.write(bytes); out.write(bytes);
// out.flush(); out.flush();
// mSeeded = true; } catch (IOException e) {
// } catch (IOException e) { // On a small fraction of devices /dev/urandom is not writable.
// throw new SecurityException("Failed to mix seed into " + URANDOM_FILE, e); // Log and ignore.
// } Log.w(PRNGFixes.class.getSimpleName(),
"Failed to mix seed into " + URANDOM_FILE);
} finally {
mSeeded = true;
}
} }
@Override @Override
protected void engineNextBytes(byte[] bytes) { protected void engineNextBytes(byte[] bytes) {
// if (!mSeeded) { if (!mSeeded) {
// // Mix in the device- and invocation-specific seed. // Mix in the device- and invocation-specific seed.
// engineSetSeed(generateSeed()); engineSetSeed(generateSeed());
// } }
try { try {
DataInputStream in; DataInputStream in;
@ -240,7 +265,8 @@ public final class PRNGFixes {
in.readFully(bytes); in.readFully(bytes);
} }
} catch (IOException e) { } catch (IOException e) {
throw new SecurityException("Failed to read from " + URANDOM_FILE, e); throw new SecurityException(
"Failed to read from " + URANDOM_FILE, e);
} }
} }
@ -259,38 +285,36 @@ public final class PRNGFixes {
// PRNG output performance and can live with future PRNG // PRNG output performance and can live with future PRNG
// output being pulled into this process prematurely. // output being pulled into this process prematurely.
try { try {
sUrandomIn = new DataInputStream(new FileInputStream(URANDOM_FILE)); sUrandomIn = new DataInputStream(
new FileInputStream(URANDOM_FILE));
} catch (IOException e) { } catch (IOException e) {
throw new SecurityException("Failed to open " + URANDOM_FILE throw new SecurityException("Failed to open "
+ " for reading", e); + URANDOM_FILE + " for reading", e);
} }
} }
return sUrandomIn; return sUrandomIn;
} }
} }
// private OutputStream getUrandomOutputStream() { private OutputStream getUrandomOutputStream() throws IOException {
// synchronized (sLock) { synchronized (sLock) {
// if (sUrandomOut == null) { if (sUrandomOut == null) {
// try { sUrandomOut = new FileOutputStream(URANDOM_FILE);
// sUrandomOut = new FileOutputStream(URANDOM_FILE); }
// } catch (IOException e) { return sUrandomOut;
// throw new SecurityException("Failed to open " + URANDOM_FILE }
// + " for writing", e); }
// }
// }
// return sUrandomOut;
// }
// }
} }
/** /**
* Generates a device- and invocation-specific seed to be mixed into the Linux PRNG. * Generates a device- and invocation-specific seed to be mixed into the
* Linux PRNG.
*/ */
private static byte[] generateSeed() { private static byte[] generateSeed() {
try { try {
ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer); DataOutputStream seedBufferOut =
new DataOutputStream(seedBuffer);
seedBufferOut.writeLong(System.currentTimeMillis()); seedBufferOut.writeLong(System.currentTimeMillis());
seedBufferOut.writeLong(System.nanoTime()); seedBufferOut.writeLong(System.nanoTime());
seedBufferOut.writeInt(Process.myPid()); seedBufferOut.writeInt(Process.myPid());
@ -305,7 +329,7 @@ public final class PRNGFixes {
/** /**
* Gets the hardware serial number of this device. * Gets the hardware serial number of this device.
* *
* @return serial number or {@code null} if not available. * @return serial number or {@code null} if not available.
*/ */
private static String getDeviceSerialNumber() { private static String getDeviceSerialNumber() {