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