Path: blob/master/src/java.base/unix/classes/sun/security/provider/NativePRNG.java
41137 views
/*1* Copyright (c) 2003, 2021, 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 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*/127@SuppressWarnings("removal")128private static RandomIO initIO(final Variant v) {129return AccessController.doPrivileged(130new PrivilegedAction<>() {131@Override132public RandomIO run() {133134File seedFile;135File nextFile;136137switch(v) {138case MIXED:139URL egdUrl;140File egdFile = null;141142if ((egdUrl = getEgdUrl()) != null) {143try {144egdFile = SunEntries.getDeviceFile(egdUrl);145} catch (IOException e) {146// Swallow, seedFile is still null147}148}149150// Try egd first.151if ((egdFile != null) && egdFile.canRead()) {152seedFile = egdFile;153} else {154// fall back to /dev/random.155seedFile = new File(NAME_RANDOM);156}157nextFile = new File(NAME_URANDOM);158break;159160case BLOCKING:161seedFile = new File(NAME_RANDOM);162nextFile = new File(NAME_RANDOM);163break;164165case NONBLOCKING:166seedFile = new File(NAME_URANDOM);167nextFile = new File(NAME_URANDOM);168break;169170default:171// Shouldn't happen!172return null;173}174175if (debug != null) {176debug.println("NativePRNG." + v +177" seedFile: " + seedFile +178" nextFile: " + nextFile);179}180181if (!seedFile.canRead() || !nextFile.canRead()) {182if (debug != null) {183debug.println("NativePRNG." + v +184" Couldn't read Files.");185}186return null;187}188189try {190return new RandomIO(seedFile, nextFile);191} catch (Exception e) {192return null;193}194}195});196}197198// return whether the NativePRNG is available199static boolean isAvailable() {200return INSTANCE != null;201}202203// constructor, called by the JCA framework204public NativePRNG() {205super();206if (INSTANCE == null) {207throw new AssertionError("NativePRNG not available");208}209}210211// set the seed212@Override213protected void engineSetSeed(byte[] seed) {214INSTANCE.implSetSeed(seed);215}216217// get pseudo random bytes218@Override219protected void engineNextBytes(byte[] bytes) {220INSTANCE.implNextBytes(bytes);221}222223// get true random bytes224@Override225protected byte[] engineGenerateSeed(int numBytes) {226return INSTANCE.implGenerateSeed(numBytes);227}228229/**230* A NativePRNG-like class that uses /dev/random for both231* seed and random material.232*233* Note that it does not respect the egd properties, since we have234* no way of knowing what those qualities are.235*236* This is very similar to the outer NativePRNG class, minimizing any237* breakage to the serialization of the existing implementation.238*239* @since 1.8240*/241public static final class Blocking extends SecureRandomSpi {242private static final long serialVersionUID = -6396183145759983347L;243244private static final RandomIO INSTANCE = initIO(Variant.BLOCKING);245246// return whether this is available247static boolean isAvailable() {248return INSTANCE != null;249}250251// constructor, called by the JCA framework252public Blocking() {253super();254if (INSTANCE == null) {255throw new AssertionError("NativePRNG$Blocking not available");256}257}258259// set the seed260@Override261protected void engineSetSeed(byte[] seed) {262INSTANCE.implSetSeed(seed);263}264265// get pseudo random bytes266@Override267protected void engineNextBytes(byte[] bytes) {268INSTANCE.implNextBytes(bytes);269}270271// get true random bytes272@Override273protected byte[] engineGenerateSeed(int numBytes) {274return INSTANCE.implGenerateSeed(numBytes);275}276}277278/**279* A NativePRNG-like class that uses /dev/urandom for both280* seed and random material.281*282* Note that it does not respect the egd properties, since we have283* no way of knowing what those qualities are.284*285* This is very similar to the outer NativePRNG class, minimizing any286* breakage to the serialization of the existing implementation.287*288* @since 1.8289*/290public static final class NonBlocking extends SecureRandomSpi {291private static final long serialVersionUID = -1102062982994105487L;292293private static final RandomIO INSTANCE = initIO(Variant.NONBLOCKING);294295// return whether this is available296static boolean isAvailable() {297return INSTANCE != null;298}299300// constructor, called by the JCA framework301public NonBlocking() {302super();303if (INSTANCE == null) {304throw new AssertionError(305"NativePRNG$NonBlocking not available");306}307}308309// set the seed310@Override311protected void engineSetSeed(byte[] seed) {312INSTANCE.implSetSeed(seed);313}314315// get pseudo random bytes316@Override317protected void engineNextBytes(byte[] bytes) {318INSTANCE.implNextBytes(bytes);319}320321// get true random bytes322@Override323protected byte[] engineGenerateSeed(int numBytes) {324return INSTANCE.implGenerateSeed(numBytes);325}326}327328/**329* Nested class doing the actual work. Singleton, see INSTANCE above.330*/331private static class RandomIO {332333// we buffer data we read from the "next" file for efficiency,334// but we limit the lifetime to avoid using stale bits335// lifetime in ms, currently 100 ms (0.1 s)336private static final long MAX_BUFFER_TIME = 100;337338// size of the "next" buffer339private static final int MAX_BUFFER_SIZE = 65536;340private static final int MIN_BUFFER_SIZE = 32;341private int bufferSize = 256;342343// Holder for the seedFile. Used if we ever add seed material.344File seedFile;345346// In/OutputStream for "seed" and "next"347private final InputStream seedIn, nextIn;348private OutputStream seedOut;349350// flag indicating if we have tried to open seedOut yet351private boolean seedOutInitialized;352353// SHA1PRNG instance for mixing354// initialized lazily on demand to avoid problems during startup355private volatile sun.security.provider.SecureRandom mixRandom;356357// buffer for next bits358private byte[] nextBuffer;359360// number of bytes left in nextBuffer361private int buffered;362363// time we read the data into the nextBuffer364private long lastRead;365366// Count for the number of buffer size changes requests367// Positive value in increase size, negative to lower it.368private int change_buffer = 0;369370// Request limit to trigger an increase in nextBuffer size371private static final int REQ_LIMIT_INC = 1000;372373// Request limit to trigger a decrease in nextBuffer size374private static final int REQ_LIMIT_DEC = -100;375376// mutex lock for nextBytes()377private final Object LOCK_GET_BYTES = new Object();378379// mutex lock for generateSeed()380private final Object LOCK_GET_SEED = new Object();381382// mutex lock for setSeed()383private final Object LOCK_SET_SEED = new Object();384385// constructor, called only once from initIO()386private RandomIO(File seedFile, File nextFile) throws IOException {387this.seedFile = seedFile;388seedIn = FileInputStreamPool.getInputStream(seedFile);389nextIn = FileInputStreamPool.getInputStream(nextFile);390nextBuffer = new byte[bufferSize];391}392393// get the SHA1PRNG for mixing394// initialize if not yet created395private sun.security.provider.SecureRandom getMixRandom() {396sun.security.provider.SecureRandom r = mixRandom;397if (r == null) {398synchronized (LOCK_GET_BYTES) {399r = mixRandom;400if (r == null) {401r = new sun.security.provider.SecureRandom();402try {403byte[] b = new byte[20];404readFully(nextIn, b);405r.engineSetSeed(b);406} catch (IOException e) {407throw new ProviderException("init failed", e);408}409mixRandom = r;410}411}412}413return r;414}415416// read data.length bytes from in417// These are not normal files, so we need to loop the read.418// just keep trying as long as we are making progress419private static void readFully(InputStream in, byte[] data)420throws IOException {421int len = data.length;422int ofs = 0;423while (len > 0) {424int k = in.read(data, ofs, len);425if (k <= 0) {426throw new EOFException("File(s) closed?");427}428ofs += k;429len -= k;430}431if (len > 0) {432throw new IOException("Could not read from file(s)");433}434}435436// get true random bytes, just read from "seed"437private byte[] implGenerateSeed(int numBytes) {438synchronized (LOCK_GET_SEED) {439try {440byte[] b = new byte[numBytes];441readFully(seedIn, b);442return b;443} catch (IOException e) {444throw new ProviderException("generateSeed() failed", e);445}446}447}448449// supply random bytes to the OS450// write to "seed" if possible451// always add the seed to our mixing random452@SuppressWarnings("removal")453private void implSetSeed(byte[] seed) {454synchronized (LOCK_SET_SEED) {455if (seedOutInitialized == false) {456seedOutInitialized = true;457seedOut = AccessController.doPrivileged(458new PrivilegedAction<>() {459@Override460public OutputStream run() {461try {462return new FileOutputStream(seedFile, true);463} catch (Exception e) {464return null;465}466}467});468}469if (seedOut != null) {470try {471seedOut.write(seed);472} catch (IOException e) {473// Ignored. On Mac OS X, /dev/urandom can be opened474// for write, but actual write is not permitted.475}476}477getMixRandom().engineSetSeed(seed);478}479}480481// ensure that there is at least one valid byte in the buffer482// if not, read new bytes483private void ensureBufferValid() throws IOException {484long time = System.currentTimeMillis();485int new_buffer_size = 0;486487// Check if buffer has bytes available that are not too old488if (buffered > 0) {489if (time - lastRead < MAX_BUFFER_TIME) {490return;491} else {492// byte is old, so subtract from counter to shrink buffer493change_buffer--;494}495} else {496// No bytes available, so add to count to increase buffer497change_buffer++;498}499500// If counter has it a limit, increase or decrease size501if (change_buffer > REQ_LIMIT_INC) {502new_buffer_size = nextBuffer.length * 2;503} else if (change_buffer < REQ_LIMIT_DEC) {504new_buffer_size = nextBuffer.length / 2;505}506507// If buffer size is to be changed, replace nextBuffer.508if (new_buffer_size > 0) {509if (new_buffer_size <= MAX_BUFFER_SIZE &&510new_buffer_size >= MIN_BUFFER_SIZE) {511nextBuffer = new byte[new_buffer_size];512if (debug != null) {513debug.println("Buffer size changed to " +514new_buffer_size);515}516} else {517if (debug != null) {518debug.println("Buffer reached limit: " +519nextBuffer.length);520}521}522change_buffer = 0;523}524525// Load fresh random bytes into nextBuffer526lastRead = time;527readFully(nextIn, nextBuffer);528buffered = nextBuffer.length;529}530531// get pseudo random bytes532// read from "next" and XOR with bytes generated by the533// mixing SHA1PRNG534private void implNextBytes(byte[] data) {535try {536getMixRandom().engineNextBytes(data);537int data_len = data.length;538int ofs = 0;539int len;540int buf_pos;541int localofs;542byte[] localBuffer;543544while (data_len > 0) {545synchronized (LOCK_GET_BYTES) {546ensureBufferValid();547buf_pos = nextBuffer.length - buffered;548if (data_len > buffered) {549len = buffered;550buffered = 0;551} else {552len = data_len;553buffered -= len;554}555localBuffer = Arrays.copyOfRange(nextBuffer, buf_pos,556buf_pos + len);557}558localofs = 0;559while (len > localofs) {560data[ofs] ^= localBuffer[localofs];561ofs++;562localofs++;563}564data_len -= len;565}566} catch (IOException e){567throw new ProviderException("nextBytes() failed", e);568}569}570}571}572573574