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