blob: 8e25f2560683db09cb049d73ac2ed837e7d90a11 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2003-2004 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.security.provider;
27
28import java.io.*;
29
30import java.security.*;
31import java.security.SecureRandom;
32
33/**
34 * Native PRNG implementation for Solaris/Linux. It interacts with
35 * /dev/random and /dev/urandom, so it is only available if those
36 * files are present. Otherwise, SHA1PRNG is used instead of this class.
37 *
38 * getSeed() and setSeed() directly read/write /dev/random. However,
39 * /dev/random is only writable by root in many configurations. Because
40 * we cannot just ignore bytes specified via setSeed(), we keep a
41 * SHA1PRNG around in parallel.
42 *
43 * nextBytes() reads the bytes directly from /dev/urandom (and then
44 * mixes them with bytes from the SHA1PRNG for the reasons explained
45 * above). Reading bytes from /dev/urandom means that constantly get
46 * new entropy the operating system has collected. This is a notable
47 * advantage over the SHA1PRNG model, which acquires entropy only
48 * initially during startup although the VM may be running for months.
49 *
50 * Also note that we do not need any initial pure random seed from
51 * /dev/random. This is an advantage because on some versions of Linux
52 * it can be exhausted very quickly and could thus impact startup time.
53 *
54 * Finally, note that we use a singleton for the actual work (RandomIO)
55 * to avoid having to open and close /dev/[u]random constantly. However,
56 * there may me many NativePRNG instances created by the JCA framework.
57 *
58 * @since 1.5
59 * @author Andreas Sterbenz
60 */
61public final class NativePRNG extends SecureRandomSpi {
62
63 private static final long serialVersionUID = -6599091113397072932L;
64
65 // name of the pure random file (also used for setSeed())
66 private static final String NAME_RANDOM = "/dev/random";
67 // name of the pseudo random file
68 private static final String NAME_URANDOM = "/dev/urandom";
69
70 // singleton instance or null if not available
71 private static final RandomIO INSTANCE = initIO();
72
73 private static RandomIO initIO() {
74 Object o = AccessController.doPrivileged(new PrivilegedAction() {
75 public Object run() {
76 File randomFile = new File(NAME_RANDOM);
77 if (randomFile.exists() == false) {
78 return null;
79 }
80 File urandomFile = new File(NAME_URANDOM);
81 if (urandomFile.exists() == false) {
82 return null;
83 }
84 try {
85 return new RandomIO(randomFile, urandomFile);
86 } catch (Exception e) {
87 return null;
88 }
89 }
90 });
91 return (RandomIO)o;
92 }
93
94 // return whether the NativePRNG is available
95 static boolean isAvailable() {
96 return INSTANCE != null;
97 }
98
99 // constructor, called by the JCA framework
100 public NativePRNG() {
101 super();
102 if (INSTANCE == null) {
103 throw new AssertionError("NativePRNG not available");
104 }
105 }
106
107 // set the seed
108 protected void engineSetSeed(byte[] seed) {
109 INSTANCE.implSetSeed(seed);
110 }
111
112 // get pseudo random bytes
113 protected void engineNextBytes(byte[] bytes) {
114 INSTANCE.implNextBytes(bytes);
115 }
116
117 // get true random bytes
118 protected byte[] engineGenerateSeed(int numBytes) {
119 return INSTANCE.implGenerateSeed(numBytes);
120 }
121
122 /**
123 * Nested class doing the actual work. Singleton, see INSTANCE above.
124 */
125 private static class RandomIO {
126
127 // we buffer data we read from /dev/urandom for efficiency,
128 // but we limit the lifetime to avoid using stale bits
129 // lifetime in ms, currently 100 ms (0.1 s)
130 private final static long MAX_BUFFER_TIME = 100;
131
132 // size of the /dev/urandom buffer
133 private final static int BUFFER_SIZE = 32;
134
135 // In/OutputStream for /dev/random and /dev/urandom
136 private final InputStream randomIn, urandomIn;
137 private OutputStream randomOut;
138
139 // flag indicating if we have tried to open randomOut yet
140 private boolean randomOutInitialized;
141
142 // SHA1PRNG instance for mixing
143 // initialized lazily on demand to avoid problems during startup
144 private volatile sun.security.provider.SecureRandom mixRandom;
145
146 // buffer for /dev/urandom bits
147 private final byte[] urandomBuffer;
148
149 // number of bytes left in urandomBuffer
150 private int buffered;
151
152 // time we read the data into the urandomBuffer
153 private long lastRead;
154
155 // mutex lock for nextBytes()
156 private final Object LOCK_GET_BYTES = new Object();
157
158 // mutex lock for getSeed()
159 private final Object LOCK_GET_SEED = new Object();
160
161 // mutex lock for setSeed()
162 private final Object LOCK_SET_SEED = new Object();
163
164 // constructor, called only once from initIO()
165 private RandomIO(File randomFile, File urandomFile) throws IOException {
166 randomIn = new FileInputStream(randomFile);
167 urandomIn = new FileInputStream(urandomFile);
168 urandomBuffer = new byte[BUFFER_SIZE];
169 }
170
171 // get the SHA1PRNG for mixing
172 // initialize if not yet created
173 private sun.security.provider.SecureRandom getMixRandom() {
174 sun.security.provider.SecureRandom r = mixRandom;
175 if (r == null) {
176 synchronized (LOCK_GET_BYTES) {
177 r = mixRandom;
178 if (r == null) {
179 r = new sun.security.provider.SecureRandom();
180 try {
181 byte[] b = new byte[20];
182 readFully(urandomIn, b);
183 r.engineSetSeed(b);
184 } catch (IOException e) {
185 throw new ProviderException("init failed", e);
186 }
187 mixRandom = r;
188 }
189 }
190 }
191 return r;
192 }
193
194 // read data.length bytes from in
195 // /dev/[u]random are not normal files, so we need to loop the read.
196 // just keep trying as long as we are making progress
197 private static void readFully(InputStream in, byte[] data)
198 throws IOException {
199 int len = data.length;
200 int ofs = 0;
201 while (len > 0) {
202 int k = in.read(data, ofs, len);
203 if (k <= 0) {
204 throw new EOFException("/dev/[u]random closed?");
205 }
206 ofs += k;
207 len -= k;
208 }
209 if (len > 0) {
210 throw new IOException("Could not read from /dev/[u]random");
211 }
212 }
213
214 // get true random bytes, just read from /dev/random
215 private byte[] implGenerateSeed(int numBytes) {
216 synchronized (LOCK_GET_SEED) {
217 try {
218 byte[] b = new byte[numBytes];
219 readFully(randomIn, b);
220 return b;
221 } catch (IOException e) {
222 throw new ProviderException("generateSeed() failed", e);
223 }
224 }
225 }
226
227 // supply random bytes to the OS
228 // write to /dev/random if possible
229 // always add the seed to our mixing random
230 private void implSetSeed(byte[] seed) {
231 synchronized (LOCK_SET_SEED) {
232 if (randomOutInitialized == false) {
233 randomOutInitialized = true;
234 randomOut = AccessController.doPrivileged(
235 new PrivilegedAction<OutputStream>() {
236 public OutputStream run() {
237 try {
238 return new FileOutputStream(NAME_RANDOM, true);
239 } catch (Exception e) {
240 return null;
241 }
242 }
243 });
244 }
245 if (randomOut != null) {
246 try {
247 randomOut.write(seed);
248 } catch (IOException e) {
249 throw new ProviderException("setSeed() failed", e);
250 }
251 }
252 getMixRandom().engineSetSeed(seed);
253 }
254 }
255
256 // ensure that there is at least one valid byte in the buffer
257 // if not, read new bytes
258 private void ensureBufferValid() throws IOException {
259 long time = System.currentTimeMillis();
260 if ((buffered > 0) && (time - lastRead < MAX_BUFFER_TIME)) {
261 return;
262 }
263 lastRead = time;
264 readFully(urandomIn, urandomBuffer);
265 buffered = urandomBuffer.length;
266 }
267
268 // get pseudo random bytes
269 // read from /dev/urandom and XOR with bytes generated by the
270 // mixing SHA1PRNG
271 private void implNextBytes(byte[] data) {
272 synchronized (LOCK_GET_BYTES) {
273 try {
274 getMixRandom().engineNextBytes(data);
275 int len = data.length;
276 int ofs = 0;
277 while (len > 0) {
278 ensureBufferValid();
279 int bufferOfs = urandomBuffer.length - buffered;
280 while ((len > 0) && (buffered > 0)) {
281 data[ofs++] ^= urandomBuffer[bufferOfs++];
282 len--;
283 buffered--;
284 }
285 }
286 } catch (IOException e) {
287 throw new ProviderException("nextBytes() failed", e);
288 }
289 }
290 }
291
292 }
293
294}