kms_encrypted 1.0.0 → 1.0.1
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 +4 -4
- data/CHANGELOG.md +6 -0
- data/LICENSE.txt +1 -1
- data/README.md +25 -1
- data/lib/kms_encrypted.rb +1 -0
- data/lib/kms_encrypted/box.rb +91 -0
- data/lib/kms_encrypted/database.rb +26 -56
- data/lib/kms_encrypted/model.rb +16 -6
- data/lib/kms_encrypted/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: edd8de63fd58086fa749186d51d6a99cd4343600c8f50827fbb019f1ec2d9788
|
4
|
+
data.tar.gz: 7f91126392f0ad2e0a4c8d48d1a2f3c3fe7fc257a4eaef2bf526acf1badb491d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e83a606cd2b3cacf47854a7425245b163bcdd267a07a501d0344cad3fe98513fbafed99ed6096f53dade5aa8c36be267766293d7639525159c0be3b635e90c3
|
7
|
+
data.tar.gz: 9649c83297b1562e973f83cfcc1b67541f0e08ae2ef05cc6decaef661bd7b9f65b32ea3f35ac2faf8d6c7c10c1110e92e3760cdc8792c98734135abd4f3a882c
|
data/CHANGELOG.md
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -5,13 +5,15 @@ Simple, secure key management for [attr_encrypted](https://github.com/attr-encry
|
|
5
5
|
With KMS Encrypted:
|
6
6
|
|
7
7
|
- Master encryption keys are not on application servers
|
8
|
+
- Encrypt and decrypt permissions can be granted separately
|
8
9
|
- There’s an immutable audit log of all activity
|
9
10
|
- Decryption can be disabled if an attack is detected
|
10
|
-
- Encrypt and decrypt permissions can be granted separately
|
11
11
|
- It’s easy to rotate keys
|
12
12
|
|
13
13
|
Supports [AWS KMS](https://aws.amazon.com/kms/), [Google Cloud KMS](https://cloud.google.com/kms/), and [Vault](https://www.vaultproject.io/)
|
14
14
|
|
15
|
+
Check out [this post](https://ankane.org/sensitive-data-rails) for more info on securing sensitive data with Rails
|
16
|
+
|
15
17
|
[](https://travis-ci.org/ankane/kms_encrypted)
|
16
18
|
|
17
19
|
## How It Works
|
@@ -30,6 +32,28 @@ Follow the instructions for your key management service:
|
|
30
32
|
- [Google Cloud KMS](guides/Google.md)
|
31
33
|
- [Vault](guides/Vault.md)
|
32
34
|
|
35
|
+
## Outside Models
|
36
|
+
|
37
|
+
To encrypt and decrypt outside of models, create a box:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
kms = KmsEncrypted::Box.new
|
41
|
+
```
|
42
|
+
|
43
|
+
You can pass `key_id`, `version`, and `previous_versions` if needed.
|
44
|
+
|
45
|
+
Encrypt
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
kms.encrypt(message, context: {model_name: "User", model_id: 123})
|
49
|
+
```
|
50
|
+
|
51
|
+
Decrypt
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
kms.decrypt(ciphertext, context: {model_name: "User", model_id: 123})
|
55
|
+
```
|
56
|
+
|
33
57
|
## Related Projects
|
34
58
|
|
35
59
|
To securely search encrypted data, check out [Blind Index](https://github.com/ankane/blind_index).
|
data/lib/kms_encrypted.rb
CHANGED
@@ -0,0 +1,91 @@
|
|
1
|
+
module KmsEncrypted
|
2
|
+
class Box
|
3
|
+
attr_reader :key_id, :version, :previous_versions
|
4
|
+
|
5
|
+
def initialize(key_id: nil, version: nil, previous_versions: nil)
|
6
|
+
@key_id = key_id || ENV["KMS_KEY_ID"]
|
7
|
+
@version = version || 1
|
8
|
+
@previous_versions = previous_versions || {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def encrypt(plaintext, context: nil)
|
12
|
+
context = version_context(context, version)
|
13
|
+
key_id = version_key_id(version)
|
14
|
+
ciphertext = KmsEncrypted::Client.new(key_id: key_id, data_key: true).encrypt(plaintext, context: context)
|
15
|
+
"v#{version}:#{encode64(ciphertext)}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def decrypt(ciphertext, context: nil)
|
19
|
+
m = /\Av(\d+):/.match(ciphertext)
|
20
|
+
if m
|
21
|
+
version = m[1].to_i
|
22
|
+
ciphertext = ciphertext.sub("v#{version}:", "")
|
23
|
+
else
|
24
|
+
version = 1
|
25
|
+
legacy_context = true
|
26
|
+
|
27
|
+
# legacy
|
28
|
+
if ciphertext.start_with?("$gc$")
|
29
|
+
_, _, short_key_id, ciphertext = ciphertext.split("$", 4)
|
30
|
+
|
31
|
+
# restore key, except for cryptoKeyVersion
|
32
|
+
stored_key_id = decode64(short_key_id).split("/")[0..3]
|
33
|
+
stored_key_id.insert(0, "projects")
|
34
|
+
stored_key_id.insert(2, "locations")
|
35
|
+
stored_key_id.insert(4, "keyRings")
|
36
|
+
stored_key_id.insert(6, "cryptoKeys")
|
37
|
+
key_id = stored_key_id.join("/")
|
38
|
+
elsif ciphertext.start_with?("vault:")
|
39
|
+
ciphertext = Base64.encode64(ciphertext)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
key_id ||= version_key_id(version)
|
44
|
+
ciphertext = decode64(ciphertext)
|
45
|
+
context = version_context(context, version)
|
46
|
+
|
47
|
+
KmsEncrypted::Client.new(
|
48
|
+
key_id: key_id,
|
49
|
+
data_key: true,
|
50
|
+
legacy_context: legacy_context
|
51
|
+
).decrypt(ciphertext, context: context)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def version_key_id(version)
|
57
|
+
key_id =
|
58
|
+
if previous_versions[version]
|
59
|
+
previous_versions[version][:key_id]
|
60
|
+
elsif self.version == version
|
61
|
+
self.key_id
|
62
|
+
else
|
63
|
+
raise KmsEncrypted::Error, "Version not active: #{version}"
|
64
|
+
end
|
65
|
+
|
66
|
+
raise ArgumentError, "Missing key id" unless key_id
|
67
|
+
|
68
|
+
key_id
|
69
|
+
end
|
70
|
+
|
71
|
+
def version_context(context, version)
|
72
|
+
if context.respond_to?(:call)
|
73
|
+
if context.arity == 0
|
74
|
+
context.call
|
75
|
+
else
|
76
|
+
context.call(version)
|
77
|
+
end
|
78
|
+
else
|
79
|
+
context
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def encode64(bytes)
|
84
|
+
Base64.strict_encode64(bytes)
|
85
|
+
end
|
86
|
+
|
87
|
+
def decode64(bytes)
|
88
|
+
Base64.decode64(bytes)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -8,32 +8,20 @@ module KmsEncrypted
|
|
8
8
|
@options = record.class.kms_keys[key_method.to_sym]
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
12
|
-
|
11
|
+
def version
|
12
|
+
@version ||= evaluate_option(:version).to_i
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
16
|
-
@
|
17
|
-
version = options[:version]
|
18
|
-
version = record.instance_exec(&version) if version.respond_to?(:call)
|
19
|
-
version.to_i
|
20
|
-
end
|
15
|
+
def key_id
|
16
|
+
@key_id ||= evaluate_option(:key_id)
|
21
17
|
end
|
22
18
|
|
23
|
-
def
|
24
|
-
|
25
|
-
versions[current_version] ||= options.slice(:key_id)
|
26
|
-
|
27
|
-
raise KmsEncrypted::Error, "Version not active: #{version}" unless versions[version]
|
28
|
-
|
29
|
-
key_id = versions[version][:key_id]
|
30
|
-
|
31
|
-
raise ArgumentError, "Missing key id" unless key_id
|
32
|
-
|
33
|
-
key_id
|
19
|
+
def previous_versions
|
20
|
+
@previous_versions ||= evaluate_option(:previous_versions)
|
34
21
|
end
|
35
22
|
|
36
23
|
def context(version)
|
24
|
+
name = options[:name]
|
37
25
|
context_method = name ? "kms_encryption_context_#{name}" : "kms_encryption_context"
|
38
26
|
if record.method(context_method).arity == 0
|
39
27
|
record.send(context_method)
|
@@ -43,51 +31,33 @@ module KmsEncrypted
|
|
43
31
|
end
|
44
32
|
|
45
33
|
def encrypt(plaintext)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
34
|
+
context = context(version)
|
35
|
+
|
36
|
+
KmsEncrypted::Box.new(
|
37
|
+
key_id: key_id,
|
38
|
+
version: version,
|
39
|
+
previous_versions: previous_versions
|
40
|
+
).encrypt(plaintext, context: context)
|
50
41
|
end
|
51
42
|
|
52
43
|
def decrypt(ciphertext)
|
44
|
+
# determine version for context
|
53
45
|
m = /\Av(\d+):/.match(ciphertext)
|
54
|
-
|
55
|
-
|
56
|
-
ciphertext = ciphertext.sub("v#{version}:", "")
|
57
|
-
else
|
58
|
-
version = 1
|
59
|
-
context = {} if options[:upgrade_context]
|
60
|
-
legacy_context = true
|
61
|
-
|
62
|
-
# legacy
|
63
|
-
if ciphertext.start_with?("$gc$")
|
64
|
-
_, _, short_key_id, ciphertext = ciphertext.split("$", 4)
|
65
|
-
|
66
|
-
# restore key, except for cryptoKeyVersion
|
67
|
-
stored_key_id = decode64(short_key_id).split("/")[0..3]
|
68
|
-
stored_key_id.insert(0, "projects")
|
69
|
-
stored_key_id.insert(2, "locations")
|
70
|
-
stored_key_id.insert(4, "keyRings")
|
71
|
-
stored_key_id.insert(6, "cryptoKeys")
|
72
|
-
key_id = stored_key_id.join("/")
|
73
|
-
elsif ciphertext.start_with?("vault:")
|
74
|
-
ciphertext = Base64.encode64(ciphertext)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
key_id ||= key_version(version)
|
79
|
-
context ||= context(version)
|
80
|
-
ciphertext = decode64(ciphertext)
|
46
|
+
version = m ? m[1].to_i : 1
|
47
|
+
context = (options[:upgrade_context] && !m) ? {} : context(version)
|
81
48
|
|
82
|
-
KmsEncrypted::
|
49
|
+
KmsEncrypted::Box.new(
|
50
|
+
key_id: key_id,
|
51
|
+
previous_versions: previous_versions
|
52
|
+
).decrypt(ciphertext, context: context)
|
83
53
|
end
|
84
54
|
|
85
|
-
|
86
|
-
Base64.strict_encode64(bytes)
|
87
|
-
end
|
55
|
+
private
|
88
56
|
|
89
|
-
def
|
90
|
-
|
57
|
+
def evaluate_option(key)
|
58
|
+
opt = options[key]
|
59
|
+
opt = record.instance_exec(&opt) if opt.respond_to?(:call)
|
60
|
+
opt
|
91
61
|
end
|
92
62
|
end
|
93
63
|
end
|
data/lib/kms_encrypted/model.rb
CHANGED
@@ -8,12 +8,22 @@ module KmsEncrypted
|
|
8
8
|
context_method = name ? "kms_encryption_context_#{name}" : "kms_encryption_context"
|
9
9
|
|
10
10
|
class_eval do
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
@kms_keys ||= {}
|
12
|
+
|
13
|
+
unless respond_to?(:kms_keys)
|
14
|
+
def self.kms_keys
|
15
|
+
parent_keys =
|
16
|
+
if superclass.respond_to?(:kms_keys)
|
17
|
+
superclass.kms_keys
|
18
|
+
else
|
19
|
+
{}
|
20
|
+
end
|
21
|
+
|
22
|
+
parent_keys.merge(@kms_keys || {})
|
23
|
+
end
|
15
24
|
end
|
16
|
-
|
25
|
+
|
26
|
+
@kms_keys[key_method.to_sym] = {
|
17
27
|
key_id: key_id,
|
18
28
|
name: name,
|
19
29
|
version: version,
|
@@ -21,7 +31,7 @@ module KmsEncrypted
|
|
21
31
|
upgrade_context: upgrade_context
|
22
32
|
}
|
23
33
|
|
24
|
-
if kms_keys.size == 1
|
34
|
+
if @kms_keys.size == 1
|
25
35
|
after_save :encrypt_kms_keys
|
26
36
|
|
27
37
|
# fetch all keys together so only need to update database once
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kms_encrypted
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-01-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '4.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '4.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -160,6 +160,7 @@ files:
|
|
160
160
|
- LICENSE.txt
|
161
161
|
- README.md
|
162
162
|
- lib/kms_encrypted.rb
|
163
|
+
- lib/kms_encrypted/box.rb
|
163
164
|
- lib/kms_encrypted/client.rb
|
164
165
|
- lib/kms_encrypted/clients/aws.rb
|
165
166
|
- lib/kms_encrypted/clients/base.rb
|