Android Open Source - conceal Secure Random Fix






From Project

Back to project page conceal.

License

The source code is released under:

BSD License For Conceal software Copyright (c) 2014, Facebook, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that...

If you think the Android project conceal listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 *  Copyright (c) 2014, Facebook, Inc.//www.  ja v a 2  s . c o  m
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */

package com.facebook.android.crypto.keychain;

import android.os.Process;

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;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.security.Security;

import android.os.Build;
import com.facebook.crypto.exception.KeyChainException;

/**
 * SecureRandom is not really random in android and are biased:
 * http://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html.
 * The following code is copied from the above source with some modifications.
 * <p>
 * We rely on SecureRandom to generate cryptographically secure random numbers for
 * various applications. This applies the fix suggested in the blog post which is
 * seeding random number generators for jellybean, and using /dev/urandom for pre
 * jellybean systems.
 */
public class SecureRandomFix {
    private static final int VERSION_CODE_JELLY_BEAN = 17;
    private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
    private static final String DEV_URANDOM = "/dev/urandom";

    private boolean mFixApplied;

    /**
     * Applies the fixes to make the random numbers cryptographically secure for
     * platforms in which they are insecure.
     * <p>
     * This function will only have effect once and subsequent calls to this function
     * will return.
     */
    public synchronized void tryApplyFixes() throws KeyChainException {
        if (mFixApplied) {
            return;
        }
        try {
            tryApplyOpenSSLFix();
            tryInstallLinuxPRNGSecureRandom();
        } catch (Throwable t) {
            // Practically, this should not crash.
            // However we can't do much in this case if it does.
            // We would rather abort than using an insecure random number
            // generator.
            throw new KeyChainException("Random number generated", t);
        }

        mFixApplied = true;
    }

    private void tryApplyOpenSSLFix() {
        if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
                || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
            // No need to apply the fix
            return;
        }

        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());

            // Mix output of Linux PRNG into OpenSSL's 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(
                        "Unexpected number of bytes read from Linux PRNG: "
                                + bytesRead);
            }
        } catch (Exception e) {
            throw new SecurityException("Failed to seed OpenSSL PRNG", e);
        }
    }

    /**
     * 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);
            seedBufferOut.writeLong(System.currentTimeMillis());
            seedBufferOut.writeLong(System.nanoTime());
            seedBufferOut.writeInt(android.os.Process.myPid());
            seedBufferOut.writeInt(Process.myUid());
            seedBufferOut.write(getBuildFingerprintAndDeviceSerial());
            seedBufferOut.close();
            return seedBuffer.toByteArray();
        } catch (IOException e) {
            throw new SecurityException("Failed to generate seed", e);
        }
    }

    private static byte[] getBuildFingerprintAndDeviceSerial() {
        StringBuilder result = new StringBuilder();
        String fingerprint = Build.FINGERPRINT;
        if (fingerprint != null) {
            result.append(fingerprint);
        }
        String serial = getDeviceSerialNumber();
        if (serial != null) {
            result.append(serial);
        }
        try {
            return result.toString().getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("UTF-8 encoding not supported");
        }
    }

    /**
     * Gets the hardware serial number of this device.
     *
     * @return serial number or {@code null} if not available.
     */
    private static String getDeviceSerialNumber() {
        // We're using the Reflection API because Build.SERIAL is only available
        // since API Level 9 (Gingerbread, Android 2.3).
        try {
            return (String) Build.class.getField("SERIAL").get(null);
        } catch (Exception ignored) {
            return null;
        }
    }

    /**
     * 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 tryInstallLinuxPRNGSecureRandom()
            throws SecurityException {
        if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
            // No need to apply the fix
            return;
        }

        // Install a Linux PRNG-based SecureRandom implementation as the
        // default, if not yet installed.
        Provider[] secureRandomProviders =
                Security.getProviders("SecureRandom.SHA1PRNG");
        if ((secureRandomProviders == null)
                || (secureRandomProviders.length < 1)
                || (!LinuxPRNGSecureRandomProvider.class.equals(
                secureRandomProviders[0].getClass()))) {
            Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
        }

        // Assert that new SecureRandom() and
        // 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());
        }

        SecureRandom rng2;
        try {
            rng2 = SecureRandom.getInstance("SHA1PRNG");
        } 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"
                            + " Provider: " + rng2.getProvider().getClass());
        }
    }

    /**
     * {@code Provider} of {@code SecureRandom} engines which pass through
     * all requests to the Linux PRNG.
     */
    private static class LinuxPRNGSecureRandomProvider extends Provider {

        public LinuxPRNGSecureRandomProvider() {
            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
            // may have low entropy.
            put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
            put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
        }
    }

    /**
     * {@link java.security.SecureRandomSpi} which passes all requests to the Linux PRNG
     * ({@code /dev/urandom}).
     */
    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.
         */

        private static final File URANDOM_FILE = new File(DEV_URANDOM);

        private static final Object sLock = new Object();

        /**
         * 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 tried to seed /dev/urandom.
         */
        private boolean mSeedAttempted;

        @Override
        protected void engineSetSeed(byte[] bytes) {
            try {
                OutputStream out;
                synchronized (sLock) {
                    out = getUrandomOutputStream();
                }
                out.write(bytes);
                out.flush();
            } catch (Throwable t) {
                // Do nothing here. This can fail on Samsung devices and other devices
                // where /dev/urandom is not writable probably due to SELinux policies.
                // discussion here: https://plus.google.com/+AndroidDevelopers/posts/YxWzeNQMJS2
                // Although it is good practise to seed it with more entropy,
                // /dev/urandom should already be seeded.
            } finally {
                mSeedAttempted = true;
            }
        }

        @Override
        protected void engineNextBytes(byte[] bytes) {
            if (!mSeedAttempted) {
                // Mix in the device- and invocation-specific seed.
                engineSetSeed(generateSeed());
            }

            try {
                DataInputStream in;
                synchronized (sLock) {
                    in = getUrandomInputStream();
                }
                synchronized (in) {
                    in.readFully(bytes);
                }
            } catch (IOException e) {
                throw new SecurityException(
                        "Failed to read from " + URANDOM_FILE, e);
            }
        }

        @Override
        protected byte[] engineGenerateSeed(int size) {
            byte[] seed = new byte[size];
            engineNextBytes(seed);
            return seed;
        }

        private DataInputStream getUrandomInputStream() {
            synchronized (sLock) {
                if (sUrandomIn == null) {
                    // NOTE: Consider inserting a BufferedInputStream between
                    // DataInputStream and FileInputStream if you need higher
                    // PRNG output performance and can live with future PRNG
                    // output being pulled into this process prematurely.
                    try {
                        sUrandomIn = new DataInputStream(
                                new FileInputStream(URANDOM_FILE));
                    } catch (IOException 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;
            }
        }
    }

}




