Skip to content

Commit 2edfaa6

Browse files
committed
[GR-20446] Update Random layout and implementation
PullRequest: truffleruby/2362
2 parents 687ae1f + 13635a3 commit 2edfaa6

37 files changed

+1095
-585
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ Compatibility:
5151
* Coerce the inherit argument to a boolean in `Module#const_defined?` and `Module#const_get` (#2240).
5252
* Refinements take place at `Object#method` and `Module#instance_method` (#2004, @ssnickolay).
5353
* Add support for `rb_scan_args_kw` in C API (#2244, @LillianZ).
54+
* Update random implementation layout to be more compatible (#2234).
5455

5556
Performance:
5657

lib/mri/securerandom.rb

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
# -*- coding: us-ascii -*-
2+
# frozen_string_literal: true
3+
4+
# == Secure random number generator interface.
5+
#
6+
# This library is an interface to secure random number generators which are
7+
# suitable for generating session keys in HTTP cookies, etc.
8+
#
9+
# You can use this library in your application by requiring it:
10+
#
11+
# require 'securerandom'
12+
#
13+
# It supports the following secure random number generators:
14+
#
15+
# * openssl
16+
# * /dev/urandom
17+
# * Win32
18+
#
19+
# SecureRandom is extended by the Random::Formatter module which
20+
# defines the following methods:
21+
#
22+
# * alphanumeric
23+
# * base64
24+
# * choose
25+
# * gen_random
26+
# * hex
27+
# * rand
28+
# * random_bytes
29+
# * random_number
30+
# * urlsafe_base64
31+
# * uuid
32+
#
33+
# These methods are usable as class methods of SecureRandom such as
34+
# `SecureRandom.hex`.
35+
#
36+
# === Examples
37+
#
38+
# Generate random hexadecimal strings:
39+
#
40+
# require 'securerandom'
41+
#
42+
# SecureRandom.hex(10) #=> "52750b30ffbc7de3b362"
43+
# SecureRandom.hex(10) #=> "92b15d6c8dc4beb5f559"
44+
# SecureRandom.hex(13) #=> "39b290146bea6ce975c37cfc23"
45+
#
46+
# Generate random base64 strings:
47+
#
48+
# SecureRandom.base64(10) #=> "EcmTPZwWRAozdA=="
49+
# SecureRandom.base64(10) #=> "KO1nIU+p9DKxGg=="
50+
# SecureRandom.base64(12) #=> "7kJSM/MzBJI+75j8"
51+
#
52+
# Generate random binary strings:
53+
#
54+
# SecureRandom.random_bytes(10) #=> "\016\t{\370g\310pbr\301"
55+
# SecureRandom.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337"
56+
#
57+
# Generate alphanumeric strings:
58+
#
59+
# SecureRandom.alphanumeric(10) #=> "S8baxMJnPl"
60+
# SecureRandom.alphanumeric(10) #=> "aOxAg8BAJe"
61+
#
62+
# Generate UUIDs:
63+
#
64+
# SecureRandom.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
65+
# SecureRandom.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
66+
#
67+
68+
module SecureRandom
69+
@rng_chooser = Mutex.new # :nodoc:
70+
71+
class << self
72+
def bytes(n)
73+
return gen_random(n)
74+
end
75+
76+
def gen_random(n)
77+
ret = Random.urandom(1)
78+
if ret.nil?
79+
begin
80+
require 'openssl'
81+
rescue NoMethodError
82+
raise NotImplementedError, "No random device"
83+
else
84+
@rng_chooser.synchronize do
85+
class << self
86+
remove_method :gen_random
87+
alias gen_random gen_random_openssl
88+
public :gen_random
89+
end
90+
end
91+
return gen_random(n)
92+
end
93+
else
94+
@rng_chooser.synchronize do
95+
class << self
96+
remove_method :gen_random
97+
alias gen_random gen_random_urandom
98+
public :gen_random
99+
end
100+
end
101+
return gen_random(n)
102+
end
103+
end
104+
105+
private
106+
107+
def gen_random_openssl(n)
108+
@pid = 0 unless defined?(@pid)
109+
pid = $$
110+
unless @pid == pid
111+
now = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
112+
OpenSSL::Random.random_add([now, @pid, pid].join(""), 0.0)
113+
seed = Random.urandom(16)
114+
if (seed)
115+
OpenSSL::Random.random_add(seed, 16)
116+
end
117+
@pid = pid
118+
end
119+
return OpenSSL::Random.random_bytes(n)
120+
end
121+
122+
def gen_random_urandom(n)
123+
ret = Random.urandom(n)
124+
unless ret
125+
raise NotImplementedError, "No random device"
126+
end
127+
unless ret.length == n
128+
raise NotImplementedError, "Unexpected partial read from random device: only #{ret.length} for #{n} bytes"
129+
end
130+
ret
131+
end
132+
end
133+
end
134+
135+
module Random::Formatter
136+
137+
# SecureRandom.random_bytes generates a random binary string.
138+
#
139+
# The argument _n_ specifies the length of the result string.
140+
#
141+
# If _n_ is not specified or is nil, 16 is assumed.
142+
# It may be larger in future.
143+
#
144+
# The result may contain any byte: "\x00" - "\xff".
145+
#
146+
# require 'securerandom'
147+
#
148+
# SecureRandom.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6"
149+
# SecureRandom.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97"
150+
#
151+
# If a secure random number generator is not available,
152+
# +NotImplementedError+ is raised.
153+
def random_bytes(n=nil)
154+
n = n ? n.to_int : 16
155+
gen_random(n)
156+
end
157+
158+
# SecureRandom.hex generates a random hexadecimal string.
159+
#
160+
# The argument _n_ specifies the length, in bytes, of the random number to be generated.
161+
# The length of the resulting hexadecimal string is twice of _n_.
162+
#
163+
# If _n_ is not specified or is nil, 16 is assumed.
164+
# It may be larger in the future.
165+
#
166+
# The result may contain 0-9 and a-f.
167+
#
168+
# require 'securerandom'
169+
#
170+
# SecureRandom.hex #=> "eb693ec8252cd630102fd0d0fb7c3485"
171+
# SecureRandom.hex #=> "91dc3bfb4de5b11d029d376634589b61"
172+
#
173+
# If a secure random number generator is not available,
174+
# +NotImplementedError+ is raised.
175+
def hex(n=nil)
176+
random_bytes(n).unpack("H*")[0]
177+
end
178+
179+
# SecureRandom.base64 generates a random base64 string.
180+
#
181+
# The argument _n_ specifies the length, in bytes, of the random number
182+
# to be generated. The length of the result string is about 4/3 of _n_.
183+
#
184+
# If _n_ is not specified or is nil, 16 is assumed.
185+
# It may be larger in the future.
186+
#
187+
# The result may contain A-Z, a-z, 0-9, "+", "/" and "=".
188+
#
189+
# require 'securerandom'
190+
#
191+
# SecureRandom.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A=="
192+
# SecureRandom.base64 #=> "6BbW0pxO0YENxn38HMUbcQ=="
193+
#
194+
# If a secure random number generator is not available,
195+
# +NotImplementedError+ is raised.
196+
#
197+
# See RFC 3548 for the definition of base64.
198+
def base64(n=nil)
199+
[random_bytes(n)].pack("m0")
200+
end
201+
202+
# SecureRandom.urlsafe_base64 generates a random URL-safe base64 string.
203+
#
204+
# The argument _n_ specifies the length, in bytes, of the random number
205+
# to be generated. The length of the result string is about 4/3 of _n_.
206+
#
207+
# If _n_ is not specified or is nil, 16 is assumed.
208+
# It may be larger in the future.
209+
#
210+
# The boolean argument _padding_ specifies the padding.
211+
# If it is false or nil, padding is not generated.
212+
# Otherwise padding is generated.
213+
# By default, padding is not generated because "=" may be used as a URL delimiter.
214+
#
215+
# The result may contain A-Z, a-z, 0-9, "-" and "_".
216+
# "=" is also used if _padding_ is true.
217+
#
218+
# require 'securerandom'
219+
#
220+
# SecureRandom.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg"
221+
# SecureRandom.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg"
222+
#
223+
# SecureRandom.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ=="
224+
# SecureRandom.urlsafe_base64(nil, true) #=> "-M8rLhr7JEpJlqFGUMmOxg=="
225+
#
226+
# If a secure random number generator is not available,
227+
# +NotImplementedError+ is raised.
228+
#
229+
# See RFC 3548 for the definition of URL-safe base64.
230+
def urlsafe_base64(n=nil, padding=false)
231+
s = [random_bytes(n)].pack("m0")
232+
s.tr!("+/", "-_")
233+
s.delete!("=") unless padding
234+
s
235+
end
236+
237+
# SecureRandom.uuid generates a random v4 UUID (Universally Unique IDentifier).
238+
#
239+
# require 'securerandom'
240+
#
241+
# SecureRandom.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
242+
# SecureRandom.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
243+
# SecureRandom.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b"
244+
#
245+
# The version 4 UUID is purely random (except the version).
246+
# It doesn't contain meaningful information such as MAC addresses, timestamps, etc.
247+
#
248+
# The result contains 122 random bits (15.25 random bytes).
249+
#
250+
# See RFC 4122 for details of UUID.
251+
#
252+
def uuid
253+
ary = random_bytes(16).unpack("NnnnnN")
254+
ary[2] = (ary[2] & 0x0fff) | 0x4000
255+
ary[3] = (ary[3] & 0x3fff) | 0x8000
256+
"%08x-%04x-%04x-%04x-%04x%08x" % ary
257+
end
258+
259+
private def gen_random(n)
260+
self.bytes(n)
261+
end
262+
263+
# SecureRandom.choose generates a string that randomly draws from a
264+
# source array of characters.
265+
#
266+
# The argument _source_ specifies the array of characters from which
267+
# to generate the string.
268+
# The argument _n_ specifies the length, in characters, of the string to be
269+
# generated.
270+
#
271+
# The result may contain whatever characters are in the source array.
272+
#
273+
# require 'securerandom'
274+
#
275+
# SecureRandom.choose([*'l'..'r'], 16) #=> "lmrqpoonmmlqlron"
276+
# SecureRandom.choose([*'0'..'9'], 5) #=> "27309"
277+
#
278+
# If a secure random number generator is not available,
279+
# +NotImplementedError+ is raised.
280+
private def choose(source, n)
281+
size = source.size
282+
m = 1
283+
limit = size
284+
while limit * size <= 0x100000000
285+
limit *= size
286+
m += 1
287+
end
288+
result = ''.dup
289+
while m <= n
290+
rs = random_number(limit)
291+
is = rs.digits(size)
292+
(m-is.length).times { is << 0 }
293+
result << source.values_at(*is).join('')
294+
n -= m
295+
end
296+
if 0 < n
297+
rs = random_number(limit)
298+
is = rs.digits(size)
299+
if is.length < n
300+
(n-is.length).times { is << 0 }
301+
else
302+
is.pop while n < is.length
303+
end
304+
result.concat source.values_at(*is).join('')
305+
end
306+
result
307+
end
308+
309+
ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9']
310+
# SecureRandom.alphanumeric generates a random alphanumeric string.
311+
#
312+
# The argument _n_ specifies the length, in characters, of the alphanumeric
313+
# string to be generated.
314+
#
315+
# If _n_ is not specified or is nil, 16 is assumed.
316+
# It may be larger in the future.
317+
#
318+
# The result may contain A-Z, a-z and 0-9.
319+
#
320+
# require 'securerandom'
321+
#
322+
# SecureRandom.alphanumeric #=> "2BuBuLf3WfSKyQbR"
323+
# SecureRandom.alphanumeric(10) #=> "i6K93NdqiH"
324+
#
325+
# If a secure random number generator is not available,
326+
# +NotImplementedError+ is raised.
327+
def alphanumeric(n=nil)
328+
n = 16 if n.nil?
329+
choose(ALPHANUMERIC, n)
330+
end
331+
end
332+
333+
SecureRandom.extend(Random::Formatter)

lib/patches/securerandom.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# truffleruby_primitives: true
2+
3+
require 'securerandom'
4+
5+
module SecureRandom
6+
@randomizer = Truffle::SecureRandomizer.new
7+
8+
def self.gen_random(n)
9+
Primitive.vm_dev_urandom_bytes(n)
10+
end
11+
end

0 commit comments

Comments
 (0)