trustworthy 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +51 -19
- data/bin/trustworthy +2 -140
- data/lib/trustworthy/cli/add_key.rb +22 -0
- data/lib/trustworthy/cli/command.rb +63 -0
- data/lib/trustworthy/cli/decrypt.rb +52 -0
- data/lib/trustworthy/cli/encrypt.rb +52 -0
- data/lib/trustworthy/cli/helpers.rb +64 -0
- data/lib/trustworthy/cli/init.rb +50 -0
- data/lib/trustworthy/cli.rb +47 -0
- data/lib/trustworthy/master_key.rb +14 -13
- data/lib/trustworthy/settings.rb +30 -37
- data/lib/trustworthy/version.rb +1 -1
- data/lib/trustworthy.rb +7 -4
- data/spec/spec_helper.rb +19 -7
- data/spec/trustworthy/cli/add_key_spec.rb +37 -0
- data/spec/trustworthy/cli/command_spec.rb +114 -0
- data/spec/trustworthy/cli/decrypt_spec.rb +63 -0
- data/spec/trustworthy/cli/encrypt_spec.rb +64 -0
- data/spec/trustworthy/cli/init_spec.rb +111 -0
- data/spec/trustworthy/master_key_spec.rb +12 -12
- data/spec/trustworthy/settings_spec.rb +54 -59
- metadata +51 -52
- data/lib/trustworthy/crypto.rb +0 -50
- data/spec/trustworthy/crypto_spec.rb +0 -42
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fc925ea8d2efecd58ea4c6a1e821c68a39d6934e
|
4
|
+
data.tar.gz: 40e0962287a78553690fce334db7721ad2775a53
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 43f7de2e9c4b1b20f18d81e973f85e32e692e13dc5f7a614c7a075fdcb3688b5eba18b0696257f7fdfecf3d55af49a6968d124efa574585f11cd1f0dfeba6e72
|
7
|
+
data.tar.gz: 267b8564a9787c5966d5fc81221a3904f1418083b3d3e835eeb4288a5be4e35a9e60bff9fd9083fbb81dd54823a5caa742e91a11f02510e7e98565a6350f5f18
|
data/README.md
CHANGED
@@ -1,35 +1,67 @@
|
|
1
|
-
|
1
|
+
# Trustworthy [![Build Status](https://secure.travis-ci.org/jtdowney/trustworthy.png?branch=master)](http://travis-ci.org/jtdowney/trustworthy)
|
2
2
|
|
3
|
-
Implements a special case (k = 2) of [Adi Shamir's](http://en.wikipedia.org/wiki/Adi_Shamir) [secret sharing algorithm](http://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing). This allows secret files to be encrypted on disk
|
3
|
+
Implements a special case (k = 2) of [Adi Shamir's](http://en.wikipedia.org/wiki/Adi_Shamir) [secret sharing algorithm](http://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing). This allows secret files to be encrypted on disk and require two secret holders to decrypt it.
|
4
4
|
|
5
|
-
|
5
|
+
## Usage
|
6
6
|
|
7
|
-
|
7
|
+
### Generate a new master key
|
8
8
|
|
9
|
-
trustworthy init
|
9
|
+
trustworthy init
|
10
10
|
|
11
|
-
This will create a new master key and ask for two users to be added who can decrypt the master key. The information about users and secrets is stored in trustworthy.yml.
|
11
|
+
This will create a new master key and ask for two users to be added who can decrypt the master key. The information about users and secrets is stored in trustworthy.yml. With all trustworthy commands you can also specify the configuration file to work with using `-c` or `--config`.
|
12
12
|
|
13
|
-
|
13
|
+
trustworthy init -c myconfig.yml
|
14
14
|
|
15
|
-
|
15
|
+
### Add an additional user key to the master key
|
16
16
|
|
17
|
-
|
17
|
+
trustworthy add-key
|
18
18
|
|
19
|
-
|
19
|
+
The master key will be loaded so that a new user key can be added to the configuration.
|
20
20
|
|
21
|
-
|
21
|
+
### Encrypt a file
|
22
22
|
|
23
|
-
|
23
|
+
trustworthy encrypt -i foo.txt -o foo.txt.tw
|
24
24
|
|
25
|
-
|
25
|
+
The master key will be loaded and then used to encrypt the file specified.
|
26
26
|
|
27
|
-
|
27
|
+
### Decrypt a file
|
28
28
|
|
29
|
-
|
29
|
+
trustworthy decrypt -i foo.txt.tw -o foo.txt
|
30
30
|
|
31
|
-
|
31
|
+
Decrypting works similar to encrypting, first the master key will be loaded and then used to decrypt the file.
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
## Configuration format
|
34
|
+
|
35
|
+
The configuration uses ruby's `YAML::Store` to provide a simple transactional data store. The salt is used in combination with the user password to derive a key using [scrypt](http://www.tarsnap.com/scrypt.html). That derived key encrypts the point for Shamir's algorithm.
|
36
|
+
|
37
|
+
---
|
38
|
+
a-user:
|
39
|
+
salt: 400$8$28$4c7fc59a31a9f38a
|
40
|
+
encrypted_point: !binary |-
|
41
|
+
CWcT++kRSAw/0IAVRX6KKAfwbyWBX7ZUh4dZNxL8An413CvRL2tUhWlwsKVl
|
42
|
+
ZKyzmc6VjpKqS4ZpGoPPUCu6xrIo5LkLwXVIpDsBx7SCoK72uEsxd9x0GW5i
|
43
|
+
9Mf8r40KE3gUCudntBXlxduDrqWZgW1uFFCg+U3ACt28GzGftOGjYW6PCAZO
|
44
|
+
N35aHYpGHWhddWeFvbaXNrAPtLSiVWSNW35RU2qo+HS+uSYGO65r9viCXC8f
|
45
|
+
3yLZsPjtouDRyMEv5xOPVZKnvsf3Ju3EBH7Abyw/zezS2LvzrtmxHTN5yF92
|
46
|
+
xphq0imIR52Yj2/k6pdRz/X/8ZsdS+HEifvvRBM+oVKQ2PQh4MIFPJuE0CWA
|
47
|
+
iPnpqdvjYt0M7BsodX2K897A
|
48
|
+
another-user:
|
49
|
+
salt: 400$8$28$9dceab0f3414ab23
|
50
|
+
encrypted_point: !binary |-
|
51
|
+
7B1hXxwwXDU0vTLAPj6U9+WWT3o4i1r7prPOamgStDjvv7f0gZp0D3T56gZk
|
52
|
+
b+2Q4zwyhTM4p6DS0xdG3lfhnkQEYQ6tROnLbI1O7IvuOmFVsDNLej9ps7hJ
|
53
|
+
e1kdFiLaF3efRYtHs2GYdEVrRWWDFgfLDVFVoFbqDruRX1ltTVuaJvS9f7Qb
|
54
|
+
FPI8a2gJ0sl+1B5eBJeR1Chbdn3rHxK7SHq+J/SAJV7xKmkQa6B8g2V1D3xE
|
55
|
+
oB45Gmgm9o1s1/van72ckT91HPh55B8tHnjeZZwdHEp7Z8lyLrDxhbpQm7ql
|
56
|
+
ESpbM8BvdFCmzns5ZSku5Jgc78MwQ5YO1y/QXY+s9so7SDLI9yF18q4no81f
|
57
|
+
sNpbmdY+NolXChlDRZcZ9qJk
|
58
|
+
|
59
|
+
## Reference
|
60
|
+
|
61
|
+
* RSA Labs - [http://www.rsa.com/rsalabs/node.asp?id=2259](http://www.rsa.com/rsalabs/node.asp?id=2259)
|
62
|
+
* ssss - [http://point-at-infinity.org/ssss/](http://point-at-infinity.org/ssss/)
|
63
|
+
* Secret sharing on Wikipedia - [http://en.wikipedia.org/wiki/Secret_sharing](http://en.wikipedia.org/wiki/Secret_sharing)
|
64
|
+
|
65
|
+
## License
|
66
|
+
|
67
|
+
Trustworthy is released under the [MIT license](http://www.opensource.org/licenses/MIT).
|
data/bin/trustworthy
CHANGED
@@ -1,145 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
|
-
require 'commander/import'
|
5
4
|
require 'trustworthy'
|
5
|
+
require 'trustworthy/cli'
|
6
6
|
|
7
|
-
|
8
|
-
program :description, 'Trustworthy secret sharing'
|
9
|
-
|
10
|
-
command 'init' do |c|
|
11
|
-
c.syntax = 'trustworthy init [options]'
|
12
|
-
c.summary = 'Create a new master key'
|
13
|
-
c.description = 'Used to generate a setup a new master key for trustworthy'
|
14
|
-
c.example 'create a new master key named foo', 'trustworthy init -c foo.yml'
|
15
|
-
c.option '-k', '--keys NUMBER', Integer, 'Initial number of keys to create (must be 2 or more, defaults to 2)'
|
16
|
-
c.option '-c', '--config FILE', String, 'Configuration file'
|
17
|
-
c.action do |args, options|
|
18
|
-
options.default :keys => 2
|
19
|
-
|
20
|
-
raise Commander::Runner::InvalidCommandError.new('Must provide a config file') unless options.config
|
21
|
-
raise Commander::Runner::InvalidCommandError.new('Cannot generate fewer than two sub-keys') if options.keys < 2
|
22
|
-
|
23
|
-
say "Creating a new master key with #{options.keys} sub-keys."
|
24
|
-
|
25
|
-
master_key = Trustworthy::MasterKey.create
|
26
|
-
settings = Trustworthy::Settings.new
|
27
|
-
|
28
|
-
options.keys.times do
|
29
|
-
add_user_key(settings, master_key)
|
30
|
-
end
|
31
|
-
|
32
|
-
settings.write(options.config)
|
33
|
-
say "Created #{options.config}"
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
command 'add-user' do |c|
|
38
|
-
c.syntax = 'trustworthy add-user [options]'
|
39
|
-
c.summary = 'Add a new user sub-key'
|
40
|
-
c.description = 'Load the master key and create a new user sub-key for it.'
|
41
|
-
c.example 'add a new user the master key', 'trustworthy add-key -c foo.yml'
|
42
|
-
c.option '-k', '--keys NUMBER', Integer, 'Number of keys to create'
|
43
|
-
c.option '-c', '--config FILE', String, 'Configuration file'
|
44
|
-
c.action do |args, options|
|
45
|
-
options.default :keys => 1
|
46
|
-
|
47
|
-
raise Commander::Runner::InvalidCommandError.new('Must provide a config file') unless options.config
|
48
|
-
raise Commander::Runner::InvalidCommandError.new('Cannot generate fewer than one key') if options.keys < 1
|
49
|
-
|
50
|
-
settings = Trustworthy::Settings.load(options.config)
|
51
|
-
master_key = load_master_key(settings)
|
52
|
-
|
53
|
-
options.keys.times do
|
54
|
-
add_user_key(settings, master_key)
|
55
|
-
end
|
56
|
-
|
57
|
-
settings.write(options.config)
|
58
|
-
say "Updated #{options.config} with #{options.keys} new key(s)"
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
command 'add-secret' do |c|
|
63
|
-
c.syntax = 'trustworthy add-secret [options]'
|
64
|
-
c.summary = 'Add a new secret'
|
65
|
-
c.description = 'Loads the master key and uses it to encrypt a new secret.'
|
66
|
-
c.example 'add a new secret with master key', 'trustworthy add-secret -c foo.yml -e ENCRYPTION_KEY -i encryption.key -o encryption.key.tw'
|
67
|
-
c.option '-e', '--environment ENVIRONMENT', 'Environment variable to store secret in when running exec'
|
68
|
-
c.option '-i', '--input FILE', 'File to be encrypted'
|
69
|
-
c.option '-o', '--output FILE', 'Encrypted output file'
|
70
|
-
c.option '-c', '--config FILE', String, 'Configuration file'
|
71
|
-
c.action do |args, options|
|
72
|
-
raise Commander::Runner::InvalidCommandError.new('Must provide a config file') unless options.config
|
73
|
-
raise Commander::Runner::InvalidCommandError.new('Must provide an environment variable') unless options.environment
|
74
|
-
raise Commander::Runner::InvalidCommandError.new('Must provide an input file') unless options.input
|
75
|
-
raise Commander::Runner::InvalidCommandError.new('Must provide an output file') unless options.output
|
76
|
-
|
77
|
-
settings = Trustworthy::Settings.load(options.config)
|
78
|
-
master_key = load_master_key(settings)
|
79
|
-
|
80
|
-
plaintext = File.read(options.input)
|
81
|
-
ciphertext = master_key.encrypt(plaintext)
|
82
|
-
|
83
|
-
File.open(options.output, 'w') do |file|
|
84
|
-
file.write(ciphertext)
|
85
|
-
end
|
86
|
-
|
87
|
-
settings.add_secret(options.environment, options.output)
|
88
|
-
settings.write(options.config)
|
89
|
-
say "Updated #{options.config} with the #{options.environment} secret"
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
command 'exec' do |c|
|
94
|
-
c.syntax = 'trustworthy exec -c [config file] program'
|
95
|
-
c.summary = 'Load secrets and run program'
|
96
|
-
c.description = 'Loads and decrypted all secrets with the master key and stores them in the environment before forking program'
|
97
|
-
c.example 'run rails console', 'trustworthy -c foo.yml rails console'
|
98
|
-
c.option '-c', '--config FILE', String, 'Configuration file'
|
99
|
-
c.action do |args, options|
|
100
|
-
raise Commander::Runner::InvalidCommandError.new('Must provide a config file') unless options.config
|
101
|
-
|
102
|
-
settings = Trustworthy::Settings.load(options.config)
|
103
|
-
master_key = load_master_key(settings)
|
104
|
-
|
105
|
-
environment = {}
|
106
|
-
settings.secrets.each do |key, encrypted_file|
|
107
|
-
ciphertext = File.read(encrypted_file)
|
108
|
-
environment[key] = master_key.decrypt(ciphertext)
|
109
|
-
end
|
110
|
-
|
111
|
-
pid = POSIX::Spawn.spawn(environment, *args)
|
112
|
-
Process.waitpid(pid)
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def load_master_key(settings)
|
117
|
-
keys = []
|
118
|
-
2.times do
|
119
|
-
username = ask('Username: ')
|
120
|
-
password = password('Password: ', '')
|
121
|
-
keys << settings.unlock_key(username, password)
|
122
|
-
say "Unlocked key for #{username}."
|
123
|
-
end
|
124
|
-
|
125
|
-
Trustworthy::MasterKey.create_from_keys(*keys)
|
126
|
-
end
|
127
|
-
|
128
|
-
def add_user_key(settings, master_key)
|
129
|
-
key = master_key.create_key
|
130
|
-
username = ask('Username: ').to_s
|
131
|
-
|
132
|
-
password = nil
|
133
|
-
loop do
|
134
|
-
password = password('Password: ', '')
|
135
|
-
password_confirm = password('Password (again): ', '')
|
136
|
-
if password == password_confirm
|
137
|
-
break
|
138
|
-
else
|
139
|
-
say_error 'Password mismatch'
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
settings.add_key(key, username, password)
|
144
|
-
say "User #{username} added."
|
145
|
-
end
|
7
|
+
Trustworthy::CLI.new.run(ARGV)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Trustworthy
|
2
|
+
class CLI
|
3
|
+
class AddKey
|
4
|
+
include Trustworthy::CLI::Command
|
5
|
+
|
6
|
+
def self.description
|
7
|
+
'Add a new user key for a master key'
|
8
|
+
end
|
9
|
+
|
10
|
+
def run(args)
|
11
|
+
options = parse_options('add-key', args)
|
12
|
+
info 'Adding a new key to master key'
|
13
|
+
|
14
|
+
Trustworthy::Settings.open(options[:config_file]) do |settings|
|
15
|
+
master_key = unlock_master_key(settings)
|
16
|
+
username = add_key(settings, master_key)
|
17
|
+
info "Added #{username} to #{options[:config_file]}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Trustworthy
|
2
|
+
class CLI
|
3
|
+
module Command
|
4
|
+
include Trustworthy::CLI::Helpers
|
5
|
+
|
6
|
+
def default_options
|
7
|
+
{ :config_file => 'trustworthy.yml' }
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse_options(command, args)
|
11
|
+
options = default_options
|
12
|
+
@parser = OptionParser.new do |opts|
|
13
|
+
opts.banner = "#{Trustworthy::CLI.banner}\n\nUsage: trustworthy #{command} [options]\n"
|
14
|
+
opts.on('-c', '--config FILE', 'Configuration file to use (default: trustworthy.yml)') do |file|
|
15
|
+
options[:config_file] = file
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
19
|
+
puts opts
|
20
|
+
exit
|
21
|
+
end
|
22
|
+
|
23
|
+
if block_given?
|
24
|
+
yield opts, options
|
25
|
+
end
|
26
|
+
end
|
27
|
+
@parser.parse!(args)
|
28
|
+
options
|
29
|
+
end
|
30
|
+
|
31
|
+
def print_help
|
32
|
+
if @parser
|
33
|
+
puts @parser
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def ask(prompt)
|
38
|
+
$terminal.ask(prompt).to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
def ask_password(prompt)
|
42
|
+
$terminal.ask(prompt) { |q| q.echo = false }.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
def error(message)
|
46
|
+
say_color(message, :error)
|
47
|
+
end
|
48
|
+
|
49
|
+
def info(message)
|
50
|
+
say_color(message, :info)
|
51
|
+
end
|
52
|
+
|
53
|
+
def say(message)
|
54
|
+
$terminal.say(message)
|
55
|
+
end
|
56
|
+
|
57
|
+
def say_color(message, color)
|
58
|
+
colored_message = $terminal.color(message, color)
|
59
|
+
say(colored_message)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Trustworthy
|
2
|
+
class CLI
|
3
|
+
class Decrypt
|
4
|
+
include Trustworthy::CLI::Command
|
5
|
+
|
6
|
+
def self.description
|
7
|
+
'Decrypt a file using the master key'
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse_options(args)
|
11
|
+
super('encrypt', args) do |opts, options|
|
12
|
+
opts.on('-i', '--input FILE', 'File to decrypt') do |file|
|
13
|
+
options[:input_file] = file
|
14
|
+
end
|
15
|
+
|
16
|
+
opts.on('-o', '--output FILE', 'File to write decrypted contents to') do |file|
|
17
|
+
options[:output_file] = file
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def run(args)
|
23
|
+
options = parse_options(args)
|
24
|
+
|
25
|
+
unless options.has_key?(:input_file)
|
26
|
+
error 'Must provide an input file'
|
27
|
+
print_help
|
28
|
+
return
|
29
|
+
end
|
30
|
+
|
31
|
+
unless options.has_key?(:output_file)
|
32
|
+
error 'Must provide an output file'
|
33
|
+
print_help
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
File.open(options[:input_file], 'rb') do |input_file|
|
38
|
+
ciphertext = input_file.read
|
39
|
+
Trustworthy::Settings.open(options[:config_file]) do |settings|
|
40
|
+
master_key = unlock_master_key(settings)
|
41
|
+
plaintext = master_key.decrypt(ciphertext)
|
42
|
+
File.open(options[:output_file], 'wb+') do |output_file|
|
43
|
+
output_file.write(plaintext)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
info "Decrypted #{options[:input_file]} to #{options[:output_file]}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Trustworthy
|
2
|
+
class CLI
|
3
|
+
class Encrypt
|
4
|
+
include Trustworthy::CLI::Command
|
5
|
+
|
6
|
+
def self.description
|
7
|
+
'Encrypt a file using the master key'
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse_options(args)
|
11
|
+
super('encrypt', args) do |opts, options|
|
12
|
+
opts.on('-i', '--input FILE', 'File to encrypt') do |file|
|
13
|
+
options[:input_file] = file
|
14
|
+
end
|
15
|
+
|
16
|
+
opts.on('-o', '--output FILE', 'File to write encrypted contents to') do |file|
|
17
|
+
options[:output_file] = file
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def run(args)
|
23
|
+
options = parse_options(args)
|
24
|
+
|
25
|
+
unless options.has_key?(:input_file)
|
26
|
+
error 'Must provide an input file'
|
27
|
+
print_help
|
28
|
+
return
|
29
|
+
end
|
30
|
+
|
31
|
+
unless options.has_key?(:output_file)
|
32
|
+
error 'Must provide an output file'
|
33
|
+
print_help
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
File.open(options[:input_file], 'rb') do |input_file|
|
38
|
+
plaintext = input_file.read
|
39
|
+
Trustworthy::Settings.open(options[:config_file]) do |settings|
|
40
|
+
master_key = unlock_master_key(settings)
|
41
|
+
ciphertext = master_key.encrypt(plaintext)
|
42
|
+
File.open(options[:output_file], 'wb+') do |output_file|
|
43
|
+
output_file.write(ciphertext)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
info "Encrypted #{options[:input_file]} to #{options[:output_file]}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Trustworthy
|
2
|
+
class CLI
|
3
|
+
module Helpers
|
4
|
+
def add_key(settings, master_key)
|
5
|
+
key = master_key.create_key
|
6
|
+
username = ask('Username: ')
|
7
|
+
|
8
|
+
loop do
|
9
|
+
password = ask_password('Password: ')
|
10
|
+
password_confirm = ask_password('Password (again): ')
|
11
|
+
if password == password_confirm
|
12
|
+
settings.add_key(key, username, password)
|
13
|
+
break
|
14
|
+
else
|
15
|
+
error 'Passwords do not match.'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
username
|
20
|
+
end
|
21
|
+
|
22
|
+
def unlock_master_key(settings)
|
23
|
+
usernames_in_use = []
|
24
|
+
|
25
|
+
username1, key1 = _unlock_key(settings, usernames_in_use)
|
26
|
+
usernames_in_use << username1
|
27
|
+
|
28
|
+
username2, key2 = _unlock_key(settings, usernames_in_use)
|
29
|
+
|
30
|
+
master_key = Trustworthy::MasterKey.create_from_keys(key1, key2)
|
31
|
+
info "Reconstructed master key"
|
32
|
+
|
33
|
+
master_key
|
34
|
+
end
|
35
|
+
|
36
|
+
def _unlock_key(settings, usernames_in_use)
|
37
|
+
username = nil
|
38
|
+
loop do
|
39
|
+
username = ask('Username: ')
|
40
|
+
if usernames_in_use.include?(username)
|
41
|
+
error "Key #{username} is already in use"
|
42
|
+
elsif settings.find_key(username).nil?
|
43
|
+
error "Key #{username} does not exist"
|
44
|
+
else
|
45
|
+
break
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
key = nil
|
50
|
+
begin
|
51
|
+
password = ask_password('Password: ')
|
52
|
+
key = settings.unlock_key(username, password)
|
53
|
+
rescue ArgumentError
|
54
|
+
error "Password incorrect for #{username}"
|
55
|
+
retry
|
56
|
+
end
|
57
|
+
|
58
|
+
info "Unlocked #{username}"
|
59
|
+
|
60
|
+
[username, key]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Trustworthy
|
2
|
+
class CLI
|
3
|
+
class Init
|
4
|
+
include Trustworthy::CLI::Command
|
5
|
+
|
6
|
+
def self.description
|
7
|
+
'Generate a new master key and user keys'
|
8
|
+
end
|
9
|
+
|
10
|
+
def default_options
|
11
|
+
{ :keys => 2 }.merge(super)
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse_options(args)
|
15
|
+
super('init', args) do |opts, options|
|
16
|
+
opts.on('-k', '--keys N', OptionParser::DecimalInteger, 'Number of keys to generate (default: 2, minimum: 2)') do |k|
|
17
|
+
options[:keys] = k
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def run(args)
|
23
|
+
options = parse_options(args)
|
24
|
+
|
25
|
+
if options[:keys] < 2
|
26
|
+
error "Must generate at least two keys"
|
27
|
+
print_help
|
28
|
+
return
|
29
|
+
end
|
30
|
+
|
31
|
+
Trustworthy::Settings.open(options[:config_file]) do |settings|
|
32
|
+
unless settings.empty?
|
33
|
+
error "Config #{options[:config_file]} already exists"
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
info "Creating a new master key with #{options[:keys]} keys"
|
38
|
+
|
39
|
+
master_key = Trustworthy::MasterKey.create
|
40
|
+
options[:keys].times do
|
41
|
+
username = add_key(settings, master_key)
|
42
|
+
info "Key #{username} added"
|
43
|
+
end
|
44
|
+
|
45
|
+
info "Created #{options[:config_file]}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'highline/import'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
require 'trustworthy/cli/helpers'
|
5
|
+
require 'trustworthy/cli/command'
|
6
|
+
require 'trustworthy/cli/add_key'
|
7
|
+
require 'trustworthy/cli/init'
|
8
|
+
require 'trustworthy/cli/decrypt'
|
9
|
+
require 'trustworthy/cli/encrypt'
|
10
|
+
|
11
|
+
HighLine.color_scheme = HighLine::SampleColorScheme.new
|
12
|
+
|
13
|
+
module Trustworthy
|
14
|
+
class CLI
|
15
|
+
include Trustworthy::CLI::Helpers
|
16
|
+
|
17
|
+
Commands = {
|
18
|
+
'add-key' => Trustworthy::CLI::AddKey,
|
19
|
+
'init' => Trustworthy::CLI::Init,
|
20
|
+
'decrypt' => Trustworthy::CLI::Decrypt,
|
21
|
+
'encrypt' => Trustworthy::CLI::Encrypt
|
22
|
+
}
|
23
|
+
|
24
|
+
def self.banner
|
25
|
+
"Trustworthy CLI v#{Trustworthy::VERSION}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def run(args)
|
29
|
+
command = args.shift
|
30
|
+
if Commands.has_key?(command)
|
31
|
+
klass = Commands[command]
|
32
|
+
klass.new.run(args)
|
33
|
+
else
|
34
|
+
_print_help
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def _print_help
|
39
|
+
say "#{Trustworthy::CLI.banner}\n\n"
|
40
|
+
say 'Commands:'
|
41
|
+
Commands.each do |name, klass|
|
42
|
+
say ' %-8s %s' % [name, klass.description]
|
43
|
+
end
|
44
|
+
say "\nSee 'trustworthy <command> --help' for more information on a specific command"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Trustworthy
|
2
2
|
class MasterKey
|
3
|
+
attr_reader :slope, :intercept
|
4
|
+
|
3
5
|
def self.create
|
4
6
|
slope = Trustworthy::Random.number
|
5
7
|
intercept = Trustworthy::Random.number
|
@@ -17,32 +19,31 @@ module Trustworthy
|
|
17
19
|
@intercept = intercept
|
18
20
|
end
|
19
21
|
|
22
|
+
def ==(other)
|
23
|
+
@slope == other.slope && @intercept == other.intercept
|
24
|
+
end
|
25
|
+
|
20
26
|
def create_key
|
21
27
|
Trustworthy::Key.create(@slope, @intercept)
|
22
28
|
end
|
23
29
|
|
24
30
|
def encrypt(plaintext)
|
25
|
-
|
26
|
-
|
27
|
-
signature + ciphertext
|
31
|
+
nonce = Trustworthy::Cipher.generate_nonce
|
32
|
+
nonce + _cipher.encrypt(nonce, '', plaintext)
|
28
33
|
end
|
29
34
|
|
30
35
|
def decrypt(ciphertext)
|
31
36
|
ciphertext.force_encoding('BINARY') if ciphertext.respond_to?(:force_encoding)
|
32
|
-
|
33
|
-
ciphertext = ciphertext.slice(
|
34
|
-
|
35
|
-
_crypto.decrypt(ciphertext)
|
37
|
+
nonce = ciphertext.slice(0, Trustworthy::Cipher.nonce_len)
|
38
|
+
ciphertext = ciphertext.slice(Trustworthy::Cipher.nonce_len..-1)
|
39
|
+
_cipher.decrypt(nonce, '', ciphertext)
|
36
40
|
end
|
37
41
|
|
38
|
-
def
|
39
|
-
return @crypto if @crypto
|
40
|
-
|
42
|
+
def _cipher
|
41
43
|
secret = @intercept.to_s('F')
|
42
44
|
hkdf = HKDF.new(secret)
|
43
|
-
|
44
|
-
|
45
|
-
@crypto = Trustworthy::Crypto.new(encryption_key, authentication_key)
|
45
|
+
key = hkdf.next_bytes(Trustworthy::Cipher.key_len)
|
46
|
+
Trustworthy::Cipher.new(key)
|
46
47
|
end
|
47
48
|
end
|
48
49
|
end
|