osp 0.6.1 → 0.7.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.
@@ -2,120 +2,116 @@
2
2
  require 'date'
3
3
 
4
4
  module TheFox
5
- module OSP
6
-
7
- class Host
8
-
9
- attr_accessor :created_at
10
- attr_accessor :updated_at
11
- attr_reader :osp
12
- attr_reader :name
13
- attr_reader :hashes
14
- attr_writer :password
15
-
16
- def initialize(osp = nil)
17
- @osp = osp
18
-
19
- @version = 1
20
- @created_at = DateTime.now
21
- @updated_at = DateTime.now
22
-
23
- @name = nil
24
- @generation = 1
25
- @length = 16
26
- @symbols = 1
27
- @hashes = @osp.nil? ? nil : @osp.hashes
28
- @password = nil
29
- end
30
-
31
- def osp=(v)
32
- if !v.is_a?(OSP)
33
- raise ArgumentError, "Wrong type -- #{v.class}"
34
- end
35
-
36
- @osp = v
37
- end
38
-
39
- def version=(v)
40
- @version = v.to_i
41
- end
42
-
43
- def version
44
- @version.to_i
45
- end
46
-
47
- def name=(v)
48
- @name = v == '' ? nil : v
49
- end
50
-
51
- def generation=(v)
52
- @generation = v.to_i
53
- end
54
-
55
- def generation
56
- @generation.to_i
57
- end
58
-
59
- def length=(v)
60
- @length = v.to_i
61
- end
62
-
63
- def length
64
- @length.to_i
65
- end
66
-
67
- def symbols=(v)
68
- @symbols = v.to_i
69
- end
70
-
71
- def symbols
72
- @symbols.to_i
73
- end
74
-
75
- def hashes=(v)
76
- @hashes = v
77
- end
78
-
79
- def generate_password(regenerate = false)
80
- if @password.nil? && !@osp.nil? || regenerate
81
- @password = @osp.password(@name, @length, @generation, @symbols)
82
- end
83
- end
84
-
85
- def password=(v)
86
- @password = v
87
- end
88
-
89
- def password
90
- generate_password
91
- @password
92
- end
93
-
94
- def has_generated_password?
95
- !@password.nil?
96
- end
97
-
98
- def to_h
99
- {
100
- 'version' => @version,
101
- 'created_at' => @created_at.to_s,
102
-
103
- 'name' => @name,
104
- 'generation' => @generation,
105
- 'length' => @length,
106
- 'symbols' => @symbols,
107
- 'hashes' => @hashes,
108
- }
109
- end
110
-
111
- def self.from_h(host)
112
- h = self.new
113
- host.each do |k, v|
114
- h.method("#{k}=").call(v)
115
- end
116
- h
117
- end
118
-
119
- end
120
- end
5
+ module OSP
6
+
7
+ class Host
8
+
9
+ attr_accessor :created_at
10
+ attr_accessor :updated_at
11
+ attr_reader :osp
12
+ attr_reader :name
13
+ attr_reader :hashes
14
+ attr_writer :password
15
+
16
+ def initialize(osp = nil)
17
+ @osp = osp
18
+
19
+ @version = 1
20
+ @created_at = DateTime.now
21
+ @updated_at = DateTime.now
22
+
23
+ @name = nil
24
+ @generation = 1
25
+ @length = 16
26
+ @symbols = 1
27
+ @hashes = @osp.nil? ? nil : @osp.hashes
28
+ @password = nil
29
+ end
30
+
31
+ def osp=(v)
32
+ if !v.is_a?(OSP)
33
+ raise ArgumentError, "Wrong type -- #{v.class}"
34
+ end
35
+
36
+ @osp = v
37
+ end
38
+
39
+ def version=(v)
40
+ @version = v.to_i
41
+ end
42
+
43
+ def version
44
+ @version.to_i
45
+ end
46
+
47
+ def name=(v)
48
+ @name = v == '' ? nil : v
49
+ end
50
+
51
+ def generation=(v)
52
+ @generation = v.to_i
53
+ end
54
+
55
+ def generation
56
+ @generation.to_i
57
+ end
58
+
59
+ def length=(v)
60
+ @length = v.to_i
61
+ end
62
+
63
+ def length
64
+ @length.to_i
65
+ end
66
+
67
+ def symbols=(v)
68
+ @symbols = v.to_i
69
+ end
70
+
71
+ def symbols
72
+ @symbols.to_i
73
+ end
74
+
75
+ def hashes=(v)
76
+ @hashes = v
77
+ end
78
+
79
+ def generate_password(regenerate = false)
80
+ if @password.nil? && !@osp.nil? || regenerate
81
+ @password = @osp.password(@name, @length, @generation, @symbols)
82
+ end
83
+ end
84
+
85
+ def password
86
+ generate_password
87
+ @password
88
+ end
89
+
90
+ def has_generated_password?
91
+ !@password.nil?
92
+ end
93
+
94
+ def to_h
95
+ {
96
+ 'version' => @version,
97
+ 'created_at' => @created_at.to_s,
98
+
99
+ 'name' => @name,
100
+ 'generation' => @generation,
101
+ 'length' => @length,
102
+ 'symbols' => @symbols,
103
+ 'hashes' => @hashes,
104
+ }
105
+ end
106
+
107
+ def self.from_h(host)
108
+ h = self.new
109
+ host.each do |k, v|
110
+ h.method("#{k}=").call(v)
111
+ end
112
+ h
113
+ end
114
+
115
+ end
116
+ end
121
117
  end
