001: /*
002: * Copyright 2003-2004 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.security.provider;
027:
028: import java.io.*;
029:
030: import java.security.*;
031: import java.security.SecureRandom;
032:
033: /**
034: * Native PRNG implementation for Solaris/Linux. It interacts with
035: * /dev/random and /dev/urandom, so it is only available if those
036: * files are present. Otherwise, SHA1PRNG is used instead of this class.
037: *
038: * getSeed() and setSeed() directly read/write /dev/random. However,
039: * /dev/random is only writable by root in many configurations. Because
040: * we cannot just ignore bytes specified via setSeed(), we keep a
041: * SHA1PRNG around in parallel.
042: *
043: * nextBytes() reads the bytes directly from /dev/urandom (and then
044: * mixes them with bytes from the SHA1PRNG for the reasons explained
045: * above). Reading bytes from /dev/urandom means that constantly get
046: * new entropy the operating system has collected. This is a notable
047: * advantage over the SHA1PRNG model, which acquires entropy only
048: * initially during startup although the VM may be running for months.
049: *
050: * Also note that we do not need any initial pure random seed from
051: * /dev/random. This is an advantage because on some versions of Linux
052: * it can be exhausted very quickly and could thus impact startup time.
053: *
054: * Finally, note that we use a singleton for the actual work (RandomIO)
055: * to avoid having to open and close /dev/[u]random constantly. However,
056: * there may me many NativePRNG instances created by the JCA framework.
057: *
058: * @since 1.5
059: * @version 1.10, 05/05/07
060: * @author Andreas Sterbenz
061: */
062: public final class NativePRNG extends SecureRandomSpi {
063:
064: private static final long serialVersionUID = -6599091113397072932L;
065:
066: // name of the pure random file (also used for setSeed())
067: private static final String NAME_RANDOM = "/dev/random";
068: // name of the pseudo random file
069: private static final String NAME_URANDOM = "/dev/urandom";
070:
071: // singleton instance or null if not available
072: private static final RandomIO INSTANCE = initIO();
073:
074: private static RandomIO initIO() {
075: Object o = AccessController
076: .doPrivileged(new PrivilegedAction() {
077: public Object run() {
078: File randomFile = new File(NAME_RANDOM);
079: if (randomFile.exists() == false) {
080: return null;
081: }
082: File urandomFile = new File(NAME_URANDOM);
083: if (urandomFile.exists() == false) {
084: return null;
085: }
086: try {
087: return new RandomIO(randomFile, urandomFile);
088: } catch (Exception e) {
089: return null;
090: }
091: }
092: });
093: return (RandomIO) o;
094: }
095:
096: // return whether the NativePRNG is available
097: static boolean isAvailable() {
098: return INSTANCE != null;
099: }
100:
101: // constructor, called by the JCA framework
102: public NativePRNG() {
103: super ();
104: if (INSTANCE == null) {
105: throw new AssertionError("NativePRNG not available");
106: }
107: }
108:
109: // set the seed
110: protected void engineSetSeed(byte[] seed) {
111: INSTANCE.implSetSeed(seed);
112: }
113:
114: // get pseudo random bytes
115: protected void engineNextBytes(byte[] bytes) {
116: INSTANCE.implNextBytes(bytes);
117: }
118:
119: // get true random bytes
120: protected byte[] engineGenerateSeed(int numBytes) {
121: return INSTANCE.implGenerateSeed(numBytes);
122: }
123:
124: /**
125: * Nested class doing the actual work. Singleton, see INSTANCE above.
126: */
127: private static class RandomIO {
128:
129: // we buffer data we read from /dev/urandom for efficiency,
130: // but we limit the lifetime to avoid using stale bits
131: // lifetime in ms, currently 100 ms (0.1 s)
132: private final static long MAX_BUFFER_TIME = 100;
133:
134: // size of the /dev/urandom buffer
135: private final static int BUFFER_SIZE = 32;
136:
137: // In/OutputStream for /dev/random and /dev/urandom
138: private final InputStream randomIn, urandomIn;
139: private OutputStream randomOut;
140:
141: // flag indicating if we have tried to open randomOut yet
142: private boolean randomOutInitialized;
143:
144: // SHA1PRNG instance for mixing
145: // initialized lazily on demand to avoid problems during startup
146: private volatile sun.security.provider.SecureRandom mixRandom;
147:
148: // buffer for /dev/urandom bits
149: private final byte[] urandomBuffer;
150:
151: // number of bytes left in urandomBuffer
152: private int buffered;
153:
154: // time we read the data into the urandomBuffer
155: private long lastRead;
156:
157: // mutex lock for nextBytes()
158: private final Object LOCK_GET_BYTES = new Object();
159:
160: // mutex lock for getSeed()
161: private final Object LOCK_GET_SEED = new Object();
162:
163: // mutex lock for setSeed()
164: private final Object LOCK_SET_SEED = new Object();
165:
166: // constructor, called only once from initIO()
167: private RandomIO(File randomFile, File urandomFile)
168: throws IOException {
169: randomIn = new FileInputStream(randomFile);
170: urandomIn = new FileInputStream(urandomFile);
171: urandomBuffer = new byte[BUFFER_SIZE];
172: }
173:
174: // get the SHA1PRNG for mixing
175: // initialize if not yet created
176: private sun.security.provider.SecureRandom getMixRandom() {
177: sun.security.provider.SecureRandom r = mixRandom;
178: if (r == null) {
179: synchronized (LOCK_GET_BYTES) {
180: r = mixRandom;
181: if (r == null) {
182: r = new sun.security.provider.SecureRandom();
183: try {
184: byte[] b = new byte[20];
185: readFully(urandomIn, b);
186: r.engineSetSeed(b);
187: } catch (IOException e) {
188: throw new ProviderException("init failed",
189: e);
190: }
191: mixRandom = r;
192: }
193: }
194: }
195: return r;
196: }
197:
198: // read data.length bytes from in
199: // /dev/[u]random are not normal files, so we need to loop the read.
200: // just keep trying as long as we are making progress
201: private static void readFully(InputStream in, byte[] data)
202: throws IOException {
203: int len = data.length;
204: int ofs = 0;
205: while (len > 0) {
206: int k = in.read(data, ofs, len);
207: if (k <= 0) {
208: throw new EOFException("/dev/[u]random closed?");
209: }
210: ofs += k;
211: len -= k;
212: }
213: if (len > 0) {
214: throw new IOException(
215: "Could not read from /dev/[u]random");
216: }
217: }
218:
219: // get true random bytes, just read from /dev/random
220: private byte[] implGenerateSeed(int numBytes) {
221: synchronized (LOCK_GET_SEED) {
222: try {
223: byte[] b = new byte[numBytes];
224: readFully(randomIn, b);
225: return b;
226: } catch (IOException e) {
227: throw new ProviderException(
228: "generateSeed() failed", e);
229: }
230: }
231: }
232:
233: // supply random bytes to the OS
234: // write to /dev/random if possible
235: // always add the seed to our mixing random
236: private void implSetSeed(byte[] seed) {
237: synchronized (LOCK_SET_SEED) {
238: if (randomOutInitialized == false) {
239: randomOutInitialized = true;
240: randomOut = AccessController
241: .doPrivileged(new PrivilegedAction<OutputStream>() {
242: public OutputStream run() {
243: try {
244: return new FileOutputStream(
245: NAME_RANDOM, true);
246: } catch (Exception e) {
247: return null;
248: }
249: }
250: });
251: }
252: if (randomOut != null) {
253: try {
254: randomOut.write(seed);
255: } catch (IOException e) {
256: throw new ProviderException("setSeed() failed",
257: e);
258: }
259: }
260: getMixRandom().engineSetSeed(seed);
261: }
262: }
263:
264: // ensure that there is at least one valid byte in the buffer
265: // if not, read new bytes
266: private void ensureBufferValid() throws IOException {
267: long time = System.currentTimeMillis();
268: if ((buffered > 0) && (time - lastRead < MAX_BUFFER_TIME)) {
269: return;
270: }
271: lastRead = time;
272: readFully(urandomIn, urandomBuffer);
273: buffered = urandomBuffer.length;
274: }
275:
276: // get pseudo random bytes
277: // read from /dev/urandom and XOR with bytes generated by the
278: // mixing SHA1PRNG
279: private void implNextBytes(byte[] data) {
280: synchronized (LOCK_GET_BYTES) {
281: try {
282: getMixRandom().engineNextBytes(data);
283: int len = data.length;
284: int ofs = 0;
285: while (len > 0) {
286: ensureBufferValid();
287: int bufferOfs = urandomBuffer.length - buffered;
288: while ((len > 0) && (buffered > 0)) {
289: data[ofs++] ^= urandomBuffer[bufferOfs++];
290: len--;
291: buffered--;
292: }
293: }
294: } catch (IOException e) {
295: throw new ProviderException("nextBytes() failed", e);
296: }
297: }
298: }
299:
300: }
301:
302: }
|