kasefet 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ef83c9b70e27855bb629f577ac6fd37de8b5477d
4
- data.tar.gz: b2b865ac6e19bcb17cc5143c5c3f0e0b7554f3e2
3
+ metadata.gz: a594687e14c4388b280060fb9684510f8f3151d0
4
+ data.tar.gz: 33368567de1b1ebda745b80552e58291eeb91bd9
5
5
  SHA512:
6
- metadata.gz: ddd53fc18dd123c69c9b4113765b72c06d0240eede4efb976b10a7dec3b7886d2aa00ae2b9d1f7c6a90f9dfb2f032c853246bcb574255f8daff075edf3771182
7
- data.tar.gz: 813b83d332a7bd0817d746978811ace68885ddfe9bf4d897f85a71871d1edf439b914a39425f563c77c8c6650129e232eb54226802e5c15e5d488d671afbf380
6
+ metadata.gz: fd9155c78f966ae7a061fbee9f4594d131f614a20360114d26771317b79c0883bd7b065815e19ff3323b036da649c8b0add3c743769e22c4eb397556b031c1de
7
+ data.tar.gz: 0662f1cadf77d4f873620b2f959e42227b0d2060fe58c036e4c3e19ca44f4b982204a9b360c631564ade8c6a259e13eda6de48b56cbe603cd770637ab0dd78e3
data/DESIGN.md CHANGED
@@ -4,6 +4,8 @@ We reuse this concept in several places, so here's the generic concept. I need t
4
4
 
5
5
  ```
6
6
  root
7
+ |--index
8
+ | |--index
7
9
  |--db
8
10
  |--c39d7d6b239c2e20a31672ed979fed2b45c88748d06ba3be6bff85767b5d3d
9
11
  |--20160224.184012.laptop
@@ -22,15 +24,40 @@ I leave it as an exercise to the sync program to correctly identify deleted valu
22
24
 
23
25
  When encrypting files in such a format, I like to keep them prefixed to the encrypted content and then the auth tag. Note that the first version only supports AES-256-GCM.
24
26
 
25
- # File format
27
+ ## File format
28
+
29
+ The actual format of the value files is a binary file with the magic number "KSFT". The layout is simple:
30
+
31
+ ```
32
+ +-------------------------------------------------------------------------------------------+
33
+ | Magic Number | Key Length (32 bit big endian unsigned integer) | Key | Value until EOF |
34
+ | KSFT | 0x0006 | foobar | bazquux.... |
35
+ +-------------------------------------------------------------------------------------------+
36
+ ```
37
+
38
+ ## Index directory
39
+
40
+ The index is a quick way to convert/iterate over all the keys in a KV store.
41
+
42
+ There are two versions of the index file, which involve tradeoffs between change velocity and the likelihood of conflicts. The tradeoff is between storage size and expected velocity.
43
+
44
+ ## Low-velocity index
45
+
46
+ This form is ideal for stores that don't change very often, as the index can be shared between all nodes, and generally doesn't need to be regenerated unless there's a conflict.
47
+
48
+ The idea is that there is a single "index" file, which gets updated each time there's a new key added, and it's left to the sync program to leave a "conflicted copy" of the file, which indicates that the index should be rebuilt automatically.
49
+
50
+ ## High-velocity index
51
+
52
+ This is the preferred form if you expect to have many concurrent changes. In this approach, each node maintains its own view of the index, and regenerates it pretty much every single chance it gets. The drawback is that changes are not automatically detected, and the extra storage, which can be non-trivial if there are a large number of keys.
53
+
54
+ # Kasefet Password Wallets
26
55
 
27
56
  A kasefet wallet has the following layout:
28
57
 
29
58
  ```
30
59
  wallet
31
60
  |--key
32
- |--index
33
- | |--index
34
61
  |--metadata (flat file kv directory)
35
62
  |--ksft (flat file kv directory)
