3636import org .jruby .runtime .ThreadContext ;
3737import org .jruby .runtime .builtin .IRubyObject ;
3838import org .jruby .util .ByteList ;
39+ import org .jruby .util .SafePropertyAccessor ;
40+
41+ import java .lang .reflect .Field ;
42+ import java .lang .reflect .InvocationTargetException ;
43+ import java .lang .reflect .Method ;
44+ import java .lang .reflect .Modifier ;
45+ import java .util .concurrent .ThreadLocalRandom ;
3946
4047/**
4148 * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
4249 */
4350public class Random {
4451
45- private static class Holder {
52+ // thread-local (default), shared, strong
53+ static final String HOLDER_TYPE = SafePropertyAccessor .getProperty ("jruby.openssl.random" , "" );
54+
55+ private static Holder createHolderImpl () {
56+ if (HOLDER_TYPE .equals ("default" ) || HOLDER_TYPE .equals ("thread-local" )) {
57+ return new ThreadLocalHolder ();
58+ }
59+ if (HOLDER_TYPE .equals ("" )) {
60+ // NOTE: fine to remove when support for running on Java 6 is gone ...
61+ if (OpenSSL .javaVersion6 (false )) {
62+ return new SharedHolder (); // can not-use ThreadLocalRandom.current()
63+ }
64+ }
65+ if (HOLDER_TYPE .equals ("shared" )) {
66+ return new SharedHolder ();
67+ }
68+ if (HOLDER_TYPE .equals ("strong" )) {
69+ return new StrongHolder ();
70+ }
71+ return new ThreadLocalHolder ();
72+ }
73+
74+ static abstract class Holder {
75+
76+ abstract java .util .Random getPlainRandom () ;
77+
78+ abstract java .security .SecureRandom getSecureRandom (ThreadContext context ) ;
79+
80+ void seedSecureRandom (ThreadContext context , byte [] seed ) {
81+ getSecureRandom (context ).setSeed (seed );
82+ }
83+
84+ void seedPlainRandom (long seed ) {
85+ getPlainRandom ().setSeed (seed );
86+ }
87+
88+ }
89+
90+ private static class SharedHolder extends Holder {
4691
4792 private volatile java .util .Random plainRandom ;
4893 private volatile java .security .SecureRandom secureRandom ;
@@ -58,7 +103,7 @@ java.util.Random getPlainRandom() {
58103 return plainRandom ;
59104 }
60105
61- java .security .SecureRandom getSecureRandom () {
106+ java .security .SecureRandom getSecureRandom (ThreadContext context ) {
62107 if (secureRandom == null ) {
63108 synchronized (this ) {
64109 if (secureRandom == null ) {
@@ -71,6 +116,131 @@ java.security.SecureRandom getSecureRandom() {
71116
72117 }
73118
119+ private static class ThreadLocalHolder extends Holder {
120+
121+ @ Override
122+ java .util .Random getPlainRandom () {
123+ return ThreadLocalRandom .current ();
124+ }
125+
126+ @ Override
127+ void seedPlainRandom (long seed ) {
128+ return ; // NO-OP - UnsupportedOperationException
129+ }
130+
131+ @ Override
132+ java .security .SecureRandom getSecureRandom (ThreadContext context ) {
133+ java .security .SecureRandom secureRandom = context .secureRandom ;
134+ if (secureRandom == null ) {
135+ secureRandom = getSecureRandomImpl ();
136+ setSecureRandom (context , secureRandom ); // context.secureRandom = ...
137+ }
138+ return secureRandom ;
139+ }
140+
141+ private static final Field secureRandomField ;
142+
143+ private static void setSecureRandom (ThreadContext context , java .security .SecureRandom secureRandom ) {
144+ if (secureRandomField != null ) {
145+ try {
146+ secureRandomField .set (context , secureRandom );
147+ }
148+ catch (Exception ex ) { /* IllegalAccessException should not happen */ }
149+ }
150+ }
151+
152+ private static final String PREFERRED_PRNG ;
153+ static {
154+ PREFERRED_PRNG = SafePropertyAccessor .getProperty ("jruby.preferred.prng" , "NativePRNGNonBlocking" );
155+
156+ Field secureRandom = null ;
157+ try {
158+ secureRandom = ThreadContext .class .getField ("secureRandom" );
159+ if ( ! secureRandom .isAccessible () || Modifier .isFinal (secureRandom .getModifiers ()) ) {
160+ secureRandom = null ;
161+ }
162+ }
163+ catch (Exception ex ) { /* ignore NoSuchFieldException */ }
164+ secureRandomField = secureRandom ;
165+ }
166+
167+ private static boolean tryPreferredPRNG = true ;
168+ private static boolean trySHA1PRNG = true ;
169+
170+ // copied from JRuby (not available in all 1.7.x) :
171+ public java .security .SecureRandom getSecureRandomImpl () {
172+ java .security .SecureRandom secureRandom = null ;
173+ // Try preferred PRNG, which defaults to NativePRNGNonBlocking
174+ if (tryPreferredPRNG ) {
175+ try {
176+ secureRandom = java .security .SecureRandom .getInstance (PREFERRED_PRNG );
177+ }
178+ catch (Exception e ) { tryPreferredPRNG = false ; }
179+ }
180+
181+ // Try SHA1PRNG
182+ if (secureRandom == null && trySHA1PRNG ) {
183+ try {
184+ secureRandom = java .security .SecureRandom .getInstance ("SHA1PRNG" );
185+ }
186+ catch (Exception e ) { trySHA1PRNG = false ; }
187+ }
188+
189+ // Just let JDK do whatever it does
190+ if (secureRandom == null ) {
191+ secureRandom = new java .security .SecureRandom ();
192+ }
193+
194+ return secureRandom ;
195+ }
196+
197+ }
198+
199+ private static class StrongHolder extends Holder {
200+
201+ static {
202+ Method method = null ;
203+ if (OpenSSL .javaVersion8 (true )) {
204+ try {
205+ method = java .security .SecureRandom .class .getMethod ("getInstanceStrong" );
206+ }
207+ catch (NoSuchMethodException ex ) { OpenSSL .debugStackTrace (ex ); }
208+ }
209+ getInstanceStrong = method ;
210+ }
211+
212+ private static final Method getInstanceStrong ;
213+
214+ @ Override
215+ java .util .Random getPlainRandom () {
216+ return new java .util .Random ();
217+ }
218+
219+ @ Override
220+ java .security .SecureRandom getSecureRandom (ThreadContext context ) {
221+ // return java.security.SecureRandom.getInstanceStrong(); (on Java 8)
222+ if (getInstanceStrong == null ) return SecurityHelper .getSecureRandom ();
223+ try {
224+ return (java .security .SecureRandom ) getInstanceStrong .invoke (null );
225+ }
226+ catch (IllegalAccessException ex ) {
227+ Utils .throwException (ex ); return null ; // won't happen
228+ }
229+ catch (InvocationTargetException ex ) {
230+ Utils .throwException (ex .getTargetException ()); return null ;
231+ }
232+ }
233+
234+ void seedSecureRandom (ThreadContext context , byte [] seed ) {
235+ // NOOP - new instance returned for getSecureRandom
236+ }
237+
238+ void seedPlainRandom (long seed ) {
239+ // NOOP - new instance returned for getPlainRandom
240+ }
241+
242+ }
243+
74244 public static void createRandom (final Ruby runtime , final RubyModule OpenSSL ) {
75245 final RubyModule Random = OpenSSL .defineModuleUnder ("Random" );
76246
@@ -79,31 +249,29 @@ public static void createRandom(final Ruby runtime, final RubyModule OpenSSL) {
79249
80250 Random .defineAnnotatedMethods (Random .class );
81251
82- Random .dataWrapStruct (new Holder ());
252+ Random .dataWrapStruct (createHolderImpl ());
83253 }
84254
85255 @ JRubyMethod (meta = true )
86256 public static RubyString random_bytes (final ThreadContext context ,
87257 final IRubyObject self , final IRubyObject arg ) {
88- final Ruby runtime = context .runtime ;
89- return random_bytes (runtime , self , toInt (runtime , arg ));
258+ return random_bytes (context , self , toInt (context .runtime , arg ));
90259 }
91260
92- static RubyString random_bytes (final Ruby runtime , final int len ) {
93- final RubyModule Random = (RubyModule ) runtime .getModule ("OpenSSL" ).getConstantAt ("Random" );
94- return generate (runtime , Random , len , true ); // secure-random
261+ static RubyString random_bytes (final ThreadContext context , final int len ) {
262+ final RubyModule Random = (RubyModule ) context . runtime .getModule ("OpenSSL" ).getConstantAt ("Random" );
263+ return generate (context , Random , len , true ); // secure-random
95264 }
96265
97- private static RubyString random_bytes (final Ruby runtime ,
266+ private static RubyString random_bytes (final ThreadContext context ,
98267 final IRubyObject self , final int len ) {
99- return generate (runtime , self , len , true ); // secure-random
268+ return generate (context , self , len , true ); // secure-random
100269 }
101270
102271 @ JRubyMethod (meta = true )
103272 public static RubyString pseudo_bytes (final ThreadContext context ,
104273 final IRubyObject self , final IRubyObject len ) {
105- final Ruby runtime = context .runtime ;
106- return generate (runtime , self , toInt (runtime , len ), false ); // plain-random
274+ return generate (context , self , toInt (context .runtime , len ), false ); // plain-random
107275 }
108276
109277 private static int toInt (final Ruby runtime , final IRubyObject arg ) {
@@ -114,12 +282,16 @@ private static int toInt(final Ruby runtime, final IRubyObject arg) {
114282 return (int ) len ;
115283 }
116284
117- private static RubyString generate (final Ruby runtime ,
285+ private static RubyString generate (final ThreadContext context ,
118286 final IRubyObject self , final int len , final boolean secure ) {
119287 final Holder holder = retrieveHolder ((RubyModule ) self );
120288 final byte [] bytes = new byte [len ];
121- ( secure ? holder .getSecureRandom () : holder .getPlainRandom () ).nextBytes (bytes );
122- return RubyString .newString (runtime , new ByteList (bytes , false ));
289+ ( secure ? holder .getSecureRandom (context ) : holder .getPlainRandom () ).nextBytes (bytes );
290+ return RubyString .newString (context .runtime , new ByteList (bytes , false ));
291+ }
292+
293+ static Holder getHolder (final Ruby runtime ) {
294+ return retrieveHolder ((RubyModule ) runtime .getModule ("OpenSSL" ).getConstantAt ("Random" ));
123295 }
124296
125297 private static Holder retrieveHolder (final RubyModule Random ) {
@@ -129,23 +301,23 @@ private static Holder retrieveHolder(final RubyModule Random) {
129301 @ JRubyMethod (meta = true ) // seed(str) -> str
130302 public static IRubyObject seed (final ThreadContext context ,
131303 final IRubyObject self , IRubyObject str ) {
132- seedImpl ((RubyModule ) self , str );
304+ seedImpl (context , (RubyModule ) self , str );
133305 return str ;
134306 }
135307
136- private static void seedImpl (final RubyModule Random , final IRubyObject str ) {
308+ private static void seedImpl (ThreadContext context , final RubyModule Random , final IRubyObject str ) {
137309 final byte [] seed = str .asString ().getBytes ();
138310 final Holder holder = retrieveHolder (Random );
139311
140- holder .getSecureRandom (). setSeed ( seed ); // seed supplements existing (secure) seeding mechanism
312+ holder .seedSecureRandom ( context , seed ); // seed supplements existing (secure) seeding mechanism
141313
142314 long s ; int l = seed .length ;
143315 if ( l >= 4 ) {
144316 s = (seed [0 ] << 24 ) | (seed [1 ] << 16 ) | (seed [2 ] << 8 ) | seed [3 ];
145317 if ( l >= 8 ) {
146318 s = s ^ (seed [l -4 ] << 24 ) | (seed [l -3 ] << 16 ) | (seed [l -2 ] << 8 ) | seed [l -1 ];
147319 }
148- holder .getPlainRandom (). setSeed (s );
320+ holder .seedPlainRandom (s );
149321 }
150322 }
151323
@@ -158,7 +330,7 @@ public static IRubyObject status_p(final ThreadContext context, final IRubyObjec
158330 @ JRubyMethod (meta = true , name = { "random_add" , "add" }) // random_add(str, entropy) -> self
159331 public static IRubyObject random_add (final ThreadContext context ,
160332 final IRubyObject self , IRubyObject str , IRubyObject entropy ) {
161- seedImpl ((RubyModule ) self , str ); // simply ignoring _entropy_ hint
333+ seedImpl (context , (RubyModule ) self , str ); // simply ignoring _entropy_ hint
162334 return self ;
163335 }
164336
0 commit comments