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.
- checksums.yaml +5 -5
- data/.editorconfig +5 -4
- data/.env.example +2 -0
- data/.gitignore +4 -3
- data/.gitlab-ci.yml +37 -37
- data/.travis.yml +17 -16
- data/README.md +6 -2
- data/Vagrantfile +34 -0
- data/bin/.gitignore +1 -0
- data/bin/dev_setup.sh +19 -0
- data/bin/install.sh +51 -0
- data/bin/osp +342 -329
- data/bin/release.sh +37 -0
- data/bin/test.sh +15 -0
- data/bin/uninstall.sh +24 -0
- data/lib/ext/string.rb +3 -3
- data/lib/osp/database.rb +195 -184
- data/lib/osp/host.rb +112 -116
- data/lib/osp/osp.rb +185 -150
- data/lib/osp/ospdev.rb +12 -10
- data/lib/osp/ospdotcom.rb +14 -14
- data/lib/osp/version.rb +5 -5
- data/osp.gemspec +25 -25
- data/osp.sublime-project +8 -8
- metadata +11 -5
- data/Makefile +0 -21
- data/Makefile.common +0 -58
data/lib/osp/host.rb
CHANGED
@@ -2,120 +2,116 @@
|
|
2
2
|
require 'date'
|
3
3
|
|
4
4
|
module TheFox
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
data/lib/osp/osp.rb
CHANGED
@@ -5,154 +5,189 @@ require 'msgpack'
|
|
5
5
|
require 'thefox-ext'
|
6
6
|
|
7
7
|
module TheFox
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|