Java Source Code List

com.facebook.android.crypto.keychain.SecureRandomFix.java
com.facebook.android.crypto.keychain.SharedPrefsBackedKeyChainTest.java
com.facebook.android.crypto.keychain.SharedPrefsBackedKeyChain.java
com.facebook.crypto.BouncyCastleHelper.java
com.facebook.crypto.CipherHelper.java
com.facebook.crypto.CryptoSerializerHelper.java
com.facebook.crypto.CryptoTestUtils.java
com.facebook.crypto.Crypto.java
com.facebook.crypto.Entity.java
com.facebook.crypto.FakeKeyChain.java
com.facebook.crypto.FbInstrumentationTestRunner.java
com.facebook.crypto.NativeGCMCipherInputStreamTest.java
com.facebook.crypto.NativeGCMCipherOutputStreamTest.java
com.facebook.crypto.NativeMacLayeredInputStreamTest.java
com.facebook.crypto.NativeMacLayeredOutputStreamTest.java
com.facebook.crypto.SimpleDecryptTest.java
com.facebook.crypto.SimpleEncryptTest.java
com.facebook.crypto.VersionCodes.java
com.facebook.crypto.benchmarks.BenchmarkNativeCryptoLibrary.java
com.facebook.crypto.benchmarks.CipherReadBenchmark.java
com.facebook.crypto.benchmarks.CipherWriteBenchmark.java
com.facebook.crypto.benchmarks.MacBenchmark.java
com.facebook.crypto.benchmarks.cipher.AESCipher.java
com.facebook.crypto.benchmarks.cipher.BaseCipher.java
com.facebook.crypto.benchmarks.cipher.BouncyCastleCCMCipher.java
com.facebook.crypto.benchmarks.cipher.BouncyCastleGCMCipher.java
com.facebook.crypto.benchmarks.cipher.NativeGCMCipherHelper.java
com.facebook.crypto.benchmarks.mac.BaseMac.java
com.facebook.crypto.benchmarks.mac.HMAC.java
com.facebook.crypto.benchmarks.mac.NativeMacHelper.java
com.facebook.crypto.benchmarks.mac.streams.MacLayeredInputStream.java
com.facebook.crypto.benchmarks.mac.streams.MacLayeredOutputStream.java
com.facebook.crypto.cipher.NativeGCMCipherException.java
com.facebook.crypto.cipher.NativeGCMCipher.java
com.facebook.crypto.exception.CryptoInitializationException.java
com.facebook.crypto.exception.KeyChainException.java
com.facebook.crypto.keychain.KeyChain.java
com.facebook.crypto.mac.NativeMac.java
com.facebook.crypto.streams.BetterCipherInputStreamTest.java
com.facebook.crypto.streams.BetterCipherInputStream.java
com.facebook.crypto.streams.FixedSizeByteArrayOutputStream.java
com.facebook.crypto.streams.NativeGCMCipherInputStream.java
com.facebook.crypto.streams.NativeGCMCipherOutputStream.java
com.facebook.crypto.streams.NativeMacLayeredInputStream.java
com.facebook.crypto.streams.NativeMacLayeredOutputStream.java
com.facebook.crypto.streams.TailBufferHelper.java
com.facebook.crypto.streams.TailInputStreamTest.java
com.facebook.crypto.streams.TailInputStream.java
com.facebook.crypto.util.Assertions.java
com.facebook.crypto.util.NativeCryptoLibrary.java
com.facebook.crypto.util.SystemNativeCryptoLibrary.java
com.facebook.proguard.annotations.DoNotStrip.java
com.facebook.proguard.annotations.InternalBuildOnly.java
com.facebook.proguard.annotations.KeepGettersAndSetters.java