class Uniring

Show help it nothing to do ARGV << “-h” if ARGV == []

Attributes

option_parser[R]

Public Class Methods

new(&block) click to toggle source
# File lib/universa_tools/uniring.rb, line 22
def initialize(&block)
  @main_password = nil
  @keyring_path = File.expand_path("~/.universa/main_keyring")
  @tasks = []
  @commands = {}
  @term_width = ANSI::Terminal.terminal_width
  init_opts &block
end

Public Instance Methods

init_opts(&initializer) click to toggle source
# File lib/universa_tools/uniring.rb, line 44
  def init_opts &initializer
    @option_parser = OptionParser.new { |opts|
      opts.banner = <<-End
#{ANSI.bold { "\nUniversa KeyRing tool #{UniversaTools::VERSION}, Universa core #{Universa::VERSION}" }}
      End
      @usage = <<-End
      
Usage:

#{sample "uniring [options] command [arguments]"}

#{sample "uniring -h"}

#{sample "uniring help"}
      End
      opts.separator ""

      # opts.on("--term-with SIZE", "override terminal width to specified size when formatting output, default is taken from the terminal and is currently #@term_width.") { |size|
      #   @term_width = size
      # }

      opts.on("--all", "allow delete/update more than one key at a time") {
        @allow_all = true
      }

      opts.on("-p PASSWORD", "--password PASSWORD", "specify keyring password in the command line") { |x|
        @password = x
      }

      opts.on("-a [TAG,]KEY_FILE", "--add [TAG,]KEYFILE", Array, "add key from a file, with optional tag",
              "tag could be used to select keys in extract/sign/delete operations") { |tag, file_name|
        task {
          if !file_name
            tag, file_name = file_name, tag
          end
          file_name, password = file_name.split(':', 2)
          begin
            keyring.add_key load_key(file_name, password), tag
            puts success_style("\nKey added\n")
          rescue ArgumentError
            error "key already in ring"
          end
        }
      }

      opts.on("-d prefix", "--delete prefix", "delete key by beginning of its key address or a tag",
              "this method prompts confirmation works only if exactly one",
              "matching key exists") { |part| task {
        keys = keyring.matching_records(part)
        case keys.size
          when 0
            error "no matching keys found"
          when 1
            delete_keys(keys)
          else
            if @allow_all
              delete_keys(keys)
            else
              puts error_style("\nMore than one key matches the criteria:")
              keys.each { |kr| show_record kr }
              error "Only one key could be deleted unless --all is specified"
            end
        end
      } }

      opts.on("-l", "--list", "show keys in a ring") {
        task {
          keyring # this may ask for password so do it first
          puts "--" * 40
          puts ANSI.yellow { "KeyRing v.#{keyring.version.to_s}: " } + ANSI.bold { @keyring_path }
          puts ANSI.yellow { "fingerprint:     " } + ANSI.bold{ ANSI.green { keyring.fingerprint } }
          puts "--" * 40
          keyring.keys.sort_by { |x| x&.tag || '' }.each(&method(:show_record))
          if (keyring.keys.size > 0)
            puts "--" * 40
            puts "#{keyring.keys.size} key(s)\n"
          else
            puts "\nno keys\n"
          end
        }
      }

      opts.on("-x", "--change-password [NEW_PASSWORD]",
              "change password. if the password it not specified, it ",
              "will be requested. Don't forget to use -- to separate",
              "this option from the repository name when needed") { |np|
        task {
          kr = keyring
          new_password = np || request_password2("enter new password")
          kr.change_password new_password
          puts "password has been changed"
        }
      }

      opts.on("--init", "-i", "initialize new keyring. Does not change any existing keyring") {
        task {
          @keyring = KeyRing.new(@keyring_path, password: @password, password_proc: ->(x) { self.request_password2 x },
                                 generate: true, override: @force_init)
          puts success_style("keyring has been created")
        }
      }

      opts.on("--force", "with --init, deletes existing reing if exists") {
        @force_init = true
      }

      initializer&.call(opts)

    }
  end
