tss 0.1.1 → 0.2.0

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