lockbox 2.2.0 → 2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3a6c05fd83da2e2fc8474f2daf3b55f27ee61dd8a86ff3264df772816bcf92b
4
- data.tar.gz: d269773e126958e5288a2fcb48ee228dbcd3918648e02834057c8be04f36ec45
3
+ metadata.gz: 3c892b20d24ec1db29f60f508b0103c315450bf85f71e3d5ff9b92d3f21d1cca
4
+ data.tar.gz: 000fdbc4bc30ba97738d3823b719bbb37d23c4d926792d2f65197043502e843e
5
5
  SHA512:
6
- metadata.gz: f05224cd3e90993ba6a5b7cadfd2bdac839e6590ae3a5702935dc1146b1349ce76d41d3de989f9e10b3510cf420a86936d283968cc343a7eaccfa119a4dff1b6
7
- data.tar.gz: d185de56bd634a6ba6753d3a1acf91a7f3940edb02ccbcaa89b22839ef4e57e62a5c698074b6cff821bc5869e3b423dee94781fad4a7e0961794982fa2ed1faa
6
+ metadata.gz: 4fc7026684f02a5f8a34e827c16566425e2987efc5f5b02ff905431e1d1e9bdbf2ac542d87413418d8c0cb2b3d8feb2ce58c61aaed96b1dc32bf2a490cea8c4c
7
+ data.tar.gz: a04a81cab9ea3b9c8b8591846fe10866610fadd4829e04ffdc6bbd67138df307a78e05da0cf820bd1a44544a54980191202a4afd8b8b9e5f4ca1bf6f8109c529
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 2.3.0 (2026-11-10)
2
+
3
+ - Fixed hybrid encryption key being able to decrypt - [more info](https://github.com/ankane/lockbox/security/advisories/GHSA-586r-mm5q-xhg2)
4
+
1
5
  ## 2.2.0 (2026-04-04)
2
6
 
3
7
  - Dropped support for Active Record < 7.2 and Ruby < 3.3
data/lib/lockbox/box.rb CHANGED
@@ -6,8 +6,8 @@ module Lockbox
6
6
  raise ArgumentError, "Cannot pass both key and encryption/decryption key" if key && (encryption_key || decryption_key)
7
7
 
8
8
  key = Lockbox::Utils.decode_key(key) if key
9
- encryption_key = Lockbox::Utils.decode_key(encryption_key, size: 64) if encryption_key
10
- decryption_key = Lockbox::Utils.decode_key(decryption_key, size: 64) if decryption_key
9
+ encryption_key, legacy_encryption = decode_hybrid_key(encryption_key) if encryption_key
10
+ decryption_key, legacy_decryption = decode_hybrid_key(decryption_key) if decryption_key
11
11
 
12
12
  algorithm ||= "aes-gcm"
13
13
 
@@ -26,8 +26,22 @@ module Lockbox
26
26
  when "hybrid"
27
27
  raise ArgumentError, "Missing key" unless encryption_key || decryption_key
28
28
  require "rbnacl"
29
- @encryption_box = RbNaCl::Boxes::Curve25519XSalsa20Poly1305.new(encryption_key.slice(0, 32), encryption_key.slice(32..-1)) if encryption_key
30
- @decryption_box = RbNaCl::Boxes::Curve25519XSalsa20Poly1305.new(decryption_key.slice(32..-1), decryption_key.slice(0, 32)) if decryption_key
29
+ if encryption_key
30
+ @encryption_box =
31
+ if legacy_encryption
32
+ nil
33
+ else
34
+ RbNaCl::Boxes::Sealed.from_public_key(encryption_key)
35
+ end
36
+ end
37
+ if decryption_key
38
+ @decryption_box =
39
+ if legacy_decryption
40
+ RbNaCl::Boxes::Curve25519XSalsa20Poly1305.new(decryption_key.byteslice(32..-1), decryption_key.byteslice(0, 32))
41
+ else
42
+ RbNaCl::Boxes::Sealed.from_private_key(decryption_key)
43
+ end
44
+ end
31
45
  else
32
46
  raise ArgumentError, "Unknown algorithm: #{algorithm}"
33
47
  end
@@ -44,17 +58,16 @@ module Lockbox
44
58
  when "hybrid"
45
59
  raise ArgumentError, "No encryption key set" unless defined?(@encryption_box)
46
60
  raise ArgumentError, "Associated data not supported with this algorithm" if associated_data
47
- nonce = generate_nonce(@encryption_box)
48
- ciphertext = @encryption_box.encrypt(nonce, message)
61
+ Utils.raise_hybrid_error if @encryption_box.nil?
62
+ @encryption_box.encrypt(message)
49
63
  when "xsalsa20"
50
64
  raise ArgumentError, "Associated data not supported with this algorithm" if associated_data
51
65
  nonce = generate_nonce(@box)
52
- ciphertext = @box.encrypt(nonce, message)
66
+ nonce + @box.encrypt(nonce, message)
53
67
  else
54
68
  nonce = generate_nonce(@box)
55
- ciphertext = @box.encrypt(nonce, message, associated_data)
69
+ nonce + @box.encrypt(nonce, message, associated_data)
56
70
  end
57
- nonce + ciphertext
58
71
  end
59
72
 
60
73
  def decrypt(ciphertext, associated_data: NOT_SET)
@@ -64,8 +77,12 @@ module Lockbox
64
77
  when "hybrid"
65
78
  raise ArgumentError, "No decryption key set" unless defined?(@decryption_box)
66
79
  raise ArgumentError, "Associated data not supported with this algorithm" if associated_data
67
- nonce, ciphertext = extract_nonce(@decryption_box, ciphertext)
68
- @decryption_box.decrypt(nonce, ciphertext)
80
+ if @decryption_box.is_a?(RbNaCl::Boxes::Sealed)
81
+ @decryption_box.decrypt(ciphertext)
82
+ else
83
+ nonce, ciphertext = extract_nonce(@decryption_box, ciphertext)
84
+ @decryption_box.decrypt(nonce, ciphertext)
85
+ end
69
86
  when "xsalsa20"
70
87
  raise ArgumentError, "Associated data not supported with this algorithm" if associated_data
71
88
  nonce, ciphertext = extract_nonce(@box, ciphertext)
@@ -94,5 +111,15 @@ module Lockbox
94
111
  nonce = bytes.slice(0, nonce_bytes)
95
112
  [nonce, bytes.slice(nonce_bytes..-1)]
96
113
  end
114
+
115
+ def decode_hybrid_key(key)
116
+ [Lockbox::Utils.decode_key(key), false]
117
+ rescue Error => e
118
+ begin
119
+ [Lockbox::Utils.decode_key(key, size: 64), true]
120
+ rescue Error
121
+ raise e
122
+ end
123
+ end
97
124
  end
98
125
  end
@@ -1,6 +1,8 @@
1
1
  module Lockbox
2
2
  module CarrierWaveExtensions
3
3
  def encrypt(**options)
4
+ Utils.check_options(options)
5
+
4
6
  class_eval do
5
7
  # uses same hook as process (before cache)
6
8
  # processing can be disabled, so better to keep separate
data/lib/lockbox/model.rb CHANGED
@@ -23,6 +23,8 @@ module Lockbox
23
23
  # options[:type] = :decimal
24
24
  # end
25
25
 
26
+ Utils.check_options(options)
27
+
26
28
  custom_type = options[:type].respond_to?(:serialize) && options[:type].respond_to?(:deserialize)
27
29
  valid_types = [nil, :string, :boolean, :date, :datetime, :time, :integer, :float, :decimal, :binary, :json, :hash, :array, :inet]
28
30
  raise ArgumentError, "Unknown type: #{options[:type]}" unless custom_type || valid_types.include?(options[:type])
@@ -516,7 +518,7 @@ module Lockbox
516
518
  # set previous attribute so changes populate correctly
517
519
  # it's fine if this is set on future decryptions (as is the case when message is nil)
518
520
  # as only the first value is loaded into changes
519
- @attributes[name.to_s].instance_variable_set("@value_before_type_cast", message)
521
+ @attributes[name.to_s].instance_variable_set(:@value_before_type_cast, message)
520
522
 
521
523
  # cache
522
524
  # decrypt method does type casting
@@ -677,6 +679,8 @@ module Lockbox
677
679
 
678
680
  module Attached
679
681
  def encrypts_attached(*attributes, **options)
682
+ Utils.check_options(options)
683
+
680
684
  attributes.each do |name|
681
685
  name = name.to_sym
682
686
 
data/lib/lockbox/utils.rb CHANGED
@@ -121,5 +121,15 @@ module Lockbox
121
121
  content_type: attachment.content_type
122
122
  }
