yamp 0.1.0 → 1.0.0
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.
- 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
|