tss 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGELOG.md +6 -0
- data/README.md +11 -12
- data/lib/tss/combiner.rb +4 -3
- data/lib/tss/custom_contracts.rb +66 -0
- data/lib/tss/hasher.rb +17 -7
- data/lib/tss/splitter.rb +7 -40
- data/lib/tss/tss.rb +15 -25
- data/lib/tss/util.rb +15 -0
- data/lib/tss/version.rb +1 -1
- metadata +3 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e448b355558f4f493400bac55a51fd14a8c56bf3
|
4
|
+
data.tar.gz: 537024e64cf91c9394784d76d9df61347add2041
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b69d52a5340fda8078e46e1ed95dc4c5a6e4ffbb54b5ac0295dce9fc2633decacf61a7616dff3c838772e3bb9e97be110d6a819a07fbf04d7f4ef0e8cfac9491
|
7
|
+
data.tar.gz: 5cc4fa4acc4e920dda21772432512da25b46bf9a16ac960496e0eb8cd0c0b2a3b5da979e4863e5571d0dcaa3962202ee9ec8bc3c98eb589b05ef8e8f5e653a0b
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## v0.3.0 (9/24/2016)
|
4
|
+
|
5
|
+
* Breaking change, identifier cannot be an empty string
|
6
|
+
* Much greater coverage of functions with Contracts
|
7
|
+
* Related documentation updates
|
8
|
+
|
3
9
|
## v0.2.0 (9/23/2016)
|
4
10
|
|
5
11
|
* clone the shares object passed to TSS.combine to prevent modification
|
data/README.md
CHANGED
@@ -57,8 +57,8 @@ command line or Ruby, with the default `3 out of 5` secret sharing.
|
|
57
57
|
### Command Line Interface (CLI)
|
58
58
|
|
59
59
|
```sh
|
60
|
-
$ echo 'secret unicode characters ½ ♥ 💩' |
|
61
|
-
$
|
60
|
+
$ echo 'secret unicode characters ½ ♥ 💩' | tss split -O /tmp/shares.txt
|
61
|
+
$ tss combine -I /tmp/shares.txt
|
62
62
|
|
63
63
|
RECOVERED SECRET METADATA
|
64
64
|
*************************
|
@@ -253,7 +253,7 @@ cautioned that this method may leave command history which may contain your
|
|
253
253
|
secret.
|
254
254
|
|
255
255
|
```text
|
256
|
-
echo 'a secret' |
|
256
|
+
echo 'a secret' | tss split -O /tmp/shares.txt
|
257
257
|
```
|
258
258
|
|
259
259
|
**Example : `--input-file`**
|
@@ -263,7 +263,7 @@ cautioned that storing the secret on a filesystem may expose you to certain
|
|
263
263
|
attacks since it can be hard to fully erase files once written.
|
264
264
|
|
265
265
|
```text
|
266
|
-
$
|
266
|
+
$ tss split -I /tmp/secret.txt
|
267
267
|
tss~v1~3f784d9d04dff796~3~M2Y3ODRkOWQwNGRmZjc5NgIDACsBvAVAo1dH3QrsYl0S30j2VVTu6dZLRe9EEk6zLtPTwZNYk-t1e4SAA41D
|
268
268
|
tss~v1~3f784d9d04dff796~3~M2Y3ODRkOWQwNGRmZjc5NgIDACsCMfy6YlI-CQrd-l93Xtw8YqOWSP5ToolKTaZyO2fPYzceaaVmB30ycdVr
|
269
269
|
tss~v1~3f784d9d04dff796~3~M2Y3ODRkOWQwNGRmZjc5NgIDACsD4IDasmAapmVFkuYPMvuavCtXiJgPZsaBHglGm4FU_U2rGZzfapRk-ZNN
|
@@ -279,7 +279,7 @@ in the command shell history or in a file on a filesystem that is hard to
|
|
279
279
|
erase securely. It does not protect you from simple keylogger attacks though.
|
280
280
|
|
281
281
|
```text
|
282
|
-
$
|
282
|
+
$ tss split
|
283
283
|
Enter your secret, enter a dot (.) on a line by itself to finish :
|
284
284
|
secret > the vault password is:
|
285
285
|
secret > V0ulT!
|
@@ -306,7 +306,7 @@ Here are some simple examples of using each:
|
|
306
306
|
**Example : `STDIN`**
|
307
307
|
|
308
308
|
```text
|
309
|
-
$ echo 'a secret' |
|
309
|
+
$ echo 'a secret' | tss split | tss combine
|
310
310
|
|
311
311
|
RECOVERED SECRET METADATA
|
312
312
|
*************************
|
@@ -322,8 +322,8 @@ a secret
|
|
322
322
|
**Example : `--input-file`**
|
323
323
|
|
324
324
|
```text
|
325
|
-
$ echo 'a secret' |
|
326
|
-
$
|
325
|
+
$ echo 'a secret' | tss split -O /tmp/shares.txt
|
326
|
+
$ tss combine -I /tmp/shares.txt
|
327
327
|
|
328
328
|
RECOVERED SECRET METADATA
|
329
329
|
*************************
|
@@ -351,7 +351,7 @@ tss~v1~ae2983e30e0471fe~3~YWUyOTgzZTMwZTA0NzFmZQIDACoDsnUaZf94pMArKZL7jmavzk9Qa6
|
|
351
351
|
tss~v1~ae2983e30e0471fe~3~YWUyOTgzZTMwZTA0NzFmZQIDACoETCWwSGLHcutzGCLb1yOkH7i6MF7j2b0wr0ZsUh6LuBmx6FkN2MbTkTc=
|
352
352
|
tss~v1~ae2983e30e0471fe~3~YWUyOTgzZTMwZTA0NzFmZQIDACoFazXEwgGBilMwY7k7DtDR9qqG7mgigMJLmIVB70eAULfQJ89xbXbONF4=
|
353
353
|
|
354
|
-
$
|
354
|
+
$ tss combine
|
355
355
|
Enter shares, one per line, and a dot (.) on a line by itself to finish :
|
356
356
|
share> tss~v1~ae2983e30e0471fe~3~YWUyOTgzZTMwZTA0NzFmZQIDACoBRjAH7wA0ncxJr3GliBqKxedf3bGaQH6AscuLqxtrKxxtxuC7A7a5Sqk=
|
357
357
|
share> tss~v1~ae2983e30e0471fe~3~YWUyOTgzZTMwZTA0NzFmZQIDACoClWVu75w-XHhoUgkbV5XaJ11stZCB5Z8HTArpv4QsIYDBpNO9DHQTbQ4=
|
@@ -402,14 +402,13 @@ between `1-255` inclusive. The `num_shares` value must be
|
|
402
402
|
greater-than-or-equal-to the `threshold` value. If you don't pass in
|
403
403
|
these options they will be set to `threshold = 3` and `num_shares = 5` by default.
|
404
404
|
|
405
|
-
The `identifier` is a `
|
405
|
+
The `identifier` is a `1-16` Byte String that will be embedded in
|
406
406
|
each output share and should uniquely identify a secret. All shares output
|
407
407
|
from the same secret splitting operation will have the same `identifier`. This
|
408
408
|
value can be retrieved easily from a share header and should be assumed to be
|
409
409
|
known to shareholders. Nothing that leaks information about the secret should
|
410
410
|
be used as an `identifier`. If an `identifier` is not set, it will default
|
411
|
-
to the output of `SecureRandom.hex(8)` which is 8 random hex bytes
|
412
|
-
16 characters long.
|
411
|
+
to the output of `SecureRandom.hex(8)` which is 8 random hex bytes (16 characters).
|
413
412
|
|
414
413
|
The `hash_alg` is a String that represents which cryptographic one-way
|
415
414
|
hash function should be embedded in shares. The hash is used to verify
|
data/lib/tss/combiner.rb
CHANGED
@@ -53,6 +53,7 @@ module TSS
|
|
53
53
|
# The output string is returned (along with some metadata).
|
54
54
|
#
|
55
55
|
# rubocop:disable CyclomaticComplexity
|
56
|
+
Contract C::None => ({ :hash => C::Maybe[String], :hash_alg => C::Maybe[C::Enum['NONE', 'SHA1', 'SHA256']], :identifier => TSS::IdentifierArg, :process_time => C::Num, :secret => TSS::SecretArg, :threshold => TSS::ThresholdArg})
|
56
57
|
def combine
|
57
58
|
# unwrap 'human' shares into binary shares
|
58
59
|
if all_shares_appear_human?(shares)
|
@@ -287,9 +288,9 @@ module TSS
|
|
287
288
|
Contract C::ArrayOf[String] => C::Bool
|
288
289
|
def validate_all_shares(shares)
|
289
290
|
if shares_have_valid_headers!(shares) &&
|
290
|
-
|
291
|
-
|
292
|
-
|
291
|
+
shares_have_same_bytesize!(shares) &&
|
292
|
+
shares_have_expected_length!(shares) &&
|
293
|
+
shares_meet_threshold_min!(shares)
|
293
294
|
return true
|
294
295
|
else
|
295
296
|
return false
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module TSS
|
2
|
+
|
3
|
+
# Custom Contracts
|
4
|
+
# See : https://egonschiele.github.io/contracts.ruby/
|
5
|
+
class SecretArg
|
6
|
+
def self.valid? val
|
7
|
+
val.present? && val.is_a?(String) &&
|
8
|
+
val.length.between?(1,65502) &&
|
9
|
+
['UTF-8', 'US-ASCII'].include?(val.encoding.name) &&
|
10
|
+
val.slice(0) != "\u001F"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.to_s
|
14
|
+
'must be a UTF-8 or US-ASCII String between 1 and 65,502 characters in length and must not begin with the padding char \u001F'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class ThresholdArg
|
19
|
+
def self.valid? val
|
20
|
+
val.present? &&
|
21
|
+
val.is_a?(Integer) &&
|
22
|
+
val.between?(1,255)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.to_s
|
26
|
+
'must be an Integer between 1 and 255'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class NumSharesArg
|
31
|
+
def self.valid? val
|
32
|
+
val.present? &&
|
33
|
+
val.is_a?(Integer) &&
|
34
|
+
val.between?(1,255)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.to_s
|
38
|
+
'must be an Integer between 1 and 255'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class IdentifierArg
|
43
|
+
def self.valid? val
|
44
|
+
val.present? &&
|
45
|
+
val.is_a?(String) &&
|
46
|
+
val.length.between?(1,16) &&
|
47
|
+
val =~ /^[a-zA-Z0-9\-\_\.]*$/i
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.to_s
|
51
|
+
'must be a String between 1 and 16 characters in length limited to [a-z, A-Z, -, _, .]'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class PadBlocksizeArg
|
56
|
+
def self.valid? val
|
57
|
+
val.present? &&
|
58
|
+
val.is_a?(Integer) &&
|
59
|
+
val.between?(0,255)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.to_s
|
63
|
+
'must be an Integer between 0 and 255'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/tss/hasher.rb
CHANGED
@@ -2,15 +2,18 @@ module TSS
|
|
2
2
|
# Hasher is responsible for managing access to the various one-way hash
|
3
3
|
# functions that can be used to validate a secret.
|
4
4
|
class Hasher
|
5
|
+
include Contracts::Core
|
6
|
+
C = Contracts
|
7
|
+
|
5
8
|
HASHES = { NONE: { code: 0, bytesize: 0, hasher: nil },
|
6
9
|
SHA1: { code: 1, bytesize: 20, hasher: Digest::SHA1 },
|
7
|
-
SHA256: { code: 2, bytesize: 32, hasher: Digest::SHA256 }
|
8
|
-
}.freeze
|
10
|
+
SHA256: { code: 2, bytesize: 32, hasher: Digest::SHA256 }}.freeze
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
# Lookup the Symbol key for a Hash with the code.
|
13
|
+
#
|
14
|
+
# @param code [Integer] the hash code to convert to a Symbol key
|
15
|
+
# @return [Symbol,nil] the hash key Symbol or nil if not found
|
16
|
+
Contract C::Int => C::Maybe[Symbol]
|
14
17
|
def self.key_from_code(code)
|
15
18
|
return nil unless Hasher.codes.include?(code)
|
16
19
|
HASHES.each do |k, v|
|
@@ -22,6 +25,7 @@ module TSS
|
|
22
25
|
#
|
23
26
|
# @param hash_key [Symbol, String] the hash key to convert to an Integer code
|
24
27
|
# @return [Integer] the hash key code
|
28
|
+
Contract C::Maybe[C::Enum['NONE', 'SHA1', 'SHA256'], C::Enum[:NONE, :SHA1, :SHA256]] => C::Maybe[C::Int]
|
25
29
|
def self.code(hash_key)
|
26
30
|
HASHES[hash_key.upcase.to_sym][:code]
|
27
31
|
end
|
@@ -29,6 +33,7 @@ module TSS
|
|
29
33
|
# Lookup all valid hash codes, including NONE.
|
30
34
|
#
|
31
35
|
# @return [Array<Integer>] all hash codes including NONE
|
36
|
+
Contract C::None => C::ArrayOf[C::Int]
|
32
37
|
def self.codes
|
33
38
|
HASHES.map do |_k, v|
|
34
39
|
v[:code]
|
@@ -38,6 +43,7 @@ module TSS
|
|
38
43
|
# All valid hash codes that actually do hashing, excluding NONE.
|
39
44
|
#
|
40
45
|
# @return [Array<Integer>] all hash codes excluding NONE
|
46
|
+
Contract C::None => C::ArrayOf[C::Int]
|
41
47
|
def self.codes_without_none
|
42
48
|
HASHES.map do |_k, v|
|
43
49
|
v[:code] if v[:code] > 0
|
@@ -48,8 +54,9 @@ module TSS
|
|
48
54
|
#
|
49
55
|
# @param hash_key [Symbol, String] the hash key to lookup
|
50
56
|
# @return [Integer] the size in Bytes for a specific hash_key
|
57
|
+
Contract C::Or[C::Enum['NONE', 'SHA1', 'SHA256'], C::Enum[:NONE, :SHA1, :SHA256]] => C::Int
|
51
58
|
def self.bytesize(hash_key)
|
52
|
-
HASHES[hash_key.
|
59
|
+
HASHES[hash_key.to_sym][:bytesize]
|
53
60
|
end
|
54
61
|
|
55
62
|
# Return a hexdigest hash for a String using hash_key hash algorithm.
|
@@ -58,6 +65,7 @@ module TSS
|
|
58
65
|
# @param hash_key [Symbol, String] the hash key to use to hash a String
|
59
66
|
# @param str [String] the String to hash
|
60
67
|
# @return [String] the hex digest for str
|
68
|
+
Contract C::Or[C::Enum['NONE', 'SHA1', 'SHA256'], C::Enum[:NONE, :SHA1, :SHA256]], String => String
|
61
69
|
def self.hex_string(hash_key, str)
|
62
70
|
hash_key = hash_key.upcase.to_sym
|
63
71
|
return '' if hash_key == :NONE
|
@@ -70,6 +78,7 @@ module TSS
|
|
70
78
|
# @param hash_key [Symbol, String] the hash key to use to hash a String
|
71
79
|
# @param str [String] the String to hash
|
72
80
|
# @return [String] the Byte String digest for str
|
81
|
+
Contract C::Or[C::Enum['NONE', 'SHA1', 'SHA256'], C::Enum[:NONE, :SHA1, :SHA256]], String => String
|
73
82
|
def self.byte_string(hash_key, str)
|
74
83
|
hash_key = hash_key.upcase.to_sym
|
75
84
|
return '' if hash_key == :NONE
|
@@ -82,6 +91,7 @@ module TSS
|
|
82
91
|
# @param hash_key [Symbol, String] the hash key to use to hash a String
|
83
92
|
# @param str [String] the String to hash
|
84
93
|
# @return [Array<Integer>] the Byte Array digest for str
|
94
|
+
Contract C::Or[C::Enum['NONE', 'SHA1', 'SHA256'], C::Enum[:NONE, :SHA1, :SHA256]], String => C::ArrayOf[C::Int]
|
85
95
|
def self.byte_array(hash_key, str)
|
86
96
|
hash_key = hash_key.upcase.to_sym
|
87
97
|
return [] if hash_key == :NONE
|
data/lib/tss/splitter.rb
CHANGED
@@ -8,26 +8,15 @@ module TSS
|
|
8
8
|
|
9
9
|
attr_reader :secret, :threshold, :num_shares, :identifier, :hash_alg, :format, :pad_blocksize
|
10
10
|
|
11
|
-
Contract ({ :secret =>
|
11
|
+
Contract ({ :secret => TSS::SecretArg, :threshold => C::Maybe[TSS::ThresholdArg], :num_shares => C::Maybe[TSS::NumSharesArg], :identifier => C::Maybe[TSS::IdentifierArg], :hash_alg => C::Maybe[C::Enum['NONE', 'SHA1', 'SHA256']], :format => C::Maybe[C::Enum['binary', 'human']], :pad_blocksize => C::Maybe[TSS::PadBlocksizeArg] }) => C::Any
|
12
12
|
def initialize(opts = {})
|
13
13
|
@secret = opts.fetch(:secret)
|
14
|
-
raise TSS::ArgumentError, 'Invalid secret length. Must be between 1 and 65502' unless @secret.size.between?(1,65502)
|
15
|
-
|
16
14
|
@threshold = opts.fetch(:threshold, 3)
|
17
|
-
raise TSS::ArgumentError, 'Invalid threshold size. Must be between 1 and 255' unless @threshold.between?(1,255)
|
18
|
-
|
19
15
|
@num_shares = opts.fetch(:num_shares, 5)
|
20
|
-
raise TSS::ArgumentError, 'Invalid num_shares size. Must be between 1 and 255' unless @num_shares.between?(1,255)
|
21
|
-
|
22
16
|
@identifier = opts.fetch(:identifier, SecureRandom.hex(8))
|
23
|
-
raise TSS::ArgumentError, 'Invalid identifier characters' unless @identifier =~ /^[a-zA-Z0-9\-\_\.]*$/i
|
24
|
-
raise TSS::ArgumentError, 'Invalid identifier size. Must be between 0 and 16' unless @identifier.size.between?(0,16)
|
25
|
-
|
26
17
|
@hash_alg = opts.fetch(:hash_alg, 'SHA256')
|
27
18
|
@format = opts.fetch(:format, 'human')
|
28
|
-
|
29
19
|
@pad_blocksize = opts.fetch(:pad_blocksize, 0)
|
30
|
-
raise TSS::ArgumentError, 'Invalid pad_blocksize size. Must be between 0 and 255' unless @pad_blocksize.between?(0,255)
|
31
20
|
end
|
32
21
|
|
33
22
|
SHARE_HEADER_STRUCT = BinaryStruct.new([
|
@@ -60,9 +49,8 @@ module TSS
|
|
60
49
|
# If the operation can not be completed successfully, then an error
|
61
50
|
# code should be returned.
|
62
51
|
#
|
52
|
+
Contract C::None => C::ArrayOf[String]
|
63
53
|
def split
|
64
|
-
secret_has_acceptable_encoding!(secret)
|
65
|
-
secret_does_not_begin_with_padding_char!(secret)
|
66
54
|
num_shares_not_less_than_threshold!(threshold, num_shares)
|
67
55
|
|
68
56
|
# RTSS : Combine the secret with a hash digest before splitting. On recombine
|
@@ -129,35 +117,11 @@ module TSS
|
|
129
117
|
human = ['tss', 'v1', identifier, threshold, Base64.urlsafe_encode64(binary)].join('~')
|
130
118
|
format == 'binary' ? binary : human
|
131
119
|
end
|
132
|
-
end
|
133
|
-
|
134
|
-
private
|
135
120
|
|
136
|
-
|
137
|
-
#
|
138
|
-
# @param secret [String] a secret String
|
139
|
-
# @return [true] returns true if acceptable encoding
|
140
|
-
# @raise [TSS::ArgumentError] if invalid
|
141
|
-
def secret_has_acceptable_encoding!(secret)
|
142
|
-
unless secret.encoding.name == 'UTF-8' || secret.encoding.name == 'US-ASCII'
|
143
|
-
raise TSS::ArgumentError, "invalid secret, must be a UTF-8 or US-ASCII encoded String not '#{secret.encoding.name}'"
|
144
|
-
else
|
145
|
-
return true
|
146
|
-
end
|
121
|
+
return shares
|
147
122
|
end
|
148
123
|
|
149
|
-
|
150
|
-
#
|
151
|
-
# @param secret [String] a secret String
|
152
|
-
# @return [true] returns true if String does not begin with padding character
|
153
|
-
# @raise [TSS::ArgumentError] if invalid
|
154
|
-
def secret_does_not_begin_with_padding_char!(secret)
|
155
|
-
if secret.slice(0) == "\u001F"
|
156
|
-
raise TSS::ArgumentError, 'invalid secret, first byte of secret is the reserved left-pad character (\u001F)'
|
157
|
-
else
|
158
|
-
return true
|
159
|
-
end
|
160
|
-
end
|
124
|
+
private
|
161
125
|
|
162
126
|
# The num_shares must be greater than or equal to the threshold or it is invalid.
|
163
127
|
#
|
@@ -165,6 +129,7 @@ module TSS
|
|
165
129
|
# @param num_shares [Integer] the num_shares value
|
166
130
|
# @return [true] returns true if num_shares is >= threshold
|
167
131
|
# @raise [TSS::ArgumentError] if invalid
|
132
|
+
Contract TSS::ThresholdArg, TSS::NumSharesArg => C::Bool
|
168
133
|
def num_shares_not_less_than_threshold!(threshold, num_shares)
|
169
134
|
if num_shares < threshold
|
170
135
|
raise TSS::ArgumentError, "invalid num_shares, must be >= threshold (#{threshold})"
|
@@ -179,6 +144,7 @@ module TSS
|
|
179
144
|
# @param secret_bytes [Array<Integer>] the Byte Array containing the secret
|
180
145
|
# @return [true] returns true if num_shares is >= threshold
|
181
146
|
# @raise [TSS::ArgumentError] if invalid
|
147
|
+
Contract C::ArrayOf[C::Int] => C::Bool
|
182
148
|
def secret_bytes_is_smaller_than_max_size!(secret_bytes)
|
183
149
|
if secret_bytes.size >= 65_535
|
184
150
|
raise TSS::ArgumentError, 'invalid secret, combined padded secret and hash are too large'
|
@@ -194,6 +160,7 @@ module TSS
|
|
194
160
|
# @param threshold [Integer] the threshold value
|
195
161
|
# @param share_len [Integer] the length of the share in Bytes
|
196
162
|
# @return [String] returns an octet String of Bytes containing the binary header
|
163
|
+
Contract TSS::IdentifierArg, C::Maybe[C::Enum['NONE', 'SHA1', 'SHA256']], TSS::ThresholdArg, C::Int => String
|
197
164
|
def share_header(identifier, hash_alg, threshold, share_len)
|
198
165
|
SHARE_HEADER_STRUCT.encode(identifier: identifier,
|
199
166
|
hash_id: Hasher.code(hash_alg),
|
data/lib/tss/tss.rb
CHANGED
@@ -5,6 +5,7 @@ require 'binary_struct'
|
|
5
5
|
require 'contracts'
|
6
6
|
require 'tss/blank'
|
7
7
|
require 'tss/version'
|
8
|
+
require 'tss/custom_contracts'
|
8
9
|
require 'tss/util'
|
9
10
|
require 'tss/hasher'
|
10
11
|
require 'tss/splitter'
|
@@ -14,6 +15,9 @@ require 'tss/combiner'
|
|
14
15
|
#
|
15
16
|
# @author Glenn Rempe <glenn@rempe.us>
|
16
17
|
module TSS
|
18
|
+
include Contracts::Core
|
19
|
+
C = Contracts
|
20
|
+
|
17
21
|
# An unexpected error has occurred.
|
18
22
|
class Error < StandardError; end
|
19
23
|
|
@@ -35,17 +39,17 @@ module TSS
|
|
35
39
|
#
|
36
40
|
# @param [Hash] opts the options to create a message with.
|
37
41
|
# @option opts [String] :secret takes a String (UTF-8 or US-ASCII encoding) with a length between 1..65_534
|
38
|
-
# @option opts [
|
42
|
+
# @option opts [Integer] :threshold (3) The number of shares (M) that will be required to recombine the
|
39
43
|
# secret. Must be a value between 1..255 inclusive. Defaults to a threshold of 3 shares.
|
40
|
-
# @option opts [
|
44
|
+
# @option opts [Integer] :num_shares (5) The total number of shares (N) that will be created. Must be
|
41
45
|
# a value between the `threshold` value (M) and 255 inclusive.
|
42
46
|
# The upper limit is particular to the TSS algorithm used.
|
43
|
-
# @option opts [String] :identifier (SecureRandom.hex(8)) A
|
47
|
+
# @option opts [String] :identifier (SecureRandom.hex(8)) A 1-16 byte String limited to the characters 0-9, a-z, A-Z,
|
44
48
|
# the dash (-), the underscore (_), and the period (.). The identifier will
|
45
49
|
# be embedded in each the binary header of each share and should not reveal
|
46
50
|
# anything about the secret.
|
47
51
|
#
|
48
|
-
#
|
52
|
+
# If the arg is nil it defaults to the value of `SecureRandom.hex(8)`
|
49
53
|
# which returns a random 16 Byte string which represents a Base10 decimal
|
50
54
|
# between 1 and 18446744073709552000.
|
51
55
|
# @option opts [String] :hash_alg ('SHA256') The one-way hash algorithm that will be used to verify the
|
@@ -57,7 +61,7 @@ module TSS
|
|
57
61
|
# who are recombining the shares to verify if they have in fact recovered
|
58
62
|
# the correct secret.
|
59
63
|
# @option opts [String] :format ('binary') the format of the String share output, 'binary' or 'human'
|
60
|
-
# @option opts [
|
64
|
+
# @option opts [Integer] :pad_blocksize (0) An integer representing the nearest multiple of Bytes
|
61
65
|
# to left pad the secret to. Defaults to not adding any padding (0). Padding
|
62
66
|
# is done with the "\u001F" character (decimal 31 in a Byte Array).
|
63
67
|
#
|
@@ -72,17 +76,10 @@ module TSS
|
|
72
76
|
# the share is recombined, or instruct recipients to ignore it.
|
73
77
|
#
|
74
78
|
# @return [Array<String>] an Array of String shares
|
75
|
-
# @raise [TSS::ArgumentError] if the options Types or Values are invalid
|
79
|
+
# @raise [ParamContractError, TSS::ArgumentError] if the options Types or Values are invalid
|
80
|
+
Contract ({ :secret => TSS::SecretArg, :threshold => C::Maybe[TSS::ThresholdArg], :num_shares => C::Maybe[TSS::NumSharesArg], :identifier => C::Maybe[TSS::IdentifierArg], :hash_alg => C::Maybe[C::Enum['NONE', 'SHA1', 'SHA256']], :format => C::Maybe[C::Enum['binary', 'human']], :pad_blocksize => C::Maybe[TSS::PadBlocksizeArg] }) => C::ArrayOf[String]
|
76
81
|
def self.split(opts)
|
77
|
-
|
78
|
-
raise TSS::ArgumentError, 'TSS.split takes a Hash of options with at least a :secret key'
|
79
|
-
end
|
80
|
-
|
81
|
-
begin
|
82
|
-
TSS::Splitter.new(opts).split
|
83
|
-
rescue ParamContractError => e
|
84
|
-
raise TSS::ArgumentError, e.message
|
85
|
-
end
|
82
|
+
TSS::Splitter.new(opts).split
|
86
83
|
end
|
87
84
|
|
88
85
|
# The reconstruction, or combining, operation reconstructs the secret from a
|
@@ -126,16 +123,9 @@ module TSS
|
|
126
123
|
# @return [Hash] a Hash containing the ':secret' and other metadata
|
127
124
|
# @raise [TSS::NoSecretError] if the secret cannot be re-created from the shares provided
|
128
125
|
# @raise [TSS::InvalidSecretHashError] if the embedded hash of the secret does not match the hash of the recreated secret
|
129
|
-
# @raise [TSS::ArgumentError] if the options Types or Values are invalid
|
126
|
+
# @raise [ParamContractError, TSS::ArgumentError] if the options Types or Values are invalid
|
127
|
+
Contract ({ :shares => C::ArrayOf[String], :select_by => C::Maybe[C::Enum['first', 'sample', 'combinations']] }) => ({ :hash => C::Maybe[String], :hash_alg => C::Enum['NONE', 'SHA1', 'SHA256'], :identifier => TSS::IdentifierArg, :process_time => C::Num, :secret => TSS::SecretArg, :threshold => TSS::ThresholdArg})
|
130
128
|
def self.combine(opts)
|
131
|
-
|
132
|
-
raise TSS::ArgumentError, 'TSS.combine takes a Hash of options with at least a :shares key'
|
133
|
-
end
|
134
|
-
|
135
|
-
begin
|
136
|
-
TSS::Combiner.new(opts).combine
|
137
|
-
rescue ParamContractError => e
|
138
|
-
raise TSS::ArgumentError, e.message
|
139
|
-
end
|
129
|
+
TSS::Combiner.new(opts).combine
|
140
130
|
end
|
141
131
|
end
|
data/lib/tss/util.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
module TSS
|
2
2
|
# Common utility, math, and conversion functions.
|
3
3
|
module Util
|
4
|
+
include Contracts::Core
|
5
|
+
C = Contracts
|
6
|
+
|
4
7
|
# The regex to match against human style shares
|
5
8
|
HUMAN_SHARE_RE = /^tss~v1~*[a-zA-Z0-9\.\-\_]{0,16}~[0-9]{1,3}~([a-zA-Z0-9\-\_]+\={0,2})$/
|
6
9
|
|
@@ -216,6 +219,7 @@ module TSS
|
|
216
219
|
#
|
217
220
|
# @param str [String] a UTF-8 String to convert
|
218
221
|
# @return [Array<Integer>] an Array of Integer Bytes
|
222
|
+
Contract String => C::ArrayOf[C::Int]
|
219
223
|
def self.utf8_to_bytes(str)
|
220
224
|
str.bytes.to_a
|
221
225
|
end
|
@@ -224,6 +228,7 @@ module TSS
|
|
224
228
|
#
|
225
229
|
# @param bytes [Array<Integer>] an Array of Bytes to convert
|
226
230
|
# @return [String] a UTF-8 String
|
231
|
+
Contract C::ArrayOf[C::Int] => String
|
227
232
|
def self.bytes_to_utf8(bytes)
|
228
233
|
bytes.pack('C*').force_encoding('utf-8')
|
229
234
|
end
|
@@ -232,6 +237,7 @@ module TSS
|
|
232
237
|
#
|
233
238
|
# @param bytes [Array<Integer>] an Array of Bytes to convert
|
234
239
|
# @return [String] a hex String
|
240
|
+
Contract C::ArrayOf[C::Int] => String
|
235
241
|
def self.bytes_to_hex(bytes)
|
236
242
|
hex = ''
|
237
243
|
bytes.each { |b| hex += sprintf('%02x', b) }
|
@@ -242,6 +248,7 @@ module TSS
|
|
242
248
|
#
|
243
249
|
# @param str [String] a hex String to convert
|
244
250
|
# @return [Array<Integer>] an Array of Integer Bytes
|
251
|
+
Contract String => C::ArrayOf[C::Int]
|
245
252
|
def self.hex_to_bytes(str)
|
246
253
|
[str].pack('H*').unpack('C*')
|
247
254
|
end
|
@@ -250,6 +257,7 @@ module TSS
|
|
250
257
|
#
|
251
258
|
# @param hex [String] a hex String to convert
|
252
259
|
# @return [String] a UTF-8 String
|
260
|
+
Contract String => String
|
253
261
|
def self.hex_to_utf8(hex)
|
254
262
|
bytes_to_utf8(hex_to_bytes(hex))
|
255
263
|
end
|
@@ -258,6 +266,7 @@ module TSS
|
|
258
266
|
#
|
259
267
|
# @param str [String] a UTF-8 String to convert
|
260
268
|
# @return [String] a hex String
|
269
|
+
Contract String => String
|
261
270
|
def self.utf8_to_hex(str)
|
262
271
|
bytes_to_hex(utf8_to_bytes(str))
|
263
272
|
end
|
@@ -268,6 +277,7 @@ module TSS
|
|
268
277
|
# @param input_string [String] the String to pad
|
269
278
|
# @param pad_char [String] the String to pad with
|
270
279
|
# @return [String] a padded String
|
280
|
+
Contract C::Int, String, String => String
|
271
281
|
def self.left_pad(byte_multiple, input_string, pad_char = "\u001F")
|
272
282
|
return input_string if byte_multiple == 0
|
273
283
|
pad_length = byte_multiple - (input_string.length % byte_multiple)
|
@@ -288,6 +298,7 @@ module TSS
|
|
288
298
|
# @param a [String] the private value
|
289
299
|
# @param b [String] the user provided value
|
290
300
|
# @return [true, false] whether the strings match or not
|
301
|
+
Contract String, String => C::Bool
|
291
302
|
def self.secure_compare(a, b)
|
292
303
|
return false unless a.bytesize == b.bytesize
|
293
304
|
|
@@ -303,6 +314,7 @@ module TSS
|
|
303
314
|
#
|
304
315
|
# @param share [String] a binary octet share
|
305
316
|
# @return [Hash] header attributes
|
317
|
+
Contract String => Hash
|
306
318
|
def self.extract_share_header(share)
|
307
319
|
h = Splitter::SHARE_HEADER_STRUCT.decode(share)
|
308
320
|
h[:identifier] = h[:identifier].delete("\x00")
|
@@ -313,6 +325,7 @@ module TSS
|
|
313
325
|
#
|
314
326
|
# @param n [Integer] the Integer to calculate for
|
315
327
|
# @return [Integer] the factorial of n
|
328
|
+
Contract C::Int => C::Int
|
316
329
|
def self.factorial(n)
|
317
330
|
(1..n).reduce(:*) || 1
|
318
331
|
end
|
@@ -330,6 +343,7 @@ module TSS
|
|
330
343
|
# @param n [Integer] the total number of shares
|
331
344
|
# @param r [Integer] the threshold number of shares
|
332
345
|
# @return [Integer] the number of possible combinations
|
346
|
+
Contract C::Int, C::Int => C::Int
|
333
347
|
def self.calc_combinations(n, r)
|
334
348
|
factorial(n) / (factorial(r) * factorial(n - r))
|
335
349
|
end
|
@@ -339,6 +353,7 @@ module TSS
|
|
339
353
|
# @param n [Integer] an Integer to convert
|
340
354
|
# @param delimiter [String] the String to delimit n in three Integer groups
|
341
355
|
# @return [String] the object converted into a comma separated String.
|
356
|
+
Contract C::Int, String => String
|
342
357
|
def self.int_commas(n, delimiter = ',')
|
343
358
|
n.to_s.reverse.gsub(%r{([0-9]{3}(?=([0-9])))}, "\\1#{delimiter}").reverse
|
344
359
|
end
|
data/lib/tss/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tss
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Glenn Rempe
|
@@ -30,7 +30,7 @@ cert_chain:
|
|
30
30
|
zieXiXZSAojfFx9g91fKdIrlPbInHU/BaCxXSLBwvOM0drE+c2ue9X8gB55XAhzX
|
31
31
|
37oBiw==
|
32
32
|
-----END CERTIFICATE-----
|
33
|
-
date: 2016-09-
|
33
|
+
date: 2016-09-24 00:00:00.000000000 Z
|
34
34
|
dependencies:
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
36
|
name: sysrandom
|
@@ -238,6 +238,7 @@ files:
|
|
238
238
|
- lib/tss/cli_split.rb
|
239
239
|
- lib/tss/cli_version.rb
|
240
240
|
- lib/tss/combiner.rb
|
241
|
+
- lib/tss/custom_contracts.rb
|
241
242
|
- lib/tss/hasher.rb
|
242
243
|
- lib/tss/splitter.rb
|
243
244
|
- lib/tss/tss.rb
|
metadata.gz.sig
CHANGED
Binary file
|