keepassx 0.1.0 → 1.0.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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +30 -0
  3. data/.gitignore +9 -0
  4. data/.rubocop.yml +64 -0
  5. data/.travis.yml +12 -3
  6. data/Gemfile +4 -2
  7. data/Guardfile +16 -0
  8. data/LICENSE +19 -0
  9. data/README.md +33 -0
  10. data/Rakefile +3 -2
  11. data/keepassx.gemspec +20 -10
  12. data/lib/keepassx.rb +42 -3
  13. data/lib/keepassx/aes_crypt.rb +16 -6
  14. data/lib/keepassx/database.rb +218 -27
  15. data/lib/keepassx/database/dumper.rb +87 -0
  16. data/lib/keepassx/database/finder.rb +102 -0
  17. data/lib/keepassx/database/loader.rb +217 -0
  18. data/lib/keepassx/entry.rb +70 -38
  19. data/lib/keepassx/field/base.rb +191 -0
  20. data/lib/keepassx/field/entry.rb +32 -0
  21. data/lib/keepassx/field/group.rb +27 -0
  22. data/lib/keepassx/fieldable.rb +161 -0
  23. data/lib/keepassx/group.rb +93 -20
  24. data/lib/keepassx/hashable_payload.rb +6 -0
  25. data/lib/keepassx/header.rb +102 -27
  26. data/lib/keepassx/version.rb +5 -0
  27. data/spec/factories.rb +23 -0
  28. data/spec/fixtures/database_empty.kdb +0 -0
  29. data/spec/fixtures/database_test.kdb +0 -0
  30. data/spec/fixtures/database_test_dumped.yml +76 -0
  31. data/spec/fixtures/database_with_key.kdb +0 -0
  32. data/spec/fixtures/database_with_key.key +1 -0
  33. data/spec/fixtures/database_with_key2.key +1 -0
  34. data/spec/fixtures/test_data_array.yml +113 -0
  35. data/spec/fixtures/test_data_array_dumped.yml +124 -0
  36. data/spec/keepassx/database_spec.rb +491 -29
  37. data/spec/keepassx/entry_spec.rb +95 -0
  38. data/spec/keepassx/group_spec.rb +92 -0
  39. data/spec/keepassx_spec.rb +17 -0
  40. data/spec/spec_helper.rb +59 -3
  41. metadata +143 -69
  42. data/.rvmrc +0 -1
  43. data/Gemfile.lock +0 -28
  44. data/lib/keepassx/entry_field.rb +0 -49
  45. data/lib/keepassx/group_field.rb +0 -44
  46. data/spec/test_database.kdb +0 -0
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class HashablePayload < Hash
4
+ include Hashie::Extensions::MergeInitializer
5
+ include Hashie::Extensions::IndifferentAccess
6
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # The keepass file header.
2
4
  #
3
5
  # From the KeePass doc:
@@ -32,38 +34,62 @@
32
34
  # - aContentsHash: "plain contents" refers to the database file, minus the
33
35
  # database header, decrypted by FinalKey.
34
36
  # * PlainContents = Decrypt_with_FinalKey(DatabaseFile - DatabaseHeader)
37
+
35
38
  module Keepassx
36
39
  class Header
37
40
 
