Class: KeyTool

Inherits:
Object
  • Object
show all
Defined in:
lib/universa/keytool/keytool.rb

Instance Method Summary collapse

Constructor Details

#initializeKeyTool

Returns a new instance of KeyTool.



43
44
45
46
47
48
49
# File 'lib/universa/keytool/keytool.rb', line 43

def initialize
  @require_password = true
  @autogenerate_password = false
  @tasks = []
  @rounds = 1000000
  init_parser()
end

Instance Method Details

#check_overwrite(output) ⇒ Object



103
104
105
# File 'lib/universa/keytool/keytool.rb', line 103

def check_overwrite output
  error "File #{output} already exists" if File.exists?(output) && !@overwrite
end

#init_parserObject



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# 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) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# 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) ⇒ Object



74
75
76
77
78
79
80
81
# 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

#save_key(name, key) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
# 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_passwordObject



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# 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) ⇒ Object



51
52
53
# File 'lib/universa/keytool/keytool.rb', line 51

def task &block
  @tasks << block
end