rubeepass 3.1.1 → 3.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ebdf490b8dbfa0fad27628bf3ee55cb12bf672dd81e54ca36b225499039ed77
4
- data.tar.gz: 783c86924e17c98334a8efc781868eb3f1ff3d14aeac714ea44ac0cf3dd4b395
3
+ metadata.gz: 6389189519412fd7b336eea64280403924c313955e78f3b6eaea377303a3552a
4
+ data.tar.gz: d4c77407fa1c2b92764f2dc314e8ac31c472b0c9c01a9c3158bca3a08ddecc8d
5
5
  SHA512:
6
- metadata.gz: 9af2bc5f82ba17b63c02ebef05a7da7a1b1ed0d32ec4ec607c819e84bff3a387bdeb4fd321c5c3778d82b285f827a35e75a53b2ddd1c83a35742a33abaf3f7b3
7
- data.tar.gz: e182c2ddd5cd06e7af83e344f893bba31c6b8670b65f29469c71e4721a8f22559e23ddde0f00cb3a9c3aab920920fc32407500aa94f3365cb4b2fe55a3251ac8
6
+ metadata.gz: c90641b9ff5afcf3272464a4c2a45383d48c648869eb5d485ff593af5838aca8d137a77c2006a0a3cc2d446b89cee2d30308190154a913df341f3c4db87b1cdd
7
+ data.tar.gz: 380cb82f98469fe27e4a7ab4d21883835d9855b78f863a40d2a2fc4eec50ac0119043200545fa94b9148d72be34692d5010330bb09690154c9f5ae14f0e148e3
data/bin/rpass CHANGED
@@ -77,10 +77,10 @@ def parse(args)
77
77
 
78
78
  opts.banner = "Usage: #{File.basename($0)} [OPTIONS] [kdbx]"
79
79
 
80
- opts.on("")
80
+ opts.on("", "DESCRIPTION")
81
81
 
82
- info.scan(/\S.{0,80}\S(?=\s|$)|\S+/).each do |line|
83
- opts.on("#{line}")
82
+ info.scan(/\S.{0,66}\S(?=\s|$)|\S+/).each do |line|
83
+ opts.on(" #{line}")
84
84
  end
85
85
 
86
86
  opts.on("", "OPTIONS")
@@ -148,6 +148,13 @@ def parse(args)
148
148
  options["verbose"] = true
149
149
  end
150
150
 
