osp 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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