lockbox 0.4.2 → 0.4.7
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 +24 -0
- data/README.md +73 -6
- data/SECURITY.md +3 -0
- data/lib/lockbox.rb +2 -0
- data/lib/lockbox/active_storage_extensions.rb +29 -4
- data/lib/lockbox/calculations.rb +36 -0
- data/lib/lockbox/carrier_wave_extensions.rb +22 -3
- data/lib/lockbox/encryptor.rb +5 -4
- data/lib/lockbox/key_generator.rb +1 -1
- data/lib/lockbox/migrator.rb +7 -0
- data/lib/lockbox/model.rb +66 -10
- data/lib/lockbox/utils.rb +11 -7
- data/lib/lockbox/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f8c447dd90537203a1a3038c347c10de0e48f5b29795b382a2a77019e6e5764
|
4
|
+
data.tar.gz: 9133e9eb0c2132b7c77c39f8c24a3c27ea9b3cbb1d3d82f7f069b2db9992198f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4396ee4ead0de0592e7a3574b563f98d58f0402198dd8662cbdc374cc5f8a39e629f6397414b498456c665e8a635518db3a532056ecd42154206fde4ab938e5c
|
7
|
+
data.tar.gz: 6057ea6f43db261580a0ee0ae13f1303251d2ee8ef2b1bcdba8399b6a858dca1fdf6c84e25f5da85a059a6d260be7094969e2118ff11a8c1d2565617c48f74cd
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
## 0.4.7 (2020-08-18)
|
2
|
+
|
3
|
+
- Added `lockbox_options` method to encrypted CarrierWave uploaders
|
4
|
+
- Improved attribute loading when no decryption key specified
|
5
|
+
|
6
|
+
## 0.4.6 (2020-07-02)
|
7
|
+
|
8
|
+
- Added support for `update_column` and `update_columns`
|
9
|
+
|
10
|
+
## 0.4.5 (2020-06-26)
|
11
|
+
|
12
|
+
- Improved error message for non-string values
|
13
|
+
- Fixed error with migrating Action Text
|
14
|
+
- Fixed error with migrating serialized attributes
|
15
|
+
|
16
|
+
## 0.4.4 (2020-06-23)
|
17
|
+
|
18
|
+
- Added support for `pluck`
|
19
|
+
|
20
|
+
## 0.4.3 (2020-05-26)
|
21
|
+
|
22
|
+
- Improved error message for bad key length
|
23
|
+
- Fixed missing attribute error
|
24
|
+
|
1
25
|
## 0.4.2 (2020-05-11)
|
2
26
|
|
3
27
|
- Added experimental support for migrating Active Storage files
|
data/README.md
CHANGED
@@ -190,6 +190,8 @@ end
|
|
190
190
|
|
191
191
|
## Action Text
|
192
192
|
|
193
|
+
**Note:** Action Text uses direct uploads for files, which cannot be encrypted with application-level encryption like Lockbox. This only encrypts the database field.
|
194
|
+
|
193
195
|
Create a migration with:
|
194
196
|
|
195
197
|
```ruby
|
@@ -264,8 +266,9 @@ end
|
|
264
266
|
|
265
267
|
There are a few limitations to be aware of:
|
266
268
|
|
267
|
-
-
|
268
|
-
-
|
269
|
+
- Variants and previews aren’t supported when encrypted
|
270
|
+
- Metadata like image width and height aren’t extracted when encrypted
|
271
|
+
- Direct uploads can’t be encrypted with application-level encryption like Lockbox, but can use server-side encryption
|
269
272
|
|
270
273
|
To serve encrypted files, use a controller action.
|
271
274
|
|
@@ -278,7 +281,7 @@ end
|
|
278
281
|
|
279
282
|
#### Migrating Existing Files [experimental]
|
280
283
|
|
281
|
-
**Note:** This feature is experimental. Please try it in a non-production environment and
|
284
|
+
**Note:** This feature is experimental. Please try it in a non-production environment and [share](https://github.com/ankane/lockbox/issues/44) how it goes.
|
282
285
|
|
283
286
|
Lockbox makes it easy to encrypt existing files without downtime.
|
284
287
|
|
@@ -508,6 +511,24 @@ Lockbox.rotate(User, attributes: [:email])
|
|
508
511
|
|
509
512
|
Once all records are rotated, you can remove `previous_versions` from the model.
|
510
513
|
|
514
|
+
### Action Text
|
515
|
+
|
516
|
+
Update your initializer:
|
517
|
+
|
518
|
+
```ruby
|
519
|
+
Lockbox.encrypts_action_text_body(previous_versions: [{key: previous_key}])
|
520
|
+
```
|
521
|
+
|
522
|
+
Use `master_key` instead of `key` if passing the master key.
|
523
|
+
|
524
|
+
To rotate existing records, use:
|
525
|
+
|
526
|
+
```ruby
|
527
|
+
Lockbox.rotate(ActionText::RichText, attributes: [:body])
|
528
|
+
```
|
529
|
+
|
530
|
+
Once all records are rotated, you can remove `previous_versions` from the initializer.
|
531
|
+
|
511
532
|
### Active Storage
|
512
533
|
|
513
534
|
Update your model:
|
@@ -550,6 +571,14 @@ User.find_each do |user|
|
|
550
571
|
end
|
551
572
|
```
|
552
573
|
|
574
|
+
For multiple files, use:
|
575
|
+
|
576
|
+
```ruby
|
577
|
+
User.find_each do |user|
|
578
|
+
user.licenses.map(&:rotate_encryption!)
|
579
|
+
end
|
580
|
+
```
|
581
|
+
|
553
582
|
Once all files are rotated, you can remove `previous_versions` from the model.
|
554
583
|
|
555
584
|
### Local Files & Strings
|
@@ -734,12 +763,32 @@ end
|
|
734
763
|
|
735
764
|
You can use a key management service to manage your keys with [KMS Encrypted](https://github.com/ankane/kms_encrypted).
|
736
765
|
|
766
|
+
For Active Record and Mongoid, use:
|
767
|
+
|
737
768
|
```ruby
|
738
769
|
class User < ApplicationRecord
|
739
770
|
encrypts :email, key: :kms_key
|
740
771
|
end
|
741
772
|
```
|
742
773
|
|
774
|
+
For Action Text, use:
|
775
|
+
|
776
|
+
```ruby
|
777
|
+
ActiveSupport.on_load(:action_text_rich_text) do
|
778
|
+
ActionText::RichText.has_kms_key
|
779
|
+
end
|
780
|
+
|
781
|
+
Lockbox.encrypts_action_text_body(key: :kms_key)
|
782
|
+
```
|
783
|
+
|
784
|
+
For Active Storage, use:
|
785
|
+
|
786
|
+
```ruby
|
787
|
+
class User < ApplicationRecord
|
788
|
+
encrypts_attached :license, key: :kms_key
|
789
|
+
end
|
790
|
+
```
|
791
|
+
|
743
792
|
For CarrierWave, use:
|
744
793
|
|
745
794
|
```ruby
|
@@ -772,7 +821,7 @@ lockbox.encrypt("clear").bytesize # 44
|
|
772
821
|
lockbox.encrypt("consider").bytesize # 44
|
773
822
|
```
|
774
823
|
|
775
|
-
The block size for padding is 16 bytes by default.
|
824
|
+
The block size for padding is 16 bytes by default. Lockbox uses [ISO/IEC 7816-4](https://en.wikipedia.org/wiki/Padding_(cryptography)#ISO/IEC_7816-4) padding, which uses at least one byte, so if we have a status larger than 15 bytes, it will have a different length than the others.
|
776
825
|
|
777
826
|
```ruby
|
778
827
|
box.encrypt("length15status!").bytesize # 44
|
@@ -785,9 +834,25 @@ Change the block size with:
|
|
785
834
|
Lockbox.new(padding: 32) # bytes
|
786
835
|
```
|
787
836
|
|
837
|
+
## Associated Data
|
838
|
+
|
839
|
+
You can pass extra context during encryption to make sure encrypted data isn’t moved to a different context.
|
840
|
+
|
841
|
+
```ruby
|
842
|
+
lockbox = Lockbox.new(key: key)
|
843
|
+
ciphertext = lockbox.encrypt(message, associated_data: "somecontext")
|
844
|
+
```
|
845
|
+
|
846
|
+
Without the same context, decryption will fail.
|
847
|
+
|
848
|
+
```ruby
|
849
|
+
lockbox.decrypt(ciphertext, associated_data: "somecontext") # success
|
850
|
+
lockbox.decrypt(ciphertext, associated_data: "othercontext") # fails
|
851
|
+
```
|
852
|
+
|
788
853
|
## Binary Columns
|
789
854
|
|
790
|
-
You can use `binary` columns for the ciphertext instead of `text` columns
|
855
|
+
You can use `binary` columns for the ciphertext instead of `text` columns.
|
791
856
|
|
792
857
|
```ruby
|
793
858
|
class AddEmailCiphertextToUsers < ActiveRecord::Migration[6.0]
|
@@ -797,7 +862,7 @@ class AddEmailCiphertextToUsers < ActiveRecord::Migration[6.0]
|
|
797
862
|
end
|
798
863
|
```
|
799
864
|
|
800
|
-
|
865
|
+
Disable Base64 encoding to save space.
|
801
866
|
|
802
867
|
```ruby
|
803
868
|
class User < ApplicationRecord
|
@@ -965,3 +1030,5 @@ cd lockbox
|
|
965
1030
|
bundle install
|
966
1031
|
bundle exec rake test
|
967
1032
|
```
|
1033
|
+
|
1034
|
+
For security issues, send an email to the address on [this page](https://github.com/ankane).
|
data/SECURITY.md
ADDED
data/lib/lockbox.rb
CHANGED
@@ -5,6 +5,7 @@ require "securerandom"
|
|
5
5
|
|
6
6
|
# modules
|
7
7
|
require "lockbox/box"
|
8
|
+
require "lockbox/calculations"
|
8
9
|
require "lockbox/encryptor"
|
9
10
|
require "lockbox/key_generator"
|
10
11
|
require "lockbox/io"
|
@@ -25,6 +26,7 @@ if defined?(ActiveSupport)
|
|
25
26
|
ActiveSupport.on_load(:active_record) do
|
26
27
|
extend Lockbox::Model
|
27
28
|
extend Lockbox::Model::Attached
|
29
|
+
ActiveRecord::Calculations.prepend Lockbox::Calculations
|
28
30
|
end
|
29
31
|
|
30
32
|
ActiveSupport.on_load(:mongoid) do
|
@@ -1,7 +1,22 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
1
|
+
# Ideally encryption and decryption would happen at the blob/service level.
|
2
|
+
# However, Active Storage < 6.1 only supports a single service (per environment).
|
3
|
+
# This means all attachments need to be encrypted or none of them,
|
4
|
+
# which is often not practical.
|
5
|
+
#
|
6
|
+
# Active Storage 6.1 adds support for multiple services, which changes this.
|
7
|
+
# We could have a Lockbox service:
|
8
|
+
#
|
9
|
+
# lockbox:
|
10
|
+
# service: Lockbox
|
11
|
+
# backend: local # delegate to another service, like mirror service
|
12
|
+
# key: ... # Lockbox options
|
13
|
+
#
|
14
|
+
# However, the checksum is computed *and stored on the blob*
|
15
|
+
# before the file is passed to the service.
|
16
|
+
# We don't want the MD5 checksum of the plaintext stored in the database.
|
17
|
+
#
|
18
|
+
# Instead, we encrypt and decrypt at the attachment level,
|
19
|
+
# and we define encryption settings at the model level.
|
5
20
|
module Lockbox
|
6
21
|
module ActiveStorageExtensions
|
7
22
|
module Attached
|
@@ -95,6 +110,16 @@ module Lockbox
|
|
95
110
|
result
|
96
111
|
end
|
97
112
|
|
113
|
+
def variant(*args)
|
114
|
+
raise Lockbox::Error, "Variant not supported for encrypted files" if Utils.encrypted_options(record, name)
|
115
|
+
super
|
116
|
+
end
|
117
|
+
|
118
|
+
def preview(*args)
|
119
|
+
raise Lockbox::Error, "Preview not supported for encrypted files" if Utils.encrypted_options(record, name)
|
120
|
+
super
|
121
|
+
end
|
122
|
+
|
98
123
|
if ActiveStorage::VERSION::MAJOR >= 6
|
99
124
|
def open(**options)
|
100
125
|
blob.open(**options) do |file|
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Lockbox
|
2
|
+
module Calculations
|
3
|
+
def pluck(*column_names)
|
4
|
+
return super unless model.respond_to?(:lockbox_attributes)
|
5
|
+
|
6
|
+
lockbox_columns = column_names.map.with_index { |c, i| [model.lockbox_attributes[c.to_sym], i] }.select(&:first)
|
7
|
+
return super unless lockbox_columns.any?
|
8
|
+
|
9
|
+
# replace column with ciphertext column
|
10
|
+
lockbox_columns.each do |la, i|
|
11
|
+
column_names[i] = la[:encrypted_attribute]
|
12
|
+
end
|
13
|
+
|
14
|
+
# pluck
|
15
|
+
result = super(*column_names)
|
16
|
+
|
17
|
+
# decrypt result
|
18
|
+
# handle pluck to single columns and multiple
|
19
|
+
#
|
20
|
+
# we can't pass context to decrypt method
|
21
|
+
# so this won't work if any options are a symbol or proc
|
22
|
+
if column_names.size == 1
|
23
|
+
la = lockbox_columns.first.first
|
24
|
+
result.map! { |v| model.send("decrypt_#{la[:encrypted_attribute]}", v) }
|
25
|
+
else
|
26
|
+
lockbox_columns.each do |la, i|
|
27
|
+
result.each do |v|
|
28
|
+
v[i] = model.send("decrypt_#{la[:encrypted_attribute]}", v[i])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
result
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -2,18 +2,32 @@ module Lockbox
|
|
2
2
|
module CarrierWaveExtensions
|
3
3
|
def encrypt(**options)
|
4
4
|
class_eval do
|
5
|
+
# uses same hook as process (before cache)
|
6
|
+
# processing can be disabled, so better to keep separate
|
5
7
|
before :cache, :encrypt
|
6
8
|
|
9
|
+
define_singleton_method :lockbox_options do
|
10
|
+
options
|
11
|
+
end
|
12
|
+
|
7
13
|
def encrypt(file)
|
8
|
-
|
14
|
+
# safety check
|
15
|
+
# see CarrierWave::Uploader::Cache#cache!
|
16
|
+
raise Lockbox::Error, "Expected files to be equal. Please report an issue." unless file && @file && file == @file
|
17
|
+
|
18
|
+
# processors in CarrierWave move updated file to current_path
|
19
|
+
# however, this causes versions to use the processed file
|
20
|
+
# we only want to change the file for the current version
|
21
|
+
@file = CarrierWave::SanitizedFile.new(lockbox_notify("encrypt_file") { lockbox.encrypt_io(file) })
|
9
22
|
end
|
10
23
|
|
11
24
|
# TODO safe to memoize?
|
12
25
|
def read
|
13
26
|
r = super
|
14
|
-
|
27
|
+
lockbox_notify("decrypt_file") { lockbox.decrypt(r) } if r
|
15
28
|
end
|
16
29
|
|
30
|
+
# use size of plaintext since read and content type use plaintext
|
17
31
|
def size
|
18
32
|
read.bytesize
|
19
33
|
end
|
@@ -23,6 +37,7 @@ module Lockbox
|
|
23
37
|
MimeMagic.by_magic(read).try(:type) || "invalid/invalid"
|
24
38
|
end
|
25
39
|
|
40
|
+
# disable processing since already processed
|
26
41
|
def rotate_encryption!
|
27
42
|
io = Lockbox::IO.new(read)
|
28
43
|
io.original_filename = file.filename
|
@@ -46,6 +61,8 @@ module Lockbox
|
|
46
61
|
end
|
47
62
|
end
|
48
63
|
|
64
|
+
# for mounted uploaders, use mounted name
|
65
|
+
# for others, use uploader name
|
49
66
|
def lockbox_name
|
50
67
|
if mounted_as
|
51
68
|
mounted_as.to_s
|
@@ -58,7 +75,9 @@ module Lockbox
|
|
58
75
|
end
|
59
76
|
end
|
60
77
|
|
61
|
-
|
78
|
+
# Active Support notifications so it's easier
|
79
|
+
# to see when files are encrypted and decrypted
|
80
|
+
def lockbox_notify(type)
|
62
81
|
if defined?(ActiveSupport::Notifications)
|
63
82
|
name = lockbox_name
|
64
83
|
|
data/lib/lockbox/encryptor.rb
CHANGED
@@ -13,7 +13,7 @@ module Lockbox
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def encrypt(message, **options)
|
16
|
-
message = check_string(message
|
16
|
+
message = check_string(message)
|
17
17
|
ciphertext = @boxes.first.encrypt(message, **options)
|
18
18
|
ciphertext = Base64.strict_encode64(ciphertext) if @encode
|
19
19
|
ciphertext
|
@@ -21,7 +21,7 @@ module Lockbox
|
|
21
21
|
|
22
22
|
def decrypt(ciphertext, **options)
|
23
23
|
ciphertext = Base64.decode64(ciphertext) if @encode
|
24
|
-
ciphertext = check_string(ciphertext
|
24
|
+
ciphertext = check_string(ciphertext)
|
25
25
|
|
26
26
|
# ensure binary
|
27
27
|
if ciphertext.encoding != Encoding::BINARY
|
@@ -66,9 +66,10 @@ module Lockbox
|
|
66
66
|
|
67
67
|
private
|
68
68
|
|
69
|
-
def check_string(str
|
69
|
+
def check_string(str)
|
70
70
|
str = str.read if str.respond_to?(:read)
|
71
|
-
|
71
|
+
# Ruby uses "no implicit conversion of Object into String"
|
72
|
+
raise TypeError, "can't convert #{str.class.name} to String" unless str.respond_to?(:to_str)
|
72
73
|
str.to_str
|
73
74
|
end
|
74
75
|
|
@@ -11,7 +11,7 @@ module Lockbox
|
|
11
11
|
raise ArgumentError, "Missing attribute for key generation" if attribute.to_s.empty?
|
12
12
|
|
13
13
|
c = "\xB4"*32
|
14
|
-
hkdf(Lockbox::Utils.decode_key(@master_key), salt: table.to_s, info: "#{c}#{attribute}", length: 32, hash: "sha384")
|
14
|
+
hkdf(Lockbox::Utils.decode_key(@master_key, name: "Master key"), salt: table.to_s, info: "#{c}#{attribute}", length: 32, hash: "sha384")
|
15
15
|
end
|
16
16
|
|
17
17
|
private
|
data/lib/lockbox/migrator.rb
CHANGED
@@ -116,6 +116,13 @@ module Lockbox
|
|
116
116
|
end
|
117
117
|
end
|
118
118
|
|
119
|
+
# there's a small chance for this process to read data,
|
120
|
+
# another process to update the data, and
|
121
|
+
# this process to write the now stale data
|
122
|
+
# this time window can be reduced with smaller batch sizes
|
123
|
+
# locking individual records could eliminate this
|
124
|
+
# one option is: relation.in_batches { |batch| batch.lock }
|
125
|
+
# which runs SELECT ... FOR UPDATE in Postgres
|
119
126
|
def migrate_records(records, fields:, blind_indexes:, restart:, rotate:)
|
120
127
|
# do computation outside of transaction
|
121
128
|
# especially expensive blind index computation
|
data/lib/lockbox/model.rb
CHANGED
@@ -87,7 +87,13 @@ module Lockbox
|
|
87
87
|
# essentially a no-op if already loaded
|
88
88
|
# an exception is thrown if decryption fails
|
89
89
|
self.class.lockbox_attributes.each do |_, lockbox_attribute|
|
90
|
-
|
90
|
+
# don't try to decrypt if no decryption key given
|
91
|
+
next if lockbox_attribute[:algorithm] == "hybrid" && lockbox_attribute[:decryption_key].nil?
|
92
|
+
|
93
|
+
# it is possible that the encrypted attribute is not loaded, eg.
|
94
|
+
# if the record was fetched partially (`User.select(:id).first`).
|
95
|
+
# accessing a not loaded attribute raises an `ActiveModel::MissingAttributeError`.
|
96
|
+
send(lockbox_attribute[:attribute]) if has_attribute?(lockbox_attribute[:encrypted_attribute])
|
91
97
|
end
|
92
98
|
super
|
93
99
|
end
|
@@ -104,6 +110,49 @@ module Lockbox
|
|
104
110
|
end
|
105
111
|
end
|
106
112
|
end
|
113
|
+
|
114
|
+
def update_columns(attributes)
|
115
|
+
return super unless attributes.is_a?(Hash)
|
116
|
+
|
117
|
+
# transform keys like Active Record
|
118
|
+
attributes = attributes.transform_keys do |key|
|
119
|
+
n = key.to_s
|
120
|
+
self.class.attribute_aliases[n] || n
|
121
|
+
end
|
122
|
+
|
123
|
+
lockbox_attributes = self.class.lockbox_attributes.slice(*attributes.keys.map(&:to_sym))
|
124
|
+
return super unless lockbox_attributes.any?
|
125
|
+
|
126
|
+
attributes_to_set = {}
|
127
|
+
|
128
|
+
lockbox_attributes.each do |key, lockbox_attribute|
|
129
|
+
attribute = key.to_s
|
130
|
+
# check read only
|
131
|
+
verify_readonly_attribute(attribute)
|
132
|
+
|
133
|
+
message = attributes[attribute]
|
134
|
+
attributes.delete(attribute) unless lockbox_attribute[:migrating]
|
135
|
+
encrypted_attribute = lockbox_attribute[:encrypted_attribute]
|
136
|
+
ciphertext = self.class.send("generate_#{encrypted_attribute}", message, context: self)
|
137
|
+
attributes[encrypted_attribute] = ciphertext
|
138
|
+
attributes_to_set[attribute] = message
|
139
|
+
attributes_to_set[lockbox_attribute[:attribute]] = message if lockbox_attribute[:migrating]
|
140
|
+
end
|
141
|
+
|
142
|
+
result = super(attributes)
|
143
|
+
|
144
|
+
# same logic as Active Record
|
145
|
+
# (although this happens before saving)
|
146
|
+
attributes_to_set.each do |k, v|
|
147
|
+
if respond_to?(:write_attribute_without_type_cast, true)
|
148
|
+
write_attribute_without_type_cast(k, v)
|
149
|
+
else
|
150
|
+
raw_write_attribute(k, v)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
result
|
155
|
+
end
|
107
156
|
else
|
108
157
|
def reload
|
109
158
|
self.class.lockbox_attributes.each do |_, v|
|
@@ -143,6 +192,10 @@ module Lockbox
|
|
143
192
|
# however, we can try to use the original type if its already defined
|
144
193
|
if attributes_to_define_after_schema_loads.key?(original_name.to_s)
|
145
194
|
attribute name, attributes_to_define_after_schema_loads[original_name.to_s].first
|
195
|
+
elsif options[:migrating]
|
196
|
+
# we use the original attribute for serialization in the encrypt and decrypt methods
|
197
|
+
# so we can use a generic value here
|
198
|
+
attribute name, ActiveRecord::Type::Value.new
|
146
199
|
else
|
147
200
|
attribute name, :string
|
148
201
|
end
|
@@ -213,12 +266,13 @@ module Lockbox
|
|
213
266
|
define_method("#{name}=") do |message|
|
214
267
|
# decrypt first for dirty tracking
|
215
268
|
# don't raise error if can't decrypt previous
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
269
|
+
# don't try to decrypt if no decryption key given
|
270
|
+
unless options[:algorithm] == "hybrid" && options[:decryption_key].nil?
|
271
|
+
begin
|
272
|
+
send(name)
|
273
|
+
rescue Lockbox::DecryptionError
|
274
|
+
warn "[lockbox] Decrypting previous value failed"
|
275
|
+
end
|
222
276
|
end
|
223
277
|
|
224
278
|
send("lockbox_direct_#{name}=", message)
|
@@ -267,7 +321,7 @@ module Lockbox
|
|
267
321
|
# cache
|
268
322
|
# decrypt method does type casting
|
269
323
|
if respond_to?(:write_attribute_without_type_cast, true)
|
270
|
-
write_attribute_without_type_cast(name, message) if !@attributes.frozen?
|
324
|
+
write_attribute_without_type_cast(name.to_s, message) if !@attributes.frozen?
|
271
325
|
else
|
272
326
|
raw_write_attribute(name, message) if !@attributes.frozen?
|
273
327
|
end
|
@@ -316,7 +370,8 @@ module Lockbox
|
|
316
370
|
# do nothing
|
317
371
|
# encrypt will convert to binary
|
318
372
|
else
|
319
|
-
|
373
|
+
# use original name for serialized attributes
|
374
|
+
type = (try(:attribute_types) || {})[original_name.to_s]
|
320
375
|
message = type.serialize(message) if type
|
321
376
|
end
|
322
377
|
end
|
@@ -358,7 +413,8 @@ module Lockbox
|
|
358
413
|
# do nothing
|
359
414
|
# decrypt returns binary string
|
360
415
|
else
|
361
|
-
|
416
|
+
# use original name for serialized attributes
|
417
|
+
type = (try(:attribute_types) || {})[original_name.to_s]
|
362
418
|
message = type.deserialize(message) if type
|
363
419
|
message.force_encoding(Encoding::UTF_8) if !type || type.is_a?(ActiveModel::Type::String)
|
364
420
|
end
|
data/lib/lockbox/utils.rb
CHANGED
@@ -4,22 +4,26 @@ module Lockbox
|
|
4
4
|
options = options.except(:attribute, :encrypted_attribute, :migrating, :attached, :type)
|
5
5
|
options[:encode] = false unless options.key?(:encode)
|
6
6
|
options.each do |k, v|
|
7
|
-
if v.
|
8
|
-
|
7
|
+
if v.respond_to?(:call)
|
8
|
+
# context not present for pluck
|
9
|
+
# still possible to use if not dependent on context
|
10
|
+
options[k] = context ? context.instance_exec(&v) : v.call
|
9
11
|
elsif v.is_a?(Symbol)
|
12
|
+
# context not present for pluck
|
13
|
+
raise Error, "Not available since :#{k} depends on record" unless context
|
10
14
|
options[k] = context.send(v)
|
11
15
|
end
|
12
16
|
end
|
13
17
|
|
14
18
|
unless options[:key] || options[:encryption_key] || options[:decryption_key]
|
15
|
-
options[:key] = Lockbox.attribute_key(table: table, attribute: attribute, master_key: options.delete(:master_key))
|
19
|
+
options[:key] = Lockbox.attribute_key(table: table, attribute: attribute, master_key: options.delete(:master_key), encode: false)
|
16
20
|
end
|
17
21
|
|
18
22
|
if options[:previous_versions].is_a?(Array)
|
19
23
|
options[:previous_versions] = options[:previous_versions].dup
|
20
24
|
options[:previous_versions].each_with_index do |version, i|
|
21
25
|
if !(version[:key] || version[:encryption_key] || version[:decryption_key]) && version[:master_key]
|
22
|
-
options[:previous_versions][i] = version.merge(key: Lockbox.attribute_key(table: table, attribute: attribute, master_key: version.delete(:master_key)))
|
26
|
+
options[:previous_versions][i] = version.merge(key: Lockbox.attribute_key(table: table, attribute: attribute, master_key: version.delete(:master_key), encode: false))
|
23
27
|
end
|
24
28
|
end
|
25
29
|
end
|
@@ -31,13 +35,13 @@ module Lockbox
|
|
31
35
|
record.class.respond_to?(:lockbox_attachments) ? record.class.lockbox_attachments[name.to_sym] : nil
|
32
36
|
end
|
33
37
|
|
34
|
-
def self.decode_key(key, size: 32)
|
38
|
+
def self.decode_key(key, size: 32, name: "Key")
|
35
39
|
if key.encoding != Encoding::BINARY && key =~ /\A[0-9a-f]{#{size * 2}}\z/i
|
36
40
|
key = [key].pack("H*")
|
37
41
|
end
|
38
42
|
|
39
|
-
raise Lockbox::Error, "
|
40
|
-
raise Lockbox::Error, "
|
43
|
+
raise Lockbox::Error, "#{name} must be 32 bytes (64 hex digits)" if key.bytesize != size
|
44
|
+
raise Lockbox::Error, "#{name} must use binary encoding" if key.encoding != Encoding::BINARY
|
41
45
|
|
42
46
|
key
|
43
47
|
end
|
data/lib/lockbox/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lockbox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -173,6 +173,7 @@ files:
|
|
173
173
|
- CHANGELOG.md
|
174
174
|
- LICENSE.txt
|
175
175
|
- README.md
|
176
|
+
- SECURITY.md
|
176
177
|
- lib/generators/lockbox/audits_generator.rb
|
177
178
|
- lib/generators/lockbox/templates/migration.rb.tt
|
178
179
|
- lib/generators/lockbox/templates/model.rb.tt
|
@@ -180,6 +181,7 @@ files:
|
|
180
181
|
- lib/lockbox/active_storage_extensions.rb
|
181
182
|
- lib/lockbox/aes_gcm.rb
|
182
183
|
- lib/lockbox/box.rb
|
184
|
+
- lib/lockbox/calculations.rb
|
183
185
|
- lib/lockbox/carrier_wave_extensions.rb
|
184
186
|
- lib/lockbox/encryptor.rb
|
185
187
|
- lib/lockbox/io.rb
|