lockbox 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25557e658cab349f0b1e5875b87f3c0a804a5c3aa587171331c5bdbece0ebf2f
4
- data.tar.gz: 6d9d0e425c3c2107e9cdce85880c9b11db16062c68e1f87706c8f70afac6eef0
3
+ metadata.gz: f26c45499c84a35d3a4aaeb7043853a4090dd63b2268e128fc0f2713dfc25b09
4
+ data.tar.gz: 697626daf2a92ecf69eabc56390074f497ba51a31d1ee24a2a5644c9437d0f09
5
5
  SHA512:
6
- metadata.gz: 1df5017733e14858ed3bacb5a480bbe43858974b05a74abe0165d680bb4a6a0e700e49990e5cd8004f8b021c33d6ed0c4edfe3bd5d0e7a02dc114d16894cf02d
7
- data.tar.gz: 3d51c407d23036699bf2b3a7813eca85c1004e01ba8edda1bc75b5e0e6a1086e3fe1cbd0593f9cab4b0ce7fd16dd1cc320dd6fac8aead84fdfd436530db0ae5a
6
+ metadata.gz: 1b0832e473ba43bb36cc9b11398d20385eae5f94b3a2d13f66a5cc4322605f28bf93a22b9fcb9ac5e171e7004b909b59e0f26d5a75d1be6561c61bbdf054c65e
7
+ data.tar.gz: 77995606e64719168bb0688215721e89c84b7ce9b758e49dbbb1beeecd460ca85c129089c8f1f160320ead16cbe64bd7eb2fb72491d7e9575ac392ef11ca492b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 1.2.0 (2023-03-20)
2
+
3
+ - Made it easier to rotate master key
4
+ - Added `associated_data` option for database fields and files
5
+ - Added `decimal` type
6
+ - Added `encode_attributes` option
7
+ - Fixed deprecation warnings with Rails 7.1
8
+
1
9
  ## 1.1.2 (2023-02-01)
2
10
 
3
11
  - Fixed error when migrating to `array`, `hash`, and `json` types
data/README.md CHANGED
@@ -117,6 +117,7 @@ class User < ApplicationRecord
117
117
  has_encrypted :active, type: :boolean
118
118
  has_encrypted :salary, type: :integer
119
119
  has_encrypted :latitude, type: :float
120
+ has_encrypted :longitude, type: :decimal
120
121
  has_encrypted :video, type: :binary
121
122
  has_encrypted :properties, type: :json
122
123
  has_encrypted :settings, type: :hash
@@ -566,51 +567,25 @@ Use `decrypt_str` get the value as UTF-8
566
567
 
567
568
  To make key rotation easy, you can pass previous versions of keys that can decrypt.
568
569
 
569
- ### Active Record & Mongoid
570
-
571
- Update your model:
570
+ Create `config/initializers/lockbox.rb` with:
572
571
 
573
572
  ```ruby
574
- class User < ApplicationRecord
575
- has_encrypted :email, previous_versions: [{master_key: previous_key}]
576
- end
573
+ Lockbox.default_options[:previous_versions] = [{master_key: previous_key}]
577
574
  ```
578
575
 
579
- To rotate existing records, use:
576
+ To rotate existing Active Record & Mongoid records, use:
580
577
 
581
578
  ```ruby
582
579
  Lockbox.rotate(User, attributes: [:email])
583
580
  ```
584
581
 
585
- Once all records are rotated, you can remove `previous_versions` from the model.
586
-
587
- ### Action Text
588
-
589
- Update your initializer:
590
-
591
- ```ruby
592
- Lockbox.encrypts_action_text_body(previous_versions: [{master_key: previous_key}])
593
- ```
594
-
595
- To rotate existing records, use:
582
+ To rotate existing Action Text records, use:
596
583
 
597
584
  ```ruby
598
585
  Lockbox.rotate(ActionText::RichText, attributes: [:body])
599
586
  ```
600
587
 
601
- Once all records are rotated, you can remove `previous_versions` from the initializer.
602
-
603
- ### Active Storage
604
-
605
- Update your model:
606
-
607
- ```ruby
608
- class User < ApplicationRecord
609
- encrypts_attached :license, previous_versions: [{master_key: previous_key}]
610
- end
611
- ```
612
-
613
- To rotate existing files, use:
588
+ To rotate existing Active Storage files, use:
614
589
 
615
590
  ```ruby
616
591
  User.with_attached_license.find_each do |user|
@@ -618,39 +593,31 @@ User.with_attached_license.find_each do |user|
618
593
  end
619
594
  ```
620
595
 
621
- Once all files are rotated, you can remove `previous_versions` from the model.
622
-
623
- ### CarrierWave
624
-
625
- Update your model:
626
-
627
- ```ruby
628
- class LicenseUploader < CarrierWave::Uploader::Base
629
- encrypt previous_versions: [{master_key: previous_key}]
630
- end
631
- ```
632
-
633
- To rotate existing files, use:
596
+ To rotate existing CarrierWave files, use:
634
597
 
