1 /*
   2  * Copyright (c) 1996, 2014, 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.length() != 0) {
 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     static public 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     static byte[] getSystemEntropy() {
 153         final MessageDigest md;
 154 
 155         try {
 156             md = MessageDigest.getInstance("SHA");
 157         } catch (NoSuchAlgorithmException nsae) {
 158             throw new InternalError("internal error: SHA-1 not available.",
 159                     nsae);
 160         }
 161 
 162         // The current time in millis
 163         byte b =(byte)System.currentTimeMillis();
 164         md.update(b);
 165 
 166         java.security.AccessController.doPrivileged
 167             (new java.security.PrivilegedAction<Void>() {
 168                 @Override
 169                 public Void run() {
 170                     try {
 171                         // System properties can change from machine to machine
 172                         Properties p = System.getProperties();
 173                         for (String s: p.stringPropertyNames()) {
 174                             md.update(s.getBytes());
 175                             md.update(p.getProperty(s).getBytes());
 176                         }
 177 
 178                         // Include network adapter names (and a Mac address)
 179                         addNetworkAdapterInfo(md);
 180 
 181                         // The temporary dir
 182                         File f = new File(p.getProperty("java.io.tmpdir"));
 183                         int count = 0;
 184                         try (
 185                             DirectoryStream<Path> stream =
 186                                 Files.newDirectoryStream(f.toPath())) {
 187                             // We use a Random object to choose what file names
 188                             // should be used. Otherwise on a machine with too
 189                             // many files, the same first 1024 files always get
 190                             // used. Any, We make sure the first 512 files are
 191                             // always used.
 192                             Random r = new Random();
 193                             for (Path entry: stream) {
 194                                 if (count < 512 || r.nextBoolean()) {
 195                                     md.update(entry.getFileName()
 196                                         .toString().getBytes());
 197                                 }
 198                                 if (count++ > 1024) {
 199                                     break;
 200                                 }
 201                             }
 202                         }
 203                     } catch (Exception ex) {
 204                         md.update((byte)ex.hashCode());
 205                     }
 206 
 207                     // get Runtime memory stats
 208                     Runtime rt = Runtime.getRuntime();
 209                     byte[] memBytes = longToByteArray(rt.totalMemory());
 210                     md.update(memBytes, 0, memBytes.length);
 211                     memBytes = longToByteArray(rt.freeMemory());
 212                     md.update(memBytes, 0, memBytes.length);
 213 
 214                     return null;
 215                 }
 216             });
 217         return md.digest();
 218     }
 219 
 220     /*
 221      * Include network adapter names and, if available, a Mac address
 222      *
 223      * See also java.util.concurrent.ThreadLocalRandom.initialSeed()
 224      */
 225     private static void addNetworkAdapterInfo(MessageDigest md) {
 226 
 227         try {
 228             Enumeration<NetworkInterface> ifcs =
 229                 NetworkInterface.getNetworkInterfaces();
 230             while (ifcs.hasMoreElements()) {
 231                 NetworkInterface ifc = ifcs.nextElement();
 232                 md.update(ifc.toString().getBytes());
 233                 if (!ifc.isVirtual()) { // skip fake addresses
 234                     byte[] bs = ifc.getHardwareAddress();
 235                     if (bs != null) {
 236                         md.update(bs);
 237                         break;
 238                     }
 239                 }
 240             }
 241         } catch (Exception ignore) {
 242         }
 243     }
 244 
 245     /**
 246      * Helper function to convert a long into a byte array (least significant
 247      * byte first).
 248      */
 249     private static byte[] longToByteArray(long l) {
 250         byte[] retVal = new byte[8];
 251 
 252         for (int i=0; i<8; i++) {
 253             retVal[i] = (byte) l;
 254             l >>= 8;
 255         }
 256 
 257         return retVal;
 258     }
 259 
 260     /*
 261     // This method helps the test utility receive unprocessed seed bytes.
 262     public static int genTestSeed() {
 263         return myself.getByte();
 264     }
 265     */
 266 
 267 
 268     private static class ThreadedSeedGenerator extends SeedGenerator
 269             implements Runnable {
 270         // Queue is used to collect seed bytes
 271         private byte[] pool;
 272         private int start, end, count;
 273 
 274         // Thread group for our threads
 275         ThreadGroup seedGroup;
 276 
 277         /**
 278          * The constructor is only called once to construct the one
 279          * instance we actually use. It instantiates the message digest
 280          * and starts the thread going.
 281          */
 282         ThreadedSeedGenerator() {
 283             pool = new byte[20];
 284             start = end = 0;
 285 
 286             MessageDigest digest;
 287 
 288             try {
 289                 digest = MessageDigest.getInstance("SHA");
 290             } catch (NoSuchAlgorithmException e) {
 291                 throw new InternalError("internal error: SHA-1 not available."
 292                         , e);
 293             }
 294 
 295             final ThreadGroup[] finalsg = new ThreadGroup[1];
 296             Thread t = java.security.AccessController.doPrivileged
 297                 (new java.security.PrivilegedAction<Thread>() {
 298                         @Override
 299                         public Thread run() {
 300                             ThreadGroup parent, group =
 301                                 Thread.currentThread().getThreadGroup();
 302                             while ((parent = group.getParent()) != null) {
 303                                 group = parent;
 304                             }
 305                             finalsg[0] = new ThreadGroup
 306                                 (group, "SeedGenerator ThreadGroup");
 307                             Thread newT = new Thread(finalsg[0],
 308                                 ThreadedSeedGenerator.this,
 309                                 "SeedGenerator Thread");
 310                             newT.setPriority(Thread.MIN_PRIORITY);
 311                             newT.setDaemon(true);
 312                             return newT;
 313                         }
 314                     });
 315             seedGroup = finalsg[0];
 316             t.start();
 317         }
 318 
 319         /**
 320          * This method does the actual work. It collects random bytes and
 321          * pushes them into the queue.
 322          */
 323         @Override
 324         final public void run() {
 325             try {
 326                 while (true) {
 327                     // Queue full? Wait till there's room.
 328                     synchronized(this) {
 329                         while (count >= pool.length) {
 330                             wait();
 331                         }
 332                     }
 333 
 334                     int counter, quanta;
 335                     byte v = 0;
 336 
 337                     // Spin count must not be under 64000
 338                     for (counter = quanta = 0;
 339                             (counter < 64000) && (quanta < 6); quanta++) {
 340 
 341                         // Start some noisy threads
 342                         try {
 343                             BogusThread bt = new BogusThread();
 344                             Thread t = new Thread
 345                                 (seedGroup, bt, "SeedGenerator Thread");
 346                             t.start();
 347                         } catch (Exception e) {
 348                             throw new InternalError("internal error: " +
 349                                 "SeedGenerator thread creation error.", e);
 350                         }
 351 
 352                         // We wait 250milli quanta, so the minimum wait time
 353                         // cannot be under 250milli.
 354                         int latch = 0;
 355                         long l = System.currentTimeMillis() + 250;
 356                         while (System.currentTimeMillis() < l) {
 357                             synchronized(this){};
 358                             latch++;
 359                         }
 360 
 361                         // Translate the value using the permutation, and xor
 362                         // it with previous values gathered.
 363                         v ^= rndTab[latch % 255];
 364                         counter += latch;
 365                     }
 366 
 367                     // Push it into the queue and notify anybody who might
 368                     // be waiting for it.
 369                     synchronized(this) {
 370                         pool[end] = v;
 371                         end++;
 372                         count++;
 373                         if (end >= pool.length) {
 374                             end = 0;
 375                         }
 376 
 377                         notifyAll();
 378                     }
 379                 }
 380             } catch (Exception e) {
 381                 throw new InternalError("internal error: " +
 382                     "SeedGenerator thread generated an exception.", e);
 383             }
 384         }
 385 
 386         @Override
 387         void getSeedBytes(byte[] result) {
 388             for (int i = 0; i < result.length; i++) {
 389                 result[i] = getSeedByte();
 390             }
 391         }
 392 
 393         byte getSeedByte() {
 394             byte b;
 395 
 396             try {
 397                 // Wait for it...
 398                 synchronized(this) {
 399                     while (count <= 0) {
 400                         wait();
 401                     }
 402                 }
 403             } catch (Exception e) {
 404                 if (count <= 0) {
 405                     throw new InternalError("internal error: " +
 406                         "SeedGenerator thread generated an exception.", e);
 407                 }
 408             }
 409 
 410             synchronized(this) {
 411                 // Get it from the queue
 412                 b = pool[start];
 413                 pool[start] = 0;
 414                 start++;
 415                 count--;
 416                 if (start == pool.length) {
 417                     start = 0;
 418                 }
 419 
 420                 // Notify the daemon thread, just in case it is
 421                 // waiting for us to make room in the queue.
 422                 notifyAll();
 423             }
 424 
 425             return b;
 426         }
 427 
 428         // The permutation was calculated by generating 64k of random
 429         // data and using it to mix the trivial permutation.
 430         // It should be evenly distributed. The specific values
 431         // are not crucial to the security of this class.
 432         private static byte[] rndTab = {
 433             56, 30, -107, -6, -86, 25, -83, 75, -12, -64,
 434             5, -128, 78, 21, 16, 32, 70, -81, 37, -51,
 435             -43, -46, -108, 87, 29, 17, -55, 22, -11, -111,
 436             -115, 84, -100, 108, -45, -15, -98, 72, -33, -28,
 437             31, -52, -37, -117, -97, -27, 93, -123, 47, 126,
 438             -80, -62, -93, -79, 61, -96, -65, -5, -47, -119,
 439             14, 89, 81, -118, -88, 20, 67, -126, -113, 60,
 440             -102, 55, 110, 28, 85, 121, 122, -58, 2, 45,
 441             43, 24, -9, 103, -13, 102, -68, -54, -101, -104,
 442             19, 13, -39, -26, -103, 62, 77, 51, 44, 111,
 443             73, 18, -127, -82, 4, -30, 11, -99, -74, 40,
 444             -89, 42, -76, -77, -94, -35, -69, 35, 120, 76,
 445             33, -73, -7, 82, -25, -10, 88, 125, -112, 58,
 446             83, 95, 6, 10, 98, -34, 80, 15, -91, 86,
 447             -19, 52, -17, 117, 49, -63, 118, -90, 36, -116,
 448             -40, -71, 97, -53, -109, -85, 109, -16, -3, 104,
 449             -95, 68, 54, 34, 26, 114, -1, 106, -121, 3,
 450             66, 0, 100, -84, 57, 107, 119, -42, 112, -61,
 451             1, 48, 38, 12, -56, -57, 39, -106, -72, 41,
 452             7, 71, -29, -59, -8, -38, 79, -31, 124, -124,
 453             8, 91, 116, 99, -4, 9, -36, -78, 63, -49,
 454             -67, -87, 59, 101, -32, 92, 94, 53, -41, 115,
 455             -66, -70, -122, 50, -50, -22, -20, -18, -21, 23,
 456             -2, -48, 96, 65, -105, 123, -14, -110, 69, -24,
 457             -120, -75, 74, 127, -60, 113, 90, -114, 105, 46,
 458             27, -125, -23, -44, 64
 459         };
 460 
 461         /**
 462          * This inner thread causes the thread scheduler to become 'noisy',
 463          * thus adding entropy to the system load.
 464          * At least one instance of this class is generated for every seed byte.
 465          */
 466         private static class BogusThread implements Runnable {
 467             @Override
 468             final public void run() {
 469                 try {
 470                     for (int i = 0; i < 5; i++) {
 471                         Thread.sleep(50);
 472                     }
 473                     // System.gc();
 474                 } catch (Exception e) {
 475                 }
 476             }
 477         }
 478     }
 479 
 480     static class URLSeedGenerator extends SeedGenerator {
 481 
 482         private String deviceName;
 483         private InputStream seedStream;
 484 
 485         /**
 486          * The constructor is only called once to construct the one
 487          * instance we actually use. It opens the entropy gathering device
 488          * which will supply the randomness.
 489          */
 490 
 491         URLSeedGenerator(String egdurl) throws IOException {
 492         if (egdurl == null) {
 493                 throw new IOException("No random source specified");
 494             }
 495             deviceName = egdurl;
 496             init();
 497         }
 498 
 499         private void init() throws IOException {
 500             final URL device = new URL(deviceName);
 501             try {
 502                 seedStream = java.security.AccessController.doPrivileged
 503                     (new java.security.PrivilegedExceptionAction<InputStream>() {
 504                         @Override
 505                         public InputStream run() throws IOException {
 506                             /*
 507                              * return a shared InputStream from FileInputStreamPool
 508                              * for file URLs and avoid buffering.
 509                              * The URL.openStream() call wraps
 510                              * InputStream in a BufferedInputStream which
 511                              * can buffer up to 8K bytes. This read is a
 512                              * performance issue for entropy sources which
 513                              * can be slow to replenish.
 514                              */
 515                             if (device.getProtocol().equalsIgnoreCase("file")) {
 516                                 File deviceFile =
 517                                     SunEntries.getDeviceFile(device);
 518                                 return FileInputStreamPool.getInputStream(deviceFile);
 519                             } else {
 520                                 return device.openStream();
 521                             }
 522                         }
 523                     });
 524             } catch (Exception e) {
 525                 throw new IOException(
 526                     "Failed to open " + deviceName, e.getCause());
 527             }
 528         }
 529 
 530         @Override
 531         void getSeedBytes(byte[] result) {
 532             int len = result.length;
 533             int read = 0;
 534             try {
 535                 while (read < len) {
 536                     int count = seedStream.read(result, read, len - read);
 537                     // /dev/random blocks - should never have EOF
 538                     if (count < 0) {
 539                         throw new InternalError(
 540                             "URLSeedGenerator " + deviceName +
 541                             " reached end of file");
 542                     }
 543                     read += count;
 544                 }
 545             } catch (IOException ioe) {
 546                 throw new InternalError("URLSeedGenerator " + deviceName +
 547                     " generated exception: " + ioe.getMessage(), ioe);
 548             }
 549         }
 550     }
 551 }