yamp 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +40 -0
- data/bin/yamp +44 -22
- data/lib/yamp.rb +41 -7
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95c693293fc5c7bd9d42cf0f3aeca7361f02e4b9
|
4
|
+
data.tar.gz: d9de29b05eda8ee531f14e319c6527eec01a77cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b99277a33f585c6cd412b21d60bfec039d103825d7ccc5cb0b45f1e288bc794398d571f1d44cec45ce1afce2e7829ca36908cd0fe9586582a4b22561bf31428
|
7
|
+
data.tar.gz: c455b39c38c2378c36502415272d21c0d90f067d27d9e4d0d943e6295d2d475b3368b95218810c9e7eeebbea5c683a67e8ec3cce86ce8ae5d7ac2a09f7abbc2e
|
data/README.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# YAMP
|
2
|
+
|
3
|
+
**Y**et **A**nother **M**inimalistic **P**asswordmanager
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
A minimal CLI-based password manager built upon redis-cache.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
*yamp* is available at [rubygems](https://rubygems.org). You can install it
|
12
|
+
by typing `gem install yamp` in your terminal or download it directly from https://rubygems.org/gems/yamp.
|
13
|
+
|
14
|
+
## Setup
|
15
|
+
|
16
|
+
### General
|
17
|
+
|
18
|
+
A running [redis](https://redis.io) server is required.
|
19
|
+
|
20
|
+
You'll be prompted for the master password on every action and if *yamp* hasn't
|
21
|
+
already been set up on the specified redis db instance the input will be used as the
|
22
|
+
new master password, else the input gets validated against an existing master password.
|
23
|
+
|
24
|
+
### Configuration File
|
25
|
+
|
26
|
+
*yamp* creates a YAML configuration file in your home directory called *.yamp*
|
27
|
+
where you can customize some behavior and specify another redis server to connect to
|
28
|
+
instead of the local unprotected standard redis instance which is used by default.
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
Each command prompts interactively for the master password which is used to encrypt and
|
33
|
+
decrypt the data on the inside. When creating entries and no password is specified
|
34
|
+
explicitly a random and secure 32 byte password will be generated and used instead.
|
35
|
+
While a password is always mandatory for an entry a username is not.
|
36
|
+
|
37
|
+
## Important notes
|
38
|
+
|
39
|
+
This project is still under active development and is yet to see many upcoming
|
40
|
+
changes, bug fixes and also feature implementations.
|
data/bin/yamp
CHANGED
@@ -8,6 +8,9 @@ require 'yamp'
|
|
8
8
|
|
9
9
|
ARGV << '-h' if ARGV.empty?
|
10
10
|
|
11
|
+
CONFIG = ENV['HOME'] + "/.yamp"
|
12
|
+
PASSWORD_CHARS = [*'0'..'9', *'a'..'z',*'A'..'Z','#','!','?','$','/','(',')','[',']']
|
13
|
+
|
11
14
|
trap "SIGINT" do puts "\nk bye!"; exit(130) end
|
12
15
|
|
13
16
|
class String
|
@@ -16,9 +19,6 @@ class String
|
|
16
19
|
def yellow; "\033[33m#{self}\033[0m" end
|
17
20
|
end
|
18
21
|
|
19
|
-
CONFIG = ENV['HOME'] + "/.yamp"
|
20
|
-
PASSWORD_CHARS = [*'0'..'9', *'a'..'z',*'A'..'Z','#','!','?','$','/','(',')','[',']']
|
21
|
-
|
22
22
|
options = {}
|
23
23
|
OptionParser.new do |opts|
|
24
24
|
opts.banner = %q(
|
@@ -32,18 +32,20 @@ OptionParser.new do |opts|
|
|
32
32
|
yamp -g twitter --to-clipboard
|
33
33
|
yamp --delete facebook
|
34
34
|
)
|
35
|
-
opts.on('-a', '--add ENTRY', 'create an entry')
|
36
|
-
opts.on('-g', '--get ENTRY', 'read an entry')
|
37
|
-
opts.on('-u', '--update ENTRY', 'update an entry')
|
38
|
-
opts.on('-d', '--delete ENTRY', 'delete an entry')
|
39
|
-
opts.on('-p', '--password PWD', 'specify a password')
|
40
|
-
opts.on('-n', '--username USR', 'specify a username')
|
41
|
-
opts.on('-l', '--list', 'list all entries')
|
42
|
-
opts.on('-c', '--to-clipboard', 'copy password to clipboard')
|
35
|
+
opts.on('-a', '--add ENTRY', 'create an entry') {|id| options[:add] = id}
|
36
|
+
opts.on('-g', '--get ENTRY', 'read an entry') {|id| options[:get] = id}
|
37
|
+
opts.on('-u', '--update ENTRY', 'update an entry') {|id| options[:upd] = id}
|
38
|
+
opts.on('-d', '--delete ENTRY', 'delete an entry') {|id| options[:del] = id}
|
39
|
+
opts.on('-p', '--password PWD', 'specify a password') {|pwd| options[:pwd] = pwd}
|
40
|
+
opts.on('-n', '--username USR', 'specify a username') {|usr| options[:usr] = usr}
|
41
|
+
opts.on('-l', '--list', 'list all entries') {options[:lst] = true}
|
42
|
+
opts.on('-c', '--to-clipboard', 'copy password to clipboard') {options[:clip] = true}
|
43
|
+
opts.on('-e', '--export FILE', 'export all entries to file') {|f| options[:exp] = f}
|
44
|
+
opts.on('-i', '--import FILE', 'import all entries from file') {|f| options[:imp] = f}
|
43
45
|
end.parse!
|
44
46
|
|
45
|
-
unless (options.keys & [:add, :del, :upd, :get, :lst]).size == 1
|
46
|
-
puts
|
47
|
+
unless (options.keys & [:add, :del, :upd, :get, :lst, :exp, :imp]).size == 1
|
48
|
+
puts 'ERR zero or too many actions specified'
|
47
49
|
exit
|
48
50
|
end
|
49
51
|
|
@@ -62,7 +64,7 @@ cfg = YAML.load_file(CONFIG)
|
|
62
64
|
|
63
65
|
print cfg['ui']['prompt']
|
64
66
|
master = STDIN.noecho(&:gets).chomp!; puts "\n"
|
65
|
-
redis
|
67
|
+
redis = Redis.new(
|
66
68
|
url: "redis://" +
|
67
69
|
(cfg['redis']['protected'] ? ":#{master}@" : "") +
|
68
70
|
"#{cfg['redis']['ip']}:" +
|
@@ -79,9 +81,9 @@ end
|
|
79
81
|
if id = options[:add]
|
80
82
|
pwd = options[:pwd] ? options[:pwd] : 32.times.map {PASSWORD_CHARS.sample}.join
|
81
83
|
unless @vault.add id, pwd, options[:usr]
|
82
|
-
puts
|
84
|
+
puts 'ERR entry already exists'
|
83
85
|
else
|
84
|
-
puts
|
86
|
+
puts '+'.green + " #{id}"
|
85
87
|
Clipboard.copy pwd if options[:clip]
|
86
88
|
end
|
87
89
|
exit
|
@@ -89,16 +91,16 @@ end
|
|
89
91
|
|
90
92
|
if id = options[:del]
|
91
93
|
unless @vault.remove id
|
92
|
-
puts
|
94
|
+
puts 'ERR no such entry'
|
93
95
|
else
|
94
|
-
puts
|
96
|
+
puts '-'.red + " #{id}"
|
95
97
|
end
|
96
98
|
exit
|
97
99
|
end
|
98
100
|
|
99
101
|
if id = options[:upd]
|
100
102
|
unless options[:pwd] || options[:usr]
|
101
|
-
puts
|
103
|
+
puts 'ERR no new data'
|
102
104
|
exit
|
103
105
|
end
|
104
106
|
pwd_updated = false
|
@@ -110,9 +112,9 @@ if id = options[:upd]
|
|
110
112
|
usr_updated = @vault.update id, :usr, usr
|
111
113
|
end
|
112
114
|
unless pwd_updated || usr_updated
|
113
|
-
puts
|
115
|
+
puts 'ERR no such entry'
|
114
116
|
else
|
115
|
-
puts
|
117
|
+
puts '*'.yellow + " #{id}"
|
116
118
|
Clipboard.copy pwd if options[:clip]
|
117
119
|
end
|
118
120
|
exit
|
@@ -122,7 +124,7 @@ if id = options[:get]
|
|
122
124
|
password = @vault.get id, :pwd
|
123
125
|
username = @vault.get id, :usr
|
124
126
|
unless password || username
|
125
|
-
puts
|
127
|
+
puts 'ERR no such entry'
|
126
128
|
else
|
127
129
|
puts username if username
|
128
130
|
unless options[:clip]
|
@@ -138,3 +140,23 @@ if options[:lst]
|
|
138
140
|
puts @vault.list.sort
|
139
141
|
exit
|
140
142
|
end
|
143
|
+
|
144
|
+
if export_file = options[:exp]
|
145
|
+
begin
|
146
|
+
@vault.export export_file
|
147
|
+
puts '>'.red + " #{export_file}"
|
148
|
+
rescue Exception => e
|
149
|
+
puts e.message
|
150
|
+
exit
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
if import_file = options[:imp]
|
155
|
+
begin
|
156
|
+
@vault.import import_file
|
157
|
+
puts '<'.green + " #{import_file}"
|
158
|
+
rescue Exception => e
|
159
|
+
puts e.message
|
160
|
+
exit
|
161
|
+
end
|
162
|
+
end
|
data/lib/yamp.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
|
2
2
|
require 'redis'
|
3
3
|
require 'openssl'
|
4
|
+
require 'yaml'
|
4
5
|
|
5
6
|
module YAMP
|
6
7
|
|
@@ -11,7 +12,7 @@ module YAMP
|
|
11
12
|
master_hash = redis.get "__mstr_h"
|
12
13
|
master_salt = redis.get "__mstr_s"
|
13
14
|
if master_hash && master_salt
|
14
|
-
@master_key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(master, master_salt, 10000, 32)
|
15
|
+
@master_key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(master, hex_to_bytes(master_salt), 10000, 32)
|
15
16
|
unless OpenSSL::Digest::SHA512.hexdigest(@master_key) == master_hash
|
16
17
|
raise ArgumentError, 'ERR invalid password'
|
17
18
|
end
|
@@ -19,7 +20,7 @@ module YAMP
|
|
19
20
|
salt = OpenSSL::Random.random_bytes(32)
|
20
21
|
@master_key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(master, salt, 10000, 32)
|
21
22
|
redis.set "__mstr_h", OpenSSL::Digest::SHA512.hexdigest(@master_key)
|
22
|
-
redis.set "__mstr_s", salt
|
23
|
+
redis.set "__mstr_s", bytes_to_hex(salt)
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
@@ -52,28 +53,61 @@ module YAMP
|
|
52
53
|
@redis.keys - %w{__mstr_h __mstr_s}
|
53
54
|
end
|
54
55
|
|
56
|
+
def export file
|
57
|
+
begin
|
58
|
+
File.open file, 'w' do |f|
|
59
|
+
f.write "__mstr_h: #{@redis.get '__mstr_h'}\n"
|
60
|
+
f.write "__mstr_s: #{@redis.get '__mstr_s'}\n"
|
61
|
+
@redis.keys.select {|k| @redis.type(k) == "hash"} .each do |k|
|
62
|
+
f.write(({k => @redis.hgetall(k)}).to_yaml[3..-1])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
rescue Errno::ENOENT
|
66
|
+
raise ArgumentError, 'ERR invalid path'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def import file
|
71
|
+
begin
|
72
|
+
data = YAML.load_file file
|
73
|
+
@redis.flushdb
|
74
|
+
data.each do |key, val|
|
75
|
+
if key.match "^__mstr_[h|s]"
|
76
|
+
@redis.set key, val
|
77
|
+
next
|
78
|
+
end
|
79
|
+
data[key].each {|k, v| @redis.hset key, k, v}
|
80
|
+
end
|
81
|
+
rescue Errno::ENOENT
|
82
|
+
raise ArgumentError, 'ERR file not found'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
55
86
|
private
|
56
87
|
|
88
|
+
def bytes_to_hex s; s.unpack('H*').first end
|
89
|
+
def hex_to_bytes s; s.scan(/../).map {|x| x.hex}.pack('c*') end
|
90
|
+
|
57
91
|
def encrypt id, data
|
58
92
|
cipher = OpenSSL::Cipher.new 'chacha20'
|
59
93
|
cipher.encrypt
|
60
94
|
iv = @redis.hget id, "__iv"
|
61
95
|
unless iv
|
62
96
|
iv = cipher.random_iv
|
63
|
-
@redis.hset id, "__iv", iv
|
97
|
+
@redis.hset id, "__iv", bytes_to_hex(iv)
|
64
98
|
else
|
65
|
-
cipher.iv = iv
|
99
|
+
cipher.iv = hex_to_bytes(iv)
|
66
100
|
end
|
67
101
|
cipher.key = @master_key
|
68
|
-
cipher.update(data) + cipher.final
|
102
|
+
bytes_to_hex(cipher.update(data) + cipher.final)
|
69
103
|
end
|
70
104
|
|
71
105
|
def decrypt id, data
|
72
106
|
cipher = OpenSSL::Cipher.new 'chacha20'
|
73
107
|
cipher.decrypt
|
74
|
-
cipher.iv = @redis.hget id, "__iv"
|
108
|
+
cipher.iv = hex_to_bytes(@redis.hget id, "__iv")
|
75
109
|
cipher.key = @master_key
|
76
|
-
cipher.update(data) + cipher.final
|
110
|
+
cipher.update(hex_to_bytes(data)) + cipher.final
|
77
111
|
end
|
78
112
|
|
79
113
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yamp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tobias Heilig
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-09-
|
11
|
+
date: 2017-09-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -57,8 +57,10 @@ email: holy708145@gmail.com
|
|
57
57
|
executables:
|
58
58
|
- yamp
|
59
59
|
extensions: []
|
60
|
-
extra_rdoc_files:
|
60
|
+
extra_rdoc_files:
|
61
|
+
- README.md
|
61
62
|
files:
|
63
|
+
- README.md
|
62
64
|
- bin/yamp
|
63
65
|
- lib/yamp.rb
|
64
66
|
homepage: https://github.com/HIGHphen/yamp
|