mpw 4.0.0 → 4.1.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.
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/ruby
2
2
  # MPW is a software to crypt and manage your passwords
3
- # Copyright (C) 2016 Adrien Waksberg <mpw@yae.im>
4
- #
3
+ # Copyright (C) 2017 Adrien Waksberg <mpw@yae.im>
4
+ #
5
5
  # This program is free software; you can redistribute it and/or
6
6
  # modify it under the terms of the GNU General Public License
7
7
  # as published by the Free Software Foundation; either version 2
8
8
  # of the License, or (at your option) any later version.
9
- #
9
+ #
10
10
  # This program is distributed in the hope that it will be useful,
11
11
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
12
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
13
  # GNU General Public License for more details.
14
- #
14
+ #
15
15
  # You should have received a copy of the GNU General Public License
16
16
  # along with this program; if not, write to the Free Software
17
17
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
@@ -22,466 +22,329 @@ require 'i18n'
22
22
  require 'yaml'
23
23
  require 'rotp'
24
24
  require 'mpw/item'
25
-
26
- module MPW
27
- class MPW
28
-
29
- # Constructor
30
- def initialize(key, wallet_file, gpg_pass=nil, gpg_exe=nil)
31
- @key = key
32
- @gpg_pass = gpg_pass
33
- @gpg_exe = gpg_exe
34
- @wallet_file = wallet_file
35
-
36
- if not @gpg_exe.to_s.empty?
37
- GPGME::Engine.set_info(GPGME::PROTOCOL_OpenPGP, @gpg_exe, "#{Dir.home}/.gnupg")
38
- end
39
- end
40
-
41
- # Read mpw file
42
- def read_data
43
- @config = {}
44
- @data = []
45
- @keys = {}
46
- @passwords = {}
47
- @otp_keys = {}
48
-
49
- data = nil
50
-
51
- return if not File.exists?(@wallet_file)
52
-
53
- Gem::Package::TarReader.new(File.open(@wallet_file)) do |tar|
54
- tar.each do |f|
55
- case f.full_name
56
- when 'wallet/config.gpg'
57
- @config = YAML.load(decrypt(f.read))
58
-
59
- when 'wallet/meta.gpg'
60
- data = decrypt(f.read)
61
-
62
- when /^wallet\/keys\/(?<key>.+)\.pub$/
63
- key = Regexp.last_match('key')
64
-
65
- if GPGME::Key.find(:public, key).length == 0
66
- GPGME::Key.import(f.read, armor: true)
67
- end
68
-
69
- @keys[key] = f.read
70
-
71
- when /^wallet\/passwords\/(?<id>[a-zA-Z0-9]+)\.gpg$/
72
- @passwords[Regexp.last_match('id')] = f.read
73
-
74
- when /^wallet\/otp_keys\/(?<id>[a-zA-Z0-9]+)\.gpg$/
75
- @otp_keys[Regexp.last_match('id')] = f.read
76
-
77
- else
78
- next
79
- end
80
- end
81
- end
82
-
83
- if not data.nil? and not data.empty?
84
- YAML.load(data).each_value do |d|
85
- @data.push(Item.new(id: d['id'],
86
- group: d['group'],
87
- host: d['host'],
88
- protocol: d['protocol'],
89
- user: d['user'],
90
- port: d['port'],
91
- otp: @otp_keys.has_key?(d['id']),
92
- comment: d['comment'],
93
- last_edit: d['last_edit'],
94
- created: d['created'],
95
- )
96
- )
97
- end
98
- end
99
-
100
- add_key(@key) if @keys[@key].nil?
101
- rescue Exception => e
102
- raise "#{I18n.t('error.mpw_file.read_data')}\n#{e}"
103
- end
104
-
105
- # Encrypt a file
106
- def write_data
107
- data = {}
108
- tmp_file = "#{@wallet_file}.tmp"
109
-
110
- @data.each do |item|
111
- next if item.empty?
112
-
113
- data.merge!(item.id => { 'id' => item.id,
114
- 'group' => item.group,
115
- 'host' => item.host,
116
- 'protocol' => item.protocol,
117
- 'user' => item.user,
118
- 'port' => item.port,
119
- 'comment' => item.comment,
120
- 'last_edit' => item.last_edit,
121
- 'created' => item.created,
122
- }
123
- )
124
- end
125
-
126
- @config['last_update'] = Time.now.to_i
127
-
128
- Gem::Package::TarWriter.new(File.open(tmp_file, 'w+')) do |tar|
129
- data_encrypt = encrypt(data.to_yaml)
130
- tar.add_file_simple('wallet/meta.gpg', 0400, data_encrypt.length) do |io|
131
- io.write(data_encrypt)
132
- end
133
-
134
- config = encrypt(@config.to_yaml)
135
- tar.add_file_simple('wallet/config.gpg', 0400, config.length) do |io|
136
- io.write(config)
137
- end
138
-
139
- @passwords.each do |id, password|
140
- tar.add_file_simple("wallet/passwords/#{id}.gpg", 0400, password.length) do |io|
141
- io.write(password)
142
- end
143
- end
144
-
145
- @otp_keys.each do |id, key|
146
- tar.add_file_simple("wallet/otp_keys/#{id}.gpg", 0400, key.length) do |io|
147
- io.write(key)
148
- end
149
- end
150
-
151
- @keys.each do |id, key|
152
- tar.add_file_simple("wallet/keys/#{id}.pub", 0400, key.length) do |io|
153
- io.write(key)
154
- end
155
- end
156
- end
157
-
158
- File.rename(tmp_file, @wallet_file)
159
- rescue Exception => e
160
- File.unlink(tmp_file) if File.exist?(tmp_file)
161
-
162
- raise "#{I18n.t('error.mpw_file.write_data')}\n#{e}"
163
- end
164
-
165
- # Get a password
166
- # args: id -> the item id
167
- def get_password(id)
168
- password = decrypt(@passwords[id])
169
-
170
- if /^\$[a-zA-Z0-9]{4,9}::(?<password>.+)$/ =~ password
171
- return Regexp.last_match('password')
172
- else
173
- return password
174
- end
175
- end
176
-
177
- # Set a password
178
- # args: id -> the item id
179
- # password -> the new password
180
- def set_password(id, password)
181
- salt = MPW::password(length: Random.rand(4..9))
182
- password = "$#{salt}::#{password}"
183
-
184
- @passwords[id] = encrypt(password)
185
- end
186
-
187
- # Return the list of all gpg keys
188
- # rtrn: an array with the gpg keys name
189
- def list_keys
190
- return @keys.keys
191
- end
192
-
193
- # Add a public key
194
- # args: key -> new public key file or name
195
- def add_key(key)
196
- if File.exists?(key)
197
- data = File.open(key).read
198
- key_import = GPGME::Key.import(data, armor: true)
199
- key = GPGME::Key.get(key_import.imports[0].fpr).uids[0].email
200
- else
201
- data = GPGME::Key.export(key, armor: true).read
202
- end
203
-
204
- if data.to_s.empty?
205
- raise I18n.t('error.export_key')
206
- end
207
-
208
- @keys[key] = data
209
- @passwords.each_key { |id| set_password(id, get_password(id)) }
210
- @otp_keys.each_key { |id| set_otp_key(id, get_otp_key(id)) }
211
- end
212
-
213
- # Delete a public key
214
- # args: key -> public key to delete
215
- def delete_key(key)
216
- @keys.delete(key)
217
- @passwords.each_key { |id| set_password(id, get_password(id)) }
218
- @otp_keys.each_key { |id| set_otp_key(id, get_otp_key(id)) }
219
- end
220
-
221
- # Set config
222
- # args: config -> a hash with config options
223
- def set_config(options={})
224
- @config = {} if @config.nil?
225
-
226
- @config['protocol'] = options[:protocol] if options.has_key?(:protocol)
227
- @config['host'] = options[:host] if options.has_key?(:host)
228
- @config['port'] = options[:port] if options.has_key?(:port)
229
- @config['user'] = options[:user] if options.has_key?(:user)
230
- @config['password'] = options[:password] if options.has_key?(:password)
231
- @config['path'] = options[:path] if options.has_key?(:path)
232
- @config['last_sync'] = @config['last_sync'].nil? ? 0 : @config['last_sync']
233
- end
234
-
235
- # Add a new item
236
- # @args: item -> Object MPW::Item
237
- def add(item)
238
- if not item.instance_of?(Item)
239
- raise I18n.t('error.bad_class')
240
- elsif item.empty?
241
- raise I18n.t('error.empty')
242
- else
243
- @data.push(item)
244
- end
245
- end
246
-
247
- # Search in some csv data
248
- # @args: options -> a hash with paramaters
249
- # @rtrn: a list with the resultat of the search
250
- def list(options={})
251
- result = []
252
-
253
- search = options[:pattern].to_s.downcase
254
- group = options[:group].to_s.downcase
255
-
256
- @data.each do |item|
257
- next if item.empty?
258
- next if not group.empty? and not group.eql?(item.group.to_s.downcase)
259
-
260
- host = item.host.to_s.downcase
261
- comment = item.comment.to_s.downcase
262
-
263
- if not host =~ /^.*#{search}.*$/ and not comment =~ /^.*#{search}.*$/
264
- next
265
- end
266
-
267
- result.push(item)
268
- end
269
-
270
- return result
271
- end
272
-
273
- # Search in some csv data
274
- # @args: id -> the id item
275
- # @rtrn: a row with the result of the search
276
- def search_by_id(id)
277
- @data.each do |item|
278
- return item if item.id == id
279
- end
280
-
281
- return nil
282
- end
283
-
284
- # Get last sync
285
- def get_last_sync
286
- return @config['last_sync'].to_i
287
- rescue
288
- return 0
289
- end
290
-
291
- # Sync data with remote file
292
- # @args: force -> force the sync
293
- def sync(force=false)
294
- return if @config.empty? or @config['protocol'].to_s.empty?
295
- return if get_last_sync + 300 > Time.now.to_i and not force
296
-
297
- tmp_file = "#{@wallet_file}.sync"
298
-
299
- case @config['protocol']
300
- when 'sftp', 'scp', 'ssh'
301
- require "mpw/sync/ssh"
302
- sync = SyncSSH.new(@config)
303
- when 'ftp'
304
- require 'mpw/sync/ftp'
305
- sync = SyncFTP.new(@config)
306
- else
307
- raise I18n.t('error.sync.unknown_type')
308
- end
309
-
310
- sync.connect
311
- sync.get(tmp_file)
312
-
313
- remote = MPW.new(@key, tmp_file, @gpg_pass, @gpg_exe)
314
- remote.read_data
315
-
316
- File.unlink(tmp_file) if File.exist?(tmp_file)
317
-
318
- return if remote.get_last_sync == @config['last_update']
319
-
320
- if not remote.to_s.empty?
321
- @data.each do |item|
322
- next if item.empty?
323
-
324
- update = false
325
-
326
- remote.list.each do |r|
327
- next if item.id != r.id
328
-
329
- # Update item
330
- if item.last_edit < r.last_edit
331
- item.update(group: r.group,
332
- host: r.host,
333
- protocol: r.protocol,
334
- user: r.user,
335
- port: r.port,
336
- comment: r.comment
337
- )
338
- set_password(item.id, remote.get_password(item.id))
339
- end
340
-
341
- r.delete
342
- update = true
343
-
344
- break
345
- end
346
-
347
- # Remove an old item
348
- if not update and item.last_sync.to_i < get_last_sync and item.last_edit < get_last_sync
349
- item.delete
350
- end
351
- end
352
- end
353
-
354
- # Add item
355
- remote.list.each do |r|
356
- next if r.last_edit <= get_last_sync
357
-
358
- item = Item.new(id: r.id,
359
- group: r.group,
360
- host: r.host,
361
- protocol: r.protocol,
362
- user: r.user,
363
- port: r.port,
364
- comment: r.comment,
365
- created: r.created,
366
- last_edit: r.last_edit
367
- )
368
-
369
- set_password(item.id, remote.get_password(item.id))
370
- add(item)
371
- end
372
-
373
- remote = nil
374
-
375
- @data.each do |item|
376
- item.set_last_sync
377
- end
378
-
379
- @config['last_sync'] = Time.now.to_i
380
-
381
- write_data
382
- sync.update(@wallet_file)
383
- rescue Exception => e
384
- File.unlink(tmp_file) if File.exist?(tmp_file)
385
-
386
- raise "#{I18n.t('error.sync.general')}\n#{e}"
387
- end
388
-
389
- # Set an opt key
390
- # args: id -> the item id
391
- # key -> the new key
392
- def set_otp_key(id, key)
393
- if not key.to_s.empty?
394
- @otp_keys[id] = encrypt(key.to_s)
395
- end
396
- end
397
-
398
- # Get an opt key
399
- # args: id -> the item id
400
- # key -> the new key
401
- def get_otp_key(id)
402
- if @otp_keys.has_key?(id)
403
- return decrypt(@otp_keys[id])
404
- else
405
- return nil
406
- end
407
- end
408
-
409
-
410
- # Get an otp code
411
- # @args: id -> the item id
412
- # @rtrn: an otp code
413
- def get_otp_code(id)
414
- if not @otp_keys.has_key?(id)
415
- return 0
416
- else
417
- return ROTP::TOTP.new(decrypt(@otp_keys[id])).now
418
- end
419
- end
420
-
421
- # Get remaining time before expire otp code
422
- # @rtrn: return time in seconde
423
- def get_otp_remaining_time
424
- return (Time.now.utc.to_i / 30 + 1) * 30 - Time.now.utc.to_i
425
- end
426
-
427
- # Generate a random password
428
- # @args: options -> :length, :special, :alpha, :numeric
429
- # @rtrn: a random string
430
- def self.password(options={})
431
- if not options.include?(:length) or options[:length].to_i <= 0
432
- length = 8
433
- elsif options[:length].to_i >= 32768
434
- length = 32768
435
- else
436
- length = options[:length].to_i
437
- end
438
-
439
- chars = []
440
- chars += [*('!'..'?')] - [*('0'..'9')] if options[:special]
441
- chars += [*('A'..'Z'),*('a'..'z')] if options[:alpha]
442
- chars += [*('0'..'9')] if options[:numeric]
443
- chars = [*('A'..'Z'),*('a'..'z'),*('0'..'9')] if chars.empty?
444
-
445
- result = ''
446
- while length > 62 do
447
- result << chars.sample(62).join
448
- length -= 62
449
- end
450
- result << chars.sample(length).join
451
-
452
- return result
453
- end
454
-
455
- # Decrypt a gpg file
456
- # @args: data -> string to decrypt
457
- private
458
- def decrypt(data)
459
- return nil if data.to_s.empty?
460
-
461
- crypto = GPGME::Crypto.new(armor: true)
462
-
463
- return crypto.decrypt(data, password: @gpg_pass).read.force_encoding('utf-8')
464
- rescue Exception => e
465
- raise "#{I18n.t('error.gpg_file.decrypt')}\n#{e}"
466
- end
467
-
468
- # Encrypt a file
469
- # args: data -> string to encrypt
470
- private
471
- def encrypt(data)
472
- recipients = []
473
- crypto = GPGME::Crypto.new(armor: true, always_trust: true)
474
-
475
- recipients.push(@key)
476
- @keys.each_key do |key|
477
- next if key == @key
478
- recipients.push(key)
479
- end
480
-
481
- return crypto.encrypt(data, recipients: recipients).read
482
- rescue Exception => e
483
- raise "#{I18n.t('error.gpg_file.encrypt')}\n#{e}"
484
- end
485
25
 