36
63
  ```
data/PHILOSOPHY.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # The name
2
2
 
3
- Kasefet is the hebrew word for "safe", (the locked box meaning of safe). Seemed as good as any, and wasn't taken on rubygems
3
+ Kasefet is the Hebrew word for "safe", (the locked box meaning of safe). Seemed as good as any, and wasn't taken on RubyGems
4
4
 
5
5
  # A bit of history
6
6
 
data/README.md CHANGED
@@ -10,12 +10,12 @@ Kasefet is currently a work in progress. This is the todo list for version 1.0,
10
10
 
11
11
  - [x] Flat file wallet
12
12
  - [x] SSL encrypted wallet
13
- - [ ] support for multiple wallets
13
+ - [x] support for multiple wallets
14
14
  - [x] editor integration
15
- - [ ] clipboard integration (Mac)
16
- - [ ] autotype (Mac)
17
- - [ ] clipboard integration (Ubuntu)
18
- - [ ] autotype (Ubuntu)
15
+ - [x] clipboard integration (Mac Yosemite 10.10)
16
+ - [ ] autotype (Mac Yosemite 10.10)
17
+ - [x] clipboard integration (Ubuntu 14.04)
18
+ - [ ] autotype (Ubuntu 14.04)
19
19
 
20
20
  ## Installation
21
21
 
data/kasefet.gemspec CHANGED
@@ -1,7 +1,9 @@
1
1
  # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
2
+ lib = File.expand_path("../lib", __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'kasefet/version'
4
+ require "kasefet/version"
5
+
6
+ require "rbconfig" # for platform data
5
7
 
6
8
  Gem::Specification.new do |spec|
7
9
  spec.name = "kasefet"
@@ -19,8 +21,15 @@ Gem::Specification.new do |spec|
19
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
22
  spec.require_paths = ["lib"]
21
23
 
24
+ spec.add_runtime_dependency "thunder", "~> 0.7"
25
+ spec.add_runtime_dependency "clipboard", "~> 1.0"
26
+
27
+ case RbConfig::CONFIG['host_os']
28
+ when /windows/
29
+ spec.add_runtime_dependency "ffi"
30
+ end
31
+
22
32
  spec.add_development_dependency "bundler", "~> 1.10"
23
33
  spec.add_development_dependency "rake", "~> 10.0"
24
- spec.add_development_dependency "thunder", "~> 0.7"
25
34
  spec.add_development_dependency "minitest"
26
35
  end
data/lib/kasefet/cli.rb CHANGED
@@ -2,6 +2,7 @@ require "thunder"
2
2
 
3
3
  require "kasefet/config"
4
4
  require "kasefet/wallet"
5
+ require "kasefet/multi_wallet"
5
6
 
6
7
  class Kasefet
7
8
  class CLI
@@ -14,17 +15,35 @@ class Kasefet
14
15
 
15
16
  include Thunder
16
17
 
17
- def load_config(options)
18
+ desc "copy KEYNAME", "copy the contents of KEYNAME to the primary system clipboard"
19
+ def copy(keyname, **options)
20
+ load_config(options)
21
+ load_wallet(options)
22
+
23
+ require 'clipboard'
24
+
25
+ Clipboard.copy(@wallets.load(keyname))
26
+ end
27
+
28
+ def load_config(options = {})
18
29
  config_file = options[:config]
19
30
  config_file ||= GlobalConfigLocations.find { |file| File.exist?(File.expand_path(file)) }
20
31
  config_file ||= File.expand_path(GlobalConfigLocations.first)
21
32
  @config = Kasefet::Config.new(config_file)
33
+ @config.load
34
+ return @config
22
35
  end
23
36
 
24
- def load_wallet
25
- wallet_dir = @config["wallet"]
26
- wallet_dir = @config["wallet"] = File.expand_path(DefaultWalletLocation) unless wallet_dir
27
- @wallet = Kasefet::Wallet.new(directory: wallet_dir)
37
+ def load_wallet(options = {})
38
+ wallet_dirs = @config["wallet"]
39
+ wallet_dirs = @config["wallet"] = File.expand_path(DefaultWalletLocation) unless wallet_dirs
40
+ wallet_dirs = Array(wallet_dirs)
41
+ wallets = wallet_dirs.map do |wallet_dir|
42
+ [wallet_dir, Kasefet::Wallet.new(directory: wallet_dir)]
43
+ end.to_h
44
+
45
+ @wallets = Kasefet::MultiWallet.new(wallets)
46
+ return @wallets
28
47
  end
29
48
 
30
49
  def determine_editor
@@ -44,13 +63,13 @@ class Kasefet
44
63
  desc "edit KEYNAME", "open the contents of KEYNAME in an editor, and save the changes"
45
64
  def edit(keyname, *content, **options)
46
65
  load_config(options)
47
- load_wallet
66
+ load_wallet(options)
48
67
 
49
68
  require 'tmpdir'
50
69
  require 'pathname'
51
70
  Dir.mktmpdir do |tmpdir|
52
71
  tmpdir = Pathname.new(tmpdir)
53
- content = @wallet.load(keyname)
72
+ content = @wallets.load(keyname)
54
73
  File.binwrite(tmpdir + keyname, content)
55
74
 
56
75
  # invoke the editor
@@ -63,7 +82,7 @@ class Kasefet
63
82
  puts "`#{editor}` exited with error status: #{$?}. Not saving contents"
