secret_keys 1.0.2 → 1.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 637787c84aaeb9fc38f77ac00f67f38ee4094b0a42ac5fe714a9c10285c65ed7
4
- data.tar.gz: c57ed55617d13bee4d988d9c0283fff89534ccddd7980f8fa1afe81b5b7b61a4
3
+ metadata.gz: fd54d07d04fbd4695a6896fec897ee82171f27e10778aa59247dc2c216511149
4
+ data.tar.gz: 6faeb6462cce486f5f4e27619fe3e88e7f1587a1a51949821166ee3359419a0f
5
5
  SHA512:
6
- metadata.gz: bfb75203d78bfe74c6c394175cd08a775f3f9415d206f60df941c77ab38c74413cf4e2854a2af3c0208e1e580d0dc3e35f70fd2b59c1d7cd4a7d215a53441efd
7
- data.tar.gz: b9b1e6ee2be772b78fe9da0ed4768455ce6249e12ff37b6328b9bc197c4ded8638764810a1a23ffcc1820bcfb696f0ed3723b61cdb6af116723e0030068f988a
6
+ metadata.gz: e509e3f5b8fb49e5c7859f18cd73a620ca89d95c0e82631cdb40bf77366d5dba131107d09d2057236d8e44982a6a232497c799e5723727968b37f6c88f253d13
7
+ data.tar.gz: 772f82950d3efe42d597bc6918f10f8ed2664d6b5738a9af0bb8767fb59b8212a0420a88a5169ad35972600936aace607323921b9bdf80dedf0b66669a7c6e22
data/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## 1.0.3
8
+
9
+ ### Fixed
10
+
11
+ - Files are now written atomically (write to a temporary file and rename) so a crash or concurrent reader can never see a partially written secrets file. The mode of an existing file is preserved.
12
+ - New secrets files are now created readable and writable only by the owner (mode 0600) instead of relying on the process umask. The mode of an existing file is still preserved.
13
+ - `secret_keys init` now uses an exclusive file create so it can no longer overwrite a file created between the existence check and the write.
14
+ - `SecretKeys#save` no longer raises `ArgumentError` when passed a `Pathname`.
15
+ - An unsupported `.version` value that is a string now raises `SecretKeys::VersionError` instead of a comparison `ArgumentError`; numeric strings like `"1"` are accepted.
16
+ - Input that does not parse to a Hash now raises a clear `ArgumentError` instead of failing with confusing errors; input that parses to nothing (e.g. only comments) loads as an empty document.
17
+ - A non-Hash value in the `.encrypted` key now raises a clear `ArgumentError`.
18
+ - `SecretKeys#encryption_key=` now raises `SecretKeys::EncryptionKeyError` when given a nil or empty key instead of silently keeping the old key.
19
+ - The `--secret-key-file` CLI option now reports a missing file as a clean error instead of an unhandled `Errno::ENOENT`.
20
+
21
+ ## 1.0.2
22
+
23
+ ### Changed
24
+
25
+ - Follow [RFC 4648](https://www.ietf.org/rfc/rfc4648.txt) base 64 encoding, removing line-feeds from the encoded data.
26
+
27
+ ## 1.0.1 (June 01, 2020)
28
+
29
+ ### Fixed
30
+
31
+ - Fix missing documentation links
32
+
33
+ ## v1.0.0 (May 31, 2020)
34
+
35
+ ### Added
36
+
37
+ - Initial release
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
- # SecretKeys
1
+ # Secret Keys
2
2
 
3
- [![specs](https://github.com/bdurand/secret_keys/workflows/Run%20tests/badge.svg)](https://github.com/bdurand/secret_keys/actions?query=branch%3Amaster)
4
- [![code style](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
3
+ [![Continuous Integration](https://github.com/bdurand/secret_keys/actions/workflows/continuous_integration.yml/badge.svg)](https://github.com/bdurand/secret_keys/actions/workflows/continuous_integration.yml)
4
+ [![Regression Test](https://github.com/bdurand/secret_keys/actions/workflows/regression_test.yml/badge.svg)](https://github.com/bdurand/secret_keys/actions/workflows/regression_test.yml)
5
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
5
6
  [![gem version](https://badge.fury.io/rb/secret_keys.svg)](https://badge.fury.io/rb/secret_keys)
7
+ [![Gem Version](https://badge.fury.io/rb/secret_keys.svg)](https://badge.fury.io/rb/secret_keys)
6
8
 
7
9
  This ruby gem handles encrypting values in a JSON or YAML file. It is yet another solution for storing secrets in a ruby project.
8
10
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.2
1
+ 1.0.3
@@ -78,6 +78,7 @@ module SecretKeys::CLI
78
78
  HELP
79
79
  opts.on("--secret-key-file=PATH", String, *secret_file_docs) do |value|
80
80
  raise ArgumentError, "You have already passed in the secret key" unless @secret_key.nil?
81
+ raise ArgumentError, "Secret key file '#{value}' does not exist" unless File.exist?(value)
81
82
  @secret_key = File.read(value).chomp
82
83
  end
83
84
 
@@ -165,13 +166,19 @@ module SecretKeys::CLI
165
166
  def run!
166
167
  @secrets = SecretKeys.new({}, secret_key)
167
168
  if input.is_a?(String)
168
- if File.exist?(input)
169
+ contents = encrypted_file_contents
170
+ begin
171
+ # Open with exclusive create so a file created by another process
172
+ # between now and the write can never be clobbered. The file is
173
+ # created readable and writable only by the owner.
174
+ File.open(input, File::WRONLY | File::CREAT | File::EXCL, 0o600) do |file|
175
+ file.write(contents)
176
+ end
177
+ rescue Errno::EEXIST
169
178
  warn "Error: Cannot init preexisting file '#{input}'"
170
179
  warn "You may want to try calling `secret_keys encrypt/edit` instead"
171
180
  exit 1
172
181
  end
173
-
174
- File.write(input, encrypted_file_contents)
175
182
  else
176
183
  $stdout.write(encrypted_file_contents)
177
184
  end
@@ -220,7 +227,7 @@ module SecretKeys::CLI
220
227
  raise ArgumentError, "Cannot perform in place editing on streams" unless @input.is_a?(String)
221
228
  # make sure we read the file **before** writing to it.
222
229
  contents = encrypted_file_contents
223
- File.write(@input, contents)
230
+ SecretKeys.atomic_write(@input, contents)
224
231
  else
225
232
  $stdout.write(encrypted_file_contents)
226
233
  $stdout.flush
@@ -21,8 +21,8 @@ class SecretKeys::Encryptor
21
21
  class << self
22
22
  # Create an Encryptor from a password and salt. This is a shortcut for generating an Encryptor
23
23
  # with a 32 byte encryption key. The key will be derived from the password and salt.
24
- # @param [String] password secret used to encrypt the data
25
- # @param [String] salt random hex-encoded byte array for key derivation
24
+ # @param password [String] secret used to encrypt the data
25
+ # @param salt [String] random hex-encoded byte array for key derivation
26
26
  # @return [SecretKeys::Encryptor] a new encryptor with key derived from password and salt
27
27
  def from_password(password, salt)
28
28
  raise ArgumentError, "Password must be present" if password.nil? || password.empty?
@@ -55,7 +55,7 @@ class SecretKeys::Encryptor
55
55
  end
56
56
  end
57
57
 
58
- # @param [String] raw_key the key directly passed into the encrypt/decrypt functions. This must be exactly {KEY_LENGTH} bytes long.
58
+ # @param raw_key [String] the key directly passed into the encrypt/decrypt functions. This must be exactly {KEY_LENGTH} bytes long.
59
59
  def initialize(raw_key)
60
60
  raise ArgumentError, "key must be #{KEY_LENGTH} bytes long" unless raw_key.bytesize == KEY_LENGTH
61
61
  @derived_key = raw_key
@@ -65,7 +65,7 @@ class SecretKeys::Encryptor
65
65
  # calling this function multiple times will result in different values. Only strings
66
66
  # can be encrypted. Any other object type will be return the value passed in.
67
67
  #
68
- # @param [String] str string to encrypt (assumes UTF-8)
68
+ # @param str [String] string to encrypt (assumes UTF-8)
69
69
  # @return [String] Base64 encoded encrypted string with all aes parameters
70
70
  def encrypt(str)
71
71
  return str unless str.is_a?(String)
@@ -96,13 +96,13 @@ class SecretKeys::Encryptor
96
96
  # Decrypt a string with the encryption key. If the value is not a string or it was
97
97
  # not encrypted with the encryption key, the value itself will be returned.
98
98
  #
99
- # @param [String] encrypted_str Base64 encoded encrypted string with aes params (from {#encrypt})
99
+ # @param encrypted_str [String] Base64 encoded encrypted string with aes params (from {#encrypt})
100
100
  # @return [String] decrypted string value
101
101
  # @raise [OpenSSL::Cipher::CipherError] there is something wrong with the encoded data (usually incorrect key)
102
102
  def decrypt(encrypted_str)
103
103
  return encrypted_str unless self.class.encrypted?(encrypted_str)
104
104
 
105
- decrypt_str = encrypted_str[ENCRYPTED_PREFIX.length..-1]
105
+ decrypt_str = encrypted_str[ENCRYPTED_PREFIX.length..]
106
106
  params = decode_aes(decrypt_str)
107
107
 
108
108
  cipher = OpenSSL::Cipher.new(CIPHER).decrypt
data/lib/secret_keys.rb CHANGED
@@ -15,13 +15,42 @@ class SecretKeys < DelegateClass(Hash)
15
15
  # Error if the crypto version specified in the file is unsupported
16
16
  class VersionError < StandardError; end
17
17
 
18
+ # Write a file atomically by writing to a temporary file in the same directory and
19
+ # renaming it over the target path so that readers never see a partially written file.
20
+ # The mode of an existing file at the path will be preserved. New files are created
21
+ # readable and writable only by the owner.
22
+ #
23
+ # @api private
24
+ # @param path [String, Pathname] path of the file to write
25
+ # @param content [String] contents of the file
26
+ # @return [void]
27
+ def self.atomic_write(path, content)
28
+ path = File.expand_path(path.to_s)
29
+ mode = begin
30
+ File.stat(path).mode & 0o7777
31
+ rescue Errno::ENOENT
32
+ 0o600
33
+ end
34
+ tmp_path = File.join(File.dirname(path), ".#{File.basename(path)}.tmp.#{Process.pid}.#{rand(1_000_000)}")
35
+ begin
36
+ File.open(tmp_path, "w") do |file| # rubocop:disable Style/FileWrite
37
+ file.write(content)
38
+ end
39
+ File.chmod(mode, tmp_path)
40
+ File.rename(tmp_path, path)
41
+ ensure
42
+ File.unlink(tmp_path) if File.exist?(tmp_path)
43
+ end
44
+ nil
45
+ end
46
+
18
47
  # Parse a JSON or YAML stream or file with encrypted values. Any values in the ".encrypted" key
19
48
  # in the document will be decrypted with the provided encryption key. If values
20
49
  # were put into the ".encrypted" key manually and are not yet encrypted, they will be used
21
50
  # as is without any decryption.
22
51
  #
23
- # @param [String, #read, Hash] path_or_stream path to a JSON/YAML file to load, an IO object, or a Hash (mostly for testing purposes)
24
- # @param [String] encryption_key secret to use for encryption/decryption
52
+ # @param path_or_stream [String, #read, Hash] path to a JSON/YAML file to load, an IO object, or a Hash (mostly for testing purposes)
53
+ # @param encryption_key [String] secret to use for encryption/decryption
25
54
  #
26
55
  # @note If no encryption key is passed, this will defautl to env var SECRET_KEYS_ENCRYPTION_KEY
27
56
  # or (if that is empty) the value read from the file path in SECRET_KEYS_ENCRYPTION_KEY_FILE.
@@ -48,7 +77,7 @@ class SecretKeys < DelegateClass(Hash)
48
77
 
49
78
  # Mark the key as being encrypted when the JSON is saved.
50
79
  #
51
- # @param [String] key key to mark as needing encryption
80
+ # @param key [String] key to mark as needing encryption
52
81
  # @return [void]
53
82
  def encrypt!(key)
54
83
  @secret_keys << key
@@ -57,7 +86,7 @@ class SecretKeys < DelegateClass(Hash)
57
86
 
58
87
  # Mark the key as no longer being decrypted when the JSON is saved.
59
88
  #
60
- # @param [String] key key to mark as not needing encryption
89
+ # @param key [String] key to mark as not needing encryption
61
90
  # @return [void]
62
91
  def decrypt!(key)
63
92
  @secret_keys.delete(key)
@@ -66,7 +95,7 @@ class SecretKeys < DelegateClass(Hash)
66
95
 
67
96
  # Return true if the key is encrypted.
68
97
  #
69
- # @param [String] key key to check
98
+ # @param key [String] key to check
70
99
  # @return [Boolean]
71
100
  def encrypted?(key)
72
101
  @secret_keys.include?(key)
@@ -77,8 +106,8 @@ class SecretKeys < DelegateClass(Hash)
77
106
  # different initialization vector). This can be helpful if you have your secrets in source
78
107
  # control so that only changed keys will actually be changed in the file when it is updated.
79
108
  #
80
- # @param [String, Pathname] path path of the file to save. If the file exists, only changed values will be updated.
81
- # @param [String, Symbol] format: output format (YAML or JSON) to use. This will default based on the extension on the file path or the format originally used
109
+ # @param path [String, Pathname] path of the file to save. If the file exists, only changed values will be updated.
110
+ # @param format [String, Symbol] output format (YAML or JSON) to use. This will default based on the extension on the file path or the format originally used
82
111
  # @return [void]
83
112
  def save(path, format: nil)
84
113
  # create a copy of the encrypted hash for working on
@@ -96,7 +125,7 @@ class SecretKeys < DelegateClass(Hash)
96
125
 
97
126
  output = ((format == "yaml") ? YAML.dump(encrypted) : JSON.pretty_generate(encrypted))
98
127
  output << $/ unless output.end_with?($/) # ensure file ends with system dependent new line
99
- File.write(path, output)
128
+ self.class.atomic_write(path, output)
100
129
  nil
101
130
  end
102
131
 
@@ -131,9 +160,10 @@ class SecretKeys < DelegateClass(Hash)
131
160
 
132
161
  # Change the encryption key in the document. When saving later, this key will be used.
133
162
  #
134
- # @param [String] new_encryption_key encryption key to use for future {#save} calls
163
+ # @param new_encryption_key [String] encryption key to use for future {#save} calls
135
164
  # @return [void]
136
165
  def encryption_key=(new_encryption_key)
166
+ raise EncryptionKeyError.new("Encryption key not specified") if new_encryption_key.nil? || new_encryption_key.empty?
137
167
  @original_encrypted = {}
138
168
  update_secret(key: new_encryption_key)
139
169
  end
@@ -166,10 +196,10 @@ class SecretKeys < DelegateClass(Hash)
166
196
 
167
197
  hash = {}
168
198
  if path_or_stream.is_a?(Hash)
169
- # HACK: Perform a marshal dump/load operation to get a deep copy of the hash.
170
- # Otherwise, we can end up using destructive `#delete` operations and mess
171
- # up deeply nested values for external code (esp. when loading key: .encrypted)
172
- hash = Marshal.load(Marshal.dump(path_or_stream))
199
+ # Deep copy the hash. Otherwise, we can end up using destructive `#delete`
200
+ # operations and mess up deeply nested values for external code
201
+ # (esp. when loading key: .encrypted)
202
+ hash = deep_dup(path_or_stream)
173
203
  elsif path_or_stream
174
204
  data = path_or_stream.read
175
205
  hash = parse_data(data)
@@ -177,20 +207,31 @@ class SecretKeys < DelegateClass(Hash)
177
207
 
178
208
  encrypted_values = hash.delete(ENCRYPTED)
179
209
  if encrypted_values
180
- @original_encrypted = Marshal.load(Marshal.dump(encrypted_values))
210
+ raise ArgumentError, "The #{ENCRYPTED} key must contain a Hash of encrypted values" unless encrypted_values.is_a?(Hash)
211
+
212
+ @original_encrypted = deep_dup(encrypted_values)
181
213
 
182
214
  version = encrypted_values.delete(VERSION_KEY) || CRYPTO_VERSION
183
- raise VersionError, "Unsupported file version #{version}. Max supported is #{CRYPTO_VERSION}." if version > CRYPTO_VERSION
215
+ if version.is_a?(String)
216
+ version = begin
217
+ Integer(version, 10)
218
+ rescue ArgumentError
219
+ version
220
+ end
221
+ end
222
+ unless version.is_a?(Numeric) && version <= CRYPTO_VERSION
223
+ raise VersionError, "Unsupported file version #{version}. Max supported is #{CRYPTO_VERSION}."
224
+ end
184
225
 
185
226
  file_key = encrypted_values.delete(ENCRYPTION_KEY)
186
- salt = (encrypted_values.delete(SALT) || Encryptor.random_salt)
227
+ salt = encrypted_values.delete(SALT) || Encryptor.random_salt
187
228
  update_secret(salt: salt)
188
229
 
189
230
  # Check that we are using the right key
190
231
  if file_key && !encryption_key_matches?(file_key)
191
232
  raise EncryptionKeyError.new("Incorrect encryption key")
192
233
  end
193
- @secret_keys = encrypted_values.keys
234
+ @secret_keys = Set.new(encrypted_values.keys)
194
235
  hash.merge!(decrypt_values(encrypted_values))
195
236
  elsif @salt.nil?
196
237
  # if no salt exists, create one.
@@ -201,15 +242,38 @@ class SecretKeys < DelegateClass(Hash)
201
242
  end
202
243
 
203
244
  # Attempt to parse the file first using JSON and fallback to YAML
204
- # @param [String] data file data to parse
245
+ # @param data [String] file data to parse
205
246
  # @return [Hash] data parsed to a hash
206
247
  def parse_data(data)
207
248
  @format = :json
208
249
  return {} if data.nil? || data.empty?
209
- JSON.parse(data)
210
- rescue JSON::JSONError
211
- @format = :yaml
212
- YAML.safe_load(data)
250
+ hash = begin
251
+ JSON.parse(data)
252
+ rescue JSON::JSONError
253
+ @format = :yaml
254
+ YAML.safe_load(data)
255
+ end
256
+ # YAML.safe_load returns false instead of nil for empty documents on Ruby < 3.4.
257
+ return {} if hash.nil? || hash == false
258
+ raise ArgumentError, "Data must parse to a Hash of key value pairs" unless hash.is_a?(Hash)
259
+ hash
260
+ end
261
+
262
+ # Deep copy a parsed data structure of hashes, arrays, and scalar values.
263
+ def deep_dup(value)
264
+ if value.is_a?(Hash)
265
+ copy = {}
266
+ value.each do |key, val|
267
+ copy[deep_dup(key)] = deep_dup(val)
268
+ end
269
+ copy
270
+ elsif value.is_a?(Array)
271
+ value.collect { |val| deep_dup(val) }
272
+ elsif value.is_a?(String)
273
+ value.dup
274
+ else
275
+ value
276
+ end
213
277
  end
214
278
 
215
279
  # Recursively encrypt all values.
@@ -263,23 +327,6 @@ class SecretKeys < DelegateClass(Hash)
263
327
  @encryptor.decrypt(encrypted_value)
264
328
  end
265
329
 
266
- # Helper method to test if two values are both encrypted, but result in the same decrypted value.
267
- def equal_encrypted_values?(value_1, value_2)
268
- return true if value_1 == value_2
269
-
270
- decrypt_val_1 = decrypt_value(value_1)
271
- decrypt_val_2 = decrypt_value(value_2)
272
- if decrypt_val_1 == decrypt_val_2
273
- if value_1 == decrypt_val_1 || value_2 == decrypt_val_2
274
- false
275
- else
276
- true
277
- end
278
- else
279
- false
280
- end
281
- end
282
-
283
330
  # This is an encrypted known value that can be used determine if the secret has changed.
284
331
  def encrypted_known_value
285
332
  encrypt_value(KNOWN_VALUE)
@@ -294,13 +341,12 @@ class SecretKeys < DelegateClass(Hash)
294
341
  end
295
342
 
296
343
  def yaml_file?(path)
297
- ext = path.split(".").last.to_s.downcase
298
- ext == "yaml" || ext == "yml"
344
+ ext = File.extname(path.to_s).downcase
345
+ ext == ".yaml" || ext == ".yml"
299
346
  end
300
347
 
301
348
  def json_file?(path)
302
- ext = path.split(".").last.to_s.downcase
303
- ext == "json"
349
+ File.extname(path.to_s).downcase == ".json"
304
350
  end
305
351
 
306
352
  # Update the secret key by updating the salt
data/secret_keys.gemspec CHANGED
@@ -7,9 +7,12 @@ Gem::Specification.new do |spec|
7
7
  spec.summary = "Simple mechanism for loading JSON file with encrypted values."
8
8
  spec.homepage = "https://github.com/bdurand/secret_keys"
9
9
  spec.license = "MIT"
10
- spec.metadata["homepage_uri"] = spec.homepage
11
- spec.metadata["source_code_uri"] = "https://github.com/bdurand/secret_keys/tree/v#{spec.version}"
12
- spec.metadata["documentation_uri"] = "https://www.rubydoc.info/gems/secret_keys"
10
+
11
+ spec.metadata = {
12
+ "homepage_uri" => spec.homepage,
13
+ "source_code_uri" => spec.homepage,
14
+ "changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md"
15
+ }
13
16
 
14
17
  # Specify which files should be added to the gem when it is released.
15
18
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -17,6 +20,7 @@ Gem::Specification.new do |spec|
17
20
  .
18
21
  Appraisals
19
22
  Gemfile
23
+ Gemfile.lock
20
24
  Rakefile
21
25
  gemfiles/
22
26
  spec/
@@ -29,7 +33,7 @@ Gem::Specification.new do |spec|
29
33
  spec.bindir = "bin"
30
34
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
31
35
 
32
- spec.required_ruby_version = ">= 2.4"
36
+ spec.required_ruby_version = ">= 2.6"
33
37
 
34
- spec.add_development_dependency "bundler", "~>2.0"
38
+ spec.add_development_dependency "bundler"
35
39
  end
metadata CHANGED
@@ -1,31 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secret_keys
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
8
  - Winston Durand
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2023-04-04 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: bundler
16
15
  requirement: !ruby/object:Gem::Requirement
17
16
  requirements:
18
- - - "~>"
17
+ - - ">="
19
18
  - !ruby/object:Gem::Version
20
- version: '2.0'
19
+ version: '0'
21
20
  type: :development
22
21
  prerelease: false
23
22
  version_requirements: !ruby/object:Gem::Requirement
24
23
  requirements:
25
- - - "~>"
24
+ - - ">="
26
25
  - !ruby/object:Gem::Version
27
- version: '2.0'
28
- description:
26
+ version: '0'
29
27
  email:
30
28
  - bbdurand@gmail.com
31
29
  - me@winstondurand.com
@@ -34,7 +32,7 @@ executables:
34
32
  extensions: []
35
33
  extra_rdoc_files: []
36
34
  files:
37
- - CHANGE_LOG.md
35
+ - CHANGELOG.md
38
36
  - MIT_LICENSE.txt
39
37
  - README.md
40
38
  - VERSION
@@ -49,9 +47,8 @@ licenses:
49
47
  - MIT
50
48
  metadata:
51
49
  homepage_uri: https://github.com/bdurand/secret_keys
52
- source_code_uri: https://github.com/bdurand/secret_keys/tree/v1.0.2
53
- documentation_uri: https://www.rubydoc.info/gems/secret_keys
54
- post_install_message:
50
+ source_code_uri: https://github.com/bdurand/secret_keys
51
+ changelog_uri: https://github.com/bdurand/secret_keys/blob/main/CHANGELOG.md
55
52
  rdoc_options: []
56
53
  require_paths:
57
54
  - lib
@@ -59,15 +56,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
59
56
  requirements:
60
57
  - - ">="
61
58
  - !ruby/object:Gem::Version
62
- version: '2.4'
59
+ version: '2.6'
63
60
  required_rubygems_version: !ruby/object:Gem::Requirement
64
61
  requirements:
65
62
  - - ">="
66
63
  - !ruby/object:Gem::Version
67
64
  version: '0'
68
65
  requirements: []
69
- rubygems_version: 3.2.22
70
- signing_key:
66
+ rubygems_version: 4.0.3
71
67
  specification_version: 4
72
68
  summary: Simple mechanism for loading JSON file with encrypted values.
73
69
  test_files: []
data/CHANGE_LOG.md DELETED
@@ -1,22 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [1.0.2] - 2023-04-04
9
-
10
- - Follow [RFC 4648](https://www.ietf.org/rfc/rfc4648.txt) base 64 encoding, removing line-feeds from the encoded data.
11
-
12
- ## [1.0.1] - 2020-06-01
13
-
14
- ### Fixed
15
-
16
- - Fix missing documentation links
17
-
18
- ## [1.0.0] 2020-05-31
19
-
20
- ### Added
21
-
22
- - Initial release