shadowpass 0.0.1

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/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # ShadowPass
2
+
3
+ A tool for keeping a GnuPG encrypted password store.
4
+
5
+ ## A brief history
6
+
7
+ I have, for a long time, kept a gpg encrypted password file in markdown format
8
+ in a private git repo that I have appropriately named, 'shadow'. It follows me
9
+ everywhere. When I need a password, I vi the file, and with the help of the
10
+ gpg vim plugin, I can quickly access my secret. This has worked for a long
11
+ time. However, as the file grows it can become a bit unwieldy. Thus,
12
+ 'shadowpass'.
13
+
14
+ ### But what about the other tools?
15
+
16
+ For the past couple years I have been working on a mac as my primary desktop.
17
+ This has its benefits in the world of password storage. Namely, keychain.
18
+ Keychain is awesome. It allows the system, utilities or the user, to directly
19
+ read and write to the password store, with some mild 'would you like to grant
20
+ access' style security methods. The database is encrypted and such, which is
21
+ basically a must for any password storage utility. I've also been using
22
+ another great utility on the Mac called '1Password'. 1Password is a gui
23
+ application that stores your passwords in an encrypted password store, giving
24
+ you a gui and some browser plugins so that you can store per site passwords for
25
+ some auto fill magic. Cool stuff, but its outside the scope of this tool.
26
+
27
+ ### So why?
28
+
29
+ Having password storage tools locally on your desktop does wonders for you...
30
+ when you are local. But what if you are working on a remote system that isn't
31
+ of the Apple variety? For my case, I want `offlineimap` to be able to lookup a
32
+ password automatically with one simple command line utility on a box where I
33
+ don't have my common desktop tools like the `security` tool on OS X. There are
34
+ some other tools out there like Keypass, kwallet, gnome-pass, python-keyring,
35
+ etc, but to my knowledge none of these support GPG. I am big on GPG, and since
36
+ I am not using X on a remote system they do me little good, with the exception
37
+ of python-keyring.
38
+
39
+ ## Usage
40
+
41
+ Getting and setting a record is easy. Jus specify the path with either the `-g` or `-s` flags.
42
+
43
+ shadowpass -c ~/.shadow.yaml -s sites/forge
44
+
45
+ You will be prompted for the secret that you would like to store at this path.
46
+ Then to retrieve the value that you stored there, just use the get flag.
47
+
48
+ shadowpass -c ~/.shadow.yaml -g sites/forge
49
+
50
+ Easy, right? Paths can be of any length, so you are free to organize your
51
+ passwords in any way you like.
52
+
53
+ ## Storage format
54
+
55
+ Its all just json. The path gets translated into a hash of hashes structures
56
+ converting the path keys into hash keys, before getting or setting the value
57
+ for those keys.
58
+
59
+ Want to get the entire database? Easy.
60
+
61
+ shadowpass -c ~/.shadow.yaml -g /
62
+
63
+ This tells shadowpass to get the root, and therfore everything beneath it. You
64
+ can follow the same logic to get everything below a certain point. For
65
+ example:
66
+
67
+ shadowpass -c ~/.shadow.yaml -g sites/social/github
68
+
69
+ Will return you all the items you have stored beneath the key `github`. This
70
+ is useful so you can store username, password, and any extras, like api key,
71
+ etc.
72
+
data/bin/shadowpass ADDED
@@ -0,0 +1,62 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ begin
4
+ require 'json'
5
+ require 'yaml'
6
+ require 'optparse'
7
+ require 'pp'
8
+ require 'gpgme'
9
+ require 'cri'
10
+ require 'shadowpass'
11
+ require 'io/console'
12
+ rescue LoadError => e
13
+ puts "Unable to load library: #{e}"
14
+ exit 1
15
+ end
16
+
17
+ command = Cri::Command.define do
18
+ name 'shadowpass'
19
+ usage 'shadowpass [options]'
20
+ aliases :pws, :spass
21
+ summary 'maintain a password database'
22
+
23
+ flag :h, :help, 'show help for this command' do |value, cmd|
24
+ puts cmd.help
25
+ exit 0
26
+ end
27
+ flag :v, :verbose, 'Be less quiet'
28
+ option :c, :conf, 'Specify the configuration file', :argument => :required
29
+ option :s, :set, 'set a record', :argument => :required
30
+ option :g, :get, 'get a record', :argument => :required
31
+
32
+ run do |opts, args, cmd|
33
+ configfile = opts[:conf] || 'etc/shadowpass.yaml'
34
+
35
+ if opts[:verbose]
36
+ starttime = Time.now
37
+ puts "Initializing at #{starttime}"
38
+ end
39
+
40
+ puts "Loading configuration #{configfile}" if opts[:verbose]
41
+ config = YAML::load(File.read(configfile))
42
+ config[:opts] = opts
43
+
44
+ p = ShadowPass.new(config)
45
+
46
+ if opts[:set]
47
+ path = opts[:set].split('/')
48
+ puts "secret: "
49
+ path << STDIN.noecho {|i| i.gets}.chomp
50
+ p.set(path)
51
+ end
52
+
53
+ if opts[:get]
54
+ path = opts[:get].split('/')
55
+ value = p.get(path)
56
+ puts value
57
+ end
58
+
59
+ end
60
+ end
61
+
62
+ command.run(ARGV)
@@ -0,0 +1,3 @@
1
+ ---
2
+ :pwfile: '~/.pw.asc'
3
+ :keyid: 'ABCD1234'
data/lib/shadowpass.rb ADDED
@@ -0,0 +1,107 @@
1
+ require 'pp'
2
+ require 'gpgme'
3
+ require 'json'
4
+ require 'awesome_print'
5
+ require 'io/console'
6
+
7
+
8
+ # @author Zach Leslie
9
+ class ShadowPass
10
+ VERSION = '0.0.1'
11
+
12
+ def initialize (config)
13
+ @config = config
14
+ @pwfile = File.expand_path(@config[:pwfile])
15
+ @keyid = config[:keyid]
16
+ @verbose = config[:opts][:verbose]
17
+ # determine of this is a new database
18
+ if File.exists?(@pwfile)
19
+ @shadows = loadShadow
20
+ else
21
+ @shadows = initShadow
22
+ end
23
+ end
24
+
25
+ def get (path)
26
+ speak("looking for value at #{path.join('/')}")
27
+ value = path.inject(@shadows) {|result, element|
28
+ if result[element]
29
+ result[element]
30
+ else
31
+ result
32
+ end
33
+ }
34
+ end
35
+
36
+ def set (path)
37
+ value = path.pop
38
+
39
+ # Build the new data object with the supplied path
40
+ speak("building new data object")
41
+ data = path.reverse.inject(value) {|result, element|
42
+ { element => result }
43
+ }
44
+
45
+ # Merge the new data object into the existing shadow
46
+ speak("merging new data object into shadows")
47
+ if @shadows.empty?
48
+ flush data
49
+ else
50
+ newshadows = merge_gently(@shadows,data)
51
+ flush newshadows
52
+ end
53
+ end
54
+
55
+ def merge_gently(a,b)
56
+ speak('received original data object')
57
+ speak('received new data object')
58
+ data = Hash.new
59
+ a.each do |k,v|
60
+ if k == b.keys.first
61
+ # Ig data[k] is a string, then we are likely replacing something here.
62
+ data[k] = merge_gently(a[k],b[k]) unless data[k].is_a? String
63
+ data
64
+ else
65
+ data[k] = a[k]
66
+ data[b.keys.first] = b[b.keys.first]
67
+ data
68
+ end
69
+ end
70
+ speak('here is the merged data object to be returned')
71
+ data
72
+ end
73
+
74
+ def speak(what)
75
+ puts what if @verbose
76
+ end
77
+
78
+ # Encrypt the data object and flush to disk
79
+ def flush( data )
80
+ # Build ciphertext
81
+ crypto = GPGME::Crypto.new(:armor => true)
82
+ cipher = crypto.encrypt "#{data.to_json}", :recipients => @keyid
83
+ # Write te cypertext to the file
84
+ speak("write the shadow data to file at #{@pwfile}")
85
+ File.open(@pwfile, 'w') {|f| f.write( cipher.read ) }
86
+ end
87
+
88
+ private
89
+
90
+ # initialize the shadow data
91
+ def initShadow
92
+ speak("initializing #{@pwfile}")
93
+ # Build and return the initial data object
94
+ data = Hash.new
95
+ data
96
+ end
97
+
98
+ def loadShadow
99
+ # decrypt and read the shadow file
100
+ speak("loading datafile #{@pwfile}")
101
+ crypto = GPGME::Crypto.new(:armor => true)
102
+ clear = crypto.decrypt(File.open(@pwfile))
103
+ # returns cleartext object
104
+ JSON.parse( clear.read )
105
+ end
106
+
107
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shadowpass
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Zach Leslie
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: json
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: awesome_print
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: cri
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: yard
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: gpgme
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: ''
95
+ email: xaque208@gmail.com
96
+ executables:
97
+ - shadowpass
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - bin/shadowpass
102
+ - lib/shadowpass.rb
103
+ - etc/shadowpass.yaml.sample
104
+ - README.md
105
+ homepage: https://github.com/xaque208/shadowpass
106
+ licenses: []
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ! '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ! '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 1.8.23
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: A tool for keeping a GnuPG password store.
129
+ test_files: []
130
+ has_rdoc: