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