Skip to content

Commit 472e1eb

Browse files
committed
support jruby.openssl.random property for switching a random backend
we now default to a thread-local holder instance which will be re-using secure-random instances using JRuby's ThreadContext ('internals') **an attempt to 'fix' low-entropy systems** wating for /dev/random ... also respect jruby.preferred.prng and use its default - effectively likely avoids BC generator's constant attempts for seeding itself TO BE CONTINUED ...
1 parent 86132e0 commit 472e1eb

File tree

4 files changed

+208
-22
lines changed

4 files changed

+208
-22
lines changed

src/main/java/org/jruby/ext/openssl/Cipher.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,7 +1321,7 @@ public IRubyObject random_key(final ThreadContext context) {
13211321
// str = OpenSSL::Random.random_bytes(self.key_len)
13221322
// self.key = str
13231323
// return str
1324-
RubyString str = Random.random_bytes(context.runtime, this.keyLength);
1324+
RubyString str = Random.random_bytes(context, this.keyLength);
13251325
this.set_key(context, str); return str;
13261326
}
13271327

@@ -1330,7 +1330,7 @@ public IRubyObject random_iv(final ThreadContext context) {
13301330
// str = OpenSSL::Random.random_bytes(self.iv_len)
13311331
// self.iv = str
13321332
// return str
1333-
RubyString str = Random.random_bytes(context.runtime, this.ivLength);
1333+
RubyString str = Random.random_bytes(context, this.ivLength);
13341334
this.set_iv(context, str); return str;
13351335
}
13361336

src/main/java/org/jruby/ext/openssl/OpenSSL.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,11 @@ public static String javaVersion(final String def) {
259259
return javaVersionProperty;
260260
}
261261

262+
static boolean javaVersion6(final boolean atLeast) {
263+
final int gt = "1.6".compareTo( javaVersion("0.0").substring(0, 3) );
264+
return atLeast ? gt <= 0 : gt == 0;
265+
}
266+
262267
static boolean javaVersion7(final boolean atLeast) {
263268
final int gt = "1.7".compareTo( javaVersion("0.0").substring(0, 3) );
264269
return atLeast ? gt <= 0 : gt == 0;

src/main/java/org/jruby/ext/openssl/Random.java

Lines changed: 192 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,58 @@
3636
import org.jruby.runtime.ThreadContext;
3737
import org.jruby.runtime.builtin.IRubyObject;
3838
import 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:[email protected]">Ola Bini</a>
4249
*/
4350
public 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

src/main/java/org/jruby/ext/openssl/Utils.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,13 @@ private static RubyModule findImplementerIfNecessary(RubyModule clazz, RubyModul
140140
}
141141
}
142142

143+
144+
static void throwException(final Throwable e) {
145+
Utils.<RuntimeException>throwsUnchecked(e);
146+
}
147+
148+
private static <T extends Throwable> void throwsUnchecked(Throwable t) throws T {
149+
throw (T) t;
150+
}
151+
143152
}// Utils

0 commit comments

Comments
 (0)