38
41
  ENCRYPTION_FLAGS = [
39
- [1 , 'SHA2' ],
40
- [2 , 'Rijndael'],
41
- [2 , 'AES' ],
42
- [4 , 'ArcFour' ],
43
- [8 , 'TwoFish' ]
44
- ]
45
-
46
- attr_reader :encryption_iv
47
- attr_reader :ngroups, :nentries
48
-
49
- def initialize(header_bytes)
50
- @signature1 = header_bytes[0..4].unpack('L*').first
51
- @signature2 = header_bytes[4..8].unpack('L*').first
52
- @flags = header_bytes[8..12].unpack('L*').first
53
- @version = header_bytes[12..16].unpack('L*').first
54
- @master_seed = header_bytes[16...32]
55
- @encryption_iv = header_bytes[32...48]
56
- @ngroups = header_bytes[48..52].unpack('L*').first
57
- @nentries = header_bytes[52..56].unpack('L*').first
58
- @contents_hash = header_bytes[56..88]
59
- @master_seed2 = header_bytes[88...120]
60
- @rounds = header_bytes[120..-1].unpack('L*').first
42
+ [1, 'SHA2'],
43
+ [2, 'Rijndael'],
44
+ [2, 'AES'],
45
+ [4, 'ArcFour'],
46
+ [8, 'TwoFish'],
47
+ ].freeze
48
+
49
+ SIGNATURES = [0x9AA2D903, 0xB54BFB65].freeze
50
+
51
+ attr_reader :encryption_iv
52
+ attr_accessor :groups_count
53
+ attr_accessor :entries_count
54
+ attr_accessor :content_hash
55
+
56
+
57
+ # rubocop:disable Metrics/MethodLength
58
+ def initialize(header_bytes = nil)
59
+ if header_bytes.nil?
60
+ @signature1 = SIGNATURES[0]
61
+ @signature2 = SIGNATURES[1]
62
+ @flags = 3 # SHA2 hashing, AES encryption
63
+ @version = 0x30002
64
+ @master_seed = SecureRandom.random_bytes(16)
65
+ @encryption_iv = SecureRandom.random_bytes(16)
66
+ @groups_count = 0
67
+ @entries_count = 0
68
+ @master_seed2 = SecureRandom.random_bytes(32)
69
+ @rounds = 50_000
70
+ else
71
+ header_bytes = StringIO.new(header_bytes)
72
+ @signature1 = header_bytes.read(4).unpack('L*').first
73
+ @signature2 = header_bytes.read(4).unpack('L*').first
74
+ @flags = header_bytes.read(4).unpack('L*').first
75
+ @version = header_bytes.read(4).unpack('L*').first
76
+ @master_seed = header_bytes.read(16)
77
+ @encryption_iv = header_bytes.read(16)
78
+ @groups_count = header_bytes.read(4).unpack('L*').first
79
+ @entries_count = header_bytes.read(4).unpack('L*').first
80
+ @content_hash = header_bytes.read(32)
81
+ @master_seed2 = header_bytes.read(32)
82
+ @rounds = header_bytes.read(4).unpack('L*').first
83
+ end
61
84
  end
85
+ # rubocop:enable Metrics/MethodLength
86
+
62
87
 
63
88
  def valid?
64
- @signature1 == 0x9AA2D903 && @signature2 == 0xB54BFB65
89
+ @signature1 == SIGNATURES[0] && @signature2 == SIGNATURES[1]
65
90
  end
66
91
 
92
+
67
93
  def encryption_type
68
94
  ENCRYPTION_FLAGS.each do |(flag_mask, encryption_type)|
69
95
  return encryption_type if @flags & flag_mask
@@ -71,17 +97,66 @@ module Keepassx
71
97
  'Unknown'
72
98
  end
73
99
 
74
- def final_key(master_key)
100
+
101
+ # rubocop:disable Metrics/MethodLength
102
+ def final_key(master_key, keyfile_data = nil)
75
103
  key = Digest::SHA2.new.update(master_key).digest
76
- aes = FastAES.new(@master_seed2)
77
104
 
78
- @rounds.times do |i|
79
- key = aes.encrypt(key)
105
+ if keyfile_data
106
+ keyfile_hash = extract_keyfile_hash(keyfile_data)
107
+ key = master_key == '' ? keyfile_hash : Digest::SHA2.new.update(key + keyfile_hash).digest
108
+ end
109
+
110
+ aes = OpenSSL::Cipher.new('AES-256-ECB')
111
+ aes.encrypt
112
+ aes.key = @master_seed2
113
+ aes.padding = 0
114
+
115
+ @rounds.times do
116
+ key = aes.update(key) + aes.final
80
117
  end
81
118
 
82
119
  key = Digest::SHA2.new.update(key).digest
83
120
  key = Digest::SHA2.new.update(@master_seed + key).digest
84
121
  key
85
122
  end
