class KeyTool

Public Class Methods

new() click to toggle source
# File lib/universa/keytool/keytool.rb, line 43
def initialize
  @require_password = true
  @autogenerate_password = false
  @tasks = []
  @rounds = 1000000
  init_parser()
end

Public Instance Methods

check_overwrite(output) click to toggle source
# File lib/universa/keytool/keytool.rb, line 103
def check_overwrite output
  error "File #{output} already exists" if File.exists?(output) && !@overwrite
end
init_parser() click to toggle source
# File lib/universa/keytool/keytool.rb, line 119
  def init_parser
    opt_parser = OptionParser.new { |opts|
      opts.banner = ANSI.bold { "\nUniversa Key tool #{Universa::VERSION}" }
      opts.separator ""

      opts.on("--no-password",
              "create resources not protected by password. Not recommended.") { |x|
        @require_password = x
      }

      opts.on("-a", "--autogenerate_password",
              "the new secure password will be generated and shown on the console",
              "while the password is safe, printing it out to the console may not.",
              "Normally, system promts the password on the console that is",
              "more secure") { |x|
        @autogenerate_password = x
      }

      opts.on("-o FILE", "--output FILE", "file name for the output file") { |f|
        @output_file = f
      }

      opts.on("-F", "--force", "force overwrite file") {
        @overwrite = true
      }

      opts.on("-g SIZE", "--generate SIZE", "generate new private key of the specified bis size") { |s|
        task {
          strength = s.to_i
          case strength
            when 2048, 4096
              task {
                # check we have all to generate...
                output = output_file(".private.unikey")
                check_overwrite(output)
                key = PrivateKey.new(strength)
                save_key(output, key)
                puts "\nNew private key is generated: #{output}"
              }
            else
              error "Only supported key sizes are 2048, 4096"
          end
        }
      }

      opts.on("-u FILE", "--update FILE", "update password on the existing key (also add/remove",
              "requires -o output_file or --overwrite to change it in place") { |name|
        task {
          output = output_file(".private.unikey", name)
          check_overwrite output
          key = load_key(name)
          puts("\rKey loaded OK                       ")
          save_key output, key
        }
      }

      opts.on("-r ROUNDS", "--rounds ROUNDS", "how many PBKDF2 rounds to use when saving with password",
              "(1 million by default, the more the better, but takes time)") { |r|
        @rounds = human_to_i(r)
        @rounds < 100000 and error "To few rounds, use at least 100000"
      }

      opts.on("-s FILE", "--show FILE", "show key information") { |name|
        task {
          key = load_key(name)
          puts "\r----------------------------------------------------------------------------------------"
          puts "Private key, #{key.info.getKeyLength() * 8} bits\n"
          puts "Short address : #{ANSI.bold { key.short_address.to_s }}"
          puts "Long  address : #{ANSI.bold { key.long_address.to_s }}"
        }
      }

      opts.separator ""

      def sample(text)
        "    " + ANSI.bold { ANSI.green { text } }
      end

      opts.on_tail("-h", "--help", "Show this message") do
        puts opts
        puts <<-End

#{ANSI.bold { "Usage samples:" }}

Generate new key 'foo.private.unikey' - will ask password from console (notice extension will be added automatically)
  
#{sample "unikeys -g 2048 -o foo"}

Show foo addresses:

#{sample "unikeys -s foo.private.unikey"}

Change password of the foo key and save it into new file bar.private.unikey (keeping both for security), will ask
new password from console

#{sample "unikeys -u foo.private.unikey -o bar"}

Change password in place (overwriting old file)

#{sample "unikeys -u foo.private.unikey -f"}

See project home page at #{ANSI.underline { "https://github.com/sergeych/universa" }}

        End
        exit
      end

      opts.on_tail("-v", "--version", "Show versions") do
        puts "Universa core version: #{Service.umi.core_version}"
        puts "UMI version          : #{Service.umi.version}"
        client = Universa::Client.new
        puts "Connected nodes      : #{client.size}"
        exit
      end
    }

    begin
      opt_parser.order!
      if @tasks.empty?
        puts "nothing to do. Please specify one of: -g, -s or -u. See help for more (-h)."
      else
        @tasks.each { |t| t.call }
      end
    rescue MessageException, OptionParser::ParseError => e
      STDERR.puts ANSI.red { ANSI.bold { "\nError: #{e}\n" } }
      exit(1000)
    rescue Interrupt
      exit(1010)
    rescue
      STDERR.puts ANSI.red { "\n#{$!.backtrace.reverse.join("\n")}\n" }
      STDERR.puts ANSI.red { ANSI.bold { "Error: #$! (#{$!.class.name})" } }
      exit(2000)
    end
  end
load_key(name) click to toggle source
# File lib/universa/keytool/keytool.rb, line 83
def load_key(name)
  packed = open(name, 'rb') { |f| f.read } rescue error("can't read file: #{name}")
  begin
    PrivateKey.from_packed(packed)
  rescue Exception => e
    if e.message.include?('PasswordProtectedException')
      puts "\nThe key is password-protected"
      while (true)
        puts "\renter password for #{name}:"
        password = STDIN.noecho(&:gets).chomp
        STDOUT << ANSI.faint { "trying to decrypt..." }
        key = PrivateKey.from_packed(packed, password: password) rescue nil
        key and break key
      end
    else
      error "can't load the key (file corrupt?)"
    end
  end
end
output_file(extension = nil, overwrite_existing_name = nil) click to toggle source
# File lib/universa/keytool/keytool.rb, line 74
def output_file(extension = nil, overwrite_existing_name = nil)
  name = @output_file
  if !name
    (overwrite_existing_name && @overwrite) or error "specify output file with -o / --output"
    name = overwrite_existing_name
  end
  extension && !name.end_with?(extension) ? "#{name}#{extension}" : name
end
sample(text) click to toggle source
# File lib/universa/keytool/keytool.rb, line 193
def sample(text)
  "    " + ANSI.bold { ANSI.green { text } }
end
save_key(name, key) click to toggle source
# File lib/universa/keytool/keytool.rb, line 107
def save_key name, key
  open(name, 'wb') { |f|
    f << if @require_password
           password = session_password
           puts "\nEncrypting key with #@rounds PBKDF rounds..."
           key.pack_with_password(password, @rounds)
         else
           key.pack()
         end
  }
end
session_password() click to toggle source
# File lib/universa/keytool/keytool.rb, line 55
def session_password
  @require_password or return nil
  @session_password ||= begin
    if @autogenerate_password
      psw = 29.random_alnums
      puts "Autogenerated password: #{ANSI.bold { psw }}"
      psw
    else
      puts "\nPlease enter password for key to be generated"
      psw1 = STDIN.noecho(&:gets).chomp
      puts "Please re-enter the password"
      psw2 = STDIN.noecho(&:gets).chomp
      psw1 == psw2 or error "passwords do not match"
      psw1.length < 8 and error "password is too short"
      psw1
    end
  end
end
task(&block) click to toggle source
# File lib/universa/keytool/keytool.rb, line 51
def task &block
  @tasks << block
end