shh 0.0.6 → 0.0.7
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.rdoc +89 -0
- data/bin/shh +1 -1
- data/lib/shh/cli.rb +8 -85
- data/lib/shh/entries_menu.rb +63 -0
- data/lib/shh/entry_menu.rb +60 -0
- data/lib/shh/prompt.rb +11 -0
- data/lib/shh/repository.rb +39 -0
- metadata +6 -2
data/README.rdoc
CHANGED
@@ -5,3 +5,92 @@ Secret Squirrel
|
|
5
5
|
This is a command line utility for managing secure information such as accounts, passwords as individual files so that they can be easily managed in a version control repository.
|
6
6
|
|
7
7
|
You won't be hiding anything from the NSA with this level of encryption so it isn't recommended that you make your repository publicly accessible.
|
8
|
+
|
9
|
+
Now with more tab completion!
|
10
|
+
|
11
|
+
= Usage
|
12
|
+
|
13
|
+
Here you sit expectantly in front of a computer at the command line.
|
14
|
+
|
15
|
+
== Install
|
16
|
+
|
17
|
+
gem install shh
|
18
|
+
|
19
|
+
(you may need to install gemcutter first)
|
20
|
+
|
21
|
+
== Launch
|
22
|
+
|
23
|
+
Open all 'secrets' stored in ~/.secret
|
24
|
+
|
25
|
+
> shh
|
26
|
+
|
27
|
+
Opens all 'secrets' stored in foo/.secret
|
28
|
+
|
29
|
+
> shh foo
|
30
|
+
|
31
|
+
== Authenticate
|
32
|
+
|
33
|
+
Enter your passphrase
|
34
|
+
|
35
|
+
This passphrase will be used to encrypt and decrypt all of your secrets so don't make it too obvious.
|
36
|
+
|
37
|
+
== Listing mode
|
38
|
+
|
39
|
+
This mode allows you to view and edit 'entries' (which are encrypted hashes stored in files)
|
40
|
+
|
41
|
+
> list
|
42
|
+
|
43
|
+
bitbucket (260f34de-6779-4367-af2a-44184dec1cc1)
|
44
|
+
amazon (291a3e6c-2c7b-4960-a146-1f6635d9a74e)
|
45
|
+
yahoo (2fe408e7-7f2b-4dc0-854a-c92c21f131ae)
|
46
|
+
evernote (41ad0a21-eafc-4f70-9b0d-0251be207b9e)
|
47
|
+
gmail (8b35c1f2-851d-40fc-bd12-c2aad12c370a)
|
48
|
+
rememberthemilk (a499f612-d034-4e49-82a8-6967d29653a1)
|
49
|
+
|
50
|
+
* list - show entries
|
51
|
+
* edit <name> - edit or create entry
|
52
|
+
* view <name> - view entry
|
53
|
+
* quit
|
54
|
+
|
55
|
+
== Viewing mode
|
56
|
+
|
57
|
+
This mode allows you to view (but not edit) an existing entry
|
58
|
+
|
59
|
+
> view bitbucket
|
60
|
+
(bitbucket) > list
|
61
|
+
id,name,password,username
|
62
|
+
(bitbucket) > show username
|
63
|
+
markryall
|
64
|
+
|
65
|
+
list keys, show key, copy key or quit? c password
|
66
|
+
|
67
|
+
* list - show entry keys
|
68
|
+
* show <key> - show entry value on screen
|
69
|
+
* copy <key> - copy entry value to clipboard
|
70
|
+
* quit
|
71
|
+
|
72
|
+
== Editing mode
|
73
|
+
|
74
|
+
This mode is viewing mode plus some editing commands
|
75
|
+
|
76
|
+
> edit bitbucket
|
77
|
+
(bitbucket) > edit foo
|
78
|
+
Enter new value for foo
|
79
|
+
bar
|
80
|
+
(bitbucket) > delete foo
|
81
|
+
|
82
|
+
* edit <key> - edit or create key
|
83
|
+
* delete <key> - remove a key
|
84
|
+
|
85
|
+
== Quitting mode
|
86
|
+
|
87
|
+
You don't want to be a quitter
|
88
|
+
|
89
|
+
= Future plans for world domination
|
90
|
+
|
91
|
+
* Add some color
|
92
|
+
* Add help
|
93
|
+
* Launch applications for urls
|
94
|
+
* Add multi line values (edit with default text editor)
|
95
|
+
* Include uuid as part of encryption key (and upgrade encryption on existing entries)
|
96
|
+
* add some source control (hg/git) commands
|
data/bin/shh
CHANGED
data/lib/shh/cli.rb
CHANGED
@@ -1,93 +1,16 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
require '
|
5
|
-
require 'yaml'
|
6
|
-
require 'shh/crypt'
|
7
|
-
require 'shh/clipboard'
|
2
|
+
require 'shh/repository'
|
3
|
+
require 'shh/prompt'
|
4
|
+
require 'shh/entries_menu'
|
8
5
|
|
9
6
|
module Shh
|
10
7
|
class Cli
|
11
|
-
def execute *args
|
12
|
-
|
8
|
+
def self.execute *args
|
9
|
+
prompt = Prompt.new
|
10
|
+
passphrase = prompt.get('Enter your passphrase', :silent => true)
|
11
|
+
path = args.shift || ('~')
|
13
12
|
|
14
|
-
|
15
|
-
@folder = path ? Pathname.new(path) : Pathname.new(File.expand_path('~'))+'.secret'
|
16
|
-
@folder.mkdir_p
|
17
|
-
@crypt = Crypt.new(passphrase)
|
18
|
-
@clipboard = Shh.clipboard
|
19
|
-
|
20
|
-
loop do
|
21
|
-
choose do |menu|
|
22
|
-
menu.layout = :menu_only
|
23
|
-
menu.shell = true
|
24
|
-
menu.choice('list entries') { list }
|
25
|
-
menu.choice('edit entry') { |command, details| edit details }
|
26
|
-
menu.choice('view entry') { |command, details| view details }
|
27
|
-
menu.choice('quit') { exit }
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def list
|
33
|
-
each_entry {|entry| say "#{entry['name']} (#{entry['id']})" }
|
34
|
-
end
|
35
|
-
|
36
|
-
def edit name=''
|
37
|
-
entry = find_entry(check_name(name))
|
38
|
-
entry ||= {'name' => check_name(name), 'id' => UUIDTools::UUID.random_create.to_s}
|
39
|
-
persist_entry prompt_loop(entry)
|
40
|
-
end
|
41
|
-
|
42
|
-
def view name=''
|
43
|
-
prompt_loop find_entry(check_name(name)), true
|
44
|
-
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
def each_entry
|
49
|
-
@folder.children.each do |child|
|
50
|
-
yield load_entry(child)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def check_name name
|
55
|
-
name = ask('Enter the entry name') unless name.size > 0
|
56
|
-
name
|
57
|
-
end
|
58
|
-
|
59
|
-
def find_entry name
|
60
|
-
each_entry {|e| return e if e['name'] == name}
|
61
|
-
nil
|
62
|
-
end
|
63
|
-
|
64
|
-
def load_entry path
|
65
|
-
entry = path.open('rb') {|io| @crypt.decrypt(io) }
|
66
|
-
YAML::load(entry)
|
67
|
-
end
|
68
|
-
|
69
|
-
def persist_entry entry
|
70
|
-
(@folder + entry['id']).open('wb') {|io| @crypt.encrypt(entry.to_yaml, io) }
|
71
|
-
end
|
72
|
-
|
73
|
-
def prompt_loop hash, read_only=false
|
74
|
-
loop do
|
75
|
-
choose do |menu|
|
76
|
-
menu.layout = :menu_only
|
77
|
-
menu.shell = true
|
78
|
-
menu.choice('edit key') { |command, name| hash[name] = new_value(name) } unless read_only
|
79
|
-
menu.choice('list keys') { say(hash.keys.sort.join(',')) }
|
80
|
-
menu.choice('show key') { |command, name| say(hash[name]) if hash[name] }
|
81
|
-
menu.choice('delete key') { |command, name| hash[name] = nil } unless read_only
|
82
|
-
menu.choice('copy key') { |command, name| @clipboard.content = hash[name] } if @clipboard
|
83
|
-
menu.choice('quit') { return hash }
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def new_value name
|
89
|
-
echo = (name =~ /pass/) ? false : true
|
90
|
-
ask("Enter new value for #{name}") {|q| q.echo = echo }
|
13
|
+
EntriesMenu.new(prompt, Repository.new(passphrase, path)).main_loop
|
91
14
|
end
|
92
15
|
end
|
93
16
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'uuidtools'
|
2
|
+
require 'readline'
|
3
|
+
require 'shh/entry_menu'
|
4
|
+
|
5
|
+
module Shh
|
6
|
+
class EntriesMenu
|
7
|
+
def initialize prompt, repository
|
8
|
+
@prompt, @repository = prompt, repository
|
9
|
+
refresh
|
10
|
+
end
|
11
|
+
|
12
|
+
def main_loop
|
13
|
+
prompt_text = ' > '
|
14
|
+
|
15
|
+
begin
|
16
|
+
while line = Readline.readline(prompt_text, true).strip
|
17
|
+
case line
|
18
|
+
when 'quit'
|
19
|
+
return
|
20
|
+
when 'refresh'
|
21
|
+
refresh
|
22
|
+
when 'list'
|
23
|
+
list
|
24
|
+
when /^view (.*)/
|
25
|
+
view $1
|
26
|
+
when /^edit (.*)/
|
27
|
+
edit $1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
rescue Interrupt => e
|
31
|
+
return
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def refresh
|
36
|
+
commands = ['list', 'refresh', 'quit']
|
37
|
+
@repository.each_entry do |entry|
|
38
|
+
commands << "edit #{entry['name']}"
|
39
|
+
commands << "view #{entry['name']}"
|
40
|
+
end
|
41
|
+
Readline.completion_proc = lambda do |text|
|
42
|
+
commands.grep( /^#{Regexp.escape(text)}/ ).sort
|
43
|
+
end
|
44
|
+
Readline.completer_word_break_characters = ''
|
45
|
+
end
|
46
|
+
|
47
|
+
def list
|
48
|
+
@repository.each_entry {|entry| say "#{entry['name']} (#{entry['id']})" }
|
49
|
+
end
|
50
|
+
|
51
|
+
def edit name
|
52
|
+
entry = @repository.find_entry(name)
|
53
|
+
entry ||= {'name' => name, 'id' => UUIDTools::UUID.random_create.to_s}
|
54
|
+
@repository.persist_entry EntryMenu.new(@prompt, entry).main_loop
|
55
|
+
refresh
|
56
|
+
end
|
57
|
+
|
58
|
+
def view name
|
59
|
+
EntryMenu.new(@prompt, @repository.find_entry(name), true).main_loop
|
60
|
+
refresh
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'shh/clipboard'
|
2
|
+
|
3
|
+
module Shh
|
4
|
+
class EntryMenu
|
5
|
+
def initialize prompt, hash, read_only=false
|
6
|
+
@prompt, @hash, @read_only = prompt, hash, read_only
|
7
|
+
@clipboard = Shh.clipboard
|
8
|
+
refresh
|
9
|
+
end
|
10
|
+
|
11
|
+
def main_loop
|
12
|
+
prompt_text = "(#{@hash['name']}) > "
|
13
|
+
|
14
|
+
begin
|
15
|
+
while line = Readline.readline(prompt_text, true).strip
|
16
|
+
case line
|
17
|
+
when 'quit'
|
18
|
+
return @hash
|
19
|
+
when 'list'
|
20
|
+
say(@hash.keys.sort.join(','))
|
21
|
+
when /^edit (.*)/
|
22
|
+
name = $1
|
23
|
+
@hash[name] = new_value(name) unless @read_only
|
24
|
+
when /^copy (.*)/
|
25
|
+
name = $1
|
26
|
+
@clipboard.content = @hash[name] if @clipboard and @hash[name]
|
27
|
+
when /^delete (.*)/
|
28
|
+
name = $1
|
29
|
+
@hash.delete(name) unless @read_only
|
30
|
+
when /^show (.*)/
|
31
|
+
name = $1
|
32
|
+
say(@hash[name]) if @hash[name]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
rescue Interrupt => e
|
36
|
+
return @hash
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def refresh
|
43
|
+
commands = ['list', 'quit']
|
44
|
+
@hash.keys.each do |key|
|
45
|
+
commands << "edit #{key}" unless @read_only
|
46
|
+
commands << "delete #{key}" unless @read_only
|
47
|
+
commands << "show #{key}"
|
48
|
+
commands << "copy #{key}"
|
49
|
+
end
|
50
|
+
Readline.completion_proc = lambda do |text|
|
51
|
+
commands.grep( /^#{Regexp.escape(text)}/ ).sort
|
52
|
+
end
|
53
|
+
Readline.completer_word_break_characters = ''
|
54
|
+
end
|
55
|
+
|
56
|
+
def new_value name
|
57
|
+
@prompt.get "Enter new value for #{name}", :silent => (name =~ /pass/)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/shh/prompt.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'pathname2'
|
2
|
+
require 'yaml'
|
3
|
+
require 'shh/crypt'
|
4
|
+
|
5
|
+
module Shh
|
6
|
+
class Repository
|
7
|
+
attr_reader :folder
|
8
|
+
|
9
|
+
def initialize passphrase, path
|
10
|
+
@folder = Pathname.new(File.expand_path(path)) + '.secret'
|
11
|
+
@folder.mkdir_p
|
12
|
+
@crypt = Crypt.new(passphrase)
|
13
|
+
end
|
14
|
+
|
15
|
+
def each_entry
|
16
|
+
@folder.children.each do |child|
|
17
|
+
entry = load_entry(child)
|
18
|
+
yield entry if entry
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_entry name
|
23
|
+
each_entry {|e| return e if e['name'] == name}
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def load_entry path
|
28
|
+
return nil if path.directory?
|
29
|
+
yaml = path.open('rb') {|io| @crypt.decrypt(io) }
|
30
|
+
entry = YAML::load(yaml)
|
31
|
+
return nil unless entry
|
32
|
+
path.basename.to_s == entry['id'] ? entry : nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def persist_entry entry
|
36
|
+
(@folder + entry['id']).open('wb') {|io| @crypt.encrypt(entry.to_yaml, io) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Ryall
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-12-
|
12
|
+
date: 2009-12-26 00:00:00 +11:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -67,6 +67,10 @@ files:
|
|
67
67
|
- lib/shh/clipboard.rb
|
68
68
|
- lib/shh/crypt.rb
|
69
69
|
- lib/shh/darwin10_clipboard.rb
|
70
|
+
- lib/shh/entries_menu.rb
|
71
|
+
- lib/shh/entry_menu.rb
|
72
|
+
- lib/shh/prompt.rb
|
73
|
+
- lib/shh/repository.rb
|
70
74
|
- lib/shh/win32_clipboard.rb
|
71
75
|
- lib/shh.rb
|
72
76
|
- bin/shh
|