kms_encrypted 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11c711222f049df42c663c9e68a2822fb7e318d3f3fc02f6346801c998ead328
4
- data.tar.gz: 98200a87d8bf02a7398b075aaa6d79dff706a4d15d850584ebd952329980520b
3
+ metadata.gz: edd8de63fd58086fa749186d51d6a99cd4343600c8f50827fbb019f1ec2d9788
4
+ data.tar.gz: 7f91126392f0ad2e0a4c8d48d1a2f3c3fe7fc257a4eaef2bf526acf1badb491d
5
5
  SHA512:
6
- metadata.gz: 7b3d37f2739f1a869a8a34c62e9300c3b54b0e8b87b98377883fa252d161aaafcb387044049bf0bfb070e86d481034bcf5c19b0e83d21a4668a513b02f3da151
7
- data.tar.gz: b5596bc03466ac15b09d72cd1e00093eda05e2dcd3ca17607484ff678da808647bb7d364be27b5cfc912cdcccb991cbb55b18fd94206a4cb4510d17eaeb4c0da
6
+ metadata.gz: 7e83a606cd2b3cacf47854a7425245b163bcdd267a07a501d0344cad3fe98513fbafed99ed6096f53dade5aa8c36be267766293d7639525159c0be3b635e90c3
7
+ data.tar.gz: 9649c83297b1562e973f83cfcc1b67541f0e08ae2ef05cc6decaef661bd7b9f65b32ea3f35ac2faf8d6c7c10c1110e92e3760cdc8792c98734135abd4f3a882c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 1.0.1
2
+
3
+ - Added support for encryption and decryption outside models
4
+ - Added support for dynamic keys
5
+ - Fixed issue with inheritance
6
+
1
7
  ## 1.0.0
2
8
 
3
9
  - Added versioning
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2017-2018 Andrew Kane
1
+ Copyright (c) 2017-2019 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
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
  [![Build Status](https://travis-ci.org/ankane/kms_encrypted.svg?branch=master)](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
@@ -5,6 +5,7 @@ require "json"
5
5
  require "securerandom"
6
6
 
7
7
  # modules
8
+ require "kms_encrypted/box"
8
9
  require "kms_encrypted/database"
9
10
  require "kms_encrypted/log_subscriber"
10
11
  require "kms_encrypted/model"
@@ -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 name
12
- options[:name]
11
+ def version
12
+ @version ||= evaluate_option(:version).to_i
13
13
  end
14
14
 
15
- def current_version
16
- @version ||= begin
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 key_version(version)
24
- versions = (options[:previous_versions] || {}).dup
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
- key_id = key_version(current_version)
47
- context = context(current_version)
48
- ciphertext = KmsEncrypted::Client.new(key_id: key_id, data_key: true).encrypt(plaintext, context: context)
49
- "v#{current_version}:#{encode64(ciphertext)}"
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
- if m
55
- version = m[1].to_i
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::Client.new(key_id: key_id, data_key: true, legacy_context: legacy_context).decrypt(ciphertext, context: context)
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
- def encode64(bytes)
86
- Base64.strict_encode64(bytes)
87
- end
55
+ private
88
56
 
89
- def decode64(bytes)
90
- Base64.decode64(bytes)
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
@@ -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
- class << self
12
- def kms_keys
13
- @kms_keys ||= {}
14
- end unless respond_to?(:kms_keys)
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
- kms_keys[key_method.to_sym] = {
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
@@ -1,3 +1,3 @@
1
1
  module KmsEncrypted
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.1"
3
3
  end
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.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: 2018-12-17 00:00:00.000000000 Z
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: '0'
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: '0'
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