@@ -5,154 +5,189 @@ require 'msgpack'
5
5
  require 'thefox-ext'
6
6
 
7
7
  module TheFox
8
- module OSP
9
-
10
- class OSP
11
-
12
- ID = 'TheFox-OSP'
13
- HASHES_EXP = 24
14
- HASHES_N = 2 ** HASHES_EXP
15
- PASSWORD_MIN_SIZE = 8
16
- PASSWORD_MAX_SIZE = 32
17
- SYMBOLS = 1
18
-
19
- attr_accessor :dk
20
- attr_accessor :hashes
21
-
22
- def initialize(email, password, hashes = self.class::HASHES_N)
23
- @email = email
24
- @password = password
25
- @hashes = hashes
26
- @dk = nil
27
- @password_callback_method = nil
28
- end
29
-
30
- def key_derivation
31
- @dk = OpenSSL::PKCS5.pbkdf2_hmac(@password, @email, @hashes, 64, OpenSSL::Digest::SHA512.new)
32
- end
33
-
34
- def password(host_name, length = 16, generation = 1, symbols = self.class::SYMBOLS)
35
- if host_name.nil? || host_name == '' || !host_name
36
- raise ArgumentError, "'host_name' can't be '' or nil"
37
- end
38
-
39
- key_derivation if @dk.nil?
40
-
41
- pw = nil
42
- step = 0
43
- while pw.nil?
44
- raw = [self.class::ID, @email, host_name, generation, step]
45
- data = raw.to_msgpack
46
- hmac_p = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA512.new, @dk, data)
47
- hmac_b64 = Base64.strict_encode64(hmac_p)
48
- pw = hmac_b64 if is_ok_pw(hmac_b64)
49
-
50
- unless @password_callback_method.nil?
51
- @password_callback_method.call(step, hmac_b64)
52
- end
53
- step += 1
54
- end
55
-
56
- if symbols > 0
57
- sub_method = find_method_to_sub(pw)
58
-
59
- _b64map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
60
-
61
- indices = Array.new
62
- (0..self.class::PASSWORD_MIN_SIZE).each do |n|
63
- c = pw[n]
64
- if c.method(sub_method).call
65
- indices << n
66
- if indices.count >= symbols
67
- break
68
- end
69
- end
70
- end
71
-
72
- _map = "`~!@#$%^&*()-_+={}[]|;:,<>.?/"
73
- _map_len = _map.length
74
-
75
- last = 0
76
- arr = Array.new
77
- indices.each do |index|
78
- arr << pw[last...index]
79
- c = pw[index]
80
- i = _b64map.index(c)
81
- x = _map[i % _map_len]
82
- arr << x
83
- last = index + 1
84
- end
85
- arr << pw[last..-1]
86
- pw = arr.join
87
- end
88
- pw[0...length]
89
- end
90
-
91
- def password_callback_method=(m)
92
- @password_callback_method = m
93
- end
94
-
95
- private
96
-
97
- def is_ok_pw(pw)
98
- caps = 0
99
- lowers = 0
100
- digits = 0
101
-
102
- (0...self.class::PASSWORD_MIN_SIZE).each do |n|
103
- c = pw[n]
104
-
105
- if c.is_digit?
106
- digits += 1
107
- elsif c.is_upper?
108
- caps += 1
109
- elsif c.is_lower?
110
- lowers += 1
111
- else
112
- return false
113
- end
114
- end
115
-
116
- bad = lambda { |x| x == 0 || x > 5 }
117
-
118
- if bad.call(caps) || bad.call(lowers) || bad.call(digits)
119
- return false
120
- end
121
-
122
- (self.class::PASSWORD_MIN_SIZE...self.class::PASSWORD_MAX_SIZE).each do |n|
123
- unless pw[n].is_valid?
124
- return false
125
- end
126
- end
127
-
128
- true
129
- end
130
-
131
- def find_method_to_sub(pw)
132
- caps = 0
133
- lowers = 0
134
- digits = 0
135
-
136
- (0...self.class::PASSWORD_MIN_SIZE).each do |n|
137
- c = pw[n]
138
- if c.is_digit?
139
- digits += 1
140
- elsif c.is_upper?
141
- caps += 1
142
- elsif c.is_lower?
143
- lowers += 1
144
- end
145
- end
146
-
147
- if lowers >= caps && lowers >= digits then
148
- 'is_lower?'
149
- elsif digits > lowers && digits >= caps
150
- 'is_digit?'
151
- else
152
- 'is_upper?'
153
- end
154
- end
155
-
156
- end
157
- end
8
+ module OSP
9
+
10
+ class OSP
11
+
12
+ ID = 'TheFox-OSP'
13
+ HASHES_EXP = 24
14
+ HASHES_N = 2 ** HASHES_EXP
15
+ PASSWORD_MIN_SIZE = 8
16
+ PASSWORD_MAX_SIZE = 32
17
+ SYMBOLS = 1
18
+
19
+ attr_accessor :dk
20
+ attr_accessor :hashes
21
+ attr_reader :steps
22
+
23
+ def initialize(email, password, hashes = self.class::HASHES_N)
24
+ @email = email
25
+ @password = password
26
+ @hashes = hashes
27
+ @dk = nil
28
+ @password_callback_method = nil
29
+ @steps = 0
30
+ end
31
+
32
+ def key_derivation
33
+ @dk = OpenSSL::PKCS5.pbkdf2_hmac(@password, @email, @hashes, 64, OpenSSL::Digest::SHA512.new)
34
+ # puts
35
+ # puts 'DK: ' + Base64.strict_encode64(@dk)
36
+ # puts
37
+ end
38
+
39
+ def password(host_name, length = 16, generation = 1, symbols_n = self.class::SYMBOLS)
40
+ if length < PASSWORD_MIN_SIZE
41
+ raise RangeError, 'Invalid password length: %d. Minimum length is %d.' % [length, PASSWORD_MIN_SIZE]
42
+ end
43
+ if length > PASSWORD_MAX_SIZE
44
+ raise RangeError, 'Invalid password length: %d. Maximum length is %d.' % [length, PASSWORD_MAX_SIZE]
45
+ end
46
+ if host_name.nil? || host_name == '' || !host_name
47
+ raise ArgumentError, "'host_name' can't be '' or nil"
48
+ end
49
+
50
+ if @dk.nil?
51
+ key_derivation
52
+ end
53
+
54
+ password_s = find_password(host_name, generation)
55
+
56
+ if symbols_n > 0
57
+ password_s = find_symbols(password_s, symbols_n)
58
+ end
59
+
60
+ password_s[0...length]
61
+ end
62
+
63
+ def password_callback_method=(m)
64
+ @password_callback_method = m
65
+ end
66
+
67
+ private
68
+
69
+ def find_password(host_name, generation)
70
+ password_s = nil
71
+ @steps = 0
72
+ while password_s.nil?
73
+ raw = [self.class::ID, @email, host_name, generation, @steps]
74
+ data = raw.to_msgpack
75
+ hmac_p = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA512.new, @dk, data)
76
+ hmac_b64 = Base64.strict_encode64(hmac_p)
77
+ # puts 'b64 %s' % [hmac_b64]
78
+ if is_password_ok(hmac_b64)
79
+ password_s = hmac_b64
80
+ end
81
+
82
+ if not @password_callback_method.nil?
83
+ @password_callback_method.call(@steps, hmac_b64)
84
+ end
85
+ @steps += 1
86
+ end
87
+
88
+ password_s
89
+ end
90
+
91
+ def find_symbols(password_s, symbols_n)
92
+ sub_method = find_method_to_sub(password_s)
93
+
94
+ _b64map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
95
+
96
+ indices = Array.new
97
+ (0..self.class::PASSWORD_MIN_SIZE).each do |n|
98
+ c = password_s[n]
99
+ if c.method(sub_method).call
100
+ indices << n
101
+ if indices.count >= symbols_n
102
+ break
103
+ end
104
+ end
105
+ end
106
+
107
+ _map = "`~!@#$%^&*()-_+={}[]|;:,<>.?/"
108
+ _map_len = _map.length
109
+
110
+ last = 0
111
+ arr = Array.new
112
+ indices.each do |index|
113
+ arr << password_s[last...index]
114
+ c = password_s[index]
115
+ i = _b64map.index(c)
116
+ x = _map[i % _map_len]
117
+ arr << x
118
+ last = index + 1
119
+ end
120
+ arr << password_s[last..-1]
121
+
122
+ arr.join
123
+ end
124
+
125
+ def bad(x)
126
+ x == 0 || x > 5
127
+ end
128
+
129
+ def is_password_ok(password_s)
130
+ caps = 0
131
+ lowers = 0
132
+ digits = 0
133
+
134
+ # Get the entropy for the minium password length.
135
+ (0...self.class::PASSWORD_MIN_SIZE).each do |n|
136
+ c = password_s[n]
137
+
138
+ if c.is_digit?
139
+ digits += 1
140
+ elsif c.is_upper?
141
+ caps += 1
142
+ elsif c.is_lower?
143
+ lowers += 1
144
+ else
145
+ return false
146
+ end
147
+ end
148
+
149
+ # Check minimum entropy.
150
+ if bad(caps) || bad(lowers) || bad(digits)
151
+ return false
152
+ end
153
+
154
+ # Also check minimum to maximum password size for later use.
155
+ # Passwords can be extended later to maximum size without changing
156
+ # the first characters.
157
+ (self.class::PASSWORD_MIN_SIZE...self.class::PASSWORD_MAX_SIZE).each do |n|
158
+ if not password_s[n].is_valid?
159
+ return false
160
+ end
161
+ end
162
+
163
+ true
164
+ end
165
+
166
+ def find_method_to_sub(password_s)
167
+ caps = 0
168
+ lowers = 0
169
+ digits = 0
170
+
171
+ (0...self.class::PASSWORD_MIN_SIZE).each do |n|
172
+ c = password_s[n]
173
+ if c.is_digit?
174
+ digits += 1
175
+ elsif c.is_upper?
176
+ caps += 1
177
+ elsif c.is_lower?
178
+ lowers += 1
179
+ end
180
+ end
181
+
182
+ if lowers >= caps && lowers >= digits then
183
+ 'is_lower?'
184
+ elsif digits > lowers && digits >= caps
185
+ 'is_digit?'
186
+ else
187
+ 'is_upper?'
188
+ end
189
+ end
190
+
191
+ end
192
+ end
158
193
  end