View Javadoc
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 }