635
598
  ```ruby
636
599
  User.find_each do |user|
637
600
  user.license.rotate_encryption!
601
+ # or for multiple files
602
+ user.licenses.map(&:rotate_encryption!)
638
603
  end
639
604
  ```
640
605
 
641
- For multiple files, use:
606
+ Once everything is rotated, you can remove `previous_versions` from the initializer.
607
+
608
+ ### Individual Fields & Files
609
+
610
+ You can also pass previous versions to individual fields and files.
642
611
 
643
612
  ```ruby
644
- User.find_each do |user|
645
- user.licenses.map(&:rotate_encryption!)
613
+ class User < ApplicationRecord
614
+ has_encrypted :email, previous_versions: [{master_key: previous_key}]
646
615
  end
647
616
  ```
648
617
 
649
- Once all files are rotated, you can remove `previous_versions` from the model.
650
-
651
618
  ### Local Files & Strings
652
619
 
653
- For local files and strings, use:
620
+ To rotate local files and strings, use:
654
621
 
655
622
  ```ruby
656
623
  Lockbox.new(key: key, previous_versions: [{key: previous_key}])
@@ -948,6 +915,12 @@ class User < ApplicationRecord
948
915
  end
949
916
  ```
950
917
 
918
+ or set it globally:
919
+
920
+ ```ruby
921
+ Lockbox.encode_attributes = false
922
+ ```
923
+
951
924
  ## Compatibility
952
925
 
953
926
  It’s easy to read encrypted data in another language if needed.
data/lib/lockbox/box.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  module Lockbox
2
2
  class Box
3
- def initialize(key: nil, algorithm: nil, encryption_key: nil, decryption_key: nil, padding: false)
3
+ NOT_SET = Object.new
4
+
5
+ def initialize(key: nil, algorithm: nil, encryption_key: nil, decryption_key: nil, padding: false, associated_data: nil)
4
6
  raise ArgumentError, "Cannot pass both key and encryption/decryption key" if key && (encryption_key || decryption_key)
5
7
 
6
8
  key = Lockbox::Utils.decode_key(key) if key
@@ -32,9 +34,11 @@ module Lockbox
32
34
 
33
35
  @algorithm = algorithm
34
36
  @padding = padding == true ? 16 : padding
37
+ @associated_data = associated_data
35
38
  end
36
39
 
37
- def encrypt(message, associated_data: nil)
40
+ def encrypt(message, associated_data: NOT_SET)
41
+ associated_data = @associated_data if associated_data == NOT_SET
38
42
  message = Lockbox.pad(message, size: @padding) if @padding
39
43
  case @algorithm
40
44
  when "hybrid"
@@ -53,7 +57,8 @@ module Lockbox
53
57
  nonce + ciphertext
54
58
  end
55
59
 
56
- def decrypt(ciphertext, associated_data: nil)
60
+ def decrypt(ciphertext, associated_data: NOT_SET)
61
+ associated_data = @associated_data if associated_data == NOT_SET
57
62
  message =
58
63
  case @algorithm
59
64
  when "hybrid"
@@ -9,7 +9,7 @@ module Lockbox
9
9
 
10
10
  @boxes =
11
11
  [Box.new(**options)] +
12
- Array(previous_versions).map { |v| Box.new(key: options[:key], **v) }
12
+ Array(previous_versions).reject { |v| v.key?(:master_key) }.map { |v| Box.new(key: options[:key], **v) }
13
13
  end
14
14
 
15
15
  def encrypt(message, **options)
@@ -6,7 +6,7 @@ module Lockbox
6
6
  payload = event.payload
7
7
  name = "Encrypt File (#{event.duration.round(1)}ms)"
8
8
 
9
- debug " #{color(name, YELLOW, true)} Encrypted #{payload[:name]}"
9
+ debug " #{color(name, YELLOW, bold: true)} Encrypted #{payload[:name]}"
10
10
  end
11
11
 
12
12
  def decrypt_file(event)
@@ -15,7 +15,7 @@ module Lockbox
15
15
  payload = event.payload
16
16
  name = "Decrypt File (#{event.duration.round(1)}ms)"
17
17
 
18
- debug " #{color(name, YELLOW, true)} Decrypted #{payload[:name]}"
18
+ debug " #{color(name, YELLOW, bold: true)} Decrypted #{payload[:name]}"
19
19
  end
20
20
  end
21
21
  end
data/lib/lockbox/model.rb CHANGED
@@ -19,10 +19,12 @@ module Lockbox
19
19
  # options[:type] = :integer
20
20
  # when Float
21
21
  # options[:type] = :float
22
+ # when BigDecimal
23
+ # options[:type] = :decimal
22
24
  # end
23
25
 
24
26
  custom_type = options[:type].respond_to?(:serialize) && options[:type].respond_to?(:deserialize)
