diff -r 836adbf7a2cd -r 3317bb8137f4 jdk/src/java.base/unix/classes/sun/security/provider/NativePRNG.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.base/unix/classes/sun/security/provider/NativePRNG.java Sun Aug 17 15:54:13 2014 +0100 @@ -0,0 +1,501 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.provider; + +import java.io.*; +import java.net.*; +import java.security.*; +import sun.security.util.Debug; + +/** + * Native PRNG implementation for Solaris/Linux/MacOS. + *

+ * It obtains seed and random numbers by reading system files such as + * the special device files /dev/random and /dev/urandom. This + * implementation respects the {@code securerandom.source} Security + * property and {@code java.security.egd} System property for obtaining + * seed material. If the file specified by the properties does not + * exist, /dev/random is the default seed source. /dev/urandom is + * the default source of random numbers. + *

+ * On some Unix platforms, /dev/random may block until enough entropy is + * available, but that may negatively impact the perceived startup + * time. By selecting these sources, this implementation tries to + * strike a balance between performance and security. + *

+ * generateSeed() and setSeed() attempt to directly read/write to the seed + * source. However, this file may only be writable by root in many + * configurations. Because we cannot just ignore bytes specified via + * setSeed(), we keep a SHA1PRNG around in parallel. + *

+ * nextBytes() reads the bytes directly from the source of random + * numbers (and then mixes them with bytes from the SHA1PRNG for the + * reasons explained above). Reading bytes from the random generator means + * that we are generally getting entropy from the operating system. This + * is a notable advantage over the SHA1PRNG model, which acquires + * entropy only initially during startup although the VM may be running + * for months. + *

+ * Also note for nextBytes() that we do not need any initial pure random + * seed from /dev/random. This is an advantage because on some versions + * of Linux entropy can be exhausted very quickly and could thus impact + * startup time. + *

+ * Finally, note that we use a singleton for the actual work (RandomIO) + * to avoid having to open and close /dev/[u]random constantly. However, + * there may be many NativePRNG instances created by the JCA framework. + * + * @since 1.5 + * @author Andreas Sterbenz + */ +public final class NativePRNG extends SecureRandomSpi { + + private static final long serialVersionUID = -6599091113397072932L; + + private static final Debug debug = Debug.getInstance("provider"); + + // name of the pure random file (also used for setSeed()) + private static final String NAME_RANDOM = "/dev/random"; + // name of the pseudo random file + private static final String NAME_URANDOM = "/dev/urandom"; + + // which kind of RandomIO object are we creating? + private enum Variant { + MIXED, BLOCKING, NONBLOCKING + } + + // singleton instance or null if not available + private static final RandomIO INSTANCE = initIO(Variant.MIXED); + + /** + * Get the System egd source (if defined). We only allow "file:" + * URLs for now. If there is a egd value, parse it. + * + * @return the URL or null if not available. + */ + private static URL getEgdUrl() { + // This will return "" if nothing was set. + String egdSource = SunEntries.getSeedSource(); + URL egdUrl; + + if (egdSource.length() != 0) { + if (debug != null) { + debug.println("NativePRNG egdUrl: " + egdSource); + } + try { + egdUrl = new URL(egdSource); + if (!egdUrl.getProtocol().equalsIgnoreCase("file")) { + return null; + } + } catch (MalformedURLException e) { + return null; + } + } else { + egdUrl = null; + } + + return egdUrl; + } + + /** + * Create a RandomIO object for all I/O of this Variant type. + */ + private static RandomIO initIO(final Variant v) { + return AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public RandomIO run() { + + File seedFile; + File nextFile; + + switch(v) { + case MIXED: + URL egdUrl; + File egdFile = null; + + if ((egdUrl = getEgdUrl()) != null) { + try { + egdFile = SunEntries.getDeviceFile(egdUrl); + } catch (IOException e) { + // Swallow, seedFile is still null + } + } + + // Try egd first. + if ((egdFile != null) && egdFile.canRead()) { + seedFile = egdFile; + } else { + // fall back to /dev/random. + seedFile = new File(NAME_RANDOM); + } + nextFile = new File(NAME_URANDOM); + break; + + case BLOCKING: + seedFile = new File(NAME_RANDOM); + nextFile = new File(NAME_RANDOM); + break; + + case NONBLOCKING: + seedFile = new File(NAME_URANDOM); + nextFile = new File(NAME_URANDOM); + break; + + default: + // Shouldn't happen! + return null; + } + + if (debug != null) { + debug.println("NativePRNG." + v + + " seedFile: " + seedFile + + " nextFile: " + nextFile); + } + + if (!seedFile.canRead() || !nextFile.canRead()) { + if (debug != null) { + debug.println("NativePRNG." + v + + " Couldn't read Files."); + } + return null; + } + + try { + return new RandomIO(seedFile, nextFile); + } catch (Exception e) { + return null; + } + } + }); + } + + // return whether the NativePRNG is available + static boolean isAvailable() { + return INSTANCE != null; + } + + // constructor, called by the JCA framework + public NativePRNG() { + super(); + if (INSTANCE == null) { + throw new AssertionError("NativePRNG not available"); + } + } + + // set the seed + @Override + protected void engineSetSeed(byte[] seed) { + INSTANCE.implSetSeed(seed); + } + + // get pseudo random bytes + @Override + protected void engineNextBytes(byte[] bytes) { + INSTANCE.implNextBytes(bytes); + } + + // get true random bytes + @Override + protected byte[] engineGenerateSeed(int numBytes) { + return INSTANCE.implGenerateSeed(numBytes); + } + + /** + * A NativePRNG-like class that uses /dev/random for both + * seed and random material. + * + * Note that it does not respect the egd properties, since we have + * no way of knowing what those qualities are. + * + * This is very similar to the outer NativePRNG class, minimizing any + * breakage to the serialization of the existing implementation. + * + * @since 1.8 + */ + public static final class Blocking extends SecureRandomSpi { + private static final long serialVersionUID = -6396183145759983347L; + + private static final RandomIO INSTANCE = initIO(Variant.BLOCKING); + + // return whether this is available + static boolean isAvailable() { + return INSTANCE != null; + } + + // constructor, called by the JCA framework + public Blocking() { + super(); + if (INSTANCE == null) { + throw new AssertionError("NativePRNG$Blocking not available"); + } + } + + // set the seed + @Override + protected void engineSetSeed(byte[] seed) { + INSTANCE.implSetSeed(seed); + } + + // get pseudo random bytes + @Override + protected void engineNextBytes(byte[] bytes) { + INSTANCE.implNextBytes(bytes); + } + + // get true random bytes + @Override + protected byte[] engineGenerateSeed(int numBytes) { + return INSTANCE.implGenerateSeed(numBytes); + } + } + + /** + * A NativePRNG-like class that uses /dev/urandom for both + * seed and random material. + * + * Note that it does not respect the egd properties, since we have + * no way of knowing what those qualities are. + * + * This is very similar to the outer NativePRNG class, minimizing any + * breakage to the serialization of the existing implementation. + * + * @since 1.8 + */ + public static final class NonBlocking extends SecureRandomSpi { + private static final long serialVersionUID = -1102062982994105487L; + + private static final RandomIO INSTANCE = initIO(Variant.NONBLOCKING); + + // return whether this is available + static boolean isAvailable() { + return INSTANCE != null; + } + + // constructor, called by the JCA framework + public NonBlocking() { + super(); + if (INSTANCE == null) { + throw new AssertionError( + "NativePRNG$NonBlocking not available"); + } + } + + // set the seed + @Override + protected void engineSetSeed(byte[] seed) { + INSTANCE.implSetSeed(seed); + } + + // get pseudo random bytes + @Override + protected void engineNextBytes(byte[] bytes) { + INSTANCE.implNextBytes(bytes); + } + + // get true random bytes + @Override + protected byte[] engineGenerateSeed(int numBytes) { + return INSTANCE.implGenerateSeed(numBytes); + } + } + + /** + * Nested class doing the actual work. Singleton, see INSTANCE above. + */ + private static class RandomIO { + + // we buffer data we read from the "next" file for efficiency, + // but we limit the lifetime to avoid using stale bits + // lifetime in ms, currently 100 ms (0.1 s) + private final static long MAX_BUFFER_TIME = 100; + + // size of the "next" buffer + private final static int BUFFER_SIZE = 32; + + // Holder for the seedFile. Used if we ever add seed material. + File seedFile; + + // In/OutputStream for "seed" and "next" + private final InputStream seedIn, nextIn; + private OutputStream seedOut; + + // flag indicating if we have tried to open seedOut yet + private boolean seedOutInitialized; + + // SHA1PRNG instance for mixing + // initialized lazily on demand to avoid problems during startup + private volatile sun.security.provider.SecureRandom mixRandom; + + // buffer for next bits + private final byte[] nextBuffer; + + // number of bytes left in nextBuffer + private int buffered; + + // time we read the data into the nextBuffer + private long lastRead; + + // mutex lock for nextBytes() + private final Object LOCK_GET_BYTES = new Object(); + + // mutex lock for generateSeed() + private final Object LOCK_GET_SEED = new Object(); + + // mutex lock for setSeed() + private final Object LOCK_SET_SEED = new Object(); + + // constructor, called only once from initIO() + private RandomIO(File seedFile, File nextFile) throws IOException { + this.seedFile = seedFile; + seedIn = new FileInputStream(seedFile); + nextIn = new FileInputStream(nextFile); + nextBuffer = new byte[BUFFER_SIZE]; + } + + // get the SHA1PRNG for mixing + // initialize if not yet created + private sun.security.provider.SecureRandom getMixRandom() { + sun.security.provider.SecureRandom r = mixRandom; + if (r == null) { + synchronized (LOCK_GET_BYTES) { + r = mixRandom; + if (r == null) { + r = new sun.security.provider.SecureRandom(); + try { + byte[] b = new byte[20]; + readFully(nextIn, b); + r.engineSetSeed(b); + } catch (IOException e) { + throw new ProviderException("init failed", e); + } + mixRandom = r; + } + } + } + return r; + } + + // read data.length bytes from in + // These are not normal files, so we need to loop the read. + // just keep trying as long as we are making progress + private static void readFully(InputStream in, byte[] data) + throws IOException { + int len = data.length; + int ofs = 0; + while (len > 0) { + int k = in.read(data, ofs, len); + if (k <= 0) { + throw new EOFException("File(s) closed?"); + } + ofs += k; + len -= k; + } + if (len > 0) { + throw new IOException("Could not read from file(s)"); + } + } + + // get true random bytes, just read from "seed" + private byte[] implGenerateSeed(int numBytes) { + synchronized (LOCK_GET_SEED) { + try { + byte[] b = new byte[numBytes]; + readFully(seedIn, b); + return b; + } catch (IOException e) { + throw new ProviderException("generateSeed() failed", e); + } + } + } + + // supply random bytes to the OS + // write to "seed" if possible + // always add the seed to our mixing random + private void implSetSeed(byte[] seed) { + synchronized (LOCK_SET_SEED) { + if (seedOutInitialized == false) { + seedOutInitialized = true; + seedOut = AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public OutputStream run() { + try { + return new FileOutputStream(seedFile, true); + } catch (Exception e) { + return null; + } + } + }); + } + if (seedOut != null) { + try { + seedOut.write(seed); + } catch (IOException e) { + throw new ProviderException("setSeed() failed", e); + } + } + getMixRandom().engineSetSeed(seed); + } + } + + // ensure that there is at least one valid byte in the buffer + // if not, read new bytes + private void ensureBufferValid() throws IOException { + long time = System.currentTimeMillis(); + if ((buffered > 0) && (time - lastRead < MAX_BUFFER_TIME)) { + return; + } + lastRead = time; + readFully(nextIn, nextBuffer); + buffered = nextBuffer.length; + } + + // get pseudo random bytes + // read from "next" and XOR with bytes generated by the + // mixing SHA1PRNG + private void implNextBytes(byte[] data) { + synchronized (LOCK_GET_BYTES) { + try { + getMixRandom().engineNextBytes(data); + int len = data.length; + int ofs = 0; + while (len > 0) { + ensureBufferValid(); + int bufferOfs = nextBuffer.length - buffered; + while ((len > 0) && (buffered > 0)) { + data[ofs++] ^= nextBuffer[bufferOfs++]; + len--; + buffered--; + } + } + } catch (IOException e) { + throw new ProviderException("nextBytes() failed", e); + } + } + } + } +}