123
123
  end
124
+
125
+ def self.check_options(options)
126
+ if (options[:encryption_key].is_a?(String) && options[:encryption_key].bytesize == 128) || (options[:decryption_key].is_a?(String) && options[:decryption_key].bytesize == 128)
127
+ raise_hybrid_error
128
+ end
129
+ end
130
+
131
+ def self.raise_hybrid_error
132
+ raise Lockbox::Error, "Rotate your hybrid cryptography keys and data to address GHSA-586r-mm5q-xhg2"
133
+ end
124
134
  end
125
135
  end
@@ -1,3 +1,3 @@
1
1
  module Lockbox
2
- VERSION = "2.2.0"
2
+ VERSION = "2.3.0"
3
3
  end
data/lib/lockbox.rb CHANGED
@@ -50,15 +50,10 @@ module Lockbox
50
50
 
51
51
  def self.generate_key_pair
52
52
  require "rbnacl"
53
- # encryption and decryption servers exchange public keys
54
- # this produces smaller ciphertext than sealed box
55
- alice = RbNaCl::PrivateKey.generate
56
- bob = RbNaCl::PrivateKey.generate
57
- # alice is sending message to bob
58
- # use bob first in both cases to prevent keys being swappable
53
+ key = RbNaCl::PrivateKey.generate
59
54
  {
60
- encryption_key: to_hex(bob.public_key.to_bytes + alice.to_bytes),
61
- decryption_key: to_hex(bob.to_bytes + alice.public_key.to_bytes)
55
+ encryption_key: to_hex(key.public_key.to_bytes),
56
+ decryption_key: to_hex(key.to_bytes)
62
57
  }
63
58
  end
64
59
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lockbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
@@ -55,7 +55,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
55
55
  - !ruby/object:Gem::Version
56
56
  version: '0'
57
57
  requirements: []
58
- rubygems_version: 4.0.6
58
+ rubygems_version: 4.0.20
59
59
  specification_version: 4
60
60
  summary: Modern encryption for Ruby and Rails
61
61
  test_files: []