mischacks 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ *.gemspec
2
+ pkg
@@ -1,3 +1,8 @@
1
+ === 0.2.0 / 2010-08-29
2
+
3
+ * Add Random.
4
+ * Add Password.
5
+
1
6
  === 0.1.1 / 2010-08-25
2
7
 
3
8
  * Reupload 0.1.0 with fixed file permissions.
data/README.txt CHANGED
@@ -31,6 +31,15 @@ dump an uncaught exception.
31
31
 
32
32
  IO#best_datasync:: Try fdatasync, falling back to fsync, falling back to flush.
33
33
 
34
+ Random#exp:: Return a random integer 0 ≤ n < 2^argument (using SecureRandom).
35
+
36
+ Random#float:: Return a random float 0.0 ≤ n < argument (using SecureRandom).
37
+
38
+ Random#int:: Return a random integer 0 ≤ n < argument (using SecureRandom).
39
+
40
+ Password:: A small wrapper for String#crypt that does secure salt generation
41
+ and easy password verification.
42
+
34
43
  == FEATURES/PROBLEMS:
35
44
 
36
45
  The sh method is only safe if your sh script is safe. If unsure, add double
data/Rakefile CHANGED
@@ -28,7 +28,12 @@ begin
28
28
  Jeweler::Tasks.new do |gemspec|
29
29
  gemspec.name = "mischacks"
30
30
  gemspec.summary = "Miscellaneous methods that may or may not be useful"
31
- gemspec.description = "sh: Safely pass untrusted parameters to sh scripts. overwrite: Safely replace a file. Exception#to_formatted_string: Return a string that looks like how Ruby would dump an uncaught exception."
31
+ gemspec.description = \
32
+ "sh: Safely pass untrusted parameters to sh scripts. " \
33
+ "overwrite: Safely replace a file. " \
34
+ "Exception#to_formatted_string: Return a string that looks like how Ruby would dump an uncaught exception. " \
35
+ "Random: Generate various types of random numbers using SecureRandom. " \
36
+ "Password: A small wrapper for String#crypt that does secure salt generation and easy password verification."
32
37
  gemspec.email = "devel@johan.kiviniemi.name"
33
38
  gemspec.homepage = "http://johan.kiviniemi.name/software/mischacks/"
34
39
  gemspec.authors = ["Johan Kiviniemi"]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.0
@@ -13,8 +13,11 @@
13
13
  # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14
14
  # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
15
 
16
+ require 'mischacks/password'
17
+ require 'mischacks/random'
18
+
16
19
  module MiscHacks
17
- VERSION = '0.0.5'
20
+ VERSION = File.read(File.dirname(__FILE__)+'/../VERSION').chomp
18
21
 
19
22
  class ChildError < RuntimeError
20
23
  attr_reader :status
