secret 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+
19
+
20
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in secret.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Christopher Thornton
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ Secret Files
2
+ ============
3
+ Keeps your files more secure by ensuring saved files are chmoded 0700 by the same user who is running the process.
4
+ For example, this could be used by a Rails application to store certificates only readable by the www-data process.
5
+
6
+ The secret files gem includes some fun over-engineering with file locking!
7
+
8
+ ## Usage
9
+ Add to your Gemfile:
10
+
11
+ ```ruby
12
+ gem 'secret'
13
+ ```
14
+
15
+ Now you should set up your "default" container in an initializer:
16
+
17
+ ```ruby
18
+ Secret.configure_default 'path/to/secret/dir'
19
+ ```
20
+
21
+ Finally, you should now be able to put stuff into the secret directory, or read contents!
22
+
23
+ ```ruby
24
+ secret = Secret.default
25
+
26
+ # Save the key
27
+ secret.some_key.stash "ThisIsSomeKey"
28
+
29
+ # Get the key
30
+ puts secret.some_key.contents
31
+
32
+ # Manually seek through the file
33
+ secret.some_key.stream do |f|
34
+ f.seek 1, IO::SEEK_CUR
35
+ end
36
+ ```
37
+
38
+ ## How Secure is It?
39
+ Ths is only *somewhat* secure and will provide protection against:
40
+
41
+ * Someone who gains access to your server with non-root access
42
+ * Other non-root server processes
43
+
44
+ However, this will **not** protect you against:
45
+
46
+ * People with root access
47
+ * Arbitrary code execution attacks by the owner (i.e. an `eval()` gone wrong)
48
+
49
+ ## Other Features
50
+ This gem also includes locking support, meaning that it should (hopefully) be resillient against multiple processes
51
+ writing to a file. The primary reason for this is because I really wanted to try file locking. However, don't do
52
+ anything too tricky and you should be okay.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,64 @@
1
+ module Secret
2
+ class Container
3
+
4
+ attr_reader :directory, :files
5
+
6
+ # Initializes a container. Does significant checking on the directory to ensure it is writeable and it exists.
7
+ # @param [String] directory the directory to the container
8
+ # @param [Boolean] auto_create if true, will attempt to create the directory if it does not exist.
9
+ def initialize(directory, auto_create = true)
10
+ @directory = directory
11
+
12
+ @files = {}
13
+
14
+ # Do some checking about our directory
15
+ if ::File.exist?(directory)
16
+ raise ArgumentError, "Specified directory '#{directory}' is actually a file!" unless ::File.directory?(directory)
17
+
18
+ # Now make our directory if auto_create
19
+ else
20
+ raise ArgumentError, "Specified directory '#{directory}' does not exist!" unless auto_create
21
+ Dir.mkdir(directory, Secret::CHMOD_MODE) # Only give read/write access to this user
22
+ end
23
+ raise ArgumentError, "Directory '#{directory}' is not writeable!" unless ::File.writable?(directory)
24
+ end
25
+
26
+ # Gets a file stored in the container.
27
+ # @param [Symbol] filename the name of the file.
28
+ # @return [Secret::File] a secret file
29
+ def file(filename)
30
+ fn = filename.to_sym
31
+ f = files[fn]
32
+ return f unless f.nil?
33
+ f = Secret::File.new(self, filename)
34
+ files[fn] = f
35
+ return f
36
+ end
37
+
38
+ def method_missing(meth, *args, &block)
39
+ super(meth, *args, &block) if args.any? or block_given?
40
+ return file(meth)
41
+ end
42
+
43
+
44
+ # Deletes the cache of objects
45
+ def uncache!
46
+ @files = {}
47
+ end
48
+
49
+ # This should be called once in some sort of initializer.
50
+ def initialize_once!
51
+ destroy_all_locks!
52
+ end
53
+
54
+ # Viciously destroys all locks that the file and its containers may have. Use carefully!
55
+ # @return [Integer] the number of files destroyed.
56
+ def destroy_all_locks!
57
+ files = Dir[::File.join(directory, '*.lock')]
58
+ files.each{|f| ::File.delete(f) }
59
+ return files.count
60
+ end
61
+
62
+
63
+ end
64
+ end
@@ -0,0 +1,45 @@
1
+ require 'openssl'
2
+
3
+ module Secret
4
+ module Encryption
5
+
6
+
7
+
8
+ def encrypt_basic(passphrase)
9
+ cipher = OpenSSL::Cipher.new('aes-256-cbc')
10
+ cipher.encrypt
11
+ key = passphrase
12
+ iv = cipher.random_iv
13
+
14
+ out = StringIO.new("", "wb") do |outf|
15
+ StringIO.new(contents, "rb") do |inf|
16
+ while inf.read(4096, buf)
17
+ outf << cipher.update(buf)
18
+ end
19
+ outf << cipher.final
20
+ end
21
+ end
22
+
23
+ return out.string
24
+ end
25
+
26
+ # Checks to see if the file is encrypted
27
+ def encrypted?
28
+ ::File.exist?(encrypted_meta_filename)
29
+ end
30
+
31
+
32
+ def stash(content); raise "Not Implemented"; end
33
+
34
+ def contents; raise "Not Implemented"; end
35
+
36
+
37
+
38
+ protected
39
+
40
+ def encrypted_meta_filename
41
+ raise NotImplementedError, "Must implement dis!"
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,187 @@
1
+ module Secret
2
+
3
+ # Handles file operations.
4
+ #
5
+ # Note that locking operations are not perfect!
6
+ class File
7
+ include Secret::Locking
8
+
9
+ # include Secret::Encryption
10
+
11
+ attr_reader :container, :identifier
12
+
13
+ # Lock timeout in MS. Defaults to 5000 MS
14
+ attr_accessor :lock_timeout_ms
15
+
16
+ # Whether this current object holds the lock over the file.
17
+ # @return [Boolean] TRUE if this object was the one that holds the lock (i.e. created the lock), FALSE if another
18
+ # object holds the lock, or the file simply isn't locked.
19
+ attr_reader :owns_lock
20
+
21
+ # Creates a new secret file. The specified identifier doesn't already need to exist.
22
+ # @param [Secret::Container] container the container object
23
+ # @param [Symbol] identifier an unique identifier for this container
24
+ def initialize(container, identifier)
25
+ raise ArgumentError, "Container must be a Secret::Container object" unless container.is_a?(Secret::Container)
26
+ @container = container; @identifier = identifier; @owns_lock = false
27
+ @lock_timeout_ms = Secret::Locking::DEFAULT_LOCK_WAIT_MS
28
+ end
29
+
30
+ # Checks whether this file actually exists or not
31
+ # @return [Boolean] true if the file exists (i.e. has content), false if otherwise.
32
+ def exist?
33
+ ::File.exist?(file_path)
34
+ end
35
+
36
+ # Gets a file stream of this file. If the file doesn't exist, then a blank file will be created. By default,
37
+ # this allows you to write to the file. However, please use the {#stash} command, as it accounts for mid-write
38
+ # crashes. Don't forget to close the file stream when you're done!
39
+ # @param [String] mode the mode for this file. Currently defaults to 'r+', which is read-write, with the
40
+ # file pointer at the beginning of the file.
41
+ # @return [IO] an IO stream to this file, if not using a block
42
+ # @note This completely bypasses any internal locking mechanisms if not using a block!
43
+ # @example
44
+ # file = container.some_file
45
+ #
46
+ # # Unsafe way!
47
+ # io = file.stream
48
+ # io.write "Hello World!"
49
+ # io.close
50
+ #
51
+ # # Safe way, with locking support
52
+ # file.stream do |f|
53
+ # f.write "Hello World!"
54
+ # end
55
+ def stream(mode = 'r+', &block)
56
+ touch!
57
+ return ::File.open(file_path, mode, Secret::CHMOD_MODE) unless block_given?
58
+ wait_for_unlock(true, lock_timeout_ms) do
59
+ io = ::File.open(file_path, mode, Secret::CHMOD_MODE)
60
+ block.call(io)
61
+ io.close unless io.closed?
62
+ end
63
+ end
64
+
65
+
66
+ # Gets the contents of the file in a string format. Will return an empty string if the file doesn't exist, or the
67
+ # file just so happens to be empty.
68
+ # @return [String] the contents of the file
69
+ def contents
70
+ wait_for_unlock(false, lock_timeout_ms) do
71
+ io = stream
72
+ str = io.read
73
+ io.close
74
+ end
75
+ return str
76
+ end
77
+
78
+
79
+ # Creates a new file if it doesn't exist. Doesn't actually change the last updated timestamp.
80
+ # @return [Boolean] true if an empty file was created, false if the file already existed.
81
+ def touch!
82
+ unless exist?
83
+ ::File.open(file_path, 'w') {}
84
+ secure!
85
+ return true
86
+ end
87
+ return false
88
+ end
89
+
90
+ # Secures the file by chmoding it to 0700
91
+ # @raise [IOError] if the file doesn't exist on the server.
92
+ def secure!
93
+ raise IOError, "File doesn't exist" unless exist?
94
+ ::File.chmod(Secret::CHMOD_MODE, file_path)
95
+ end
96
+
97
+
98
+ # Stashes some content into the file! This will write a temporary backup file before stashing, in order to prevent
99
+ # any partial writes if the server crashes. Once this finishes executing, you can be sure that contents have been
100
+ # written.
101
+ # @param [String] content the contents to stash. **Must be a string!**
102
+ # @raise [ArgumentError] if content is anything other than a String object!
103
+ def stash(content)
104
+ raise ArgumentError, "Content must be a String (was type of type #{content.class.name})" unless content.is_a?(String)
105
+
106
+ # Get an exclusive lock
107
+ wait_for_unlock(true, lock_timeout_ms) do
108
+ touch!
109
+
110
+ # Think of this as a beginning of a transaction.
111
+
112
+ # Open a temporary file for writing, and close it immediately
113
+ ::File.open(tmp_file_path, "w", Secret::CHMOD_MODE){|f| f.write content }
114
+
115
+ # Rename the existing file path
116
+ ::File.rename(file_path, backup_file_path)
117
+
118
+ # Now rename the temporary file to the correct file
119
+ ::File.rename(tmp_file_path, file_path)
120
+
121
+ # Delete the backup
122
+ ::File.delete(backup_file_path)
123
+
124
+ # Committed! Secure it just in case
125
+ secure!
126
+ end
127
+ end
128
+
129
+ # Attempts to restore a backup (i.e. if the computer crashed while doing a stash command)
130
+ # @return [Boolean] true if the backup was successfully restored, false otherwise
131
+ def restore_backup!
132
+ return false if locked?
133
+ return false unless ::File.exist?(backup_file_path)
134
+
135
+ # Ideally we want to get an exclusive lock when doing this!
136
+ wait_for_unlock(true, lock_timeout_ms) do
137
+ # If the file actually exists, then the backup file probably wasn't deleted, so just
138
+ # delete the backup file.
139
+ if ::File.exist?(file_path)
140
+ ::File.delete(backup_file_path)
141
+ return true
142
+
143
+ # Otherwise, the temporary file probably wasn't renamed, so restore the backup and delete the temporary file
144
+ # if it exists. It's possible the file was corrupted in the middle of writing, so it is better to resort
145
+ # to an old and complete file rather than a new and possibly corrupted file.
146
+ else
147
+ ::File.rename(backup_file_path, file_path)
148
+ ::File.delete(tmp_file_path) if ::File.exist?(tmp_file_path)
149
+ return true
150
+ end
151
+ end
152
+ end
153
+
154
+ protected
155
+
156
+ # Gets the path of the lock file
157
+ def lock_file_path
158
+ return file_path + '.lock'
159
+ end
160
+
161
+
162
+ private
163
+
164
+ # Gets the actual file path of this container. Intentionally made private (security through obscurity)
165
+ # @return [String] the absolute path to where this file is
166
+ def file_path
167
+ @the_file_path = ::File.join(container.directory, identifier.to_s) if @the_file_path.nil?
168
+ return @the_file_path
169
+ end
170
+
171
+ # The path of the temporary file. Used as a temporary container for stashing. This file will then be re-named.
172
+ # @return [String] the path to the temporary file
173
+ def tmp_file_path
174
+ return file_path + ".tmp"
175
+ end
176
+
177
+ # Gets the path of the backup file
178
+ # @return [String] the path of the backup file
179
+ def backup_file_path
180
+ return file_path + '.bak'
181
+ end
182
+
183
+
184
+
185
+
186
+ end
187
+ end
@@ -0,0 +1,142 @@
1
+ module Secret
2
+
3
+ # Locking utilities!
4
+ module Locking
5
+
6
+ # By default, wait for locks for 5 seconds
7
+ DEFAULT_LOCK_WAIT_MS = 5000
8
+
9
+ # Locks the current file. May prevent other object instances (or processes) from reading / writing to this file.
10
+ # You may use this to upgrade your lock if you wish.
11
+ # @param [Boolean] exclusive if TRUE, holds an exclusive lock meaning that other processes cannot read or write
12
+ # to the file. If FALSE, allows other files to read from the file, but not to write.
13
+ # @return [Boolean] true if the lock was created successfully, false if it was not (likely due to another process holding a lock,
14
+ # or we already hold the lock).
15
+ # @note Remember to unlock the file once you are done using it!
16
+ def lock!(exclusive = false)
17
+ return false if(locked? and !owns_lock?)
18
+
19
+ # Write
20
+ # The file contents will be of '<exclusive bit>:<process id>'
21
+ lock_string = (exclusive ? '0' : '1') + ":#{Process.pid}"
22
+ ::File.open(lock_file_path, 'w',Secret::CHMOD_MODE) { |f| f.write lock_string }
23
+ @owns_lock = true
24
+
25
+ return true
26
+ end
27
+
28
+ # Checks to see if this file is locked. Has no indication of whether we own the lock or not.
29
+ # @param [Boolean] exclusive if TRUE, only checks if it's an exclusive lock
30
+ # @return [Boolean] true if the file is locked, false otherwise.
31
+ def locked?(exclusive = false)
32
+ return false unless ::File.exist?(lock_file_path)
33
+ return true unless exclusive # Don't bother checking for non-exlusive
34
+ ex = false
35
+ begin
36
+ # Quickly peek at the file to see if it is exclusive
37
+ ::File.open(lock_file_path, 'r', Secret::CHMOD_MODE) do |f|
38
+ ex = f.seek(1, IO::SEEK_CUR) == '1'
39
+ f.close
40
+ end
41
+ return ex
42
+ rescue Exception => e
43
+ return false
44
+ end
45
+ end
46
+
47
+
48
+ # An alias for {#owns_lock}
49
+ def owns_lock?
50
+ return owns_lock
51
+ end
52
+
53
+ # Unlocks this file.
54
+ # @return [Boolean] true if unlock was successful, false if unlock was unsuccessful (i.e. the file wasn't locked,
55
+ # or we don't own the lock)
56
+ def unlock!
57
+ return false if !locked?
58
+ return false if !owns_lock?
59
+
60
+ ::File.delete(lock_file_path)
61
+ @owns_lock = false
62
+ return true
63
+ end
64
+
65
+ # Forcibly unlocks this file, regardless of whether the lock is owned
66
+ # @return [Boolean] true if unlock was successful, false if the file isn't locked.
67
+ def force_unlock!
68
+ return false if !locked?
69
+ ::File.delete(lock_file_path)
70
+ @owns_lock = false
71
+ return true
72
+ end
73
+
74
+
75
+ # Waits for the file to become unlocked for 'max_time_ms'. If file is locked for that long, then raises an
76
+ # {Secret::::FileLockedError}.
77
+ # @param [Boolean] exclusive if TRUE, waits for an exclusive lock or shared lock, and then locks with an
78
+ # exclusive lock if a block is given. If FALSE, then only waits for an exclusive lock.
79
+ # @param [Integer] max_time_ms the maximum number of MS to wait until raising an error. If negative, waits forever.
80
+ # @param [Integer] sleep_ms the number of MS to sleep before polling the lock again
81
+ # @param [Proc] block pass a block to then execute block while locked (i.e. {#lock_and_do})
82
+ def wait_for_unlock(exclusive = false, max_time_ms = DEFAULT_LOCK_WAIT_MS, sleep_ms = 100, &block)
83
+ ms_start = (Time.now.to_f * 1000.0).to_i
84
+ wait_exclusive = exclusive or block_given?
85
+ sleep_ms = sleep_ms / 1000.0
86
+ while locked?(!wait_exclusive)
87
+ break if owns_lock?
88
+ sleep(sleep_ms)
89
+ continue if max_time_ms < 0
90
+ ms_new = (Time.now.to_f * 1000.0).to_i
91
+ if (ms_new - ms_start) > max_time_ms
92
+ raise Secret::FileLockedError, "Timeout of #{max_time_ms} MS exceeded while waiting for lock!"
93
+ end
94
+ end
95
+
96
+ # If we are here, waiting has finished
97
+ lock_and_do(exclusive, &block) if block_given?
98
+ end
99
+
100
+ # Locks and executes a block, then unlocks upon block execution. This will always ensure that the lock
101
+ # is released upon block execution, regardless of error or no.
102
+ # @param [Boolean] exclusive if TRUE, uses an exclusive lock. If false, uses a shared lock
103
+ # @raise [ArgumentError] if a block is not provided
104
+ # @raise [FileLockedError] if the file is locked
105
+ def lock_and_do(exclusive = false, &block)
106
+ raise ArgumentError, 'Block not given' unless block_given?
107
+ raise Secret::FileLockedError, 'Cannot lock a file if it is already locked!' if locked?
108
+ lock!(exclusive)
109
+ begin
110
+ block.call
111
+ rescue Exception => e
112
+ raise e
113
+ ensure
114
+ unlock!
115
+ end
116
+ end
117
+
118
+
119
+ # Gets information about the current lock:
120
+ #
121
+ # {:pid => 123123, :exclusive => true }
122
+ #
123
+ # @return [Hash,nil] information about the current lock, or nil if no lock is present
124
+ def lock_info
125
+ return nil unless locked?
126
+ begin
127
+ str = nil
128
+ ::File.open(lock_file_path, 'r', Secret::CHMOD_MODE) {|f| str = f.read }
129
+ ex,pid = str.split ':'
130
+ return {:exclusive => (ex == "1"), :pid => pid.to_i}
131
+ rescue Exception => e
132
+ return nil
133
+ end
134
+ end
135
+
136
+ protected
137
+
138
+ def lock_file_path
139
+ raise NotImplementedError, "'lock_file_path' needs to be implemented"
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,3 @@
1
+ module Secret
2
+ VERSION = "0.0.1"
3
+ end
data/lib/secret.rb ADDED
@@ -0,0 +1,36 @@
1
+ require "secret/version"
2
+ require "secret/locking"
3
+ require "secret/file"
4
+ require "secret/container"
5
+
6
+ module Secret
7
+
8
+ # The chmod mode to use for files.
9
+ CHMOD_MODE = 0700
10
+
11
+ # Gets the UID of the current process
12
+ def self.uid
13
+ return Process.uid
14
+ end
15
+
16
+ # Gets the default container
17
+ # @return [Secret::Container] the default container
18
+ def self.default
19
+ raise ArgumentError, "Must call 'Secret.configure_default' before you can access the default container" unless @default
20
+ return @default
21
+ end
22
+
23
+ # Configures the default container once
24
+ def self.configure_default(directory, auto_create = true)
25
+ unless @default
26
+ @default = Secret::Container.new(directory, auto_create)
27
+ @default.initialize_once!
28
+ return true
29
+ else
30
+ return false
31
+ end
32
+ end
33
+
34
+ class FileLockedError < Exception; end
35
+
36
+ end
data/secret.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'secret/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "secret"
8
+ spec.version = Secret::VERSION
9
+ spec.authors = ["Christopher Thornton"]
10
+ spec.email = ["rmdirbin@gmail.com"]
11
+ spec.description = %q{Keeps your files more secure by ensuring saved files are chmoded 0700 by the same user who is running the process.}
12
+ spec.summary = %q{Keeps files more secure on server environments}
13
+ spec.homepage = "https://github.com/cgthornt/secret"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ end
data/test/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'secret', path: '../'
@@ -0,0 +1 @@
1
+ Hello World!
data/test/tester.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'secret'
5
+
6
+ Secret.configure_default 'secrets'
7
+
8
+ container = Secret.default
9
+ container.cert_key.stash "Hello World!"
10
+ puts container.cert_key.encrypt_basic "password"
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: secret
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Christopher Thornton
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Keeps your files more secure by ensuring saved files are chmoded 0700
47
+ by the same user who is running the process.
48
+ email:
49
+ - rmdirbin@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - Gemfile
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - lib/secret.rb
60
+ - lib/secret/container.rb
61
+ - lib/secret/encryption.rb
62
+ - lib/secret/file.rb
63
+ - lib/secret/locking.rb
64
+ - lib/secret/version.rb
65
+ - secret.gemspec
66
+ - test/Gemfile
67
+ - test/secrets/cert_key
68
+ - test/tester.rb
69
+ homepage: https://github.com/cgthornt/secret
70
+ licenses:
71
+ - MIT
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ segments:
83
+ - 0
84
+ hash: 296992438860896651
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ segments:
92
+ - 0
93
+ hash: 296992438860896651
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 1.8.25
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Keeps files more secure on server environments
100
+ test_files:
101
+ - test/Gemfile
102
+ - test/secrets/cert_key
103
+ - test/tester.rb