Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/solaris/classes/sun/security/provider/NativePRNG.java
32288 views
/*1* Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.provider;2627import java.io.*;28import java.net.*;29import java.security.*;30import java.util.Arrays;3132import sun.security.util.Debug;3334/**35* Native PRNG implementation for Solaris/Linux/MacOS.36* <p>37* It obtains seed and random numbers by reading system files such as38* the special device files /dev/random and /dev/urandom. This39* implementation respects the {@code securerandom.source} Security40* property and {@code java.security.egd} System property for obtaining41* seed material. If the file specified by the properties does not42* exist, /dev/random is the default seed source. /dev/urandom is43* the default source of random numbers.44* <p>45* On some Unix platforms, /dev/random may block until enough entropy is46* available, but that may negatively impact the perceived startup47* time. By selecting these sources, this implementation tries to48* strike a balance between performance and security.49* <p>50* generateSeed() and setSeed() attempt to directly read/write to the seed51* source. However, this file may only be writable by root in many52* configurations. Because we cannot just ignore bytes specified via53* setSeed(), we keep a SHA1PRNG around in parallel.54* <p>55* nextBytes() reads the bytes directly from the source of random56* numbers (and then mixes them with bytes from the SHA1PRNG for the57* reasons explained above). Reading bytes from the random generator means58* that we are generally getting entropy from the operating system. This59* is a notable advantage over the SHA1PRNG model, which acquires60* entropy only initially during startup although the VM may be running61* for months.62* <p>63* Also note for nextBytes() that we do not need any initial pure random64* seed from /dev/random. This is an advantage because on some versions65* of Linux entropy can be exhausted very quickly and could thus impact66* startup time.67* <p>68* Finally, note that we use a singleton for the actual work (RandomIO)69* to avoid having to open and close /dev/[u]random constantly. However,70* there may be many NativePRNG instances created by the JCA framework.71*72* @since 1.573* @author Andreas Sterbenz74*/75public final class NativePRNG extends SecureRandomSpi {7677private static final long serialVersionUID = -6599091113397072932L;7879private static final Debug debug = Debug.getInstance("provider");8081// name of the pure random file (also used for setSeed())82private static final String NAME_RANDOM = "/dev/random";83// name of the pseudo random file84private static final String NAME_URANDOM = "/dev/urandom";8586// which kind of RandomIO object are we creating?87private enum Variant {88MIXED, BLOCKING, NONBLOCKING89}9091// singleton instance or null if not available92private static final RandomIO INSTANCE = initIO(Variant.MIXED);9394/**95* Get the System egd source (if defined). We only allow "file:"96* URLs for now. If there is a egd value, parse it.97*98* @return the URL or null if not available.99*/100private static URL getEgdUrl() {101// This will return "" if nothing was set.102String egdSource = SunEntries.getSeedSource();103URL egdUrl;104105if (egdSource.length() != 0) {106if (debug != null) {107debug.println("NativePRNG egdUrl: " + egdSource);108}109try {110egdUrl = new URL(egdSource);111if (!egdUrl.getProtocol().equalsIgnoreCase("file")) {112return null;113}114} catch (MalformedURLException e) {115return null;116}117} else {118egdUrl = null;119}120121return egdUrl;122}123124/**125* Create a RandomIO object for all I/O of this Variant type.126*/127private static RandomIO initIO(final Variant v) {128return AccessController.doPrivileged(129new PrivilegedAction<RandomIO>() {130@Override131public RandomIO run() {132133File seedFile;134File nextFile;135136switch(v) {137case MIXED:138URL egdUrl;139File egdFile = null;140141if ((egdUrl = getEgdUrl()) != null) {142try {143egdFile = SunEntries.getDeviceFile(egdUrl);144} catch (IOException e) {145// Swallow, seedFile is still null146}147}148149// Try egd first.150if ((egdFile != null) && egdFile.canRead()) {151seedFile = egdFile;152} else {153// fall back to /dev/random.154seedFile = new File(NAME_RANDOM);155}156nextFile = new File(NAME_URANDOM);157break;158159case BLOCKING:160seedFile = new File(NAME_RANDOM);161nextFile = new File(NAME_RANDOM);162break;163164case NONBLOCKING:165seedFile = new File(NAME_URANDOM);166nextFile = new File(NAME_URANDOM);167break;168169default:170// Shouldn't happen!171return null;172}173174if (debug != null) {175debug.println("NativePRNG." + v +176" seedFile: " + seedFile +177" nextFile: " + nextFile);178}179180if (!seedFile.canRead() || !nextFile.canRead()) {181if (debug != null) {182debug.println("NativePRNG." + v +183" Couldn't read Files.");184}185return null;186}187188try {189return new RandomIO(seedFile, nextFile);190} catch (Exception e) {191return null;192}193}194});195}196197// return whether the NativePRNG is available198static boolean isAvailable() {199return INSTANCE != null;200}201202// constructor, called by the JCA framework203public NativePRNG() {204super();205if (INSTANCE == null) {206throw new AssertionError("NativePRNG not available");207}208}209210// set the seed211@Override212protected void engineSetSeed(byte[] seed) {213INSTANCE.implSetSeed(seed);214}215216// get pseudo random bytes217@Override218protected void engineNextBytes(byte[] bytes) {219INSTANCE.implNextBytes(bytes);220}221222// get true random bytes223@Override224protected byte[] engineGenerateSeed(int numBytes) {225return INSTANCE.implGenerateSeed(numBytes);226}227228/**229* A NativePRNG-like class that uses /dev/random for both230* seed and random material.231*232* Note that it does not respect the egd properties, since we have233* no way of knowing what those qualities are.234*235* This is very similar to the outer NativePRNG class, minimizing any236* breakage to the serialization of the existing implementation.237*238* @since 1.8239*/240public static final class Blocking extends SecureRandomSpi {241private static final long serialVersionUID = -6396183145759983347L;242243private static final RandomIO INSTANCE = initIO(Variant.BLOCKING);244245// return whether this is available246static boolean isAvailable() {247return INSTANCE != null;248}249250// constructor, called by the JCA framework251public Blocking() {252super();253if (INSTANCE == null) {254throw new AssertionError("NativePRNG$Blocking not available");255}256}257258// set the seed259@Override260protected void engineSetSeed(byte[] seed) {261INSTANCE.implSetSeed(seed);262}263264// get pseudo random bytes265@Override266protected void engineNextBytes(byte[] bytes) {267INSTANCE.implNextBytes(bytes);268}269270// get true random bytes271@Override272protected byte[] engineGenerateSeed(int numBytes) {273return INSTANCE.implGenerateSeed(numBytes);274}275}276277/**278* A NativePRNG-like class that uses /dev/urandom for both279* seed and random material.280*281* Note that it does not respect the egd properties, since we have282* no way of knowing what those qualities are.283*284* This is very similar to the outer NativePRNG class, minimizing any285* breakage to the serialization of the existing implementation.286*287* @since 1.8288*/289public static final class NonBlocking extends SecureRandomSpi {290private static final long serialVersionUID = -1102062982994105487L;291292private static final RandomIO INSTANCE = initIO(Variant.NONBLOCKING);293294// return whether this is available295static boolean isAvailable() {296return INSTANCE != null;297}298299// constructor, called by the JCA framework300public NonBlocking() {301super();302if (INSTANCE == null) {303throw new AssertionError(304"NativePRNG$NonBlocking not available");305}306}307308// set the seed309@Override310protected void engineSetSeed(byte[] seed) {311INSTANCE.implSetSeed(seed);312}313314// get pseudo random bytes315@Override316protected void engineNextBytes(byte[] bytes) {317INSTANCE.implNextBytes(bytes);318}319320// get true random bytes321@Override322protected byte[] engineGenerateSeed(int numBytes) {323return INSTANCE.implGenerateSeed(numBytes);324}325}326327/**328* Nested class doing the actual work. Singleton, see INSTANCE above.329*/330private static class RandomIO {331332// we buffer data we read from the "next" file for efficiency,333// but we limit the lifetime to avoid using stale bits334// lifetime in ms, currently 100 ms (0.1 s)335private final static long MAX_BUFFER_TIME = 100;336337// size of the "next" buffer338private static final int MAX_BUFFER_SIZE = 65536;339private static final int MIN_BUFFER_SIZE = 32;340private int bufferSize = 256;341342// Holder for the seedFile. Used if we ever add seed material.343File seedFile;344345// In/OutputStream for "seed" and "next"346private final InputStream seedIn, nextIn;347private OutputStream seedOut;348349// flag indicating if we have tried to open seedOut yet350private boolean seedOutInitialized;351352// SHA1PRNG instance for mixing353// initialized lazily on demand to avoid problems during startup354private volatile sun.security.provider.SecureRandom mixRandom;355356// buffer for next bits357private byte[] nextBuffer;358359// number of bytes left in nextBuffer360private int buffered;361362// time we read the data into the nextBuffer363private long lastRead;364365// Count for the number of buffer size changes requests366// Positive value in increase size, negative to lower it.367private int change_buffer = 0;368369// Request limit to trigger an increase in nextBuffer size370private static final int REQ_LIMIT_INC = 1000;371372// Request limit to trigger a decrease in nextBuffer size373private static final int REQ_LIMIT_DEC = -100;374375// mutex lock for nextBytes()376private final Object LOCK_GET_BYTES = new Object();377378// mutex lock for generateSeed()379private final Object LOCK_GET_SEED = new Object();380381// mutex lock for setSeed()382private final Object LOCK_SET_SEED = new Object();383384// constructor, called only once from initIO()385private RandomIO(File seedFile, File nextFile) throws IOException {386this.seedFile = seedFile;387seedIn = new FileInputStream(seedFile);388nextIn = new FileInputStream(nextFile);389nextBuffer = new byte[bufferSize];390}391392// get the SHA1PRNG for mixing393// initialize if not yet created394private sun.security.provider.SecureRandom getMixRandom() {395sun.security.provider.SecureRandom r = mixRandom;396if (r == null) {397synchronized (LOCK_GET_BYTES) {398r = mixRandom;399if (r == null) {400r = new sun.security.provider.SecureRandom();401try {402byte[] b = new byte[20];403readFully(nextIn, b);404r.engineSetSeed(b);405} catch (IOException e) {406throw new ProviderException("init failed", e);407}408mixRandom = r;409}410}411}412return r;413}414415// read data.length bytes from in416// These are not normal files, so we need to loop the read.417// just keep trying as long as we are making progress418private static void readFully(InputStream in, byte[] data)419throws IOException {420int len = data.length;421int ofs = 0;422while (len > 0) {423int k = in.read(data, ofs, len);424if (k <= 0) {425throw new EOFException("File(s) closed?");426}427ofs += k;428len -= k;429}430if (len > 0) {431throw new IOException("Could not read from file(s)");432}433}434435// get true random bytes, just read from "seed"436private byte[] implGenerateSeed(int numBytes) {437synchronized (LOCK_GET_SEED) {438try {439byte[] b = new byte[numBytes];440readFully(seedIn, b);441return b;442} catch (IOException e) {443throw new ProviderException("generateSeed() failed", e);444}445}446}447448// supply random bytes to the OS449// write to "seed" if possible450// always add the seed to our mixing random451private void implSetSeed(byte[] seed) {452synchronized (LOCK_SET_SEED) {453if (seedOutInitialized == false) {454seedOutInitialized = true;455seedOut = AccessController.doPrivileged(456new PrivilegedAction<OutputStream>() {457@Override458public OutputStream run() {459try {460return new FileOutputStream(seedFile, true);461} catch (Exception e) {462return null;463}464}465});466}467if (seedOut != null) {468try {469seedOut.write(seed);470} catch (IOException e) {471// Ignored. On Mac OS X, /dev/urandom can be opened472// for write, but actual write is not permitted.473}474}475getMixRandom().engineSetSeed(seed);476}477}478479// ensure that there is at least one valid byte in the buffer480// if not, read new bytes481private void ensureBufferValid() throws IOException {482long time = System.currentTimeMillis();483int new_buffer_size = 0;484485// Check if buffer has bytes available that are not too old486if (buffered > 0) {487if (time - lastRead < MAX_BUFFER_TIME) {488return;489} else {490// byte is old, so subtract from counter to shrink buffer491change_buffer--;492}493} else {494// No bytes available, so add to count to increase buffer495change_buffer++;496}497498// If counter has it a limit, increase or decrease size499if (change_buffer > REQ_LIMIT_INC) {500new_buffer_size = nextBuffer.length * 2;501} else if (change_buffer < REQ_LIMIT_DEC) {502new_buffer_size = nextBuffer.length / 2;503}504505// If buffer size is to be changed, replace nextBuffer.506if (new_buffer_size > 0) {507if (new_buffer_size <= MAX_BUFFER_SIZE &&508new_buffer_size >= MIN_BUFFER_SIZE) {509nextBuffer = new byte[new_buffer_size];510if (debug != null) {511debug.println("Buffer size changed to " +512new_buffer_size);513}514} else {515if (debug != null) {516debug.println("Buffer reached limit: " +517nextBuffer.length);518}519}520change_buffer = 0;521}522523// Load fresh random bytes into nextBuffer524lastRead = time;525readFully(nextIn, nextBuffer);526buffered = nextBuffer.length;527}528529// get pseudo random bytes530// read from "next" and XOR with bytes generated by the531// mixing SHA1PRNG532private void implNextBytes(byte[] data) {533try {534getMixRandom().engineNextBytes(data);535int data_len = data.length;536int ofs = 0;537int len;538int buf_pos;539int localofs;540byte[] localBuffer;541542while (data_len > 0) {543synchronized (LOCK_GET_BYTES) {544ensureBufferValid();545buf_pos = nextBuffer.length - buffered;546if (data_len > buffered) {547len = buffered;548buffered = 0;549} else {550len = data_len;551buffered -= len;552}553localBuffer = Arrays.copyOfRange(nextBuffer, buf_pos,554buf_pos + len);555}556localofs = 0;557while (len > localofs) {558data[ofs] ^= localBuffer[localofs];559ofs++;560localofs++;561}562data_len -= len;563}564} catch (IOException e){565throw new ProviderException("nextBytes() failed", e);566}567}568}569}570571572