Skip to content

Commit

Permalink
Teach CLI combine and split to read and write secrets and shares from…
Browse files Browse the repository at this point in the history
… external files.
  • Loading branch information
grempe committed Apr 18, 2016
1 parent c750f92 commit 32d31d6
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 36 deletions.
3 changes: 2 additions & 1 deletion bin/tss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# encoding: utf-8

require 'tss'
require 'tss/cli'
require 'tss/cli_split'
require 'tss/cli_combine'

TSS::CLI.start
101 changes: 101 additions & 0 deletions lib/tss/cli_combine.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
require 'thor'

# Command Line Interface (CLI)
# See also, `bin/tss` executable.
module TSS
class CLI < Thor
include Thor::Actions

method_option :input_file, :aliases => '-I', :banner => 'input_file', :type => :string, :desc => 'A filename to read shares from'
method_option :output_file, :aliases => '-O', :banner => 'output_file', :type => :string, :desc => 'A filename to write the recovered secret to'

desc 'combine', 'Enter shares to recover a split secret'

long_desc <<-LONGDESC
`tss combine` will take as input a number of shares that were generated using the `tss split` command.
You can enter shares one by one, or from a text file of shares. If the
shares are successfully combined to recover a secret, the secret and
some metadata will be written to STDOUT or a file.
Optional Params:
input_file :
Provide the path to a file containing shares. Any lines in the file not
beginning with `tss~` and matching the pattern expected for shares will be
ignored. Leading and trailing whitespace or any other text will be ignored
as long as the shares are each on a line by themselves.
output_file :
Provide the path to a file where you would like to write any recovered
secret, instead of to STDOUT. When this option is provided the output file
will contain only the secret itself. Some metadata, including the hash digest
of the secret, will be written to STDOUT. Running `sha1sum` or `sha256sum`
on the output file should provide a digest matching that of the secret
when it was originally split.
Example w/ options:
$ tss combine -I shares.txt -O secret.txt
LONGDESC

def combine
shares = []

# read and process shares from a file
if options[:input_file].present?
if File.exist?(options[:input_file])
file = File.open(options[:input_file], 'r')
while !file.eof?
line = file.readline.strip

if line.present? && line.start_with?('tss~') && line.match(Util::HUMAN_SHARE_RE)
shares << line
end
end
else
say("ERROR : Filename '#{options[:input_file]}' does not exist.")
exit(1)
end
end

if options[:input_file].blank?
say('Enter shares, one per line, and a dot (.) on a line by itself to finish :')
last_ans = nil
until last_ans == '.'
last_ans = ask('share> ').strip

if last_ans.present? && last_ans != '.' && last_ans.start_with?('tss~') && last_ans.match(Util::HUMAN_SHARE_RE)
shares << last_ans
end
end
end

begin
sec = TSS.combine(shares: shares)

say('')
say('RECOVERED SECRET METADATA')
say('*************************')
say("hash : #{sec[:hash]}")
say("hash_alg : #{sec[:hash_alg]}")
say("identifier : #{sec[:identifier]}")
say("process_time : #{sec[:process_time]}ms")
say("threshold : #{sec[:threshold]}")

# Write the secret to a file or STDOUT. The hash of the file checked
# using sha1sum or sha256sum should match the hash of the original
# secret when it was split.
if options[:output_file].present?
say("secret file : [#{options[:output_file]}]")
File.open(options[:output_file], 'w'){ |somefile| somefile.puts sec[:secret] }
else
say('secret :')
say(sec[:secret])
end
rescue TSS::Error => e
say("ERROR : #{e.class} : #{e.message}")
end
end
end
end
88 changes: 53 additions & 35 deletions lib/tss/cli.rb → lib/tss/cli_split.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ class CLI < Thor
method_option :hash_alg, :aliases => '-h', :banner => 'hash_alg', :type => :string, :desc => 'A hash type for verification, NONE, SHA1, SHA256'
method_option :format, :aliases => '-f', :banner => 'format', :type => :string, :default => 'human', :desc => 'Share output format, binary or human'
method_option :pad_blocksize, :aliases => '-p', :banner => 'pad_blocksize', :type => :numeric, :desc => 'Block size # secrets will be left-padded to, 0-255'
method_option :input_file, :aliases => '-I', :banner => 'input_file', :type => :string, :desc => 'A filename to read the secret from'
method_option :output_file, :aliases => '-O', :banner => 'output_file', :type => :string, :desc => 'A filename to write the shares to'

