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
26package sun.security.provider;
27
28import java.io.*;
29import java.net.*;
30import java.security.*;
31import java.util.Arrays;
32
33import 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 */
76public 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}
571