class TSS::CLI

Public Instance Methods

combine() click to toggle source

rubocop:disable CyclomaticComplexity

# File lib/tss/cli_combine.rb, line 51
def combine
  log('Starting combine')
  log("options : #{options.inspect}")
  shares = []

  # There are three ways to pass in shares. STDIN, by specifying
  # `--input-file`, and in response to being prompted and entering shares
  # line by line.

  # STDIN
  # Usage : echo 'foo bar baz' | bundle exec bin/tss split | bundle exec bin/tss combine
  unless STDIN.tty?
    $stdin.each_line do |line|
      line = line.strip
      exit_if_binary!(line)

      if line.start_with?('tss~') && line.match(Util::HUMAN_SHARE_RE)
        shares << line
      else
        log("Skipping invalid share file line : #{line}")
      end
    end
  end

  # Read from an Input File
  if STDIN.tty? && options[:input_file]
    log("Input file specified : #{options[:input_file]}")

    if File.exist?(options[:input_file])
      log("Input file found : #{options[:input_file]}")

      file = File.open(options[:input_file], 'r')
      while !file.eof?
         line = file.readline.strip
         exit_if_binary!(line)

         if line.start_with?('tss~') && line.match(Util::HUMAN_SHARE_RE)
           shares << line
         else
           log("Skipping invalid share file line : #{line}")
         end
      end
    else
      err("Filename '#{options[:input_file]}' does not exist.")
      exit(1)
    end
  end

  # Enter shares in response to a prompt.
  if STDIN.tty? && 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
      exit_if_binary!(last_ans)

      if 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, padding: options[:padding])

    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
    err("#{e.class} : #{e.message}")
  end
end
err(str) click to toggle source
# File lib/tss/cli_common.rb, line 32
def err(str)
  say_status(:error, "#{Time.now.utc.iso8601} : #{str}", :red)
end
exit_if_binary!(str) click to toggle source

rubocop:disable CyclomaticComplexity

# File lib/tss/cli_common.rb, line 10
def exit_if_binary!(str)
  str.each_byte { |c|
    # OK, 9 (TAB), 10 (CR), 13 (LF), >=32 for normal ASCII
    # Usage of anything other than 10, 13, and 32-126 ASCII decimal codes
    # looks as though contents are binary and not standard text.
    if c < 9 || (c > 10 && c < 13) || (c > 13 && c < 32) || c == 127
      err('STDIN secret appears to contain binary data.')
      exit(1)
    end
  }

  unless ['UTF-8', 'US-ASCII'].include?(str.encoding.name)
    err('STDIN secret has a non UTF-8 or US-ASCII encoding.')
    exit(1)
  end
end
log(str) click to toggle source

rubocop:enable CyclomaticComplexity

# File lib/tss/cli_common.rb, line 28
def log(str)
  say_status(:log, "#{Time.now.utc.iso8601} : #{str}", :white) if options[:verbose]
end
split() click to toggle source

rubocop:disable CyclomaticComplexity

# File lib/tss/cli_split.rb, line 74
def split
  log('Starting split')
  log('options : ' + options.inspect)
  args = {}

  # There are three ways to pass in the secret. STDIN, by specifying
  # `--input-file`, and after being prompted and entering your secret
  # line by line.

  # STDIN
  # Usage : echo 'foo bar baz' | bundle exec bin/tss split
  unless STDIN.tty?
    secret = $stdin.read
    exit_if_binary!(secret)
  end

  # Read from an Input File
  if STDIN.tty? && options[:input_file].present?
    log("Input file specified : #{options[:input_file]}")

    if File.exist?(options[:input_file])
      log("Input file found : #{options[:input_file]}")
      secret = File.open(options[:input_file], 'r'){ |file| file.read }
      exit_if_binary!(secret)
    else
      err("Filename '#{options[:input_file]}' does not exist.")
      exit(1)
    end
  end

  # Enter a secret in response to a prompt.
  if STDIN.tty? && options[:input_file].blank?
    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 add a trailing
    # newline so the hash of a secret when it is created will match
    # the hash of a file output when recombinging shares.
    secret = secret.join("\n").strip + "\n"
    exit_if_binary!(secret)
  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]
  args[:hash_alg]      = options[:hash_alg]      if options[:hash_alg]
  args[:format]        = options[:format]        if options[:format]
  args[:padding]       = options[:padding]

  begin
    log("Calling : TSS.split(#{args.inspect})")
    shares = TSS.split(args)

    if options[:output_file].present?
      file_header  = "# THRESHOLD SECRET SHARING SHARES\n"
      file_header << "# #{Time.now.utc.iso8601}\n"
      file_header << "# https://github.com/grempe/tss-rb\n"
      file_header << "\n\n"

      File.open(options[:output_file], 'w') do |somefile|
        somefile.puts file_header + shares.join("\n")
      end
      log("Process complete : Output file written : #{options[:output_file]}")
    else
      $stdout.puts shares.join("\n")
      log('Process complete')
    end
  rescue TSS::Error => e
    err("#{e.class} : #{e.message}")
  end
end
version() click to toggle source
# File lib/tss/cli_version.rb, line 11
def version
  say("TSS #{TSS::VERSION}")
end