/* * 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 = FileInputStreamPool.getInputStream(seedFile); nextIn = FileInputStreamPool.getInputStream(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); } } } } }