OpenSslJnaCryptoRandom.java

 /*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.crypto.jna;

import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.util.Properties;

import org.apache.commons.crypto.random.CryptoRandom;

import com.sun.jna.NativeLong;
import com.sun.jna.ptr.PointerByReference;

/**
 * <p>
 * OpenSSL secure random using JNA. This implementation is thread-safe.
 * </p>
 *
 * <p>
 * If using an Intel chipset with RDRAND, the high-performance hardware random
 * number generator will be used and it's much faster than SecureRandom. If
 * RDRAND is unavailable, default OpenSSL secure random generator will be used.
 * It's still faster and can generate strong random bytes.
 * </p>
 *
 * @see <a href="https://wiki.openssl.org/index.php/Random_Numbers">
 *      https://wiki.openssl.org/index.php/Random_Numbers</a>
 * @see <a href="http://en.wikipedia.org/wiki/RdRand">
 *      http://en.wikipedia.org/wiki/RdRand</a>
 */
final class OpenSslJnaCryptoRandom implements CryptoRandom {

    private static final int ENGINE_METHOD_RAND = 0x0008;

    private final boolean rdrandEnabled;
    private final transient PointerByReference rdrandEngine;

    /**
     * Constructs a {@link OpenSslJnaCryptoRandom}.
     *
     * @param props the configuration properties (not used)
     * @throws GeneralSecurityException  if could not enable JNA access
     */
    public OpenSslJnaCryptoRandom(final Properties props) //NOPMD
            throws GeneralSecurityException {
        if (!OpenSslJna.isEnabled()) {
            throw new GeneralSecurityException("Could not enable JNA access", OpenSslJna.initialisationError());
        }

        boolean rdrandLoaded = false;
        try {
            OpenSslNativeJna.ENGINE_load_rdrand();
            rdrandEngine = OpenSslNativeJna.ENGINE_by_id("rdrand");
            if (rdrandEngine != null) {
                final int rc = OpenSslNativeJna.ENGINE_init(rdrandEngine);

                if (rc != 0) {
                    final int rc2 = OpenSslNativeJna.ENGINE_set_default(rdrandEngine, ENGINE_METHOD_RAND);
                    if (rc2 != 0) {
                        rdrandLoaded = true;
                    }
                }
            }

        } catch (final Exception e) {
            throw new NoSuchAlgorithmException();
        }

        rdrandEnabled = rdrandLoaded;

        if (!rdrandLoaded) {
            closeRdrandEngine(false);
        }
    }

    /**
     * Overrides {@link java.lang.AutoCloseable#close()}. Closes OpenSSL context
     * if native enabled.
     */
    @Override
    public void close() {
        closeRdrandEngine(true);
        OpenSslNativeJna.ENGINE_cleanup();

        //cleanup locks
        //OpenSslNativeJna.CRYPTO_set_locking_callback(null);
        //LOCK.unlock();
    }

    /**
     * Closes the rdrand engine.
     * @param closing true when called while closing.
     */
    private void closeRdrandEngine(final boolean closing) {

        if (rdrandEngine != null) {
            throwOnError(OpenSslNativeJna.ENGINE_finish(rdrandEngine), closing);
            throwOnError(OpenSslNativeJna.ENGINE_free(rdrandEngine), closing);
        }
    }

    /**
     * Checks if rdrand engine is used to retrieve random bytes
     *
     * @return true if rdrand is used, false if default engine is used
     */
    public boolean isRdrandEnabled() {
        return rdrandEnabled;
    }

    /**
     * Generates a user-specified number of random bytes. It's thread-safe.
     *
     * @param bytes the array to be filled in with random bytes.
     */
    @Override
    public void nextBytes(final byte[] bytes) {

        synchronized (OpenSslJnaCryptoRandom.class) {
            // this method is synchronized for now
            // to support multithreading https://wiki.openssl.org/index.php/Manual:Threads(3) needs to be done

            if (rdrandEnabled && OpenSslNativeJna.RAND_get_rand_method().equals(OpenSslNativeJna.RAND_SSLeay())) {
                close();
                throw new IllegalStateException("rdrand should be used but default is detected");
            }

            final int byteLength = bytes.length;
            final ByteBuffer buf = ByteBuffer.allocateDirect(byteLength);
            throwOnError(OpenSslNativeJna.RAND_bytes(buf, byteLength), false);
            buf.rewind();
            buf.get(bytes, 0, byteLength);
        }
    }

    /**
     * @param retVal the result value of error.
     * @param closing true when called while closing.
     */
    private void throwOnError(final int retVal, final boolean closing) {
        if (retVal != 1) {
            final NativeLong err = OpenSslNativeJna.ERR_peek_error();
            final String errdesc = OpenSslNativeJna.ERR_error_string(err, null);
            if (!closing) {
                close();
            }
            throw new IllegalStateException("return code " + retVal + " from OpenSSL. Err code is " + err + ": " + errdesc);
        }
    }
}