trustworthy 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +35 -0
- data/bin/trustworthy +145 -0
- data/lib/trustworthy/crypto.rb +50 -0
- data/lib/trustworthy/key.rb +16 -0
- data/lib/trustworthy/master_key.rb +48 -0
- data/lib/trustworthy/random.rb +21 -0
- data/lib/trustworthy/settings.rb +63 -0
- data/lib/trustworthy/version.rb +3 -0
- data/lib/trustworthy.rb +13 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/trustworthy/crypto_spec.rb +42 -0
- data/spec/trustworthy/key_spec.rb +11 -0
- data/spec/trustworthy/master_key_spec.rb +87 -0
- data/spec/trustworthy/random_spec.rb +28 -0
- data/spec/trustworthy/settings_spec.rb +82 -0
- metadata +181 -0
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
## Trustworthy [![Build Status](https://secure.travis-ci.org/jtdowney/trustworthy.png?branch=master)](http://travis-ci.org/jtdowney/trustworthy)
|
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 but loaded into a process on start if two of the keys are available.
|
4
|
+
|
5
|
+
### Uses
|
6
|
+
|
7
|
+
#### Generate a new master key
|
8
|
+
|
9
|
+
trustworthy init -c trustworthy.yml
|
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.
|
12
|
+
|
13
|
+
#### Add an additional user to the master key
|
14
|
+
|
15
|
+
trustworthy add-user -c trustworthy.yml
|
16
|
+
|
17
|
+
The master key will be loaded so that a new user can be added to trustworthy.yml.
|
18
|
+
|
19
|
+
#### Add a secret
|
20
|
+
|
21
|
+
trustworthy add-secret -c trustworthy.yml -e ENCRYPTION_KEY -i encryption.key -o encryption.key.tw
|
22
|
+
|
23
|
+
Secrets are the main purpose behind trustworthy. This will encrypt the file using the master key and will load it into the given environment variable when executed.
|
24
|
+
|
25
|
+
#### Run a program under trustworthy
|
26
|
+
|
27
|
+
trustworthy exec -c trustworthy.yml rails console
|
28
|
+
|
29
|
+
The master key will be loaded so all secrets can be decrypted and stored in the environment. After that the given program will be spawned and inherit the environment.
|
30
|
+
|
31
|
+
### Reference
|
32
|
+
|
33
|
+
RSA Labs - [http://www.rsa.com/rsalabs/node.asp?id=2259](http://www.rsa.com/rsalabs/node.asp?id=2259)
|
34
|
+
ssss - [http://point-at-infinity.org/ssss/](http://point-at-infinity.org/ssss/)
|
35
|
+
Secret sharing on Wikipedia - [http://en.wikipedia.org/wiki/Secret_sharing](http://en.wikipedia.org/wiki/Secret_sharing)
|
data/bin/trustworthy
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'commander/import'
|
5
|
+
require 'trustworthy'
|
6
|
+
|
7
|
+
program :version, Trustworthy::VERSION
|
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
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Trustworthy
|
2
|
+
class Crypto
|
3
|
+
def initialize(encryption_key, authentication_key)
|
4
|
+
@encryption_key = encryption_key
|
5
|
+
@authentication_key = authentication_key
|
6
|
+
@digest = OpenSSL::Digest.new('SHA256')
|
7
|
+
@cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
8
|
+
end
|
9
|
+
|
10
|
+
def decrypt(ciphertext)
|
11
|
+
ciphertext.force_encoding('BINARY') if ciphertext.respond_to?(:force_encoding)
|
12
|
+
iv = ciphertext.slice(0..15)
|
13
|
+
ciphertext = ciphertext.slice(16..-1)
|
14
|
+
@cipher.decrypt
|
15
|
+
@cipher.key = @encryption_key
|
16
|
+
@cipher.iv = iv
|
17
|
+
@cipher.update(ciphertext) + @cipher.final
|
18
|
+
end
|
19
|
+
|
20
|
+
def encrypt(plaintext)
|
21
|
+
iv = Trustworthy::Random.bytes(16)
|
22
|
+
@cipher.encrypt
|
23
|
+
@cipher.key = @encryption_key
|
24
|
+
@cipher.iv = iv
|
25
|
+
ciphertext = @cipher.update(plaintext) + @cipher.final
|
26
|
+
iv + ciphertext
|
27
|
+
end
|
28
|
+
|
29
|
+
def sign(data)
|
30
|
+
OpenSSL::HMAC.digest(@digest, @authentication_key, data)
|
31
|
+
end
|
32
|
+
|
33
|
+
def valid_signature?(given_signature, data)
|
34
|
+
computed_signature = sign(data)
|
35
|
+
_secure_compare(given_signature, computed_signature)
|
36
|
+
end
|
37
|
+
|
38
|
+
def _secure_compare(a, b)
|
39
|
+
return false unless a.bytesize == b.bytesize
|
40
|
+
|
41
|
+
bytes = a.unpack("C#{a.bytesize}")
|
42
|
+
|
43
|
+
result = 0
|
44
|
+
b.each_byte do |byte|
|
45
|
+
result |= byte ^ bytes.shift
|
46
|
+
end
|
47
|
+
result == 0
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Trustworthy
|
2
|
+
class MasterKey
|
3
|
+
def self.create
|
4
|
+
slope = Trustworthy::Random.number
|
5
|
+
intercept = Trustworthy::Random.number
|
6
|
+
new(slope, intercept)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.create_from_keys(key1, key2)
|
10
|
+
slope = (key2.y - key1.y) / (key2.x - key1.x)
|
11
|
+
intercept = key1.y - (slope * key1.x)
|
12
|
+
new(slope, intercept)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(slope, intercept)
|
16
|
+
@slope = slope
|
17
|
+
@intercept = intercept
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_key
|
21
|
+
Trustworthy::Key.create(@slope, @intercept)
|
22
|
+
end
|
23
|
+
|
24
|
+
def encrypt(plaintext)
|
25
|
+
ciphertext = _crypto.encrypt(plaintext)
|
26
|
+
signature = _crypto.sign(ciphertext)
|
27
|
+
signature + ciphertext
|
28
|
+
end
|
29
|
+
|
30
|
+
def decrypt(ciphertext)
|
31
|
+
ciphertext.force_encoding('BINARY') if ciphertext.respond_to?(:force_encoding)
|
32
|
+
signature = ciphertext.slice(0..31)
|
33
|
+
ciphertext = ciphertext.slice(32..-1)
|
34
|
+
raise 'invalid signature' unless _crypto.valid_signature?(signature, ciphertext)
|
35
|
+
_crypto.decrypt(ciphertext)
|
36
|
+
end
|
37
|
+
|
38
|
+
def _crypto
|
39
|
+
return @crypto if @crypto
|
40
|
+
|
41
|
+
secret = @intercept.to_s('F')
|
42
|
+
hkdf = HKDF.new(secret)
|
43
|
+
encryption_key = hkdf.next_bytes(32)
|
44
|
+
authentication_key = hkdf.next_bytes(32)
|
45
|
+
@crypto = Trustworthy::Crypto.new(encryption_key, authentication_key)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Trustworthy
|
2
|
+
module Random
|
3
|
+
def self.number(size = 32)
|
4
|
+
raw_bytes = bytes(size)
|
5
|
+
number = raw_bytes.unpack('H*').first.hex
|
6
|
+
BigDecimal.new(number.to_s)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.bytes(size = 32)
|
10
|
+
flags = File::RDONLY
|
11
|
+
flags |= File::NOCTTY if defined? File::NOCTTY
|
12
|
+
File.open(_source, flags) do |file|
|
13
|
+
file.read(size)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self._source
|
18
|
+
'/dev/random'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Trustworthy
|
2
|
+
class Settings
|
3
|
+
attr_reader :keys, :secrets
|
4
|
+
|
5
|
+
def self.load(filename)
|
6
|
+
data = File.read(filename)
|
7
|
+
yaml = YAML.load(data)
|
8
|
+
new(yaml['keys'], yaml['secrets'])
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(keys = {}, secrets = {})
|
12
|
+
@keys = keys
|
13
|
+
@secrets = secrets
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_key(key, username, password)
|
17
|
+
salt = SCrypt::Engine.generate_salt
|
18
|
+
|
19
|
+
crypto = _crypto_from_password(salt, password)
|
20
|
+
plaintext = "#{key.x.to_s('F')},#{key.y.to_s('F')}"
|
21
|
+
ciphertext = crypto.encrypt(plaintext)
|
22
|
+
|
23
|
+
@keys[username] = {
|
24
|
+
'salt' => salt,
|
25
|
+
'ciphertext' => ciphertext,
|
26
|
+
'authentication' => crypto.sign(ciphertext)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_secret(environment, filename)
|
31
|
+
@secrets[environment] = filename
|
32
|
+
end
|
33
|
+
|
34
|
+
def unlock_key(username, password)
|
35
|
+
key_data = keys[username]
|
36
|
+
ciphertext = key_data['ciphertext']
|
37
|
+
signature = key_data['authentication']
|
38
|
+
salt = key_data['salt']
|
39
|
+
crypto = _crypto_from_password(salt, password)
|
40
|
+
raise 'invalid signature' unless crypto.valid_signature?(signature, ciphertext)
|
41
|
+
plaintext = crypto.decrypt(ciphertext)
|
42
|
+
x, y = plaintext.split(',').map { |n| BigDecimal.new(n) }
|
43
|
+
Trustworthy::Key.new(x, y)
|
44
|
+
end
|
45
|
+
|
46
|
+
def write(filename)
|
47
|
+
File.open(filename, 'w') do |file|
|
48
|
+
data = YAML.dump('keys' => @keys, 'secrets' => @secrets)
|
49
|
+
file.write(data)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def _crypto_from_password(salt, password)
|
54
|
+
cost, salt = salt.rpartition('$')
|
55
|
+
raw_key = SCrypt::Engine.scrypt(password, salt, cost, 64)
|
56
|
+
|
57
|
+
encryption_key = raw_key.slice(0, 32)
|
58
|
+
authentication_key = raw_key.slice(32, 32)
|
59
|
+
|
60
|
+
Trustworthy::Crypto.new(encryption_key, authentication_key)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/trustworthy.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'hkdf'
|
3
|
+
require 'openssl'
|
4
|
+
require 'posix/spawn'
|
5
|
+
require 'scrypt'
|
6
|
+
require 'securerandom'
|
7
|
+
require 'trustworthy/crypto'
|
8
|
+
require 'trustworthy/key'
|
9
|
+
require 'trustworthy/master_key'
|
10
|
+
require 'trustworthy/random'
|
11
|
+
require 'trustworthy/settings'
|
12
|
+
require 'trustworthy/version'
|
13
|
+
require 'yaml'
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'fakefs/safe'
|
2
|
+
|
3
|
+
require 'trustworthy'
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.order = 'random'
|
7
|
+
config.before(:each) do
|
8
|
+
Trustworthy::Random.stub(:_source).and_return('/dev/urandom')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Trustworthy
|
13
|
+
module TestValues
|
14
|
+
EncryptionKey = ['7fc6880ba7a75148e6b14a6ebca73b9861954835fd17237cbedb71a6ad3fefc4'].pack('H*')
|
15
|
+
AuthenticationKey = ['12e7489dc1b2e6da1213cac0df946615a7088eeccdd6b2d4bf330e9560fabd52'].pack('H*')
|
16
|
+
InitializationVector = ['39164ec082fb8b7336d3c5500af99dcb'].pack('H*')
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Trustworthy::Crypto do
|
4
|
+
before(:each) do
|
5
|
+
@crypto = Trustworthy::Crypto.new(Trustworthy::TestValues::EncryptionKey, Trustworthy::TestValues::AuthenticationKey)
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'sign' do
|
9
|
+
it 'should sign the input using the authentication key' do
|
10
|
+
signature = @crypto.sign('foobar')
|
11
|
+
signature.should == ['f9e948d9259d35e729b14e370a7456ec4b22d2a0a1d5daa1accbeb54f414865e'].pack('H*')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'valid_signature?' do
|
16
|
+
it 'should return true when the signature matches the data' do
|
17
|
+
given_signature = ['f9e948d9259d35e729b14e370a7456ec4b22d2a0a1d5daa1accbeb54f414865e'].pack('H*')
|
18
|
+
@crypto.should be_valid_signature(given_signature, 'foobar')
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should return false when the signature matches the data' do
|
22
|
+
given_signature = ['4d15a6427d6b56e07b819ff0a147dd8ff77b1b5fafd33f9071b0359755edde42'].pack('H*')
|
23
|
+
@crypto.should_not be_valid_signature(given_signature, 'foobar')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'encrypt' do
|
28
|
+
it 'should encrypt the input using the encryption key' do
|
29
|
+
Trustworthy::Random.stub(:bytes).and_return(Trustworthy::TestValues::InitializationVector)
|
30
|
+
ciphertext = @crypto.encrypt('foobar')
|
31
|
+
ciphertext.should == ['39164ec082fb8b7336d3c5500af99dcbf3af2b0abd6f2ac79f1a76cb4e092a7c'].pack('H*')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe 'decrypt' do
|
36
|
+
it 'should decrypt the input using the encryption key' do
|
37
|
+
ciphertext = ['39164ec082fb8b7336d3c5500af99dcbf3af2b0abd6f2ac79f1a76cb4e092a7c'].pack('H*')
|
38
|
+
plaintext = @crypto.decrypt(ciphertext)
|
39
|
+
plaintext.should == 'foobar'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Trustworthy::Key do
|
4
|
+
describe 'self.create' do
|
5
|
+
it 'should create a key along the slope and intercept' do
|
6
|
+
Trustworthy::Random.stub(:number).and_return(BigDecimal.new('10'))
|
7
|
+
key = Trustworthy::Key.create(6, 24)
|
8
|
+
key.y.should == 84
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Trustworthy::MasterKey do
|
4
|
+
it 'should encrypt and decrypt a master_key' do
|
5
|
+
master_key = Trustworthy::MasterKey.create
|
6
|
+
key1 = master_key.create_key
|
7
|
+
key2 = master_key.create_key
|
8
|
+
|
9
|
+
ciphertext = master_key.encrypt('foobar')
|
10
|
+
|
11
|
+
new_master_key = Trustworthy::MasterKey.create_from_keys(key1, key2)
|
12
|
+
plaintext = new_master_key.decrypt(ciphertext)
|
13
|
+
plaintext.should == 'foobar'
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should function with any 2 of n keys' do
|
17
|
+
master_key1 = Trustworthy::MasterKey.create
|
18
|
+
key1 = master_key1.create_key
|
19
|
+
key2 = master_key1.create_key
|
20
|
+
|
21
|
+
ciphertext = master_key1.encrypt('foobar')
|
22
|
+
|
23
|
+
master_key2 = Trustworthy::MasterKey.create_from_keys(key1, key2)
|
24
|
+
key3 = master_key2.create_key
|
25
|
+
|
26
|
+
master_key3 = Trustworthy::MasterKey.create_from_keys(key1, key3)
|
27
|
+
plaintext = master_key3.decrypt(ciphertext)
|
28
|
+
plaintext.should == 'foobar'
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'self.create' do
|
32
|
+
it 'should generate a random slope and intercept' do
|
33
|
+
Trustworthy::Random.stub(:number).and_return(BigDecimal.new('10'))
|
34
|
+
master_key = Trustworthy::MasterKey.create
|
35
|
+
key = master_key.create_key
|
36
|
+
key.x.should == 10
|
37
|
+
key.y.should == 110
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'self.create_from_keys' do
|
42
|
+
it 'should calculate the slope and intercept given two keys' do
|
43
|
+
Trustworthy::Random.stub(:number).and_return(BigDecimal.new('10'))
|
44
|
+
|
45
|
+
key1 = Trustworthy::Key.new(BigDecimal.new('2'), BigDecimal.new('30'))
|
46
|
+
key2 = Trustworthy::Key.new(BigDecimal.new('5'), BigDecimal.new('60'))
|
47
|
+
|
48
|
+
master_key = Trustworthy::MasterKey.create_from_keys(key1, key2)
|
49
|
+
new_key = master_key.create_key
|
50
|
+
new_key.x.should == 10
|
51
|
+
new_key.y.should == 110
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'create_key' do
|
56
|
+
it 'should define a new key' do
|
57
|
+
master_key = Trustworthy::MasterKey.new(BigDecimal.new('6'), BigDecimal.new('24'))
|
58
|
+
key = master_key.create_key
|
59
|
+
key.x.should_not == 0
|
60
|
+
key.y.should_not == 0
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'encrypt' do
|
65
|
+
it 'should encrypt and sign the data using the intercept' do
|
66
|
+
Trustworthy::Random.stub(:bytes).and_return(Trustworthy::TestValues::InitializationVector)
|
67
|
+
master_key = Trustworthy::MasterKey.new(BigDecimal.new('6'), BigDecimal.new('24'))
|
68
|
+
ciphertext = master_key.encrypt('foobar')
|
69
|
+
ciphertext.should == ['0438a89ef9e5792849ef611bff0e7b405a84ac515461489499679ca77ade6a6a39164ec082fb8b7336d3c5500af99dcbde056af859baa7e72c4c2661651e88d5'].pack('H*')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'decrypt' do
|
74
|
+
it 'should decrypt and verify the data using the intercept' do
|
75
|
+
master_key = Trustworthy::MasterKey.new(BigDecimal.new('6'), BigDecimal.new('24'))
|
76
|
+
ciphertext = ['0438a89ef9e5792849ef611bff0e7b405a84ac515461489499679ca77ade6a6a39164ec082fb8b7336d3c5500af99dcbde056af859baa7e72c4c2661651e88d5'].pack('H*')
|
77
|
+
plaintext = master_key.decrypt(ciphertext)
|
78
|
+
plaintext.should == 'foobar'
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should raise an invalid signature error if signatures do not match' do
|
82
|
+
master_key = Trustworthy::MasterKey.new(BigDecimal.new('6'), BigDecimal.new('24'))
|
83
|
+
ciphertext = ['00000000000000000000000000000000000000005461489499679ca77ade6a6a39164ec082fb8b7336d3c5500af99dcbde056af859baa7e72c4c2661651e88d5'].pack('H*')
|
84
|
+
expect { master_key.decrypt(ciphertext) }.to raise_error('invalid signature')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Trustworthy::Random do
|
4
|
+
describe 'self.number' do
|
5
|
+
it 'should return a random number' do
|
6
|
+
random = Trustworthy::Random.number
|
7
|
+
random.should_not == Trustworthy::Random.number
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should return a number up to a given byte size' do
|
11
|
+
random = Trustworthy::Random.number(2)
|
12
|
+
random.should <= 2 ** 16
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'self.bytes' do
|
17
|
+
it 'should return a string of the given length' do
|
18
|
+
bytes = Trustworthy::Random.bytes(16)
|
19
|
+
bytes.size.should == 16
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should return a random string' do
|
23
|
+
bytes1 = Trustworthy::Random.bytes(16)
|
24
|
+
bytes2 = Trustworthy::Random.bytes(16)
|
25
|
+
bytes1.should_not == bytes2
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Trustworthy::Settings do
|
4
|
+
before(:each) do
|
5
|
+
SCrypt::Engine.stub(:generate_salt).and_return('400$8$1b$3e31f076a3226825')
|
6
|
+
Trustworthy::Random.stub(:bytes).and_return(Trustworthy::TestValues::InitializationVector)
|
7
|
+
|
8
|
+
key = Trustworthy::Key.new(BigDecimal.new('2'), BigDecimal.new('3'))
|
9
|
+
@settings = Trustworthy::Settings.new
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'add_key' do
|
13
|
+
it 'encrypts and signs the key with the password' do
|
14
|
+
key = Trustworthy::Key.new(BigDecimal.new('2'), BigDecimal.new('3'))
|
15
|
+
settings = Trustworthy::Settings.new
|
16
|
+
settings.add_key(key, 'user', 'password1')
|
17
|
+
settings.keys['user']['salt'].should == '400$8$1b$3e31f076a3226825'
|
18
|
+
settings.keys['user']['authentication'].should == ['20c274efc68a460568853cc4be586183012769a312549d1aa57e29a36ec1503d'].pack('H*')
|
19
|
+
settings.keys['user']['ciphertext'].should == ['39164ec082fb8b7336d3c5500af99dcb17d2e60496ed553dfab4b05c568aa926'].pack('H*')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'add_secret' do
|
24
|
+
it 'adds a secret file name with an environment' do
|
25
|
+
settings = Trustworthy::Settings.new
|
26
|
+
settings.add_secret('foo', 'foo.enc')
|
27
|
+
settings.secrets['foo'].should == 'foo.enc'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'unlock_key' do
|
32
|
+
it 'verifies and decrypts the key with the password' do
|
33
|
+
key = Trustworthy::Key.new(BigDecimal.new('2'), BigDecimal.new('3'))
|
34
|
+
settings = Trustworthy::Settings.new
|
35
|
+
settings.add_key(key, 'user', 'password1')
|
36
|
+
unlocked_key = settings.unlock_key('user', 'password1')
|
37
|
+
unlocked_key.x.should == BigDecimal.new('2')
|
38
|
+
unlocked_key.y.should == BigDecimal.new('3')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
describe 'write' do
|
44
|
+
it 'writes key information to the given file' do
|
45
|
+
key = Trustworthy::Key.new(BigDecimal.new('2'), BigDecimal.new('3'))
|
46
|
+
settings = Trustworthy::Settings.new
|
47
|
+
settings.add_key(key, 'user', 'password1')
|
48
|
+
FakeFS do
|
49
|
+
settings.write('settings.yml')
|
50
|
+
|
51
|
+
data = File.read('settings.yml')
|
52
|
+
yaml = YAML.load(data)
|
53
|
+
yaml['keys']['user']['salt'].should == '400$8$1b$3e31f076a3226825'
|
54
|
+
yaml['keys']['user']['authentication'].should == ['20c274efc68a460568853cc4be586183012769a312549d1aa57e29a36ec1503d'].pack('H*')
|
55
|
+
yaml['keys']['user']['ciphertext'].should == ['39164ec082fb8b7336d3c5500af99dcb17d2e60496ed553dfab4b05c568aa926'].pack('H*')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'self.load' do
|
61
|
+
it 'loads the key information from the given file' do
|
62
|
+
FakeFS do
|
63
|
+
File.open('settings.yml', 'w') do |file|
|
64
|
+
file.write(<<-EOF)
|
65
|
+
keys:
|
66
|
+
user:
|
67
|
+
salt: 400$8$1b$3e31f076a3226825
|
68
|
+
ciphertext: !binary |-
|
69
|
+
ORZOwIL7i3M208VQCvmdyxfS5gSW7VU9+rSwXFaKqSY=
|
70
|
+
authentication: !binary |-
|
71
|
+
IMJ078aKRgVohTzEvlhhgwEnaaMSVJ0apX4po27BUD0=
|
72
|
+
EOF
|
73
|
+
end
|
74
|
+
|
75
|
+
settings = Trustworthy::Settings.load('settings.yml')
|
76
|
+
settings.keys['user']['salt'].should == '400$8$1b$3e31f076a3226825'
|
77
|
+
settings.keys['user']['authentication'].should == ['20c274efc68a460568853cc4be586183012769a312549d1aa57e29a36ec1503d'].pack('H*')
|
78
|
+
settings.keys['user']['ciphertext'].should == ['39164ec082fb8b7336d3c5500af99dcb17d2e60496ed553dfab4b05c568aa926'].pack('H*')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
metadata
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: trustworthy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- John Downey
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: commander
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '4.1'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4.1'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: hkdf
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.1.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.1.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: posix-spawn
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.3.6
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.3.6
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: scrypt
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.1'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '1.1'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: fakefs
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 0.4.0
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 0.4.0
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rspec
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '2.11'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '2.11'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rake
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
description: Implements a special case (k = 2) of Adi Shamir's secret sharing algorithm.
|
127
|
+
This allows secret files to be encrypted on disk but loaded into a process on start
|
128
|
+
if two keys are available.
|
129
|
+
email:
|
130
|
+
- jdowney@gmail.com
|
131
|
+
executables:
|
132
|
+
- trustworthy
|
133
|
+
extensions: []
|
134
|
+
extra_rdoc_files: []
|
135
|
+
files:
|
136
|
+
- lib/trustworthy/crypto.rb
|
137
|
+
- lib/trustworthy/key.rb
|
138
|
+
- lib/trustworthy/master_key.rb
|
139
|
+
- lib/trustworthy/random.rb
|
140
|
+
- lib/trustworthy/settings.rb
|
141
|
+
- lib/trustworthy/version.rb
|
142
|
+
- lib/trustworthy.rb
|
143
|
+
- spec/spec_helper.rb
|
144
|
+
- spec/trustworthy/crypto_spec.rb
|
145
|
+
- spec/trustworthy/key_spec.rb
|
146
|
+
- spec/trustworthy/master_key_spec.rb
|
147
|
+
- spec/trustworthy/random_spec.rb
|
148
|
+
- spec/trustworthy/settings_spec.rb
|
149
|
+
- bin/trustworthy
|
150
|
+
- README.md
|
151
|
+
homepage: http://github.com/jtdowney/trustworthy
|
152
|
+
licenses: []
|
153
|
+
post_install_message:
|
154
|
+
rdoc_options: []
|
155
|
+
require_paths:
|
156
|
+
- lib
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
158
|
+
none: false
|
159
|
+
requirements:
|
160
|
+
- - ! '>='
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '0'
|
163
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
|
+
none: false
|
165
|
+
requirements:
|
166
|
+
- - ! '>='
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
requirements: []
|
170
|
+
rubyforge_project:
|
171
|
+
rubygems_version: 1.8.24
|
172
|
+
signing_key:
|
173
|
+
specification_version: 3
|
174
|
+
summary: Launch processes while keeping secrets encrypted on disk
|
175
|
+
test_files:
|
176
|
+
- spec/spec_helper.rb
|
177
|
+
- spec/trustworthy/crypto_spec.rb
|
178
|
+
- spec/trustworthy/key_spec.rb
|
179
|
+
- spec/trustworthy/master_key_spec.rb
|
180
|
+
- spec/trustworthy/random_spec.rb
|
181
|
+
- spec/trustworthy/settings_spec.rb
|