keyring() click to toggle source
# File lib/universa_tools/uniring.rb, line 155
def keyring
  @keyring ||=
      begin
        print ANSI.faint { "trying to open keyring #{@keyring_path}" }
        KeyRing.new(@keyring_path, password: @password, password_proc: -> (x) { self.request_password(x) } )
      rescue Farcall::RemoteError => e
        if e.message =~ /HMAC authentication failed/
          error("wrong keyring password or broken ring")
        else
          raise e
        end
      ensure
        print "\r" + ' ' * 20 + "\r"
      end
end
keyring_password() click to toggle source
# File lib/universa_tools/uniring.rb, line 171
def keyring_password
  @keyring_password
end
request_password(prompt) click to toggle source
# File lib/universa_tools/uniring.rb, line 186
def request_password(prompt)
  clearstring
  print prompt + ' '
  STDIN.noecho { |x| x.gets.chomp }
ensure
  clearstring
end
request_password2(prompt) click to toggle source
# File lib/universa_tools/uniring.rb, line 175
def request_password2(prompt)
  loop do
    psw1 = request_password prompt
    psw2 = request_password "please re-enter the password"
    return psw1 if psw1 == psw2
    puts error_style("passwords do not much. try again")
  end
ensure
  clearstring
end
run() click to toggle source
# File lib/universa_tools/uniring.rb, line 31
def run
  run_options_parser @option_parser, tasks_before_commands: false do
    case ARGV.length
      when 0
        # OK, run as usual
      when 1
        @keyring_path = ARGV[0]
      else
        error("illegal arguments")
    end
  end
end

Private Instance Methods

clearstring() click to toggle source
# File lib/universa_tools/uniring.rb, line 243
def clearstring()
  print "\r" + ' ' * 120 + "\r"
end
delete_keys(keys) click to toggle source
# File lib/universa_tools/uniring.rb, line 196
def delete_keys(keys)
  keys.each { |kr|
    show_record kr
    print warning_style { "Are you sure to delete this key? [Ny] " }
    if %w[y Y].include?(STDIN.readline.chomp)
      print "deleting..."
      keyring.delete_key kr.key
      clearstring
      puts warning_style("\nkey has been deleted\n")
    else
      puts "skipped"
    end
  }
end
load_key(file_name, password = nil) click to toggle source
# File lib/universa_tools/uniring.rb, line 221
def load_key(file_name, password = nil)
  packed = open(file_name, 'rb') { |f| f.read } rescue open(file_name + ".private.unikey", 'rb') { |f| f.read }
  # NO password?
  key = Universa::PrivateKey.from_packed(packed) rescue nil
  key and return key
  print ANSI.faint("decrypting the key...")
  if password
    key = Universa::PrivateKey.from_packed(packed, password: password) rescue nil
    clearstring()
    key and return key
    puts error_style("wrong password or corrupted key file")
  end
  while !key do
    password = request_password("\rPlease enter password for #{file_name}:")
    print ANSI.faint("\rdecrypting the key...")
    key = Universa::PrivateKey.from_packed(packed, password: password) rescue nil
    clearstring()
    key or puts error_style("wrong password or corrupted key file")
  end
  key
end
sample(text) click to toggle source
# File lib/universa_tools/uniring.rb, line 251
def sample(text)
  "    " + ANSI.bold { ANSI.green { text } }
end
show_record(kr) click to toggle source
# File lib/universa_tools/uniring.rb, line 211
def show_record(kr)
  puts ANSI.bold { ANSI.green { kr.tag } } || ANSI.faint { "(no tag)" }
  puts "\t#{ANSI.bold { kr.key.short_address.to_s }}"
  puts "\t#{ANSI.bold { kr.key.long_address.to_s }}"
  if !kr.data.empty?
    puts "\t#{kr.data.inspect}"
  end
  puts
end
task(&block) click to toggle source
# File lib/universa_tools/uniring.rb, line 247
def task(&block)
  @tasks << block
end