@@ -0,0 +1,57 @@
1
+ # mischacks – Miscellaneous methods that may or may not be useful
2
+ # Copyright © 2010 Johan Kiviniemi
3
+ #
4
+ # Permission to use, copy, modify, and/or distribute this software for any
5
+ # purpose with or without fee is hereby granted, provided that the above
6
+ # copyright notice and this permission notice appear in all copies.
7
+ #
8
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
+
16
+ require 'mischacks/random'
17
+
18
+ module MiscHacks
19
+ class Password
20
+ SALT_SET = ['a'..'z', 'A'..'Z', '0'..'9', %w{ . / }].map(&:to_a).flatten
21
+ SALT_SET.freeze
22
+ if SALT_SET.length != 64
23
+ raise RuntimeError, "SALT_SET.length = #{SALT_SET.length}, expected 64"
24
+ end
25
+
26
+ def self.random_salt
27
+ length = 9 + RANDOM.exp(3) # Up to 9+(2³−1)=16
28
+ (0...length).map { SALT_SET[RANDOM.exp(6)] }.join
29
+ end
30
+
31
+ def self.new_from_password password
32
+ new password.crypt('$6$%s$' % random_salt)
33
+ end
34
+
35
+ def initialize encrypted
36
+ @salt, @encrypted = encrypted.scan(/\A(\$[^$]+\$[^$]+\$)(.+)\z/).first
37
+ if @salt.nil? or @encrypted.nil?
38
+ raise ArgumentError, "Failed to parse #{encrypted.inspect}", caller
39
+ end
40
+ end
41
+
42
+ def match? password
43
+ to_s == password.crypt(@salt)
44
+ end
45
+
46
+ def to_s
47
+ [@salt, @encrypted].join
48
+ end
49
+
50
+ def inspect
51
+ '#<%s: %s>' % [self.class, to_s.inspect]
52
+ end
53
+ end
54
+ end
55
+
56
+ # vim:set et sw=2 sts=2:
57
+
@@ -0,0 +1,66 @@
1
+ # mischacks – Miscellaneous methods that may or may not be useful
2
+ # Copyright © 2010 Johan Kiviniemi
3
+ #
4
+ # Permission to use, copy, modify, and/or distribute this software for any
5
+ # purpose with or without fee is hereby granted, provided that the above
6
+ # copyright notice and this permission notice appear in all copies.
7
+ #
8
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
+
16
+ require 'securerandom'
17
+ require 'singleton'
18
+ require 'thread'
19
+
20
+ module MiscHacks
21
+ class Random
22
+ include Singleton
23
+
24
+ def initialize
25
+ @mutex = Mutex.new
26
+ @pool = 0
27
+ @pool_size = 0
28
+ end
29
+
30
+ # 0 ≤ return_value < 2^size_bits
31
+ def exp size_bits
32
+ @mutex.synchronize do
33
+ while @pool_size < size_bits
34
+ # Push 32×8 bits to the pool.
35
+ SecureRandom.random_bytes(32).unpack('Q*').each do |byte|
36
+ @pool = (@pool << 64) | byte
37
+ @pool_size += 64
38
+ end
39
+ end
40
+
41
+ # Unshift size_bits bits from the pool.
42
+ @pool_size -= size_bits
43
+ bits = @pool >> @pool_size
44
+ @pool ^= bits << @pool_size
45
+
46
+ bits
47
+ end
48
+ end
49
+
50
+ def float n=1
51
+ n*Math.ldexp(exp(Float::MANT_DIG), -Float::MANT_DIG)
52
+ end
53
+
54
+ def int n
55
+ float(n).floor
56
+ end
57
+
58
+ def inspect
59
+ "#<#{self.class}: pool_size=#{@pool_size}>"
60
+ end
61
+ end
62
+
63
+ RANDOM = Random.instance
64
+ end
65
+
66
+ # vim:set et sw=2 sts=2:
@@ -0,0 +1,136 @@
1
+ # mischacks – Miscellaneous methods that may or may not be useful
2
+ # Copyright © 2010 Johan Kiviniemi
3
+ #
4
+ # Permission to use, copy, modify, and/or distribute this software for any
5
+ # purpose with or without fee is hereby granted, provided that the above
6
+ # copyright notice and this permission notice appear in all copies.
7
+ #
8
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
+
16
+ require File.expand_path(File.dirname(__FILE__)+'/../spec_helper.rb')
17
+ require 'mischacks/password'
18
+
19
+ require 'set'
20
+
21
+ describe MiscHacks::Password do
22
+ def match expected
23
+ simple_matcher("match #{expected.inspect}") do |given, matcher|
24
+ matcher.failure_message = "expected #{given.inspect} to match #{expected.inspect}"
25
+ matcher.negative_failure_message = "expected #{given.inspect} not to match #{expected.inspect}"
26
+ given.match? expected
27
+ end
28
+ end
29
+
30
+ before :all do
31
+ @many = 1000
32
+
33
+ @password = 'foo'
34
+ @salt = 'bar'
35
+ @encrypted = @password.crypt('$6$%s$' % @salt)
36
+ end
37
+
38
+ describe 'SALT_SET' do
39
+ salt_set = MiscHacks::Password::SALT_SET
40
+
41
+ it 'should be an array' do
42
+ salt_set.should be_an Array
43
+ end
44
+
45
+ it 'should contain 64 unique values' do
46
+ salt_set.length.should == 64
47
+ salt_set.to_set.length.should == 64
48
+ end
49
+
50
+ it 'should have characters as values' do
51
+ salt_set.each do |char|
52
+ char.should be_a String
53
+ char.bytesize.should == 1
54
+ end
55
+ end
56
+ end
57
+
58
+ describe 'random_salt' do
59
+ it 'should return strings of random length between 9 and 16' do
60
+ range = 9..16
61
+ counts = Array.new(range.max - range.min + 1) { 0 }
62
+
63
+ @many.times do
64
+ size = MiscHacks::Password.random_salt.bytesize
65
+ range.should include size
66
+ counts[size - range.min] += 1
67
+ end
68
+
69
+ probabilities = counts.map {|c| counts.length * c / @many.to_f }
70
+
71
+ probabilities.stddev.should be_close 0.0, 0.15
72
+ (probabilities.min - probabilities.mean).should be_close 0.0, 0.30
73
+ (probabilities.max - probabilities.mean).should be_close 0.0, 0.30
74
+ end
75
+
76
+ it 'should use SALT_SET with a sane distribution' do
77
+ salt_set = MiscHacks::Password::SALT_SET
78
+
79
+ counts = Array.new(salt_set.length) { 0 }
80
+ num_chars = 0
81
+ @many.times do
82
+ MiscHacks::Password.random_salt.each_char do |char|
83
+ MiscHacks::Password::SALT_SET.should include char
84
+
85
+ counts[salt_set.index(char)] += 1
86
+ num_chars += 1
87
+ end
88
+ end
89
+
90
+ probabilities = counts.map {|c| salt_set.length * c / num_chars.to_f }
91
+
92
+ probabilities.stddev.should be_close 0.0, 0.15
93
+ (probabilities.min - probabilities.mean).should be_close 0.0, 0.30
94
+ (probabilities.max - probabilities.mean).should be_close 0.0, 0.30
95
+ end
96
+ end
97
+
98
+ describe 'new_from_password' do
99
+ it 'should return a new instance with the parameter encrypted with random_salt' do
100
+ MiscHacks::Password.should_receive(:random_salt).once.and_return(@salt)
101
+
102
+ pw = MiscHacks::Password.new_from_password @password
103
+ pw.should be_a MiscHacks::Password
104
+ end
105
+ end
106
+
107
+ describe 'initialize' do
108
+ it 'should verify the format of the parameter' do
109
+ ['', 'foo', 'foo$$$', '$foo$$', '$$foo$', '$$$foo'].each do |str|
110
+ lambda { MiscHacks::Password.new str }.
111
+ should raise_exception ArgumentError
112
+ end
113
+
114
+ ['$foo$foo$foo', @encrypted].each do |str|
115
+ lambda { MiscHacks::Password.new str }.should_not raise_exception
116
+ end
117
+ end
118
+ end
119
+
120
+ describe 'match?' do
121
+ it 'should verify whether the cleartext parameter matches the encrypted password' do
122
+ %w{foo bar baz}.each do |password|
123
+ pw = MiscHacks::Password.new_from_password password
124
+ pw.should match password
125
+ pw.should_not match "#{password}x"
126
+ pw.should_not match ''
127
+ end
128
+ end
129
+ end
130
+
131
+ describe 'to_s' do
132
+ it 'should return the encrypted password' do
133
+ MiscHacks::Password.new(@encrypted).to_s.should == @encrypted
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,114 @@
1
+ # mischacks – Miscellaneous methods that may or may not be useful
2
+ # Copyright © 2010 Johan Kiviniemi
3
+ #
4
+ # Permission to use, copy, modify, and/or distribute this software for any
5
+ # purpose with or without fee is hereby granted, provided that the above
6
+ # copyright notice and this permission notice appear in all copies.
7
+ #
8
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
+
16
+ require File.expand_path(File.dirname(__FILE__)+'/../spec_helper.rb')
17
+ require 'mischacks/random'
18
+
19
+ require 'ostruct'
20
+
21
+ describe MiscHacks::Random do
22
+ before :all do
23
+ @many = 1000
24
+ end
25
+
26
+ it 'should define MiscHacks::RANDOM as an instance' do
27
+ MiscHacks::RANDOM.should == MiscHacks::Random.instance
28
+ end
29
+
30
+ def self.spec_method meth, params, &block
31
+ params = OpenStruct.new params
32
+
33
+ describe meth do
34
+ it "should return #{params.return_type} values" do
35
+ params.args.each do |arg|
36
+ MiscHacks::RANDOM.send(meth, arg).should be_a params.return_type
37
+ end
38
+ end
39
+
40
+ it "should return #{params.range_str}" do
41
+ params.args.each do |arg|
42
+ range = params.range_for_arg.call arg
43
+ @many.times do
44
+ range.should include MiscHacks::RANDOM.send(meth, arg)
45
+ end
46
+ end
47
+ end
48
+
49
+ it 'should have a sane distribution' do
50
+ params.args.each do |arg|
51
+ quarter = params.quarter_for_arg.call arg
52
+
53
+ counts = (0...@many).inject [0, 0, 0] do |c, i|
54
+ n = MiscHacks::RANDOM.send(meth, arg)
55
+ [
56
+ c[0] + if n < quarter then 1 else 0 end,
57
+ c[1] + if n < quarter*2 then 1 else 0 end,
58
+ c[2] + if n < quarter*3 then 1 else 0 end,
59
+ ]
60
+ end
61
+
62
+ counts.each_with_index do |count, i|
63
+ count.should be_close @many*0.25*(i+1), 50
64
+ end
65
+ end
66
+
67
+ instance_eval &block if block
68
+ end
69
+
70
+ if params.default_arg
71
+ it "should have #{params.default_arg} as the default argument" do
72
+ MiscHacks::RANDOM.method(meth).arity.should == -1
73
+
74
+ half = params.default_arg * 0.5
75
+
76
+ n = (0...@many).inject 0.0 do |sum, i|
77
+ sum + if MiscHacks::RANDOM.send(meth) < half then 1 else 0 end
78
+ end
79
+
80
+ n.should be_close @many*0.5, 50
81
+ end
82
+
83
+ else
84
+ it 'should have no default argument' do
85
+ MiscHacks::RANDOM.method(meth).arity.should == 1
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ spec_method :exp,
92
+ :return_type => Integer,
93
+ :range_str => '0 <= n < 2**size_bits',
94
+ :range_for_arg => lambda {|arg| 0...2**arg },
95
+ :quarter_for_arg => lambda {|arg| 2**(arg-2) },
96
+ :args => [2, 42, 99, 2345]
97
+
98
+ spec_method :float,
99
+ :return_type => Float,
100
+ :range_str => '0.0 <= n < max',
101
+ :range_for_arg => lambda {|arg| 0.0...arg },
102
+ :quarter_for_arg => lambda {|arg| arg*0.25 },
103
+ :args => [0.5, 1.5, 99.0, 1234567.8],
104
+ :default_arg => 1.0
105
+
106
+ spec_method :int,
107
+ :return_type => Integer,
108
+ :range_str => '0 <= n < max',
109
+ :range_for_arg => lambda {|arg| 0...arg },
110
+ :quarter_for_arg => lambda {|arg| arg/4 },
111
+ :args => [1, 2, 42, 99, 1234567].map {|n| n*4 }
112
+ end
113
+
114
+ # vim:set et sw=2 sts=2:
@@ -23,6 +23,21 @@ class Object
23
23
  end
