setec_astronomy 0.1.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.
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
+