Merged updates from trunk into stable branch
[feedcatcher.git] / vendor / rails / activesupport / lib / active_support / secure_random.rb
1 begin
2 require 'securerandom'
3 rescue LoadError
4 end
5
6 module ActiveSupport
7 if defined?(::SecureRandom)
8 # Use Ruby's SecureRandom library if available.
9 SecureRandom = ::SecureRandom # :nodoc:
10 else
11 # = Secure random number generator interface.
12 #
13 # This library is an interface for secure random number generator which is
14 # suitable for generating session key in HTTP cookies, etc.
15 #
16 # It supports following secure random number generators.
17 #
18 # * openssl
19 # * /dev/urandom
20 # * Win32
21 #
22 # *Note*: This module is based on the SecureRandom library from Ruby 1.9,
23 # revision 18786, August 23 2008. It's 100% interface-compatible with Ruby 1.9's
24 # SecureRandom library.
25 #
26 # == Example
27 #
28 # # random hexadecimal string.
29 # p SecureRandom.hex(10) #=> "52750b30ffbc7de3b362"
30 # p SecureRandom.hex(10) #=> "92b15d6c8dc4beb5f559"
31 # p SecureRandom.hex(11) #=> "6aca1b5c58e4863e6b81b8"
32 # p SecureRandom.hex(12) #=> "94b2fff3e7fd9b9c391a2306"
33 # p SecureRandom.hex(13) #=> "39b290146bea6ce975c37cfc23"
34 # ...
35 #
36 # # random base64 string.
37 # p SecureRandom.base64(10) #=> "EcmTPZwWRAozdA=="
38 # p SecureRandom.base64(10) #=> "9b0nsevdwNuM/w=="
39 # p SecureRandom.base64(10) #=> "KO1nIU+p9DKxGg=="
40 # p SecureRandom.base64(11) #=> "l7XEiFja+8EKEtY="
41 # p SecureRandom.base64(12) #=> "7kJSM/MzBJI+75j8"
42 # p SecureRandom.base64(13) #=> "vKLJ0tXBHqQOuIcSIg=="
43 # ...
44 #
45 # # random binary string.
46 # p SecureRandom.random_bytes(10) #=> "\016\t{\370g\310pbr\301"
47 # p SecureRandom.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337"
48 # ...
49 module SecureRandom
50 # SecureRandom.random_bytes generates a random binary string.
51 #
52 # The argument n specifies the length of the result string.
53 #
54 # If n is not specified, 16 is assumed.
55 # It may be larger in future.
56 #
57 # If secure random number generator is not available,
58 # NotImplementedError is raised.
59 def self.random_bytes(n=nil)
60 n ||= 16
61
62 unless defined? OpenSSL
63 begin
64 require 'openssl'
65 rescue LoadError
66 end
67 end
68
69 if defined? OpenSSL::Random
70 return OpenSSL::Random.random_bytes(n)
71 end
72
73 if !defined?(@has_urandom) || @has_urandom
74 flags = File::RDONLY
75 flags |= File::NONBLOCK if defined? File::NONBLOCK
76 flags |= File::NOCTTY if defined? File::NOCTTY
77 flags |= File::NOFOLLOW if defined? File::NOFOLLOW
78 begin
79 File.open("/dev/urandom", flags) {|f|
80 unless f.stat.chardev?
81 raise Errno::ENOENT
82 end
83 @has_urandom = true
84 ret = f.readpartial(n)
85 if ret.length != n
86 raise NotImplementedError, "Unexpected partial read from random device"
87 end
88 return ret
89 }
90 rescue Errno::ENOENT
91 @has_urandom = false
92 end
93 end
94
95 if !defined?(@has_win32)
96 begin
97 require 'Win32API'
98
99 crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext", 'PPPII', 'L')
100 @crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom", 'LIP', 'L')
101
102 hProvStr = " " * 4
103 prov_rsa_full = 1
104 crypt_verifycontext = 0xF0000000
105
106 if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full, crypt_verifycontext) == 0
107 raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}"
108 end
109 @hProv, = hProvStr.unpack('L')
110
111 @has_win32 = true
112 rescue LoadError
113 @has_win32 = false
114 end
115 end
116 if @has_win32
117 bytes = " " * n
118 if @crypt_gen_random.call(@hProv, bytes.size, bytes) == 0
119 raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}"
120 end
121 return bytes
122 end
123
124 raise NotImplementedError, "No random device"
125 end
126
127 # SecureRandom.hex generates a random hex string.
128 #
129 # The argument n specifies the length of the random length.
130 # The length of the result string is twice of n.
131 #
132 # If n is not specified, 16 is assumed.
133 # It may be larger in future.
134 #
135 # If secure random number generator is not available,
136 # NotImplementedError is raised.
137 def self.hex(n=nil)
138 random_bytes(n).unpack("H*")[0]
139 end
140
141 # SecureRandom.base64 generates a random base64 string.
142 #
143 # The argument n specifies the length of the random length.
144 # The length of the result string is about 4/3 of n.
145 #
146 # If n is not specified, 16 is assumed.
147 # It may be larger in future.
148 #
149 # If secure random number generator is not available,
150 # NotImplementedError is raised.
151 def self.base64(n=nil)
152 [random_bytes(n)].pack("m*").delete("\n")
153 end
154
155 # SecureRandom.random_number generates a random number.
156 #
157 # If an positive integer is given as n,
158 # SecureRandom.random_number returns an integer:
159 # 0 <= SecureRandom.random_number(n) < n.
160 #
161 # If 0 is given or an argument is not given,
162 # SecureRandom.random_number returns an float:
163 # 0.0 <= SecureRandom.random_number() < 1.0.
164 def self.random_number(n=0)
165 if 0 < n
166 hex = n.to_s(16)
167 hex = '0' + hex if (hex.length & 1) == 1
168 bin = [hex].pack("H*")
169 mask = bin[0]
170 mask |= mask >> 1
171 mask |= mask >> 2
172 mask |= mask >> 4
173 begin
174 rnd = SecureRandom.random_bytes(bin.length)
175 rnd[0] = rnd[0] & mask
176 end until rnd < bin
177 rnd.unpack("H*")[0].hex
178 else
179 # assumption: Float::MANT_DIG <= 64
180 i64 = SecureRandom.random_bytes(8).unpack("Q")[0]
181 Math.ldexp(i64 >> (64-Float::MANT_DIG), -Float::MANT_DIG)
182 end
183 end
184
185 # Following code is based on David Garamond's GUID library for Ruby.
186 def self.lastWin32ErrorMessage # :nodoc:
187 get_last_error = Win32API.new("kernel32", "GetLastError", '', 'L')
188 format_message = Win32API.new("kernel32", "FormatMessageA", 'LPLLPLPPPPPPPP', 'L')
189 format_message_ignore_inserts = 0x00000200
190 format_message_from_system = 0x00001000
191
192 code = get_last_error.call
193 msg = "\0" * 1024
194 len = format_message.call(format_message_ignore_inserts + format_message_from_system, 0, code, 0, msg, 1024, nil, nil, nil, nil, nil, nil, nil, nil)
195 msg[0, len].tr("\r", '').chomp
196 end
197 end
198 end
199 end