64
83
  else
65
84
  new_content = File.binread(tmpdir + keyname)
66
- @wallet.store(keyname, new_content)
85
+ @wallets.store(keyname, new_content)
67
86
  end
68
87
  end
69
88
  end
@@ -71,11 +90,11 @@ class Kasefet
71
90
  desc "add KEYNAME CONTENTS...", "store the given CONTENTS in KEYNAME"
72
91
  def add(keyname, *content, **options)
73
92
  load_config(options)
74
- load_wallet
93
+ load_wallet(options)
75
94
 
76
95
  content = content.join(" ")
77
96
 
78
- @wallet.store(keyname, content)
97
+ @wallets.store(keyname, content)
79
98
 
80
99
  return content
81
100
  end
@@ -83,9 +102,9 @@ class Kasefet
83
102
  desc "show KEYNAME", "print the contents of KEYNAME to stdout"
84
103
  def show(keyname, **options)
85
104
  load_config(options)
86
- load_wallet
105
+ load_wallet(options)
87
106
 
88
- content = @wallet.load(keyname)
107
+ content = @wallets.load(keyname)
89
108
 
90
109
  puts content
91
110
  return content
@@ -3,8 +3,6 @@ class Kasefet
3
3
  def initialize(file)
4
4
  @file = file
5
5
  @settings = {}
6
- return unless File.exists?(file)
7
- load
8
6
  end
9
7
 
10
8
  attr_accessor :file
@@ -18,17 +16,30 @@ class Kasefet
18
16
  last_segment[key.split(".")[-1]] = value
19
17
  end
20
18
 
19
+ def each_keys()
20
+ return @settings.keys.each unless block_given?
21
+
22
+ @settings.keys.each do |key|
23
+ yield key
24
+ end
25
+ end
26
+
21
27
  def load
22
- case File.extname(@file)
28
+ return unless File.exists?(@file)
29
+ parse(File.read(@file))
30
+ end
31
+
32
+ def parse(contents, file_path = @file)
33
+ case File.extname(file_path)
23
34
  when ".yaml", ".yml"
24
35
  require 'yaml'
25
- @settings = YAML.load(File.read(file))
36
+ @settings = YAML.load(contents)
26
37
  when ".json"
27
38
  require 'json'
28
- @settings = JSON.parse(File.read(file))
39
+ @settings = JSON.parse(contents)
29
40
  else
