portunus 0.3.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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.nvmrc +1 -0
  4. data/.travis.yml +27 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +189 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +289 -0
  10. data/Rakefile +2 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/lib/Rakefile +4 -0
  14. data/lib/generators/install_generator.rb +26 -0
  15. data/lib/generators/templates/create_portunus.rb.erb +28 -0
  16. data/lib/portunus/configuration.rb +49 -0
  17. data/lib/portunus/data_encryption_key.rb +25 -0
  18. data/lib/portunus/data_key_generator.rb +55 -0
  19. data/lib/portunus/encryptable.rb +42 -0
  20. data/lib/portunus/encrypters/open_ssl_aes.rb +58 -0
  21. data/lib/portunus/encrypters/time.rb +1 -0
  22. data/lib/portunus/field_configurer.rb +157 -0
  23. data/lib/portunus/hasher.rb +9 -0
  24. data/lib/portunus/master_key.rb +15 -0
  25. data/lib/portunus/railtie.rb +14 -0
  26. data/lib/portunus/rotators/dek.rb +69 -0
  27. data/lib/portunus/rotators/kek.rb +51 -0
  28. data/lib/portunus/storage_adaptors/credentials.rb +48 -0
  29. data/lib/portunus/storage_adaptors/environment.rb +67 -0
  30. data/lib/portunus/tasks/generate_keys.rake +36 -0
  31. data/lib/portunus/tasks/rotate_keys.rake +36 -0
  32. data/lib/portunus/type_caster.rb +37 -0
  33. data/lib/portunus/type_casters/boolean.rb +39 -0
  34. data/lib/portunus/type_casters/date.rb +29 -0
  35. data/lib/portunus/type_casters/date_time.rb +29 -0
  36. data/lib/portunus/type_casters/float.rb +29 -0
  37. data/lib/portunus/type_casters/integer.rb +29 -0
  38. data/lib/portunus/type_casters/string.rb +29 -0
  39. data/lib/portunus/version.rb +3 -0
  40. data/lib/portunus.rb +39 -0
  41. data/portunus.gemspec +51 -0
  42. data/tmp/log/development.log +0 -0
  43. metadata +255 -0
