~michaelforrest/use-case-mapper/trunk

« back to all changes in this revision

Viewing changes to vendor/rails/activesupport/lib/active_support/secure_random.rb

  • Committer: Michael Forrest
  • Date: 2010-10-15 16:28:50 UTC
  • Revision ID: michael.forrest@canonical.com-20101015162850-tj2vchanv0kr0dun
refrozeĀ gems

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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