tss 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +2 -4
- data/.coco.yml +7 -0
- data/.editorconfig +12 -0
- data/.hound.yml +10 -0
- data/.inch.yml +9 -0
- data/.rubocop.yml +129 -40
- data/.ruby-version +1 -1
- data/.travis.yml +4 -3
- data/CHANGELOG.md +22 -0
- data/README.md +218 -162
- data/RELEASE.md +105 -0
- data/Rakefile +9 -0
- data/bin/tss +4 -1
- data/lib/tss/cli_combine.rb +136 -0
- data/lib/tss/cli_common.rb +40 -0
- data/lib/tss/cli_split.rb +156 -0
- data/lib/tss/cli_version.rb +17 -0
- data/lib/tss/combiner.rb +156 -72
- data/lib/tss/hasher.rb +4 -2
- data/lib/tss/splitter.rb +71 -33
- data/lib/tss/tss.rb +4 -5
- data/lib/tss/util.rb +4 -12
- data/lib/tss/version.rb +1 -1
- data/tss.gemspec +7 -4
- data.tar.gz.sig +0 -0
- metadata +64 -14
- metadata.gz.sig +0 -0
- data/lib/tss/cli.rb +0 -107
- data/lib/tss/types.rb +0 -4
data/bin/tss
CHANGED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
# Command Line Interface (CLI)
|
4
|
+
# See also, `bin/tss` executable.
|
5
|
+
module TSS
|
6
|
+
class CLI < Thor
|
7
|
+
include Thor::Actions
|
8
|
+
|
9
|
+
method_option :input_file, :aliases => '-I', :banner => 'input_file', :type => :string, :desc => 'A filename to read shares from'
|
10
|
+
method_option :output_file, :aliases => '-O', :banner => 'output_file', :type => :string, :desc => 'A filename to write the recovered secret to'
|
11
|
+
|
12
|
+
desc 'combine', 'Enter shares to recover a split secret'
|
13
|
+
|
14
|
+
long_desc <<-LONGDESC
|
15
|
+
`tss combine` will take as input a number of shares that were generated
|
16
|
+
using the `tss split` command. Shares can be provided
|
17
|
+
using one of three different input methods; STDIN, a path to a file,
|
18
|
+
or when prompted for them interactively.
|
19
|
+
|
20
|
+
You can enter shares one by one, or from a text file of shares. If the
|
21
|
+
shares are successfully combined to recover a secret, the secret and
|
22
|
+
some metadata will be written to STDOUT or a file.
|
23
|
+
|
24
|
+
Optional Params:
|
25
|
+
|
26
|
+
input_file :
|
27
|
+
Provide the path to a file containing shares. Any lines in the file not
|
28
|
+
beginning with `tss~` and matching the pattern expected for shares will be
|
29
|
+
ignored. Leading and trailing whitespace or any other text will be ignored
|
30
|
+
as long as the shares are each on a line by themselves.
|
31
|
+
|
32
|
+
output_file :
|
33
|
+
Provide the path to a file where you would like to write any recovered
|
34
|
+
secret, instead of to STDOUT. When this option is provided the output file
|
35
|
+
will contain only the secret itself. Some metadata, including the hash digest
|
36
|
+
of the secret, will be written to STDOUT. Running `sha1sum` or `sha256sum`
|
37
|
+
on the output file should provide a digest matching that of the secret
|
38
|
+
when it was originally split.
|
39
|
+
|
40
|
+
Example w/ options:
|
41
|
+
|
42
|
+
$ tss combine -I shares.txt -O secret.txt
|
43
|
+
LONGDESC
|
44
|
+
|
45
|
+
# rubocop:disable CyclomaticComplexity
|
46
|
+
def combine
|
47
|
+
log('Starting combine')
|
48
|
+
log("options : #{options.inspect}")
|
49
|
+
shares = []
|
50
|
+
|
51
|
+
# There are three ways to pass in shares. STDIN, by specifying
|
52
|
+
# `--input-file`, and in response to being prompted and entering shares
|
53
|
+
# line by line.
|
54
|
+
|
55
|
+
# STDIN
|
56
|
+
# Usage : echo 'foo bar baz' | bundle exec bin/tss split | bundle exec bin/tss combine
|
57
|
+
unless STDIN.tty?
|
58
|
+
$stdin.each_line do |line|
|
59
|
+
line = line.strip
|
60
|
+
exit_if_binary!(line)
|
61
|
+
|
62
|
+
if line.start_with?('tss~') && line.match(Util::HUMAN_SHARE_RE)
|
63
|
+
shares << line
|
64
|
+
else
|
65
|
+
log("Skipping invalid share file line : #{line}")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Read from an Input File
|
71
|
+
if STDIN.tty? && options[:input_file]
|
72
|
+
log("Input file specified : #{options[:input_file]}")
|
73
|
+
|
74
|
+
if File.exist?(options[:input_file])
|
75
|
+
log("Input file found : #{options[:input_file]}")
|
76
|
+
|
77
|
+
file = File.open(options[:input_file], 'r')
|
78
|
+
while !file.eof?
|
79
|
+
line = file.readline.strip
|
80
|
+
exit_if_binary!(line)
|
81
|
+
|
82
|
+
if line.start_with?('tss~') && line.match(Util::HUMAN_SHARE_RE)
|
83
|
+
shares << line
|
84
|
+
else
|
85
|
+
log("Skipping invalid share file line : #{line}")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
else
|
89
|
+
err("Filename '#{options[:input_file]}' does not exist.")
|
90
|
+
exit(1)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Enter shares in response to a prompt.
|
95
|
+
if STDIN.tty? && options[:input_file].blank?
|
96
|
+
say('Enter shares, one per line, and a dot (.) on a line by itself to finish :')
|
97
|
+
last_ans = nil
|
98
|
+
until last_ans == '.'
|
99
|
+
last_ans = ask('share> ').strip
|
100
|
+
exit_if_binary!(last_ans)
|
101
|
+
|
102
|
+
if last_ans != '.' && last_ans.start_with?('tss~') && last_ans.match(Util::HUMAN_SHARE_RE)
|
103
|
+
shares << last_ans
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
begin
|
109
|
+
sec = TSS.combine(shares: shares)
|
110
|
+
|
111
|
+
say('')
|
112
|
+
say('RECOVERED SECRET METADATA')
|
113
|
+
say('*************************')
|
114
|
+
say("hash : #{sec[:hash]}")
|
115
|
+
say("hash_alg : #{sec[:hash_alg]}")
|
116
|
+
say("identifier : #{sec[:identifier]}")
|
117
|
+
say("process_time : #{sec[:process_time]}ms")
|
118
|
+
say("threshold : #{sec[:threshold]}")
|
119
|
+
|
120
|
+
# Write the secret to a file or STDOUT. The hash of the file checked
|
121
|
+
# using sha1sum or sha256sum should match the hash of the original
|
122
|
+
# secret when it was split.
|
123
|
+
if options[:output_file].present?
|
124
|
+
say("secret file : [#{options[:output_file]}]")
|
125
|
+
File.open(options[:output_file], 'w'){ |somefile| somefile.puts sec[:secret] }
|
126
|
+
else
|
127
|
+
say('secret :')
|
128
|
+
say(sec[:secret])
|
129
|
+
end
|
130
|
+
rescue TSS::Error => e
|
131
|
+
err("#{e.class} : #{e.message}")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
# rubocop:enable CyclomaticComplexity
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
# Command Line Interface (CLI)
|
4
|
+
# See also, `bin/tss` executable.
|
5
|
+
module TSS
|
6
|
+
class CLI < Thor
|
7
|
+
|
8
|
+
class_option :verbose, :type => :boolean, :aliases => '-v', :desc => 'Display additional logging output'
|
9
|
+
|
10
|
+
no_commands do
|
11
|
+
# rubocop:disable CyclomaticComplexity
|
12
|
+
def exit_if_binary!(str)
|
13
|
+
str.each_byte { |c|
|
14
|
+
# OK, 9 (TAB), 10 (CR), 13 (LF), >=32 for normal ASCII
|
15
|
+
# Usage of anything other than 10, 13, and 32-126 ASCII decimal codes
|
16
|
+
# looks as though contents are binary and not standard text.
|
17
|
+
if c < 9 || (c > 10 && c < 13) || (c > 13 && c < 32) || c == 127
|
18
|
+
err('STDIN secret appears to contain binary data.')
|
19
|
+
exit(1)
|
20
|
+
end
|
21
|
+
}
|
22
|
+
|
23
|
+
unless ['UTF-8', 'US-ASCII'].include?(str.encoding.name)
|
24
|
+
err('STDIN secret has a non UTF-8 or US-ASCII encoding.')
|
25
|
+
exit(1)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
# rubocop:enable CyclomaticComplexity
|
29
|
+
|
30
|
+
def log(str)
|
31
|
+
say_status(:log, "#{Time.now.utc.iso8601} : #{str}", :white) if options[:verbose]
|
32
|
+
end
|
33
|
+
|
34
|
+
def err(str)
|
35
|
+
say_status(:error, "#{Time.now.utc.iso8601} : #{str}", :red)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
# Command Line Interface (CLI)
|
4
|
+
# See also, `bin/tss` executable.
|
5
|
+
module TSS
|
6
|
+
class CLI < Thor
|
7
|
+
include Thor::Actions
|
8
|
+
|
9
|
+
method_option :threshold, :aliases => '-t', :banner => 'threshold', :type => :numeric, :desc => '# of shares, of total, required to reconstruct a secret'
|
10
|
+
method_option :num_shares, :aliases => '-n', :banner => 'num_shares', :type => :numeric, :desc => '# of shares total that will be generated'
|
11
|
+
method_option :identifier, :aliases => '-i', :banner => 'identifier', :type => :string, :desc => 'A unique identifier string, 0-16 Bytes, [a-zA-Z0-9.-_]'
|
12
|
+
method_option :hash_alg, :aliases => '-h', :banner => 'hash_alg', :type => :string, :desc => 'A hash type for verification, NONE, SHA1, SHA256'
|
13
|
+
method_option :format, :aliases => '-f', :banner => 'format', :type => :string, :default => 'human', :desc => 'Share output format, binary or human'
|
14
|
+
method_option :pad_blocksize, :aliases => '-p', :banner => 'pad_blocksize', :type => :numeric, :desc => 'Block size # secrets will be left-padded to, 0-255'
|
15
|
+
method_option :input_file, :aliases => '-I', :banner => 'input_file', :type => :string, :desc => 'A filename to read the secret from'
|
16
|
+
method_option :output_file, :aliases => '-O', :banner => 'output_file', :type => :string, :desc => 'A filename to write the shares to'
|
17
|
+
|
18
|
+
desc 'split', 'Split a secret into shares that can be used to re-create the secret'
|
19
|
+
|
20
|
+
long_desc <<-LONGDESC
|
21
|
+
`tss split` will generate a set of Threshold Secret Sharing shares from
|
22
|
+
a SECRET provided. A secret to be split can be provided using one of three
|
23
|
+
different input methods; STDIN, a path to a file, or when prompted
|
24
|
+
for it interactively. In all cases the secret should be UTF-8 or
|
25
|
+
US-ASCII encoded text and be no larger than 65,535 Bytes (including header
|
26
|
+
and hash verification bytes).
|
27
|
+
|
28
|
+
Optional Params:
|
29
|
+
|
30
|
+
num_shares :
|
31
|
+
The number of total shares that will be generated.
|
32
|
+
|
33
|
+
threshold :
|
34
|
+
The threshold is the number of shares required to
|
35
|
+
recreate a secret. This is always a subset of the total
|
36
|
+
shares.
|
37
|
+
|
38
|
+
identifier :
|
39
|
+
A unique identifier string that will be attached
|
40
|
+
to each share. It can be 0-16 Bytes long and use the
|
41
|
+
characters [a-zA-Z0-9.-_]
|
42
|
+
|
43
|
+
hash_alg :
|
44
|
+
One of NONE, SHA1, SHA256. The algorithm to use for a one-way hash of the secret that will be split along with the secret.
|
45
|
+
|
46
|
+
pad_blocksize :
|
47
|
+
An Integer, 0-255, that represents a multiple to which the secret will be padded. For example if pad_blocksize is set to 8, the secret 'abc' would be left-padded to '00000abc' (the padding char is not zero, that is just for illustration).
|
48
|
+
|
49
|
+
format :
|
50
|
+
Whether to output the shares as a binary octet string (RTSS), or as more human friendly URL safe Base 64 encoded text with some metadata.
|
51
|
+
|
52
|
+
input_file :
|
53
|
+
Provide the path to a file containing UTF-8 or US-ASCII text, the contents of which will be used as the secret.
|
54
|
+
|
55
|
+
output_file :
|
56
|
+
Provide the path to a file where you would like to write the shares, one per line, instead of to STDOUT.
|
57
|
+
|
58
|
+
Example w/ options:
|
59
|
+
|
60
|
+
$ tss split -t 3 -n 6 -i abc123 -h SHA256 -p 8 -f human
|
61
|
+
|
62
|
+
Enter your secret:
|
63
|
+
|
64
|
+
secret > my secret
|
65
|
+
|
66
|
+
tss~v1~abc123~3~YWJjMTIzAAAAAAAAAAAAAAIDADEBQ-AQG3PuU4oT4qHOh2oJmu-vQwGE6O5hsGRBNtdAYauTIi7VoIdi5imWSrswDdRy
|
67
|
+
tss~v1~abc123~3~YWJjMTIzAAAAAAAAAAAAAAIDADECM0OK5TSamH3nubH3FJ2EGZ4Yux4eQC-mvcYY85oOe6ae3kpvVXjuRUDU1m6sX20X
|
68
|
+
tss~v1~abc123~3~YWJjMTIzAAAAAAAAAAAAAAIDADEDb7yF4Vhr1JqNe2Nc8IXo98hmKAxsqC3c_Mn3r3t60NxQMC22ate51StDOM-BImch
|
69
|
+
tss~v1~abc123~3~YWJjMTIzAAAAAAAAAAAAAAIDADEEIXU0FajldnRtEQMLK-ZYMO2MRa0NmkBFfNAOx7olbgXLkVbP9txXMDsdokblVwke
|
70
|
+
tss~v1~abc123~3~YWJjMTIzAAAAAAAAAAAAAAIDADEFfYo7EcQUOpMH09Ggz_403rvy1r9_ckI_Pd_hm1tRxX8FfzEWyXMAoFCKTOfIKgMo
|
71
|
+
tss~v1~abc123~3~YWJjMTIzAAAAAAAAAAAAAAIDADEGDSmh74Ng8WTziMGZXAm5XcpFLqDl2oP4MH24XhYf33IIg1WsPIyMAznI0DJUeLpN
|
72
|
+
LONGDESC
|
73
|
+
|
74
|
+
# rubocop:disable CyclomaticComplexity
|
75
|
+
def split
|
76
|
+
log('Starting split')
|
77
|
+
log('options : ' + options.inspect)
|
78
|
+
args = {}
|
79
|
+
|
80
|
+
# There are three ways to pass in the secret. STDIN, by specifying
|
81
|
+
# `--input-file`, and after being prompted and entering your secret
|
82
|
+
# line by line.
|
83
|
+
|
84
|
+
# STDIN
|
85
|
+
# Usage : echo 'foo bar baz' | bundle exec bin/tss split
|
86
|
+
unless STDIN.tty?
|
87
|
+
secret = $stdin.read
|
88
|
+
exit_if_binary!(secret)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Read from an Input File
|
92
|
+
if STDIN.tty? && options[:input_file].present?
|
93
|
+
log("Input file specified : #{options[:input_file]}")
|
94
|
+
|
95
|
+
if File.exist?(options[:input_file])
|
96
|
+
log("Input file found : #{options[:input_file]}")
|
97
|
+
secret = File.open(options[:input_file], 'r'){ |file| file.read }
|
98
|
+
exit_if_binary!(secret)
|
99
|
+
else
|
100
|
+
err("Filename '#{options[:input_file]}' does not exist.")
|
101
|
+
exit(1)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Enter a secret in response to a prompt.
|
106
|
+
if STDIN.tty? && options[:input_file].blank?
|
107
|
+
say('Enter your secret, enter a dot (.) on a line by itself to finish :')
|
108
|
+
last_ans = nil
|
109
|
+
secret = []
|
110
|
+
|
111
|
+
while last_ans != '.'
|
112
|
+
last_ans = ask('secret > ')
|
113
|
+
secret << last_ans unless last_ans == '.'
|
114
|
+
end
|
115
|
+
|
116
|
+
# Strip whitespace from the leading and trailing edge of the secret.
|
117
|
+
# Separate each line of the secret with newline, and add a trailing
|
118
|
+
# newline so the hash of a secret when it is created will match
|
119
|
+
# the hash of a file output when recombinging shares.
|
120
|
+
secret = secret.join("\n").strip + "\n"
|
121
|
+
exit_if_binary!(secret)
|
122
|
+
end
|
123
|
+
|
124
|
+
args[:secret] = secret
|
125
|
+
args[:threshold] = options[:threshold] if options[:threshold]
|
126
|
+
args[:num_shares] = options[:num_shares] if options[:num_shares]
|
127
|
+
args[:identifier] = options[:identifier] if options[:identifier]
|
128
|
+
args[:hash_alg] = options[:hash_alg] if options[:hash_alg]
|
129
|
+
args[:pad_blocksize] = options[:pad_blocksize] if options[:pad_blocksize]
|
130
|
+
args[:format] = options[:format] if options[:format]
|
131
|
+
|
132
|
+
begin
|
133
|
+
log("Calling : TSS.split(#{args.inspect})")
|
134
|
+
shares = TSS.split(args)
|
135
|
+
|
136
|
+
if options[:output_file].present?
|
137
|
+
file_header = "# THRESHOLD SECRET SHARING SHARES\n"
|
138
|
+
file_header << "# #{Time.now.utc.iso8601}\n"
|
139
|
+
file_header << "# https://github.com/grempe/tss-rb\n"
|
140
|
+
file_header << "\n\n"
|
141
|
+
|
142
|
+
File.open(options[:output_file], 'w') do |somefile|
|
143
|
+
somefile.puts file_header + shares.join("\n")
|
144
|
+
end
|
145
|
+
log("Process complete : Output file written : #{options[:output_file]}")
|
146
|
+
else
|
147
|
+
$stdout.puts shares.join("\n")
|
148
|
+
log('Process complete')
|
149
|
+
end
|
150
|
+
rescue TSS::Error => e
|
151
|
+
err("#{e.class} : #{e.message}")
|
152
|
+
end
|
153
|
+
end
|
154
|
+
# rubocop:enable CyclomaticComplexity
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
# Command Line Interface (CLI)
|
4
|
+
# See also, `bin/tss` executable.
|
5
|
+
module TSS
|
6
|
+
class CLI < Thor
|
7
|
+
desc 'version', 'tss version'
|
8
|
+
|
9
|
+
long_desc <<-LONGDESC
|
10
|
+
Display the current version of TSS
|
11
|
+
LONGDESC
|
12
|
+
|
13
|
+
def version
|
14
|
+
say("TSS #{TSS::VERSION}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|