1 /* 2 * Copyright (c) 2003, 2021, 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 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 */ getEgdUrl()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 @SuppressWarnings("removal") initIO(final Variant v)129 private static RandomIO initIO(final Variant v) { 130 return AccessController.doPrivileged( 131 new PrivilegedAction<>() { 132 @Override 133 public RandomIO run() { 134 135 File seedFile; 136 File nextFile; 137 138 switch(v) { 139 case MIXED: 140 URL egdUrl; 141 File egdFile = null; 142 143 if ((egdUrl = getEgdUrl()) != null) { 144 try { 145 egdFile = SunEntries.getDeviceFile(egdUrl); 146 } catch (IOException e) { 147 // Swallow, seedFile is still null 148 } 149 } 150 151 // Try egd first. 152 if ((egdFile != null) && egdFile.canRead()) { 153 seedFile = egdFile; 154 } else { 155 // fall back to /dev/random. 156 seedFile = new File(NAME_RANDOM); 157 } 158 nextFile = new File(NAME_URANDOM); 159 break; 160 161 case BLOCKING: 162 seedFile = new File(NAME_RANDOM); 163 nextFile = new File(NAME_RANDOM); 164 break; 165 166 case NONBLOCKING: 167 seedFile = new File(NAME_URANDOM); 168 nextFile = new File(NAME_URANDOM); 169 break; 170 171 default: 172 // Shouldn't happen! 173 return null; 174 } 175 176 if (debug != null) { 177 debug.println("NativePRNG." + v + 178 " seedFile: " + seedFile + 179 " nextFile: " + nextFile); 180 } 181 182 if (!seedFile.canRead() || !nextFile.canRead()) { 183 if (debug != null) { 184 debug.println("NativePRNG." + v + 185 " Couldn't read Files."); 186 } 187 return null; 188 } 189 190 try { 191 return new RandomIO(seedFile, nextFile); 192 } catch (Exception e) { 193 return null; 194 } 195 } 196 }); 197 } 198 199 // return whether the NativePRNG is available 200 static boolean isAvailable() { 201 return INSTANCE != null; 202 } 203 204 // constructor, called by the JCA framework 205 public NativePRNG() { 206 super(); 207 if (INSTANCE == null) { 208 throw new AssertionError("NativePRNG not available"); 209 } 210 } 211 212 // set the seed 213 @Override 214 protected void engineSetSeed(byte[] seed) { 215 INSTANCE.implSetSeed(seed); 216 } 217 218 // get pseudo random bytes 219 @Override 220 protected void engineNextBytes(byte[] bytes) { 221 INSTANCE.implNextBytes(bytes); 222 } 223 224 // get true random bytes 225 @Override 226 protected byte[] engineGenerateSeed(int numBytes) { 227 return INSTANCE.implGenerateSeed(numBytes); 228 } 229 230 /** 231 * A NativePRNG-like class that uses /dev/random for both 232 * seed and random material. 233 * 234 * Note that it does not respect the egd properties, since we have 235 * no way of knowing what those qualities are. 236 * 237 * This is very similar to the outer NativePRNG class, minimizing any 238 * breakage to the serialization of the existing implementation. 239 * 240 * @since 1.8 241 */ 242 public static final class Blocking extends SecureRandomSpi { 243 private static final long serialVersionUID = -6396183145759983347L; 244 245 private static final RandomIO INSTANCE = initIO(Variant.BLOCKING); 246 247 // return whether this is available 248 static boolean isAvailable() { 249 return INSTANCE != null; 250 } 251 252 // constructor, called by the JCA framework 253 public Blocking() { 254 super(); 255 if (INSTANCE == null) { 256 throw new AssertionError("NativePRNG$Blocking not available"); 257 } 258 } 259 260 // set the seed 261 @Override 262 protected void engineSetSeed(byte[] seed) { 263 INSTANCE.implSetSeed(seed); 264 } 265 266 // get pseudo random bytes 267 @Override 268 protected void engineNextBytes(byte[] bytes) { 269 INSTANCE.implNextBytes(bytes); 270 } 271 272 // get true random bytes 273 @Override 274 protected byte[] engineGenerateSeed(int numBytes) { 275 return INSTANCE.implGenerateSeed(numBytes); 276 } 277 } 278 279 /** 280 * A NativePRNG-like class that uses /dev/urandom for both 281 * seed and random material. 282 * 283 * Note that it does not respect the egd properties, since we have 284 * no way of knowing what those qualities are. 285 * 286 * This is very similar to the outer NativePRNG class, minimizing any 287 * breakage to the serialization of the existing implementation. 288 * 289 * @since 1.8 290 */ 291 public static final class NonBlocking extends SecureRandomSpi { 292 private static final long serialVersionUID = -1102062982994105487L; 293 294 private static final RandomIO INSTANCE = initIO(Variant.NONBLOCKING); 295 296 // return whether this is available 297 static boolean isAvailable() { 298 return INSTANCE != null; 299 } 300 301 // constructor, called by the JCA framework 302 public NonBlocking() { 303 super(); 304 if (INSTANCE == null) { 305 throw new AssertionError( 306 "NativePRNG$NonBlocking not available"); 307 } 308 } 309 310 // set the seed 311 @Override 312 protected void engineSetSeed(byte[] seed) { 313 INSTANCE.implSetSeed(seed); 314 } 315 316 // get pseudo random bytes 317 @Override 318 protected void engineNextBytes(byte[] bytes) { 319 INSTANCE.implNextBytes(bytes); 320 } 321 322 // get true random bytes 323 @Override 324 protected byte[] engineGenerateSeed(int numBytes) { 325 return INSTANCE.implGenerateSeed(numBytes); 326 } 327 } 328 329 /** 330 * Nested class doing the actual work. Singleton, see INSTANCE above. 331 */ 332 private static class RandomIO { 333 334 // we buffer data we read from the "next" file for efficiency, 335 // but we limit the lifetime to avoid using stale bits 336 // lifetime in ms, currently 100 ms (0.1 s) 337 private static final long MAX_BUFFER_TIME = 100; 338 339 // size of the "next" buffer 340 private static final int MAX_BUFFER_SIZE = 65536; 341 private static final int MIN_BUFFER_SIZE = 32; 342 private int bufferSize = 256; 343 344 // Holder for the seedFile. Used if we ever add seed material. 345 File seedFile; 346 347 // In/OutputStream for "seed" and "next" 348 private final InputStream seedIn, nextIn; 349 private OutputStream seedOut; 350 351 // flag indicating if we have tried to open seedOut yet 352 private boolean seedOutInitialized; 353 354 // SHA1PRNG instance for mixing 355 // initialized lazily on demand to avoid problems during startup 356 private volatile sun.security.provider.SecureRandom mixRandom; 357 358 // buffer for next bits 359 private byte[] nextBuffer; 360 361 // number of bytes left in nextBuffer 362 private int buffered; 363 364 // time we read the data into the nextBuffer 365 private long lastRead; 366 367 // Count for the number of buffer size changes requests 368 // Positive value in increase size, negative to lower it. 369 private int change_buffer = 0; 370 371 // Request limit to trigger an increase in nextBuffer size 372 private static final int REQ_LIMIT_INC = 1000; 373 374 // Request limit to trigger a decrease in nextBuffer size 375 private static final int REQ_LIMIT_DEC = -100; 376 377 // mutex lock for nextBytes() 378 private final Object LOCK_GET_BYTES = new Object(); 379 380 // mutex lock for generateSeed() 381 private final Object LOCK_GET_SEED = new Object(); 382 383 // mutex lock for setSeed() 384 private final Object LOCK_SET_SEED = new Object(); 385 386 // constructor, called only once from initIO() 387 private RandomIO(File seedFile, File nextFile) throws IOException { 388 this.seedFile = seedFile; 389 seedIn = FileInputStreamPool.getInputStream(seedFile); 390 nextIn = FileInputStreamPool.getInputStream(nextFile); 391 nextBuffer = new byte[bufferSize]; 392 } 393 394 // get the SHA1PRNG for mixing 395 // initialize if not yet created 396 private sun.security.provider.SecureRandom getMixRandom() { 397 sun.security.provider.SecureRandom r = mixRandom; 398 if (r == null) { 399 synchronized (LOCK_GET_BYTES) { 400 r = mixRandom; 401 if (r == null) { 402 r = new sun.security.provider.SecureRandom(); 403 try { 404 byte[] b = new byte[20]; 405 readFully(nextIn, b); 406 r.engineSetSeed(b); 407 } catch (IOException e) { 408 throw new ProviderException("init failed", e); 409 } 410 mixRandom = r; 411 } 412 } 413 } 414 return r; 415 } 416 417 // read data.length bytes from in 418 // These are not normal files, so we need to loop the read. 419 // just keep trying as long as we are making progress 420 private static void readFully(InputStream in, byte[] data) 421 throws IOException { 422 int len = data.length; 423 int ofs = 0; 424 while (len > 0) { 425 int k = in.read(data, ofs, len); 426 if (k <= 0) { 427 throw new EOFException("File(s) closed?"); 428 } 429 ofs += k; 430 len -= k; 431 } 432 if (len > 0) { 433 throw new IOException("Could not read from file(s)"); 434 } 435 } 436 437 // get true random bytes, just read from "seed" 438 private byte[] implGenerateSeed(int numBytes) { 439 synchronized (LOCK_GET_SEED) { 440 try { 441 byte[] b = new byte[numBytes]; 442 readFully(seedIn, b); 443 return b; 444 } catch (IOException e) { 445 throw new ProviderException("generateSeed() failed", e); 446 } 447 } 448 } 449 450 // supply random bytes to the OS 451 // write to "seed" if possible 452 // always add the seed to our mixing random 453 @SuppressWarnings("removal") 454 private void implSetSeed(byte[] seed) { 455 synchronized (LOCK_SET_SEED) { 456 if (seedOutInitialized == false) { 457 seedOutInitialized = true; 458 seedOut = AccessController.doPrivileged( 459 new PrivilegedAction<>() { 460 @Override 461 public OutputStream run() { 462 try { 463 return new FileOutputStream(seedFile, true); 464 } catch (Exception e) { 465 return null; 466 } 467 } 468 }); 469 } 470 if (seedOut != null) { 471 try { 472 seedOut.write(seed); 473 } catch (IOException e) { 474 // Ignored. On Mac OS X, /dev/urandom can be opened 475 // for write, but actual write is not permitted. 476 } 477 } 478 getMixRandom().engineSetSeed(seed); 479 } 480 } 481 482 // ensure that there is at least one valid byte in the buffer 483 // if not, read new bytes 484 private void ensureBufferValid() throws IOException { 485 long time = System.currentTimeMillis(); 486 int new_buffer_size = 0; 487 488 // Check if buffer has bytes available that are not too old 489 if (buffered > 0) { 490 if (time - lastRead < MAX_BUFFER_TIME) { 491 return; 492 } else { 493 // byte is old, so subtract from counter to shrink buffer 494 change_buffer--; 495 } 496 } else { 497 // No bytes available, so add to count to increase buffer 498 change_buffer++; 499 } 500 501 // If counter has it a limit, increase or decrease size 502 if (change_buffer > REQ_LIMIT_INC) { 503 new_buffer_size = nextBuffer.length * 2; 504 } else if (change_buffer < REQ_LIMIT_DEC) { 505 new_buffer_size = nextBuffer.length / 2; 506 } 507 508 // If buffer size is to be changed, replace nextBuffer. 509 if (new_buffer_size > 0) { 510 if (new_buffer_size <= MAX_BUFFER_SIZE && 511 new_buffer_size >= MIN_BUFFER_SIZE) { 512 nextBuffer = new byte[new_buffer_size]; 513 if (debug != null) { 514 debug.println("Buffer size changed to " + 515 new_buffer_size); 516 } 517 } else { 518 if (debug != null) { 519 debug.println("Buffer reached limit: " + 520 nextBuffer.length); 521 } 522 } 523 change_buffer = 0; 524 } 525 526 // Load fresh random bytes into nextBuffer 527 lastRead = time; 528 readFully(nextIn, nextBuffer); 529 buffered = nextBuffer.length; 530 } 531 532 // get pseudo random bytes 533 // read from "next" and XOR with bytes generated by the 534 // mixing SHA1PRNG 535 private void implNextBytes(byte[] data) { 536 try { 537 getMixRandom().engineNextBytes(data); 538 int data_len = data.length; 539 int ofs = 0; 540 int len; 541 int buf_pos; 542 int localofs; 543 byte[] localBuffer; 544 545 while (data_len > 0) { 546 synchronized (LOCK_GET_BYTES) { 547 ensureBufferValid(); 548 buf_pos = nextBuffer.length - buffered; 549 if (data_len > buffered) { 550 len = buffered; 551 buffered = 0; 552 } else { 553 len = data_len; 554 buffered -= len; 555 } 556 localBuffer = Arrays.copyOfRange(nextBuffer, buf_pos, 557 buf_pos + len); 558 } 559 localofs = 0; 560 while (len > localofs) { 561 data[ofs] ^= localBuffer[localofs]; 562 ofs++; 563 localofs++; 564 } 565 data_len -= len; 566 } 567 } catch (IOException e){ 568 throw new ProviderException("nextBytes() failed", e); 569 } 570 } 571 } 572 } 573