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 }