rubeepass 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/rpass +238 -0
- data/lib/builtins/cd_wish.rb +56 -0
- data/lib/builtins/clear_wish.rb +24 -0
- data/lib/builtins/copy_wish.rb +134 -0
- data/lib/builtins/ls_wish.rb +60 -0
- data/lib/builtins/pwd_wish.rb +24 -0
- data/lib/builtins/show_wish.rb +79 -0
- data/lib/rubeepass/entry.rb +74 -0
- data/lib/rubeepass/error/invalid_gzip_error.rb +7 -0
- data/lib/rubeepass/error/invalid_header_error.rb +7 -0
- data/lib/rubeepass/error/invalid_magic_error.rb +7 -0
- data/lib/rubeepass/error/invalid_password_error.rb +7 -0
- data/lib/rubeepass/error/invalid_protected_data_error.rb +7 -0
- data/lib/rubeepass/error/invalid_protected_stream_key_error.rb +7 -0
- data/lib/rubeepass/error/invalid_version_error.rb +7 -0
- data/lib/rubeepass/error/not_aes_error.rb +7 -0
- data/lib/rubeepass/error/not_salsa20_error.rb +7 -0
- data/lib/rubeepass/error.rb +12 -0
- data/lib/rubeepass/group.rb +133 -0
- data/lib/rubeepass/protected_decryptor.rb +30 -0
- data/lib/rubeepass.rb +491 -0
- data/lib/string.rb +39 -0
- metadata +167 -0
data/lib/rubeepass.rb
ADDED
@@ -0,0 +1,491 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require "digest"
|
3
|
+
require "openssl"
|
4
|
+
require "os"
|
5
|
+
require "scoobydoo"
|
6
|
+
require "uri"
|
7
|
+
require "zlib"
|
8
|
+
|
9
|
+
class RubeePass
|
10
|
+
@@END_OF_HEADER = 0
|
11
|
+
@@COMMENT = 1
|
12
|
+
@@CIPHER_ID = 2
|
13
|
+
@@COMPRESSION = 3
|
14
|
+
@@MASTER_SEED = 4
|
15
|
+
@@TRANSFORM_SEED = 5
|
16
|
+
@@TRANSFORM_ROUNDS = 6
|
17
|
+
@@ENCRYPTION_IV = 7
|
18
|
+
@@PROTECTED_STREAM_KEY = 8
|
19
|
+
@@STREAM_START_BYTES = 9
|
20
|
+
@@INNER_RANDOM_STREAM_ID = 10
|
21
|
+
|
22
|
+
@@MAGIC_SIG1 = 0x9aa2d903
|
23
|
+
@@MAGIC_SIG2 = 0xb54bfb67
|
24
|
+
@@VERSION = 0x00030000
|
25
|
+
|
26
|
+
attr_reader :db
|
27
|
+
attr_reader :gzip
|
28
|
+
attr_reader :protected_decryptor
|
29
|
+
attr_reader :xml
|
30
|
+
|
31
|
+
def absolute_path(to, from = "/")
|
32
|
+
return "/" if (to.nil? || to.empty? || (to == "/"))
|
33
|
+
from = "/" if (to.start_with?("/"))
|
34
|
+
|
35
|
+
path = Array.new
|
36
|
+
|
37
|
+
from.split("/").each do |group|
|
38
|
+
next if (group.empty?)
|
39
|
+
case group
|
40
|
+
when "."
|
41
|
+
# Do nothing
|
42
|
+
when ".."
|
43
|
+
path.pop
|
44
|
+
else
|
45
|
+
path.push(group)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
to.split("/").each do |group|
|
50
|
+
next if (group.empty?)
|
51
|
+
case group
|
52
|
+
when "."
|
53
|
+
# Do nothing
|
54
|
+
when ".."
|
55
|
+
path.pop
|
56
|
+
else
|
57
|
+
path.push(group)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
return "/#{path.join("/")}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def clear_clipboard(time = 0)
|
65
|
+
@thread.kill if (@thread)
|
66
|
+
@thread = Thread.new do
|
67
|
+
sleep time
|
68
|
+
copy_to_clipboard("")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def copy_to_clipboard(string)
|
73
|
+
string = " \x7F" if (string.nil? || string.empty?)
|
74
|
+
if (OS::Underlying.windows?)
|
75
|
+
puts "Your OS is not currently supported!"
|
76
|
+
return
|
77
|
+
elsif (OS.mac?)
|
78
|
+
pbcopy = ScoobyDoo.where_are_you("pbcopy")
|
79
|
+
rn = ScoobyDoo.where_are_you("reattach-to-user-namespace")
|
80
|
+
|
81
|
+
cp = pbcopy
|
82
|
+
if (ENV["TMUX"])
|
83
|
+
cp = nil
|
84
|
+
cp = "#{rn} #{pbcopy}" if (rn)
|
85
|
+
end
|
86
|
+
|
87
|
+
if (cp)
|
88
|
+
system("echo \"#{string}\" | #{cp}")
|
89
|
+
else
|
90
|
+
puts "Please install reattach-to-user-namespace!"
|
91
|
+
return
|
92
|
+
end
|
93
|
+
elsif (OS.posix?)
|
94
|
+
xclip = ScoobyDoo.where_are_you("xclip")
|
95
|
+
xsel = ScoobyDoo.where_are_you("xsel")
|
96
|
+
|
97
|
+
cp = nil
|
98
|
+
if (xclip)
|
99
|
+
cp = "xclip -i -selection clipboard"
|
100
|
+
elsif (xsel)
|
101
|
+
cp = "xsel -i --clipboard"
|
102
|
+
end
|
103
|
+
|
104
|
+
if (cp)
|
105
|
+
system("echo \"#{string}\" | #{cp}")
|
106
|
+
else
|
107
|
+
puts "Please install either xclip or xsel!"
|
108
|
+
return
|
109
|
+
end
|
110
|
+
else
|
111
|
+
puts "Your OS is not currently supported!"
|
112
|
+
return
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def derive_aes_key
|
117
|
+
cipher = OpenSSL::Cipher::AES.new(256, :ECB)
|
118
|
+
cipher.encrypt
|
119
|
+
cipher.key = @header[@@TRANSFORM_SEED]
|
120
|
+
cipher.padding = 0
|
121
|
+
|
122
|
+
@header[@@TRANSFORM_ROUNDS].times do
|
123
|
+
@key = cipher.update(@key) + cipher.final
|
124
|
+
end
|
125
|
+
|
126
|
+
transform_key = Digest::SHA256::digest(@key)
|
127
|
+
combined_key = @header[@@MASTER_SEED] + transform_key
|
128
|
+
|
129
|
+
@aes_key = Digest::SHA256::digest(combined_key)
|
130
|
+
@aes_iv = @header[@@ENCRYPTION_IV]
|
131
|
+
end
|
132
|
+
private :derive_aes_key
|
133
|
+
|
134
|
+
def extract_xml
|
135
|
+
@xml = Zlib::GzipReader.new(StringIO.new(@gzip)).read
|
136
|
+
end
|
137
|
+
private :extract_xml
|
138
|
+
|
139
|
+
def find_group(path)
|
140
|
+
return @db.find_group(path)
|
141
|
+
end
|
142
|
+
|
143
|
+
def fuzzy_find(input)
|
144
|
+
return @db.fuzzy_find(input)
|
145
|
+
end
|
146
|
+
|
147
|
+
def handle_protected(base64)
|
148
|
+
data = nil
|
149
|
+
begin
|
150
|
+
data = base64.unpack("m*")[0].fix
|
151
|
+
rescue ArgumentError => e
|
152
|
+
raise Error::InvalidProtectedDataError.new
|
153
|
+
end
|
154
|
+
raise Error::InvalidProtectedDataError.new if (data.nil?)
|
155
|
+
|
156
|
+
return @protected_decryptor.add_to_stream(data)
|
157
|
+
end
|
158
|
+
private :handle_protected
|
159
|
+
|
160
|
+
def initialize(kdbx, password, keyfile = nil)
|
161
|
+
@aes_iv = nil
|
162
|
+
@aes_key = nil
|
163
|
+
@db = nil
|
164
|
+
@gzip = nil
|
165
|
+
@header = nil
|
166
|
+
@kdbx = kdbx
|
167
|
+
@key = nil
|
168
|
+
@keyfile = keyfile
|
169
|
+
@password = password
|
170
|
+
@xml = nil
|
171
|
+
end
|
172
|
+
|
173
|
+
def join_key_and_keyfile
|
174
|
+
passhash = Digest::SHA256.digest(@password)
|
175
|
+
|
176
|
+
filehash = ""
|
177
|
+
if (@keyfile)
|
178
|
+
contents = File.readlines(@keyfile).join.fix
|
179
|
+
if (contents[0..4] == "<?xml")
|
180
|
+
# XML Key file
|
181
|
+
# My ugly attempt to parse a small XML Key file with a
|
182
|
+
# poor attempt at schema validation
|
183
|
+
keyfile_line = false
|
184
|
+
key_line = false
|
185
|
+
contents.each_line do |line|
|
186
|
+
line.strip!
|
187
|
+
case line
|
188
|
+
when "<KeyFile>"
|
189
|
+
keyfile_line = true
|
190
|
+
when "<Key>"
|
191
|
+
key_line = true
|
192
|
+
when %r{<Data>.*</Data>}
|
193
|
+
data = line.gsub(%r{^<Data>|</Data>$}, "")
|
194
|
+
data = data.unpack("m*")[0]
|
195
|
+
break if (!keyfile_line || !key_line)
|
196
|
+
break if (data.length != 32)
|
197
|
+
filehash = data
|
198
|
+
end
|
199
|
+
end
|
200
|
+
elsif (contents.length == 32)
|
201
|
+
# Not XML but a 32 byte Key file
|
202
|
+
filehash = contents
|
203
|
+
elsif (contents.length == 64)
|
204
|
+
# Not XML but a 64 byte Key file
|
205
|
+
if (contents.match(/^[0-9A-Fa-f]+$/))
|
206
|
+
filehash = [contents].pack("H*")
|
207
|
+
end
|
208
|
+
else
|
209
|
+
# Not a Key file
|
210
|
+
filehash = Digest::SHA256.digest(contents)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
@key = Digest::SHA256.digest(passhash + filehash)
|
215
|
+
end
|
216
|
+
private :join_key_and_keyfile
|
217
|
+
|
218
|
+
def method_missing(method_name, *args)
|
219
|
+
if (method_name.to_s.match(/^clear_clipboard_after_/))
|
220
|
+
mn = method_name.to_s.gsub!(/^clear_clipboard_after_/, "")
|
221
|
+
case mn
|
222
|
+
when /^[0-9]+_sec(ond)?s$/
|
223
|
+
time = mn.gsub(/_sec(ond)?s$/, "").to_i
|
224
|
+
clear_clipboard(time)
|
225
|
+
when /^[0-9]+_min(ute)?s$/
|
226
|
+
time = mn.gsub(/_min(ute)?s$/, "").to_i
|
227
|
+
clear_clipboard(time * 60)
|
228
|
+
else
|
229
|
+
super
|
230
|
+
end
|
231
|
+
else
|
232
|
+
super
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def open
|
237
|
+
file = File.open(@kdbx)
|
238
|
+
|
239
|
+
read_magic_and_version(file)
|
240
|
+
read_header(file)
|
241
|
+
join_key_and_keyfile
|
242
|
+
derive_aes_key
|
243
|
+
read_gzip(file)
|
244
|
+
|
245
|
+
file.close
|
246
|
+
|
247
|
+
@protected_decryptor = ProtectedDecryptor.new(
|
248
|
+
Digest::SHA256.digest(
|
249
|
+
@header[@@PROTECTED_STREAM_KEY]
|
250
|
+
),
|
251
|
+
["E830094B97205D2A"].pack("H*")
|
252
|
+
)
|
253
|
+
|
254
|
+
extract_xml
|
255
|
+
parse_xml
|
256
|
+
|
257
|
+
return self
|
258
|
+
end
|
259
|
+
|
260
|
+
def parse_gzip(file)
|
261
|
+
gzip = ""
|
262
|
+
block_id = 0
|
263
|
+
|
264
|
+
loop do
|
265
|
+
# Read block ID
|
266
|
+
data = file.read(4)
|
267
|
+
raise Error::InvalidGzipError.new if (data.nil?)
|
268
|
+
id = data.unpack("L*")[0]
|
269
|
+
raise Error::InvalidGzipError.new if (block_id != id)
|
270
|
+
|
271
|
+
block_id += 1
|
272
|
+
|
273
|
+
# Read expected hash
|
274
|
+
data = file.read(32)
|
275
|
+
raise Error::InvalidGzipError.new if (data.nil?)
|
276
|
+
expected_hash = data
|
277
|
+
|
278
|
+
# Read size
|
279
|
+
data = file.read(4)
|
280
|
+
raise Error::InvalidGzipError.new if (data.nil?)
|
281
|
+
size = data.unpack("L*")[0]
|
282
|
+
|
283
|
+
# Break is size is 0 and expected hash is all 0's
|
284
|
+
if (size == 0)
|
285
|
+
expected_hash.each_byte do |byte|
|
286
|
+
if (byte != 0)
|
287
|
+
raise Error::InvalidGzipError.new
|
288
|
+
end
|
289
|
+
end
|
290
|
+
break
|
291
|
+
end
|
292
|
+
|
293
|
+
# Read data and get actual hash
|
294
|
+
data = file.read(size)
|
295
|
+
actual_hash = Digest::SHA256.digest(data)
|
296
|
+
|
297
|
+
# Check that actual hash is same as expected hash
|
298
|
+
if (actual_hash != expected_hash)
|
299
|
+
raise Error::InvalidGzipError.new
|
300
|
+
end
|
301
|
+
|
302
|
+
# Append data
|
303
|
+
gzip += data
|
304
|
+
end
|
305
|
+
|
306
|
+
return gzip
|
307
|
+
end
|
308
|
+
private :parse_gzip
|
309
|
+
|
310
|
+
def parse_xml
|
311
|
+
curr = Group.new(
|
312
|
+
{
|
313
|
+
"Keepass" => self,
|
314
|
+
"Name" => "/"
|
315
|
+
}
|
316
|
+
)
|
317
|
+
entry_params = Hash.new
|
318
|
+
group_params = Hash.new
|
319
|
+
ignore = true
|
320
|
+
status = nil
|
321
|
+
|
322
|
+
@xml.each_line do |line|
|
323
|
+
line.strip!
|
324
|
+
|
325
|
+
case line
|
326
|
+
when "<History>"
|
327
|
+
ignore = true
|
328
|
+
next
|
329
|
+
when "</History>"
|
330
|
+
ignore = false
|
331
|
+
next
|
332
|
+
when "<Root>"
|
333
|
+
ignore = false
|
334
|
+
next
|
335
|
+
when "</Root>"
|
336
|
+
break
|
337
|
+
end
|
338
|
+
|
339
|
+
line = CGI::unescapeHTML(line)
|
340
|
+
line = URI::unescape(line)
|
341
|
+
|
342
|
+
# Always handle protected data
|
343
|
+
case line
|
344
|
+
when %r{<Value Protected}
|
345
|
+
line.gsub!(%r{<?Value( Protected=\"True\")?>}, "")
|
346
|
+
if (ignore)
|
347
|
+
handle_protected(line)
|
348
|
+
else
|
349
|
+
entry_params[status] = handle_protected(line)
|
350
|
+
status = nil
|
351
|
+
end
|
352
|
+
next
|
353
|
+
else
|
354
|
+
next if (ignore)
|
355
|
+
end
|
356
|
+
|
357
|
+
case line
|
358
|
+
when "<Entry>"
|
359
|
+
entry_params = { "Keepass" => self, "Group" => curr }
|
360
|
+
when "</Entry>"
|
361
|
+
entry = Entry.new(entry_params)
|
362
|
+
curr.entries[entry.title] = entry
|
363
|
+
when "<Group>"
|
364
|
+
group_params = { "Keepass" => self, "Group" => curr }
|
365
|
+
when "</Group>"
|
366
|
+
curr = curr.group
|
367
|
+
break if (curr.nil?)
|
368
|
+
when %r{<Key>.+</Key>}
|
369
|
+
status = line.gsub(%r{^<Key>|</Key>$}, "")
|
370
|
+
when %r{<Name>}
|
371
|
+
line.gsub!(%r{^<Name>|</Name>$}, "")
|
372
|
+
group_params["Name"] = line
|
373
|
+
|
374
|
+
group = Group.new(group_params)
|
375
|
+
curr.groups[group.name] = group
|
376
|
+
curr = group
|
377
|
+
when %r{<UUID>.+</UUID>}
|
378
|
+
uuid = line.gsub(%r{^<UUID>|</UUID>$}, "")
|
379
|
+
if (group_params["UUID"].nil?)
|
380
|
+
group_params["UUID"] = uuid
|
381
|
+
else
|
382
|
+
entry_params["UUID"] = uuid
|
383
|
+
end
|
384
|
+
when %r{<Value>}
|
385
|
+
line.gsub!(%r{</?Value( /)?>}, "")
|
386
|
+
entry_params[status] = line
|
387
|
+
status = nil
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
@db = curr
|
392
|
+
end
|
393
|
+
private :parse_xml
|
394
|
+
|
395
|
+
def read_gzip(file)
|
396
|
+
cipher = OpenSSL::Cipher::AES.new(256, :CBC)
|
397
|
+
cipher.decrypt
|
398
|
+
cipher.key = @aes_key
|
399
|
+
cipher.iv = @aes_iv
|
400
|
+
|
401
|
+
encrypted = file.read
|
402
|
+
|
403
|
+
begin
|
404
|
+
data = StringIO.new(
|
405
|
+
cipher.update(encrypted) + cipher.final
|
406
|
+
)
|
407
|
+
rescue OpenSSL::Cipher::CipherError => e
|
408
|
+
raise Error::InvalidPasswordError.new
|
409
|
+
end
|
410
|
+
|
411
|
+
if (data.read(32) != @header[@@STREAM_START_BYTES])
|
412
|
+
raise Error::InvalidPasswordError.new
|
413
|
+
end
|
414
|
+
|
415
|
+
@gzip = parse_gzip(data)
|
416
|
+
end
|
417
|
+
private :read_gzip
|
418
|
+
|
419
|
+
def read_header(file)
|
420
|
+
header = Hash.new
|
421
|
+
loop do
|
422
|
+
data = file.read(1)
|
423
|
+
raise Error::InvalidHeaderError.new if (data.nil?)
|
424
|
+
id = data.unpack("C*")[0]
|
425
|
+
|
426
|
+
data = file.read(2)
|
427
|
+
raise Error::InvalidHeaderError.new if (data.nil?)
|
428
|
+
size = data.unpack("S*")[0]
|
429
|
+
|
430
|
+
data = file.read(size)
|
431
|
+
|
432
|
+
case id
|
433
|
+
when @@END_OF_HEADER
|
434
|
+
break
|
435
|
+
when @@TRANSFORM_ROUNDS
|
436
|
+
header[id] = data.unpack("Q*")[0]
|
437
|
+
else
|
438
|
+
header[id] = data
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
irsi = "\x02\x00\x00\x00"
|
443
|
+
aes = "31c1f2e6bf714350be5805216afc5aff"
|
444
|
+
if (
|
445
|
+
(header[@@MASTER_SEED].length != 32) ||
|
446
|
+
(header[@@TRANSFORM_SEED].length != 32)
|
447
|
+
)
|
448
|
+
raise Error::InvalidHeaderError.new
|
449
|
+
elsif (header[@@INNER_RANDOM_STREAM_ID] != irsi)
|
450
|
+
raise Error::NotSalsaError.new
|
451
|
+
elsif (header[@@CIPHER_ID].unpack("H*")[0] != aes)
|
452
|
+
raise Error::NotAESError.new
|
453
|
+
end
|
454
|
+
|
455
|
+
@header = header
|
456
|
+
end
|
457
|
+
private :read_header
|
458
|
+
|
459
|
+
def read_magic_and_version(file)
|
460
|
+
data = file.read(4)
|
461
|
+
raise Error::InvalidMagicError.new if (data.nil?)
|
462
|
+
sig1 = data.unpack("L*")[0]
|
463
|
+
if (sig1 != @@MAGIC_SIG1)
|
464
|
+
raise Error::InvalidMagicError.new
|
465
|
+
end
|
466
|
+
|
467
|
+
data = file.read(4)
|
468
|
+
raise Error::InvalidMagicError.new if (data.nil?)
|
469
|
+
sig2 = data.unpack("L*")[0]
|
470
|
+
if (sig2 != @@MAGIC_SIG2)
|
471
|
+
raise Error::InvalidMagicError.new
|
472
|
+
end
|
473
|
+
|
474
|
+
data = file.read(4)
|
475
|
+
raise Error::InvalidVersionError.new if (data.nil?)
|
476
|
+
ver = data.unpack("L*")[0]
|
477
|
+
if ((ver & 0xffff0000) != @@VERSION)
|
478
|
+
raise Error::InvalidVersionError.new if (data.nil?)
|
479
|
+
end
|
480
|
+
end
|
481
|
+
private :read_magic_and_version
|
482
|
+
|
483
|
+
def to_s
|
484
|
+
return @db.to_s
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
require "rubeepass/entry"
|
489
|
+
require "rubeepass/error"
|
490
|
+
require "rubeepass/group"
|
491
|
+
require "rubeepass/protected_decryptor"
|
data/lib/string.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Redefine String class to allow for colorizing and rsplit
|
2
|
+
class String
|
3
|
+
def blue
|
4
|
+
return colorize(36)
|
5
|
+
end
|
6
|
+
|
7
|
+
def colorize(color)
|
8
|
+
return "\e[#{color}m#{self}\e[0m"
|
9
|
+
end
|
10
|
+
|
11
|
+
def fix
|
12
|
+
# Fix unicode (I think???)
|
13
|
+
# Apparently sometimes length and bytesize don't always agree.
|
14
|
+
# When this happens, there are "invisible" bytes, which I need
|
15
|
+
# to be "visible". Converting to hex and back fixes this.
|
16
|
+
if (length != bytesize)
|
17
|
+
return self.unpack("H*").pack("H*")
|
18
|
+
end
|
19
|
+
return self
|
20
|
+
end
|
21
|
+
|
22
|
+
def green
|
23
|
+
return colorize(32)
|
24
|
+
end
|
25
|
+
|
26
|
+
def red
|
27
|
+
return colorize(31)
|
28
|
+
end
|
29
|
+
|
30
|
+
def rsplit(pattern)
|
31
|
+
ret = rpartition(pattern)
|
32
|
+
ret.delete_at(1)
|
33
|
+
return ret
|
34
|
+
end
|
35
|
+
|
36
|
+
def white
|
37
|
+
return colorize(37)
|
38
|
+
end
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubeepass
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Miles Whittaker
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.8'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 5.8.1
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '5.8'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 5.8.1
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: djinni
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.0'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.0.2
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '1.0'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.0.2
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: os
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0.9'
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 0.9.6
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0.9'
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 0.9.6
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: salsa20
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - "~>"
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0.1'
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.1.1
|
83
|
+
type: :runtime
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.1'
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 0.1.1
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: scoobydoo
|
95
|
+
requirement: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - "~>"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0.1'
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 0.1.1
|
103
|
+
type: :runtime
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0.1'
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 0.1.1
|
113
|
+
description: Ruby KeePass 2.x implementation. Currently it is read-only.
|
114
|
+
email: mjwhitta@gmail.com
|
115
|
+
executables:
|
116
|
+
- rpass
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- bin/rpass
|
121
|
+
- lib/builtins/cd_wish.rb
|
122
|
+
- lib/builtins/clear_wish.rb
|
123
|
+
- lib/builtins/copy_wish.rb
|
124
|
+
- lib/builtins/ls_wish.rb
|
125
|
+
- lib/builtins/pwd_wish.rb
|
126
|
+
- lib/builtins/show_wish.rb
|
127
|
+
- lib/rubeepass.rb
|
128
|
+
- lib/rubeepass/entry.rb
|
129
|
+
- lib/rubeepass/error.rb
|
130
|
+
- lib/rubeepass/error/invalid_gzip_error.rb
|
131
|
+
- lib/rubeepass/error/invalid_header_error.rb
|
132
|
+
- lib/rubeepass/error/invalid_magic_error.rb
|
133
|
+
- lib/rubeepass/error/invalid_password_error.rb
|
134
|
+
- lib/rubeepass/error/invalid_protected_data_error.rb
|
135
|
+
- lib/rubeepass/error/invalid_protected_stream_key_error.rb
|
136
|
+
- lib/rubeepass/error/invalid_version_error.rb
|
137
|
+
- lib/rubeepass/error/not_aes_error.rb
|
138
|
+
- lib/rubeepass/error/not_salsa20_error.rb
|
139
|
+
- lib/rubeepass/group.rb
|
140
|
+
- lib/rubeepass/protected_decryptor.rb
|
141
|
+
- lib/string.rb
|
142
|
+
homepage: http://mjwhitta.github.io/rubeepass
|
143
|
+
licenses:
|
144
|
+
- GPL-3.0
|
145
|
+
metadata: {}
|
146
|
+
post_install_message:
|
147
|
+
rdoc_options: []
|
148
|
+
require_paths:
|
149
|
+
- lib
|
150
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
requirements: []
|
161
|
+
rubyforge_project:
|
162
|
+
rubygems_version: 2.4.8
|
163
|
+
signing_key:
|
164
|
+
specification_version: 4
|
165
|
+
summary: Ruby KeePass 2.x implementation
|
166
|
+
test_files: []
|
167
|
+
has_rdoc:
|