can 0.10.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []