1 /*
  2  * Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package sun.security.provider;
 27 
 28 /**
 29  * This class generates seeds for the SHA1PRNG cryptographically strong
 30  * random number generator.
 31  * <p>
 32  * The seed is produced using one of two techniques, via a computation
 33  * of current system activity or from an entropy gathering device.
 34  * <p>
 35  * In the default technique the seed is produced by counting the
 36  * number of times the VM manages to loop in a given period. This number
 37  * roughly reflects the machine load at that point in time.
 38  * The samples are translated using a permutation (s-box)
 39  * and then XORed together. This process is non linear and
 40  * should prevent the samples from "averaging out". The s-box
 41  * was designed to have even statistical distribution; it's specific
 42  * values are not crucial for the security of the seed.
 43  * We also create a number of sleeper threads which add entropy
 44  * to the system by keeping the scheduler busy.
 45  * Twenty such samples should give us roughly 160 bits of randomness.
 46  * <p>
 47  * These values are gathered in the background by a daemon thread
 48  * thus allowing the system to continue performing it's different
 49  * activites, which in turn add entropy to the random seed.
 50  * <p>
 51  * The class also gathers miscellaneous system information, some
 52  * machine dependent, some not. This information is then hashed together
 53  * with the 20 seed bytes.
 54  * <p>
 55  * The alternative to the above approach is to acquire seed material
 56  * from an entropy gathering device, such as /dev/random. This can be
 57  * accomplished by setting the value of the {@code securerandom.source}
 58  * Security property to a URL specifying the location of the entropy
 59  * gathering device, or by setting the {@code java.security.egd} System
 60  * property.
 61  * <p>
 62  * In the event the specified URL cannot be accessed the default
 63  * threading mechanism is used.
 64  *
 65  * @author Joshua Bloch
 66  * @author Gadi Guy
 67  */
 68 
 69 import java.security.*;
 70 import java.io.*;
 71 import java.util.Properties;
 72 import java.util.Enumeration;
 73 import java.net.*;
 74 import java.nio.file.DirectoryStream;
 75 import java.nio.file.Files;
 76 import java.nio.file.Path;
 77 import java.util.Random;
 78 import sun.security.util.Debug;
 79 
 80 abstract class SeedGenerator {
 81 
 82     // Static instance is created at link time
 83     private static SeedGenerator instance;
 84 
 85     private static final Debug debug = Debug.getInstance("provider");
 86 
 87     // Static initializer to hook in selected or best performing generator
 88     static {
 89         String egdSource = SunEntries.getSeedSource();
 90 
 91         /*
 92          * Try the URL specifying the source (e.g. file:/dev/random)
 93          *
 94          * The URLs "file:/dev/random" or "file:/dev/urandom" are used to
 95          * indicate the SeedGenerator should use OS support, if available.
 96          *
 97          * On Windows, this causes the MS CryptoAPI seeder to be used.
 98          *
 99          * On Solaris/Linux/MacOS, this is identical to using
100          * URLSeedGenerator to read from /dev/[u]random
101          */
102         if (egdSource.equals(SunEntries.URL_DEV_RANDOM) ||
103                 egdSource.equals(SunEntries.URL_DEV_URANDOM)) {
104             try {
105                 instance = new NativeSeedGenerator(egdSource);
106                 if (debug != null) {
107                     debug.println(
108                         "Using operating system seed generator" + egdSource);
109                 }
110             } catch (IOException e) {
111                 if (debug != null) {
112                     debug.println("Failed to use operating system seed "
113                                   + "generator: " + e.toString());
114                 }
115             }
116         } else if (!egdSource.isEmpty()) {
117             try {
118                 instance = new URLSeedGenerator(egdSource);
119                 if (debug != null) {
120                     debug.println("Using URL seed generator reading from "
121                                   + egdSource);
122                 }
123             } catch (IOException e) {
124                 if (debug != null) {
125                     debug.println("Failed to create seed generator with "
126                                   + egdSource + ": " + e.toString());
127                 }
128             }
129         }
130 
131         // Fall back to ThreadedSeedGenerator
132         if (instance == null) {
133             if (debug != null) {
134                 debug.println("Using default threaded seed generator");
135             }
136             instance = new ThreadedSeedGenerator();
137         }
138     }
139 
140     /**
141      * Fill result with bytes from the queue. Wait for it if it isn't ready.
142      */
143     public static void generateSeed(byte[] result) {
144         instance.getSeedBytes(result);
145     }
146 
147     abstract void getSeedBytes(byte[] result);
148 
149     /**
150      * Retrieve some system information, hashed.
151      */
152     @SuppressWarnings("removal")
153     static byte[] getSystemEntropy() {
154         final MessageDigest md;
155 
156         try {
157             md = MessageDigest.getInstance("SHA");
158         } catch (NoSuchAlgorithmException nsae) {
159             throw new InternalError("internal error: SHA-1 not available.",
160                     nsae);
161         }
162 
163         // The current time in millis
164         byte b =(byte)System.currentTimeMillis();
165         md.update(b);
166 
167         java.security.AccessController.doPrivileged
168             (new java.security.PrivilegedAction<>() {
169                 @Override
170                 public Void run() {
171                     try {
172                         // System properties can change from machine to machine
173                         Properties p = System.getProperties();
174                         for (String s: p.stringPropertyNames()) {
175                             md.update(s.getBytes());
176                             md.update(p.getProperty(s).getBytes());
177                         }
178 
179                         // Include network adapter names (and a Mac address)
180                         addNetworkAdapterInfo(md);
181 
182                         // The temporary dir
183                         File f = new File(p.getProperty("java.io.tmpdir"));
184                         int count = 0;
185                         try (
186                             DirectoryStream<Path> stream =
187                                 Files.newDirectoryStream(f.toPath())) {
188                             // We use a Random object to choose what file names
189                             // should be used. Otherwise on a machine with too
190                             // many files, the same first 1024 files always get
191                             // used. Any, We make sure the first 512 files are
192                             // always used.
193                             Random r = new Random();
194                             for (Path entry: stream) {
195                                 if (count < 512 || r.nextBoolean()) {
196                                     md.update(entry.getFileName()
197                                         .toString().getBytes());
198                                 }
199                                 if (count++ > 1024) {
200                                     break;
201                                 }
202                             }
203                         }
204                     } catch (Exception ex) {
205                         md.update((byte)ex.hashCode());
206                     }
207 
208                     // get Runtime memory stats
209                     Runtime rt = Runtime.getRuntime();
210                     byte[] memBytes = longToByteArray(rt.totalMemory());
211                     md.update(memBytes, 0, memBytes.length);
212                     memBytes = longToByteArray(rt.freeMemory());
213                     md.update(memBytes, 0, memBytes.length);
214 
215                     return null;
216                 }
217             });
218         return md.digest();
219     }
220 
221     /*
222      * Include network adapter names and, if available, a Mac address
223      *
224      * See also java.util.concurrent.ThreadLocalRandom.initialSeed()
225      */
226     private static void addNetworkAdapterInfo(MessageDigest md) {
227 
228         try {
229             Enumeration<NetworkInterface> ifcs =
230                 NetworkInterface.getNetworkInterfaces();
231             while (ifcs.hasMoreElements()) {
232                 NetworkInterface ifc = ifcs.nextElement();
233                 md.update(ifc.toString().getBytes());
234                 if (!ifc.isVirtual()) { // skip fake addresses
235                     byte[] bs = ifc.getHardwareAddress();
236                     if (bs != null) {
237                         md.update(bs);
238                         break;
239                     }
240                 }
241             }
242         } catch (Exception ignore) {
243         }
244     }
245 
246     /**
247      * Helper function to convert a long into a byte array (least significant
248      * byte first).
249      */
250     private static byte[] longToByteArray(long l) {
251         byte[] retVal = new byte[8];
252 
253         for (int i=0; i<8; i++) {
254             retVal[i] = (byte) l;
255             l >>= 8;
256         }
257 
258         return retVal;
259     }
260 
261     /*
262     // This method helps the test utility receive unprocessed seed bytes.
263     public static int genTestSeed() {
264         return myself.getByte();
265     }
266     */
267 
268 
269     private static class ThreadedSeedGenerator extends SeedGenerator
270             implements Runnable {
271         // Queue is used to collect seed bytes
272         private byte[] pool;
273         private int start, end, count;
274 
275         // Thread group for our threads
276         ThreadGroup seedGroup;
277 
278         /**
279          * The constructor is only called once to construct the one
280          * instance we actually use. It instantiates the message digest
281          * and starts the thread going.
282          */
283         ThreadedSeedGenerator() {
284             pool = new byte[20];
285             start = end = 0;
286 
287             MessageDigest digest;
288 
289             try {
290                 digest = MessageDigest.getInstance("SHA");
291             } catch (NoSuchAlgorithmException e) {
292                 throw new InternalError("internal error: SHA-1 not available."
293                         , e);
294             }
295 
296             final ThreadGroup[] finalsg = new ThreadGroup[1];
297             @SuppressWarnings("removal")
298             Thread t = java.security.AccessController.doPrivileged
299                 (new java.security.PrivilegedAction<>() {
300                         @Override
301                         @SuppressWarnings("deprecation")
302                         public Thread run() {
303                             ThreadGroup parent, group =
304                                 Thread.currentThread().getThreadGroup();
305                             while ((parent = group.getParent()) != null) {
306                                 group = parent;
307                             }
308                             finalsg[0] = new ThreadGroup
309                                 (group, "SeedGenerator ThreadGroup");
310                             Thread newT = new Thread(finalsg[0],
311                                 ThreadedSeedGenerator.this,
312                                 "SeedGenerator Thread",
313                                 0,
314                                 false);
315                             newT.setPriority(Thread.MIN_PRIORITY);
316                             newT.setDaemon(true);
317                             return newT;
318                         }
319                     });
320             seedGroup = finalsg[0];
321             t.start();
322         }
323 
324         /**
325          * This method does the actual work. It collects random bytes and
326          * pushes them into the queue.
327          */
328         @Override
329         public final void run() {
330             try {
331                 while (true) {
332                     // Queue full? Wait till there's room.
333                     synchronized(this) {
334                         while (count >= pool.length) {
335                             wait();
336                         }
337                     }
338 
339                     int counter, quanta;
340                     byte v = 0;
341 
342                     // Spin count must not be under 64000
343                     for (counter = quanta = 0;
344                             (counter < 64000) && (quanta < 6); quanta++) {
345 
346                         // Start some noisy threads
347                         try {
348                             BogusThread bt = new BogusThread();
349                             Thread t = new Thread
350                                 (seedGroup, bt, "SeedGenerator Thread", 0,
351                                         false);
352                             t.start();
353                         } catch (Exception e) {
354                             throw new InternalError("internal error: " +
355                                 "SeedGenerator thread creation error.", e);
356                         }
357 
358                         // We wait 250milli quanta, so the minimum wait time
359                         // cannot be under 250milli.
360                         int latch = 0;
361                         long startTime = System.nanoTime();
362                         while (System.nanoTime() - startTime < 250000000) {
363                             synchronized(this){};
364                             // Mask the sign bit and keep latch non-negative
365                             latch = (latch + 1) & 0x1FFFFFFF;
366                         }
367 
368                         // Translate the value using the permutation, and xor
369                         // it with previous values gathered.
370                         v ^= rndTab[latch % 255];
371                         counter += latch;
372                     }
373 
374                     // Push it into the queue and notify anybody who might
375                     // be waiting for it.
376                     synchronized(this) {
377                         pool[end] = v;
378                         end++;
379                         count++;
380                         if (end >= pool.length) {
381                             end = 0;
382                         }
383 
384                         notifyAll();
385                     }
386                 }
387             } catch (Exception e) {
388                 throw new InternalError("internal error: " +
389                     "SeedGenerator thread generated an exception.", e);
390             }
391         }
392 
393         @Override
394         void getSeedBytes(byte[] result) {
395             for (int i = 0; i < result.length; i++) {
396                 result[i] = getSeedByte();
397             }
398         }
399 
400         byte getSeedByte() {
401             byte b;
402 
403             try {
404                 // Wait for it...
405                 synchronized(this) {
406                     while (count <= 0) {
407                         wait();
408                     }
409                 }
410             } catch (Exception e) {
411                 if (count <= 0) {
412                     throw new InternalError("internal error: " +
413                         "SeedGenerator thread generated an exception.", e);
414                 }
415             }
416 
417             synchronized(this) {
418                 // Get it from the queue
419                 b = pool[start];
420                 pool[start] = 0;
421                 start++;
422                 count--;
423                 if (start == pool.length) {
424                     start = 0;
425                 }
426 
427                 // Notify the daemon thread, just in case it is
428                 // waiting for us to make room in the queue.
429                 notifyAll();
430             }
431 
432             return b;
433         }
434 
435         // The permutation was calculated by generating 64k of random
436         // data and using it to mix the trivial permutation.
437         // It should be evenly distributed. The specific values
438         // are not crucial to the security of this class.
439         private static final byte[] rndTab = {
440             56, 30, -107, -6, -86, 25, -83, 75, -12, -64,
441             5, -128, 78, 21, 16, 32, 70, -81, 37, -51,
442             -43, -46, -108, 87, 29, 17, -55, 22, -11, -111,
443             -115, 84, -100, 108, -45, -15, -98, 72, -33, -28,
444             31, -52, -37, -117, -97, -27, 93, -123, 47, 126,
445             -80, -62, -93, -79, 61, -96, -65, -5, -47, -119,
446             14, 89, 81, -118, -88, 20, 67, -126, -113, 60,
447             -102, 55, 110, 28, 85, 121, 122, -58, 2, 45,
448             43, 24, -9, 103, -13, 102, -68, -54, -101, -104,
449             19, 13, -39, -26, -103, 62, 77, 51, 44, 111,
450             73, 18, -127, -82, 4, -30, 11, -99, -74, 40,
451             -89, 42, -76, -77, -94, -35, -69, 35, 120, 76,
452             33, -73, -7, 82, -25, -10, 88, 125, -112, 58,
453             83, 95, 6, 10, 98, -34, 80, 15, -91, 86,
454             -19, 52, -17, 117, 49, -63, 118, -90, 36, -116,
455             -40, -71, 97, -53, -109, -85, 109, -16, -3, 104,
456             -95, 68, 54, 34, 26, 114, -1, 106, -121, 3,
457             66, 0, 100, -84, 57, 107, 119, -42, 112, -61,
458             1, 48, 38, 12, -56, -57, 39, -106, -72, 41,
459             7, 71, -29, -59, -8, -38, 79, -31, 124, -124,
460             8, 91, 116, 99, -4, 9, -36, -78, 63, -49,
461             -67, -87, 59, 101, -32, 92, 94, 53, -41, 115,
462             -66, -70, -122, 50, -50, -22, -20, -18, -21, 23,
463             -2, -48, 96, 65, -105, 123, -14, -110, 69, -24,
464             -120, -75, 74, 127, -60, 113, 90, -114, 105, 46,
465             27, -125, -23, -44, 64
466         };
467 
468         /**
469          * This inner thread causes the thread scheduler to become 'noisy',
470          * thus adding entropy to the system load.
471          * At least one instance of this class is generated for every seed byte.
472          */
473         private static class BogusThread implements Runnable {
474             @Override
475             public final void run() {
476                 try {
477                     for (int i = 0; i < 5; i++) {
478                         Thread.sleep(50);
479                     }
480                     // System.gc();
481                 } catch (Exception e) {
482                 }
483             }
484         }
485     }
486 
487     static class URLSeedGenerator extends SeedGenerator {
488 
489         private String deviceName;
490         private InputStream seedStream;
491 
492         /**
493          * The constructor is only called once to construct the one
494          * instance we actually use. It opens the entropy gathering device
495          * which will supply the randomness.
496          */
497 
498         URLSeedGenerator(String egdurl) throws IOException {
499         if (egdurl == null) {
500                 throw new IOException("No random source specified");
501             }
502             deviceName = egdurl;
503             init();
504         }
505 
506         @SuppressWarnings("removal")
507         private void init() throws IOException {
508             final URL device = new URL(deviceName);
509             try {
510                 seedStream = java.security.AccessController.doPrivileged
511                     (new java.security.PrivilegedExceptionAction<>() {
512                         @Override
513                         public InputStream run() throws IOException {
514                             /*
515                              * return a shared InputStream for file URLs and
516                              * avoid buffering.
517                              * The URL.openStream() call wraps InputStream in a
518                              * BufferedInputStream which
519                              * can buffer up to 8K bytes. This read is a
520                              * performance issue for entropy sources which
521                              * can be slow to replenish.
522                              */
523                             if (device.getProtocol().equalsIgnoreCase("file")) {
524                                 File deviceFile =
525                                     SunEntries.getDeviceFile(device);
526                                 return FileInputStreamPool
527                                     .getInputStream(deviceFile);
528                             } else {
529                                 return device.openStream();
530                             }
531                         }
532                     });
533             } catch (Exception e) {
534                 throw new IOException(
535                     "Failed to open " + deviceName, e.getCause());
536             }
537         }
538 
539         @Override
540         void getSeedBytes(byte[] result) {
541             int len = result.length;
542             int read = 0;
543             try {
544                 while (read < len) {
545                     int count = seedStream.read(result, read, len - read);
546                     // /dev/random blocks - should never have EOF
547                     if (count < 0) {
548                         throw new InternalError(
549                             "URLSeedGenerator " + deviceName +
550                             " reached end of file");
551                     }
552                     read += count;
553                 }
554             } catch (IOException ioe) {
555                 throw new InternalError("URLSeedGenerator " + deviceName +
556                     " generated exception: " + ioe.getMessage(), ioe);
557             }
558         }
559     }
560 }