keyrack 0.3.0.pre → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +8 -15
  4. data/Guardfile +6 -0
  5. data/LICENSE.txt +4 -2
  6. data/Rakefile +2 -56
  7. data/keyrack.gemspec +22 -104
  8. data/lib/keyrack.rb +9 -1
  9. data/lib/keyrack/database.rb +64 -98
  10. data/lib/keyrack/event.rb +13 -0
  11. data/lib/keyrack/exceptions.rb +4 -0
  12. data/lib/keyrack/group.rb +225 -0
  13. data/lib/keyrack/migrator.rb +45 -0
  14. data/lib/keyrack/runner.rb +98 -44
  15. data/lib/keyrack/site.rb +92 -0
  16. data/lib/keyrack/ui/console.rb +234 -95
  17. data/lib/keyrack/utils.rb +0 -18
  18. data/lib/keyrack/version.rb +3 -0
  19. data/test/fixtures/database-3.dat +0 -0
  20. data/test/helper.rb +11 -1
  21. data/test/integration/test_interactive_console.rb +139 -0
  22. data/test/unit/store/test_filesystem.rb +21 -0
  23. data/test/unit/store/test_ssh.rb +32 -0
  24. data/test/unit/test_database.rb +201 -0
  25. data/test/unit/test_event.rb +41 -0
  26. data/test/unit/test_group.rb +703 -0
  27. data/test/unit/test_migrator.rb +105 -0
  28. data/test/unit/test_runner.rb +407 -0
  29. data/test/unit/test_site.rb +181 -0
  30. data/test/unit/test_store.rb +13 -0
  31. data/test/unit/test_utils.rb +8 -0
  32. data/test/unit/ui/test_console.rb +495 -0
  33. metadata +98 -94
  34. data/Gemfile.lock +0 -45
  35. data/TODO +0 -4
  36. data/VERSION +0 -1
  37. data/features/console.feature +0 -103
  38. data/features/step_definitions/keyrack_steps.rb +0 -61
  39. data/features/support/env.rb +0 -16
  40. data/test/fixtures/aes +0 -0
  41. data/test/fixtures/config.yml +0 -4
  42. data/test/fixtures/id_rsa +0 -54
  43. data/test/keyrack/store/test_filesystem.rb +0 -25
  44. data/test/keyrack/store/test_ssh.rb +0 -36
  45. data/test/keyrack/test_database.rb +0 -111
  46. data/test/keyrack/test_runner.rb +0 -111
  47. data/test/keyrack/test_store.rb +0 -15
  48. data/test/keyrack/test_utils.rb +0 -41
  49. data/test/keyrack/ui/test_console.rb +0 -308
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: efd84a41e075f8edc77908a7bb71ea21a3590af2
4
+ data.tar.gz: 3714b2f53884956c10b480ba128efcb58e463727
5
+ SHA512:
6
+ metadata.gz: d53d8a1ca8ce99b1147907d48d87a6e20a95efe4fcbe13af11d777e179ec294994b6aa903d4f189f27cda35bea060c7cb181e70d1f72f3ff9332b7fe799b9d85
7
+ data.tar.gz: e0d31fee61321d665a632ed2e59259778a37824ba19136834c3ece001ca3ef39798fcbe3605862e33a80bac26a1f6872cbbb2db1eb746cd9374c283f0cb9b56d
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile CHANGED
@@ -1,18 +1,11 @@
1
- source :rubygems
2
- # Add dependencies required to use your gem here.
3
- # Example:
4
- # gem "activesupport", ">= 2.3.5"
5
- gem 'net-scp', :require => 'net/scp'
6
- gem 'highline'
7
- gem 'copier'
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in keyrack.gemspec
4
+ gemspec
8
5
 
9
- # Add dependencies to develop your gem here.
10
- # Include everything needed to run rake, tests, features, etc.
11
6
  group :development do
12
- gem "bundler", "~> 1.0.0"
13
- gem "jeweler", "~> 1.5.1"
14
- gem "rcov", ">= 0"
15
- gem "mocha", :require => false
16
- gem "cucumber"
17
- gem 'test-unit'
7
+ gem 'guard-test'
8
+ gem 'rb-inotify'
9
+ gem 'ffi-ncurses'
10
+ gem 'rake'
18
11
  end