30
41
  # try the key=value format
31
- content = File.read(file)
42
+ content = contents
32
43
  content.split("\n").each do |line|
33
44
  key, value = line.split("=")
34
45
  value = value.to_i if value =~ /\d+/
@@ -43,15 +54,19 @@ class Kasefet
43
54
  end
44
55
 
45
56
  def save
46
- case File.extname(@file)
57
+ File.write(@file, format())
58
+ end
59
+
60
+ def format(file_path = @file)
61
+ case File.extname(file_path)
47
62
  when ".json"
48
63
  require 'json'
49
- File.write(@file, @settings.to_json)
64
+ return @settings.to_json
50
65
  when ".yaml", ".yml"
51
66
  require 'yaml'
52
- File.write(@file, @settings.to_yaml)
67
+ return @settings.to_yaml
53
68
  else
54
- to_write = flatten_hash(@settings).map do |key, value|
69
+ return flatten_hash(@settings).map do |key, value|
55
70
  if value.is_a? Array
56
71
  value.map do |array_value|
57
72
  "#{key}=#{array_value}"
@@ -60,7 +75,6 @@ class Kasefet
60
75
  "#{key}=#{value}"
61
76
  end
62
77
  end.join("\n")
63
- File.write(@file, to_write)
64
78
  end
65
79
  end
66
80
 
@@ -8,13 +8,19 @@ class Kasefet
8
8
  CipherIVLength = 12
9
9
  CipherAuthTagLength = 16
10
10
 
11
- def initialize(cipher_key:, **options)
11
+ def initialize(cipher_key:, key_salt: nil, **options)
12
12
  super(**options)
13
13
  @cipher = OpenSSL::Cipher.new("aes-256-gcm")
14
14
  @cipher_key = cipher_key
15
+ @key_salt = key_salt
15
16
  end
16
17
 
17
- attr_accessor :cipher_key
18
+ attr_accessor :cipher_key, :key_salt
19
+
20
+ def key_to_digest(key)
21
+ key = "#{@key_salt}/#{key}/#{@key_salt}" if @key_salt
22
+ return super(key)
23
+ end
18
24
 
19
25
  def reencrypt_all_values!(new_key)
20
26
  old_key = @cipher_key
@@ -51,14 +57,14 @@ class Kasefet
51
57
  return value
52
58
  end
53
59
 
54
- def [](key)
55
- encrypted_value = super(key)
56
- return nil if encrypted_value.nil?
57
- return decrypt_value(encrypted_value, @cipher_key)
60
+ def read_file(file_path)
61
+ encrypted_contents = super(file_path)
62
+ return nil if encrypted_contents.nil?
63
+ return decrypt_value(encrypted_contents, @cipher_key)
58
64
  end
59
65
 
60
- def []=(key, value)
61
- super(key, encrypt_value(value, @cipher_key))
66
+ def write_file(path, contents)
67
+ return super(path, encrypt_value(contents, @cipher_key))
62
68
  end
63
69
  end
64
70
  end
@@ -8,6 +8,8 @@ class Kasefet
8
8
  #
9
9
  # Flat-file based key-value storage engine that is designed to be compatible with naive sync programs
10
10
  class FlatKV
11
+ MagicNumber = "KSFT"
12
+
11
13
  # @option [String] :root The root directory of the FlatKV store
12
14
  # @option [String] :device_name The device name to use to identify the writer
13
15
  # @option [String] :extension The default extension to use for the value files
@@ -26,15 +28,44 @@ class Kasefet
26
28
 
27
29
  def [](key)
28
30
  value_file = file_for_key(key)
