NativePRNG.java revision 12745:f068a4ffddd2
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 26package sun.security.provider; 27 28import java.io.*; 29import java.net.*; 30import java.security.*; 31import 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 */ 74public 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<>() { 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 static final long MAX_BUFFER_TIME = 100; 335 336 // size of the "next" buffer 337 private static final 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 = FileInputStreamPool.getInputStream(seedFile); 375 nextIn = FileInputStreamPool.getInputStream(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<>() { 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} 502