NativePRNG.java :  » 6.0-JDK-Platform » solaris » sun » security » provider » Java Open Source

Java Open Source » 6.0 JDK Platform » solaris 
solaris » sun » security » provider » NativePRNG.java
/*
 * Copyright 2003-2004 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package sun.security.provider;

import java.io.*;

import java.security.*;
import java.security.SecureRandom;

/**
 * Native PRNG implementation for Solaris/Linux. It interacts with
 * /dev/random and /dev/urandom, so it is only available if those
 * files are present. Otherwise, SHA1PRNG is used instead of this class.
 *
 * getSeed() and setSeed() directly read/write /dev/random. However,
 * /dev/random is only writable by root in many configurations. Because
 * we cannot just ignore bytes specified via setSeed(), we keep a
 * SHA1PRNG around in parallel.
 *
 * nextBytes() reads the bytes directly from /dev/urandom (and then
 * mixes them with bytes from the SHA1PRNG for the reasons explained
 * above). Reading bytes from /dev/urandom means that constantly get
 * new entropy the operating system has collected. This is a notable
 * advantage over the SHA1PRNG model, which acquires entropy only
 * initially during startup although the VM may be running for months.
 *
 * Also note that we do not need any initial pure random seed from
 * /dev/random. This is an advantage because on some versions of Linux
 * it can be exhausted very quickly and could thus impact startup time.
 *
 * Finally, note that we use a singleton for the actual work (RandomIO)
 * to avoid having to open and close /dev/[u]random constantly. However,
 * there may me many NativePRNG instances created by the JCA framework.
 *
 * @since   1.5
 * @version 1.10, 05/05/07
 * @author  Andreas Sterbenz
 */
public final class NativePRNG extends SecureRandomSpi {
    
    private static final long serialVersionUID = -6599091113397072932L;

    // name of the pure random file (also used for setSeed())
    private static final String NAME_RANDOM = "/dev/random";
    // name of the pseudo random file
    private static final String NAME_URANDOM = "/dev/urandom";
    
    // singleton instance or null if not available
    private static final RandomIO INSTANCE = initIO();
    
    private static RandomIO initIO() {
  Object o = AccessController.doPrivileged(new PrivilegedAction() {
      public Object run() {
    File randomFile = new File(NAME_RANDOM);
    if (randomFile.exists() == false) {
        return null;
    }
    File urandomFile = new File(NAME_URANDOM);
    if (urandomFile.exists() == false) {
        return null;
    }
    try {
        return new RandomIO(randomFile, urandomFile);
    } catch (Exception e) {
        return null;
    }
      }
  });
  return (RandomIO)o;
    }
    
    // return whether the NativePRNG is available
    static boolean isAvailable() {
  return INSTANCE != null;
    }
    
    // constructor, called by the JCA framework
    public NativePRNG() {
  super();
  if (INSTANCE == null) {
      throw new AssertionError("NativePRNG not available");
  }
    }
    
    // set the seed
    protected void engineSetSeed(byte[] seed) {
  INSTANCE.implSetSeed(seed);
    }
    
    // get pseudo random bytes
    protected void engineNextBytes(byte[] bytes) {
  INSTANCE.implNextBytes(bytes);
    }
    
    // get true random bytes
    protected byte[] engineGenerateSeed(int numBytes) {
  return INSTANCE.implGenerateSeed(numBytes);
    }
    
    /**
     * Nested class doing the actual work. Singleton, see INSTANCE above.
     */
    private static class RandomIO {
  
  // we buffer data we read from /dev/urandom for efficiency,
  // but we limit the lifetime to avoid using stale bits
  // lifetime in ms, currently 100 ms (0.1 s)
  private final static long MAX_BUFFER_TIME = 100;
  
  // size of the /dev/urandom buffer
  private final static int BUFFER_SIZE = 32;
  
  // In/OutputStream for /dev/random and /dev/urandom
  private final InputStream randomIn, urandomIn;
  private OutputStream randomOut;
  
  // flag indicating if we have tried to open randomOut yet
  private boolean randomOutInitialized;
  
  // SHA1PRNG instance for mixing
  // initialized lazily on demand to avoid problems during startup
  private volatile sun.security.provider.SecureRandom mixRandom;
  
