keepassx 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []