can 0.10.13

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d06281ea5c30184ab441e8e55e192b5f283e592926440f5a0e9c7fd99c7632fe
4
+ data.tar.gz: a10271e0b9fc97618632b9d733578825e3e156e319cd976149431a97beb83c7e
5
+ SHA512:
6
+ metadata.gz: 166a03130bbb9e6078fc4fb4e87a57cae1e07fdd4fd70b959737a9c42f4b0e9a47b43fe1ff625bd3896ee225638e950e50c4294b9ac41655aa187d338984ffd1
7
+ data.tar.gz: 564ee2b2a40f920820195f480fbaa238921657f303198307e9d914964278a77f65c46e3c2c66e0bbf95eefa00d9a6754601e234f4252af2f3f76a715c32234c7
@@ -0,0 +1 @@
1
+ pkg/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,95 @@
1
+ # Can
2
+
3
+ A small cli tool that stores encrypted goods.
4
+
5
+ It uses symmetric cryptography with the cipher `AES-256-CBC`. The file database
6
+ of secrets is ascii based so feel free to commit it.
7
+
8
+
9
+ # Installation
10
+
11
+ gem install can
12
+
13
+
14
+ # Usage
15
+
16
+ % can set test ok # Stores a secret
17
+ Key test was stored.
18
+ % can get test # Copy that secret to the clipboard
19
+ Password:
20
+ Key test was copied to the clipboard.
21
+
22
+
23
+ ## Commands
24
+
25
+ % can
26
+ Commands:
27
+ can decrypt DATA # Decrypts data
28
+ can encrypt DATA # Encrypts data
29
+ can get KEY # Copies a KEY to the clipboard
30
+ can help [COMMAND] # Describe available commands or one specific command
31
+ can ls [TAG] # Lists all keys (filter optionally by TAG)
32
+ can password # Change the can password
33
+ can random [LENGTH] # Generates a random password
34
+ can rename KEY NEW_NAME # Renames a secret
35
+ can rm KEY # Removes a key
36
+ can set KEY [VALUE] # Stores a value (empty VALUE show the prompt; use '@rando...
37
+ can tag KEY TAG # Tags a key
38
+ can tags [KEY] # Show all tags (filter for a key)
39
+ can untag KEY TAG # Untags a tag from a key
40
+ can version # Show the current version
41
+
42
+ Options:
43
+ -p, [--password=PASSWORD]
44
+ -v, [--verbose], [--no-verbose]
45
+ -f, [--file=FILE]
46
+
47
+
48
+ ## Using another can file
49
+
50
+ Use the `CAN_FILE` environment variable or pass the `--file FILE` or `-f FILE`
51
+ param option to use another can file:
52
+
53
+ % export CAN_FILE="$HOME/secrets/main.can"
54
+ % can ls --file $HOME/secrets/main.can
55
+ % can ls -f $HOME/secrets/main.can
56
+ aws-root
57
+ azure-aad
58
+ vpn-demo
59
+
60
+
61
+ ## Default lookup files
62
+
63
+ If you don't pass an explicit `--file` or `-f` the default ones are checked in
64
+ order. The first one to exist is used:
65
+
66
+ ~/.config/can/main.can
67
+ ~/.can
68
+
69
+
70
+ ## Default lookup directories
71
+
72
+ If you pass an explicit file (via the `--file` or `-f` flags) *without* a `.can`
73
+ file extension the following directories are checked. The first one to have a
74
+ file that exists with a `<dir>/<file>.can` match is used:
75
+
76
+ ~/.config/can
77
+ /etc/can
78
+
79
+
80
+ ## Avoid the password prompt
81
+
82
+ Use the `CAN_PASSWORD` environment variable to avoid the password prompt:
83
+
84
+ % export CAN_PASSWORD="secret"
85
+ % can ls
86
+ aws-root
87
+ azure-aad
88
+ vpn-demo
89
+
90
+ Or pass it as a arg option (`-p` or `--password`):
91
+
92
+ % can ls -p secret
93
+ aws-root
94
+ azure-aad
95
+ vpn-demo
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ task default: [
5
+ :release,
6
+ ]
data/bin/can ADDED
@@ -0,0 +1,330 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + "/../lib"
3
+
4
+ require "thor"
5
+ require "can"
6
+ require "io/console"
7
+ require "json"
8
+ require "tablelize"
9
+
10
+ DIRS = [
11
+ "#{ENV['HOME']}/.config/can",
12
+ "/etc/can",
13
+ ]
14
+
15
+ FILES = [
16
+ "#{ENV['HOME']}/.config/can/main.can",
17
+ "#{ENV['HOME']}/.can",
18
+ ]
19
+
20
+ module Can
21
+ class Cli < Thor
22
+ class_option :password, aliases: "-p"
23
+ class_option :verbose, aliases: "-v", :type => :boolean
24
+ class_option :file, aliases: "-f"
25
+
26
+ desc "version", "Show the current version"
27
+ def version
28
+ puts VERSION
29
+ latest = Util.latest()
30
+ debug "Remote version: #{latest}"
31
+ if Gem::Version.new(latest) > Gem::Version.new(VERSION)
32
+ STDERR.puts "New version #{latest} available."
33
+ end
34
+ end
35
+
36
+ # desc "dump", "Show all the contents"
37
+ # def dump
38
+ # init
39
+ # p @can.all()
40
+ # end
41
+
42
+ # desc "info", "Show the current state of affairs"
43
+ # def info
44
+ # init
45
+ # # options[:verbose] = true
46
+ # ENV["CAN_DEBUG"] = "1"
47
+ # _info()
48
+ # end
49
+
50
+ desc "ls [TAG]", "Lists all keys (filter optionally by TAG)"
51
+ option :short, :type => :boolean, aliases: "-s"
52
+ def ls(tag=nil)
53
+ init
54
+ rows = [["KEY", "LEN", "CREATED", "TAGS"]]
55
+ empty = true
56
+
57
+ @can.all().each do |key, entry|
58
+ empty = false
59
+ next if tag and (not entry["tags"] or not entry["tags"].include?(tag))
60
+ if options[:short]
61
+ puts key
62
+ next
63
+ end
64
+
65
+ if entry.class == Hash
66
+ value = entry["value"]
67
+ created = entry["created"]
68
+ entry["tags"] = entry["tags"] || []
69
+ tags = entry["tags"].join(" ")
70
+ else
71
+ value = entry
72
+ created = ""
73
+ tags = ""
74
+ end
75
+
76
+ rows << [key, value.size, created, tags]
77
+ # if options[:values]
78
+ # rows << [key, value, created, tags]
79
+ # else
80
+ # rows << [key, value.size, created, tags]
81
+ # end
82
+ end
83
+
84
+ if empty
85
+ puts "The can file #{@file} is empty."
86
+ return
87
+ end
88
+
89
+ Tablelize::table(rows) if not options[:short]
90
+ end
91
+
92
+ desc "get KEY", "Copies a KEY to the clipboard"
93
+ def get(key)
94
+ init
95
+ entry = @can.get(key) or abort "Key #{key} does not exist."
96
+ copy(val(entry))
97
+ puts "Key #{key} was copied to the clipboard."
98
+ end
99
+
100
+ desc "rename KEY NEW_NAME", "Renames a secret"
101
+ def rename(key, new_name)
102
+ init
103
+ entry = @can.get(key) or abort "Key #{key} does not exist."
104
+ @can.rename key, new_name
105
+ puts "Key #{key} was renamed to #{new_name}."
106
+ end
107
+
108
+ desc "password", "Change the can password"
109
+ def password
110
+ init
111
+ pass1 = ask("New password: ")
112
+ pass2 = ask("Confirm password: ")
113
+ if pass1 != pass2
114
+ puts "Passwords don't match."
115
+ abort
116
+ end
117
+ @can.password pass1
118
+ puts "Can password was changed."
119
+ end
120
+
121
+ desc "set KEY [VALUE]", "Stores a value (empty VALUE show the prompt; use '@random' for a random value)"
122
+ def set(key, value=nil)
123
+ init
124
+ clipboard = false
125
+ if value == "@random"
126
+ clipboard = true
127
+ value = random(60, true)
128
+ end
129
+ value = value || ask("Value: ")
130
+ @can.set key, value
131
+ if clipboard
132
+ copy(value)
133
+ puts "Key #{key} was stored and copied to the clipboard."
134
+ else
135
+ puts "Key #{key} was stored."
136
+ end
137
+ end
138
+
139
+ desc "rm KEY", "Removes a key"
140
+ def rm(key)
141
+ init
142
+ @can.exists(key) or abort "Key #{key} does not exist."
143
+ @can.remove key
144
+ puts "Key #{key} was deleted."
145
+ end
146
+
147
+ desc "tag KEY TAG", "Tags a key"
148
+ def tag(key, tag)
149
+ init
150
+ @can.exists(key) or abort "Key #{key} does not exist."
151
+ ok = @can.tag(key, tag)
152
+ puts "Tag #{tag} was added to #{key}." if ok
153
+ puts "Tag #{tag} already exists on #{key}." unless ok
154
+ end
155
+
156
+ desc "tags [KEY]", "Show all tags (filter for a key)"
157
+ def tags(key=nil)
158
+ init
159
+ if key
160
+ @can.exists(key) or abort "Key #{key} does not exist."
161
+ entry = @can.get(key)
162
+ tags = entry["tags"] || []
163
+ else
164
+ tags = []
165
+ @can.all().each do |key, entry|
166
+ entry["tags"] = entry["tags"] || []
167
+ tags = tags | entry["tags"]
168
+ end
169
+ end
170
+
171
+ puts tags.uniq.join("\n")
172
+ end
173
+
174
+ desc "untag KEY TAG", "Untags a tag from a key"
175
+ def untag(key, tag)
176
+ init
177
+ @can.exists(key) or abort "Key #{key} does not exist."
178
+ ok = @can.untag(key, tag)
179
+ puts "Tag #{tag} was removed from #{key}." if ok
180
+ puts "Tag #{tag} doesn't exist on #{key}." unless ok
181
+ end
182
+
183
+ desc "migrate", "Migrates to new format"
184
+ def migrate()
185
+ init
186
+ count = @can.migrate()
187
+ puts "Keys migrated: #{count}."
188
+ end
189
+
190
+ desc "encrypt DATA", "Encrypts data"
191
+ def encrypt(data)
192
+ init
193
+ puts @can.encrypt(data)
194
+ end
195
+
196
+ desc "decrypt DATA", "Decrypts data"
197
+ def decrypt(data)
198
+ init
199
+ puts @can.decrypt(data)
200
+ end
201
+
202
+ desc "random [LENGTH]", "Generates a random password"
203
+ option :symbols, :type => :boolean, aliases: "-s"
204
+ def random(length=60, capture=false)
205
+ info
206
+ length = length.to_i
207
+ chars = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a
208
+ # chars += ("!".."?").to_a if options[:symbols]
209
+ chars += %w(! ? # $ @ % & ~ _ - + = : . ; , æ ø å\\ / \( \) { } < >).to_a \
210
+ if options[:symbols]
211
+ chars.delete(["'", '"'])
212
+ # pass = chars.sort_by { rand }.join[0...length]
213
+ pass = []
214
+ (1 .. length).each do
215
+ pass << chars[rand(chars.size)]
216
+ end
217
+ pass = pass.join()
218
+ return pass if capture
219
+ puts pass
220
+ end
221
+
222
+ # def method_missing name, *args
223
+ # name = name.to_s
224
+ # value = args[0]
225
+ # if value
226
+ # set name, value
227
+ # else
228
+ # get name
229
+ # end
230
+ # end
231
+
232
+ private
233
+ def info()
234
+ # latest = Can::Util::latest()
235
+ debug "Can version: #{VERSION}"
236
+ # debug "Latest version: #{latest}"
237
+ debug "Check files #{FILES}"
238
+ debug "Check dirs #{DIRS}"
239
+ debug "Using file #{@file}"
240
+ debug "Using options #{options}"
241
+ debug "Using verbose mode (goes to stderr)"
242
+ end
243
+
244
+ def init()
245
+ @file = options[:file] || ENV.fetch("CAN_FILE", nil)
246
+
247
+ if @file == nil or @file.length == 0
248
+ FILES.each do |file|
249
+ debug "Checking file #{file}"
250
+ if File.file?(file)
251
+ @file = file
252
+ debug "Using file #{file}"
253
+ break
254
+ end
255
+ end
256
+
257
+ elsif File.extname(@file) != ".can"
258
+ DIRS.each do |dir|
259
+ file = "#{dir}/#{@file}.can"
260
+ debug "Checking file #{file}"
261
+ if File.file?(file)
262
+ @file = file
263
+ debug "Using file #{file}"
264
+ break
265
+ end
266
+ end
267
+
268
+ end
269
+
270
+ raise "Cound't find a valid can file (#{@file})." if not File.file?(@file)
271
+
272
+ info()
273
+
274
+ return if check(options[:password], "options")
275
+ return if check(ENV.fetch("CAN_PASSWORD", nil), "environment")
276
+ return if check(ask(), "prompt")
277
+ abort
278
+ end
279
+
280
+ def check(password, source)
281
+ return if not password or password.length < 1
282
+
283
+ @can = Can::Store.new(@file, password)
284
+ begin
285
+ @can.all()
286
+ debug "Opened with password from #{source}"
287
+ debug "Can format: v#{@can.format()}"
288
+ return true
289
+ rescue
290
+ debug "Failed to open with password from #{source}"
291
+ raise "Wrong password"
292
+ end
293
+ end
294
+
295
+ def val(value)
296
+ return value["value"] if value.class == Hash
297
+ value
298
+ end
299
+
300
+ def ask(prompt = "Password: ")
301
+ print prompt
302
+ answer = STDIN.noecho(&:gets).chomp
303
+ raise Interrupt if answer.length < 1
304
+ puts
305
+ answer
306
+ end
307
+
308
+ def debug(message)
309
+ show = options[:verbose] || ENV.fetch("CAN_DEBUG", "0") == "1"
310
+ return if not show
311
+ STDERR.puts "\033[38;5;240m#{message}\033[0m"
312
+ end
313
+
314
+ def copy(value)
315
+ IO.popen("pbcopy", "w") { |cc| cc.write(value) }
316
+ value
317
+ end
318
+
319
+ end
320
+ end
321
+
322
+ begin
323
+ Can::Cli.start ARGV
324
+ rescue Interrupt
325
+ puts
326
+ exit 2
327
+ rescue Exception => e
328
+ puts "Error: #{e}"
329
+ exit 1
330
+ end
@@ -0,0 +1,27 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require "can"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "can"
8
+ spec.version = Can::VERSION
9
+ spec.summary = "Can stores encrypted goods using symmetric cryptography."
10
+ # spec.description = File.read("readme.md")
11
+ spec.description = "Can stores encrypted goods using symmetric cryptography (AES-256-CBC)"
12
+ spec.homepage = "https://github.com/ptdorf/can"
13
+ spec.authors = ["ptdorf"]
14
+ spec.email = ["ptdorf@gmail.com"]
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files`.split $/
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename f }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "thor"
22
+ spec.add_dependency "tablelize"
23
+ spec.add_dependency "base62-rb"
24
+ spec.add_development_dependency "rake"
25
+
26
+ # spec.required_ruby_version = "~> 2.4"
27
+ end
@@ -0,0 +1,9 @@
1
+ # $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + "/../lib"
2
+
3
+ require "can/store"
4
+ require "can/crypto"
5
+ require "can/util"
6
+
7
+ module Can
8
+ VERSION = "0.10.13"
9
+ end
@@ -0,0 +1,41 @@
1
+ require "openssl"
2
+ require "digest/sha1"
3
+ require "base64"
4
+
5
+ module Can
6
+ class Crypto
7
+
8
+ CIPHER = "AES-256-CBC"
9
+
10
+ def self.encrypt(content, password)
11
+ digest = Digest::SHA1.hexdigest(password)
12
+ cipher = OpenSSL::Cipher.new(CIPHER)
13
+ cipher.encrypt
14
+ cipher.key = digest[0..31]
15
+ cipher.iv = iv = cipher.random_iv
16
+ encrypted = cipher.update(content) + cipher.final
17
+
18
+ binit = Base64.strict_encode64(iv)
19
+ ilen = binit.length.to_s
20
+ blen = Base64.strict_encode64(ilen)
21
+ bdata = Base64.strict_encode64(encrypted)
22
+ # blen + "--" + binit + "--" + bdata
23
+ binit + "--" + bdata
24
+ end
25
+
26
+ def self.decrypt(content, password)
27
+ digest = Digest::SHA1.hexdigest(password)
28
+ init, encrypted = content.split("--").map do |v|
29
+ Base64.strict_decode64(v)
30
+ end
31
+
32
+ cipher = OpenSSL::Cipher.new(CIPHER)
33
+ cipher.decrypt
34
+ cipher.key = digest[0..31]
35
+ cipher.iv = init
36
+
37
+ cipher.update(encrypted) + cipher.final
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,174 @@
1
+ require "json"
2
+ require "time"
3
+ # require "zlib"
4
+
5
+ module Can
6
+ class Store
7
+
8
+ HEADER = "Can"
9
+ SEPARATOR = "\n\n"
10
+
11
+ def initialize(file, password)
12
+ @password = password
13
+ @file = file
14
+ @format = "1"
15
+ end
16
+
17
+ def all()
18
+ read()
19
+ end
20
+
21
+ def format()
22
+ @format
23
+ end
24
+
25
+ def exists(key)
26
+ data = read()
27
+ data[key] ? true : false
28
+ end
29
+
30
+ def get(key)
31
+ data = read()
32
+ data[key] || nil
33
+ end
34
+
35
+ def set(key, value)
36
+ data = read()
37
+ data[key] ||= {}
38
+ data[key]["value"] = value
39
+ data[key]["created"] = Time.new
40
+ data[key]["tags"] ||= []
41
+ write(data)
42
+ end
43
+
44
+ def rename(key, new_key)
45
+ data = read()
46
+ data[new_key] = data[key]
47
+ data.delete(key)
48
+ write(data)
49
+ end
50
+
51
+ def tag(key, tag)
52
+ data = read()
53
+ data[key]["tags"] = data[key]["tags"] || []
54
+ if not data[key]["tags"].include?(tag)
55
+ data[key]["tags"] << tag
56
+ return write(data)
57
+ end
58
+ false
59
+ end
60
+
61
+ def untag(key, tag)
62
+ data = read()
63
+ if data[key]["tags"] and data[key]["tags"].include?(tag)
64
+ data[key]["tags"].delete(tag)
65
+ return write(data)
66
+ end
67
+ false
68
+ end
69
+
70
+ def remove(key)
71
+ data = read()
72
+ data.delete(key)
73
+ write(data)
74
+ end
75
+
76
+ def encrypt(payload)
77
+ encrypted = Crypto.encrypt(payload, @password)
78
+ encode(encrypted)
79
+ end
80
+
81
+ def decrypt(payload)
82
+ decoded = decode(payload)
83
+ Crypto.decrypt(decoded, @password)
84
+ end
85
+
86
+ def password(new_password)
87
+ data = read()
88
+ @password = new_password
89
+ write(data)
90
+ end
91
+
92
+ def migrate()
93
+ count = 0
94
+ data = read()
95
+ data.each do |key, value|
96
+ # puts "Checking key #{key}..."
97
+ if value.class != Hash
98
+ data[key] = {}
99
+ data[key]["value"] = value
100
+ data[key]["created"] = Time.new
101
+ data[key]["tags"] = []
102
+ count += 1
103
+ puts "Key #{key} migrated to new format"
104
+ else
105
+ puts "Key #{key} already exists in new format"
106
+ end
107
+ end
108
+
109
+ write(data)
110
+ count
111
+ end
112
+
113
+ private
114
+ def read()
115
+ return {} unless File.exist?(@file)
116
+
117
+ content = File.read(@file)
118
+ headless = rm_header(content)
119
+ cleaned = clean(headless)
120
+ decoded = decode(cleaned)
121
+ decrypted = Crypto.decrypt(decoded, @password)
122
+
123
+ JSON.parse(decrypted)
124
+ end
125
+
126
+ def write(data)
127
+ payload = JSON.dump(data)
128
+ encrypted = Crypto.encrypt(payload, @password)
129
+ encoded = encode(encrypted)
130
+ aligned = align(encoded)
131
+ content = add_header(aligned)
132
+
133
+ File.write(@file, content)
134
+ end
135
+
136
+ def encode(data)
137
+ data.unpack("H*").first
138
+ end
139
+
140
+ def decode(data)
141
+ data.scan(/../).map { |x| x.hex }.pack("c*")
142
+ end
143
+
144
+ # def compress(data)
145
+ # Zlib::Deflate.deflate(data)
146
+ # end
147
+
148
+ # def uncompress(data)
149
+ # Zlib::Inflate.inflate(data)
150
+ # end
151
+
152
+ def align(data)
153
+ data.scan(/.{1,64}/).join("\n")
154
+ end
155
+
156
+ def clean(data)
157
+ data.split("\n").join("")
158
+ end
159
+
160
+ def add_header(payload)
161
+ "#{HEADER}:v#{@format}#{SEPARATOR}#{payload}"
162
+ end
163
+
164
+ def rm_header(payload)
165
+ parts = payload.split(SEPARATOR)
166
+ header = parts[0]
167
+ body = parts[1]
168
+ m = header.match(/Can\:v(\d+)/)
169
+ @format = m[1]
170
+ body
171
+ end
172
+
173
+ end
174
+ end
@@ -0,0 +1,15 @@
1
+ require "net/http"
2
+
3
+ module Can
4
+
5
+ module Util
6
+
7
+ def self.latest()
8
+ uri = URI("https://rubygems.org/api/v1/gems/can.json")
9
+ res = Net::HTTP.get(uri)
10
+ data = JSON.load(res)
11
+ return data["version"]
12
+ end
13
+
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: can
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.10.13
5
+ platform: ruby
6
+ authors:
7
+ - ptdorf
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-09-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tablelize
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: base62-rb
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Can stores encrypted goods using symmetric cryptography (AES-256-CBC)
70
+ email:
71
+ - ptdorf@gmail.com
72
+ executables:
73
+ - can
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - Gemfile
79
+ - README.md
80
+ - Rakefile
81
+ - bin/can
82
+ - can.gemspec
83
+ - lib/can.rb
84
+ - lib/can/crypto.rb
85
+ - lib/can/store.rb
86
+ - lib/can/util.rb
87
+ homepage: https://github.com/ptdorf/can
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubygems_version: 3.0.3
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Can stores encrypted goods using symmetric cryptography.
110
+ test_files: []