mischacks 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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