kms_attrs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []