seekrit 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/bin/seekrit ADDED
@@ -0,0 +1,53 @@
1
+ #! /usr/bin/ruby1.8
2
+ require 'seekrit/store'
3
+ require 'seekrit/interactor'
4
+ require 'highline/import'
5
+ require 'fileutils'
6
+
7
+ DATAFILE = ENV['HOME'] + '/.config/seekrit/secrets'
8
+
9
+ def interactor(&blk)
10
+ err = nil
11
+ FileUtils.mkdir_p(File.dirname(DATAFILE))
12
+ File.open(DATAFILE, "a+") do |file|
13
+ 3.times do
14
+ begin
15
+ password = ask('Enter password: '){ |q| q.echo = false }
16
+ store = Seekrit::Store.new(password, file)
17
+ yield Seekrit::Interactor.new(store)
18
+ return
19
+ rescue Seekrit::DecryptionError => err
20
+ end
21
+ end
22
+ puts err.message
23
+ exit 1
24
+ end
25
+ end
26
+
27
+ command = ARGV.shift
28
+ names = ARGV
29
+ case command
30
+ when 'show', 's'
31
+ interactor{ |i| i.show names }
32
+ when 'edit', 'e'
33
+ interactor{ |i| i.edit names }
34
+ when 'list', 'l'
35
+ interactor{ |i| i.list names }
36
+ when 'delete'
37
+ interactor{ |i| i.delete names }
38
+ when 'rename'
39
+ interactor{ |i| i.rename names.shift, names.shift }
40
+ when 'export'
41
+ interactor{ |i| i.export names.shift }
42
+ when 'import'
43
+ interactor{ |i| i.import names.shift }
44
+ else
45
+ puts "Unknown command '#{command}'", '' if command
46
+ puts <<END
47
+ list List all entries.
48
+ show name(s) Show matching entries.
49
+ edit name(s) Create or modify entries.
50
+ delete name(s) Delete entries.
51
+ rename old_name new_name Rename entry.
52
+ END
53
+ end
@@ -0,0 +1,106 @@
1
+ require 'tempfile'
2
+
3
+ module Seekrit
4
+ class Interactor
5
+ EDITOR = ENV['EDITOR'] || 'vi'
6
+ RANDOM_SOURCE = '/dev/urandom'
7
+
8
+ attr_reader :store
9
+
10
+ def initialize(store)
11
+ @store = store
12
+ end
13
+
14
+ def show(names)
15
+ names.each do |name|
16
+ data = store[name]
17
+ puts(name, data, '')
18
+ end
19
+ end
20
+
21
+ def edit(names)
22
+ names.each do |name|
23
+ comment = "\# #{name}\n"
24
+ data = comment + (store[name] || '')
25
+ external_editor(data) do |data|
26
+ data.sub!(/\A#{Regexp.escape(comment)}/, '')
27
+ store[name] = data
28
+ store.save
29
+ end
30
+ end
31
+ end
32
+
33
+ def delete(names)
34
+ names.each do |name|
35
+ store.delete(name)
36
+ end
37
+ store.save
38
+ end
39
+
40
+ def list(patterns)
41
+ if patterns.empty?
42
+ regexp = /.*/
43
+ else
44
+ regexp = /#{ patterns.map{ |p| Regexp.escape(p) }.join('|') }/
45
+ end
46
+ store.keys.sort_by{ |a| a.upcase }.each do |name|
47
+ puts name if name =~ regexp
48
+ end
49
+ end
50
+
51
+ def rename(old_name, new_name)
52
+ store.rename(old_name, new_name)
53
+ store.save
54
+ end
55
+
56
+ def export(filename)
57
+ File.open(filename, 'w') do |io|
58
+ store.export io
59
+ end
60
+ end
61
+
62
+ def import(filename)
63
+ File.open(filename, 'r') do |io|
64
+ store.import io
65
+ store.save
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def shred(filename, cycles=1)
72
+ data_length = File.stat(filename).size
73
+ File.open(filename, 'wb') do |io|
74
+ cycles.times do
75
+ io.rewind
76
+ io << random_bytes(data_length)
77
+ io.flush
78
+ end
79
+ end
80
+ end
81
+
82
+ def random_bytes(num_bytes)
83
+ File.open(RANDOM_SOURCE){ |io| io.read(num_bytes) }
84
+ end
85
+
86
+ def puts(*args)
87
+ $stderr.puts(*args)
88
+ end
89
+
90
+ def external_editor(data, &blk)
91
+ tempfile = Tempfile.new('seekrit')
92
+ tempfile << data
93
+ tempfile.close
94
+ mtime = File.stat(tempfile.path).mtime
95
+ system( EDITOR + ' ' + tempfile.path )
96
+ if File.stat(tempfile.path).mtime == mtime
97
+ puts('No modifications.')
98
+ else
99
+ yield File.read(tempfile.path)
100
+ end
101
+ shred(tempfile.path)
102
+ end
103
+
104
+ end
105
+ end
106
+
@@ -0,0 +1,124 @@
1
+ require 'digest/sha2'
2
+ require 'openssl'
3
+ require 'yaml'
4
+
5
+ module Seekrit
6
+ class DecryptionError < RuntimeError
7
+ end
8
+
9
+ class Store
10
+ CIPHER = 'aes-256-cbc'
11
+
12
+ attr_reader :secrets
13
+
14
+ def initialize(password, file, cipher=CIPHER)
15
+ @password = password
16
+ @cipher = cipher
17
+ @file = file
18
+ @secrets = load_data(file)
19
+ end
20
+
21
+ def keys
22
+ secrets.keys
23
+ end
24
+
25
+ def [](name)
26
+ secrets[name]
27
+ end
28
+
29
+ def []=(name, value)
30
+ secrets[name] = value
31
+ end
32
+
33
+ def delete(name)
34
+ secrets.delete(name)
35
+ end
36
+
37
+ def rename(oldname, newname)
38
+ self[newname] = self[oldname]
39
+ delete(oldname)
40
+ end
41
+
42
+ def save
43
+ @file.rewind
44
+ secrets.sort_by{ |k,_| k }.each do |name, value|
45
+ @file << escape(name) << "\t" << hexdump(encrypt(value)) << "\n"
46
+ end
47
+ end
48
+
49
+ def export(io)
50
+ secrets.sort_by{ |k,_| k }.each do |name, value|
51
+ io << escape(name) << "\t" << escape(value) << "\n"
52
+ end
53
+ end
54
+
55
+ def import(io)
56
+ secrets.clear
57
+ while line = io.gets
58
+ name, data = line.chomp.split(/\t/, 2).map{ |a| unescape(a) }
59
+ self[name] = data
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def crypt_key
66
+ Digest::SHA256.digest(@password)
67
+ end
68
+
69
+ def escape(value)
70
+ value.
71
+ gsub(/\\/, "\\\\\\\\").
72
+ gsub(/\t/, "\\\\t").
73
+ gsub(/\n/, "\\\\n")
74
+ end
75
+
76
+ def unescape(value)
77
+ value.
78
+ gsub(/\\n/, "\n").
79
+ gsub(/\\t/, "\t").
80
+ gsub(/\\\\/, "\\\\")
81
+ end
82
+
83
+ def load_data(file)
84
+ data = {}
85
+ while line = file.gets
86
+ a, b = line.split(/\t/, 2)
87
+ data[unescape(a)] = decrypt(hexload(b))
88
+ end
89
+ data
90
+ end
91
+
92
+ def hexdump(binary)
93
+ binary.unpack('C*').map{ |a| "%02x" % a }.join
94
+ end
95
+
96
+ def hexload(hex)
97
+ hex.scan(/../).map{ |a| a.to_i(16) }.pack('C*')
98
+ end
99
+
100
+ def encrypt(data)
101
+ cipher = OpenSSL::Cipher::Cipher.new(@cipher)
102
+ cipher.encrypt
103
+ cipher.key = crypt_key
104
+ cipher.iv = iv = cipher.random_iv
105
+ ciphertext = cipher.update(data)
106
+ ciphertext << cipher.final
107
+ return iv + ciphertext
108
+ end
109
+
110
+ def decrypt(data)
111
+ cipher = OpenSSL::Cipher::Cipher.new(@cipher)
112
+ iv = data[0, cipher.iv_len]
113
+ ciphertext = data[cipher.iv_len..-1]
114
+ cipher.decrypt
115
+ cipher.key = crypt_key
116
+ cipher.iv = iv
117
+ plaintext = cipher.update(ciphertext)
118
+ plaintext << cipher.final
119
+ return plaintext
120
+ rescue => err
121
+ raise DecryptionError, err.message
122
+ end
123
+ end
124
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: seekrit
3
+ version: !ruby/object:Gem::Version
4
+ hash: 21
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 1
10
+ version: 0.2.1
11
+ platform: ruby
12
+ authors:
13
+ - Paul Battley
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-16 00:00:00 +00:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: highline
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: A password safe
36
+ email:
37
+ - pbattley@gmail.com
38
+ executables:
39
+ - seekrit
40
+ extensions: []
41
+
42
+ extra_rdoc_files: []
43
+
44
+ files:
45
+ - bin/seekrit
46
+ - lib/seekrit/store.rb
47
+ - lib/seekrit/interactor.rb
48
+ has_rdoc: true
49
+ homepage: http://github.com/threedaymonk/seekrit
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 3
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ requirements: []
76
+
77
+ rubyforge_project:
78
+ rubygems_version: 1.3.7
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: Password safe
82
+ test_files: []
83
+