forgiva 1.0.pre.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/forgiva.rb ADDED
@@ -0,0 +1,287 @@
1
+ #!/usr/bin/ruby
2
+ require 'openssl'
3
+ require 'highline/import'
4
+ require 'constants'
5
+
6
+ # Password generation from 4 inputs
7
+ class Forgiva
8
+ attr_accessor :hostname, :account, :renewal_date, :master_password, :complexity, :length
9
+
10
+ def initialize(hostname, account, renewal_date, master_password, complexity, length)
11
+ @hostname = hostname
12
+ @account = account
13
+ @renewal_date = renewal_date
14
+ @master_password = master_password
15
+ @complexity = complexity
16
+ @length = length
17
+ end
18
+
19
+ def passwords
20
+ @passwords ||= generate
21
+ end
22
+
23
+
24
+
25
+ def generate
26
+ passwords = {}
27
+
28
+ # Getting input data as encrypted as salt
29
+ salt = encrypted_inputs
30
+
31
+ puts "SALT: #{salt.unpack('H*')}" if Constants::DEBUG_OUTPUT
32
+
33
+ # Getting master password as already hashed SHA512
34
+ key = master_password
35
+
36
+ puts "CLEAR KEY: #{key.unpack('H*')}" if Constants::DEBUG_OUTPUT
37
+
38
+ # If we have complexity options, then we overrun key
39
+ # with the pbkdf2 hmac sha256 or sha512 algorithms
40
+ if (@complexity == 2 || @complexity == 3) then
41
+ key = Forgiva.pbkdf2_hmac_sha(key,salt,@complexity)
42
+ end
43
+
44
+ puts "ENC KEY: #{key.unpack('H*')}" if Constants::DEBUG_OUTPUT
45
+
46
+
47
+ Constants::ANIMALS.each do |a|
48
+ # For every other animal we re-run pbkdf2 hmac with sha1 over key
49
+ key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(key, salt, 10_000, 32)
50
+
51
+ puts "GEN_KEY: #{key.unpack('H*')}" if Constants::DEBUG_OUTPUT
52
+
53
+ passwords[a] = Forgiva.hash_to_password(key,@complexity, @length)
54
+ end
55
+
56
+ passwords
57
+ end
58
+
59
+ def hashed_hostname
60
+ Forgiva.hash_twice(hostname)
61
+ end
62
+
63
+ def hashed_account
64
+ Forgiva.hash_twice(account)
65
+ end
66
+
67
+ def hashed_renewal_date
68
+ Forgiva.hash_twice(renewal_date)
69
+ end
70
+
71
+ def hashed_master_password
72
+ Forgiva.hash_twice(master_password)
73
+ end
74
+
75
+ def encrypted_inputs
76
+
77
+
78
+ puts "hashed_hostname: #{hashed_hostname.unpack('H*')}" if Constants::DEBUG_OUTPUT
79
+ puts "hashed_account: #{hashed_account.unpack('H*')}" if Constants::DEBUG_OUTPUT
80
+
81
+ # Encrypt iteratively hostname and account and master_password
82
+ encrypt01 = Forgiva.iterative_encrypt(Forgiva.iterative_encrypt(hashed_hostname, hashed_account),
83
+ hashed_master_password)
84
+
85
+ puts "encrypt01: #{encrypt01.unpack('H*')}" if Constants::DEBUG_OUTPUT
86
+
87
+ puts "hashed_renewal_date: #{hashed_renewal_date.unpack('H*')}" if Constants::DEBUG_OUTPUT
88
+ puts "hashed_master_password: #{hashed_master_password.unpack('H*')}" if Constants::DEBUG_OUTPUT
89
+
90
+ # Encrypt iteratively renewal date and master key
91
+ encrypt02 = Forgiva.iterative_encrypt(hashed_renewal_date, hashed_master_password)
92
+
93
+ puts "encrypt02: #{encrypt02.unpack('H*')}" if Constants::DEBUG_OUTPUT
94
+
95
+ # Encrypt iteratively prior generated values
96
+ ret = Forgiva.iterative_encrypt(
97
+ encrypt01,
98
+ encrypt02)
99
+
100
+ puts "forgiva_encrypted_inputs: #{ret.unpack('H*')}" if Constants::DEBUG_OUTPUT
101
+
102
+ return ret
103
+ end
104
+
105
+
106
+
107
+ #################
108
+ # Class methods #
109
+ #################
110
+ def self.pbkdf2_hmac_sha(key,salt,type)
111
+
112
+ rkey = nil
113
+
114
+ if (type == Constants::FORGIVA_PG_SIMPLE) then
115
+ rkey = OpenSSL::PKCS5.pbkdf2_hmac_sha1(key, salt, 10_000, 32)
116
+ elsif (type == Constants::FORGIVA_PG_INTERMEDIATE) then
117
+ digest = OpenSSL::Digest::SHA256.new
118
+ rkey = OpenSSL::PKCS5.pbkdf2_hmac(key,salt,10_000 * 1000, 32,digest);
119
+ elsif (type == Constants::FORGIVA_PG_ADVANCED) then
120
+ digest = OpenSSL::Digest::SHA512.new
121
+ rkey = OpenSSL::PKCS5.pbkdf2_hmac(key,salt,10_000 * 10000, 32,digest);
122
+ end
123
+
124
+ return rkey
125
+ end
126
+
127
+ def self.pad_with_zeroes(data,block_size)
128
+
129
+ if (data.length % block_size != 0) then
130
+
131
+ tot = block_size + data.length
132
+
133
+ toadd = (tot - (tot % block_size) - data.length)
134
+
135
+ for i in (1..toadd) do
136
+ data = data + "\x00"
137
+ end
138
+
139
+ end
140
+
141
+ data
142
+ end
143
+
144
+ def self.shorten_if_long(data,block_size)
145
+
146
+ data = data[0..block_size] if data.length > block_size
147
+
148
+ data
149
+ end
150
+
151
+ def self.encrypt_ex(alg, data, key,iv)
152
+
153
+ cipher = OpenSSL::Cipher::Cipher.new(alg)
154
+ cipher.encrypt
155
+
156
+ key_ = shorten_if_long(pad_with_zeroes(key,cipher.key_len),cipher.key_len)
157
+ iv_ = shorten_if_long(pad_with_zeroes(iv,cipher.iv_len),cipher.iv_len)
158
+
159
+ cipher.key = key_
160
+ cipher.iv = iv_
161
+
162
+ ## Padding if length is not multiple of block size of cipher
163
+ data = pad_with_zeroes(data,cipher.block_size)
164
+
165
+ puts "self.encrypt: #{alg} - #{data.unpack('H*')} Key: #{cipher.key_len} #{key_.unpack('H*')} IV: #{cipher.iv_len} #{iv_.unpack('H*')}" if Constants::DEBUG_OUTPUT
166
+
167
+
168
+ ret = cipher.update(data)
169
+
170
+ #+ cipher.final
171
+
172
+ puts "self.encrypt: (ret) #{ret.unpack('H*')}" if Constants::DEBUG_OUTPUT
173
+
174
+ return ret
175
+
176
+
177
+ rescue OpenSSL::Cipher::CipherError => e
178
+ puts "Error: #{e.message} data_len #{data.length} key_len #{key.length} iv_len #{iv.length}"
179
+ end
180
+
181
+ def self.encrypt(alg, data, extension)
182
+
183
+ return self.encrypt_ex(alg,
184
+ data,
185
+ self.pbkdf2_hmac_sha(extension,'forgiva', Constants::FORGIVA_PG_SIMPLE),
186
+ sha512ize(extension))
187
+ end
188
+
189
+ def self.iterative_encrypt(val1, val2)
190
+ ret = val1
191
+
192
+ val1.each_byte do |c|
193
+ alg = Constants::ENC_ALGS[c % Constants::ENC_ALGS.length]
194
+ ret = encrypt(alg, ret, val2)
195
+ end
196
+
197
+
198
+ puts "#{ret.unpack('H*')}" if Constants::DEBUG_OUTPUT
199
+
200
+ ret
201
+ end
202
+
203
+ def self.hash(alg, val)
204
+ dig = OpenSSL::Digest.new(alg)
205
+ nval =val
206
+ puts "alg: #{alg}" if (val == nil)
207
+
208
+
209
+ ret = ""
210
+ puts "HASH IN: #{nval.unpack('H*')}:#{nval.length} alg: #{alg} dl: #{dig.digest_length}" if Constants::DEBUG_OUTPUT
211
+
212
+ if (dig.digest_length < val.length) then
213
+
214
+ st = 0
215
+ en = dig.digest_length
216
+ while (true) do
217
+
218
+ inblock = val[st..en-1]
219
+ puts "INBLOCK: #{inblock.unpack('H*')}:#{inblock.length}" if Constants::DEBUG_OUTPUT
220
+ outblock = OpenSSL::Digest.digest(alg,inblock)
221
+ puts "OUTBLOCK: #{outblock.unpack('H*')}:#{outblock.length}" if Constants::DEBUG_OUTPUT
222
+ ret = ret + outblock
223
+ puts "NRET: #{ret.unpack('H*')}:#{ret.length}" if Constants::DEBUG_OUTPUT
224
+
225
+ st = en
226
+ break if (st >= val.length)
227
+
228
+ en = st + dig.digest_length
229
+ en = val.length if (en > val.length)
230
+ end
231
+
232
+ ret = ret[0...val.length]
233
+
234
+ else
235
+ ret = dig.digest(nval)
236
+ end
237
+
238
+
239
+ puts "HASH OUT: #{ret.unpack('H*')}:#{ret.length}" if Constants::DEBUG_OUTPUT
240
+ return ret
241
+ end
242
+
243
+ def self.iterative_hash(val)
244
+ ret = val
245
+
246
+ val.each_byte do |c|
247
+ alg = Constants::HASH_ALGS[c % Constants::HASH_ALGS.length]
248
+ ret = hash(alg, ret)
249
+ end
250
+
251
+ ret
252
+ end
253
+
254
+ def self.sha512ize(val)
255
+ hash('sha512', val)
256
+ end
257
+
258
+ def self.hash_twice(val)
259
+ iterative_hash(iterative_hash(val))
260
+ end
261
+
262
+ def self.hash_to_password(val,complexity,length)
263
+ ret = ''
264
+
265
+ # to be sure it is long enough
266
+ hashed = sha512ize(val)
267
+
268
+
269
+ pchars = (complexity == 2 ? Constants::PASSWORD_CHARS_INTERMEDIATE :
270
+ (complexity == 3 ? Constants::PASSWORD_CHARS_ADVANCED : Constants::PASSWORD_CHARS))
271
+
272
+
273
+ hashed.each_byte do |c|
274
+ ret += pchars[c % pchars.length]
275
+ break if ret.length >= length
276
+ end
277
+
278
+ ret = ret[0..length-1]
279
+
280
+ puts "HASH_TO_PASSWORD (IN): #{val.unpack('H*')}" if Constants::DEBUG_OUTPUT
281
+ puts "HASH_TO_PASSWORD (HASHED): #{hashed.unpack('H*')}" if Constants::DEBUG_OUTPUT
282
+ puts "HASH_TO_PASSWORD (OUT): #{ret.unpack('H*')}" if Constants::DEBUG_OUTPUT
283
+
284
+ return ret
285
+
286
+ end
287
+ end
@@ -0,0 +1,191 @@
1
+ require 'forgiva'
2
+ require 'date'
3
+
4
+ # Command line access to Forgiva
5
+ class ForgivaCommands
6
+ attr_accessor :hash_args
7
+
8
+ def initialize(hash_args = {})
9
+ @hash_args = hash_args
10
+ end
11
+
12
+ def run
13
+
14
+ single_generate_choose if single_by_choose?
15
+ single_generate if !single_by_choose?
16
+ end
17
+
18
+ def ask_for_master_password
19
+
20
+ master_password = 'a'
21
+ master_password_check = 'b'
22
+
23
+ while master_password != master_password_check
24
+ master_password = ask(Constants::COLOR_CYA + "Master password: " + Constants::COLOR_RST ) { |q| q.echo = false }
25
+ master_password_check = ask(Constants::COLOR_CYA + "Master password (again): " + Constants::COLOR_RST ) { |q| q.echo = false }
26
+
27
+ puts 'Master passwords do not match!' unless master_password == master_password_check
28
+ end
29
+
30
+
31
+
32
+ digest = OpenSSL::Digest.digest("sha512",master_password)
33
+
34
+ puts "digest: #{digest.unpack('H*')}" if Constants::DEBUG_OUTPUT
35
+ return digest
36
+ end
37
+
38
+
39
+ def forgiva_r_path
40
+ File.join(Dir.home, '.forgivacr')
41
+ end
42
+
43
+ def record
44
+ line_to_add = "#{@hostname};#{@account};#{@renewal_date}"
45
+
46
+ File.open(forgiva_r_path, 'a') do |file|
47
+ file.puts line_to_add
48
+ end
49
+ end
50
+
51
+ def saved_records
52
+ recs = []
53
+ i_a = 1
54
+ File.open(forgiva_r_path).each do |line|
55
+
56
+ begin
57
+ recs << line.rstrip
58
+ hostname, account, renewal_date = line.rstrip.split(';')
59
+
60
+ puts "#{Constants::COLOR_BRI}#{i_a} - #{Constants::COLOR_BLU}#{hostname} #{Constants::COLOR_CYA} #{account} : #{Constants::COLOR_RST} #{renewal_date}"
61
+ i_a += 1
62
+ rescue
63
+ puts "Invalid line in credentials file (#{forgiva_r_path}) - #{line} "
64
+ exit(1)
65
+ end
66
+ end
67
+
68
+ recs
69
+ end
70
+
71
+ def single_generate_choose
72
+ recs = saved_records
73
+
74
+ puts('')
75
+ idx = ask("#{Constants::COLOR_GRN}Selection: #{Constants::COLOR_RST}")
76
+
77
+ r = recs[idx.to_i-1].split(';')
78
+
79
+ @hostname = r[0]
80
+ @account = r[1]
81
+ @renewal_date = r[2]
82
+
83
+ single_generate
84
+ end
85
+
86
+ def single_generate
87
+
88
+ init_hostname
89
+ init_account
90
+ init_renewal_date
91
+ init_length
92
+ init_master_password
93
+ init_complexity
94
+
95
+ puts Constants::COLOR_GRN + "Generating..." + Constants::COLOR_RST
96
+ puts ""
97
+
98
+ record if record?
99
+
100
+ passwords = make_passwords(@hostname, @account, @renewal_date, @master_password, @complexity, @length)
101
+
102
+ if animals.length > 1
103
+ Constants::ANIMALS.each { |a| puts "#{Constants::COLOR_YEL}#{a}#{Constants::COLOR_RST}\t#{Constants::COLOR_BRI}#{passwords[a]}#{Constants::COLOR_RST}" }
104
+ else
105
+ puts passwords[animals[0]]
106
+ end
107
+ end
108
+
109
+ def record?
110
+ hash_args.key? 's' || hash_args.key?('save-credentials')
111
+ end
112
+
113
+
114
+
115
+
116
+
117
+ def single_by_choose?
118
+ hash_args.key?('e') || hash_args.key?('select-credentials')
119
+ end
120
+
121
+ def init_length
122
+ @length = 16
123
+ @length = hash_args['l'].to_i if hash_args['l'] != nil
124
+ @length = hash_args['length'].to_i if @length == nil && hash_args['length'] != nil
125
+ return @length
126
+ end
127
+
128
+ def init_master_password
129
+ @master_password = ask_for_master_password
130
+ return @master_password
131
+ end
132
+
133
+
134
+ def init_complexity
135
+ @complexity = Constants::FORGIVA_PG_SIMPLE
136
+ @complexity = hash_args['c'].to_i if hash_args['c'] != nil
137
+ @complexity = hash_args['complexity'].to_i if @complexity == nil && hash_args['complexity'] != nil
138
+
139
+ if (@complexity == Constants::FORGIVA_PG_INTERMEDIATE) then
140
+ puts Constants::COLOR_YEL + "\nINTERMEDIATE COMPLEXITY\n" + Constants::COLOR_RST
141
+ elsif (@complexity == Constants::FORGIVA_PG_ADVANCED) then
142
+ puts Constants::COLOR_RED + "\nADVANCED COMPLEXITY\n" + Constants::COLOR_RST
143
+ end
144
+
145
+ return @complexity
146
+ end
147
+
148
+
149
+ def init_hostname
150
+ @hostname = hash_args['h'] if hash_args['h'] != nil
151
+ @hostname = hash_args['host'] if @hostname == nil && hash_args['host'] != nil
152
+ @hostname = ask(Constants::COLOR_GRN + "Hostname: " + Constants::COLOR_RST ) if @hostname == nil
153
+ return @hostname
154
+ end
155
+
156
+ def init_account
157
+ @account = hash_args['a'] if hash_args['a'] != nil
158
+ @account = hash_args['account'] if hash_args['account'] != nil
159
+ @account = ask(Constants::COLOR_GRN + "Account: " + Constants::COLOR_RST ) if @account == nil
160
+ return @account
161
+ end
162
+
163
+ def init_renewal_date
164
+ @renewal_date = "1970-01-01" #Time.now.strftime("%Y-%m-%d")
165
+ @renewal_date = hash_args['r'] if hash_args['r'] != nil
166
+ @renewal_date = hash_args['renewal-date'] if @hostname == nil && hash_args['renewal-date'] != nil
167
+
168
+ begin
169
+ Date.strptime(@renewal_date, '%Y-%m-%d')
170
+ rescue
171
+ puts "WARNING: Renewal date is not valid for YEAR-MONTH-DAY format but still accepted";
172
+ end
173
+
174
+ @renewal_date = @renewal_date.gsub(';','')
175
+
176
+ return @renewal_date
177
+ end
178
+
179
+ def animals
180
+
181
+ return Constants::ANIMALS
182
+
183
+ end
184
+
185
+ def make_passwords(hostname, account, renewal_date, master_password, complexity,length)
186
+ Forgiva.new(hostname, account, renewal_date, master_password,complexity,length).passwords
187
+ end
188
+
189
+
190
+
191
+ end
@@ -0,0 +1,66 @@
1
+ require 'forgiva'
2
+ require 'testvectors'
3
+ require 'openssl'
4
+ require 'constants'
5
+
6
+ class ForgivaTest
7
+
8
+
9
+ def self.run_tests
10
+
11
+
12
+ TestVectors::FA_TESTS.each do |test_vec|
13
+
14
+ puts "#{Constants::COLOR_GRN} Testing algorithm #{test_vec[:algorithm_name]} ... #{Constants::COLOR_RST}"
15
+
16
+ plain_data = [test_vec[:data_hex]].pack('H*')
17
+ key = [test_vec[:key_hex]].pack('H*')
18
+ iv = [test_vec[:iv_hex]].pack('H*')
19
+ expected = [test_vec[:target_hex]].pack('H*')
20
+
21
+ if (test_vec[:is_encryption_algorithm]) then
22
+ result = Forgiva.encrypt_ex(test_vec[:algorithm_name], plain_data, key, iv)
23
+
24
+ else
25
+ result = Forgiva.hash(test_vec[:algorithm_name],plain_data)
26
+ end
27
+
28
+ if (result != expected) then
29
+ puts "#{Constants::COLOR_RED} FAILED: (Expected: #{test_vec[:target_hex]}) #{result.unpack('H*') if result != nil} #{Constants::COLOR_RST}"
30
+ end
31
+
32
+ end
33
+
34
+
35
+ TestVectors::FG_TESTS.each do |test_vec|
36
+
37
+ puts "#{Constants::COLOR_GRN} Testing forgiva #{Constants::COLOR_BLU} #{test_vec[:host]} " \
38
+ <<"/ #{test_vec[:account]} / #{test_vec[:renewal_date]} / #{Constants::COLOR_MGN} #{test_vec[:animal_name]} #{Constants::COLOR_GRN} " \
39
+ <<" on complexity #{test_vec[:complexity]} #{Constants::COLOR_RST}"
40
+
41
+ p_hash = OpenSSL::Digest.digest("sha512",test_vec[:master_key])
42
+
43
+ passes = Forgiva.new(test_vec[:host],
44
+ test_vec[:account],
45
+ test_vec[:renewal_date],
46
+ p_hash,
47
+ test_vec[:complexity],
48
+ 16).passwords
49
+
50
+ g_pass = passes[test_vec[:animal_name]].unpack('H*')[0]
51
+
52
+
53
+ if (g_pass.downcase != test_vec[:expected_password_hash]) then
54
+ puts "#{Constants::COLOR_RED} FAILED: (Expected: #{test_vec[:expected_password_hash]}) #{Constants::COLOR_RST} #{g_pass}"
55
+ else
56
+ puts "#{Constants::COLOR_GRN}! SUCCESS: (#{g_pass}) #{Constants::COLOR_RST}"
57
+ end
58
+
59
+
60
+ end
61
+
62
+
63
+ end
64
+
65
+
66
+ end