1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 package org.apache.commons.crypto.jna;
19
20 import java.nio.ByteBuffer;
21 import java.security.GeneralSecurityException;
22 import java.security.NoSuchAlgorithmException;
23 import java.util.Properties;
24
25 import org.apache.commons.crypto.random.CryptoRandom;
26
27 import com.sun.jna.NativeLong;
28 import com.sun.jna.ptr.PointerByReference;
29
30 /**
31 * <p>
32 * OpenSSL secure random using JNA. This implementation is thread-safe.
33 * </p>
34 *
35 * <p>
36 * If using an Intel chipset with RDRAND, the high-performance hardware random
37 * number generator will be used and it's much faster than SecureRandom. If
38 * RDRAND is unavailable, default OpenSSL secure random generator will be used.
39 * It's still faster and can generate strong random bytes.
40 * </p>
41 *
42 * @see <a href="https://wiki.openssl.org/index.php/Random_Numbers">
43 * https://wiki.openssl.org/index.php/Random_Numbers</a>
44 * @see <a href="http://en.wikipedia.org/wiki/RdRand">
45 * http://en.wikipedia.org/wiki/RdRand</a>
46 */
47 final class OpenSslJnaCryptoRandom implements CryptoRandom {
48
49 private static final int ENGINE_METHOD_RAND = 0x0008;
50
51 private final boolean rdrandEnabled;
52 private final transient PointerByReference rdrandEngine;
53
54 /**
55 * Constructs a {@link OpenSslJnaCryptoRandom}.
56 *
57 * @param props the configuration properties (not used)
58 * @throws GeneralSecurityException if could not enable JNA access
59 */
60 public OpenSslJnaCryptoRandom(final Properties props) //NOPMD
61 throws GeneralSecurityException {
62 if (!OpenSslJna.isEnabled()) {
63 throw new GeneralSecurityException("Could not enable JNA access", OpenSslJna.initialisationError());
64 }
65
66 boolean rdrandLoaded = false;
67 try {
68 OpenSslNativeJna.ENGINE_load_rdrand();
69 rdrandEngine = OpenSslNativeJna.ENGINE_by_id("rdrand");
70 if (rdrandEngine != null) {
71 final int rc = OpenSslNativeJna.ENGINE_init(rdrandEngine);
72
73 if (rc != 0) {
74 final int rc2 = OpenSslNativeJna.ENGINE_set_default(rdrandEngine, ENGINE_METHOD_RAND);
75 if (rc2 != 0) {
76 rdrandLoaded = true;
77 }
78 }
79 }
80
81 } catch (final Exception e) {
82 throw new NoSuchAlgorithmException();
83 }
84
85 rdrandEnabled = rdrandLoaded;
86
87 if (!rdrandLoaded) {
88 closeRdrandEngine(false);
89 }
90 }
91
92 /**
93 * Overrides {@link java.lang.AutoCloseable#close()}. Closes OpenSSL context
94 * if native enabled.
95 */
96 @Override
97 public void close() {
98 closeRdrandEngine(true);
99 OpenSslNativeJna.ENGINE_cleanup();
100
101 //cleanup locks
102 //OpenSslNativeJna.CRYPTO_set_locking_callback(null);
103 //LOCK.unlock();
104 }
105
106 /**
107 * Closes the rdrand engine.
108 * @param closing true when called while closing.
109 */
110 private void closeRdrandEngine(final boolean closing) {
111
112 if (rdrandEngine != null) {
113 throwOnError(OpenSslNativeJna.ENGINE_finish(rdrandEngine), closing);
114 throwOnError(OpenSslNativeJna.ENGINE_free(rdrandEngine), closing);
115 }
116 }
117
118 /**
119 * Checks if rdrand engine is used to retrieve random bytes
120 *
121 * @return true if rdrand is used, false if default engine is used
122 */
123 public boolean isRdrandEnabled() {
124 return rdrandEnabled;
125 }
126
127 /**
128 * Generates a user-specified number of random bytes. It's thread-safe.
129 *
130 * @param bytes the array to be filled in with random bytes.
131 */
132 @Override
133 public void nextBytes(final byte[] bytes) {
134
135 synchronized (OpenSslJnaCryptoRandom.class) {
136 // this method is synchronized for now
137 // to support multithreading https://wiki.openssl.org/index.php/Manual:Threads(3) needs to be done
138
139 if (rdrandEnabled && OpenSslNativeJna.RAND_get_rand_method().equals(OpenSslNativeJna.RAND_SSLeay())) {
140 close();
141 throw new IllegalStateException("rdrand should be used but default is detected");
142 }
143
144 final int byteLength = bytes.length;
145 final ByteBuffer buf = ByteBuffer.allocateDirect(byteLength);
146 throwOnError(OpenSslNativeJna.RAND_bytes(buf, byteLength), false);
147 buf.rewind();
148 buf.get(bytes, 0, byteLength);
149 }
150 }
151
152 /**
153 * @param retVal the result value of error.
154 * @param closing true when called while closing.
155 */
156 private void throwOnError(final int retVal, final boolean closing) {
157 if (retVal != 1) {
158 final NativeLong err = OpenSslNativeJna.ERR_peek_error();
159 final String errdesc = OpenSslNativeJna.ERR_error_string(err, null);
160 if (!closing) {
161 close();
162 }
163 throw new IllegalStateException("return code " + retVal + " from OpenSSL. Err code is " + err + ": " + errdesc);
164 }
165 }
166 }