lockbox 0.4.9 → 0.6.3
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 +30 -0
- data/LICENSE.txt +1 -1
- data/README.md +119 -42
- data/lib/generators/lockbox/audits_generator.rb +11 -3
- data/lib/lockbox.rb +6 -0
- data/lib/lockbox/active_storage_extensions.rb +4 -0
- data/lib/lockbox/aes_gcm.rb +4 -5
- data/lib/lockbox/box.rb +3 -4
- data/lib/lockbox/calculations.rb +2 -1
- data/lib/lockbox/carrier_wave_extensions.rb +17 -2
- data/lib/lockbox/model.rb +90 -14
- data/lib/lockbox/railtie.rb +13 -1
- data/lib/lockbox/utils.rb +8 -7
- data/lib/lockbox/version.rb +1 -1
- metadata +10 -164
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fbcd16c017fd2282888d124ab9d82029b92405bdaca39034ab4e52ee5eb92010
|
|
4
|
+
data.tar.gz: a54398f0e0acbd1543be296b4ca0214a3b6ca120dc5d89a5f488ac160309ea6b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c1487a899e37749915555456b59ddb23b75bdc95ce8d8598750e13873c1226de7b7183d3306cfef3e3190ec52275ad452c25ac7c1aab7585a05630c1d312268c
|
|
7
|
+
data.tar.gz: 4686137e29c935e5f7f141c8239332f89d14764a94a70ee6160f138aa29cc1ce496a0fe384965c0d77ba795700fb58a4ed86feabf5318e25434b86ee0df47153
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
## 0.6.3 (2021-03-30)
|
|
2
|
+
|
|
3
|
+
- Fixed empty arrays and hashes
|
|
4
|
+
- Fixed content type for CarrierWave 2.2.1
|
|
5
|
+
|
|
6
|
+
## 0.6.2 (2021-02-08)
|
|
7
|
+
|
|
8
|
+
- Added `inet` type
|
|
9
|
+
- Fixed error when `lockbox` key in Rails credentials has a string value
|
|
10
|
+
- Fixed deprecation warning with Active Record 6.1
|
|
11
|
+
|
|
12
|
+
## 0.6.1 (2020-12-03)
|
|
13
|
+
|
|
14
|
+
- Added integration with Rails credentials
|
|
15
|
+
- Fixed in place changes for Active Record 6.1
|
|
16
|
+
- Fixed error with `content_type` method for CarrierWave < 2
|
|
17
|
+
|
|
18
|
+
## 0.6.0 (2020-12-03)
|
|
19
|
+
|
|
20
|
+
- Added `encrypted` flag to Active Storage metadata
|
|
21
|
+
- Added encrypted columns to `filter_attributes`
|
|
22
|
+
- Improved `inspect` method
|
|
23
|
+
|
|
24
|
+
## 0.5.0 (2020-11-22)
|
|
25
|
+
|
|
26
|
+
- Improved error messages for hybrid cryptography
|
|
27
|
+
- Changed warning to error when no attributes specified
|
|
28
|
+
- Fixed issue with `pluck` when migrating
|
|
29
|
+
- Fixed error with `key_table` and `key_attribute` options with `previous_versions`
|
|
30
|
+
|
|
1
31
|
## 0.4.9 (2020-10-01)
|
|
2
32
|
|
|
3
33
|
- Added `key_table` and `key_attribute` options to `previous_versions`
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# Lockbox
|
|
2
2
|
|
|
3
|
-
:package: Modern encryption for Rails
|
|
3
|
+
:package: Modern encryption for Ruby and Rails
|
|
4
4
|
|
|
5
5
|
- Works with database fields, files, and strings
|
|
6
6
|
- Maximizes compatibility with existing code and libraries
|
|
7
7
|
- Makes migrating existing data and key rotation easy
|
|
8
|
+
- Has zero dependencies and many integrations
|
|
8
9
|
|
|
9
10
|
Learn [the principles behind it](https://ankane.org/modern-encryption-rails), [how to secure emails with Devise](https://ankane.org/securing-user-emails-lockbox), and [how to secure sensitive data in Rails](https://ankane.org/sensitive-data-rails).
|
|
10
11
|
|
|
11
|
-
[](https://github.com/ankane/lockbox/actions)
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
14
15
|
|
|
@@ -26,7 +27,7 @@ Generate a key
|
|
|
26
27
|
Lockbox.generate_key
|
|
27
28
|
```
|
|
28
29
|
|
|
29
|
-
Store the key with your other secrets. This is typically Rails credentials or an environment variable ([dotenv](https://github.com/bkeepers/dotenv) is great for this). Be sure to use different keys in development and production.
|
|
30
|
+
Store the key with your other secrets. This is typically Rails credentials or an environment variable ([dotenv](https://github.com/bkeepers/dotenv) is great for this). Be sure to use different keys in development and production.
|
|
30
31
|
|
|
31
32
|
Set the following environment variable with your key (you can use this one in development)
|
|
32
33
|
|
|
@@ -34,10 +35,17 @@ Set the following environment variable with your key (you can use this one in de
|
|
|
34
35
|
LOCKBOX_MASTER_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
|
35
36
|
```
|
|
36
37
|
|
|
38
|
+
or add it to your credentials for each environment (`rails credentials:edit --environment <env>` for Rails 6+)
|
|
39
|
+
|
|
40
|
+
```yml
|
|
41
|
+
lockbox:
|
|
42
|
+
master_key: "0000000000000000000000000000000000000000000000000000000000000000"
|
|
43
|
+
```
|
|
44
|
+
|
|
37
45
|
or create `config/initializers/lockbox.rb` with something like
|
|
38
46
|
|
|
39
47
|
```ruby
|
|
40
|
-
Lockbox.master_key = Rails.application.credentials.
|
|
48
|
+
Lockbox.master_key = Rails.application.credentials.lockbox[:master_key]
|
|
41
49
|
```
|
|
42
50
|
|
|
43
51
|
Then follow the instructions below for the data you want to encrypt.
|
|
@@ -64,7 +72,7 @@ Then follow the instructions below for the data you want to encrypt.
|
|
|
64
72
|
Create a migration with:
|
|
65
73
|
|
|
66
74
|
```ruby
|
|
67
|
-
class AddEmailCiphertextToUsers < ActiveRecord::Migration[6.
|
|
75
|
+
class AddEmailCiphertextToUsers < ActiveRecord::Migration[6.1]
|
|
68
76
|
def change
|
|
69
77
|
add_column :users, :email_ciphertext, :text
|
|
70
78
|
end
|
|
@@ -113,6 +121,7 @@ class User < ApplicationRecord
|
|
|
113
121
|
encrypts :properties, type: :json
|
|
114
122
|
encrypts :settings, type: :hash
|
|
115
123
|
encrypts :messages, type: :array
|
|
124
|
+
encrypts :ip, type: :inet
|
|
116
125
|
end
|
|
117
126
|
```
|
|
118
127
|
|
|
@@ -196,6 +205,34 @@ class User < ApplicationRecord
|
|
|
196
205
|
end
|
|
197
206
|
```
|
|
198
207
|
|
|
208
|
+
#### Model Changes
|
|
209
|
+
|
|
210
|
+
If tracking changes to model attributes, be sure to remove or redact encrypted attributes.
|
|
211
|
+
|
|
212
|
+
PaperTrail
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
class User < ApplicationRecord
|
|
216
|
+
# for an encrypted history (still tracks ciphertext changes)
|
|
217
|
+
has_paper_trail skip: [:email]
|
|
218
|
+
|
|
219
|
+
# for no history (add blind indexes as well)
|
|
220
|
+
has_paper_trail skip: [:email, :email_ciphertext]
|
|
221
|
+
end
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Audited
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
class User < ApplicationRecord
|
|
228
|
+
# for an encrypted history (still tracks ciphertext changes)
|
|
229
|
+
audited except: [:email]
|
|
230
|
+
|
|
231
|
+
# for no history (add blind indexes as well)
|
|
232
|
+
audited except: [:email, :email_ciphertext]
|
|
233
|
+
end
|
|
234
|
+
```
|
|
235
|
+
|
|
199
236
|
#### Decryption
|
|
200
237
|
|
|
201
238
|
To decrypt data outside the model, use:
|
|
@@ -211,7 +248,7 @@ User.decrypt_email_ciphertext(user.email_ciphertext)
|
|
|
211
248
|
Create a migration with:
|
|
212
249
|
|
|
213
250
|
```ruby
|
|
214
|
-
class AddBodyCiphertextToRichTexts < ActiveRecord::Migration[6.
|
|
251
|
+
class AddBodyCiphertextToRichTexts < ActiveRecord::Migration[6.1]
|
|
215
252
|
def change
|
|
216
253
|
add_column :action_text_rich_texts, :body_ciphertext, :text
|
|
217
254
|
end
|
|
@@ -342,7 +379,7 @@ Encryption is applied to all versions after processing.
|
|
|
342
379
|
You can mount the uploader [as normal](https://github.com/carrierwaveuploader/carrierwave#activerecord). With Active Record, this involves creating a migration:
|
|
343
380
|
|
|
344
381
|
```ruby
|
|
345
|
-
class AddLicenseToUsers < ActiveRecord::Migration[6.
|
|
382
|
+
class AddLicenseToUsers < ActiveRecord::Migration[6.1]
|
|
346
383
|
def change
|
|
347
384
|
add_column :users, :license, :string
|
|
348
385
|
end
|
|
@@ -413,44 +450,58 @@ Finally, delete the unencrypted files and drop the column for the original uploa
|
|
|
413
450
|
|
|
414
451
|
## Shrine
|
|
415
452
|
|
|
416
|
-
|
|
453
|
+
#### Models
|
|
454
|
+
|
|
455
|
+
Include the attachment as normal:
|
|
417
456
|
|
|
418
457
|
```ruby
|
|
419
|
-
|
|
458
|
+
class User < ApplicationRecord
|
|
459
|
+
include LicenseUploader::Attachment(:license)
|
|
460
|
+
end
|
|
420
461
|
```
|
|
421
462
|
|
|
422
|
-
|
|
463
|
+
And encrypt in a controller (or background job, etc) with:
|
|
423
464
|
|
|
424
465
|
```ruby
|
|
425
|
-
|
|
466
|
+
license = params.require(:user).fetch(:license)
|
|
467
|
+
lockbox = Lockbox.new(key: Lockbox.attribute_key(table: "users", attribute: "license"))
|
|
468
|
+
user.license = lockbox.encrypt_io(license)
|
|
426
469
|
```
|
|
427
470
|
|
|
428
|
-
|
|
471
|
+
To serve encrypted files, use a controller action.
|
|
429
472
|
|
|
430
473
|
```ruby
|
|
431
|
-
|
|
474
|
+
def license
|
|
475
|
+
user = User.find(params[:id])
|
|
476
|
+
lockbox = Lockbox.new(key: Lockbox.attribute_key(table: "users", attribute: "license"))
|
|
477
|
+
send_data lockbox.decrypt(user.license.read), type: user.license.mime_type
|
|
478
|
+
end
|
|
432
479
|
```
|
|
433
480
|
|
|
434
|
-
|
|
481
|
+
#### Non-Models
|
|
482
|
+
|
|
483
|
+
Generate a key
|
|
435
484
|
|
|
436
485
|
```ruby
|
|
437
|
-
|
|
486
|
+
key = Lockbox.generate_key
|
|
438
487
|
```
|
|
439
488
|
|
|
440
|
-
|
|
489
|
+
Create a lockbox
|
|
441
490
|
|
|
442
491
|
```ruby
|
|
443
|
-
|
|
444
|
-
user.license = lockbox.encrypt_io(license)
|
|
492
|
+
lockbox = Lockbox.new(key: key)
|
|
445
493
|
```
|
|
446
494
|
|
|
447
|
-
|
|
495
|
+
Encrypt files before passing them to Shrine
|
|
448
496
|
|
|
449
497
|
```ruby
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
498
|
+
LicenseUploader.upload(lockbox.encrypt_io(file), :store)
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
And decrypt them after reading
|
|
502
|
+
|
|
503
|
+
```ruby
|
|
504
|
+
lockbox.decrypt(uploaded_file.read)
|
|
454
505
|
```
|
|
455
506
|
|
|
456
507
|
## Local Files
|
|
@@ -517,12 +568,10 @@ Update your model:
|
|
|
517
568
|
|
|
518
569
|
```ruby
|
|
519
570
|
class User < ApplicationRecord
|
|
520
|
-
encrypts :email, previous_versions: [{
|
|
571
|
+
encrypts :email, previous_versions: [{master_key: previous_key}]
|
|
521
572
|
end
|
|
522
573
|
```
|
|
523
574
|
|
|
524
|
-
Use `master_key` instead of `key` if passing the master key.
|
|
525
|
-
|
|
526
575
|
To rotate existing records, use:
|
|
527
576
|
|
|
528
577
|
```ruby
|
|
@@ -536,11 +585,9 @@ Once all records are rotated, you can remove `previous_versions` from the model.
|
|
|
536
585
|
Update your initializer:
|
|
537
586
|
|
|
538
587
|
```ruby
|
|
539
|
-
Lockbox.encrypts_action_text_body(previous_versions: [{
|
|
588
|
+
Lockbox.encrypts_action_text_body(previous_versions: [{master_key: previous_key}])
|
|
540
589
|
```
|
|
541
590
|
|
|
542
|
-
Use `master_key` instead of `key` if passing the master key.
|
|
543
|
-
|
|
544
591
|
To rotate existing records, use:
|
|
545
592
|
|
|
546
593
|
```ruby
|
|
@@ -555,12 +602,10 @@ Update your model:
|
|
|
555
602
|
|
|
556
603
|
```ruby
|
|
557
604
|
class User < ApplicationRecord
|
|
558
|
-
encrypts_attached :license, previous_versions: [{
|
|
605
|
+
encrypts_attached :license, previous_versions: [{master_key: previous_key}]
|
|
559
606
|
end
|
|
560
607
|
```
|
|
561
608
|
|
|
562
|
-
Use `master_key` instead of `key` if passing the master key.
|
|
563
|
-
|
|
564
609
|
To rotate existing files, use:
|
|
565
610
|
|
|
566
611
|
```ruby
|
|
@@ -577,12 +622,10 @@ Update your model:
|
|
|
577
622
|
|
|
578
623
|
```ruby
|
|
579
624
|
class LicenseUploader < CarrierWave::Uploader::Base
|
|
580
|
-
encrypt previous_versions: [{
|
|
625
|
+
encrypt previous_versions: [{master_key: previous_key}]
|
|
581
626
|
end
|
|
582
627
|
```
|
|
583
628
|
|
|
584
|
-
Use `master_key` instead of `key` if passing the master key.
|
|
585
|
-
|
|
586
629
|
To rotate existing files, use:
|
|
587
630
|
|
|
588
631
|
```ruby
|
|
@@ -655,7 +698,9 @@ This is the default algorithm. It’s:
|
|
|
655
698
|
- an IETF standard
|
|
656
699
|
- fast thanks to a [dedicated instruction set](https://en.wikipedia.org/wiki/AES_instruction_set)
|
|
657
700
|
|
|
658
|
-
|
|
701
|
+
Lockbox uses 256-bit keys.
|
|
702
|
+
|
|
703
|
+
**For users who do a lot of encryptions:** You should rotate an individual key after 2 billion encryptions to minimize the chance of a [nonce collision](https://www.cryptologie.net/article/402/is-symmetric-security-solved/), which will expose the authentication key. Each database field and file uploader use a different key (derived from the master key) to extend this window.
|
|
659
704
|
|
|
660
705
|
### XSalsa20
|
|
661
706
|
|
|
@@ -708,6 +753,22 @@ For Ubuntu 16.04, use:
|
|
|
708
753
|
sudo apt-get install libsodium18
|
|
709
754
|
```
|
|
710
755
|
|
|
756
|
+
##### GitHub Actions
|
|
757
|
+
|
|
758
|
+
For Ubuntu 20.04 and 18.04, use:
|
|
759
|
+
|
|
760
|
+
```yml
|
|
761
|
+
- name: Install Libsodium
|
|
762
|
+
run: sudo apt-get update && sudo apt-get install libsodium23
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
For Ubuntu 16.04, use:
|
|
766
|
+
|
|
767
|
+
```yml
|
|
768
|
+
- name: Install Libsodium
|
|
769
|
+
run: sudo apt-get update && sudo apt-get install libsodium18
|
|
770
|
+
```
|
|
771
|
+
|
|
711
772
|
##### Travis CI
|
|
712
773
|
|
|
713
774
|
On Bionic, add to `.travis.yml`:
|
|
@@ -735,8 +796,7 @@ Add a step to `.circleci/config.yml`:
|
|
|
735
796
|
```yml
|
|
736
797
|
- run:
|
|
737
798
|
name: install Libsodium
|
|
738
|
-
command:
|
|
739
|
-
sudo apt-get install -y libsodium18
|
|
799
|
+
command: sudo apt-get install -y libsodium18
|
|
740
800
|
```
|
|
741
801
|
|
|
742
802
|
## Hybrid Cryptography
|
|
@@ -929,7 +989,7 @@ lockbox.decrypt(ciphertext, associated_data: "othercontext") # fails
|
|
|
929
989
|
You can use `binary` columns for the ciphertext instead of `text` columns.
|
|
930
990
|
|
|
931
991
|
```ruby
|
|
932
|
-
class AddEmailCiphertextToUsers < ActiveRecord::Migration[6.
|
|
992
|
+
class AddEmailCiphertextToUsers < ActiveRecord::Migration[6.1]
|
|
933
993
|
def change
|
|
934
994
|
add_column :users, :email_ciphertext, :binary
|
|
935
995
|
end
|
|
@@ -974,7 +1034,7 @@ end
|
|
|
974
1034
|
Create a migration with:
|
|
975
1035
|
|
|
976
1036
|
```ruby
|
|
977
|
-
class MigrateToLockbox < ActiveRecord::Migration[6.
|
|
1037
|
+
class MigrateToLockbox < ActiveRecord::Migration[6.1]
|
|
978
1038
|
def change
|
|
979
1039
|
add_column :users, :name_ciphertext, :text
|
|
980
1040
|
add_column :users, :email_ciphertext, :text
|
|
@@ -1007,7 +1067,7 @@ end
|
|
|
1007
1067
|
Then remove the previous gem from your Gemfile and drop its columns.
|
|
1008
1068
|
|
|
1009
1069
|
```ruby
|
|
1010
|
-
class RemovePreviousEncryptedColumns < ActiveRecord::Migration[6.
|
|
1070
|
+
class RemovePreviousEncryptedColumns < ActiveRecord::Migration[6.1]
|
|
1011
1071
|
def change
|
|
1012
1072
|
remove_column :users, :encrypted_name, :text
|
|
1013
1073
|
remove_column :users, :encrypted_name_iv, :text
|
|
@@ -1019,12 +1079,29 @@ end
|
|
|
1019
1079
|
|
|
1020
1080
|
## Upgrading
|
|
1021
1081
|
|
|
1082
|
+
### 0.6.0
|
|
1083
|
+
|
|
1084
|
+
0.6.0 adds `encrypted: true` to Active Storage metadata for new files. This field is informational, but if you prefer to add it to existing files, use:
|
|
1085
|
+
|
|
1086
|
+
```ruby
|
|
1087
|
+
User.with_attached_license.find_each do |user|
|
|
1088
|
+
next unless user.license.attached?
|
|
1089
|
+
|
|
1090
|
+
metadata = user.license.metadata
|
|
1091
|
+
unless metadata["encrypted"]
|
|
1092
|
+
user.license.blob.update!(metadata: metadata.merge("encrypted" => true))
|
|
1093
|
+
end
|
|
1094
|
+
end
|
|
1095
|
+
```
|
|
1096
|
+
|
|
1022
1097
|
### 0.3.6
|
|
1023
1098
|
|
|
1024
1099
|
0.3.6 makes content type detection more reliable for Active Storage. You can check and update the content type of existing files with:
|
|
1025
1100
|
|
|
1026
1101
|
```ruby
|
|
1027
|
-
User.find_each do |user|
|
|
1102
|
+
User.with_attached_license.find_each do |user|
|
|
1103
|
+
next unless user.license.attached?
|
|
1104
|
+
|
|
1028
1105
|
license = user.license
|
|
1029
1106
|
content_type = Marcel::MimeType.for(license.download, name: license.filename.to_s)
|
|
1030
1107
|
if content_type != license.content_type
|
|
@@ -16,9 +16,7 @@ module Lockbox
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def data_type
|
|
19
|
-
|
|
20
|
-
# so database connection isn't needed
|
|
21
|
-
case ActiveRecord::Base.connection_config[:adapter].to_s
|
|
19
|
+
case adapter
|
|
22
20
|
when /postg/i # postgres, postgis
|
|
23
21
|
"jsonb"
|
|
24
22
|
when /mysql/i
|
|
@@ -27,6 +25,16 @@ module Lockbox
|
|
|
27
25
|
"text"
|
|
28
26
|
end
|
|
29
27
|
end
|
|
28
|
+
|
|
29
|
+
# use connection_config instead of connection.adapter
|
|
30
|
+
# so database connection isn't needed
|
|
31
|
+
def adapter
|
|
32
|
+
if ActiveRecord::VERSION::STRING.to_f >= 6.1
|
|
33
|
+
ActiveRecord::Base.connection_db_config.adapter.to_s
|
|
34
|
+
else
|
|
35
|
+
ActiveRecord::Base.connection_config[:adapter].to_s
|
|
36
|
+
end
|
|
37
|
+
end
|
|
30
38
|
end
|
|
31
39
|
end
|
|
32
40
|
end
|
data/lib/lockbox.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "openssl"
|
|
|
4
4
|
require "securerandom"
|
|
5
5
|
|
|
6
6
|
# modules
|
|
7
|
+
require "lockbox/aes_gcm"
|
|
7
8
|
require "lockbox/box"
|
|
8
9
|
require "lockbox/calculations"
|
|
9
10
|
require "lockbox/encryptor"
|
|
@@ -26,6 +27,11 @@ end
|
|
|
26
27
|
|
|
27
28
|
if defined?(ActiveSupport.on_load)
|
|
28
29
|
ActiveSupport.on_load(:active_record) do
|
|
30
|
+
# TODO raise error in 0.7.0
|
|
31
|
+
if ActiveRecord::VERSION::STRING.to_f <= 5.0
|
|
32
|
+
warn "Active Record version (#{ActiveRecord::VERSION::STRING}) not supported in this version of Lockbox (#{Lockbox::VERSION})"
|
|
33
|
+
end
|
|
34
|
+
|
|
29
35
|
extend Lockbox::Model
|
|
30
36
|
extend Lockbox::Model::Attached
|
|
31
37
|
ActiveRecord::Calculations.prepend Lockbox::Calculations
|
|
@@ -89,6 +89,10 @@ module Lockbox
|
|
|
89
89
|
module CreateOne
|
|
90
90
|
def initialize(name, record, attachable)
|
|
91
91
|
# this won't encrypt existing blobs
|
|
92
|
+
# ideally we'd check metadata for the encrypted flag
|
|
93
|
+
# and disallow unencrypted blobs
|
|
94
|
+
# since they'll raise an error on decryption
|
|
95
|
+
# but earlier versions of Lockbox won't have it
|
|
92
96
|
attachable = Lockbox::Utils.encrypt_attachable(record, name, attachable) if Lockbox::Utils.encrypted?(record, name) && !attachable.is_a?(ActiveStorage::Blob)
|
|
93
97
|
super(name, record, attachable)
|
|
94
98
|
end
|
data/lib/lockbox/aes_gcm.rb
CHANGED
|
@@ -43,11 +43,10 @@ module Lockbox
|
|
|
43
43
|
cipher.auth_data = associated_data || ""
|
|
44
44
|
|
|
45
45
|
begin
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
end
|
|
46
|
+
message = String.new
|
|
47
|
+
message << cipher.update(ciphertext) unless ciphertext.to_s.empty?
|
|
48
|
+
message << cipher.final
|
|
49
|
+
message
|
|
51
50
|
rescue OpenSSL::Cipher::CipherError
|
|
52
51
|
fail_decryption
|
|
53
52
|
end
|
data/lib/lockbox/box.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Lockbox
|
|
2
2
|
class Box
|
|
3
3
|
def initialize(key: nil, algorithm: nil, encryption_key: nil, decryption_key: nil, padding: false)
|
|
4
|
-
raise ArgumentError, "Cannot pass both key and
|
|
4
|
+
raise ArgumentError, "Cannot pass both key and encryption/decryption key" if key && (encryption_key || decryption_key)
|
|
5
5
|
|
|
6
6
|
key = Lockbox::Utils.decode_key(key) if key
|
|
7
7
|
encryption_key = Lockbox::Utils.decode_key(encryption_key, size: 64) if encryption_key
|
|
@@ -12,7 +12,6 @@ module Lockbox
|
|
|
12
12
|
case algorithm
|
|
13
13
|
when "aes-gcm"
|
|
14
14
|
raise ArgumentError, "Missing key" unless key
|
|
15
|
-
require "lockbox/aes_gcm"
|
|
16
15
|
@box = AES_GCM.new(key)
|
|
17
16
|
when "xchacha20"
|
|
18
17
|
raise ArgumentError, "Missing key" unless key
|
|
@@ -39,7 +38,7 @@ module Lockbox
|
|
|
39
38
|
message = Lockbox.pad(message, size: @padding) if @padding
|
|
40
39
|
case @algorithm
|
|
41
40
|
when "hybrid"
|
|
42
|
-
raise ArgumentError, "No
|
|
41
|
+
raise ArgumentError, "No encryption key set" unless defined?(@encryption_box)
|
|
43
42
|
raise ArgumentError, "Associated data not supported with this algorithm" if associated_data
|
|
44
43
|
nonce = generate_nonce(@encryption_box)
|
|
45
44
|
ciphertext = @encryption_box.encrypt(nonce, message)
|
|
@@ -58,7 +57,7 @@ module Lockbox
|
|
|
58
57
|
message =
|
|
59
58
|
case @algorithm
|
|
60
59
|
when "hybrid"
|
|
61
|
-
raise ArgumentError, "No
|
|
60
|
+
raise ArgumentError, "No decryption key set" unless defined?(@decryption_box)
|
|
62
61
|
raise ArgumentError, "Associated data not supported with this algorithm" if associated_data
|
|
63
62
|
nonce, ciphertext = extract_nonce(@decryption_box, ciphertext)
|
|
64
63
|
@decryption_box.decrypt(nonce, ciphertext)
|
data/lib/lockbox/calculations.rb
CHANGED
|
@@ -3,7 +3,8 @@ module Lockbox
|
|
|
3
3
|
def pluck(*column_names)
|
|
4
4
|
return super unless model.respond_to?(:lockbox_attributes)
|
|
5
5
|
|
|
6
|
-
lockbox_columns = column_names.map.with_index { |c, i| [model.lockbox_attributes[c.to_sym], i] }.select
|
|
6
|
+
lockbox_columns = column_names.map.with_index { |c, i| [model.lockbox_attributes[c.to_sym], i] }.select { |la, _i| la && !la[:migrating] }
|
|
7
|
+
|
|
7
8
|
return super unless lockbox_columns.any?
|
|
8
9
|
|
|
9
10
|
# replace column with ciphertext column
|
|
@@ -32,9 +32,17 @@ module Lockbox
|
|
|
32
32
|
read.bytesize
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
# based on CarrierWave::SanitizedFile#mime_magic_content_type
|
|
36
35
|
def content_type
|
|
37
|
-
|
|
36
|
+
if Gem::Version.new(CarrierWave::VERSION) >= Gem::Version.new("2.2.1")
|
|
37
|
+
# based on CarrierWave::SanitizedFile#marcel_magic_content_type
|
|
38
|
+
Marcel::Magic.by_magic(read).try(:type) || "invalid/invalid"
|
|
39
|
+
elsif CarrierWave::VERSION.to_i >= 2
|
|
40
|
+
# based on CarrierWave::SanitizedFile#mime_magic_content_type
|
|
41
|
+
MimeMagic.by_magic(read).try(:type) || "invalid/invalid"
|
|
42
|
+
else
|
|
43
|
+
# uses filename
|
|
44
|
+
super
|
|
45
|
+
end
|
|
38
46
|
end
|
|
39
47
|
|
|
40
48
|
# disable processing since already processed
|
|
@@ -97,4 +105,11 @@ module Lockbox
|
|
|
97
105
|
end
|
|
98
106
|
end
|
|
99
107
|
|
|
108
|
+
if CarrierWave::VERSION.to_i > 2
|
|
109
|
+
raise "CarrierWave version (#{CarrierWave::VERSION}) not supported in this version of Lockbox (#{Lockbox::VERSION})"
|
|
110
|
+
elsif CarrierWave::VERSION.to_i < 1
|
|
111
|
+
# TODO raise error in 0.7.0
|
|
112
|
+
warn "CarrierWave version (#{CarrierWave::VERSION}) not supported in this version of Lockbox (#{Lockbox::VERSION})"
|
|
113
|
+
end
|
|
114
|
+
|
|
100
115
|
CarrierWave::Uploader::Base.extend(Lockbox::CarrierWaveExtensions)
|
data/lib/lockbox/model.rb
CHANGED
|
@@ -22,13 +22,13 @@ module Lockbox
|
|
|
22
22
|
# end
|
|
23
23
|
|
|
24
24
|
custom_type = options[:type].respond_to?(:serialize) && options[:type].respond_to?(:deserialize)
|
|
25
|
-
|
|
25
|
+
valid_types = [nil, :string, :boolean, :date, :datetime, :time, :integer, :float, :binary, :json, :hash, :array, :inet]
|
|
26
|
+
raise ArgumentError, "Unknown type: #{options[:type]}" unless custom_type || valid_types.include?(options[:type])
|
|
26
27
|
|
|
27
28
|
activerecord = defined?(ActiveRecord::Base) && self < ActiveRecord::Base
|
|
28
29
|
raise ArgumentError, "Type not supported yet with Mongoid" if options[:type] && !activerecord
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
warn "[lockbox] WARNING: No attributes specified" if attributes.empty?
|
|
31
|
+
raise ArgumentError, "No attributes specified" if attributes.empty?
|
|
32
32
|
|
|
33
33
|
raise ArgumentError, "Cannot use key_attribute with multiple attributes" if options[:key_attribute] && attributes.size > 1
|
|
34
34
|
|
|
@@ -56,6 +56,15 @@ module Lockbox
|
|
|
56
56
|
decrypt_method_name = "decrypt_#{encrypted_attribute}"
|
|
57
57
|
|
|
58
58
|
class_eval do
|
|
59
|
+
# Lockbox uses custom inspect
|
|
60
|
+
# but this could be useful for other gems
|
|
61
|
+
if activerecord && ActiveRecord::VERSION::MAJOR >= 6
|
|
62
|
+
# only add virtual attribute
|
|
63
|
+
# need to use regexp since strings do partial matching
|
|
64
|
+
# also, need to use += instead of <<
|
|
65
|
+
self.filter_attributes += [/\A#{Regexp.escape(options[:attribute])}\z/]
|
|
66
|
+
end
|
|
67
|
+
|
|
59
68
|
@lockbox_attributes ||= {}
|
|
60
69
|
|
|
61
70
|
if @lockbox_attributes.empty?
|
|
@@ -80,15 +89,40 @@ module Lockbox
|
|
|
80
89
|
super(options)
|
|
81
90
|
end
|
|
82
91
|
|
|
83
|
-
#
|
|
92
|
+
# maintain order
|
|
93
|
+
# replace ciphertext attributes w/ virtual attributes (filtered)
|
|
84
94
|
def inspect
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
lockbox_attributes = {}
|
|
96
|
+
lockbox_encrypted_attributes = {}
|
|
97
|
+
self.class.lockbox_attributes.each do |_, lockbox_attribute|
|
|
98
|
+
lockbox_attributes[lockbox_attribute[:attribute]] = true
|
|
99
|
+
lockbox_encrypted_attributes[lockbox_attribute[:encrypted_attribute]] = lockbox_attribute[:attribute]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
inspection = []
|
|
103
|
+
# use serializable_hash like Devise
|
|
104
|
+
values = serializable_hash
|
|
105
|
+
self.class.attribute_names.each do |k|
|
|
106
|
+
next if !has_attribute?(k) || lockbox_attributes[k]
|
|
107
|
+
|
|
108
|
+
# check for lockbox attribute
|
|
109
|
+
if lockbox_encrypted_attributes[k]
|
|
110
|
+
# check if ciphertext attribute nil to avoid loading attribute
|
|
111
|
+
v = send(k).nil? ? "nil" : "[FILTERED]"
|
|
112
|
+
k = lockbox_encrypted_attributes[k]
|
|
113
|
+
elsif values.key?(k)
|
|
114
|
+
v = respond_to?(:attribute_for_inspect) ? attribute_for_inspect(k) : values[k].inspect
|
|
115
|
+
|
|
116
|
+
# fix for https://github.com/rails/rails/issues/40725
|
|
117
|
+
# TODO only apply to Active Record 6.0
|
|
118
|
+
if respond_to?(:inspection_filter, true) && v != "nil"
|
|
119
|
+
v = inspection_filter.filter_param(k, v)
|
|
120
|
+
end
|
|
121
|
+
else
|
|
122
|
+
next
|
|
88
123
|
end
|
|
89
124
|
|
|
90
|
-
|
|
91
|
-
inspection << "#{lockbox_attribute[:attribute]}: [FILTERED]" if has_attribute?(lockbox_attribute[:encrypted_attribute])
|
|
125
|
+
inspection << "#{k}: #{v}"
|
|
92
126
|
end
|
|
93
127
|
|
|
94
128
|
"#<#{self.class} #{inspection.join(", ")}>"
|
|
@@ -160,8 +194,11 @@ module Lockbox
|
|
|
160
194
|
attributes_to_set.each do |k, v|
|
|
161
195
|
if respond_to?(:write_attribute_without_type_cast, true)
|
|
162
196
|
write_attribute_without_type_cast(k, v)
|
|
163
|
-
|
|
197
|
+
elsif respond_to?(:raw_write_attribute, true)
|
|
164
198
|
raw_write_attribute(k, v)
|
|
199
|
+
else
|
|
200
|
+
@attributes.write_cast_value(k, v)
|
|
201
|
+
clear_attribute_change(k)
|
|
165
202
|
end
|
|
166
203
|
end
|
|
167
204
|
|
|
@@ -214,6 +251,23 @@ module Lockbox
|
|
|
214
251
|
else
|
|
215
252
|
attribute name, :string
|
|
216
253
|
end
|
|
254
|
+
else
|
|
255
|
+
# hack for Active Record 6.1
|
|
256
|
+
# to set string type after serialize
|
|
257
|
+
# otherwise, type gets set to ActiveModel::Type::Value
|
|
258
|
+
# which always returns false for changed_in_place?
|
|
259
|
+
# earlier versions of Active Record take the previous code path
|
|
260
|
+
if ActiveRecord::VERSION::STRING.to_f >= 7.0 && attributes_to_define_after_schema_loads[name.to_s].first.is_a?(Proc)
|
|
261
|
+
attribute_type = attributes_to_define_after_schema_loads[name.to_s].first.call(nil)
|
|
262
|
+
if attribute_type.is_a?(ActiveRecord::Type::Serialized) && attribute_type.subtype.nil?
|
|
263
|
+
attribute name, ActiveRecord::Type::Serialized.new(ActiveRecord::Type::String.new, attribute_type.coder)
|
|
264
|
+
end
|
|
265
|
+
elsif ActiveRecord::VERSION::STRING.to_f >= 6.1 && attributes_to_define_after_schema_loads[name.to_s].first.is_a?(Proc)
|
|
266
|
+
attribute_type = attributes_to_define_after_schema_loads[name.to_s].first.call
|
|
267
|
+
if attribute_type.is_a?(ActiveRecord::Type::Serialized) && attribute_type.subtype.nil?
|
|
268
|
+
attribute name, ActiveRecord::Type::Serialized.new(ActiveRecord::Type::String.new, attribute_type.coder)
|
|
269
|
+
end
|
|
270
|
+
end
|
|
217
271
|
end
|
|
218
272
|
|
|
219
273
|
define_method("#{name}_was") do
|
|
@@ -325,7 +379,11 @@ module Lockbox
|
|
|
325
379
|
# check for this explicitly as a layer of safety
|
|
326
380
|
if message.nil? || ((message == {} || message == []) && activerecord && @attributes[name.to_s].value_before_type_cast.nil?)
|
|
327
381
|
ciphertext = send(encrypted_attribute)
|
|
328
|
-
|
|
382
|
+
|
|
383
|
+
# keep original message for empty hashes and arrays
|
|
384
|
+
unless ciphertext.nil?
|
|
385
|
+
message = self.class.send(decrypt_method_name, ciphertext, context: self)
|
|
386
|
+
end
|
|
329
387
|
|
|
330
388
|
if activerecord
|
|
331
389
|
# set previous attribute so changes populate correctly
|
|
@@ -337,8 +395,13 @@ module Lockbox
|
|
|
337
395
|
# decrypt method does type casting
|
|
338
396
|
if respond_to?(:write_attribute_without_type_cast, true)
|
|
339
397
|
write_attribute_without_type_cast(name.to_s, message) if !@attributes.frozen?
|
|
340
|
-
|
|
398
|
+
elsif respond_to?(:raw_write_attribute, true)
|
|
341
399
|
raw_write_attribute(name, message) if !@attributes.frozen?
|
|
400
|
+
else
|
|
401
|
+
if !@attributes.frozen?
|
|
402
|
+
@attributes.write_cast_value(name.to_s, message)
|
|
403
|
+
clear_attribute_change(name)
|
|
404
|
+
end
|
|
342
405
|
end
|
|
343
406
|
else
|
|
344
407
|
instance_variable_set("@#{name}", message)
|
|
@@ -353,7 +416,7 @@ module Lockbox
|
|
|
353
416
|
table = activerecord ? table_name : collection_name.to_s
|
|
354
417
|
|
|
355
418
|
unless message.nil?
|
|
356
|
-
# TODO use attribute type class in 0.
|
|
419
|
+
# TODO use attribute type class in 0.7.0
|
|
357
420
|
case options[:type]
|
|
358
421
|
when :boolean
|
|
359
422
|
message = ActiveRecord::Type::Boolean.new.serialize(message)
|
|
@@ -381,6 +444,14 @@ module Lockbox
|
|
|
381
444
|
message = ActiveRecord::Type::Float.new.serialize(message)
|
|
382
445
|
# double precision, big endian
|
|
383
446
|
message = [message].pack("G") unless message.nil?
|
|
447
|
+
when :inet
|
|
448
|
+
unless message.nil?
|
|
449
|
+
ip = message.is_a?(IPAddr) ? message : (IPAddr.new(message) rescue nil)
|
|
450
|
+
# same format as Postgres, with ipv4 padded to 16 bytes
|
|
451
|
+
# family, netmask, ip
|
|
452
|
+
# return nil for invalid IP like Active Record
|
|
453
|
+
message = ip ? [ip.ipv4? ? 0 : 1, ip.prefix, ip.hton].pack("CCa16") : nil
|
|
454
|
+
end
|
|
384
455
|
when :string, :binary
|
|
385
456
|
# do nothing
|
|
386
457
|
# encrypt will convert to binary
|
|
@@ -408,7 +479,7 @@ module Lockbox
|
|
|
408
479
|
end
|
|
409
480
|
|
|
410
481
|
unless message.nil?
|
|
411
|
-
# TODO use attribute type class in 0.
|
|
482
|
+
# TODO use attribute type class in 0.7.0
|
|
412
483
|
case options[:type]
|
|
413
484
|
when :boolean
|
|
414
485
|
message = message == "t"
|
|
@@ -427,6 +498,11 @@ module Lockbox
|
|
|
427
498
|
when :binary
|
|
428
499
|
# do nothing
|
|
429
500
|
# decrypt returns binary string
|
|
501
|
+
when :inet
|
|
502
|
+
family, prefix, addr = message.unpack("CCa16")
|
|
503
|
+
len = family == 0 ? 4 : 16
|
|
504
|
+
message = IPAddr.new_ntoh(addr.first(len))
|
|
505
|
+
message.prefix = prefix
|
|
430
506
|
else
|
|
431
507
|
# use original name for serialized attributes
|
|
432
508
|
type = (try(:attribute_types) || {})[original_name.to_s]
|
data/lib/lockbox/railtie.rb
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
module Lockbox
|
|
2
2
|
class Railtie < Rails::Railtie
|
|
3
3
|
initializer "lockbox" do |app|
|
|
4
|
+
if defined?(Rails.application.credentials)
|
|
5
|
+
# needs to work when lockbox key has a string value
|
|
6
|
+
Lockbox.master_key ||= Rails.application.credentials.try(:lockbox).try(:fetch, :master_key, nil)
|
|
7
|
+
end
|
|
8
|
+
|
|
4
9
|
require "lockbox/carrier_wave_extensions" if defined?(CarrierWave)
|
|
5
10
|
|
|
6
11
|
if defined?(ActiveStorage)
|
|
@@ -14,7 +19,14 @@ module Lockbox
|
|
|
14
19
|
ActiveStorage::Attached::Many.prepend(Lockbox::ActiveStorageExtensions::AttachedMany)
|
|
15
20
|
|
|
16
21
|
# use load hooks when possible
|
|
17
|
-
if ActiveStorage::VERSION::MAJOR >=
|
|
22
|
+
if ActiveStorage::VERSION::MAJOR >= 7
|
|
23
|
+
ActiveSupport.on_load(:active_storage_attachment) do
|
|
24
|
+
prepend Lockbox::ActiveStorageExtensions::Attachment
|
|
25
|
+
end
|
|
26
|
+
ActiveSupport.on_load(:active_storage_blob) do
|
|
27
|
+
prepend Lockbox::ActiveStorageExtensions::Blob
|
|
28
|
+
end
|
|
29
|
+
elsif ActiveStorage::VERSION::MAJOR >= 6
|
|
18
30
|
ActiveSupport.on_load(:active_storage_attachment) do
|
|
19
31
|
include Lockbox::ActiveStorageExtensions::Attachment
|
|
20
32
|
end
|
data/lib/lockbox/utils.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module Lockbox
|
|
2
2
|
class Utils
|
|
3
3
|
def self.build_box(context, options, table, attribute)
|
|
4
|
+
# dup options (with except) since keys are sometimes changed or deleted
|
|
4
5
|
options = options.except(:attribute, :encrypted_attribute, :migrating, :attached, :type)
|
|
5
6
|
options[:encode] = false unless options.key?(:encode)
|
|
6
7
|
options.each do |k, v|
|
|
@@ -26,9 +27,11 @@ module Lockbox
|
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
if options[:previous_versions].is_a?(Array)
|
|
29
|
-
|
|
30
|
+
# dup previous versions array (with map) since elements are updated
|
|
31
|
+
# dup each version (with dup) since keys are sometimes deleted
|
|
32
|
+
options[:previous_versions] = options[:previous_versions].map(&:dup)
|
|
30
33
|
options[:previous_versions].each_with_index do |version, i|
|
|
31
|
-
if !(version[:key] || version[:encryption_key] || version[:decryption_key]) && version[:master_key]
|
|
34
|
+
if !(version[:key] || version[:encryption_key] || version[:decryption_key]) && (version[:master_key] || version[:key_table] || version[:key_attribute])
|
|
32
35
|
# could also use key_table and key_attribute from options
|
|
33
36
|
# when specified, but keep simple for now
|
|
34
37
|
# also, this change isn't backward compatible
|
|
@@ -56,7 +59,7 @@ module Lockbox
|
|
|
56
59
|
key = [key].pack("H*")
|
|
57
60
|
end
|
|
58
61
|
|
|
59
|
-
raise Lockbox::Error, "#{name} must be
|
|
62
|
+
raise Lockbox::Error, "#{name} must be #{size} bytes (#{size * 2} hex digits)" if key.bytesize != size
|
|
60
63
|
raise Lockbox::Error, "#{name} must use binary encoding" if key.encoding != Encoding::BINARY
|
|
61
64
|
|
|
62
65
|
key
|
|
@@ -86,13 +89,11 @@ module Lockbox
|
|
|
86
89
|
attachable = attachable.dup
|
|
87
90
|
attachable[:io] = box.encrypt_io(io)
|
|
88
91
|
else
|
|
89
|
-
|
|
90
|
-
raise NotImplementedError, "Could not find or build blob: expected attachable, got #{attachable.inspect}"
|
|
92
|
+
raise ArgumentError, "Could not find or build blob: expected attachable, got #{attachable.inspect}"
|
|
91
93
|
end
|
|
92
94
|
|
|
93
95
|
# don't analyze encrypted data
|
|
94
|
-
metadata = {"analyzed" => true}
|
|
95
|
-
metadata["encrypted"] = true if options[:migrating]
|
|
96
|
+
metadata = {"analyzed" => true, "encrypted" => true}
|
|
96
97
|
attachable[:metadata] = (attachable[:metadata] || {}).merge(metadata)
|
|
97
98
|
end
|
|
98
99
|
|
data/lib/lockbox/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,171 +1,17 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lockbox
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Kane
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
-
dependencies:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '0'
|
|
20
|
-
type: :development
|
|
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: carrierwave
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0'
|
|
34
|
-
type: :development
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: combustion
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - ">="
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '1.3'
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - ">="
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '1.3'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: rails
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - ">="
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '0'
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - ">="
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '0'
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: minitest
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - ">="
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: '5'
|
|
76
|
-
type: :development
|
|
77
|
-
prerelease: false
|
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - ">="
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: '5'
|
|
83
|
-
- !ruby/object:Gem::Dependency
|
|
84
|
-
name: rake
|
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
|
86
|
-
requirements:
|
|
87
|
-
- - ">="
|
|
88
|
-
- !ruby/object:Gem::Version
|
|
89
|
-
version: '0'
|
|
90
|
-
type: :development
|
|
91
|
-
prerelease: false
|
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
-
requirements:
|
|
94
|
-
- - ">="
|
|
95
|
-
- !ruby/object:Gem::Version
|
|
96
|
-
version: '0'
|
|
97
|
-
- !ruby/object:Gem::Dependency
|
|
98
|
-
name: rbnacl
|
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
|
100
|
-
requirements:
|
|
101
|
-
- - ">="
|
|
102
|
-
- !ruby/object:Gem::Version
|
|
103
|
-
version: '6'
|
|
104
|
-
type: :development
|
|
105
|
-
prerelease: false
|
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
-
requirements:
|
|
108
|
-
- - ">="
|
|
109
|
-
- !ruby/object:Gem::Version
|
|
110
|
-
version: '6'
|
|
111
|
-
- !ruby/object:Gem::Dependency
|
|
112
|
-
name: sqlite3
|
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
|
114
|
-
requirements:
|
|
115
|
-
- - ">="
|
|
116
|
-
- !ruby/object:Gem::Version
|
|
117
|
-
version: '0'
|
|
118
|
-
type: :development
|
|
119
|
-
prerelease: false
|
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
-
requirements:
|
|
122
|
-
- - ">="
|
|
123
|
-
- !ruby/object:Gem::Version
|
|
124
|
-
version: '0'
|
|
125
|
-
- !ruby/object:Gem::Dependency
|
|
126
|
-
name: pg
|
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
|
128
|
-
requirements:
|
|
129
|
-
- - ">="
|
|
130
|
-
- !ruby/object:Gem::Version
|
|
131
|
-
version: '0'
|
|
132
|
-
type: :development
|
|
133
|
-
prerelease: false
|
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
-
requirements:
|
|
136
|
-
- - ">="
|
|
137
|
-
- !ruby/object:Gem::Version
|
|
138
|
-
version: '0'
|
|
139
|
-
- !ruby/object:Gem::Dependency
|
|
140
|
-
name: mysql2
|
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
|
142
|
-
requirements:
|
|
143
|
-
- - ">="
|
|
144
|
-
- !ruby/object:Gem::Version
|
|
145
|
-
version: '0'
|
|
146
|
-
type: :development
|
|
147
|
-
prerelease: false
|
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
-
requirements:
|
|
150
|
-
- - ">="
|
|
151
|
-
- !ruby/object:Gem::Version
|
|
152
|
-
version: '0'
|
|
153
|
-
- !ruby/object:Gem::Dependency
|
|
154
|
-
name: benchmark-ips
|
|
155
|
-
requirement: !ruby/object:Gem::Requirement
|
|
156
|
-
requirements:
|
|
157
|
-
- - ">="
|
|
158
|
-
- !ruby/object:Gem::Version
|
|
159
|
-
version: '0'
|
|
160
|
-
type: :development
|
|
161
|
-
prerelease: false
|
|
162
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
-
requirements:
|
|
164
|
-
- - ">="
|
|
165
|
-
- !ruby/object:Gem::Version
|
|
166
|
-
version: '0'
|
|
167
|
-
description:
|
|
168
|
-
email: andrew@chartkick.com
|
|
11
|
+
date: 2021-03-31 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description:
|
|
14
|
+
email: andrew@ankane.org
|
|
169
15
|
executables: []
|
|
170
16
|
extensions: []
|
|
171
17
|
extra_rdoc_files: []
|
|
@@ -197,7 +43,7 @@ homepage: https://github.com/ankane/lockbox
|
|
|
197
43
|
licenses:
|
|
198
44
|
- MIT
|
|
199
45
|
metadata: {}
|
|
200
|
-
post_install_message:
|
|
46
|
+
post_install_message:
|
|
201
47
|
rdoc_options: []
|
|
202
48
|
require_paths:
|
|
203
49
|
- lib
|
|
@@ -212,8 +58,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
212
58
|
- !ruby/object:Gem::Version
|
|
213
59
|
version: '0'
|
|
214
60
|
requirements: []
|
|
215
|
-
rubygems_version: 3.
|
|
216
|
-
signing_key:
|
|
61
|
+
rubygems_version: 3.0.3
|
|
62
|
+
signing_key:
|
|
217
63
|
specification_version: 4
|
|
218
|
-
summary: Modern encryption for Rails
|
|
64
|
+
summary: Modern encryption for Ruby and Rails
|
|
219
65
|
test_files: []
|