aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport/lib/active_support')
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb5
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb46
-rw-r--r--activesupport/lib/active_support/message_verifier.rb45
-rw-r--r--activesupport/lib/active_support/messages/rotation_configuration.rb11
-rw-r--r--activesupport/lib/active_support/messages/rotator.rb41
-rw-r--r--activesupport/lib/active_support/security_utils.rb2
6 files changed, 60 insertions, 90 deletions
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 12291af443..fcc13feb8c 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -306,6 +306,11 @@ module ActiveSupport
dup.tap { |hash| hash.transform_values!(*args, &block) }
end
+ def transform_keys(*args, &block)
+ return to_enum(:transform_keys) unless block_given?
+ dup.tap { |hash| hash.transform_keys!(*args, &block) }
+ end
+
def compact
dup.tap(&:compact!)
end
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 003fb4c354..8a1918039c 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -57,31 +57,27 @@ module ActiveSupport
#
# === Rotating keys
#
- # This class also defines a +rotate+ method which can be used to rotate out
- # encryption keys no longer in use.
- #
- # This method is called with an options hash where a +:cipher+ option and
- # either a +:raw_key+ or +:secret+ option must be defined. If +:raw_key+ is
- # defined, it is used directly for the underlying encryption function. If
- # the +:secret+ option is defined, a +:salt+ option must also be defined and
- # a +KeyGenerator+ instance will be used to derive a key using +:salt+. When
- # +:secret+ is used, a +:key_generator+ option may also be defined allowing
- # for custom +KeyGenerator+ instances. If CBC encryption is used a
- # `:raw_signed_key` or a `:signed_salt` option must also be defined. A
- # +:digest+ may also be defined when using CBC encryption. This method can be
- # called multiple times and new encryptor instances will be added to the
- # rotation stack on each call.
- #
- # # Specifying the key used for encryption
- # crypt.rotate raw_key: old_aead_key, cipher: "aes-256-gcm"
- # crypt.rotate raw_key: old_cbc_key, raw_signed_key: old_cbc_sign_key, cipher: "aes-256-cbc", digest: "SHA1"
- #
- # # Using a KeyGenerator instance with a secret and salt(s)
- # crypt.rotate secret: old_aead_secret, salt: old_aead_salt, cipher: "aes-256-gcm"
- # crypt.rotate secret: old_cbc_secret, salt: old_cbc_salt, signed_salt: old_cbc_signed_salt, cipher: "aes-256-cbc", digest: "SHA1"
- #
- # # Specifying the key generator instance
- # crypt.rotate key_generator: old_key_gen, salt: old_salt, cipher: "aes-256-gcm"
+ # MessageEncryptor also supports rotating out old configurations by falling
+ # back to a stack of encryptors. Call `rotate` to build and add an encryptor
+ # so `decrypt_and_verify` will also try the fallback.
+ #
+ # By default any rotated encryptors use the values of the primary
+ # encryptor unless specified otherwise.
+ #
+ # You'd give your encryptor the new defaults:
+ #
+ # crypt = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm")
+ #
+ # Then gradually rotate the old values out by adding them as fallbacks. Any message
+ # generated with the old values will then work until the rotation is removed.
+ #
+ # crypt.rotate old_secret # Fallback to an old secret instead of @secret.
+ # crypt.rotate cipher: "aes-256-cbc" # Fallback to an old cipher instead of aes-256-gcm.
+ #
+ # Though if both the secret and the cipher was changed at the same time,
+ # the above should be combined into:
+ #
+ # verifier.rotate old_secret, cipher: "aes-256-cbc"
class MessageEncryptor
prepend Messages::Rotator::Encryptor
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index 0be13f6f03..f0b6503b96 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -77,30 +77,27 @@ module ActiveSupport
#
# === Rotating keys
#
- # This class also defines a +rotate+ method which can be used to rotate out
- # verification keys no longer in use.
- #
- # This method is called with an options hash where a +:digest+ option and
- # either a +:raw_key+ or +:secret+ option must be defined. If +:raw_key+ is
- # defined, it is used directly for the underlying HMAC function. If the
- # +:secret+ option is defined, a +:salt+ option must also be defined and a
- # +KeyGenerator+ instance will be used to derive a key using +:salt+. When
- # +:secret+ is used, a +:key_generator+ option may also be defined allowing
- # for custom +KeyGenerator+ instances. This method can be called multiple
- # times and new verifier instances will be added to the rotation stack on
- # each call.
- #
- # # Specifying the key used for verification
- # @verifier.rotate raw_key: older_key, digest: "SHA1"
- #
- # # Specify the digest
- # @verifier.rotate raw_key: old_key, digest: "SHA256"
- #
- # # Using a KeyGenerator instance with a secret and salt
- # @verifier.rotate secret: old_secret, salt: old_salt, digest: "SHA1"
- #
- # # Specifying the key generator instance
- # @verifier.rotate key_generator: old_key_gen, salt: old_salt, digest: "SHA256"
+ # MessageVerifier also supports rotating out old configurations by falling
+ # back to a stack of verifiers. Call `rotate` to build and add a verifier to
+ # so either `verified` or `verify` will also try verifying with the fallback.
+ #
+ # By default any rotated verifiers use the values of the primary
+ # verifier unless specified otherwise.
+ #
+ # You'd give your verifier the new defaults:
+ #
+ # verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512", serializer: JSON)
+ #
+ # Then gradually rotate the old values out by adding them as fallbacks. Any message
+ # generated with the old values will then work until the rotation is removed.
+ #
+ # verifier.rotate old_secret # Fallback to an old secret instead of @secret.
+ # verifier.rotate digest: "SHA256" # Fallback to an old digest instead of SHA512.
+ # verifier.rotate serializer: Marshal # Fallback to an old serializer instead of JSON.
+ #
+ # Though the above would most likely be combined into one rotation:
+ #
+ # verifier.rotate old_secret, digest: "SHA256", serializer: Marshal
class MessageVerifier
prepend Messages::Rotator::Verifier
diff --git a/activesupport/lib/active_support/messages/rotation_configuration.rb b/activesupport/lib/active_support/messages/rotation_configuration.rb
index 12566bdb63..bd50d6d348 100644
--- a/activesupport/lib/active_support/messages/rotation_configuration.rb
+++ b/activesupport/lib/active_support/messages/rotation_configuration.rb
@@ -2,22 +2,19 @@
module ActiveSupport
module Messages
- class RotationConfiguration
+ class RotationConfiguration # :nodoc:
attr_reader :signed, :encrypted
def initialize
@signed, @encrypted = [], []
end
- def rotate(kind = nil, **options)
+ def rotate(kind, *args)
case kind
when :signed
- @signed << options
+ @signed << args
when :encrypted
- @encrypted << options
- else
- rotate :signed, options
- rotate :encrypted, options
+ @encrypted << args
end
end
end
diff --git a/activesupport/lib/active_support/messages/rotator.rb b/activesupport/lib/active_support/messages/rotator.rb
index 21ae643138..823a399d67 100644
--- a/activesupport/lib/active_support/messages/rotator.rb
+++ b/activesupport/lib/active_support/messages/rotator.rb
@@ -3,14 +3,15 @@
module ActiveSupport
module Messages
module Rotator # :nodoc:
- def initialize(*args)
+ def initialize(*, **options)
super
+ @options = options
@rotations = []
end
- def rotate(*args)
- @rotations << create_rotation(*args)
+ def rotate(*secrets, **options)
+ @rotations << build_rotation(*secrets, @options.merge(options))
end
module Encryptor
@@ -23,25 +24,8 @@ module ActiveSupport
end
private
- def create_rotation(raw_key: nil, raw_signed_key: nil, **options)
- self.class.new \
- raw_key || extract_key(options),
- raw_signed_key || extract_signing_key(options),
- options.slice(:cipher, :digest, :serializer)
- end
-
- def extract_key(cipher:, salt:, key_generator: nil, secret: nil, **)
- key_generator ||= key_generator_for(secret)
- key_generator.generate_key(salt, self.class.key_len(cipher))
- end
-
- def extract_signing_key(cipher:, signed_salt: nil, key_generator: nil, secret: nil, **)
- if cipher.downcase.end_with?("cbc")
- raise ArgumentError, "missing signed_salt for signing key generation" unless signed_salt
-
- key_generator ||= key_generator_for(secret)
- key_generator.generate_key(signed_salt)
- end
+ def build_rotation(secret = @secret, sign_secret = @sign_secret, options)
+ self.class.new(secret, sign_secret, options)
end
end
@@ -53,21 +37,12 @@ module ActiveSupport
end
private
- def create_rotation(raw_key: nil, digest: nil, serializer: nil, **options)
- self.class.new(raw_key || extract_key(options), digest: digest, serializer: serializer)
- end
-
- def extract_key(key_generator: nil, secret: nil, salt:)
- key_generator ||= key_generator_for(secret)
- key_generator.generate_key(salt)
+ def build_rotation(secret = @secret, options)
+ self.class.new(secret, options)
end
end
private
- def key_generator_for(secret)
- ActiveSupport::KeyGenerator.new(secret, iterations: 1000)
- end
-
def run_rotations(on_rotation)
@rotations.find do |rotation|
if message = yield(rotation) rescue next
diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb
index 51870559ec..b6b31ef140 100644
--- a/activesupport/lib/active_support/security_utils.rb
+++ b/activesupport/lib/active_support/security_utils.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "digest"
+require "digest/sha2"
module ActiveSupport
module SecurityUtils