setec_astronomy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.8.7-p249@setec_astronomy
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,32 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ setec_astronomy (0.1.0)
5
+ clipboard (~> 0.9)
6
+ fast-aes (~> 0.1)
7
+ highline (~> 1.6)
8
+ thor (~> 0.14)
9
+
10
+ GEM
11
+ remote: http://rubygems.org/
12
+ specs:
13
+ clipboard (0.9.9)
14
+ diff-lcs (1.1.3)
15
+ fast-aes (0.1.1)
16
+ highline (1.6.2)
17
+ rspec (2.6.0)
18
+ rspec-core (~> 2.6.0)
19
+ rspec-expectations (~> 2.6.0)
20
+ rspec-mocks (~> 2.6.0)
21
+ rspec-core (2.6.4)
22
+ rspec-expectations (2.6.0)
23
+ diff-lcs (~> 1.1.2)
24
+ rspec-mocks (2.6.0)
25
+ thor (0.14.6)
26
+
27
+ PLATFORMS
28
+ ruby
29
+
30
+ DEPENDENCIES
31
+ rspec (= 2.6.0)
32
+ setec_astronomy!
data/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # setec astronomy
2
+
3
+ <img src="http://www.cyberpunkreview.com/wp-content/uploads/toomanysecrets.gif" />
4
+
5
+ # almost ready for initial release!
6
+
7
+ a `setec` command is included with two basic commands:
8
+
9
+ `setec search PATTERN -f /path/to/passwords.kdb` will search your password database and output matching entries
10
+
11
+ `setec copy ENTRY_TITLE -f /path/to/passwords.kdb` will copy the password for the entry you specify straight into the clipboard
12
+
13
+ with no options, the master password is requested on the console. add the `-g` option to have the password prompt be an applescript-based gui dialog. this is especially useful when using this library for alfred integration.
14
+
15
+ an example alfred extension is included at `alfred/setec.alfredextension` - it assumes that you have this library checked out in `$HOME/dev/setec_astronomy` and all of the required gems installed in the gemset. you need the alfred powerpack to install the extension. (it's pretty easy to take a look at the command used by the extension and modify it.) assuming you get this all set up properly, you can type "stc test entry" into alfred and--if you type the master password properly--see the test password gets copied into your clipboard.
16
+
17
+ # security warning
18
+
19
+ no attempt is made to protect the memory used by this library; there may be something we can do with libgcrypt's secure-malloc functions, but right now your master password is unencrypted in ram that could possibly be paged to disk.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'rake'
2
+ require 'rspec/core/rake_task'
3
+
4
+ task :default => :spec
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
Binary file
data/bin/setec ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ $:.unshift File.expand_path('../../lib', __FILE__)
5
+
6
+ require 'setec_astronomy/cli'
7
+
8
+ SetecAstronomy::CLI.start
@@ -0,0 +1,15 @@
1
+ require 'base64'
2
+ require 'stringio'
3
+ require 'openssl'
4
+ require 'digest/sha2'
5
+
6
+ require 'rubygems'
7
+ require 'fast-aes'
8
+
9
+ require 'setec_astronomy/kee_pass/database'
10
+ require 'setec_astronomy/kee_pass/entry'
11
+ require 'setec_astronomy/kee_pass/entry_field'
12
+ require 'setec_astronomy/kee_pass/group'
13
+ require 'setec_astronomy/kee_pass/group_field'
14
+ require 'setec_astronomy/kee_pass/header'
15
+ require 'setec_astronomy/aes_crypt'
@@ -0,0 +1,19 @@
1
+ module SetecAstronomy
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,43 @@
1
+ require 'setec_astronomy'
2
+ require 'setec_astronomy/prompt'
3
+
4
+ require 'thor'
5
+ require 'clipboard'
6
+
7
+ module SetecAstronomy
8
+ class CLI < Thor
9
+ desc "search PATTERN", "searches the database for entries matching the pattern"
10
+ method_option :file, :type => :string, :required => true, :aliases => '-f'
11
+ method_option :gui, :type => :boolean, :aliases => '-g'
12
+ def search(pattern)
13
+ keepass = database(options[:file], options[:gui])
14
+ keepass.search(pattern).each do |match|
15
+ puts "#{match.title} - #{match.notes}"
16
+ end
17
+ end
18
+
19
+ desc "copy ENTRY", "copies the password for the given entry to the system clipboard"
20
+ method_option :file, :type => :string, :required => true, :aliases => '-f'
21
+ method_option :gui, :type => :boolean, :aliases => '-g'
22
+ def copy(title)
23
+ keepass = database(options[:file], options[:gui])
24
+ entry = keepass.entry(title)
25
+ resign("#{title} not found") if entry.nil?
26
+ Clipboard.copy entry.password
27
+ end
28
+
29
+ no_tasks do
30
+ def database(file, gui=false)
31
+ db = KeePass::Database.open(file)
32
+ password = gui ? Prompt.ask_password_gui : Prompt.ask_password_console
33
+ resign("Unable to unlock database... exiting") unless db.unlock password
34
+ db
35
+ end
36
+
37
+ def resign(error)
38
+ puts error
39
+ exit 1
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,44 @@
1
+ module SetecAstronomy
2
+ module KeePass
3
+ class Database
4
+
5
+ attr_reader :header, :groups, :entries
6
+
7
+ def self.open(path)
8
+ self.new(File.read(path))
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
+ entries.select { |e| e.title =~ /#{pattern}/ }
33
+ end
34
+
35
+ def valid?
36
+ @header.valid?
37
+ end
38
+
39
+ def decrypt_payload
40
+ @payload = AESCrypt.decrypt(@encrypted_payload, @final_key, header.encryption_iv, 'AES-256-CBC')
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,75 @@
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 SetecAstronomy
31
+ module KeePass
32
+ class Entry
33
+ def self.extract_from_payload(header, payload_io)
34
+ groups = []
35
+ header.nentries.times do
36
+ group = Entry.new(payload_io)
37
+ groups << group
38
+ end
39
+ groups
40
+ end
41
+
42
+ attr_reader :fields
43
+
44
+ def initialize(payload_io)
45
+ fields = []
46
+ begin
47
+ field = EntryField.new(payload_io)
48
+ fields << field
49
+ end while not field.terminator?
50
+
51
+ @fields = fields
52
+ end
53
+
54
+ def length
55
+ @fields.map(&:length).reduce(&:+)
56
+ end
57
+
58
+ def notes
59
+ @fields.detect { |field| field.name == 'notes' }.data
60
+ end
61
+
62
+ def password
63
+ @fields.detect { |field| field.name == 'password' }.data.chomp("\000")
64
+ end
65
+
66
+ def title
67
+ @fields.detect { |field| field.name == 'title' }.data.chomp("\000")
68
+ end
69
+
70
+ def username
71
+ @fields.detect { |field| field.name == 'username' }.data
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,51 @@
1
+ module SetecAstronomy
2
+ module KeePass
3
+ class EntryField
4
+ FIELD_TYPES = [
5
+ [0x0, 'ignored', :null],
6
+ [0x1, 'uuid', :ascii],
7
+ [0x2, 'groupid', :int],
8
+ [0x3, 'imageid', :int],
9
+ [0x4, 'title', :string],
10
+ [0x5, 'url', :string],
11
+ [0x6, 'username', :string],
12
+ [0x7, 'password', :string],
13
+ [0x8, 'notes', :string],
14
+ [0x9, 'creation_time', :date],
15
+ [0xa, 'last_mod_time', :date],
16
+ [0xb, 'last_acc_time', :date],
17
+ [0xc, 'expiration_time', :date],
18
+ [0xd, 'binary_desc', :string],
19
+ [0xe, 'binary_data', :shunt],
20
+ [0xFFFF, 'terminator', :nil]
21
+ ]
22
+ FIELD_TERMINATOR = 0xFFFF
23
+ TYPE_CODE_FIELD_SIZE = 2 # unsigned short integer
24
+ DATA_LENGTH_FIELD_SIZE = 4 # unsigned integer
25
+
26
+
27
+ attr_reader :name, :data_type, :data
28
+
29
+ def initialize(payload)
30
+ type_code, @data_length = payload.read(TYPE_CODE_FIELD_SIZE + DATA_LENGTH_FIELD_SIZE).unpack('SI')
31
+ @name, @data_type = _parse_type_code(type_code)
32
+ @data = payload.read(@data_length)
33
+ end
34
+
35
+ def terminator?
36
+ name == 'terminator'
37
+ end
38
+
39
+ def length
40
+ TYPE_CODE_FIELD_SIZE + DATA_LENGTH_FIELD_SIZE + @data_length
41
+ end
42
+
43
+ def _parse_type_code(type_code)
44
+ (_, name, data_type) = FIELD_TYPES.detect do |(code, *rest)|
45
+ code == type_code
46
+ end
47
+ [name, data_type]
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,54 @@
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 SetecAstronomy
24
+ module KeePass
25
+ class Group
26
+ def self.extract_from_payload(header, payload_io)
27
+ groups = []
28
+ header.ngroups.times do
29
+ group = Group.new(payload_io)
30
+ groups << group
31
+ end
32
+ groups
33
+ end
34
+
35
+ def initialize(payload_io)
36
+ fields = []
37
+ begin
38
+ field = GroupField.new(payload_io)
39
+ fields << field
40
+ end while not field.terminator?
41
+
42
+ @fields = fields
43
+ end
44
+
45
+ def length
46
+ @fields.map(&:length).reduce(&:+)
47
+ end
48
+
49
+ def name
50
+ @fields.detect { |field| field.name == 'group_name' }.data.chomp("\000")
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,46 @@
1
+ module SetecAstronomy
2
+ module KeePass
3
+ class GroupField
4
+ FIELD_TYPES = [
5
+ [0x0, 'ignored', :null],
6
+ [0x1, 'groupid', :int],
7
+ [0x2, 'group_name', :string],
8
+ [0x3, 'creation_time', :date],
9
+ [0x4, 'lastmod_time', :date],
10
+ [0x5, 'lastacc_time', :date],
11
+ [0x6, 'expire_time', :date],
12
+ [0x7, 'imageid', :int],
13
+ [0x8, 'level', :short],
14
+ [0x9, 'flags', :int],
15
+ [0xFFFF, 'terminator', :null]
16
+ ]
17
+ FIELD_TERMINATOR = 0xFFFF
18
+ TYPE_CODE_FIELD_SIZE = 2 # unsigned short integer
19
+ DATA_LENGTH_FIELD_SIZE = 4 # unsigned integer
20
+
21
+
22
+ attr_reader :name, :data_type, :data
23
+
24
+ def initialize(payload)
25
+ type_code, @data_length = payload.read(TYPE_CODE_FIELD_SIZE + DATA_LENGTH_FIELD_SIZE).unpack('SI')
26
+ @name, @data_type = _parse_type_code(type_code)
27
+ @data = payload.read(@data_length)
28
+ end
29
+
30
+ def terminator?
31
+ name == 'terminator'
32
+ end
33
+
34
+ def length
35
+ TYPE_CODE_FIELD_SIZE + DATA_LENGTH_FIELD_SIZE + @data_length
36
+ end
37
+
38
+ def _parse_type_code(type_code)
39
+ (_, name, data_type) = FIELD_TYPES.detect do |(code, *rest)|
40
+ code == type_code
41
+ end
42
+ [name, data_type]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,89 @@
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 SetecAstronomy
36
+ module KeePass
37
+ class Header
38
+
39
+ ENCRYPTION_FLAGS = [
40
+ [1 , 'SHA2' ],
41
+ [2 , 'Rijndael'],
42
+ [2 , 'AES' ],
43
+ [4 , 'ArcFour' ],
44
+ [8 , 'TwoFish' ]
45
+ ]
46
+
47
+ attr_reader :encryption_iv
48
+ attr_reader :ngroups, :nentries
49
+
50
+ def initialize(header_bytes)
51
+ @signature1 = header_bytes[0..4].unpack('L*').first
52
+ @signature2 = header_bytes[4..8].unpack('L*').first
53
+ @flags = header_bytes[8..12].unpack('L*').first
54
+ @version = header_bytes[12..16].unpack('L*').first
55
+ @master_seed = header_bytes[16...32]
56
+ @encryption_iv = header_bytes[32...48]
57
+ @ngroups = header_bytes[48..52].unpack('L*').first
58
+ @nentries = header_bytes[52..56].unpack('L*').first
59
+ @contents_hash = header_bytes[56..88]
60
+ @master_seed2 = header_bytes[88...120]
61
+ @rounds = header_bytes[120..-1].unpack('L*').first
62
+ end
63
+
64
+ def valid?
65
+ @signature1 == 0x9AA2D903 && @signature2 == 0xB54BFB65
66
+ end
67
+
68
+ def encryption_type
69
+ ENCRYPTION_FLAGS.each do |(flag_mask, encryption_type)|
70
+ return encryption_type if @flags & flag_mask
71
+ end
72
+ 'Unknown'
73
+ end
74
+
75
+ def final_key(master_key)
76
+ key = Digest::SHA2.new.update(master_key).digest
77
+ aes = FastAES.new(@master_seed2)
78
+
79
+ @rounds.times do |i|
80
+ key = aes.encrypt(key)
81
+ end
82
+
83
+ key = Digest::SHA2.new.update(key).digest
84
+ key = Digest::SHA2.new.update(@master_seed + key).digest
85
+ key
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,28 @@
1
+ require 'highline/import'
2
+
3
+ module Prompt
4
+ def self.ask_password_console
5
+ ask("Password: ") { |q| q.echo = false }
6
+ end
7
+
8
+ def self.ask_password_gui
9
+ `#{_password_dialog_actionscript}`.chomp
10
+ end
11
+
12
+ def self._password_dialog_actionscript
13
+ <<-APPLESCRIPT.gsub(/^ */, '')
14
+ /usr/bin/osascript <<EOT
15
+ tell application "Finder"
16
+ activate
17
+ set output to text returned of ( \
18
+ display dialog "Enter your master password:" \
19
+ with title "KeePass Database Master Password" \
20
+ default answer "" \
21
+ with hidden answer \
22
+ with icon (path to "apps") as Unicode text & "Utilities:Keychain Access.app:Contents:Resources:Keychain.icns" as alias \
23
+ )
24
+ end tell
25
+ EOT
26
+ APPLESCRIPT
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "setec_astronomy"
3
+ s.summary = "Command line and API access to KeePassX databases"
4
+ s.description = "See http://github.com/pitluga/setec_astronomy"
5
+ s.version = "0.1.0"
6
+ s.author = "Tony Pitluga"
7
+ s.email = "tony.pitluga@gmail.com"
8
+ s.homepage = "http://github.com/pitluga/supply_drop"
9
+ s.files = `git ls-files`.split("\n")
10
+ s.bindir = "bin"
11
+ s.executables = ["setec"]
12
+
13
+ s.add_dependency "clipboard", "~> 0.9"
14
+ s.add_dependency "fast-aes", "~> 0.1"
15
+ s.add_dependency "highline", "~> 1.6"
16
+ s.add_dependency "thor", "~> 0.14"
17
+
18
+ s.add_development_dependency "rspec", "2.6.0"
19
+ end
data/spec/cli_spec.rb ADDED
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+ require 'setec_astronomy/cli'
3
+ require 'pty'
4
+ require 'expect'
5
+
6
+ describe "Command Line" do
7
+ def setec(options, master_password = nil)
8
+ bin = File.expand_path('../../bin/setec', __FILE__)
9
+ cmd = "#{bin} #{options} --file=#{TEST_DATABASE_PATH}"
10
+ output = ''
11
+ PTY.spawn cmd do |reader, writer, pid|
12
+ reader.expect("Password:") do
13
+ unless master_password.nil?
14
+ writer.puts master_password
15
+ reader.gets
16
+ end
17
+ end
18
+ until reader.eof?
19
+ output << reader.gets
20
+ end
21
+ end
22
+ output
23
+ end
24
+
25
+ describe "search" do
26
+ it "lists the entries that contain the given text" do
27
+ output = setec 'search test', "testmasterpassword"
28
+ output.should include("test entry")
29
+ end
30
+ end
31
+
32
+ describe "copy" do
33
+ it "copies the given password to the system clipboard" do
34
+ setec 'copy "test entry"', "testmasterpassword"
35
+ Clipboard.paste.should == "testpassword"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe SetecAstronomy::KeePass::Database do
4
+ describe 'self.open' do
5
+ it "creates a new instance of the databse with the file" do
6
+ db = SetecAstronomy::KeePass::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 = SetecAstronomy::KeePass::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 = SetecAstronomy::KeePass::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 == ["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
+ end
45
+ end
@@ -0,0 +1,6 @@
1
+ $:.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'setec_astronomy'
3
+ require 'rubygems'
4
+ require 'rspec'
5
+
6
+ TEST_DATABASE_PATH = File.expand_path('../test_database.kdb', __FILE__)
Binary file
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: setec_astronomy
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Tony Pitluga
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-12-09 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: clipboard
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 25
29
+ segments:
30
+ - 0
31
+ - 9
32
+ version: "0.9"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: fast-aes
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 9
44
+ segments:
45
+ - 0
46
+ - 1
47
+ version: "0.1"
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: highline
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ~>
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 1
61
+ - 6
62
+ version: "1.6"
63
+ type: :runtime
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: thor
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ~>
72
+ - !ruby/object:Gem::Version
73
+ hash: 23
74
+ segments:
75
+ - 0
76
+ - 14
77
+ version: "0.14"
78
+ type: :runtime
79
+ version_requirements: *id004
80
+ - !ruby/object:Gem::Dependency
81
+ name: rspec
82
+ prerelease: false
83
+ requirement: &id005 !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - "="
87
+ - !ruby/object:Gem::Version
88
+ hash: 23
89
+ segments:
90
+ - 2
91
+ - 6
92
+ - 0
93
+ version: 2.6.0
94
+ type: :development
95
+ version_requirements: *id005
96
+ description: See http://github.com/pitluga/setec_astronomy
97
+ email: tony.pitluga@gmail.com
98
+ executables:
99
+ - setec
100
+ extensions: []
101
+
102
+ extra_rdoc_files: []
103
+
104
+ files:
105
+ - .rvmrc
106
+ - Gemfile
107
+ - Gemfile.lock
108
+ - README.md
109
+ - Rakefile
110
+ - alfred/setec.alfredextension
111
+ - bin/setec
112
+ - lib/setec_astronomy.rb
113
+ - lib/setec_astronomy/aes_crypt.rb
114
+ - lib/setec_astronomy/cli.rb
115
+ - lib/setec_astronomy/kee_pass/database.rb
116
+ - lib/setec_astronomy/kee_pass/entry.rb
117
+ - lib/setec_astronomy/kee_pass/entry_field.rb
118
+ - lib/setec_astronomy/kee_pass/group.rb
119
+ - lib/setec_astronomy/kee_pass/group_field.rb
120
+ - lib/setec_astronomy/kee_pass/header.rb
121
+ - lib/setec_astronomy/prompt.rb
122
+ - setec_astronomy.gemspec
123
+ - spec/cli_spec.rb
124
+ - spec/kee_pass/database_spec.rb
125
+ - spec/spec_helper.rb
126
+ - spec/test_database.kdb
127
+ homepage: http://github.com/pitluga/supply_drop
128
+ licenses: []
129
+
130
+ post_install_message:
131
+ rdoc_options: []
132
+
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ none: false
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ hash: 3
141
+ segments:
142
+ - 0
143
+ version: "0"
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ hash: 3
150
+ segments:
151
+ - 0
152
+ version: "0"
153
+ requirements: []
154
+
155
+ rubyforge_project:
156
+ rubygems_version: 1.8.10
157
+ signing_key:
158
+ specification_version: 3
159
+ summary: Command line and API access to KeePassX databases
160
+ test_files: []
161
+