desc 'split', 'split a secret into shares'

long_desc <<-LONGDESC
`tss split` will generate a set of Threshold Secret
Sharing shares from the SECRET provided. To protect
your secret from being saved in your shell history
you will be prompted for the single-line secret.
`tss split` will generate a set of Threshold Secret Sharing shares from
the SECRET provided. To protect your secret from being saved in your
shell history you will be prompted for it unless you are providing
the secret from an external file. You can enter as many lines
as you like within the limits of the max size for a secret.
Optional Params:
Expand All @@ -43,7 +48,13 @@ class CLI < Thor
format :
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.
Example using all options:
input_file :
Provide the path to a file containing UTF-8 or US-ASCII text, the contents of which will be used as the secret.
output_file :
Provide the path to a file where you would like to write the shares, one per line, instead of to STDOUT.
Example w/ options:
$ tss split -t 3 -n 6 -i abc123 -h SHA256 -p 8 -f human
Expand All @@ -58,11 +69,40 @@ class CLI < Thor
tss~v1~abc123~3~YWJjMTIzAAAAAAAAAAAAAAIDADEFfYo7EcQUOpMH09Ggz_403rvy1r9_ckI_Pd_hm1tRxX8FfzEWyXMAoFCKTOfIKgMo
tss~v1~abc123~3~YWJjMTIzAAAAAAAAAAAAAAIDADEGDSmh74Ng8WTziMGZXAm5XcpFLqDl2oP4MH24XhYf33IIg1WsPIyMAznI0DJUeLpN
LONGDESC

def split
args = {}

say('Enter your (single line) secret :')
args[:secret] = ask('secret > ')
# read and process a secret from a file
if options[:input_file].present?
if File.exist?(options[:input_file])
secret = File.open(options[:input_file], 'r'){ |file| file.read }
else
say("ERROR : Filename '#{options[:input_file]}' does not exist.")
exit(1)
end
else
# read and process a secret, line by line, ending with a (.)
say('Enter your secret, enter a dot (.) on a line by itself to finish :')
last_ans = nil
secret = []

while last_ans != '.'
last_ans = ask('secret > ')
secret << last_ans unless last_ans == '.'
end

# Strip whitespace from the leading and trailing edge
# of the secret.
#
# Separate each line of the secret with newline, and
# also add a trailing newline so the hashes of the secret
# when split and then joined and placed in a file will
# also match.
secret = secret.join("\n").strip + "\n"
end

args[:secret] = secret
args[:threshold] = options[:threshold] if options[:threshold]
args[:num_shares] = options[:num_shares] if options[:num_shares]
args[:identifier] = options[:identifier] if options[:identifier]
Expand All @@ -72,35 +112,13 @@ def split

begin
shares = TSS.split(args)
shares.each { |s| say(s) }
rescue TSS::Error => e
say("ERROR : #{e.class} : #{e.message}")
end
end

desc 'combine', 'Enter min threshold # of shares, one at a time, to reconstruct a split secret'
def combine
shares = []
last_ans = nil

say('Enter shares, one per line, enter a blank line or dot (.) to finish:')
until last_ans == '.' || last_ans == ''
last_ans = ask('share> ')
shares << last_ans unless last_ans.blank? || last_ans == '.'
end

begin
sec = TSS.combine(shares: shares)

say('')
say('SECRET RECOVERED')
say('****************')
say("hash : #{sec[:hash]}")
say("hash_alg : #{sec[:hash_alg]}")
say("identifier : #{sec[:identifier]}")
say("process_time : #{sec[:process_time]}ms")
say("threshold : #{sec[:threshold]}")
say("secret : #{sec[:secret]}")
# write the shares to a file or STDOUT
if options[:output_file].present?
File.open(options[:output_file], 'w'){ |somefile| somefile.puts shares.join("\n") }
else
say(shares.join("\n"))
end
rescue TSS::Error => e
say("ERROR : #{e.class} : #{e.message}")
end
Expand Down

0 comments on commit 32d31d6

Please sign in to comment.