151
+ opts.on("-V", "--version", "Show version") do
152
+ __FILE__.match(/rubeepass-(\d+\.\d+\.\d+)/) do |m|
153
+ puts m[1]
154
+ end
155
+ exit RubeePassExit::GOOD
156
+ end
157
+
151
158
  opts.on(
152
159
  "",
153
160
  "FORMATS",
@@ -295,24 +302,31 @@ rescue SystemExit
295
302
  # Quit from djinni
296
303
  # Exit gracefully
297
304
  rescue Interrupt
298
- # ^C
299
- # Exit gracefully
305
+ # Exit gracefully on ^C
300
306
  rescue Errno::EPIPE
301
307
  # Do nothing. This can happen if piping to another program such as
302
308
  # less. Usually if less is closed before we're done with STDOUT.
303
309
  rescue RubeePass::Error => e
304
- puts e.message
310
+ $stderr.puts e.message.red
311
+ if (options["verbose"])
312
+ e.backtrace.each do |line|
313
+ $stderr.puts line.yellow
314
+ end
315
+ end
305
316
  exit RubeePassExit::EXCEPTION
306
317
  rescue Exception => e
307
- $stderr.puts
308
- $stderr.puts "Oops! Looks like an error has occured! If the " \
309
- "error persists, file a bug at:"
318
+ $stderr.puts [
319
+ "Oops! Looks like an error has occured! If the error",
320
+ "persists, file a bug at:"
321
+ ].join(" ").wrap
310
322
  $stderr.puts
311
323
  $stderr.puts " https://gitlab.com/mjwhitta/rubeepass/issues"
312
324
  $stderr.puts
313
- $stderr.puts "Maybe the message below will help. If not, you " \
314
- "can use the --verbose flag to get"
315
- $stderr.puts "a backtrace."
325
+ $stderr.puts [
326
+ "Maybe the message below will help. If not, you can use the",
327
+ "--verbose flag to get a backtrace."
328
+ ].join(" ").wrap
329
+ $stderr.puts
316
330
 
317
331
  $stderr.puts e.message.white.on_red
318
332
  if (options["verbose"])
@@ -10,6 +10,7 @@ require "uri"
10
10
  require "zlib"
11
11
 
12
12
  class RubeePass
13
+ # Header fields
13
14
  @@END_OF_HEADER = 0
14
15
  @@COMMENT = 1
15
16
  @@CIPHER_ID = 2
@@ -22,10 +23,19 @@ class RubeePass
22
23
  @@STREAM_START_BYTES = 9
23
24
  @@INNER_RANDOM_STREAM_ID = 10
24
25
 
26
+ # Magic values
25
27
  @@MAGIC_SIG1 = 0x9aa2d903
26
28
  @@MAGIC_SIG2 = 0xb54bfb67
27
29
  @@VERSION = 0x00030000
28
30
 
31
+ # Encryption schemes
32
+ @@AES_AESKDF3 = "31c1f2e6bf714350be5805216afc5aff"
33
+ @@AES_AESKDF4_OR_ARGON2 = "000031c1f2e6bf714350be5805216afc"
34
+ @@CHACHA20_AESKDF3 = "d6038a2b8b6f4cb5a524339a31dbb59a"
35
+ @@CHACHA20_AESKDF4_OR_ARGON2 = "0000d6038a2b8b6f4cb5a524339a31db"
36
+ @@TWOFISH_AESKDF3 = "ad68f29f576f4bb9a36ad47af965346c"
37
+ @@TWOFISH_AESKDF4_OR_ARGON2 = "0000ad68f29f576f4bb9a36ad47af965"
38
+
29
39
  attr_reader :attachment_decoder
30
40
  attr_reader :db
31
41
  attr_reader :gzip
@@ -135,23 +145,43 @@ class RubeePass
135
145
  end
136
146
  end
137
147
 
138
- def derive_aes_key
148
+ def derive_aeskdf3_key(header)
149
+ irsi = "\x02\x00\x00\x00"
150
+ if (
151
+ (header[@@MASTER_SEED].length != 32) ||
152
+ (header[@@TRANSFORM_SEED].length != 32)
153
+ )
154
+ raise Error::InvalidHeader.new
155
+ elsif (header[@@INNER_RANDOM_STREAM_ID] != irsi)
156
+ raise Error::NotSalsa.new
157
+ end
158
+
139
159
  cipher = OpenSSL::Cipher::AES.new(256, :ECB)
140
160
  cipher.encrypt
141
161
  cipher.key = @header[@@TRANSFORM_SEED]
142
162
  cipher.padding = 0
143
163
 
164
+ key = @initial_key
144
165
  @header[@@TRANSFORM_ROUNDS].times do
145
- @key = cipher.update(@key) + cipher.final
166
+ key = cipher.update(key) + cipher.final
146
167
  end
147
168
 
148
- transform_key = Digest::SHA256::digest(@key)
169
+ transform_key = Digest::SHA256::digest(key)
149
170
  combined_key = @header[@@MASTER_SEED] + transform_key
150
171
 
151
- @aes_key = Digest::SHA256::digest(combined_key)
152
- @aes_iv = @header[@@ENCRYPTION_IV]
172
+ @cipher = OpenSSL::Cipher::AES.new(256, :CBC)
173
+ @key = Digest::SHA256::digest(combined_key)
174
+ @iv = @header[@@ENCRYPTION_IV]
153
175
  end
154
- private :derive_aes_key
176
+ private :derive_aeskdf3_key
177
+
178
+ def derive_aeskdf4_or_argon2_key(header)
179
+ # require "pry"
180
+ # binding.pry
181
+ # puts "AES with AES-KDF4 or Argon2"
182
+ raise Error::NotSupported.new # TODO
183
+ end
184
+ private :derive_aeskdf4_or_argon2_key
155
185
 
156
186
  def export(export_file, format)
157
187
  start_opening
@@ -251,7 +281,7 @@ class RubeePass
251
281
  end
252
282
  end
253
283
 
254
- @key = Digest::SHA256.digest(passhash + filehash)
284
+ @initial_key = Digest::SHA256.digest(passhash + filehash)
255
285
  end
256
286
  private :join_key_and_keyfile
257
287
 
@@ -336,6 +366,31 @@ class RubeePass
336
366
  end
337
367
  private :parse_gzip
338
368
 
369
+ def parse_header(header)
370
+ case header[@@CIPHER_ID].unpack("H*")[0]
371
+ when @@AES_AESKDF3
372
+ derive_aeskdf3_key(header)
373
+ when @@AES_AESKDF4_OR_ARGON2
374
+ derive_aeskdf4_or_argon2_key(header)
375
+ when @@CHACHA20_AESKDF3
376
+ # puts "ChaCha20 with AES-KDF3"
377
+ raise Error::NotSupported.new # TODO
378
+ when @@CHACHA20_AESKDF4_OR_ARGON2
379
+ # puts "ChaCha20 with AES-KDF4 or Argon2"
380
+ raise Error::NotSupported.new # TODO
381
+ when @@TWOFISH_AESKDF3
382
+ # puts "Twofish with AES-KDF3"
383
+ raise Error::NotSupported.new # TODO
384
+ when @@TWOFISH_AESKDF4_OR_ARGON2
385
+ # puts "Twofish with AES-KDF4 or Argon2"
386
+ raise Error::NotSupported.new # TODO
387
+ else
388
+ # puts header[@@CIPHER_ID].unpack("H*")[0]
389
+ raise Error::NotSupported.new
390
+ end
391
+ end
392
+ private :parse_header
393
+
339
394
  def parse_xml
340
395
  doc = REXML::Document.new(@xml)
341
396
  if (doc.elements["KeePassFile/Root"].nil?)
@@ -352,10 +407,10 @@ class RubeePass
352
407
  private :parse_xml
353
408
 
354
409
  def read_gzip(file)
355
- cipher = OpenSSL::Cipher::AES.new(256, :CBC)
410
+ cipher = @cipher.clone
356
411
  cipher.decrypt
357
- cipher.key = @aes_key
358
- cipher.iv = @aes_iv
412
+ cipher.key = @key
413
+ cipher.iv = @iv
359
414
 
360
415
  encrypted = file.read
361
416
 
@@ -379,7 +434,7 @@ class RubeePass
379
434
  header = Hash.new
380
435
  loop do
381
436
  data = file.read(1)
382
- raise Error::InvalidHeader.new if (data.nil?)
437
+ break if (data.nil?)
383
438
  id = data.unpack("C*")[0]
384
439
 
385
440
  data = file.read(2)
@@ -387,6 +442,9 @@ class RubeePass
387
442
  size = data.unpack("S*")[0]
388
443
 
389
444
  data = file.read(size)
445
+ if (data.nil? && (size > 0))
446
+ raise Error::InvalidHeader.new
447
+ end
390
448
 
391
449
  case id
392
450
  when @@END_OF_HEADER
@@ -398,19 +456,6 @@ class RubeePass
398
456
  end
399
457
  end
400
458
 
401
- irsi = "\x02\x00\x00\x00"
402
- aes = "31c1f2e6bf714350be5805216afc5aff"
403
- if (
404
- (header[@@MASTER_SEED].length != 32) ||
405
- (header[@@TRANSFORM_SEED].length != 32)
406
- )
407
- raise Error::InvalidHeader.new
408
- elsif (header[@@INNER_RANDOM_STREAM_ID] != irsi)
409
- raise Error::NotSalsa.new
410
- elsif (header[@@CIPHER_ID].unpack("H*")[0] != aes)
411
- raise Error::NotAES.new
412
- end
413
-
414
459
  @header = header
415
460
  end
416
461
  private :read_header
@@ -436,20 +481,20 @@ class RubeePass
436
481
  private :read_magic_and_version
437
482
 
438
483
  def start_opening
439
- @aes_iv = nil
440
- @aes_key = nil
441
484
  @db = nil
442
485
  @gzip = nil
443
486
  @header = nil
487
+ @initial_key = nil
488
+ @iv = nil
444
489
  @key = nil
445
490
  @xml = nil
446
491
 
447
492
  file = File.open(@kdbx)
448
493
 
449
494
  read_magic_and_version(file)
450
- read_header(file)
495
+ header = read_header(file)
451
496
  join_key_and_keyfile
452
- derive_aes_key
497
+ parse_header(header)
453
498
  read_gzip(file)
454
499
 
455
500
  file.close
@@ -10,5 +10,5 @@ require "rubeepass/error/invalid_protected_data"
10
10
  require "rubeepass/error/invalid_protected_stream_key"
11
11
  require "rubeepass/error/invalid_version"
12
12
  require "rubeepass/error/invalid_xml"
13
- require "rubeepass/error/not_aes"
14
13
  require "rubeepass/error/not_salsa20"
14
+ require "rubeepass/error/not_supported"
@@ -0,0 +1,5 @@
1
+ class RubeePass::Error::NotSupported < RubeePass::Error
2
+ def initialize
3
+ super("Encryption scheme not currently supported")
4
+ end
5
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubeepass
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.1
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Whittaker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-08-12 00:00:00.000000000 Z
11
+ date: 2018-08-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -192,8 +192,8 @@ files:
192
192
  - lib/rubeepass/error/invalid_protected_stream_key.rb
193
193
  - lib/rubeepass/error/invalid_version.rb
194
194
  - lib/rubeepass/error/invalid_xml.rb
195
- - lib/rubeepass/error/not_aes.rb
196
195
  - lib/rubeepass/error/not_salsa20.rb
196
+ - lib/rubeepass/error/not_supported.rb
197
197
  - lib/rubeepass/group.rb
198
198
  - lib/rubeepass/protected_decryptor.rb
199
199
  - lib/rubeepass/wish/cd_wish.rb
@@ -1,5 +0,0 @@
1
- class RubeePass::Error::NotAES < RubeePass::Error
2
- def initialize
3
- super("Not AES!")
4
- end
5
- end