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      */
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      */
initIO(final Variant v)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 }
571