active_cipher_storage 1.0.0
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 +7 -0
- data/CHANGELOG.md +24 -0
- data/CONTRIBUTING.md +46 -0
- data/LICENSE +21 -0
- data/README.md +644 -0
- data/SECURITY.md +35 -0
- data/active_cipher_storage.gemspec +55 -0
- data/lib/active_cipher_storage/adapters/active_storage_service.rb +129 -0
- data/lib/active_cipher_storage/adapters/s3_adapter.rb +252 -0
- data/lib/active_cipher_storage/blob_metadata.rb +84 -0
- data/lib/active_cipher_storage/cipher.rb +97 -0
- data/lib/active_cipher_storage/configuration.rb +57 -0
- data/lib/active_cipher_storage/engine.rb +29 -0
- data/lib/active_cipher_storage/errors.rb +31 -0
- data/lib/active_cipher_storage/format.rb +126 -0
- data/lib/active_cipher_storage/key_rotation.rb +121 -0
- data/lib/active_cipher_storage/key_utils.rb +12 -0
- data/lib/active_cipher_storage/multipart_upload.rb +190 -0
- data/lib/active_cipher_storage/providers/aws_kms_provider.rb +90 -0
- data/lib/active_cipher_storage/providers/base.rb +38 -0
- data/lib/active_cipher_storage/providers/env_provider.rb +122 -0
- data/lib/active_cipher_storage/stream_cipher.rb +102 -0
- data/lib/active_cipher_storage/version.rb +3 -0
- data/lib/active_cipher_storage.rb +43 -0
- data/lib/active_storage/service/active_cipher_storage_service.rb +10 -0
- metadata +224 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
require "openssl"
|
|
2
|
+
require "securerandom"
|
|
3
|
+
require "base64"
|
|
4
|
+
|
|
5
|
+
module ActiveCipherStorage
|
|
6
|
+
module Providers
|
|
7
|
+
class EnvProvider < Base
|
|
8
|
+
include KeyUtils
|
|
9
|
+
|
|
10
|
+
PROVIDER_ID = "env"
|
|
11
|
+
WRAP_ALGO = "aes-256-gcm"
|
|
12
|
+
MASTER_KEY_SIZE = 32
|
|
13
|
+
WRAP_IV_SIZE = 12
|
|
14
|
+
WRAP_TAG_SIZE = 16
|
|
15
|
+
|
|
16
|
+
def initialize(env_var: "ACTIVE_CIPHER_MASTER_KEY", old_env_var: nil)
|
|
17
|
+
@env_var = env_var
|
|
18
|
+
@old_env_var = old_env_var
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def provider_id = PROVIDER_ID
|
|
22
|
+
def key_id = @env_var
|
|
23
|
+
|
|
24
|
+
def generate_data_key
|
|
25
|
+
master = read_master_key(@env_var)
|
|
26
|
+
dek = SecureRandom.random_bytes(Cipher::KEY_SIZE)
|
|
27
|
+
{ plaintext_key: dek, encrypted_key: wrap_key(dek, master) }
|
|
28
|
+
ensure
|
|
29
|
+
zero_bytes!(master)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def decrypt_data_key(encrypted_key)
|
|
33
|
+
master = read_master_key(@env_var)
|
|
34
|
+
unwrap_key(encrypted_key, master)
|
|
35
|
+
ensure
|
|
36
|
+
zero_bytes!(master)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def wrap_data_key(plaintext_dek)
|
|
40
|
+
master = read_master_key(@env_var)
|
|
41
|
+
wrap_key(plaintext_dek, master)
|
|
42
|
+
ensure
|
|
43
|
+
zero_bytes!(master)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def rotate_data_key(encrypted_key, old_provider: nil)
|
|
47
|
+
source = old_provider || begin
|
|
48
|
+
raise Errors::UnsupportedOperation,
|
|
49
|
+
"Supply :old_provider to rotate via EnvProvider" unless @old_env_var
|
|
50
|
+
EnvProvider.new(env_var: @old_env_var)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
plaintext_dek = source.decrypt_data_key(encrypted_key)
|
|
54
|
+
new_master = read_master_key(@env_var)
|
|
55
|
+
wrap_key(plaintext_dek, new_master)
|
|
56
|
+
ensure
|
|
57
|
+
zero_bytes!(plaintext_dek)
|
|
58
|
+
zero_bytes!(new_master)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# Wrapped DEK: [12 IV][32 ciphertext][16 auth-tag] = 60 bytes
|
|
64
|
+
def wrap_key(dek, master)
|
|
65
|
+
iv = SecureRandom.random_bytes(WRAP_IV_SIZE)
|
|
66
|
+
c = new_cipher(:encrypt, master, iv)
|
|
67
|
+
ct = c.update(dek) + c.final
|
|
68
|
+
iv + ct + c.auth_tag
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def unwrap_key(wrapped, master)
|
|
72
|
+
expected = WRAP_IV_SIZE + Cipher::KEY_SIZE + WRAP_TAG_SIZE
|
|
73
|
+
unless wrapped.bytesize == expected
|
|
74
|
+
raise Errors::InvalidFormat, "Wrapped DEK has unexpected size #{wrapped.bytesize}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
iv = wrapped.byteslice(0, WRAP_IV_SIZE)
|
|
78
|
+
ct = wrapped.byteslice(WRAP_IV_SIZE, Cipher::KEY_SIZE)
|
|
79
|
+
tag = wrapped.byteslice(-WRAP_TAG_SIZE, WRAP_TAG_SIZE)
|
|
80
|
+
|
|
81
|
+
new_cipher(:decrypt, master, iv, tag).then { |c| c.update(ct) + c.final }
|
|
82
|
+
rescue OpenSSL::Cipher::CipherError
|
|
83
|
+
raise Errors::KeyManagementError,
|
|
84
|
+
"Master-key authentication failed — wrong key or tampered DEK"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def new_cipher(mode, key, iv, auth_tag = nil)
|
|
88
|
+
c = OpenSSL::Cipher.new(WRAP_ALGO)
|
|
89
|
+
mode == :encrypt ? c.encrypt : c.decrypt
|
|
90
|
+
c.key = key
|
|
91
|
+
c.iv = iv
|
|
92
|
+
c.auth_tag = auth_tag if auth_tag
|
|
93
|
+
c.auth_data = ""
|
|
94
|
+
c
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def read_master_key(var_name)
|
|
98
|
+
encoded = ENV.fetch(var_name) do
|
|
99
|
+
raise Errors::ProviderError,
|
|
100
|
+
"Environment variable #{var_name.inspect} is not set. " \
|
|
101
|
+
"Generate one with: ruby -rsecurerandom -e " \
|
|
102
|
+
"'puts Base64.strict_encode64(SecureRandom.bytes(32))'"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
key = begin
|
|
106
|
+
Base64.strict_decode64(encoded)
|
|
107
|
+
rescue ArgumentError
|
|
108
|
+
raise Errors::ProviderError,
|
|
109
|
+
"#{var_name} must be Base64-encoded (strict, no line breaks)"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
unless key.bytesize == MASTER_KEY_SIZE
|
|
113
|
+
raise Errors::ProviderError,
|
|
114
|
+
"#{var_name} must decode to exactly #{MASTER_KEY_SIZE} bytes " \
|
|
115
|
+
"(got #{key.bytesize})"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
key
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require "openssl"
|
|
2
|
+
require "securerandom"
|
|
3
|
+
|
|
4
|
+
module ActiveCipherStorage
|
|
5
|
+
class StreamCipher
|
|
6
|
+
include KeyUtils
|
|
7
|
+
|
|
8
|
+
def initialize(config = ActiveCipherStorage.configuration)
|
|
9
|
+
config.validate!
|
|
10
|
+
@config = config
|
|
11
|
+
@provider = config.provider
|
|
12
|
+
@chunk_size = config.chunk_size
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def encrypt(input_io, output_io)
|
|
16
|
+
dek_bundle = @provider.generate_data_key
|
|
17
|
+
key = dek_bundle.fetch(:plaintext_key)
|
|
18
|
+
|
|
19
|
+
Format.write_header(output_io, Format::Header.new(
|
|
20
|
+
version: Format::VERSION,
|
|
21
|
+
algorithm: Format::ALGO_AES256GCM,
|
|
22
|
+
chunked: true,
|
|
23
|
+
chunk_size: @chunk_size,
|
|
24
|
+
provider_id: @provider.provider_id,
|
|
25
|
+
encrypted_dek: dek_bundle.fetch(:encrypted_key)
|
|
26
|
+
))
|
|
27
|
+
|
|
28
|
+
seq = 0
|
|
29
|
+
done = false
|
|
30
|
+
until done
|
|
31
|
+
plaintext = input_io.read(@chunk_size) || "".b
|
|
32
|
+
done = plaintext.bytesize < @chunk_size
|
|
33
|
+
seq += 1
|
|
34
|
+
frame_seq = done ? Format::FINAL_SEQ : seq
|
|
35
|
+
iv = SecureRandom.random_bytes(Format::IV_SIZE)
|
|
36
|
+
ct, tag = encrypt_chunk(plaintext, key, iv, frame_seq)
|
|
37
|
+
Format.write_chunk(output_io, seq: frame_seq, iv: iv, ciphertext: ct, auth_tag: tag)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
seq
|
|
41
|
+
ensure
|
|
42
|
+
zero_bytes!(key)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def decrypt(input_io, output_io)
|
|
46
|
+
header = Format.read_header(input_io)
|
|
47
|
+
raise Errors::InvalidFormat, "Payload is not chunked; use Cipher#decrypt" unless header.chunked
|
|
48
|
+
|
|
49
|
+
key = @provider.decrypt_data_key(header.encrypted_dek)
|
|
50
|
+
loop do
|
|
51
|
+
frame = Format.read_chunk(input_io)
|
|
52
|
+
raise Errors::InvalidFormat, "Unexpected end of stream — missing final frame" if frame.nil?
|
|
53
|
+
|
|
54
|
+
output_io.write(decrypt_chunk(frame[:ciphertext], key, frame[:iv], frame[:auth_tag], frame[:seq]))
|
|
55
|
+
break if frame[:seq] == Format::FINAL_SEQ
|
|
56
|
+
end
|
|
57
|
+
ensure
|
|
58
|
+
zero_bytes!(key)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def encrypt_to_io(io)
|
|
62
|
+
out = StringIO.new("".b)
|
|
63
|
+
encrypt(io, out)
|
|
64
|
+
out.rewind
|
|
65
|
+
out
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def decrypt_to_io(io)
|
|
69
|
+
out = StringIO.new("".b)
|
|
70
|
+
decrypt(io, out)
|
|
71
|
+
out.rewind
|
|
72
|
+
out
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def encrypt_chunk(plaintext, key, iv, seq)
|
|
78
|
+
c = build_cipher(:encrypt, key, iv, nil, seq)
|
|
79
|
+
# OpenSSL raises "data must not be empty" on update(""); call final directly.
|
|
80
|
+
ct = plaintext.empty? ? c.final : (c.update(plaintext.b) + c.final)
|
|
81
|
+
[ct, c.auth_tag]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def decrypt_chunk(ciphertext, key, iv, auth_tag, seq)
|
|
85
|
+
c = build_cipher(:decrypt, key, iv, auth_tag, seq)
|
|
86
|
+
ciphertext.empty? ? c.final : (c.update(ciphertext) + c.final)
|
|
87
|
+
rescue OpenSSL::Cipher::CipherError
|
|
88
|
+
raise Errors::DecryptionError,
|
|
89
|
+
"Authentication failed on chunk seq=#{seq} — data may be tampered"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def build_cipher(mode, key, iv, auth_tag, seq)
|
|
93
|
+
c = OpenSSL::Cipher.new(Cipher::OPENSSL_ALGO)
|
|
94
|
+
mode == :encrypt ? c.encrypt : c.decrypt
|
|
95
|
+
c.key = key
|
|
96
|
+
c.iv = iv
|
|
97
|
+
c.auth_tag = auth_tag if auth_tag
|
|
98
|
+
c.auth_data = [seq].pack("N") # seq as AAD prevents chunk reordering attacks
|
|
99
|
+
c
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require "openssl"
|
|
2
|
+
require "securerandom"
|
|
3
|
+
require "base64"
|
|
4
|
+
require "stringio"
|
|
5
|
+
require "concurrent"
|
|
6
|
+
|
|
7
|
+
require_relative "active_cipher_storage/version"
|
|
8
|
+
require_relative "active_cipher_storage/errors"
|
|
9
|
+
require_relative "active_cipher_storage/key_utils"
|
|
10
|
+
require_relative "active_cipher_storage/format"
|
|
11
|
+
require_relative "active_cipher_storage/configuration"
|
|
12
|
+
require_relative "active_cipher_storage/providers/base"
|
|
13
|
+
require_relative "active_cipher_storage/providers/env_provider"
|
|
14
|
+
require_relative "active_cipher_storage/providers/aws_kms_provider"
|
|
15
|
+
require_relative "active_cipher_storage/cipher"
|
|
16
|
+
require_relative "active_cipher_storage/stream_cipher"
|
|
17
|
+
require_relative "active_cipher_storage/adapters/s3_adapter"
|
|
18
|
+
require_relative "active_cipher_storage/adapters/active_storage_service"
|
|
19
|
+
require_relative "active_cipher_storage/blob_metadata"
|
|
20
|
+
require_relative "active_cipher_storage/key_rotation"
|
|
21
|
+
require_relative "active_cipher_storage/multipart_upload"
|
|
22
|
+
|
|
23
|
+
# Rails Engine wires the service into ActiveStorage's service registry.
|
|
24
|
+
require_relative "active_cipher_storage/engine" if defined?(Rails)
|
|
25
|
+
|
|
26
|
+
module ActiveCipherStorage
|
|
27
|
+
@config_mutex = Mutex.new
|
|
28
|
+
@configuration = Configuration.new
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
def configuration
|
|
32
|
+
@configuration
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def configure
|
|
36
|
+
@config_mutex.synchronize { yield @configuration }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def reset_configuration!
|
|
40
|
+
@config_mutex.synchronize { @configuration = Configuration.new }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require "active_storage/service"
|
|
2
|
+
require "active_cipher_storage/adapters/active_storage_service"
|
|
3
|
+
|
|
4
|
+
module ActiveStorage
|
|
5
|
+
class Service
|
|
6
|
+
unless const_defined?(:ActiveCipherStorageService, false)
|
|
7
|
+
ActiveCipherStorageService = ::ActiveCipherStorage::Adapters::ActiveStorageService
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: active_cipher_storage
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jaspreet Singh
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-04-25 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: concurrent-ruby
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.2'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.2'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activestorage
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '7.0'
|
|
34
|
+
- - "<"
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: '9.0'
|
|
37
|
+
type: :development
|
|
38
|
+
prerelease: false
|
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '7.0'
|
|
44
|
+
- - "<"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '9.0'
|
|
47
|
+
- !ruby/object:Gem::Dependency
|
|
48
|
+
name: aws-sdk-kms
|
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.0'
|
|
54
|
+
type: :development
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '1.0'
|
|
61
|
+
- !ruby/object:Gem::Dependency
|
|
62
|
+
name: aws-sdk-s3
|
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.0'
|
|
68
|
+
type: :development
|
|
69
|
+
prerelease: false
|
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '1.0'
|
|
75
|
+
- !ruby/object:Gem::Dependency
|
|
76
|
+
name: rspec
|
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '3.12'
|
|
82
|
+
type: :development
|
|
83
|
+
prerelease: false
|
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '3.12'
|
|
89
|
+
- !ruby/object:Gem::Dependency
|
|
90
|
+
name: rspec-mocks
|
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '3.12'
|
|
96
|
+
type: :development
|
|
97
|
+
prerelease: false
|
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '3.12'
|
|
103
|
+
- !ruby/object:Gem::Dependency
|
|
104
|
+
name: rubocop
|
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '1.0'
|
|
110
|
+
type: :development
|
|
111
|
+
prerelease: false
|
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '1.0'
|
|
117
|
+
- !ruby/object:Gem::Dependency
|
|
118
|
+
name: simplecov
|
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0.22'
|
|
124
|
+
type: :development
|
|
125
|
+
prerelease: false
|
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0.22'
|
|
131
|
+
- !ruby/object:Gem::Dependency
|
|
132
|
+
name: faker
|
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - "~>"
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '3.0'
|
|
138
|
+
type: :development
|
|
139
|
+
prerelease: false
|
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - "~>"
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '3.0'
|
|
145
|
+
- !ruby/object:Gem::Dependency
|
|
146
|
+
name: rake
|
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - "~>"
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '13.0'
|
|
152
|
+
type: :development
|
|
153
|
+
prerelease: false
|
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
155
|
+
requirements:
|
|
156
|
+
- - "~>"
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
version: '13.0'
|
|
159
|
+
description: |
|
|
160
|
+
active_cipher_storage provides AES-256-GCM envelope encryption for files stored
|
|
161
|
+
via Rails Active Storage or directly via the AWS S3 SDK. Key management is
|
|
162
|
+
delegated to pluggable KMS providers: environment-variable keys, AWS KMS,
|
|
163
|
+
or any custom provider implementing the base interface.
|
|
164
|
+
email:
|
|
165
|
+
- codebyjass@users.noreply.github.com
|
|
166
|
+
executables: []
|
|
167
|
+
extensions: []
|
|
168
|
+
extra_rdoc_files: []
|
|
169
|
+
files:
|
|
170
|
+
- CHANGELOG.md
|
|
171
|
+
- CONTRIBUTING.md
|
|
172
|
+
- LICENSE
|
|
173
|
+
- README.md
|
|
174
|
+
- SECURITY.md
|
|
175
|
+
- active_cipher_storage.gemspec
|
|
176
|
+
- lib/active_cipher_storage.rb
|
|
177
|
+
- lib/active_cipher_storage/adapters/active_storage_service.rb
|
|
178
|
+
- lib/active_cipher_storage/adapters/s3_adapter.rb
|
|
179
|
+
- lib/active_cipher_storage/blob_metadata.rb
|
|
180
|
+
- lib/active_cipher_storage/cipher.rb
|
|
181
|
+
- lib/active_cipher_storage/configuration.rb
|
|
182
|
+
- lib/active_cipher_storage/engine.rb
|
|
183
|
+
- lib/active_cipher_storage/errors.rb
|
|
184
|
+
- lib/active_cipher_storage/format.rb
|
|
185
|
+
- lib/active_cipher_storage/key_rotation.rb
|
|
186
|
+
- lib/active_cipher_storage/key_utils.rb
|
|
187
|
+
- lib/active_cipher_storage/multipart_upload.rb
|
|
188
|
+
- lib/active_cipher_storage/providers/aws_kms_provider.rb
|
|
189
|
+
- lib/active_cipher_storage/providers/base.rb
|
|
190
|
+
- lib/active_cipher_storage/providers/env_provider.rb
|
|
191
|
+
- lib/active_cipher_storage/stream_cipher.rb
|
|
192
|
+
- lib/active_cipher_storage/version.rb
|
|
193
|
+
- lib/active_storage/service/active_cipher_storage_service.rb
|
|
194
|
+
homepage: https://github.com/codebyjass/active-cipher-storage
|
|
195
|
+
licenses:
|
|
196
|
+
- MIT
|
|
197
|
+
metadata:
|
|
198
|
+
bug_tracker_uri: https://github.com/codebyjass/active-cipher-storage/issues
|
|
199
|
+
changelog_uri: https://github.com/codebyjass/active-cipher-storage/blob/main/CHANGELOG.md
|
|
200
|
+
documentation_uri: https://github.com/codebyjass/active-cipher-storage
|
|
201
|
+
homepage_uri: https://github.com/codebyjass/active-cipher-storage
|
|
202
|
+
rubygems_mfa_required: 'true'
|
|
203
|
+
source_code_uri: https://github.com/codebyjass/active-cipher-storage
|
|
204
|
+
post_install_message:
|
|
205
|
+
rdoc_options: []
|
|
206
|
+
require_paths:
|
|
207
|
+
- lib
|
|
208
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
209
|
+
requirements:
|
|
210
|
+
- - ">="
|
|
211
|
+
- !ruby/object:Gem::Version
|
|
212
|
+
version: '3.2'
|
|
213
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
214
|
+
requirements:
|
|
215
|
+
- - ">="
|
|
216
|
+
- !ruby/object:Gem::Version
|
|
217
|
+
version: '0'
|
|
218
|
+
requirements: []
|
|
219
|
+
rubygems_version: 3.4.19
|
|
220
|
+
signing_key:
|
|
221
|
+
specification_version: 4
|
|
222
|
+
summary: Transparent file encryption for Active Storage and S3 with pluggable KMS
|
|
223
|
+
providers
|
|
224
|
+
test_files: []
|