29
- return nil unless value_file
30
- return File.binread(value_file)
31
+ contents = read_file(value_file)
32
+ _, value = read_value(contents)
33
+ return value
34
+ end
35
+
36
+ def read_file(file_path)
37
+ return nil unless file_path
38
+ return File.binread(file_path)
39
+ end
40
+
41
+ def read_value(file_contents)
42
+ return nil, nil unless file_contents
43
+
44
+ raise "FlatKV value file must be at least 8 bytes long" unless file_contents.bytesize >= 8
45
+ magic_number, key_size, file_contents = file_contents.unpack("A4NA*")
46
+ raise "FlatKV value file must be a KSFT file" unless magic_number == MagicNumber
47
+ raise "FlatKV value file has been corrupted. Key length in header is longer than file" unless file_contents.bytesize >= key_size
48
+ key_name, file_contents = file_contents.unpack("A#{key_size}A*")
49
+
50
+ return key_name, file_contents
31
51
  end
32
52
 
33
53
  def []=(key, value)
34
54
  key_dir = dir_for_key(key)
35
55
  FileUtils.mkdir_p(key_dir)
36
56
  value_file_name = Time.now.strftime("%Y%m%d.%H%M%S%6N.") + @device_name + @extension
37
- File.binwrite(key_dir + value_file_name, value)
57
+
58
+ value = format_value(key, value)
59
+
60
+ write_file(key_dir + value_file_name, value)
61
+ end
62
+
63
+ def format_value(key, value)
64
+ [MagicNumber, key.bytesize, key, value].pack("A4NA*A*")
65
+ end
66
+
67
+ def write_file(path, contents)
68
+ File.binwrite(path, contents)
38
69
  end
39
70
 
40
71
  def file_for_key(key)
@@ -44,8 +75,12 @@ class Kasefet
44
75
  return files.last
45
76
  end
46
77
 
78
+ def key_to_digest(key)
79
+ OpenSSL::Digest::SHA256.hexdigest(key)
80
+ end
81
+
47
82
  def dir_for_key(key)
48
- digest = OpenSSL::Digest::SHA256.hexdigest(key)
83
+ digest = key_to_digest(key)
49
84
  return @root + digest[0..1] + digest[2..-1]
50
85
  end
51
86
  end
@@ -0,0 +1,76 @@
1
+ require 'fileutils'
2
+
3
+ require 'kasefet/flat_kv'
4
+ require 'kasefet/config'
5
+
6
+ class Kasefet
7
+ class IndexedFlatKV
8
+ def initialize(flat_kv:, index_dir: "index", index_ext: "")
9
+ @flat_kv = flat_kv
10
+ @root = flat_kv.root
11
+ @index_dir = @root + "#{index_dir}"
12
+ @index_file = @index_dir + "index#{index_ext}"
13
+ @index = Kasefet::Config.new(@index_file)
14
+ end
15
+
16
+ attr_accessor :index, :index_file
17
+
18
+ def [](key)
19
+ return @flat_kv[key]
20
+ end
21
+
22
+ def []=(key, value)
23
+ mark_key(key, @flat_kv.key_to_digest(key))
24
+ return @flat_kv[key] = value
25
+ end
26
+
27
+ def load_index
28
+ index_contents = @flat_kv.read_file(@index_file)
29
+ @index.parse(index_contents)
30
+ end
31
+
32
+ def conflicted?
33
+ Dir[@index_dir + "*"].size != 1
34
+ end
35
+
36
+ def rebuild_index
37
+ Dir.foreach(@root) do |prefix|
38
+ next unless prefix =~ /\h\h/
39
+ Dir.foreach(@root + prefix) do |key_dir|
40
+ next unless key_dir =~ /\h{62}/ # SHA256 digest is 64 bytes, first two are the prefix
41
+ Dir.foreach(@root + prefix + key_dir) do |value_file|
42
+ next if [".", ".."].include?(value_file)
43
+ value_path = @root + prefix + key_dir + value_file
44
+ contents = @flat_kv.read_file(value_path)
45
+ key_name, value = @flat_kv.read_value(contents)
46
+ mark_key(key_name, prefix + key_dir)
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ def mark_key(key, digest)
53
+ @index["dig:" + digest] = key
54
+ @index["key:" + key] = digest
55
+ end
56
+
57
+ def save_index
58
+ index_contents = @index.format
59
+ FileUtils.mkdir_p(@index_dir)
60
+ @flat_kv.write_file(@index.file, index_contents)
61
+ end
62
+
63
+ def has_key?(key)
64
+ return !! @index["key:" + key]
65
+ end
66
+
67
+ def each_keys
68
+ return enum_for(__method__) unless block_given?
69
+
70
+ @index.each_keys do |key|
71
+ next unless key.start_with?("key:")
72
+ yield key[4..-1]
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,24 @@
1
+ require "kasefet/wallet"
2
+
3
+ class Kasefet
4
+ class MultiWallet
5
+ def initialize(wallets)
6
+ @wallets = wallets
7
+ end
8
+
9
+ attr_accessor :wallets
10
+
11
+ def load(keyname)
12
+ @wallets.each do |name, wallet|
13
+ contents = wallet.load(keyname)
14
+ return contents unless contents.nil?
15
+ end
16
+ return nil
17
+ end
18
+
19
+ def store(keyname, content, name = nil)
20
+ name ||= @wallets.keys.first
21
+ @wallets[name].store(keyname, content)
22
+ end
23
+ end
24
+ end
@@ -1,3 +1,3 @@
1
1
  class Kasefet
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -27,22 +27,21 @@ class Kasefet
27
27
  @wallet_version = @wallet_version.to_i
