1 /*
   2  * Copyright (c) 1996, 2015, 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.misc.ManagedLocalsThread;
  79 import sun.security.util.Debug;
  80 
  81 abstract class SeedGenerator {
  82 
  83     // Static instance is created at link time
  84     private static SeedGenerator instance;
  85 
  86     private static final Debug debug = Debug.getInstance("provider");
  87 
  88     // Static initializer to hook in selected or best performing generator
  89     static {
  90         String egdSource = SunEntries.getSeedSource();
  91 
  92         /*
  93          * Try the URL specifying the source (e.g. file:/dev/random)
  94          *
  95          * The URLs "file:/dev/random" or "file:/dev/urandom" are used to
  96          * indicate the SeedGenerator should use OS support, if available.
  97          *
  98          * On Windows, this causes the MS CryptoAPI seeder to be used.
  99          *
 100          * On Solaris/Linux/MacOS, this is identical to using
 101          * URLSeedGenerator to read from /dev/[u]random
 102          */
 103         if (egdSource.equals(SunEntries.URL_DEV_RANDOM) ||
 104                 egdSource.equals(SunEntries.URL_DEV_URANDOM)) {
 105             try {
 106                 instance = new NativeSeedGenerator(egdSource);
 107                 if (debug != null) {
 108                     debug.println(
 109                         "Using operating system seed generator" + egdSource);
 110                 }
 111             } catch (IOException e) {
 112                 if (debug != null) {
 113                     debug.println("Failed to use operating system seed "
 114                                   + "generator: " + e.toString());
 115                 }
 116             }
 117         } else if (egdSource.length() != 0) {
 118             try {
 119                 instance = new URLSeedGenerator(egdSource);
 120                 if (debug != null) {
 121                     debug.println("Using URL seed generator reading from "
 122                                   + egdSource);
 123                 }
 124             } catch (IOException e) {
 125                 if (debug != null) {
 126                     debug.println("Failed to create seed generator with "
 127                                   + egdSource + ": " + e.toString());
 128                 }
 129             }
 130         }
 131 
 132         // Fall back to ThreadedSeedGenerator
 133         if (instance == null) {
 134             if (debug != null) {
 135                 debug.println("Using default threaded seed generator");
 136             }
 137             instance = new ThreadedSeedGenerator();
 138         }
 139     }
 140 
 141     /**
 142      * Fill result with bytes from the queue. Wait for it if it isn't ready.
 143      */
 144     static public void generateSeed(byte[] result) {
 145         instance.getSeedBytes(result);
 146     }
 147 
 148     abstract void getSeedBytes(byte[] result);
 149 
 150     /**
 151      * Retrieve some system information, hashed.
 152      */
 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             Thread t = java.security.AccessController.doPrivileged
 298                 (new java.security.PrivilegedAction<>() {
 299                         @Override
 300                         public Thread run() {
 301                             ThreadGroup parent, group =
 302                                 Thread.currentThread().getThreadGroup();
 303                             while ((parent = group.getParent()) != null) {
 304                                 group = parent;
 305                             }
 306                             finalsg[0] = new ThreadGroup
 307                                 (group, "SeedGenerator ThreadGroup");
 308                             Thread newT = new ManagedLocalsThread(finalsg[0],
 309                                 ThreadedSeedGenerator.this,
 310                                 "SeedGenerator Thread");
 311                             newT.setPriority(Thread.MIN_PRIORITY);
 312                             newT.setDaemon(true);
 313                             return newT;
 314                         }
 315                     });
 316             seedGroup = finalsg[0];
 317             t.start();
 318         }
 319 
 320         /**
 321          * This method does the actual work. It collects random bytes and
 322          * pushes them into the queue.
 323          */
 324         @Override
 325         final public void run() {
 326             try {
 327                 while (true) {
 328                     // Queue full? Wait till there's room.
 329                     synchronized(this) {
 330                         while (count >= pool.length) {
 331                             wait();
 332                         }
 333                     }
 334 
 335                     int counter, quanta;
 336                     byte v = 0;
 337 
 338                     // Spin count must not be under 64000
 339                     for (counter = quanta = 0;
 340                             (counter < 64000) && (quanta < 6); quanta++) {
 341 
 342                         // Start some noisy threads
 343                         try {
 344                             BogusThread bt = new BogusThread();
 345                             Thread t = new ManagedLocalsThread
 346                                 (seedGroup, bt, "SeedGenerator Thread");
 347                             t.start();
 348                         } catch (Exception e) {
 349                             throw new InternalError("internal error: " +
 350                                 "SeedGenerator thread creation error.", e);
 351                         }
 352 
 353                         // We wait 250milli quanta, so the minimum wait time
 354                         // cannot be under 250milli.
 355                         int latch = 0;
 356                         long l = System.currentTimeMillis() + 250;
 357                         while (System.currentTimeMillis() < l) {
 358                             synchronized(this){};
 359                             latch++;
 360                         }
 361 
 362                         // Translate the value using the permutation, and xor
 363                         // it with previous values gathered.
 364                         v ^= rndTab[latch % 255];
 365                         counter += latch;
 366                     }
 367 
 368                     // Push it into the queue and notify anybody who might
 369                     // be waiting for it.
 370                     synchronized(this) {
 371                         pool[end] = v;
 372                         end++;
 373                         count++;
 374                         if (end >= pool.length) {
 375                             end = 0;
 376                         }
 377 
 378                         notifyAll();
 379                     }
 380                 }
 381             } catch (Exception e) {
 382                 throw new InternalError("internal error: " +
 383                     "SeedGenerator thread generated an exception.", e);
 384             }
 385         }
 386 
 387         @Override
 388         void getSeedBytes(byte[] result) {
 389             for (int i = 0; i < result.length; i++) {
 390                 result[i] = getSeedByte();
 391             }
 392         }
 393 
 394         byte getSeedByte() {
 395             byte b;
 396 
 397             try {
 398                 // Wait for it...
 399                 synchronized(this) {
 400                     while (count <= 0) {
 401                         wait();
 402                     }
 403                 }
 404             } catch (Exception e) {
 405                 if (count <= 0) {
 406                     throw new InternalError("internal error: " +
 407                         "SeedGenerator thread generated an exception.", e);
 408                 }
 409             }
 410 
 411             synchronized(this) {
 412                 // Get it from the queue
 413                 b = pool[start];
 414                 pool[start] = 0;
 415                 start++;
 416                 count--;
 417                 if (start == pool.length) {
 418                     start = 0;
 419                 }
 420 
 421                 // Notify the daemon thread, just in case it is
 422                 // waiting for us to make room in the queue.
 423                 notifyAll();
 424             }
 425 
 426             return b;
 427         }
 428 
 429         // The permutation was calculated by generating 64k of random
 430         // data and using it to mix the trivial permutation.
 431         // It should be evenly distributed. The specific values
 432         // are not crucial to the security of this class.
 433         private static byte[] rndTab = {
 434             56, 30, -107, -6, -86, 25, -83, 75, -12, -64,
 435             5, -128, 78, 21, 16, 32, 70, -81, 37, -51,
 436             -43, -46, -108, 87, 29, 17, -55, 22, -11, -111,
 437             -115, 84, -100, 108, -45, -15, -98, 72, -33, -28,
 438             31, -52, -37, -117, -97, -27, 93, -123, 47, 126,
 439             -80, -62, -93, -79, 61, -96, -65, -5, -47, -119,
 440             14, 89, 81, -118, -88, 20, 67, -126, -113, 60,
 441             -102, 55, 110, 28, 85, 121, 122, -58, 2, 45,
 442             43, 24, -9, 103, -13, 102, -68, -54, -101, -104,
 443             19, 13, -39, -26, -103, 62, 77, 51, 44, 111,
 444             73, 18, -127, -82, 4, -30, 11, -99, -74, 40,
 445             -89, 42, -76, -77, -94, -35, -69, 35, 120, 76,
 446             33, -73, -7, 82, -25, -10, 88, 125, -112, 58,
 447             83, 95, 6, 10, 98, -34, 80, 15, -91, 86,
 448             -19, 52, -17, 117, 49, -63, 118, -90, 36, -116,
 449             -40, -71, 97, -53, -109, -85, 109, -16, -3, 104,
 450             -95, 68, 54, 34, 26, 114, -1, 106, -121, 3,
 451             66, 0, 100, -84, 57, 107, 119, -42, 112, -61,
 452             1, 48, 38, 12, -56, -57, 39, -106, -72, 41,
 453             7, 71, -29, -59, -8, -38, 79, -31, 124, -124,
 454             8, 91, 116, 99, -4, 9, -36, -78, 63, -49,
 455             -67, -87, 59, 101, -32, 92, 94, 53, -41, 115,
 456             -66, -70, -122, 50, -50, -22, -20, -18, -21, 23,
 457             -2, -48, 96, 65, -105, 123, -14, -110, 69, -24,
 458             -120, -75, 74, 127, -60, 113, 90, -114, 105, 46,
 459             27, -125, -23, -44, 64
 460         };
 461 
 462         /**
 463          * This inner thread causes the thread scheduler to become 'noisy',
 464          * thus adding entropy to the system load.
 465          * At least one instance of this class is generated for every seed byte.
 466          */
 467         private static class BogusThread implements Runnable {
 468             @Override
 469             final public void run() {
 470                 try {
 471                     for (int i = 0; i < 5; i++) {
 472                         Thread.sleep(50);
 473                     }
 474                     // System.gc();
 475                 } catch (Exception e) {
 476                 }
 477             }
 478         }
 479     }
 480 
 481     static class URLSeedGenerator extends SeedGenerator {
 482 
 483         private String deviceName;
 484         private InputStream seedStream;
 485 
 486         /**
 487          * The constructor is only called once to construct the one
 488          * instance we actually use. It opens the entropy gathering device
 489          * which will supply the randomness.
 490          */
 491 
 492         URLSeedGenerator(String egdurl) throws IOException {
 493         if (egdurl == null) {
 494                 throw new IOException("No random source specified");
 495             }
 496             deviceName = egdurl;
 497             init();
 498         }
 499 
 500         private void init() throws IOException {
 501             final URL device = new URL(deviceName);
 502             try {
 503                 seedStream = java.security.AccessController.doPrivileged
 504                     (new java.security.PrivilegedExceptionAction<>() {
 505                         @Override
 506                         public InputStream run() throws IOException {
 507                             /*
 508                              * return a shared InputStream for file URLs and
 509                              * avoid buffering.
 510                              * The URL.openStream() call wraps InputStream in a
 511                              * BufferedInputStream which
 512                              * can buffer up to 8K bytes. This read is a
 513                              * performance issue for entropy sources which
 514                              * can be slow to replenish.
 515                              */
 516                             if (device.getProtocol().equalsIgnoreCase("file")) {
 517                                 File deviceFile =
 518                                     SunEntries.getDeviceFile(device);
 519                                 return FileInputStreamPool
 520                                     .getInputStream(deviceFile);
 521                             } else {
 522                                 return device.openStream();
 523                             }
 524                         }
 525                     });
 526             } catch (Exception e) {
 527                 throw new IOException(
 528                     "Failed to open " + deviceName, e.getCause());
 529             }
 530         }
 531 
 532         @Override
 533         void getSeedBytes(byte[] result) {
 534             int len = result.length;
 535             int read = 0;
 536             try {
 537                 while (read < len) {
 538                     int count = seedStream.read(result, read, len - read);
 539                     // /dev/random blocks - should never have EOF
 540                     if (count < 0) {
 541                         throw new InternalError(
 542                             "URLSeedGenerator " + deviceName +
 543                             " reached end of file");
 544                     }
 545                     read += count;
 546                 }
 547             } catch (IOException ioe) {
 548                 throw new InternalError("URLSeedGenerator " + deviceName +
 549                     " generated exception: " + ioe.getMessage(), ioe);
 550             }
 551         }
 552     }
 553 }