24
24
  end
25
25
 
26
+ module Enumerable
27
+ def mean
28
+ inject(:+) / length.to_f
29
+ end
30
+
31
+ def stddev
32
+ Math.sqrt variance
33
+ end
34
+
35
+ def variance
36
+ mean_ = mean
37
+ inject(0) {|sum, e| sum + (e - mean_)**2} / length.to_f
38
+ end
39
+ end
40
+
26
41
  Spec::Matchers.define :exit_with do |expected|
27
42
  match do |block|
28
43
  @status = 0
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mischacks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johan Kiviniemi
@@ -9,11 +9,11 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-08-25 00:00:00 +03:00
12
+ date: 2010-08-29 00:00:00 +03:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
16
- description: "sh: Safely pass untrusted parameters to sh scripts. overwrite: Safely replace a file. Exception#to_formatted_string: Return a string that looks like how Ruby would dump an uncaught exception."
16
+ description: "sh: Safely pass untrusted parameters to sh scripts. overwrite: Safely replace a file. Exception#to_formatted_string: Return a string that looks like how Ruby would dump an uncaught exception. Random: Generate various types of random numbers using SecureRandom. Password: A small wrapper for String#crypt that does secure salt generation and easy password verification."
17
17
  email: devel@johan.kiviniemi.name
18
18
  executables: []
19
19
 
@@ -22,6 +22,7 @@ extensions: []
22
22
  extra_rdoc_files:
23
23
  - README.txt
24
24
  files:
25
+ - .gitignore
25
26
  - COPYING
26
27
  - History.txt
27
28
  - Manifest.txt
@@ -29,6 +30,10 @@ files:
29
30
  - Rakefile
30
31
  - VERSION
31
32
  - lib/mischacks.rb
33
+ - lib/mischacks/password.rb
34
+ - lib/mischacks/random.rb
35
+ - spec/mischacks/password_spec.rb
36
+ - spec/mischacks/random_spec.rb
32
37
  - spec/mischacks_spec.rb
33
38
  - spec/spec_helper.rb
34
39
  has_rdoc: true
@@ -60,5 +65,7 @@ signing_key:
60
65
  specification_version: 3
61
66
  summary: Miscellaneous methods that may or may not be useful
62
67
  test_files:
68
+ - spec/mischacks/random_spec.rb
69
+ - spec/mischacks/password_spec.rb
63
70
  - spec/spec_helper.rb
64
71
  - spec/mischacks_spec.rb