28
28
  raise "Unknown Kasefet Wallet version: #{@wallet_version}" unless @wallet_version <= VERSION
29
29
 
30
- @name_salt = @metadata["kasefet.name_salt"]
31
- @credentials = Kasefet::EncryptedFlatKV.new(root: @root + CREDENTIALS_DIR, cipher_key: @master_key.key)
30
+ @credentials = Kasefet::EncryptedFlatKV.new(
31
+ root: @root + CREDENTIALS_DIR,
32
+ cipher_key: @master_key.key,
33
+ key_salt: @metadata["kasefet.name_salt"],
34
+ )
32
35
  end
33
36
 
34
37
  attr_accessor :root
35
38
 
36
- def salted_keyname(name)
37
- return "#{@name_salt}/#{name}/#{@name_salt}"
38
- end
39
-
40
39
  def load(name)
41
- return @credentials[salted_keyname(name)]
40
+ return @credentials[name]
42
41
  end
43
42
 
44
43
  def store(name, creds)
45
- @credentials[salted_keyname(name)] = creds
44
+ @credentials[name] = creds
46
45
  end
47
46
  end
48
47
  end
metadata CHANGED
@@ -1,57 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kasefet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Karas
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-03-07 00:00:00.000000000 Z
11
+ date: 2016-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: bundler
14
+ name: thunder
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.10'
20
- type: :development
19
+ version: '0.7'
20
+ type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.10'
26
+ version: '0.7'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rake
28
+ name: clipboard
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.10'
34
48
  type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: '10.0'
54
+ version: '1.10'
41
55
  - !ruby/object:Gem::Dependency
42
- name: thunder
56
+ name: rake
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - "~>"
46
60
  - !ruby/object:Gem::Version
47
- version: '0.7'
61
+ version: '10.0'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
- version: '0.7'
68
+ version: '10.0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: minitest
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -91,7 +105,9 @@ files:
91
105
  - lib/kasefet/config.rb
92
106
  - lib/kasefet/encrypted_flat_kv.rb
93
107
  - lib/kasefet/flat_kv.rb
108
+ - lib/kasefet/indexed_flat_kv.rb
94
109
  - lib/kasefet/master_key.rb
110
+ - lib/kasefet/multi_wallet.rb
95
111
  - lib/kasefet/version.rb
96
112
  - lib/kasefet/wallet.rb
97
113
  homepage: https://github.com/stevenkaras/kasefet