diff --git a/OpenPGP-Keychain/AndroidManifest.xml b/OpenPGP-Keychain/AndroidManifest.xml index fdaf3b3e3..a38d9b071 100644 --- a/OpenPGP-Keychain/AndroidManifest.xml +++ b/OpenPGP-Keychain/AndroidManifest.xml @@ -350,7 +350,7 @@ android:name=".ui.HelpActivity" android:label="@string/title_help" /> - + - + - - + diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/util/PRNGFixes.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/util/PRNGFixes.java index a7919e5e3..530a81044 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/util/PRNGFixes.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/util/PRNGFixes.java @@ -12,13 +12,16 @@ package org.sufficientlysecure.keychain.util; import android.os.Build; import android.os.Process; +import android.util.Log; import java.io.ByteArrayOutputStream; 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.io.OutputStream; import java.io.UnsupportedEncodingException; import java.security.NoSuchAlgorithmException; import java.security.Provider; @@ -51,22 +54,27 @@ import java.security.Security; * 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 * 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 { private static final int VERSION_CODE_JELLY_BEAN = 16; 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. */ - private PRNGFixes() { - } + private PRNGFixes() {} /** * Applies all fixes. - * - * @throws SecurityException - * if a fix is needed but could not be applied. + * + * @throws SecurityException if a fix is needed but could not be applied. */ public static void apply() { 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. - * - * @throws SecurityException - * if the fix is needed but could not be applied. + * 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. */ private static void applyOpenSSLFix() throws SecurityException { if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) @@ -89,16 +97,18 @@ public final class PRNGFixes { try { // Mix in the device- and invocation-specific seed. 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 - int bytesRead = (Integer) Class - .forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") + 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("Unexpected number of bytes read from Linux PRNG: " - + bytesRead); + throw new IOException( + "Unexpected number of bytes read from Linux PRNG: " + + bytesRead); } } catch (Exception 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 - * if the implementation is already the default or if there is not need to install the - * implementation. - * - * @throws SecurityException - * if the fix is needed but could not be applied. + * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the + * default. Does nothing if the implementation is already the default or if + * there is not need to install the implementation. + * + * @throws SecurityException 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) { // No need to apply the fix return; @@ -121,11 +131,12 @@ public final class PRNGFixes { // Install a Linux PRNG-based SecureRandom implementation as the // default, if not yet installed. - Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG"); + Provider[] secureRandomProviders = + Security.getProviders("SecureRandom.SHA1PRNG"); if ((secureRandomProviders == null) || (secureRandomProviders.length < 1) - || (!LinuxPRNGSecureRandomProvider.class - .equals(secureRandomProviders[0].getClass()))) { + || (!LinuxPRNGSecureRandomProvider.class.equals( + secureRandomProviders[0].getClass()))) { Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1); } @@ -133,9 +144,11 @@ public final class PRNGFixes { // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed // by the Linux PRNG-based SecureRandom implementation. SecureRandom rng1 = new SecureRandom(); - if (!LinuxPRNGSecureRandomProvider.class.equals(rng1.getProvider().getClass())) { - throw new SecurityException("new SecureRandom() backed by wrong Provider: " - + rng1.getProvider().getClass()); + if (!LinuxPRNGSecureRandomProvider.class.equals( + rng1.getProvider().getClass())) { + throw new SecurityException( + "new SecureRandom() backed by wrong Provider: " + + rng1.getProvider().getClass()); } SecureRandom rng2; @@ -144,22 +157,25 @@ public final class PRNGFixes { } catch (NoSuchAlgorithmException e) { throw new SecurityException("SHA1PRNG not available", e); } - if (!LinuxPRNGSecureRandomProvider.class.equals(rng2.getProvider().getClass())) { - throw new SecurityException("SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + if (!LinuxPRNGSecureRandomProvider.class.equals( + rng2.getProvider().getClass())) { + throw new SecurityException( + "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: " + rng2.getProvider().getClass()); } } /** - * {@code Provider} of {@code SecureRandom} engines which pass through all requests to the Linux - * PRNG. + * {@code Provider} of {@code SecureRandom} engines which pass through + * all requests to the Linux PRNG. */ - @SuppressWarnings("serial") private static class LinuxPRNGSecureRandomProvider extends Provider { public LinuxPRNGSecureRandomProvider() { - super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses" - + " /dev/urandom"); + super("LinuxPRNG", + 1.0, + "A Linux-specific random number provider that uses" + + " /dev/urandom"); // Although /dev/urandom is not a SHA-1 PRNG, some apps // explicitly request a SHA1PRNG SecureRandom and we thus need to // 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 { /* - * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed are passed through - * to the Linux PRNG (/dev/urandom). Instances of this class seed themselves by mixing in - * the current time, PID, UID, 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. + * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed + * are passed through to the Linux PRNG (/dev/urandom). Instances of + * this class seed themselves by mixing in the current time, PID, UID, + * 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. */ 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(); /** - * 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") */ private static DataInputStream sUrandomIn; -// /** -// * Output stream for writing to Linux PRNG or {@code null} if not yet opened. -// * -// * @GuardedBy("sLock") -// */ -// 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. -// */ -// private boolean mSeeded; + /** + * Output stream for writing to Linux PRNG or {@code null} if not yet + * opened. + * + * @GuardedBy("sLock") + */ + 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. + */ + private boolean mSeeded; @Override protected void engineSetSeed(byte[] bytes) { -// try { -// OutputStream out; -// synchronized (sLock) { -// out = getUrandomOutputStream(); -// } -// out.write(bytes); -// out.flush(); -// mSeeded = true; -// } catch (IOException e) { -// throw new SecurityException("Failed to mix seed into " + URANDOM_FILE, e); -// } + try { + OutputStream out; + synchronized (sLock) { + out = getUrandomOutputStream(); + } + out.write(bytes); + out.flush(); + } catch (IOException e) { + // On a small fraction of devices /dev/urandom is not writable. + // Log and ignore. + Log.w(PRNGFixes.class.getSimpleName(), + "Failed to mix seed into " + URANDOM_FILE); + } finally { + mSeeded = true; + } } @Override protected void engineNextBytes(byte[] bytes) { -// if (!mSeeded) { -// // Mix in the device- and invocation-specific seed. -// engineSetSeed(generateSeed()); -// } + if (!mSeeded) { + // Mix in the device- and invocation-specific seed. + engineSetSeed(generateSeed()); + } try { DataInputStream in; @@ -240,7 +265,8 @@ public final class PRNGFixes { in.readFully(bytes); } } 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 // output being pulled into this process prematurely. try { - sUrandomIn = new DataInputStream(new FileInputStream(URANDOM_FILE)); + sUrandomIn = new DataInputStream( + new FileInputStream(URANDOM_FILE)); } catch (IOException e) { - throw new SecurityException("Failed to open " + URANDOM_FILE - + " for reading", e); + throw new SecurityException("Failed to open " + + URANDOM_FILE + " for reading", e); } } return sUrandomIn; } } -// private OutputStream getUrandomOutputStream() { -// synchronized (sLock) { -// if (sUrandomOut == null) { -// try { -// sUrandomOut = new FileOutputStream(URANDOM_FILE); -// } catch (IOException e) { -// throw new SecurityException("Failed to open " + URANDOM_FILE -// + " for writing", e); -// } -// } -// return sUrandomOut; -// } -// } + private OutputStream getUrandomOutputStream() throws IOException { + synchronized (sLock) { + if (sUrandomOut == null) { + sUrandomOut = new FileOutputStream(URANDOM_FILE); + } + 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() { try { ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); - DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer); + DataOutputStream seedBufferOut = + new DataOutputStream(seedBuffer); seedBufferOut.writeLong(System.currentTimeMillis()); seedBufferOut.writeLong(System.nanoTime()); seedBufferOut.writeInt(Process.myPid()); @@ -305,7 +329,7 @@ public final class PRNGFixes { /** * Gets the hardware serial number of this device. - * + * * @return serial number or {@code null} if not available. */ private static String getDeviceSerialNumber() {