@@ -0,0 +1,28 @@
1
+ class CreatePortunus < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :portunus_data_encryption_keys do |t|
4
+ t.string :encrypted_key, null: false
5
+ t.string :master_keyname, null: false
6
+ t.string :encryptable_type, null: false
7
+ t.integer :encryptable_id, null: false
8
+ t.datetime :last_dek_rotation
9
+ t.datetime :last_kek_rotation
10
+ t.timestamps
11
+ end
12
+
13
+ add_index(
14
+ :portunus_data_encryption_keys,
15
+ [:encryptable_id, :encryptable_type],
16
+ unique: true,
17
+ name: "portunus_dek_on_encryptable_id_and_encryptable_type"
18
+ )
19
+ add_index(
20
+ :portunus_data_encryption_keys,
21
+ :last_dek_rotation
22
+ )
23
+ add_index(
24
+ :portunus_data_encryption_keys,
25
+ :last_kek_rotation
26
+ )
27
+ end
28
+ end
@@ -0,0 +1,49 @@
1
+ module Portunus
2
+ class Configuration
3
+ attr_accessor :storage_adaptor, :encrypter, :max_key_duration
4
+
5
+ def initialize
6
+ @storage_adaptor = ::Portunus::StorageAdaptors::Credentials
7
+ @encrypter = ::Portunus::Encrypters::OpenSslAes
8
+ @keys_loaded = false
9
+ @master_key_names = []
10
+ @max_key_duration = 6.months
11
+ end
12
+
13
+ def load_keys
14
+ storage_adaptor.load
15
+ @keys_loaded = true
16
+ end
17
+
18
+ def add_key(key_name)
19
+ @master_key_names.push(key_name)
20
+ # we want to load all the names of the keys in the storage
21
+ # adaptor. Because we might need to search through an environment
22
+ # that has quite a few keys often for every key we want to load
23
+ # the valid keys
24
+ end
25
+
26
+ def keys_loaded?
27
+ @keys_loaded
28
+ end
29
+
30
+ def reset_master_keys
31
+ # Clear all master keys to empty, used for testing
32
+ @master_key_names = []
33
+ @keys_loaded = false
34
+ end
35
+
36
+ def reload_master_keys
37
+ # Perform a reload on the master keys. This is used in tests and to
38
+ # add new keys into the environment without rebooting the app.
39
+ @master_key_names = []
40
+ load_keys
41
+ end
42
+
43
+ def master_key_names
44
+ load_keys unless keys_loaded?
45
+
46
+ @master_key_names
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,25 @@
1
+ module Portunus
2
+ class DataEncryptionKey < ::ActiveRecord::Base
3
+ belongs_to :encryptable, polymorphic: true
4
+
5
+ def key
6
+ ::Portunus.configuration.encrypter.decrypt(
7
+ key: master_encryption_key.value,
8
+ value: encrypted_key
9
+ )
10
+ end
11
+
12
+ def master_keyname=(new_key_value)
13
+ @_master_encryption_key = nil
14
+ super(new_key_value)
15
+ end
16
+
17
+ private
18
+
19
+ def master_encryption_key
20
+ @_master_encryption_key ||= Portunus.configuration.storage_adaptor.lookup(
21
+ master_keyname.to_sym
22
+ )
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,55 @@
1
+ module Portunus
2
+ class DataKeyGenerator
3
+ def self.generate(encrypted_object)
4
+ new(encrypted_object: encrypted_object).generate
5
+ end
6
+
7
+ def initialize(
8
+ encrypted_object:,
9
+ encrypter: ::Portunus.configuration.encrypter,
10
+ key_finder: Portunus.configuration.storage_adaptor
11
+ )
12
+ @encrypter = encrypter
13
+ @key_finder = key_finder
14
+ @encrypted_object = encrypted_object
15
+ end
16
+
17
+ def generate
18
+ dek = encrypted_object.build_data_encryption_key(
19
+ encrypted_key: new_encrypted_key,
20
+ master_keyname: master_keyname
21
+ )
22
+
23
+ if dek.key != new_plaintext_key
24
+ raise ::Portunus::Error.new(
25
+ "Dek Key creation failed: Decrypted key does not match the original"
26
+ )
27
+ end
28
+
29
+ dek
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :encrypted_object, :encrypter, :key_finder
35
+
36
+ def new_encrypted_key
37
+ @_new_encrypted_key ||= encrypter.encrypt(
38
+ key: master_encryption_key.value, value: new_plaintext_key
39
+ )
40
+ end
41
+
42
+ def new_plaintext_key
43
+ # this will be a base64 encoded key
44
+ @_new_key ||= encrypter.generate_key
45
+ end
46
+
47
+ def master_keyname
48
+ @_master_keyname ||= key_finder.list.sample
49
+ end
50
+
51
+ def master_encryption_key
52
+ @_master_encryption_key = key_finder.lookup(master_keyname)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,42 @@
1
+ module Portunus
2
+ module Encryptable
3
+ extend ActiveSupport::Concern
4
+
5
+ class_methods do
6
+ def encrypted_fields_list
7
+ @_encrypted_fields_list ||= []
8
+ end
9
+
10
+ def encrypted_fields(*fields)
11
+ fields.map do |field|
12
+ ::Portunus::FieldConfigurer.for(self, field)
13
+ end
14
+ end
15
+ end
16
+
17
+ included do
18
+ before_validation :hash_encrypted_fields
19
+
20
+ has_one(
21
+ :data_encryption_key,
22
+ as: :encryptable,
23
+ class_name: "::Portunus::DataEncryptionKey"
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ def hash_encrypted_fields
30
+ self.class.encrypted_fields_list.each do |field|
31
+ hashed_field_name = "hashed_#{field}".to_sym
32
+
33
+ if respond_to?(hashed_field_name)
34
+ write_attribute(
35
+ hashed_field_name,
36
+ ::Portunus::Hasher.for(send(field.to_sym))
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,58 @@
1
+ module Portunus
2
+ module Encrypters
3
+ class OpenSslAes
4
+ def self.encrypt(key:, value:)
5
+ new(key: key, value: value).encrypt
6
+ end
7
+
8
+ def self.generate_key
9
+ cipher = OpenSSL::Cipher::AES256.new :CBC
10
+ cipher.encrypt
11
+ Base64.strict_encode64(cipher.random_key)
12
+ end
13
+
14
+ def self.decrypt(key:, value:)
15
+ new(key: key, value: value).decrypt
16
+ end
17
+
18
+ def initialize(key:, value:)
19
+ @key = Base64.strict_decode64(key)
20
+ @value = value
21
+ end
22
+
23
+ def encrypt
24
+ cipher = OpenSSL::Cipher::AES256.new :CBC
25
+ cipher.encrypt
26
+ iv = cipher.random_iv
27
+
28
+ cipher.key = key
29
+ cipher.iv = iv
30
+
31
+ encrypted_output = cipher.update(value) + cipher.final
32
+
33
+ [
34
+ Base64.strict_encode64(iv),
35
+ Base64.strict_encode64(encrypted_output)
36
+ ].join("$")
37
+ end
38
+
39
+ def decrypt
40
+ iv, encrypted_text = value.split("$")
41
+
42
+ decipher = OpenSSL::Cipher::AES256.new :CBC
43
+ decipher.decrypt
44
+
45
+ decipher.iv = Base64.strict_decode64(iv) # previously saved
46
+ decipher.key = key
47
+
48
+ output = decipher.update(Base64.strict_decode64(encrypted_text)) + decipher.final
49
+
50
+ output
51
+ end
52
+
53
+ private
54
+
55
+ attr_reader :key, :value
56
+ end
57
+ end
58
+ end
@@ -0,0 +1 @@
1
+ # DELETE ME, USE DATETIME AND NOT TIME CLASS
@@ -0,0 +1,157 @@
1
+ module Portunus
2
+ class FieldConfigurer
3
+ def self.for(object, field)
4
+ new(object: object, field: field).setup
5
+ end
6
+
7
+ def initialize(object:, field:)
8
+ require "pry"
9
+ @object = object
10
+
11
+ if field.is_a?(Hash)
12
+ @field = field.keys.first.to_sym
13
+ @field_options = default_field_options.merge(field[field.keys.first])
14
+ else
15
+ @field = field
16
+ @field_options = default_field_options
17
+ end
18
+ end
19
+
20
+ def setup
21
+ # add the field to a configuration so we can easily determine if it's
22
+ # a field we need to attempt to encrypt and decrypt. We need this in
23
+ # the `field_before_typecast` method since the normal accessors are
24
+ # not used after a validation fail
25
+ register_field
26
+
27
+ # setup the methods on the model itself
28
+ define_instance_methods
29
+ end
30
+
31
+ private
32
+
33
+ attr_accessor :field, :field_options, :object
34
+
35
+ def default_field_options
36
+ {
37
+ type: :string
38
+ }
39
+ end
40
+
41
+ def instance_methods_for_model
42
+ [
43
+ :define_getter,
44
+ :define_setter,
45
+ :define_before_type_cast,
46
+ :define_association
47
+ ]
48
+ end
49
+
50
+ def define_instance_methods
51
+ instance_methods_for_model.map do |method|
52
+ send(method)
53
+ end
54
+ end
55
+
56
+ def define_setter
57
+ l_field_options = field_options
58
+
59
+ # Override the setter of the field to do the encryption
60
+ object.define_method "#{field}=" do |value, &block|
61
+ if value.present?
62
+ dek = data_encryption_key
63
+
64
+ encrypted_value = ::Portunus.
65
+ configuration.
66
+ encrypter.
67
+ encrypt(
68
+ value: Portunus::TypeCaster.cast(
69
+ value: value,
70
+ type: l_field_options[:type]
71
+ ),
72
+ key: dek.key
73
+ )
74
+ end
75
+
76
+ super(encrypted_value)
77
+ end
78
+ end
79
+
80
+ def define_getter
81
+ # This is required to force the proper scope in this context.
82
+ l_field = field
83
+ l_field_options = field_options
84
+
85
+ object.define_method(l_field.to_sym) do
86
+ value = read_attribute(l_field.to_sym)
87
+
88
+ if value.present?
89
+ dek = data_encryption_key
90
+
91
+ decrypted_value = ::Portunus.
92
+ configuration.
93
+ encrypter.
94
+ decrypt(value: value, key: dek.key)
95
+
96
+ Portunus::TypeCaster.uncast(
97
+ value: decrypted_value,
98
+ type: l_field_options[:type]
99
+ )
100
+ else
101
+ nil
102
+ end
103
+ end
104
+ end
105
+
106
+ def define_before_type_cast
107
+ l_field = field
108
+ l_field_options = field_options
109
+
110
+ object.define_method "#{l_field}_before_type_cast" do
111
+ value = super()
112
+ encrypted = self.class.encrypted_fields_list.include?(l_field.to_sym)
113
+
114
+ if encrypted && value.present?
115
+ dek = data_encryption_key
116
+ decrypted_value = ::Portunus.
117
+ configuration.
118
+ encrypter.
119
+ decrypt(value: value, key: dek.key)
120
+
121
+ value = ::Portunus::TypeCaster.uncast(
122
+ value: decrypted_value,
123
+ type: l_field_options[:type]
124
+ )
125
+ end
126
+
127
+ return value
128
+ end
129
+ end
130
+
131
+ def define_association
132
+ # setup a lazy instantiaion of the DEK so that we don't need to worry
133
+ # about building it for every type of model
134
+ object.define_method :data_encryption_key do
135
+ # this is to determine if a data encryption key is present
136
+ # if not it will lazily create one and fill out the attribute
137
+ # field on the model containing the key
138
+ result = super()
139
+
140
+ if result.blank?
141
+ # self here is the model including encryptable. We pass this
142
+ # so we can call the rails build_data_encryption_key on the
143
+ # model and set up polymorphic columns automatically
144
+ dek = ::Portunus::DataKeyGenerator.generate(self)
145
+ dek
146
+ else
147
+ result
148
+ end
149
+ end
150
+ end
151
+
152
+ def register_field
153
+ # Register the field so we can look it up later
154
+ object.encrypted_fields_list << field
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,9 @@
1
+ module Portunus
2
+ class Hasher
3
+ def self.for(input)
4
+ return nil if input.blank?
5
+
6
+ Digest::SHA2.new(512).hexdigest(input)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ module Portunus
2
+ class MasterKey
3
+ include ActiveModel::Model
4
+
5
+ attr_accessor :name, :value, :enabled, :created_at
6
+
7
+ def self.load
8
+ Portunus.configuration.storage_adaptor.load
9
+ end
10
+
11
+ def self.lookup(key_name)
12
+ Portunus.configuration.storage_adaptor.lookup(key_name)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ # lib/railtie.rb
2
+ require "portunus"
3
+ require "rails"
4
+
5
+ module Portunus
6
+ class Railtie < Rails::Railtie
7
+ railtie_name :portunus
8
+
9
+ rake_tasks do
10
+ path = File.expand_path(__dir__)
11
+ Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,69 @@
1
+ module Portunus
2
+ module Rotators
3
+ class Dek
4
+ def self.for(data_encryption_key)
5
+ new(data_encryption_key).rotate
6
+ end
7
+
8
+ def initialize(data_encryption_key)
9
+ @data_encryption_key = data_encryption_key
10
+ end
11
+
12
+ def rotate
13
+ encryptable = data_encryption_key.encryptable
14
+
15
+ encryptable.class.encrypted_fields_list.map do |field_name|
16
+ field_value_map[field_name.to_sym] = encryptable.send(field_name.to_sym)
17
+ end
18
+
19
+ data_encryption_key.encrypted_key = new_encrypted_key
20
+
21
+ field_value_map.map do |field_name, value|
22
+ encryptable.send("#{field_name}=".to_sym, value)
23
+ end
24
+
25
+ ActiveRecord::Base.transaction do
26
+ encryptable.save
27
+ data_encryption_key.last_dek_rotation = DateTime.now
28
+ data_encryption_key.save
29
+ end
30
+
31
+ true
32
+ rescue StandardError => error
33
+ raise ::Portunus::Error.new(
34
+ "Rotating DEK failed: #{error.full_message}"
35
+ )
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :data_encryption_key
41
+
42
+ def storage_adaptor
43
+ ::Portunus.configuration.storage_adaptor
44
+ end
45
+
46
+ def encrypter
47
+ ::Portunus.configuration.encrypter
48
+ end
49
+
50
+ def field_value_map
51
+ @_field_value_map ||= {}
52
+ end
53
+
54
+ def master_key
55
+ storage_adaptor.lookup(data_encryption_key.master_keyname)
56
+ end
57
+
58
+ def new_plaintext_key
59
+ @_new_plaintext_key ||= encrypter.generate_key
60
+ end
61
+
62
+ def new_encrypted_key
63
+ encrypter.encrypt(
64
+ key: master_key.value, value: new_plaintext_key
65
+ )
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,51 @@
1
+ module Portunus
2
+ module Rotators
3
+ class Kek
4
+ def self.for(data_encryption_key)
5
+ new(data_encryption_key).rotate
6
+ end
7
+
8
+ def initialize(data_encryption_key)
9
+ @data_encryption_key = data_encryption_key
10
+ @unencrypted_dek = data_encryption_key.key
11
+ end
12
+
13
+ def rotate
14
+ data_encryption_key.master_keyname = new_master_key_name
15
+ data_encryption_key.encrypted_key = encrypted_dek_with_new_master
16
+ data_encryption_key.last_kek_rotation = DateTime.now
17
+ data_encryption_key.save!
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :data_encryption_key, :unencrypted_dek
23
+
24
+ def encrypted_dek_with_new_master
25
+ Portunus.configuration.encrypter.encrypt(
26
+ key: new_master_key.value,
27
+ value: unencrypted_dek
28
+ )
29
+ end
30
+
31
+ def new_master_key
32
+ @_new_master_key ||= ::Portunus.configuration.storage_adaptor.lookup(
33
+ new_master_key_name.to_sym
34
+ )
35
+ end
36
+
37
+ def wrapped_current_master_key
38
+ [data_encryption_key.master_keyname]
39
+ end
40
+
41
+ def master_keys
42
+ Portunus.configuration.storage_adaptor.list
43
+ end
44
+
45
+ def new_master_key_name
46
+ @_new_master_key_name ||= (master_keys - wrapped_current_master_key).
47
+ sample
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,48 @@
1
+ module Portunus
2
+ module StorageAdaptors
3
+ class Credentials
4
+ def self.for(data_encryption_key)
5
+ self.lookup(data_encryption_key.master_keyname)
6
+ end
7
+
8
+ def self.load
9
+ key_names = Rails.application.credentials.portunus.keys
10
+
11
+ key_names.map do |key_name|
12
+ ::Portunus.configuration.add_key(key_name)
13
+ end
14
+ end
15
+
16
+ # Required method
17
+ def self.lookup(key_name)
18
+ master_key = Rails.application.credentials.portunus[key_name.to_sym]
19
+
20
+ MasterKey.new(
21
+ enabled: master_key[:enabled],
22
+ name: key_name,
23
+ value: master_key[:key],
24
+ created_at: master_key[:created_at]
25
+ )
26
+ rescue StandardError
27
+ raise ::Portunus::Error.new("Portunus: Master key not found")
28
+ end
29
+
30
+ # Required method
31
+ def self.list
32
+ key_names = Rails.application.credentials.portunus.keys
33
+ # reject any disabled keys so we no longer utilize them for new
34
+ # deks
35
+ key_names.reject! do |key_name|
36
+ Rails.application.credentials.portunus[key_name][:enabled] == false
37
+ end
38
+
39
+ if key_names.length == 0
40
+ raise ::Portunus::Error.new("No valid master keys configured")
41
+ end
42
+
43
+ key_names
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,67 @@
1
+ module Portunus
2
+ module StorageAdaptors
3
+ class Environment
4
+ def self.for(data_encryption_key)
5
+ self.lookup(data_encryption_key.master_keyname)
6
+ end
7
+
8
+ def self.key_map
9
+ @@key_map ||= {}
10
+ end
11
+
12
+ def self.reset_key_map
13
+ @@key_map = {}
14
+ end
15
+
16
+ def self.load
17
+ key_names = ENV.keys.select { |key| key.start_with?("PORTUNUS_") }
18
+
19
+ key_names.map do |name|
20
+ _portunus, key_name, key_type = name.split("_")
21
+
22
+ if self.key_map[key_name.to_sym].blank?
23
+ self.key_map[key_name.to_sym] = {}
24
+ end
25
+
26
+ self.key_map[key_name.to_sym][key_type.to_sym] = ENV.fetch(name)
27
+ end
28
+
29
+ true
30
+ rescue StandardError => error
31
+ raise ::Portunus::Error.new(
32
+ "Portunus: Master keys failed to load: #{error.full_message}"
33
+ )
34
+ end
35
+
36
+ def self.lookup(key_name)
37
+ master_key = self.key_map[key_name.to_sym]
38
+
39
+ MasterKey.new(
40
+ enabled: master_key[:ENABLED],
41
+ name: key_name,
42
+ value: master_key[:KEY],
43
+ created_at: master_key[:CREATED]
44
+ )
45
+ rescue StandardError
46
+ raise ::Portunus::Error.new("Portunus: Master key not found")
47
+ end
48
+
49
+ def self.list
50
+ # Select only enabled keys
51
+ key_names = self.key_map.keys.map do |keyname|
52
+ keyname if self.key_map[keyname][:ENABLED] == "true"
53
+ end.compact
54
+
55
+ if key_names.length == 0
56
+ raise ::Portunus::Error.new("No valid master keys configured")
57
+ end
58
+
59
+ key_names
60
+ end
61
+
62
+ private
63
+
64
+ attr_reader :data_encryption_key
65
+ end
66
+ end
67
+ end