  // buffer for /dev/urandom bits
  private final byte[] urandomBuffer;
  
  // number of bytes left in urandomBuffer
  private int buffered;
  
  // time we read the data into the urandomBuffer
  private long lastRead;
  
  // mutex lock for nextBytes()
  private final Object LOCK_GET_BYTES = new Object();

  // mutex lock for getSeed()
  private final Object LOCK_GET_SEED = new Object();

  // mutex lock for setSeed()
  private final Object LOCK_SET_SEED = new Object();
  
  // constructor, called only once from initIO()
  private RandomIO(File randomFile, File urandomFile) throws IOException {
      randomIn = new FileInputStream(randomFile);
      urandomIn = new FileInputStream(urandomFile);
      urandomBuffer = new byte[BUFFER_SIZE];
  }
  
  // get the SHA1PRNG for mixing
  // initialize if not yet created
  private sun.security.provider.SecureRandom getMixRandom() {
      sun.security.provider.SecureRandom r = mixRandom;
      if (r == null) {
    synchronized (LOCK_GET_BYTES) {
        r = mixRandom;
        if (r == null) {
      r = new sun.security.provider.SecureRandom();
      try {
          byte[] b = new byte[20];
          readFully(urandomIn, b);
          r.engineSetSeed(b);
      } catch (IOException e) {
          throw new ProviderException("init failed", e);
      }
      mixRandom = r;
        }
    }
      }
      return r;
  }
  
  // read data.length bytes from in
  // /dev/[u]random are not normal files, so we need to loop the read.
  // just keep trying as long as we are making progress
  private static void readFully(InputStream in, byte[] data) 
    throws IOException {
      int len = data.length;
      int ofs = 0;
      while (len > 0) {
    int k = in.read(data, ofs, len);
    if (k <= 0) {
        throw new EOFException("/dev/[u]random closed?");
    }
    ofs += k;
    len -= k;
      }
      if (len > 0) {
    throw new IOException("Could not read from /dev/[u]random");
      }
  }
  
  // get true random bytes, just read from /dev/random
  private byte[] implGenerateSeed(int numBytes) {
      synchronized (LOCK_GET_SEED) {
    try {
        byte[] b = new byte[numBytes];
        readFully(randomIn, b);
        return b;
    } catch (IOException e) {
        throw new ProviderException("generateSeed() failed", e);
    }
      }
  }
  
  // supply random bytes to the OS
  // write to /dev/random if possible
  // always add the seed to our mixing random
  private void implSetSeed(byte[] seed) {
      synchronized (LOCK_SET_SEED) {
    if (randomOutInitialized == false) {
        randomOutInitialized = true;
        randomOut = AccessController.doPrivileged(
          new PrivilegedAction<OutputStream>() {
      public OutputStream run() {
          try {
        return new FileOutputStream(NAME_RANDOM, true);
          } catch (Exception e) {
        return null;
          }
      }
        });
    }
    if (randomOut != null) {
        try {
      randomOut.write(seed);
        } catch (IOException e) {
      throw new ProviderException("setSeed() failed", e);
        }
    }
    getMixRandom().engineSetSeed(seed);
      }
  }
  
  // ensure that there is at least one valid byte in the buffer
  // if not, read new bytes
  private void ensureBufferValid() throws IOException {
      long time = System.currentTimeMillis();
      if ((buffered > 0) && (time - lastRead < MAX_BUFFER_TIME)) {
    return;
      }
      lastRead = time;
      readFully(urandomIn, urandomBuffer);
      buffered = urandomBuffer.length;
  }
    
      // get pseudo random bytes
  // read from /dev/urandom and XOR with bytes generated by the
  // mixing SHA1PRNG
  private void implNextBytes(byte[] data) {
      synchronized (LOCK_GET_BYTES) {
    try {
        getMixRandom().engineNextBytes(data);
        int len = data.length;
        int ofs = 0;
        while (len > 0) {
      ensureBufferValid();
      int bufferOfs = urandomBuffer.length - buffered;
      while ((len > 0) && (buffered > 0)) {
          data[ofs++] ^= urandomBuffer[bufferOfs++];
          len--;
          buffered--;
      }
        }
    } catch (IOException e) {
        throw new ProviderException("nextBytes() failed", e);
    }
      }
  }
  
    }
    
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.