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);
}
}
}