123
+ # rubocop:enable Metrics/MethodLength
124
+
125
+
126
+ # Return encoded header
127
+ #
128
+ # @return [String] Encoded header representation.
129
+ def encode
130
+ [@signature1].pack('L*') <<
131
+ [@signature2].pack('L*') <<
132
+ [@flags].pack('L*') <<
133
+ [@version].pack('L*') <<
134
+ @master_seed <<
135
+ @encryption_iv <<
136
+ [@groups_count].pack('L*') <<
137
+ [@entries_count].pack('L*') <<
138
+ @content_hash <<
139
+ @master_seed2 <<
140
+ [@rounds].pack('L*')
141
+ end
142
+
143
+
144
+ private
145
+
146
+
147
+ def extract_keyfile_hash(keyfile_data)
148
+ # Hex encoded key
149
+ if keyfile_data.size == 64
150
+ [keyfile_data].pack('H*')
151
+
152
+ # Raw key
153
+ elsif keyfile_data.size == 32
154
+ keyfile_data
155
+
156
+ else
157
+ Digest::SHA2.new.update(keyfile_data).digest
158
+ end
159
+ end
160
+
86
161
  end
87
162
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Keepassx
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,23 @@
1
+ FactoryBot.define do
2
+
3
+ factory :group, class: Keepassx::Group do
4
+ id { 1 }
5
+ name { 'test_group' }
6
+ icon { 20 }
7
+
8
+ initialize_with { new(attributes) }
9
+ end
10
+
11
+ factory :entry, class: Keepassx::Entry do
12
+ name { 'test_entry' }
13
+ group { build(:group) }
14
+ username { 'test' }
15
+ password { 'test' }
16
+ icon { 20 }
17
+ url { 'https://example.com' }
18
+ notes { 'Test comment' }
19
+
20
+ initialize_with { new(attributes) }
21
+ end
22
+
23
+ end
@@ -0,0 +1,76 @@
1
+ ---
2
+ - id: 503880962
3
+ name: Internet
4
+ icon: 1
5
+ level: 0
6
+ flags: 0
7
+ entries:
8
+ - id: 678f624ea0204444f775fb252d754ff3
9
+ group_id: 503880962
10
+ icon: 1
11
+ name: test entry
12
+ url: http://example.com/testurl
13
+ username: testuser
14
+ password: testpassword
15
+ notes: test comment
16
+ - id: 8bfa7a503bb6046b1ab6331f3de3bdeb
17
+ group_id: 503880962
18
+ icon: 1
19
+ name: entry2
20
+ url: http://example.com
21
+ username: user
22
+ password: pass2
23
+ notes: comment
24
+ - id: '00000000000000000000000000000000'
25
+ group_id: 503880962
26
+ icon: 0
27
+ name: Meta-Info
28
+ url: "$"
29
+ username: SYSTEM
30
+ password: ''
31
+ notes: KPX_CUSTOM_ICONS_4
32
+ - id: '00000000000000000000000000000000'
33
+ group_id: 503880962
34
+ icon: 0
35
+ name: Meta-Info
36
+ url: "$"
37
+ username: SYSTEM
38
+ password: ''
39
+ notes: KPX_GROUP_TREE_STATE
40
+ groups:
41
+ - id: 3210836170
42
+ name: Web
43
+ icon: 61
44
+ level: 1
45
+ flags: 0
46
+ entries:
47
+ - id: fb3bd253ca650a0faaf8b1c94ffdd648
48
+ group_id: 3210836170
49
+ icon: 20
50
+ name: test entry 2
51
+ url: ''
52
+ username: ''
53
+ password: ''
54
+ notes: ''
55
+ groups:
56
+ - id: 1532025522
57
+ name: Wikipedia
58
+ icon: 54
59
+ level: 2
60
+ flags: 0
61
+ entries: []
62
+ groups: []
63
+ - id: 2863278214
64
+ name: eMail
65
+ icon: 19
66
+ level: 0
67
+ flags: 0
68
+ entries: []
69
+ groups: []
70
+ - id: 1190700005
71
+ name: Backup
72
+ icon: 4
73
+ level: 0
74
+ flags: 0
75
+ entries: []
76
+ groups: []
@@ -0,0 +1 @@
1
+ D27454A8D561887ADA616E972FA4A00BCF2F869FB49E03EF06CB27DB2060737D
@@ -0,0 +1 @@
1
+ 2BAD1145C4EA3E0A1DE25B3302DE8FC03634916137A3BB03D064647094DB30A1
@@ -0,0 +1,113 @@
1
+ ---
2
+ - :name: Internet
3
+ :icon: 1
4
+ :creation_time: 2016-09-03 21:09:06.000000000 +02:00
5
+ :last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
6
+ :last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
7
+ :groups:
8
+ - :name: Web
9
+ :icon: 61
10
+ :creation_time: 2016-09-03 21:09:06.000000000 +02:00
11
+ :last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
12
+ :last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
13
+ :groups:
14
+ - :name: Amazon
15
+ :icon: 30
16
+ :creation_time: 2016-09-03 21:09:06.000000000 +02:00
17
+ :last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
18
+ :last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
19
+ - :name: Wikipedia
20
+ :icon: 54
21
+ :creation_time: 2016-09-03 21:09:06.000000000 +02:00
22
+ :last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
23
+ :last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
24
+ :entries:
25
+ - :name: Wikipedia URL 1
26
+ :username: testwiki1
27
+ :url: http://wiki.com/testurl1
28
+ :password: testwiki1
29
+ :notes: test wiki comment1
30
+ :group: Wikipedia
31
+ :id: f720371d-15f1-4b9b-918e-68bb83e4407e
32
+ :last_mod_time: 2008-09-07 23:40:43
33
+ :last_acc_time: 2008-09-07 23:40:43
34
+ :creation_time: 2008-09-07 23:40:43
35
+ - :name: Wikipedia URL 2
36
+ :username: testwiki2
37
+ :url: http://wiki.com/testurl2
38
+ :password: testwiki2
39
+ :notes: test wiki comment2
40
+ :group: Wikipedia
41
+ :id: e720371d-15f1-4c9b-918e-68bb83e4407e
42
+ :last_mod_time: 2008-09-07 23:43:43
43
+ :last_acc_time: 2008-09-07 23:20:43
44
+ :creation_time: 2008-09-07 23:50:43
45
+ :entries:
46
+ - :name: Internet URL 1
47
+ :username: testuser1
48
+ :url: http://example.com/testurl1
49
+ :password: testuser1
50
+ :notes: test comment1
51
+ :id: 2d3fae36-e89f-48bd-8900-389cc01ef248
52
+ :group: Internet
53
+ :last_mod_time: 2008-09-03 23:40:43
54
+ :last_acc_time: 2008-09-03 23:40:43
55
+ :creation_time: 2008-09-03 23:40:43
56
+ - :name: Internet URL 2
57
+ :username: testuser2
58
+ :url: http://example.com/testurl2
59
+ :password: testuser2
60
+ :notes: text comment2
61
+ :id: 866216ea-3a57-43df-8847-becfc3593939
62
+ :group: Internet
63
+ :last_mod_time: 2008-09-07 22:40:43
64
+ :last_acc_time: 2008-09-07 22:40:43
65
+ :creation_time: 2008-09-07 22:40:43
66
+ - :name: eMail
67
+ :icon: 19
68
+ :creation_time: 2016-09-03 21:09:06.000000000 +02:00
69
+ :last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
70
+ :last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
71
+ :groups:
72
+ - :name: Personnal
73
+ :icon: 61
74
+ :creation_time: 2016-09-03 21:09:06.000000000 +02:00
75
+ :last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
76
+ :last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
77
+ :groups:
78
+ - :name: Microsoft
79
+ :icon: 80
80
+ :creation_time: 2016-09-03 21:09:06.000000000 +02:00
81
+ :last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
82
+ :last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
83
+ - :name: Apple
84
+ :icon: 81
85
+ :creation_time: 2016-09-03 21:09:06.000000000 +02:00
86
+ :last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
87
+ :last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
88
+ - :name: Google
89
+ :icon: 82
90
+ :creation_time: 2016-09-03 21:09:06.000000000 +02:00
91
+ :last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
92
+ :last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
93
+ - :name: Profesionnal
94
+ :icon: 61
95
+ :creation_time: 2016-09-03 21:09:06.000000000 +02:00
96
+ :last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
97
+ :last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
98
+ :groups:
99
+ - :name: Microsoft
100
+ :icon: 70
101
+ :creation_time: 2016-09-03 21:09:06.000000000 +02:00
102
+ :last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
103
+ :last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
104
+ - :name: Apple
105
+ :icon: 71
106
+ :creation_time: 2016-09-03 21:09:06.000000000 +02:00
107
+ :last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
108
+ :last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
109
+ - :name: Google
110
+ :icon: 72
111
+ :creation_time: 2016-09-03 21:09:06.000000000 +02:00
112
+ :last_mod_time: 2016-09-03 21:09:06.000000000 +02:00
113
+ :last_acc_time: 2016-09-03 21:09:06.000000000 +02:00
@@ -0,0 +1,124 @@
1
+ ---
2
+ - id: 1
3
+ name: Internet
4
+ icon: 1
5
+ level: 0
6
+ flags: 0
7
+ entries:
8
+ - id: 2d3fae36de89fd48bdd8900d389cc01ef248
9
+ group_id: 1
10
+ icon: 1
11
+ name: Internet URL 1
12
+ url: http://example.com/testurl1
13
+ username: testuser1
14
+ password: testuser1
15
+ notes: test comment1
16
+ - id: 866216ead3a57d43dfd8847dbecfc3593939
17
+ group_id: 1
18
+ icon: 1
19
+ name: Internet URL 2
20
+ url: http://example.com/testurl2
21
+ username: testuser2
22
+ password: testuser2
23
+ notes: text comment2
24
+ groups:
25
+ - id: 2
26
+ name: Web
27
+ icon: 61
28
+ level: 1
29
+ flags: 0
30
+ entries: []
31
+ groups:
32
+ - id: 3
33
+ name: Amazon
34
+ icon: 30
35
+ level: 2
36
+ flags: 0
37
+ entries: []
38
+ groups: []
39
+ - id: 4
40
+ name: Wikipedia
41
+ icon: 54
42
+ level: 2
43
+ flags: 0
44
+ entries:
45
+ - id: f720371dd15f1d4b9bd918ed68bb83e4407e
46
+ group_id: 4
47
+ icon: 1
48
+ name: Wikipedia URL 1
49
+ url: http://wiki.com/testurl1
50
+ username: testwiki1
51
+ password: testwiki1
52
+ notes: test wiki comment1
53
+ - id: e720371dd15f1d4c9bd918ed68bb83e4407e
54
+ group_id: 4
55
+ icon: 1
56
+ name: Wikipedia URL 2
57
+ url: http://wiki.com/testurl2
58
+ username: testwiki2
59
+ password: testwiki2
60
+ notes: test wiki comment2
61
+ groups: []
62
+ - id: 5
63
+ name: eMail
64
+ icon: 19
65
+ level: 0
66
+ flags: 0
67
+ entries: []
68
+ groups:
69
+ - id: 6
70
+ name: Personnal
71
+ icon: 61
72
+ level: 1
73
+ flags: 0
74
+ entries: []
75
+ groups:
76
+ - id: 7
77
+ name: Microsoft
78
+ icon: 80
79
+ level: 2
80
+ flags: 0
81
+ entries: []
82
+ groups: []
83
+ - id: 8
84
+ name: Apple
85
+ icon: 81
86
+ level: 2
87
+ flags: 0
88
+ entries: []
89
+ groups: []
90
+ - id: 9
91
+ name: Google
92
+ icon: 82
93
+ level: 2
94
+ flags: 0
95
+ entries: []
96
+ groups: []
97
+ - id: 10
98
+ name: Profesionnal
99
+ icon: 61
100
+ level: 1
101
+ flags: 0
102
+ entries: []
103
+ groups:
104
+ - id: 11
105
+ name: Microsoft
106
+ icon: 70
107
+ level: 2
108
+ flags: 0
109
+ entries: []
110
+ groups: []
111
+ - id: 12
112
+ name: Apple
113
+ icon: 71
114
+ level: 2
115
+ flags: 0
116
+ entries: []
117
+ groups: []
118
+ - id: 13
119
+ name: Google
120
+ icon: 72
121
+ level: 2
122
+ flags: 0
123
+ entries: []
124
+ groups: []