trustworthy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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