486
- end
26
+ module MPW
27
+ class MPW
28
+ # Constructor
29
+ def initialize(key, wallet_file, gpg_pass = nil, gpg_exe = nil, pinmode = false)
30
+ @key = key
31
+ @gpg_pass = gpg_pass
32
+ @gpg_exe = gpg_exe
33
+ @wallet_file = wallet_file
34
+ @pinmode = pinmode
35
+
36
+ GPGME::Engine.set_info(GPGME::PROTOCOL_OpenPGP, @gpg_exe, "#{Dir.home}/.gnupg") unless @gpg_exe.to_s.empty?
37
+ end
38
+
39
+ # Read mpw file
40
+ def read_data
41
+ @data = []
42
+ @keys = {}
43
+ @passwords = {}
44
+ @otp_keys = {}
45
+
46
+ data = nil
47
+
48
+ return unless File.exist?(@wallet_file)
49
+
50
+ Gem::Package::TarReader.new(File.open(@wallet_file)) do |tar|
51
+ tar.each do |f|
52
+ case f.full_name
53
+ when 'wallet/meta.gpg'
54
+ data = decrypt(f.read)
55
+
56
+ when %r{^wallet/keys/(?<key>.+)\.pub$}
57
+ key = Regexp.last_match('key')
58
+
59
+ if GPGME::Key.find(:public, key).empty?
60
+ GPGME::Key.import(f.read, armor: true)
61
+ end
62
+
63
+ @keys[key] = f.read
64
+
65
+ when %r{^wallet/passwords/(?<id>[a-zA-Z0-9]+)\.gpg$}
66
+ @passwords[Regexp.last_match('id')] = f.read
67
+
68
+ when %r{^wallet/otp_keys/(?<id>[a-zA-Z0-9]+)\.gpg$}
69
+ @otp_keys[Regexp.last_match('id')] = f.read
70
+
71
+ else
72
+ next
73
+ end
74
+ end
75
+ end
76
+
77
+ unless data.to_s.empty?
78
+ YAML.safe_load(data).each_value do |d|
79
+ @data.push(
80
+ Item.new(
81
+ id: d['id'],
82
+ group: d['group'],
83
+ host: d['host'],
84
+ protocol: d['protocol'],
85
+ user: d['user'],
86
+ port: d['port'],
87
+ otp: @otp_keys.key?(d['id']),
88
+ comment: d['comment'],
89
+ last_edit: d['last_edit'],
90
+ created: d['created'],
91
+ )
92
+ )
93
+ end
94
+ end
95
+
96
+ add_key(@key) unless @keys.key?(@key)
97
+ rescue => e
98
+ raise "#{I18n.t('error.mpw_file.read_data')}\n#{e}"
99
+ end
100
+
101
+ # Encrypt a file
102
+ def write_data
103
+ data = {}
104
+ tmp_file = "#{@wallet_file}.tmp"
105
+
106
+ @data.each do |item|
107
+ next if item.empty?
108
+
109
+ data.merge!(
110
+ item.id => {
111
+ 'id' => item.id,
112
+ 'group' => item.group,
113
+ 'host' => item.host,
114
+ 'protocol' => item.protocol,
115
+ 'user' => item.user,
116
+ 'port' => item.port,
117
+ 'comment' => item.comment,
118
+ 'last_edit' => item.last_edit,
119
+ 'created' => item.created,
120
+ }
121
+ )
122
+ end
123
+
124
+ Gem::Package::TarWriter.new(File.open(tmp_file, 'w+')) do |tar|
125
+ data_encrypt = encrypt(data.to_yaml)
126
+ tar.add_file_simple('wallet/meta.gpg', 0400, data_encrypt.length) do |io|
127
+ io.write(data_encrypt)
128
+ end
129
+
130
+ @passwords.each do |id, password|
131
+ tar.add_file_simple("wallet/passwords/#{id}.gpg", 0400, password.length) do |io|
132
+ io.write(password)
133
+ end
134
+ end
135
+
136
+ @otp_keys.each do |id, key|
137
+ tar.add_file_simple("wallet/otp_keys/#{id}.gpg", 0400, key.length) do |io|
138
+ io.write(key)
139
+ end
140
+ end
141
+
142
+ @keys.each do |id, key|
143
+ tar.add_file_simple("wallet/keys/#{id}.pub", 0400, key.length) do |io|
144
+ io.write(key)
145
+ end
146
+ end
147
+ end
148
+
149
+ File.rename(tmp_file, @wallet_file)
150
+ rescue => e
151
+ File.unlink(tmp_file) if File.exist?(tmp_file)
152
+
153
+ raise "#{I18n.t('error.mpw_file.write_data')}\n#{e}"
154
+ end
155
+
156
+ # Get a password
157
+ # args: id -> the item id
158
+ def get_password(id)
159
+ password = decrypt(@passwords[id])
160
+
161
+ if /^\$[a-zA-Z0-9]{4,9}::(?<password>.+)$/ =~ password
162
+ Regexp.last_match('password')
163
+ else
164
+ password
165
+ end
166
+ end
167
+
168
+ # Set a password
169
+ # args: id -> the item id
170
+ # password -> the new password
171
+ def set_password(id, password)
172
+ salt = MPW.password(length: Random.rand(4..9))
173
+ password = "$#{salt}::#{password}"
174
+
175
+ @passwords[id] = encrypt(password)
176
+ end
177
+
178
+ # Return the list of all gpg keys
179
+ # rtrn: an array with the gpg keys name
180
+ def list_keys
181
+ @keys.keys
182
+ end
183
+
184
+ # Add a public key
185
+ # args: key -> new public key file or name
186
+ def add_key(key)
187
+ if File.exist?(key)
188
+ data = File.open(key).read
189
+ key_import = GPGME::Key.import(data, armor: true)
190
+ key = GPGME::Key.get(key_import.imports[0].fpr).uids[0].email
191
+ else
192
+ data = GPGME::Key.export(key, armor: true).read
193
+ end
194
+
195
+ raise I18n.t('error.export_key') if data.to_s.empty?
196
+
197
+ @keys[key] = data
198
+ @passwords.each_key { |id| set_password(id, get_password(id)) }
199
+ @otp_keys.each_key { |id| set_otp_key(id, get_otp_key(id)) }
200
+ end
201
+
202
+ # Delete a public key
203
+ # args: key -> public key to delete
204
+ def delete_key(key)
205
+ @keys.delete(key)
206
+ @passwords.each_key { |id| set_password(id, get_password(id)) }
207
+ @otp_keys.each_key { |id| set_otp_key(id, get_otp_key(id)) }
208
+ end
209
+
210
+ # Add a new item
211
+ # @args: item -> Object MPW::Item
212
+ def add(item)
213
+ raise I18n.t('error.bad_class') unless item.instance_of?(Item)
214
+ raise I18n.t('error.empty') if item.empty?
215
+
216
+ @data.push(item)
217
+ end
218
+
219
+ # Search in some csv data
220
+ # @args: options -> a hash with paramaters
221
+ # @rtrn: a list with the resultat of the search
222
+ def list(**options)
223
+ result = []
224
+
225
+ search = options[:pattern].to_s.downcase
226
+ group = options[:group].to_s.downcase
227
+
228
+ @data.each do |item|
229
+ next if item.empty?
230
+ next unless group.empty? || group.eql?(item.group.to_s.downcase)
231
+
232
+ host = item.host.to_s.downcase
233
+ comment = item.comment.to_s.downcase
234
+
235
+ next unless host =~ /^.*#{search}.*$/ || comment =~ /^.*#{search}.*$/
236
+
237
+ result.push(item)
238
+ end
239
+
240
+ result
241
+ end
242
+
243
+ # Search in some csv data
244
+ # @args: id -> the id item
245
+ # @rtrn: a row with the result of the search
246
+ def search_by_id(id)
247
+ @data.each do |item|
248
+ return item if item.id == id
249
+ end
250
+
251
+ nil
252
+ end
253
+
254
+ # Set an opt key
255
+ # args: id -> the item id
256
+ # key -> the new key
257
+ def set_otp_key(id, key)
258
+ @otp_keys[id] = encrypt(key.to_s) unless key.to_s.empty?
259
+ end
260
+
261
+ # Get an opt key
262
+ # args: id -> the item id
263
+ # key -> the new key
264
+ def get_otp_key(id)
265
+ @otp_keys.key?(id) ? decrypt(@otp_keys[id]) : nil
266
+ end
267
+
268
+ # Get an otp code
269
+ # @args: id -> the item id
270
+ # @rtrn: an otp code
271
+ def get_otp_code(id)
272
+ @otp_keys.key?(id) ? 0 : ROTP::TOTP.new(decrypt(@otp_keys[id])).now
273
+ end
274
+
275
+ # Get remaining time before expire otp code
276
+ # @rtrn: return time in seconde
277
+ def get_otp_remaining_time
278
+ (Time.now.utc.to_i / 30 + 1) * 30 - Time.now.utc.to_i
279
+ end
280
+
281
+ # Generate a random password
282
+ # @args: options -> :length, :special, :alpha, :numeric
283
+ # @rtrn: a random string
284
+ def self.password(**options)
285
+ length =
286
+ if !options.include?(:length) || options[:length].to_i <= 0
287
+ 8
288
+ elsif options[:length].to_i >= 32_768
289
+ 32_768
290
+ else
291
+ options[:length].to_i
292
+ end
293
+
294
+ chars = []
295
+ chars += [*('!'..'?')] - [*('0'..'9')] if options[:special]
296
+ chars += [*('A'..'Z'), *('a'..'z')] if options[:alpha]
297
+ chars += [*('0'..'9')] if options[:numeric]
298
+ chars = [*('A'..'Z'), *('a'..'z'), *('0'..'9')] if chars.empty?
299
+
300
+ result = ''
301
+ while length > 62
302
+ result << chars.sample(62).join
303
+ length -= 62
304
+ end
305
+ result << chars.sample(length).join
306
+
307
+ result
308
+ end
309
+
310
+ private
311
+
312
+ # Decrypt a gpg file
313
+ # @args: data -> string to decrypt
314
+ def decrypt(data)
315
+ return nil if data.to_s.empty?
316
+
317
+ password =
318
+ if /^(1\.[0-9.]+|2\.0)(\.[0-9]+)?/ =~ GPGME::Engine.info.first.version || @pinmode
319
+ { password: @gpg_pass }
320
+ else
321
+ { password: @gpg_pass,
322
+ pinentry_mode: GPGME::PINENTRY_MODE_LOOPBACK }
323
+ end
324
+
325
+ crypto = GPGME::Crypto.new(armor: true)
326
+ crypto
327
+ .decrypt(data, password)
328
+ .read.force_encoding('utf-8')
329
+ rescue => e
330
+ raise "#{I18n.t('error.gpg_file.decrypt')}\n#{e}"
331
+ end
332
+
333
+ # Encrypt a file
334
+ # args: data -> string to encrypt
335
+ def encrypt(data)
336
+ recipients = []
337
+ crypto = GPGME::Crypto.new(armor: true, always_trust: true)
338
+
339
+ recipients.push(@key)
340
+ @keys.each_key do |key|
341
+ next if key == @key
342
+ recipients.push(key)
343
+ end
344
+
345
+ crypto.encrypt(data, recipients: recipients).read
346
+ rescue => e
347
+ raise "#{I18n.t('error.gpg_file.encrypt')}\n#{e}"
348
+ end
349
+ end
487
350
  end