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