forgiva 1.0.pre.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.
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