keepassx 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rvmrc +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +28 -0
- data/README.md +0 -0
- data/Rakefile +5 -0
- data/keepassx.gemspec +14 -0
- data/lib/keepassx.rb +13 -0
- data/lib/keepassx/aes_crypt.rb +19 -0
- data/lib/keepassx/database.rb +45 -0
- data/lib/keepassx/entry.rb +81 -0
- data/lib/keepassx/entry_field.rb +49 -0
- data/lib/keepassx/group.rb +56 -0
- data/lib/keepassx/group_field.rb +44 -0
- data/lib/keepassx/header.rb +87 -0
- data/spec/keepassx/database_spec.rb +56 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/test_database.kdb +0 -0
- metadata +108 -0
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 1.8.7-p249@keepassx --create
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
keepassx (0.1.0)
|
5
|
+
fast-aes (~> 0.1)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
diff-lcs (1.1.3)
|
11
|
+
fast-aes (0.1.1)
|
12
|
+
rake (0.8.7)
|
13
|
+
rspec (2.11.0)
|
14
|
+
rspec-core (~> 2.11.0)
|
15
|
+
rspec-expectations (~> 2.11.0)
|
16
|
+
rspec-mocks (~> 2.11.0)
|
17
|
+
rspec-core (2.11.1)
|
18
|
+
rspec-expectations (2.11.3)
|
19
|
+
diff-lcs (~> 1.1.3)
|
20
|
+
rspec-mocks (2.11.3)
|
21
|
+
|
22
|
+
PLATFORMS
|
23
|
+
ruby
|
24
|
+
|
25
|
+
DEPENDENCIES
|
26
|
+
keepassx!
|
27
|
+
rake (= 0.8.7)
|
28
|
+
rspec (= 2.11.0)
|
data/README.md
ADDED
File without changes
|
data/Rakefile
ADDED
data/keepassx.gemspec
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "keepassx"
|
3
|
+
s.summary = "Ruby API access for KeePassX databases"
|
4
|
+
s.description = "See http://github.com/pitluga/keepassx"
|
5
|
+
s.version = "0.1.0"
|
6
|
+
s.authors = ["Tony Pitluga", "Paul Hinze"]
|
7
|
+
s.email = ["tony.pitluga@gmail.com", "paul.t.hinze@gmail.com"]
|
8
|
+
s.homepage = "http://github.com/pitluga/keepassx"
|
9
|
+
s.files = `git ls-files`.split("\n")
|
10
|
+
|
11
|
+
s.add_dependency "fast-aes", "~> 0.1"
|
12
|
+
|
13
|
+
s.add_development_dependency "rspec", "2.11.0"
|
14
|
+
end
|
data/lib/keepassx.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'stringio'
|
3
|
+
require 'openssl'
|
4
|
+
require 'digest/sha2'
|
5
|
+
require 'fast-aes'
|
6
|
+
|
7
|
+
require 'keepassx/database'
|
8
|
+
require 'keepassx/entry'
|
9
|
+
require 'keepassx/entry_field'
|
10
|
+
require 'keepassx/group'
|
11
|
+
require 'keepassx/group_field'
|
12
|
+
require 'keepassx/header'
|
13
|
+
require 'keepassx/aes_crypt'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Keepassx
|
2
|
+
module AESCrypt
|
3
|
+
def self.decrypt(encrypted_data, key, iv, cipher_type)
|
4
|
+
aes = OpenSSL::Cipher::Cipher.new(cipher_type)
|
5
|
+
aes.decrypt
|
6
|
+
aes.key = key
|
7
|
+
aes.iv = iv unless iv.nil?
|
8
|
+
aes.update(encrypted_data) + aes.final
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.encrypt(data, key, iv, cipher_type)
|
12
|
+
aes = OpenSSL::Cipher::Cipher.new(cipher_type)
|
13
|
+
aes.encrypt
|
14
|
+
aes.key = key
|
15
|
+
aes.iv = iv unless iv.nil?
|
16
|
+
aes.update(data) + aes.final
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Keepassx
|
2
|
+
class Database
|
3
|
+
|
4
|
+
attr_reader :header, :groups, :entries
|
5
|
+
|
6
|
+
def self.open(path)
|
7
|
+
content = File.respond_to?(:binread) ? File.binread(path) : File.read(path)
|
8
|
+
self.new(content)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(raw_db)
|
12
|
+
@header = Header.new(raw_db[0..124])
|
13
|
+
@encrypted_payload = raw_db[124..-1]
|
14
|
+
end
|
15
|
+
|
16
|
+
def entry(title)
|
17
|
+
@entries.detect { |e| e.title == title }
|
18
|
+
end
|
19
|
+
|
20
|
+
def unlock(master_password)
|
21
|
+
@final_key = header.final_key(master_password)
|
22
|
+
decrypt_payload
|
23
|
+
payload_io = StringIO.new(@payload)
|
24
|
+
@groups = Group.extract_from_payload(header, payload_io)
|
25
|
+
@entries = Entry.extract_from_payload(header, payload_io)
|
26
|
+
true
|
27
|
+
rescue OpenSSL::Cipher::CipherError
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
def search(pattern)
|
32
|
+
backup = groups.detect { |g| g.name == "Backup" }
|
33
|
+
backup_group_id = backup && backup.group_id
|
34
|
+
entries.select { |e| e.group_id != backup_group_id && e.title =~ /#{pattern}/i }
|
35
|
+
end
|
36
|
+
|
37
|
+
def valid?
|
38
|
+
@header.valid?
|
39
|
+
end
|
40
|
+
|
41
|
+
def decrypt_payload
|
42
|
+
@payload = AESCrypt.decrypt(@encrypted_payload, @final_key, header.encryption_iv, 'AES-256-CBC')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# One entry: [FIELDTYPE(FT)][FIELDSIZE(FS)][FIELDDATA(FD)]
|
2
|
+
# [FT+FS+(FD)][FT+FS+(FD)][FT+FS+(FD)][FT+FS+(FD)][FT+FS+(FD)]...
|
3
|
+
|
4
|
+
# [ 2 bytes] FIELDTYPE
|
5
|
+
# [ 4 bytes] FIELDSIZE, size of FIELDDATA in bytes
|
6
|
+
# [ n bytes] FIELDDATA, n = FIELDSIZE
|
7
|
+
|
8
|
+
# Notes:
|
9
|
+
# - Strings are stored in UTF-8 encoded form and are null-terminated.
|
10
|
+
# - FIELDTYPE can be one of the following identifiers:
|
11
|
+
# * 0000: Invalid or comment block, block is ignored
|
12
|
+
# * 0001: UUID, uniquely identifying an entry, FIELDSIZE must be 16
|
13
|
+
# * 0002: Group ID, identifying the group of the entry, FIELDSIZE = 4
|
14
|
+
# It can be any 32-bit value except 0 and 0xFFFFFFFF
|
15
|
+
# * 0003: Image ID, identifying the image/icon of the entry, FIELDSIZE = 4
|
16
|
+
# * 0004: Title of the entry, FIELDDATA is an UTF-8 encoded string
|
17
|
+
# * 0005: URL string, FIELDDATA is an UTF-8 encoded string
|
18
|
+
# * 0006: UserName string, FIELDDATA is an UTF-8 encoded string
|
19
|
+
# * 0007: Password string, FIELDDATA is an UTF-8 encoded string
|
20
|
+
# * 0008: Notes string, FIELDDATA is an UTF-8 encoded string
|
21
|
+
# * 0009: Creation time, FIELDSIZE = 5, FIELDDATA = packed date/time
|
22
|
+
# * 000A: Last modification time, FIELDSIZE = 5, FIELDDATA = packed date/time
|
23
|
+
# * 000B: Last access time, FIELDSIZE = 5, FIELDDATA = packed date/time
|
24
|
+
# * 000C: Expiration time, FIELDSIZE = 5, FIELDDATA = packed date/time
|
25
|
+
# * 000D: Binary description UTF-8 encoded string
|
26
|
+
# * 000E: Binary data
|
27
|
+
# * FFFF: Entry terminator, FIELDSIZE must be 0
|
28
|
+
# '''
|
29
|
+
|
30
|
+
module Keepassx
|
31
|
+
class Entry
|
32
|
+
def self.extract_from_payload(header, payload_io)
|
33
|
+
groups = []
|
34
|
+
header.nentries.times do
|
35
|
+
group = Entry.new(payload_io)
|
36
|
+
groups << group
|
37
|
+
end
|
38
|
+
groups
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :fields
|
42
|
+
|
43
|
+
def initialize(payload_io)
|
44
|
+
fields = []
|
45
|
+
begin
|
46
|
+
field = EntryField.new(payload_io)
|
47
|
+
fields << field
|
48
|
+
end while not field.terminator?
|
49
|
+
|
50
|
+
@fields = fields
|
51
|
+
end
|
52
|
+
|
53
|
+
def length
|
54
|
+
@fields.map(&:length).reduce(&:+)
|
55
|
+
end
|
56
|
+
|
57
|
+
def notes
|
58
|
+
@fields.detect { |field| field.name == 'notes' }.data.chomp("\000")
|
59
|
+
end
|
60
|
+
|
61
|
+
def password
|
62
|
+
@fields.detect { |field| field.name == 'password' }.data.chomp("\000")
|
63
|
+
end
|
64
|
+
|
65
|
+
def title
|
66
|
+
@fields.detect { |field| field.name == 'title' }.data.chomp("\000")
|
67
|
+
end
|
68
|
+
|
69
|
+
def username
|
70
|
+
@fields.detect { |field| field.name == 'username' }.data.chomp("\000")
|
71
|
+
end
|
72
|
+
|
73
|
+
def group_id
|
74
|
+
@fields.detect { |field| field.name == 'groupid' }.data
|
75
|
+
end
|
76
|
+
|
77
|
+
def inspect
|
78
|
+
"Entry<title=#{title.inspect}, username=[FILTERED], password=[FILTERED], notes=#{notes.inspect}>"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Keepassx
|
2
|
+
class EntryField
|
3
|
+
FIELD_TYPES = [
|
4
|
+
[0x0, 'ignored', :null],
|
5
|
+
[0x1, 'uuid', :ascii],
|
6
|
+
[0x2, 'groupid', :int],
|
7
|
+
[0x3, 'imageid', :int],
|
8
|
+
[0x4, 'title', :string],
|
9
|
+
[0x5, 'url', :string],
|
10
|
+
[0x6, 'username', :string],
|
11
|
+
[0x7, 'password', :string],
|
12
|
+
[0x8, 'notes', :string],
|
13
|
+
[0x9, 'creation_time', :date],
|
14
|
+
[0xa, 'last_mod_time', :date],
|
15
|
+
[0xb, 'last_acc_time', :date],
|
16
|
+
[0xc, 'expiration_time', :date],
|
17
|
+
[0xd, 'binary_desc', :string],
|
18
|
+
[0xe, 'binary_data', :shunt],
|
19
|
+
[0xFFFF, 'terminator', :nil]
|
20
|
+
]
|
21
|
+
FIELD_TERMINATOR = 0xFFFF
|
22
|
+
TYPE_CODE_FIELD_SIZE = 2 # unsigned short integer
|
23
|
+
DATA_LENGTH_FIELD_SIZE = 4 # unsigned integer
|
24
|
+
|
25
|
+
|
26
|
+
attr_reader :name, :data_type, :data
|
27
|
+
|
28
|
+
def initialize(payload)
|
29
|
+
type_code, @data_length = payload.read(TYPE_CODE_FIELD_SIZE + DATA_LENGTH_FIELD_SIZE).unpack('SI')
|
30
|
+
@name, @data_type = _parse_type_code(type_code)
|
31
|
+
@data = payload.read(@data_length)
|
32
|
+
end
|
33
|
+
|
34
|
+
def terminator?
|
35
|
+
name == 'terminator'
|
36
|
+
end
|
37
|
+
|
38
|
+
def length
|
39
|
+
TYPE_CODE_FIELD_SIZE + DATA_LENGTH_FIELD_SIZE + @data_length
|
40
|
+
end
|
41
|
+
|
42
|
+
def _parse_type_code(type_code)
|
43
|
+
(_, name, data_type) = FIELD_TYPES.detect do |(code, *rest)|
|
44
|
+
code == type_code
|
45
|
+
end
|
46
|
+
[name, data_type]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# One group: [FIELDTYPE(FT)][FIELDSIZE(FS)][FIELDDATA(FD)]
|
2
|
+
# [FT+FS+(FD)][FT+FS+(FD)][FT+FS+(FD)][FT+FS+(FD)][FT+FS+(FD)]...
|
3
|
+
#
|
4
|
+
# [ 2 bytes] FIELDTYPE
|
5
|
+
# [ 4 bytes] FIELDSIZE, size of FIELDDATA in bytes
|
6
|
+
# [ n bytes] FIELDDATA, n = FIELDSIZE
|
7
|
+
#
|
8
|
+
# Notes:
|
9
|
+
# - Strings are stored in UTF-8 encoded form and are null-terminated.
|
10
|
+
# - FIELDTYPE can be one of the following identifiers:
|
11
|
+
# * 0000: Invalid or comment block, block is ignored
|
12
|
+
# * 0001: Group ID, FIELDSIZE must be 4 bytes
|
13
|
+
# It can be any 32-bit value except 0 and 0xFFFFFFFF
|
14
|
+
# * 0002: Group name, FIELDDATA is an UTF-8 encoded string
|
15
|
+
# * 0003: Creation time, FIELDSIZE = 5, FIELDDATA = packed date/time
|
16
|
+
# * 0004: Last modification time, FIELDSIZE = 5, FIELDDATA = packed date/time
|
17
|
+
# * 0005: Last access time, FIELDSIZE = 5, FIELDDATA = packed date/time
|
18
|
+
# * 0006: Expiration time, FIELDSIZE = 5, FIELDDATA = packed date/time
|
19
|
+
# * 0007: Image ID, FIELDSIZE must be 4 bytes
|
20
|
+
# * 0008: Level, FIELDSIZE = 2
|
21
|
+
# * 0009: Flags, 32-bit value, FIELDSIZE = 4
|
22
|
+
# * FFFF: Group entry terminator, FIELDSIZE must be 0
|
23
|
+
module Keepassx
|
24
|
+
class Group
|
25
|
+
def self.extract_from_payload(header, payload_io)
|
26
|
+
groups = []
|
27
|
+
header.ngroups.times do
|
28
|
+
group = Group.new(payload_io)
|
29
|
+
groups << group
|
30
|
+
end
|
31
|
+
groups
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(payload_io)
|
35
|
+
fields = []
|
36
|
+
begin
|
37
|
+
field = GroupField.new(payload_io)
|
38
|
+
fields << field
|
39
|
+
end while not field.terminator?
|
40
|
+
|
41
|
+
@fields = fields
|
42
|
+
end
|
43
|
+
|
44
|
+
def length
|
45
|
+
@fields.map(&:length).reduce(&:+)
|
46
|
+
end
|
47
|
+
|
48
|
+
def group_id
|
49
|
+
@fields.detect { |field| field.name == 'groupid' }.data
|
50
|
+
end
|
51
|
+
|
52
|
+
def name
|
53
|
+
@fields.detect { |field| field.name == 'group_name' }.data.chomp("\000")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Keepassx
|
2
|
+
class GroupField
|
3
|
+
FIELD_TYPES = [
|
4
|
+
[0x0, 'ignored', :null],
|
5
|
+
[0x1, 'groupid', :int],
|
6
|
+
[0x2, 'group_name', :string],
|
7
|
+
[0x3, 'creation_time', :date],
|
8
|
+
[0x4, 'lastmod_time', :date],
|
9
|
+
[0x5, 'lastacc_time', :date],
|
10
|
+
[0x6, 'expire_time', :date],
|
11
|
+
[0x7, 'imageid', :int],
|
12
|
+
[0x8, 'level', :short],
|
13
|
+
[0x9, 'flags', :int],
|
14
|
+
[0xFFFF, 'terminator', :null]
|
15
|
+
]
|
16
|
+
FIELD_TERMINATOR = 0xFFFF
|
17
|
+
TYPE_CODE_FIELD_SIZE = 2 # unsigned short integer
|
18
|
+
DATA_LENGTH_FIELD_SIZE = 4 # unsigned integer
|
19
|
+
|
20
|
+
|
21
|
+
attr_reader :name, :data_type, :data
|
22
|
+
|
23
|
+
def initialize(payload)
|
24
|
+
type_code, @data_length = payload.read(TYPE_CODE_FIELD_SIZE + DATA_LENGTH_FIELD_SIZE).unpack('SI')
|
25
|
+
@name, @data_type = _parse_type_code(type_code)
|
26
|
+
@data = payload.read(@data_length)
|
27
|
+
end
|
28
|
+
|
29
|
+
def terminator?
|
30
|
+
name == 'terminator'
|
31
|
+
end
|
32
|
+
|
33
|
+
def length
|
34
|
+
TYPE_CODE_FIELD_SIZE + DATA_LENGTH_FIELD_SIZE + @data_length
|
35
|
+
end
|
36
|
+
|
37
|
+
def _parse_type_code(type_code)
|
38
|
+
(_, name, data_type) = FIELD_TYPES.detect do |(code, *rest)|
|
39
|
+
code == type_code
|
40
|
+
end
|
41
|
+
[name, data_type]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# The keepass file header.
|
2
|
+
#
|
3
|
+
# From the KeePass doc:
|
4
|
+
#
|
5
|
+
# Database header: [DBHDR]
|
6
|
+
#
|
7
|
+
# [ 4 bytes] DWORD dwSignature1 = 0x9AA2D903
|
8
|
+
# [ 4 bytes] DWORD dwSignature2 = 0xB54BFB65
|
9
|
+
# [ 4 bytes] DWORD dwFlags
|
10
|
+
# [ 4 bytes] DWORD dwVersion { Ve.Ve.Mj.Mj:Mn.Mn.Bl.Bl }
|
11
|
+
# [16 bytes] BYTE{16} aMasterSeed
|
12
|
+
# [16 bytes] BYTE{16} aEncryptionIV
|
13
|
+
# [ 4 bytes] DWORD dwGroups Number of groups in database
|
14
|
+
# [ 4 bytes] DWORD dwEntries Number of entries in database
|
15
|
+
# [32 bytes] BYTE{32} aContentsHash SHA-256 hash value of the plain contents
|
16
|
+
# [32 bytes] BYTE{32} aMasterSeed2 Used for the dwKeyEncRounds AES
|
17
|
+
# master key transformations
|
18
|
+
# [ 4 bytes] DWORD dwKeyEncRounds See above; number of transformations
|
19
|
+
#
|
20
|
+
# Notes:
|
21
|
+
#
|
22
|
+
# - dwFlags is a bitmap, which can include:
|
23
|
+
# * PWM_FLAG_SHA2 (1) for SHA-2.
|
24
|
+
# * PWM_FLAG_RIJNDAEL (2) for AES (Rijndael).
|
25
|
+
# * PWM_FLAG_ARCFOUR (4) for ARC4.
|
26
|
+
# * PWM_FLAG_TWOFISH (8) for Twofish.
|
27
|
+
# - aMasterSeed is a salt that gets hashed with the transformed user master key
|
28
|
+
# to form the final database data encryption/decryption key.
|
29
|
+
# * FinalKey = SHA-256(aMasterSeed, TransformedUserMasterKey)
|
30
|
+
# - aEncryptionIV is the initialization vector used by AES/Twofish for
|
31
|
+
# encrypting/decrypting the database data.
|
32
|
+
# - aContentsHash: "plain contents" refers to the database file, minus the
|
33
|
+
# database header, decrypted by FinalKey.
|
34
|
+
# * PlainContents = Decrypt_with_FinalKey(DatabaseFile - DatabaseHeader)
|
35
|
+
module Keepassx
|
36
|
+
class Header
|
37
|
+
|
38
|
+
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
|
61
|
+
end
|
62
|
+
|
63
|
+
def valid?
|
64
|
+
@signature1 == 0x9AA2D903 && @signature2 == 0xB54BFB65
|
65
|
+
end
|
66
|
+
|
67
|
+
def encryption_type
|
68
|
+
ENCRYPTION_FLAGS.each do |(flag_mask, encryption_type)|
|
69
|
+
return encryption_type if @flags & flag_mask
|
70
|
+
end
|
71
|
+
'Unknown'
|
72
|
+
end
|
73
|
+
|
74
|
+
def final_key(master_key)
|
75
|
+
key = Digest::SHA2.new.update(master_key).digest
|
76
|
+
aes = FastAES.new(@master_seed2)
|
77
|
+
|
78
|
+
@rounds.times do |i|
|
79
|
+
key = aes.encrypt(key)
|
80
|
+
end
|
81
|
+
|
82
|
+
key = Digest::SHA2.new.update(key).digest
|
83
|
+
key = Digest::SHA2.new.update(@master_seed + key).digest
|
84
|
+
key
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Keepassx::Database do
|
4
|
+
describe 'self.open' do
|
5
|
+
it "creates a new instance of the databse with the file" do
|
6
|
+
db = Keepassx::Database.open(TEST_DATABASE_PATH)
|
7
|
+
db.should_not be_nil
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "unlock" do
|
12
|
+
before :each do
|
13
|
+
@db = Keepassx::Database.open(TEST_DATABASE_PATH)
|
14
|
+
@db.should be_valid
|
15
|
+
end
|
16
|
+
|
17
|
+
it "returns true when the master password is correct" do
|
18
|
+
@db.unlock('testmasterpassword').should be_true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns false when the master password is incorrect" do
|
22
|
+
@db.unlock('bad password').should be_false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "an unlocked database" do
|
27
|
+
before :each do
|
28
|
+
@db = Keepassx::Database.open(TEST_DATABASE_PATH)
|
29
|
+
@db.unlock('testmasterpassword')
|
30
|
+
end
|
31
|
+
|
32
|
+
it "can find entries by their title" do
|
33
|
+
@db.entry("test entry").password.should == "testpassword"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "can find groups" do
|
37
|
+
@db.groups.map(&:name).sort.should == ["Backup", "Internet", "eMail"]
|
38
|
+
end
|
39
|
+
|
40
|
+
it "can search for entries" do
|
41
|
+
entries = @db.search "test"
|
42
|
+
entries.first.title.should == "test entry"
|
43
|
+
end
|
44
|
+
|
45
|
+
it "can search for entries case-insensitively" do
|
46
|
+
entries = @db.search "TEST"
|
47
|
+
entries.first.title.should == "test entry"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "will find the current values of entries with history" do
|
51
|
+
entries = @db.search "entry2"
|
52
|
+
entries.size.should == 1
|
53
|
+
entries.first.title.should == "entry2"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/spec/spec_helper.rb
ADDED
Binary file
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: keepassx
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Tony Pitluga
|
13
|
+
- Paul Hinze
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-10-14 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: fast-aes
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
- 1
|
31
|
+
version: "0.1"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rspec
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 2
|
43
|
+
- 11
|
44
|
+
- 0
|
45
|
+
version: 2.11.0
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
description: See http://github.com/pitluga/keepassx
|
49
|
+
email:
|
50
|
+
- tony.pitluga@gmail.com
|
51
|
+
- paul.t.hinze@gmail.com
|
52
|
+
executables: []
|
53
|
+
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
extra_rdoc_files: []
|
57
|
+
|
58
|
+
files:
|
59
|
+
- .rvmrc
|
60
|
+
- .travis.yml
|
61
|
+
- Gemfile
|
62
|
+
- Gemfile.lock
|
63
|
+
- README.md
|
64
|
+
- Rakefile
|
65
|
+
- keepassx.gemspec
|
66
|
+
- lib/keepassx.rb
|
67
|
+
- lib/keepassx/aes_crypt.rb
|
68
|
+
- lib/keepassx/database.rb
|
69
|
+
- lib/keepassx/entry.rb
|
70
|
+
- lib/keepassx/entry_field.rb
|
71
|
+
- lib/keepassx/group.rb
|
72
|
+
- lib/keepassx/group_field.rb
|
73
|
+
- lib/keepassx/header.rb
|
74
|
+
- spec/keepassx/database_spec.rb
|
75
|
+
- spec/spec_helper.rb
|
76
|
+
- spec/test_database.kdb
|
77
|
+
has_rdoc: true
|
78
|
+
homepage: http://github.com/pitluga/keepassx
|
79
|
+
licenses: []
|
80
|
+
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
segments:
|
98
|
+
- 0
|
99
|
+
version: "0"
|
100
|
+
requirements: []
|
101
|
+
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 1.3.6
|
104
|
+
signing_key:
|
105
|
+
specification_version: 3
|
106
|
+
summary: Ruby API access for KeePassX databases
|
107
|
+
test_files: []
|
108
|
+
|