25
- valid_types = [nil, :string, :boolean, :date, :datetime, :time, :integer, :float, :binary, :json, :hash, :array, :inet]
27
+ valid_types = [nil, :string, :boolean, :date, :datetime, :time, :integer, :float, :decimal, :binary, :json, :hash, :array, :inet]
26
28
  raise ArgumentError, "Unknown type: #{options[:type]}" unless custom_type || valid_types.include?(options[:type])
27
29
 
28
30
  activerecord = defined?(ActiveRecord::Base) && self < ActiveRecord::Base
@@ -50,7 +52,7 @@ module Lockbox
50
52
 
51
53
  options[:attribute] = name.to_s
52
54
  options[:encrypted_attribute] = encrypted_attribute
53
- options[:encode] = true unless options.key?(:encode)
55
+ options[:encode] = Lockbox.encode_attributes unless options.key?(:encode)
54
56
 
55
57
  encrypt_method_name = "generate_#{encrypted_attribute}"
56
58
  decrypt_method_name = "decrypt_#{encrypted_attribute}"
@@ -321,9 +323,15 @@ module Lockbox
321
323
 
322
324
  attribute name, attribute_type
323
325
 
324
- serialize name, JSON if options[:type] == :json
325
- serialize name, Hash if options[:type] == :hash
326
- serialize name, Array if options[:type] == :array
326
+ if ActiveRecord::VERSION::STRING.to_f >= 7.1
327
+ serialize name, coder: JSON if options[:type] == :json
328
+ serialize name, type: Hash if options[:type] == :hash
329
+ serialize name, type: Array if options[:type] == :array
330
+ else
331
+ serialize name, JSON if options[:type] == :json
332
+ serialize name, Hash if options[:type] == :hash
333
+ serialize name, Array if options[:type] == :array
334
+ end
327
335
  elsif !attributes_to_define_after_schema_loads.key?(name.to_s)
328
336
  # when migrating it's best to specify the type directly
329
337
  # however, we can try to use the original type if its already defined
@@ -531,6 +539,19 @@ module Lockbox
531
539
  message = ActiveRecord::Type::Float.new.serialize(message)
532
540
  # double precision, big endian
533
541
  message = [message].pack("G") unless message.nil?
542
+ when :decimal
543
+ message =
544
+ if ActiveRecord::VERSION::MAJOR >= 6
545
+ ActiveRecord::Type::Decimal.new.serialize(message)
546
+ else
547
+ # issue with serialize in Active Record < 6
548
+ # https://github.com/rails/rails/commit/a741208f80dd33420a56486bd9ed2b0b9862234a
549
+ ActiveRecord::Type::Decimal.new.cast(message)
550
+ end
551
+ # Postgres stores 4 decimal digits in 2 bytes
552
+ # plus 3 to 8 bytes of overhead
553
+ # but use string for simplicity
554
+ message = message.to_s("F") unless message.nil?
534
555
  when :inet
535
556
  unless message.nil?
536
557
  ip = message.is_a?(IPAddr) ? message : (IPAddr.new(message) rescue nil)
@@ -579,6 +600,8 @@ module Lockbox
579
600
  message = ActiveRecord::Type::Integer.new(limit: 8).deserialize(message.unpack1("q>"))
580
601
  when :float
581
602
  message = ActiveRecord::Type::Float.new.deserialize(message.unpack1("G"))
603
+ when :decimal
604
+ message = ActiveRecord::Type::Decimal.new.deserialize(message)
582
605
  when :string
583
606
  message.force_encoding(Encoding::UTF_8)
584
607
  when :binary
data/lib/lockbox/utils.rb CHANGED
@@ -26,6 +26,10 @@ module Lockbox
26
26
  )
27
27
  end
28
28
 
29
+ unless options.key?(:previous_versions)
30
+ options[:previous_versions] = Lockbox.default_options[:previous_versions]
31
+ end
32
+
29
33
  if options[:previous_versions].is_a?(Array)
30
34
  # dup previous versions array (with map) since elements are updated
31
35
  # dup each version (with dup) since keys are sometimes deleted
@@ -1,3 +1,3 @@
1
1
  module Lockbox
2
- VERSION = "1.1.2"
2
+ VERSION = "1.2.0"
3
3
  end
data/lib/lockbox.rb CHANGED
@@ -27,10 +27,11 @@ module Lockbox
27
27
  extend Padding
28
28
 
29
29
  class << self
30
- attr_accessor :default_options
30
+ attr_accessor :default_options, :encode_attributes
31
31
  attr_writer :master_key
32
32
  end
33
33
  self.default_options = {}
34
+ self.encode_attributes = true
34
35
 
35
36
  def self.master_key
36
37
  @master_key ||= ENV["LOCKBOX_MASTER_KEY"]
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: 1.1.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-01 00:00:00.000000000 Z
11
+ date: 2023-03-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: andrew@ankane.org
@@ -58,7 +58,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
58
58
  - !ruby/object:Gem::Version
59
59
  version: '0'
60
60
  requirements: []
61
- rubygems_version: 3.4.1
61
+ rubygems_version: 3.4.6
62
62
  signing_key:
63
63
  specification_version: 4
64
64
  summary: Modern encryption for Ruby and Rails