1 /*
   2  * Copyright (c) 2003, 2013, 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 import java.io.*;
  29 import java.net.*;
  30 import java.security.*;
  31 import sun.security.util.Debug;
  32 
  33 /**
  34  * Native PRNG implementation for Solaris/Linux/MacOS.
  35  * <p>
  36  * It obtains seed and random numbers by reading system files such as
  37  * the special device files /dev/random and /dev/urandom.  This
  38  * implementation respects the {@code securerandom.source} Security
  39  * property and {@code java.security.egd} System property for obtaining
  40  * seed material.  If the file specified by the properties does not
  41  * exist, /dev/random is the default seed source.  /dev/urandom is
  42  * the default source of random numbers.
  43  * <p>
  44  * On some Unix platforms, /dev/random may block until enough entropy is
  45  * available, but that may negatively impact the perceived startup
  46  * time.  By selecting these sources, this implementation tries to
  47  * strike a balance between performance and security.
  48  * <p>
  49  * generateSeed() and setSeed() attempt to directly read/write to the seed
  50  * source. However, this file may only be writable by root in many
  51  * configurations. Because we cannot just ignore bytes specified via
  52  * setSeed(), we keep a SHA1PRNG around in parallel.
  53  * <p>
  54  * nextBytes() reads the bytes directly from the source of random
  55  * numbers (and then mixes them with bytes from the SHA1PRNG for the
  56  * reasons explained above). Reading bytes from the random generator means
  57  * that we are generally getting entropy from the operating system. This
  58  * is a notable advantage over the SHA1PRNG model, which acquires
  59  * entropy only initially during startup although the VM may be running
  60  * for months.
  61  * <p>
  62  * Also note for nextBytes() that we do not need any initial pure random
  63  * seed from /dev/random. This is an advantage because on some versions
  64  * of Linux entropy can be exhausted very quickly and could thus impact
  65  * startup time.
  66  * <p>
  67  * Finally, note that we use a singleton for the actual work (RandomIO)
  68  * to avoid having to open and close /dev/[u]random constantly. However,
  69  * there may be many NativePRNG instances created by the JCA framework.
  70  *
  71  * @since   1.5
  72  * @author  Andreas Sterbenz
  73  */
  74 public final class NativePRNG extends SecureRandomSpi {
  75 
  76     private static final long serialVersionUID = -6599091113397072932L;
  77 
  78     private static final Debug debug = Debug.getInstance("provider");
  79 
  80     // name of the pure random file (also used for setSeed())
  81     private static final String NAME_RANDOM = "/dev/random";
  82     // name of the pseudo random file
  83     private static final String NAME_URANDOM = "/dev/urandom";
  84 
  85     // which kind of RandomIO object are we creating?
  86     private enum Variant {
  87         MIXED, BLOCKING, NONBLOCKING
  88     }
  89 
  90     // singleton instance or null if not available
  91     private static final RandomIO INSTANCE = initIO(Variant.MIXED);
  92 
  93     /**
  94      * Get the System egd source (if defined).  We only allow "file:"
  95      * URLs for now. If there is a egd value, parse it.
  96      *
  97      * @return the URL or null if not available.
  98      */
  99     private static URL getEgdUrl() {
 100         // This will return "" if nothing was set.
 101         String egdSource = SunEntries.getSeedSource();
 102         URL egdUrl;
 103 
 104         if (egdSource.length() != 0) {
 105             if (debug != null) {
 106                 debug.println("NativePRNG egdUrl: " + egdSource);
 107             }
 108             try {
 109                 egdUrl = new URL(egdSource);
 110                 if (!egdUrl.getProtocol().equalsIgnoreCase("file")) {
 111                     return null;
 112                 }
 113             } catch (MalformedURLException e) {
 114                 return null;
 115             }
 116         } else {
 117             egdUrl = null;
 118         }
 119 
 120         return egdUrl;
 121     }
 122 
 123     /**
 124      * Create a RandomIO object for all I/O of this Variant type.
 125      */
 126     private static RandomIO initIO(final Variant v) {
 127         return AccessController.doPrivileged(
 128             new PrivilegedAction<RandomIO>() {
 129                 @Override
 130                 public RandomIO run() {
 131 
 132                     File seedFile;
 133                     File nextFile;
 134 
 135                     switch(v) {
 136                     case MIXED:
 137                         URL egdUrl;
 138                         File egdFile = null;
 139 
 140                         if ((egdUrl = getEgdUrl()) != null) {
 141                             try {
 142                                 egdFile = SunEntries.getDeviceFile(egdUrl);
 143                             } catch (IOException e) {
 144                                 // Swallow, seedFile is still null
 145                             }
 146                         }
 147 
 148                         // Try egd first.
 149                         if ((egdFile != null) && egdFile.canRead()) {
 150                             seedFile = egdFile;
 151                         } else {
 152                             // fall back to /dev/random.
 153                             seedFile = new File(NAME_RANDOM);
 154                         }
 155                         nextFile = new File(NAME_URANDOM);
 156                         break;
 157 
 158                     case BLOCKING:
 159                         seedFile = new File(NAME_RANDOM);
 160                         nextFile = new File(NAME_RANDOM);
 161                         break;
 162 
 163                     case NONBLOCKING:
 164                         seedFile = new File(NAME_URANDOM);
 165                         nextFile = new File(NAME_URANDOM);
 166                         break;
 167 
 168                     default:
 169                         // Shouldn't happen!
 170                         return null;
 171                     }
 172 
 173                     if (debug != null) {
 174                         debug.println("NativePRNG." + v +
 175                             " seedFile: " + seedFile +
 176                             " nextFile: " + nextFile);
 177                     }
 178 
 179                     if (!seedFile.canRead() || !nextFile.canRead()) {
 180                         if (debug != null) {
 181                             debug.println("NativePRNG." + v +
 182                                 " Couldn't read Files.");
 183                         }
 184                         return null;
 185                     }
 186 
 187                     try {
 188                         return new RandomIO(seedFile, nextFile);
 189                     } catch (Exception e) {
 190                         return null;
 191                     }
 192                 }
 193         });
 194     }
 195 
 196     // return whether the NativePRNG is available
 197     static boolean isAvailable() {
 198         return INSTANCE != null;
 199     }
 200 
 201     // constructor, called by the JCA framework
 202     public NativePRNG() {
 203         super();
 204         if (INSTANCE == null) {
 205             throw new AssertionError("NativePRNG not available");
 206         }
 207     }
 208 
 209     // set the seed
 210     @Override
 211     protected void engineSetSeed(byte[] seed) {
 212         INSTANCE.implSetSeed(seed);
 213     }
 214 
 215     // get pseudo random bytes
 216     @Override
 217     protected void engineNextBytes(byte[] bytes) {
 218         INSTANCE.implNextBytes(bytes);
 219     }
 220 
 221     // get true random bytes
 222     @Override
 223     protected byte[] engineGenerateSeed(int numBytes) {
 224         return INSTANCE.implGenerateSeed(numBytes);
 225     }
 226 
 227     /**
 228      * A NativePRNG-like class that uses /dev/random for both
 229      * seed and random material.
 230      *
 231      * Note that it does not respect the egd properties, since we have
 232      * no way of knowing what those qualities are.
 233      *
 234      * This is very similar to the outer NativePRNG class, minimizing any
 235      * breakage to the serialization of the existing implementation.
 236      *
 237      * @since   1.8
 238      */
 239     public static final class Blocking extends SecureRandomSpi {
 240         private static final long serialVersionUID = -6396183145759983347L;
 241 
 242         private static final RandomIO INSTANCE = initIO(Variant.BLOCKING);
 243 
 244         // return whether this is available
 245         static boolean isAvailable() {
 246             return INSTANCE != null;
 247         }
 248 
 249         // constructor, called by the JCA framework
 250         public Blocking() {
 251             super();
 252             if (INSTANCE == null) {
 253                 throw new AssertionError("NativePRNG$Blocking not available");
 254             }
 255         }
 256 
 257         // set the seed
 258         @Override
 259         protected void engineSetSeed(byte[] seed) {
 260             INSTANCE.implSetSeed(seed);
 261         }
 262 
 263         // get pseudo random bytes
 264         @Override
 265         protected void engineNextBytes(byte[] bytes) {
 266             INSTANCE.implNextBytes(bytes);
 267         }
 268 
 269         // get true random bytes
 270         @Override
 271         protected byte[] engineGenerateSeed(int numBytes) {
 272             return INSTANCE.implGenerateSeed(numBytes);
 273         }
 274     }
 275 
 276     /**
 277      * A NativePRNG-like class that uses /dev/urandom for both
 278      * seed and random material.
 279      *
 280      * Note that it does not respect the egd properties, since we have
 281      * no way of knowing what those qualities are.
 282      *
 283      * This is very similar to the outer NativePRNG class, minimizing any
 284      * breakage to the serialization of the existing implementation.
 285      *
 286      * @since   1.8
 287      */
 288     public static final class NonBlocking extends SecureRandomSpi {
 289         private static final long serialVersionUID = -1102062982994105487L;
 290 
 291         private static final RandomIO INSTANCE = initIO(Variant.NONBLOCKING);
 292 
 293         // return whether this is available
 294         static boolean isAvailable() {
 295             return INSTANCE != null;
 296         }
 297 
 298         // constructor, called by the JCA framework
 299         public NonBlocking() {
 300             super();
 301             if (INSTANCE == null) {
 302                 throw new AssertionError(
 303                     "NativePRNG$NonBlocking not available");
 304             }
 305         }
 306 
 307         // set the seed
 308         @Override
 309         protected void engineSetSeed(byte[] seed) {
 310             INSTANCE.implSetSeed(seed);
 311         }
 312 
 313         // get pseudo random bytes
 314         @Override
 315         protected void engineNextBytes(byte[] bytes) {
 316             INSTANCE.implNextBytes(bytes);
 317         }
 318 
 319         // get true random bytes
 320         @Override
 321         protected byte[] engineGenerateSeed(int numBytes) {
 322             return INSTANCE.implGenerateSeed(numBytes);
 323         }
 324     }
 325 
 326     /**
 327      * Nested class doing the actual work. Singleton, see INSTANCE above.
 328      */
 329     private static class RandomIO {
 330 
 331         // we buffer data we read from the "next" file for efficiency,
 332         // but we limit the lifetime to avoid using stale bits
 333         // lifetime in ms, currently 100 ms (0.1 s)
 334         private final static long MAX_BUFFER_TIME = 100;
 335 
 336         // size of the "next" buffer
 337         private final static int BUFFER_SIZE = 32;
 338 
 339         // Holder for the seedFile.  Used if we ever add seed material.
 340         File seedFile;
 341 
 342         // In/OutputStream for "seed" and "next"
 343         private final InputStream seedIn, nextIn;
 344         private OutputStream seedOut;
 345 
 346         // flag indicating if we have tried to open seedOut yet
 347         private boolean seedOutInitialized;
 348 
 349         // SHA1PRNG instance for mixing
 350         // initialized lazily on demand to avoid problems during startup
 351         private volatile sun.security.provider.SecureRandom mixRandom;
 352 
 353         // buffer for next bits
 354         private final byte[] nextBuffer;
 355 
 356         // number of bytes left in nextBuffer
 357         private int buffered;
 358 
 359         // time we read the data into the nextBuffer
 360         private long lastRead;
 361 
 362         // mutex lock for nextBytes()
 363         private final Object LOCK_GET_BYTES = new Object();
 364 
 365         // mutex lock for generateSeed()
 366         private final Object LOCK_GET_SEED = new Object();
 367 
 368         // mutex lock for setSeed()
 369         private final Object LOCK_SET_SEED = new Object();
 370 
 371         // constructor, called only once from initIO()
 372         private RandomIO(File seedFile, File nextFile) throws IOException {
 373             this.seedFile = seedFile;
 374             seedIn = new FileInputStream(seedFile);
 375             nextIn = new FileInputStream(nextFile);
 376             nextBuffer = new byte[BUFFER_SIZE];
 377         }
 378 
 379         // get the SHA1PRNG for mixing
 380         // initialize if not yet created
 381         private sun.security.provider.SecureRandom getMixRandom() {
 382             sun.security.provider.SecureRandom r = mixRandom;
 383             if (r == null) {
 384                 synchronized (LOCK_GET_BYTES) {
 385                     r = mixRandom;
 386                     if (r == null) {
 387                         r = new sun.security.provider.SecureRandom();
 388                         try {
 389                             byte[] b = new byte[20];
 390                             readFully(nextIn, b);
 391                             r.engineSetSeed(b);
 392                         } catch (IOException e) {
 393                             throw new ProviderException("init failed", e);
 394                         }
 395                         mixRandom = r;
 396                     }
 397                 }
 398             }
 399             return r;
 400         }
 401 
 402         // read data.length bytes from in
 403         // These are not normal files, so we need to loop the read.
 404         // just keep trying as long as we are making progress
 405         private static void readFully(InputStream in, byte[] data)
 406                 throws IOException {
 407             int len = data.length;
 408             int ofs = 0;
 409             while (len > 0) {
 410                 int k = in.read(data, ofs, len);
 411                 if (k <= 0) {
 412                     throw new EOFException("File(s) closed?");
 413                 }
 414                 ofs += k;
 415                 len -= k;
 416             }
 417             if (len > 0) {
 418                 throw new IOException("Could not read from file(s)");
 419             }
 420         }
 421 
 422         // get true random bytes, just read from "seed"
 423         private byte[] implGenerateSeed(int numBytes) {
 424             synchronized (LOCK_GET_SEED) {
 425                 try {
 426                     byte[] b = new byte[numBytes];
 427                     readFully(seedIn, b);
 428                     return b;
 429                 } catch (IOException e) {
 430                     throw new ProviderException("generateSeed() failed", e);
 431                 }
 432             }
 433         }
 434 
 435         // supply random bytes to the OS
 436         // write to "seed" if possible
 437         // always add the seed to our mixing random
 438         private void implSetSeed(byte[] seed) {
 439             synchronized (LOCK_SET_SEED) {
 440                 if (seedOutInitialized == false) {
 441                     seedOutInitialized = true;
 442                     seedOut = AccessController.doPrivileged(
 443                             new PrivilegedAction<OutputStream>() {
 444                         @Override
 445                         public OutputStream run() {
 446                             try {
 447                                 return new FileOutputStream(seedFile, true);
 448                             } catch (Exception e) {
 449                                 return null;
 450                             }
 451                         }
 452                     });
 453                 }
 454                 if (seedOut != null) {
 455                     try {
 456                         seedOut.write(seed);
 457                     } catch (IOException e) {
 458                         throw new ProviderException("setSeed() failed", e);
 459                     }
 460                 }
 461                 getMixRandom().engineSetSeed(seed);
 462             }
 463         }
 464 
 465         // ensure that there is at least one valid byte in the buffer
 466         // if not, read new bytes
 467         private void ensureBufferValid() throws IOException {
 468             long time = System.currentTimeMillis();
 469             if ((buffered > 0) && (time - lastRead < MAX_BUFFER_TIME)) {
 470                 return;
 471             }
 472             lastRead = time;
 473             readFully(nextIn, nextBuffer);
 474             buffered = nextBuffer.length;
 475         }
 476 
 477         // get pseudo random bytes
 478         // read from "next" and XOR with bytes generated by the
 479         // mixing SHA1PRNG
 480         private void implNextBytes(byte[] data) {
 481             synchronized (LOCK_GET_BYTES) {
 482                 try {
 483                     getMixRandom().engineNextBytes(data);
 484                     int len = data.length;
 485                     int ofs = 0;
 486                     while (len > 0) {
 487                         ensureBufferValid();
 488                         int bufferOfs = nextBuffer.length - buffered;
 489                         while ((len > 0) && (buffered > 0)) {
 490                             data[ofs++] ^= nextBuffer[bufferOfs++];
 491                             len--;
 492                             buffered--;
 493                         }
 494                     }
 495                 } catch (IOException e) {
 496                     throw new ProviderException("nextBytes() failed", e);
 497                 }
 498             }
 499         }
 500     }
 501 }