@@ -0,0 +1,6 @@
1
+ guard :test do
2
+ watch(%r{^lib/((?:[^/]+\/)*)(.+)\.rb$}) { |m| "test/unit/#{m[1]}test_#{m[2]}.rb" }
3
+ watch(%r{^test/((?:[^/]+\/)*)test.+\.rb$})
4
+ watch('test/helper.rb') { "test" }
5
+ watch('lib/keyrack.rb') { "test" }
6
+ end
@@ -1,4 +1,6 @@
1
- Copyright (c) 2010 Jeremy Stephens
1
+ Copyright (c) 2012 Jeremy Stephens
2
+
3
+ MIT License
2
4
 
3
5
  Permission is hereby granted, free of charge, to any person obtaining
4
6
  a copy of this software and associated documentation files (the
@@ -17,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
19
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
20
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
21
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile CHANGED
@@ -1,63 +1,9 @@
1
- require 'rubygems'
2
- require 'bundler'
3
- begin
4
- Bundler.setup(:default, :development)
5
- rescue Bundler::BundlerError => e
6
- $stderr.puts e.message
7
- $stderr.puts "Run `bundle install` to install missing gems"
8
- exit e.status_code
9
- end
10
- require 'rake'
11
-
12
- require 'jeweler'
13
- Jeweler::Tasks.new do |gem|
14
- # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
- gem.name = "keyrack"
16
- gem.homepage = "http://github.com/viking/keyrack"
17
- gem.license = "MIT"
18
- gem.summary = %Q{Simple password manager}
19
- gem.description = %Q{Simple password manager with local/remote database storage and RSA encryption.}
20
- gem.email = "viking@pillageandplunder.net"
21
- gem.authors = ["Jeremy Stephens"]
22
- # Include your dependencies below. Runtime dependencies are required when using your gem,
23
- # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
- # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
- # gem.add_development_dependency 'rspec', '> 1.2.3'
26
- # gem.add_runtime_dependency 'net-scp'
27
- # gem.add_runtime_dependency 'highline'
28
- # gem.add_runtime_dependency 'clipboard'
29
- # gem.add_development_dependency 'rspec', '> 1.2.3'
30
- # gem.add_development_dependency "bundler", "~> 1.0.0"
31
- # gem.add_development_dependency "jeweler", "~> 1.5.1"
32
- # gem.add_development_dependency "rcov", ">= 0"
33
- # gem.add_development_dependency "mocha"
34
- end
35
- Jeweler::RubygemsDotOrgTasks.new
36
-
1
+ require "bundler/gem_tasks"
37
2
  require 'rake/testtask'
3
+
38
4
  Rake::TestTask.new(:test) do |test|
39
5
  test.libs << 'lib' << 'test'
40
6
  test.pattern = 'test/**/test_*.rb'
41
7
  test.verbose = true
42
8
  end
43
-
44
- require 'rcov/rcovtask'
45
- Rcov::RcovTask.new do |test|
46
- test.libs << 'test'
47
- test.pattern = 'test/**/test_*.rb'
48
- test.verbose = true
49
- end
50
-
51
9
  task :default => :test
52
-
53
- require 'rdoc/task'
54
- Rake::RDocTask.new do |rdoc|
55
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
56
-
57
- rdoc.rdoc_dir = 'rdoc'
58
- rdoc.title = "keyrack #{version}"
59
- rdoc.rdoc_files.include('README*')
60
- rdoc.rdoc_files.include('lib/**/*.rb')
61
- end
62
-
63
- task :build => :gemspec
@@ -1,110 +1,28 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
1
  # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'keyrack/version'
5
5
 
6
- Gem::Specification.new do |s|
7
- s.name = "keyrack"
8
- s.version = "0.3.0.pre"
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "keyrack"
8
+ gem.version = Keyrack::VERSION
9
+ gem.authors = ["Jeremy Stephens"]
10
+ gem.email = ["viking@pillageandplunder.net"]
11
+ gem.description = %q{Simple password manager with local/remote database storage and scrypt encryption.}
12
+ gem.summary = %q{Simple password manager}
13
+ gem.homepage = "http://github.com/viking/keyrack"
9
14
 
10
- s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Jeremy Stephens"]
12
- s.date = "2012-01-11"
13
- s.description = "Simple password manager with local/remote database storage and RSA encryption."
14
- s.email = "viking@pillageandplunder.net"
15
- s.executables = ["keyrack"]
16
- s.extra_rdoc_files = [
17
- "LICENSE.txt",
18
- "README.rdoc",
19
- "TODO"
20
- ]
21
- s.files = [
22
- ".document",
23
- ".rvmrc",
24
- "Gemfile",
25
- "Gemfile.lock",
26
- "LICENSE.txt",
27
- "README.rdoc",
28
- "Rakefile",
29
- "TODO",
30
- "VERSION",
31
- "bin/keyrack",
32
- "features/console.feature",
33
- "features/step_definitions/keyrack_steps.rb",
34
- "features/support/env.rb",
35
- "keyrack.gemspec",
36
- "lib/keyrack.rb",
37
- "lib/keyrack/database.rb",
38
- "lib/keyrack/runner.rb",
39
- "lib/keyrack/store.rb",
40
- "lib/keyrack/store/filesystem.rb",
41
- "lib/keyrack/store/ssh.rb",
42
- "lib/keyrack/ui.rb",
43
- "lib/keyrack/ui/console.rb",
44
- "lib/keyrack/utils.rb",
45
- "test/fixtures/aes",
46
- "test/fixtures/config.yml",
47
- "test/fixtures/foo.txt",
48
- "test/fixtures/id_rsa",
49
- "test/helper.rb",
50
- "test/keyrack/store/test_filesystem.rb",
51
- "test/keyrack/store/test_ssh.rb",
52
- "test/keyrack/test_database.rb",
53
- "test/keyrack/test_runner.rb",
54
- "test/keyrack/test_store.rb",
55
- "test/keyrack/test_utils.rb",
56
- "test/keyrack/ui/test_console.rb"
57
- ]
58
- s.homepage = "http://github.com/viking/keyrack"
59
- s.licenses = ["MIT"]
60
- s.require_paths = ["lib"]
61
- s.rubygems_version = "1.8.11"
62
- s.summary = "Simple password manager"
63
- s.test_files = [
64
- "test/helper.rb",
65
- "test/keyrack/store/test_filesystem.rb",
66
- "test/keyrack/store/test_ssh.rb",
67
- "test/keyrack/test_database.rb",
68
- "test/keyrack/test_runner.rb",
69
- "test/keyrack/test_store.rb",
70
- "test/keyrack/test_utils.rb",
71
- "test/keyrack/ui/test_console.rb"
72
- ]
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
73
19
 
74
- if s.respond_to? :specification_version then
75
- s.specification_version = 3
20
+ gem.add_runtime_dependency 'net-scp'
21
+ gem.add_runtime_dependency 'highline'
22
+ gem.add_runtime_dependency 'clipboard'
23
+ gem.add_runtime_dependency 'scrypty'
76
24
 
77
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
78
- s.add_runtime_dependency(%q<net-scp>, [">= 0"])
79
- s.add_runtime_dependency(%q<highline>, [">= 0"])
80
- s.add_runtime_dependency(%q<copier>, [">= 0"])
81
- s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
82
- s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
83
- s.add_development_dependency(%q<rcov>, [">= 0"])
84
- s.add_development_dependency(%q<mocha>, [">= 0"])
85
- s.add_development_dependency(%q<cucumber>, [">= 0"])
86
- s.add_development_dependency(%q<test-unit>, [">= 0"])
87
- else
88
- s.add_dependency(%q<net-scp>, [">= 0"])
89
- s.add_dependency(%q<highline>, [">= 0"])
90
- s.add_dependency(%q<copier>, [">= 0"])
91
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
92
- s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
93
- s.add_dependency(%q<rcov>, [">= 0"])
94
- s.add_dependency(%q<mocha>, [">= 0"])
95
- s.add_dependency(%q<cucumber>, [">= 0"])
96
- s.add_dependency(%q<test-unit>, [">= 0"])
97
- end
98
- else
99
- s.add_dependency(%q<net-scp>, [">= 0"])
100
- s.add_dependency(%q<highline>, [">= 0"])
101
- s.add_dependency(%q<copier>, [">= 0"])
102
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
103
- s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
104
- s.add_dependency(%q<rcov>, [">= 0"])
105
- s.add_dependency(%q<mocha>, [">= 0"])
106
- s.add_dependency(%q<cucumber>, [">= 0"])
107
- s.add_dependency(%q<test-unit>, [">= 0"])
108
- end
25
+ gem.add_development_dependency 'bundler'
26
+ gem.add_development_dependency 'mocha'
27
+ gem.add_development_dependency 'test-unit'
109
28
  end
110
-
@@ -4,13 +4,21 @@ require 'optparse'
4
4
  require 'securerandom'
5
5
  require 'net/scp'
6
6
  require 'highline'
7
- require 'copier'
7
+ require 'clipboard'
8
+ require 'scrypty'
9
+ require 'fileutils'
10
+ require 'digest'
8
11
 
9
12
  module Keyrack
10
13
  end
11
14
 
15
+ require File.dirname(__FILE__) + '/keyrack/exceptions'
12
16
  require File.dirname(__FILE__) + '/keyrack/utils'
17
+ require File.dirname(__FILE__) + '/keyrack/event'
18
+ require File.dirname(__FILE__) + '/keyrack/site'
19
+ require File.dirname(__FILE__) + '/keyrack/group'
13
20
  require File.dirname(__FILE__) + '/keyrack/database'
14
21
  require File.dirname(__FILE__) + '/keyrack/store'
15
22
  require File.dirname(__FILE__) + '/keyrack/ui'
16
23
  require File.dirname(__FILE__) + '/keyrack/runner'
24
+ require File.dirname(__FILE__) + '/keyrack/migrator'
@@ -1,126 +1,92 @@
1
1
  module Keyrack
2
2
  class Database
3
- def initialize(key, iv, store)
4
- @key = key
5
- @iv = iv
6
- @store = store
7
- @data = decrypt
3
+ DEFAULT_ENCRYPT_OPTIONS = { :maxmem => 0, :maxmemfrac => 0.125, :maxtime => 5.0 }
4
+ DEFAULT_DECRYPT_OPTIONS = { :maxmem => 0, :maxmemfrac => 0.250, :maxtime => 10.0 }
5
+ VERSION = 4
6
+
7
+ def initialize(password, store, encrypt_options = {}, decrypt_options = {})
8
8
  @dirty = false
9
+ @encrypt_options = DEFAULT_ENCRYPT_OPTIONS.merge(encrypt_options)
10
+ @decrypt_options = DEFAULT_DECRYPT_OPTIONS.merge(decrypt_options)
11
+ @store = store
12
+ @password = password_hash(password)
13
+ @database = decrypt(password)
14
+ setup_hooks
9
15
  end
10
16
 
11
- def add(site, username, password, options = {})
12
- hash = options[:group] ? @data[options[:group]] ||= {} : @data
13
- if hash.has_key?(site)
14
- site_entry = hash[site]
15
- if site_entry.is_a?(Array)
16
- # Multiple entries for this site
17
- user_entry = site_entry.detect { |e| e[:username] == username }
18
- if user_entry
19
- # Update existing entry
20
- user_entry[:password] = password
21
- else
22
- # Add new entry
23
- site_entry.push({:username => username, :password => password})
24
- end
25
- elsif site_entry[:username] == username
26
- # Update existing entry
27
- site_entry[:password] = password
28
- else
29
- # Convert single entry into an array, then add new entry
30
- hash[site] = [site_entry, {:username => username, :password => password}]
31
- end
32
- else
33
- hash[site] = { :username => username, :password => password }
34
- end
35
- @dirty = true
17
+ def version
18
+ @database['version']
19
+ end
20
+
21
+ def top_group
22
+ @database['groups']['top']
36
23
  end
37
24
 
38
- def get(*args)
39
- options = args.last.is_a?(Hash) ? args.pop : {}
40
- site, username = args
25
+ def dirty?
26
+ @dirty
27
+ end
41
28
 
42
- site_entry = (options[:group] ? @data[options[:group]] : @data)[site]
43
- if username
44
- if site_entry.is_a?(Array)
45
- site_entry.find { |e| e[:username] == username }
46
- elsif site_entry[:username] == username
47
- site_entry
48
- else
49
- nil
50
- end
29
+ def save(password)
30
+ if password_hash(password) == @password
31
+ @store.write(Scrypty.encrypt(@database.to_yaml, password,
32
+ *@encrypt_options.values_at(:maxmem, :maxmemfrac, :maxtime)))
33
+ @dirty = false
34
+ true
51
35
  else
52
- site_entry
36
+ false
53
37
  end
54
38
  end
55
39
 
56
- def sites(options = {})
57
- hash = options[:group] ? @data[options[:group]] : @data
58
- if hash
59
- hash.keys.select do |key|
60
- val = hash[key]
61
- val.is_a?(Array) || (val.is_a?(Hash) && val.has_key?(:username))
62
- end.sort
40
+ def change_password(current_password, new_password)
41
+ if password_hash(current_password) == @password
42
+ @password = password_hash(new_password)
43
+ true
63
44
  else
64
- # new groups are empty
65
- []
45
+ false
66
46
  end
67
47
  end
68
48
 
69
- def groups
70
- @data.keys.reject do |key|
71
- val = @data[key]
72
- val.is_a?(Array) || (val.is_a?(Hash) && val.has_key?(:username))
73
- end.sort
74
- end
49
+ private
75
50
 
76
- def dirty?
77
- @dirty
51
+ def password_hash(password)
52
+ # Avoid storing the database password as-is in memory, but don't
53
+ # spend too much effort obfuscating it.
54
+ sha256 = Digest::SHA256.new
55
+ sha256.digest "#{password}-#{$$}"
78
56
  end
79
57
 
80
- def save
81
- cipher = OpenSSL::Cipher::Cipher.new("AES-128-CBC")
82
- cipher.encrypt; cipher.key = @key; cipher.iv = @iv
83
- @store.write(cipher.update(Marshal.dump(@data)) + cipher.final)
84
- @dirty = false
85
- end
58
+ def decrypt(password)
59
+ data = @store.read
60
+ if data
61
+ str = Scrypty.decrypt(data, password,
62
+ *@decrypt_options.values_at(:maxmem, :maxmemfrac, :maxtime))
63
+ hash = YAML.load(str)
64
+ migrated_hash = Migrator.run(hash)
65
+ if !migrated_hash.equal?(hash)
66
+ hash = migrated_hash
67
+ @dirty = true
68
+ end
86
69
 
87
- def delete(site, username, options = {})
88
- hash = options[:group] ? @data[options[:group]] : @data
89
- site_entry = hash[site]
70
+ top = Group.new
71
+ top.load(hash['groups']['top'])
72
+ hash['groups']['top'] = top
90
73
 
91
- if site_entry.is_a?(Array)
92
- site_entry.each_with_index do |entry, i|
93
- if entry[:username] == username
94
- case site_entry.length
95
- when 2
96
- site_entry.delete_at(i)
97
- hash[site] = site_entry[0]
98
- when 1
99
- hash.delete(site)
100
- else
101
- site_entry.delete_at(i)
102
- end
74
+ hash
75
+ else
76
+ {'groups' => {'top' => Group.new('top')}, 'version' => VERSION}
77
+ end
78
+ end
103
79
 
104
- @dirty = true
105
- break
106
- end
107
- end
108
- elsif site_entry[:username] == username
109
- hash.delete(site)
110
- @dirty = true
80
+ def setup_hooks
81
+ @database['groups'].each_pair do |group_name, group|
82
+ add_group_hooks_for(group)
111
83
  end
112
84
  end
113
85
 
114
- private
115
- def decrypt
116
- data = @store.read
117
- if data
118
- cipher = OpenSSL::Cipher::Cipher.new("AES-128-CBC")
119
- cipher.decrypt; cipher.key = @key; cipher.iv = @iv
120
- Marshal.load(cipher.update(data) + cipher.final)
121
- else
122
- {}
123
- end
86
+ def add_group_hooks_for(group)
87
+ group.after_event do |event|
88
+ @dirty = true
124
89
  end
90
+ end
125
91
  end
126
92
  end