grempe/tss-rb

View on GitHub
lib/tss/tss.rb

Summary

Maintainability
A
55 mins
Test Coverage
require 'active_support/core_ext/object/blank'
require 'digest'
require 'base64'
require 'sysrandom/securerandom'
require 'binary_struct'
require 'tss/version'
require 'tss/util'
require 'tss/hasher'
require 'tss/splitter'
require 'tss/combiner'

# Threshold Secret Sharing
#
# @author Glenn Rempe <glenn@rempe.us>
module TSS
  include Contracts::Core
  C = Contracts

  # Defined in TSS spec, two less than 2^16
  MAX_SECRET_SIZE = 64_534

  # Max size minus up to 16 bytes PKCS#7 padding
  # and 32 bytes of cryptographic hash
  MAX_UNPADDED_SECRET_SIZE = MAX_SECRET_SIZE - 48

  # When applying PKCS#7 padding, what block size in bytes should be used
  PADDING_BLOCK_SIZE_BYTES = 16

  # An unexpected error has occurred.
  class Error < StandardError; end

  # An argument provided is of the wrong type, or has an invalid value.
  class ArgumentError < TSS::Error; end

  # A secret was attmepted to be recovered, but failed due to invalid shares.
  class NoSecretError < TSS::Error; end

  # A secret was attempted to be recovered, but failed due to an invalid verifier hash.
  class InvalidSecretHashError < TSS::Error; end

  # Threshold Secret Sharing (TSS) provides a way to generate N shares
  # from a value, so that any M of those shares can be used to
  # reconstruct the original value, but any M-1 shares provide no
  # information about that value.  This method can provide shared access
  # control on key material and other secrets that must be strongly
  # protected.
  #
  # @param [Hash] opts the options to create a message with.
  # @option opts [String] :secret takes a String (UTF-8 or US-ASCII encoding) with a length between 1..65_534
  # @option opts [Integer] :threshold (3) The number of shares (M) that will be required to recombine the
  #   secret. Must be a value between 1..255 inclusive. Defaults to a threshold of 3 shares.
  # @option opts [Integer] :num_shares (5) The total number of shares (N) that will be created. Must be
  #   a value between the `threshold` value (M) and 255 inclusive.
  #   The upper limit is particular to the TSS algorithm used.
  # @option opts [String] :identifier (SecureRandom.hex(8)) A 1-16 byte String limited to the characters 0-9, a-z, A-Z,
  #   the dash (-), the underscore (_), and the period (.). The identifier will
  #   be embedded in each the binary header of each share and should not reveal
  #   anything about the secret.
  #
  #   If the arg is nil it defaults to the value of `SecureRandom.hex(8)`
  #   which returns a random 16 Byte string which represents a Base10 decimal
  #   between 1 and 18446744073709552000.
  # @option opts [String] :hash_alg ('SHA256') The one-way hash algorithm that will be used to verify the
  #   secret returned by a later recombine operation is identical to what was
  #   split. This value will be concatenated with the secret prior to splitting.
  #
  #   The valid hash algorithm values are `NONE`, `SHA1`, and `SHA256`. Defaults
  #   to `SHA256`. The use of `NONE` is discouraged as it does not allow those
  #   who are recombining the shares to verify if they have in fact recovered
  #   the correct secret.
  # @option opts [String] :format ('BINARY') the format of the String share output, 'BINARY' or 'HUMAN'
  # @option opts [Boolean] :padding Whether to apply PKCS#7 padding to secret
  #
  # @return an Array of formatted String shares
  # @raise [ParamContractError, TSS::ArgumentError] if the options Types or Values are invalid
  Contract ({ :secret => C::SecretArg, :threshold => C::Maybe[C::ThresholdArg], :num_shares => C::Maybe[C::NumSharesArg], :identifier => C::Maybe[C::IdentifierArg], :hash_alg => C::Maybe[C::HashAlgArg], :format => C::Maybe[C::FormatArg], :padding => C::Maybe[C::Bool] }) => C::ArrayOfShares
  def self.split(opts)
    TSS::Splitter.new(opts).split
  end

  # The reconstruction, or combining, operation reconstructs the secret from a
  # set of valid shares where the number of shares is >= the threshold when the
  # secret was initially split. All options are provided in a single Hash:
  #
  # @param [Hash] opts the options to create a message with.
  # @option opts [Array<String>] :shares an Array of String shares to try to recombine into a secret
  # @option opts [Boolean] :padding Whether PKCS#7 padding is expected in the secret and should be removed
  # @option opts [String] :select_by ('FIRST') the method to use for selecting
  #   shares from the Array if more then threshold shares are provided. Can be
  #   upper case 'FIRST', 'SAMPLE', or 'COMBINATIONS'.
  #
  #   If the number of shares provided as input to the secret
  #   reconstruction operation is greater than the threshold M, then M
  #   of those shares are selected for use in the operation.  The method
  #   used to select the shares can be chosen using the following values:
  #
  #   `FIRST` : If X shares are required by the threshold and more than X
  #   shares are provided, then the first X shares in the Array of shares provided
  #   will be used. All others will be discarded and the operation will fail if
  #   those selected shares cannot recreate the secret.
  #
  #   `SAMPLE` : If X shares are required by the threshold and more than X
  #   shares are provided, then X shares will be randomly selected from the Array
  #   of shares provided.  All others will be discarded and the operation will
  #   fail if those selected shares cannot recreate the secret.
  #
  #   `COMBINATIONS` : If X shares are required, and more than X shares are
  #   provided, then all possible combinations of the threshold number of shares
  #   will be tried to see if the secret can be recreated.
  #   This flexibility comes with a cost. All combinations of `threshold` shares
  #   must be generated. Due to the math associated with combinations it is possible
  #   that the system would try to generate a number of combinations that could never
  #   be generated or processed in many times the life of the Universe. This option
  #   can only be used if the possible combinations for the number of shares and the
  #   threshold needed to reconstruct a secret result in a number of combinations
  #   that is small enough to have a chance at being processed. If the number
  #   of combinations will be too large then the an Exception will be raised before
  #   processing has started.
  #
  # @return a Hash containing the now combined secret and other metadata
  # @raise [TSS::NoSecretError] if the secret cannot be re-created from the shares provided
  # @raise [TSS::InvalidSecretHashError] if the embedded hash of the secret does not match the hash of the recreated secret
  # @raise [ParamContractError, TSS::ArgumentError] if the options Types or Values are invalid
  Contract ({ :shares => C::ArrayOfShares, :padding => C::Maybe[C::Bool], :select_by => C::Maybe[C::SelectByArg] }) => ({ :hash => C::Maybe[String], :hash_alg => C::HashAlgArg, :identifier => C::IdentifierArg, :process_time => C::Num, :secret => C::SecretArg, :threshold => C::ThresholdArg })
  def self.combine(opts)
    TSS::Combiner.new(opts).combine
  end
end