Project

General

Profile

Bug #4579 ยป securerandom.rb.diff

nahi (Hiroshi Nakamura), 06/16/2011 08:05 PM

View differences:

lib/securerandom.rb (working copy)
49 49
  # NotImplementedError is raised.
50 50
  def self.random_bytes(n=nil)
51 51
    n ||= 16
52

  
52 53
    if defined? OpenSSL::Random
54
      @pid = $$ if !defined?(@pid)
55
      pid = $$
56
      if @pid != pid
57
        now = Time.now
58
        ary = [now.to_i, now.usec, @pid, pid]
59
        OpenSSL::Random.seed(ary.to_s)
60
        @pid = pid
61
      end
53 62
      return OpenSSL::Random.random_bytes(n)
54 63
    end
64

  
55 65
    if !defined?(@has_urandom) || @has_urandom
56 66
      @has_urandom = false
57 67
      flags = File::RDONLY
test/test_securerandom.rb (revision 0)
1
require 'test/unit'
2
require 'securerandom'
3
require 'tempfile'
4

  
5
# This testcase does NOT aim to test cryptographically strongness and randomness.
6
class TestSecureRandom < Test::Unit::TestCase
7
  def setup
8
    @it = SecureRandom
9
  end
10

  
11
  def test_s_random_bytes
12
    assert_equal(16, @it.random_bytes.size)
13
    65.times do |idx|
14
      assert_equal(idx, @it.random_bytes(idx).size)
15
    end
16
  end
17

  
18
# This test took 2 minutes on my machine.
19
# And 65536 times loop could not be enough for forcing PID recycle.
20
if false
21
  def test_s_random_bytes_is_fork_safe
22
    begin
23
      require 'openssl'
24
    rescue LoadError
25
      return
26
    end
27
    SecureRandom.random_bytes(8)
28
    pid, v1 = forking_random_bytes
29
    assert(check_forking_random_bytes(pid, v1), 'Process ID not recycled?')
30
  end
31

  
32
  def forking_random_bytes
33
    r, w = IO.pipe
34
    pid = fork {
35
      r.close
36
      w.write SecureRandom.random_bytes(8)
37
      w.close
38
    }
39
    w.close
40
    v = r.read(8)
41
    r.close
42
    Process.waitpid2(pid)
43
    [pid, v]
44
  end
45

  
46
  def check_forking_random_bytes(target_pid, target)
47
    65536.times do
48
      pid = fork {
49
        if $$ == target_pid
50
          v2 = SecureRandom.random_bytes(8)
51
          if v2 == target
52
            exit(1)
53
          else
54
            exit(2)
55
          end
56
        end
57
        exit(3)
58
      }
59
      pid, status = Process.waitpid2(pid)
60
      case status.exitstatus
61
      when 1
62
        raise 'returned same sequence for same PID'
63
      when 2
64
        return true
65
      end
66
    end
67
    false # not recycled?
68
  end
69
end
70

  
71
  def test_s_random_bytes_without_openssl
72
    begin
73
      require 'openssl'
74
    rescue LoadError
75
      return
76
    end
77
    begin
78
      load_path = $LOAD_PATH.dup
79
      loaded_features = $LOADED_FEATURES.dup
80
      openssl = Object.instance_eval { remove_const(:OpenSSL) }
81

  
82
      remove_feature('securerandom.rb')
83
      remove_feature('openssl.rb')
84
      Dir.mktmpdir do |dir|
85
        open(File.join(dir, 'openssl.rb'), 'w') { |f|
86
          f << 'raise LoadError'
87
        }
88
        $LOAD_PATH.unshift(dir)
89
        require 'securerandom'
90
        test_s_random_bytes
91
      end
92
    ensure
93
      $LOADED_FEATURES.replace(loaded_features)
94
      $LOAD_PATH.replace(load_path)
95
      Object.const_set(:OpenSSL, openssl)
96
    end
97
  end
98

  
99
  def test_s_hex
100
    assert_equal(16 * 2, @it.hex.size)
101
    33.times do |idx|
102
      assert_equal(idx * 2, @it.hex(idx).size)
103
      assert_equal(idx, @it.hex(idx).gsub(/(..)/) { [$1].pack('H*') }.size)
104
    end
105
  end
106

  
107
  def test_s_base64
108
    assert_equal(16, @it.base64.unpack('m*')[0].size)
109
    17.times do |idx|
110
      assert_equal(idx, @it.base64(idx).unpack('m*')[0].size)
111
    end
112
  end
113

  
114
if false # not in 1.8.7
115
  def test_s_urlsafe_base64
116
    safe = /[\n+\/]/
117
    65.times do |idx|
118
      assert_not_match(safe, @it.urlsafe_base64(idx))
119
    end
120
    # base64 can include unsafe byte
121
    10001.times do |idx|
122
      return if safe =~ @it.base64(idx)
123
    end
124
    flunk
125
  end
126
end
127

  
128
  def test_s_random_number_float
129
    101.times do
130
      v = @it.random_number
131
      assert(0.0 <= v && v < 1.0)
132
    end
133
  end
134

  
135
  def test_s_random_number_float_by_zero
136
    101.times do
137
      v = @it.random_number(0)
138
      assert(0.0 <= v && v < 1.0)
139
    end
140
  end
141

  
142
  def test_s_random_number_int
143
    101.times do |idx|
144
      next if idx.zero?
145
      v = @it.random_number(idx)
146
      assert(0 <= v && v < idx)
147
    end
148
  end
149

  
150
if false # not in 1.8.7
151
  def test_uuid
152
    uuid = @it.uuid
153
    assert_equal(36, uuid.size)
154
    uuid.unpack('a8xa4xa4xa4xa12').each do |e|
155
      assert_match(/^[0-9a-f]+$/, e)
156
    end
157
  end
158
end
159

  
160
  def protect
161
    begin
162
      yield
163
    rescue NotImplementedError
164
      # ignore
165
    end
166
  end
167

  
168
  def remove_feature(basename)
169
    $LOADED_FEATURES.delete_if { |path|
170
      if File.basename(path) == basename
171
        $LOAD_PATH.any? { |dir|
172
          File.exists?(File.join(dir, basename))
173
        }
174
      end
175
    }
176
  end
177

  
178
end