tss 0.1.1 → 0.2.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/bin/tss CHANGED
@@ -2,6 +2,9 @@
2
2
  # encoding: utf-8
3
3
 
4
4
  require 'tss'
5
- require 'tss/cli'
5
+ require 'tss/cli_version'
6
+ require 'tss/cli_common'
7
+ require 'tss/cli_split'
8
+ require 'tss/cli_combine'
6
9
 
7
10
  TSS::CLI.start
@@ -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