kms_attrs 0.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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/kms_attrs.rb +163 -0
  3. metadata +87 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d39a653ef22ea6014157ef79f61eef65a94a8e9c
4
+ data.tar.gz: b20183569e6787f9fba5561daad0a39973c7ed39
5
+ SHA512:
6
+ metadata.gz: 8e5176dd3b2f9e5469a7edd6fa6c14ada182511857876aa3f350171de3327a2c08ffb8dc20dc3e096f497376b695da3fb32a1b5672766aa7a1f74599cec6a116
7
+ data.tar.gz: 4556912f19423af534ae5dad63caadd1fff7810e89232573d281e7c0a0eb0cea0e173a18887ff4f233f50fdc2c16a6d4aad41c55c58db77057d6a71bf8ebfa44
data/lib/kms_attrs.rb ADDED
@@ -0,0 +1,163 @@
1
+ module KmsAttrs
2
+ class << self
3
+ def included base
4
+ base.extend ClassMethods
5
+ end
6
+ end
7
+
8
+ module ClassMethods
9
+ def kms_attr(field, key_id:, retain: false, context_key: nil, context_value: nil)
10
+ include InstanceMethods
11
+
12
+ define_method "#{field}=" do |data|
13
+ key_id = set_key_id(key_id)
14
+ data_key = aws_generate_data_key(key_id, context_key, context_value)
15
+ encrypted = encrypt_attr(data, data_key.plaintext)
16
+ data_key.plaintext = nil
17
+
18
+ if retain
19
+ set_retained(field, data)
20
+ end
21
+ data = nil
22
+
23
+ store_hash(field, {
24
+ key: data_key.ciphertext_blob,
25
+ iv: encrypted[:iv],
26
+ blob: encrypted[:data]
27
+ })
28
+ end
29
+
30
+ define_method "#{field}" do
31
+ get_hash(field)
32
+ end
33
+
34
+ define_method "#{field}_d" do
35
+ hash = get_hash(field)
36
+ if hash
37
+ if retain && plaintext = get_retained(field)
38
+ plaintext
39
+ else
40
+ plaintext = decrypt_attr(
41
+ hash[:blob],
42
+ aws_decrypt_key(hash[:key], context_key, context_value),
43
+ hash[:iv]
44
+ )
45
+
46
+ if retain
47
+ set_retained(field, plaintext)
48
+ end
49
+
50
+ plaintext
51
+ end
52
+ else
53
+ nil
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+
60
+ module InstanceMethods
61
+ def store_hash(field, data)
62
+ @_hashes ||= {}
63
+ b_data = Marshal.dump(data)
64
+ data64 = Base64.encode64(b_data)
65
+ @_hashes[field] = data64
66
+ self[field] = data64
67
+ end
68
+
69
+ def get_hash(field)
70
+ @_hashes ||= {}
71
+ hash = @_hashes[field] ||= read_attribute(field)
72
+ if hash
73
+ Marshal.load(Base64.decode64(hash))
74
+ else
75
+ nil
76
+ end
77
+ end
78
+
79
+ def get_retained(field)
80
+ @_retained ||= {}
81
+ @_retained[field]
82
+ end
83
+
84
+ def set_retained(field, plaintext)
85
+ @_retained ||= {}
86
+ @_retained[field] = plaintext
87
+ end
88
+
89
+ def decrypt_attr(data, key, iv)
90
+ decipher = OpenSSL::Cipher.new('AES-256-CBC')
91
+ decipher.decrypt
92
+ decipher.key = key
93
+ decipher.iv = iv
94
+ decipher.update(data) + decipher.final
95
+ end
96
+
97
+ def encrypt_attr(data, key)
98
+ cipher = OpenSSL::Cipher.new('AES-256-CBC')
99
+ cipher.encrypt
100
+
101
+ cipher.key = key
102
+ iv = cipher.random_iv
103
+ {iv: iv, data: cipher.update(data) + cipher.final}
104
+ end
105
+
106
+ def aws_decrypt_key(key, context_key, context_value)
107
+ args = {ciphertext_blob: key}
108
+ aws_kms.decrypt(apply_context(args, context_key, context_value)).plaintext
109
+ end
110
+
111
+ def aws_kms
112
+ @kms ||= Aws::KMS::Client.new(region: ENV['AWS_DEFAULT_REGION'])
113
+ end
114
+
115
+ def aws_generate_data_key(key_id, context_key, context_value)
116
+ args = {key_id: key_id, key_spec: 'AES_256'}
117
+ aws_kms.generate_data_key(apply_context(args, context_key, context_value))
118
+ end
119
+
120
+ def apply_context(args, key, value)
121
+ if key && value
122
+ if key.is_a?(Proc)
123
+ key = key.call
124
+ end
125
+
126
+ if value.is_a?(Proc)
127
+ value = value.call
128
+ end
129
+
130
+ if key.is_a?(Symbol)
131
+ key = self.send(key)
132
+ end
133
+
134
+ if value.is_a?(Symbol)
135
+ value = self.send(value)
136
+ end
137
+
138
+ if key.is_a?(String) && value.is_a?(String)
139
+ args[:encryption_context] = {key => value}
140
+ end
141
+ end
142
+ args
143
+ end
144
+
145
+ def set_key_id(key_id)
146
+ if key_id.is_a?(Proc)
147
+ key_id = key_id.call
148
+ end
149
+
150
+ if key_id.is_a?(Symbol)
151
+ key_id = self.send(key_id)
152
+ end
153
+
154
+ if key_id.is_a?(String)
155
+ return key_id
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ if Object.const_defined?('ActiveRecord')
162
+ ActiveRecord::Base.send(:include, KmsAttrs)
163
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kms_attrs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Justin Ouellette
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Quickly add KMS encryption and decryption to your ActiveRecord model
56
+ attributes.
57
+ email: ouellette.justin@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - lib/kms_attrs.rb
63
+ homepage: https://github.com/justinoue/kms_attrs
64
+ licenses:
65
+ - GPLv3
66
+ metadata: {}
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 2.4.3
84
+ signing_key:
85
+ specification_version: 4
86
+ summary: AWS KMS encryption for ActiveRecord.
87
+ test_files: []