trustworthy 0.1.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.
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,16 @@
1
+ module Trustworthy
2
+ class Key
3
+ attr_reader :x, :y
4
+
5
+ def self.create(slope, intercept)
6
+ x = Random.number
7
+ y = slope * x + intercept
8
+ new(x, y)
9
+ end
10
+
11
+ def initialize(x, y)
12
+ @x = x
13
+ @y = y
14
+ end
15
+ end
16
+ 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
@@ -0,0 +1,3 @@
1
+ module Trustworthy
2
+ VERSION = '0.1.0'
3
+ end
@@ -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'
@@ -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