vault-tree 0.1.0 → 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -3
- data/CHANGE_LOG.md +15 -0
- data/Gemfile.lock +15 -5
- data/README.md +12 -15
- data/Rakefile +19 -4
- data/features/.nav +11 -0
- data/features/contracts/asymmetric_vault.feature +23 -0
- data/features/contracts/block_chain_key_transfer.feature +34 -0
- data/features/contracts/one_two_three.feature +22 -0
- data/features/contracts/readme.md +111 -0
- data/features/contracts_and_vaults.md +134 -0
- data/features/contributing_to_vault_tree.md +42 -0
- data/features/decision_tree.md +16 -0
- data/features/enforcement_problem.md +20 -0
- data/features/exceptions.feature +56 -5
- data/features/install_and_usage.md +57 -0
- data/features/keywords/assembled_shamir_key.feature +57 -0
- data/features/keywords/contents.feature +24 -0
- data/features/keywords/decryption_key.feature +10 -0
- data/features/keywords/dh_key.feature +56 -0
- data/features/keywords/external_data.feature +11 -0
- data/features/keywords/generated_shamir_key.feature +55 -0
- data/features/keywords/key.feature +38 -0
- data/features/keywords/master_passphrase.feature +68 -0
- data/features/keywords/public_encryption_key.feature +14 -0
- data/features/keywords/random_number.feature +44 -0
- data/features/keywords/readme.md +3 -0
- data/features/keywords/split_key.feature +54 -0
- data/features/keywords/unlocked.feature +51 -0
- data/features/manipulating_contracts.md +84 -0
- data/features/readme.md +6 -0
- data/features/steps/asymmetric_vault.steps.rb +41 -0
- data/features/steps/block_chain_key_transfer.steps.rb +43 -0
- data/features/steps/core.steps.rb +57 -104
- data/features/steps/exceptions.steps.rb +45 -1
- data/features/steps/one_two_three.steps.rb +57 -0
- data/features/steps/secret_sharing.steps.rb +36 -0
- data/features/support/contract_fixtures/asymmetric_vault.0.1.0.json +69 -0
- data/{spec/support/fixtures → features/support/contract_fixtures}/blank_simple_test_contract.json +0 -0
- data/features/support/contract_fixtures/block_chain_key_transfer.0.1.0.json +59 -0
- data/{spec/support/fixtures → features/support/contract_fixtures}/broken_contract.json +0 -0
- data/features/support/contract_fixtures/one_two_three.0.7.0.json +108 -0
- data/{spec/support/fixtures → features/support/contract_fixtures}/simple_test_contract.json +0 -0
- data/features/support/contract_fixtures/template.json +33 -0
- data/features/what_is_vault_tree.md +18 -0
- data/lib/vault-tree.rb +23 -6
- data/lib/vault-tree/contract/close_validator.rb +0 -7
- data/lib/vault-tree/contract/contract.rb +13 -2
- data/lib/vault-tree/contract/doorman.rb +22 -21
- data/lib/vault-tree/contract/vault.rb +18 -2
- data/lib/vault-tree/exceptions/exception_template.erb +0 -0
- data/lib/vault-tree/exceptions/failed_unlock_attempt.rb +6 -0
- data/lib/vault-tree/exceptions/vault_tree_exception.rb +18 -0
- data/lib/vault-tree/keywords/assembled_shamir_key.rb +44 -0
- data/lib/vault-tree/keywords/{vault_contents.rb → contents.rb} +0 -0
- data/lib/vault-tree/keywords/decryption_key.rb +1 -6
- data/lib/vault-tree/keywords/{shared_key.rb → dh_key.rb} +2 -2
- data/lib/vault-tree/keywords/external_data.rb +19 -0
- data/lib/vault-tree/keywords/generated_shamir_key.rb +57 -0
- data/lib/vault-tree/keywords/key.rb +13 -0
- data/lib/vault-tree/keywords/keyword_interpreter.rb +6 -6
- data/lib/vault-tree/keywords/public_encryption_key.rb +1 -5
- data/lib/vault-tree/keywords/random_number.rb +1 -1
- data/lib/vault-tree/keywords/split_key.rb +19 -0
- data/lib/vault-tree/keywords/unlocked.rb +1 -1
- data/lib/vault-tree/lock_smith.rb +182 -0
- data/lib/vault-tree/lock_smith/assembled_shamir_key.rb +64 -0
- data/lib/vault-tree/lock_smith/dh_key_pair.rb +10 -0
- data/lib/vault-tree/lock_smith/generated_shamir_key.rb +65 -0
- data/lib/vault-tree/lock_smith/split_key.rb +23 -0
- data/lib/vault-tree/{config/path_helpers.rb → path_helpers.rb} +26 -2
- data/lib/vault-tree/util/json.rb +1 -0
- data/lib/vault-tree/{config → util}/string.rb +1 -5
- data/lib/vault-tree/version.rb +1 -1
- data/spec/assembled_shamir_key_spec.rb +79 -0
- data/spec/generated_shamir_key_spec.rb +52 -0
- data/spec/lock_smith_spec.rb +90 -0
- data/spec/secret_sharing_spec.rb +43 -0
- data/support/scripts/libsodium_ubuntu.sh +1 -1
- data/vault-tree.gemspec +3 -2
- metadata +123 -41
- data/features/core.feature +0 -44
- data/lib/vault-tree/config/dependencies.rb +0 -4
- data/lib/vault-tree/config/lib.rb +0 -2
- data/lib/vault-tree/lock_smith/asymmetric_cipher.rb +0 -31
- data/lib/vault-tree/lock_smith/crypto_hash.rb +0 -11
- data/lib/vault-tree/lock_smith/digital_signature.rb +0 -32
- data/lib/vault-tree/lock_smith/encryption_key_pair.rb +0 -25
- data/lib/vault-tree/lock_smith/random_number.rb +0 -11
- data/lib/vault-tree/lock_smith/shared_key_pair.rb +0 -12
- data/lib/vault-tree/lock_smith/signing_key_pair.rb +0 -25
- data/lib/vault-tree/lock_smith/symmetric_cipher.rb +0 -25
- data/spec/app/locksmith/asymmetric_cipher_spec.rb +0 -25
- data/spec/app/locksmith/signing_key_pair_spec.rb +0 -22
- data/spec/spec_helper.rb +0 -5
- data/spec/support/fixtures/one_two_three-0.5.0.EXP.json +0 -105
- data/spec/support/fixtures/reference_contract.1.0.0.json +0 -227
@@ -7,15 +7,11 @@ module VaultTree
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def evaluate
|
10
|
-
|
10
|
+
LockSmith.new(private_key: decryption_key).generate_public_key
|
11
11
|
end
|
12
12
|
|
13
13
|
private
|
14
14
|
|
15
|
-
def key_pair
|
16
|
-
LockSmith::EncryptionKeyPair.new
|
17
|
-
end
|
18
|
-
|
19
15
|
def decryption_key
|
20
16
|
begin
|
21
17
|
contract.retrieve_contents(vault_id)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module VaultTree
|
2
|
+
class SplitKey < Keyword
|
3
|
+
attr_reader :required_key_vaults
|
4
|
+
|
5
|
+
def post_initialize(arg_array)
|
6
|
+
@required_key_vaults = arg_array
|
7
|
+
end
|
8
|
+
|
9
|
+
def evaluate
|
10
|
+
SplitKeyCrypto.new(required_keys: required_keys).generate
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def required_keys
|
16
|
+
required_key_vaults.map {|id| contract.retrieve_contents(id) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'rbnacl'
|
2
|
+
|
3
|
+
module VaultTree
|
4
|
+
# VaultTree::LockSmith Interface to Crypto Primatives
|
5
|
+
#
|
6
|
+
# This class provides a interface all of the cryptographic functions used
|
7
|
+
# by Vault Tree.
|
8
|
+
#
|
9
|
+
# Specifically, it exposed the functionallity of the RbNaCl Gem and the
|
10
|
+
# vault-tree-ss secret sharing gem.
|
11
|
+
class LockSmith
|
12
|
+
|
13
|
+
def initialize(opts = {})
|
14
|
+
@message = opts[:message]
|
15
|
+
@cipher_text = opts[:cipher_text]
|
16
|
+
@secret_key = opts[:secret_key]
|
17
|
+
@private_key = opts[:private_key]
|
18
|
+
@public_key = opts[:public_key]
|
19
|
+
@signing_key = opts[:signing_key]
|
20
|
+
@verify_key = opts[:verify_key]
|
21
|
+
@signature = opts[:signature]
|
22
|
+
@outstanding_secret_shares = opts[:outstanding_secret_shares]
|
23
|
+
@secret_recovery_threshold = opts[:secret_recovery_threshold]
|
24
|
+
@secret_shares = opts[:secret_shares]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Random Key for Semetric Encryption
|
28
|
+
#
|
29
|
+
# @return [String] Hex encoded Secret Key
|
30
|
+
def generate_secret_key
|
31
|
+
bin2hex RbNaCl::Hash.sha256(RbNaCl::Random.random_bytes(128))
|
32
|
+
end
|
33
|
+
|
34
|
+
# Randomly Generated Private Key
|
35
|
+
#
|
36
|
+
# @return [String] Hex encoded Private Key
|
37
|
+
def generate_private_key
|
38
|
+
bin2hex(RbNaCl::PrivateKey.generate.to_bytes)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Public Key Derived from a given private key
|
42
|
+
#
|
43
|
+
# @return [String] Hex encoded Public Key
|
44
|
+
def generate_public_key
|
45
|
+
bin2hex(RbNaCl::PrivateKey.new(private_key).public_key.to_bytes)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Randomly Generated Signing Key
|
49
|
+
#
|
50
|
+
# @return [String] Hex encoded Signing Key
|
51
|
+
def generate_signing_key
|
52
|
+
bin2hex(RbNaCl::SigningKey.generate.to_bytes)
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# Public Signature Verification Key
|
57
|
+
# Derived from a given signing key
|
58
|
+
#
|
59
|
+
# @return [String] Hex encoded Verify Key
|
60
|
+
def generate_verify_key
|
61
|
+
bin2hex(RbNaCl::SigningKey.new(signing_key).verify_key.to_bytes)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Check the validity of a message's signature
|
65
|
+
# Will raise RbNaCl::BadSignatureError if the signature check fails
|
66
|
+
def verify_message
|
67
|
+
RbNaCl::VerifyKey.new(verify_key).verify(message, signature)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Symmetric Encryption of the given message
|
71
|
+
#
|
72
|
+
# @return [String] Hex encoded message ciphertext
|
73
|
+
def symmetric_encrypt
|
74
|
+
bin2hex RbNaCl::RandomNonceBox.from_secret_key(secret_key).box(message)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Symmetric Decryption of the given ciphertext
|
78
|
+
#
|
79
|
+
# @return [String] Decoded plaintext message
|
80
|
+
def symmetric_decrypt
|
81
|
+
RbNaCl::RandomNonceBox.from_secret_key(secret_key).open(cipher_text)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Asymmetric Encryption of the given message
|
85
|
+
#LockSmith.new()
|
86
|
+
#
|
87
|
+
# @return [String] Hex encoded message ciphertext
|
88
|
+
def asymmetric_encrypt
|
89
|
+
pri = RbNaCl::PrivateKey.new(private_key)
|
90
|
+
pub = RbNaCl::PublicKey.new(public_key)
|
91
|
+
box = RbNaCl::Box.new(pub,pri)
|
92
|
+
bin2hex RbNaCl::RandomNonceBox.new(box).box(message)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Asymmetric Decryption of the given ciphertext
|
96
|
+
#
|
97
|
+
# @return [String] Decoded plaintext message
|
98
|
+
def asymmetric_decrypt
|
99
|
+
pri = RbNaCl::PrivateKey.new(private_key)
|
100
|
+
pub = RbNaCl::PublicKey.new(public_key)
|
101
|
+
box = RbNaCl::Box.new(pub,pri)
|
102
|
+
RbNaCl::RandomNonceBox.new(box).open(cipher_text)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Random number generation
|
106
|
+
#
|
107
|
+
# This uses the underlying source of random number generation on the OS, so
|
108
|
+
# /dev/urandom on UNIX-like systems, and the MS crypto providor on windows.
|
109
|
+
def random_number
|
110
|
+
bin2hex RbNaCl::Random.random_bytes
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns the SHA-256 hash of the given data
|
114
|
+
#
|
115
|
+
# There's no streaming done, just pass in the data and be done with it.
|
116
|
+
#
|
117
|
+
# @raise [CryptoError] If the hashing fails for some reason.
|
118
|
+
#
|
119
|
+
# @return [String] The SHA-256 hash as hex
|
120
|
+
def secure_hash
|
121
|
+
bin2hex RbNaCl::Hash.sha256(message)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Sign a message with the signing key
|
125
|
+
def sign_message
|
126
|
+
bin2hex RbNaCl::SigningKey.new(signing_key).sign(message)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Check the validity of a message's signature
|
130
|
+
# Will raise RbNaCl::BadSignatureError if the signature check fails
|
131
|
+
def verify_message
|
132
|
+
RbNaCl::VerifyKey.new(verify_key).verify(signature, message)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Recovers the shared secret from the shares provided
|
136
|
+
# in the initializer.
|
137
|
+
#
|
138
|
+
# @return [String] Secure Hash digest of the assembled secret
|
139
|
+
def combine_secret_shares
|
140
|
+
end
|
141
|
+
|
142
|
+
# Secret Shares associated with the split message
|
143
|
+
#
|
144
|
+
# @return [Array] Array of strings
|
145
|
+
def split_secret
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def message; @message end
|
151
|
+
|
152
|
+
# Locksmith always expects hex representations
|
153
|
+
# of keys and ciphertext. Convert to binary to
|
154
|
+
# give to RbNaCl.
|
155
|
+
def cipher_text; hex2bin @cipher_text end
|
156
|
+
def private_key; hex2bin @private_key end
|
157
|
+
def public_key; hex2bin @public_key end
|
158
|
+
def secret_key; hex2bin @secret_key end
|
159
|
+
def signing_key; hex2bin @signing_key end
|
160
|
+
def verify_key; hex2bin @verify_key end
|
161
|
+
def signature; hex2bin @signature end
|
162
|
+
|
163
|
+
# Hex encodes a message
|
164
|
+
#
|
165
|
+
# @param [String] The bytes to encode
|
166
|
+
#
|
167
|
+
# @return [String] hexadecimal
|
168
|
+
def bin2hex(bytes)
|
169
|
+
bytes.to_s.unpack("H*").first
|
170
|
+
end
|
171
|
+
|
172
|
+
# Hex decodes a message
|
173
|
+
#
|
174
|
+
# @param [String] hex to decode.
|
175
|
+
#
|
176
|
+
# @return [String] crisp and clean bytes
|
177
|
+
def hex2bin(hex)
|
178
|
+
[hex.to_s].pack("H*")
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'secretsharing'
|
2
|
+
|
3
|
+
module VaultTree
|
4
|
+
module Crypto
|
5
|
+
class AssembledShamirKey
|
6
|
+
attr_reader :key_shares
|
7
|
+
|
8
|
+
# Creates a new AssembledShamirKey object
|
9
|
+
#
|
10
|
+
# @param [Hash]
|
11
|
+
# @option opts [Array] :key_shares Secret Shares as strings
|
12
|
+
#
|
13
|
+
# @return [AssembledShamirKey]
|
14
|
+
def initialize(params)
|
15
|
+
@key_shares = params[:key_shares]
|
16
|
+
validate_key_shares
|
17
|
+
end
|
18
|
+
|
19
|
+
# Recovers the shared secret from the shares provided
|
20
|
+
# in the initializer.
|
21
|
+
#
|
22
|
+
# @return [String] Secure Hash digest of the assembled secret
|
23
|
+
def assemble
|
24
|
+
LockSmith.new(message: assembled_secret).secure_hash
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def assembled_secret
|
30
|
+
assemble_secret_object
|
31
|
+
assembled_object_string
|
32
|
+
end
|
33
|
+
|
34
|
+
def assemble_secret_object
|
35
|
+
key_shares.each { |s| assembled_object << s }
|
36
|
+
end
|
37
|
+
|
38
|
+
def assembled_object
|
39
|
+
@assembled_object ||= SecretSharing::Shamir.new(total_key_shares)
|
40
|
+
end
|
41
|
+
|
42
|
+
def assembled_object_string
|
43
|
+
assembled_object.secret.to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
def total_key_shares
|
47
|
+
key_shares.length
|
48
|
+
end
|
49
|
+
|
50
|
+
def validate_key_shares
|
51
|
+
raise(TypeError, 'nil key_shares passed') if key_shares.nil?
|
52
|
+
raise(TypeError) if any_none_string_key_shares?
|
53
|
+
end
|
54
|
+
|
55
|
+
def any_none_string_key_shares?
|
56
|
+
key_shares.any? { |s| not_a_string?(s)}
|
57
|
+
end
|
58
|
+
|
59
|
+
def not_a_string?(s)
|
60
|
+
! s.kind_of?(String)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'secretsharing'
|
2
|
+
|
3
|
+
module VaultTree
|
4
|
+
module Crypto
|
5
|
+
class GeneratedShamirKey
|
6
|
+
|
7
|
+
def initialize(params)
|
8
|
+
@outstanding_shares = params[:outstanding_shares]
|
9
|
+
@recovery_threshold = params[:recovery_threshold]
|
10
|
+
end
|
11
|
+
|
12
|
+
# String representation of the newly generated sharmir key
|
13
|
+
#
|
14
|
+
# @return [String] Secure Hash digest of the generated secret integer
|
15
|
+
def key
|
16
|
+
create_secret
|
17
|
+
LockSmith.new(message: secret_string).secure_hash
|
18
|
+
end
|
19
|
+
|
20
|
+
# Shares associated with the newly generated Sharmir key
|
21
|
+
#
|
22
|
+
# @return [Array] Array of strings
|
23
|
+
def shares
|
24
|
+
create_secret
|
25
|
+
shares_array.map{|s| s.to_s}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Fixnum representation if string value given
|
29
|
+
#
|
30
|
+
# @return [FixNum]
|
31
|
+
def outstanding_shares
|
32
|
+
@outstanding_shares.to_i
|
33
|
+
end
|
34
|
+
|
35
|
+
# Fixnum representation if string value given
|
36
|
+
#
|
37
|
+
# @return [FixNum]
|
38
|
+
def recovery_threshold
|
39
|
+
@recovery_threshold.to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def secret_string
|
45
|
+
shamir_object.secret.to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def shares_array
|
49
|
+
shamir_object.shares
|
50
|
+
end
|
51
|
+
|
52
|
+
def shamir_object
|
53
|
+
@shamir_object ||= SecretSharing::Shamir.new(outstanding_shares, recovery_threshold)
|
54
|
+
end
|
55
|
+
|
56
|
+
def create_secret
|
57
|
+
shamir_object.create_random_secret unless secret_set?
|
58
|
+
end
|
59
|
+
|
60
|
+
def secret_set?
|
61
|
+
shamir_object.secret_set?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module VaultTree
|
2
|
+
class SplitKeyCrypto
|
3
|
+
attr_reader :required_keys
|
4
|
+
|
5
|
+
def initialize(opts)
|
6
|
+
@required_keys = opts[:required_keys]
|
7
|
+
end
|
8
|
+
|
9
|
+
def generate
|
10
|
+
secure_hash key_digests.join('')
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def key_digests
|
16
|
+
required_keys.map{|k| secure_hash(k) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def secure_hash(s)
|
20
|
+
LockSmith.new(message: s).secure_hash
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -7,7 +7,7 @@ module VaultTree
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def walk_to_root
|
10
|
-
'
|
10
|
+
'../../'
|
11
11
|
end
|
12
12
|
|
13
13
|
def project_dir
|
@@ -27,13 +27,17 @@ module VaultTree
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def fixtures_dir
|
30
|
-
"#{project_dir}/
|
30
|
+
"#{project_dir}/features/support/contract_fixtures"
|
31
31
|
end
|
32
32
|
|
33
33
|
def reference_contract
|
34
34
|
"#{fixtures_dir}/reference_contract.1.0.0.json"
|
35
35
|
end
|
36
36
|
|
37
|
+
def shared_secret_contract
|
38
|
+
"#{fixtures_dir}/shared_secret_contract.json"
|
39
|
+
end
|
40
|
+
|
37
41
|
def broken_contract
|
38
42
|
"#{fixtures_dir}/broken_contract.json"
|
39
43
|
end
|
@@ -45,5 +49,25 @@ module VaultTree
|
|
45
49
|
def simple_test_contract
|
46
50
|
"#{fixtures_dir}/simple_test_contract.json"
|
47
51
|
end
|
52
|
+
|
53
|
+
def exceptions_files
|
54
|
+
Dir["#{project_dir}/lib/vault-tree/exceptions/*.rb"]
|
55
|
+
end
|
56
|
+
|
57
|
+
def keywords_files
|
58
|
+
Dir["#{project_dir}/lib/vault-tree/keywords/*.rb"]
|
59
|
+
end
|
60
|
+
|
61
|
+
def lock_smith_files
|
62
|
+
Dir["#{project_dir}/lib/vault-tree/lock_smith/*.rb"]
|
63
|
+
end
|
64
|
+
|
65
|
+
def contract_files
|
66
|
+
Dir["#{project_dir}/lib/vault-tree/contract/*.rb"]
|
67
|
+
end
|
68
|
+
|
69
|
+
def core_contracts(file)
|
70
|
+
"#{fixtures_dir}/#{file}"
|
71
|
+
end
|
48
72
|
end
|
49
73
|
end
|