activesupport 6.0.6.1 → 7.1.3.2
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 +865 -438
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -6
- data/lib/active_support/actionable_error.rb +4 -2
- data/lib/active_support/array_inquirer.rb +4 -2
- data/lib/active_support/backtrace_cleaner.rb +30 -10
- data/lib/active_support/benchmarkable.rb +4 -3
- data/lib/active_support/broadcast_logger.rb +250 -0
- data/lib/active_support/builder.rb +1 -1
- data/lib/active_support/cache/coder.rb +153 -0
- data/lib/active_support/cache/entry.rb +134 -0
- data/lib/active_support/cache/file_store.rb +53 -20
- data/lib/active_support/cache/mem_cache_store.rb +208 -63
- data/lib/active_support/cache/memory_store.rb +120 -38
- data/lib/active_support/cache/null_store.rb +16 -2
- data/lib/active_support/cache/redis_cache_store.rb +201 -208
- data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
- data/lib/active_support/cache/strategy/local_cache.rb +73 -66
- data/lib/active_support/cache.rb +539 -261
- data/lib/active_support/callbacks.rb +273 -142
- data/lib/active_support/code_generator.rb +65 -0
- data/lib/active_support/concern.rb +53 -7
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +44 -7
- data/lib/active_support/concurrency/null_lock.rb +13 -0
- data/lib/active_support/concurrency/share_lock.rb +2 -2
- data/lib/active_support/configurable.rb +19 -6
- data/lib/active_support/configuration_file.rb +51 -0
- data/lib/active_support/core_ext/array/access.rb +1 -5
- data/lib/active_support/core_ext/array/conversions.rb +15 -13
- data/lib/active_support/core_ext/array/grouping.rb +6 -6
- data/lib/active_support/core_ext/array/inquiry.rb +2 -2
- data/lib/active_support/core_ext/benchmark.rb +2 -2
- data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
- data/lib/active_support/core_ext/class/attribute.rb +34 -44
- data/lib/active_support/core_ext/class/subclasses.rb +19 -29
- data/lib/active_support/core_ext/date/blank.rb +1 -1
- data/lib/active_support/core_ext/date/calculations.rb +24 -9
- data/lib/active_support/core_ext/date/conversions.rb +18 -16
- data/lib/active_support/core_ext/date_and_time/calculations.rb +27 -4
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
- data/lib/active_support/core_ext/date_time/blank.rb +1 -1
- data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +19 -15
- data/lib/active_support/core_ext/digest/uuid.rb +30 -13
- data/lib/active_support/core_ext/enumerable.rb +146 -72
- data/lib/active_support/core_ext/erb/util.rb +196 -0
- data/lib/active_support/core_ext/file/atomic.rb +3 -1
- data/lib/active_support/core_ext/hash/conversions.rb +3 -4
- data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +4 -4
- data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
- data/lib/active_support/core_ext/hash/keys.rb +5 -5
- data/lib/active_support/core_ext/hash/slice.rb +3 -2
- data/lib/active_support/core_ext/integer/inflections.rb +12 -12
- data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
- data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
- data/lib/active_support/core_ext/load_error.rb +1 -1
- data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
- data/lib/active_support/core_ext/module/attribute_accessors.rb +31 -29
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +51 -20
- data/lib/active_support/core_ext/module/concerning.rb +14 -8
- data/lib/active_support/core_ext/module/delegation.rb +75 -42
- data/lib/active_support/core_ext/module/deprecation.rb +15 -12
- data/lib/active_support/core_ext/module/introspection.rb +1 -26
- data/lib/active_support/core_ext/name_error.rb +23 -2
- data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +82 -73
- data/lib/active_support/core_ext/object/acts_like.rb +29 -5
- data/lib/active_support/core_ext/object/blank.rb +2 -2
- data/lib/active_support/core_ext/object/deep_dup.rb +17 -1
- data/lib/active_support/core_ext/object/duplicable.rb +15 -4
- data/lib/active_support/core_ext/object/inclusion.rb +13 -5
- data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
- data/lib/active_support/core_ext/object/json.rb +52 -28
- data/lib/active_support/core_ext/object/to_query.rb +2 -4
- data/lib/active_support/core_ext/object/try.rb +20 -20
- data/lib/active_support/core_ext/object/with.rb +44 -0
- data/lib/active_support/core_ext/object/with_options.rb +25 -6
- data/lib/active_support/core_ext/object.rb +1 -0
- data/lib/active_support/core_ext/pathname/blank.rb +16 -0
- data/lib/active_support/core_ext/pathname/existence.rb +23 -0
- data/lib/active_support/core_ext/pathname.rb +4 -0
- data/lib/active_support/core_ext/range/compare_range.rb +6 -25
- data/lib/active_support/core_ext/range/conversions.rb +34 -13
- data/lib/active_support/core_ext/range/each.rb +1 -1
- data/lib/active_support/core_ext/range/overlap.rb +40 -0
- data/lib/active_support/core_ext/range.rb +1 -2
- data/lib/active_support/core_ext/regexp.rb +8 -1
- data/lib/active_support/core_ext/securerandom.rb +25 -13
- data/lib/active_support/core_ext/string/access.rb +5 -24
- data/lib/active_support/core_ext/string/conversions.rb +3 -2
- data/lib/active_support/core_ext/string/filters.rb +21 -15
- data/lib/active_support/core_ext/string/indent.rb +1 -1
- data/lib/active_support/core_ext/string/inflections.rb +51 -10
- data/lib/active_support/core_ext/string/inquiry.rb +2 -1
- data/lib/active_support/core_ext/string/multibyte.rb +2 -2
- data/lib/active_support/core_ext/string/output_safety.rb +85 -194
- data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
- data/lib/active_support/core_ext/symbol.rb +3 -0
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +46 -8
- data/lib/active_support/core_ext/time/conversions.rb +16 -13
- data/lib/active_support/core_ext/time/zones.rb +12 -28
- data/lib/active_support/core_ext.rb +2 -1
- data/lib/active_support/current_attributes/test_helper.rb +13 -0
- data/lib/active_support/current_attributes.rb +54 -22
- data/lib/active_support/deep_mergeable.rb +53 -0
- data/lib/active_support/dependencies/autoload.rb +17 -12
- data/lib/active_support/dependencies/interlock.rb +10 -18
- data/lib/active_support/dependencies/require_dependency.rb +28 -0
- data/lib/active_support/dependencies.rb +58 -769
- data/lib/active_support/deprecation/behaviors.rb +77 -38
- data/lib/active_support/deprecation/constant_accessor.rb +5 -4
- data/lib/active_support/deprecation/deprecators.rb +104 -0
- data/lib/active_support/deprecation/disallowed.rb +54 -0
- data/lib/active_support/deprecation/instance_delegator.rb +31 -5
- data/lib/active_support/deprecation/method_wrappers.rb +12 -28
- data/lib/active_support/deprecation/proxy_wrappers.rb +40 -25
- data/lib/active_support/deprecation/reporting.rb +76 -16
- data/lib/active_support/deprecation.rb +36 -4
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +150 -68
- data/lib/active_support/digest.rb +5 -3
- data/lib/active_support/duration/iso8601_parser.rb +3 -3
- data/lib/active_support/duration/iso8601_serializer.rb +24 -12
- data/lib/active_support/duration.rb +136 -56
- data/lib/active_support/encrypted_configuration.rb +72 -9
- data/lib/active_support/encrypted_file.rb +46 -13
- data/lib/active_support/environment_inquirer.rb +40 -0
- data/lib/active_support/error_reporter/test_helper.rb +15 -0
- data/lib/active_support/error_reporter.rb +203 -0
- data/lib/active_support/evented_file_update_checker.rb +86 -137
- data/lib/active_support/execution_context/test_helper.rb +13 -0
- data/lib/active_support/execution_context.rb +53 -0
- data/lib/active_support/execution_wrapper.rb +31 -12
- data/lib/active_support/executor/test_helper.rb +7 -0
- data/lib/active_support/file_update_checker.rb +4 -2
- data/lib/active_support/fork_tracker.rb +79 -0
- data/lib/active_support/gem_version.rb +5 -5
- data/lib/active_support/gzip.rb +2 -0
- data/lib/active_support/hash_with_indifferent_access.rb +86 -42
- data/lib/active_support/html_safe_translation.rb +53 -0
- data/lib/active_support/i18n.rb +2 -1
- data/lib/active_support/i18n_railtie.rb +29 -27
- data/lib/active_support/inflector/inflections.rb +26 -9
- data/lib/active_support/inflector/methods.rb +54 -64
- data/lib/active_support/inflector/transliterate.rb +7 -5
- data/lib/active_support/isolated_execution_state.rb +76 -0
- data/lib/active_support/json/decoding.rb +6 -5
- data/lib/active_support/json/encoding.rb +31 -45
- data/lib/active_support/key_generator.rb +32 -7
- data/lib/active_support/lazy_load_hooks.rb +33 -7
- data/lib/active_support/locale/en.yml +10 -4
- data/lib/active_support/log_subscriber/test_helper.rb +2 -2
- data/lib/active_support/log_subscriber.rb +101 -32
- data/lib/active_support/logger.rb +9 -60
- data/lib/active_support/logger_silence.rb +2 -26
- data/lib/active_support/logger_thread_safe_level.rb +24 -25
- data/lib/active_support/message_encryptor.rb +205 -58
- data/lib/active_support/message_encryptors.rb +141 -0
- data/lib/active_support/message_pack/cache_serializer.rb +23 -0
- data/lib/active_support/message_pack/extensions.rb +292 -0
- data/lib/active_support/message_pack/serializer.rb +63 -0
- data/lib/active_support/message_pack.rb +50 -0
- data/lib/active_support/message_verifier.rb +237 -86
- data/lib/active_support/message_verifiers.rb +135 -0
- data/lib/active_support/messages/codec.rb +65 -0
- data/lib/active_support/messages/metadata.rb +112 -46
- data/lib/active_support/messages/rotation_configuration.rb +2 -1
- data/lib/active_support/messages/rotation_coordinator.rb +93 -0
- data/lib/active_support/messages/rotator.rb +35 -32
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +15 -52
- data/lib/active_support/multibyte/unicode.rb +8 -122
- data/lib/active_support/multibyte.rb +1 -1
- data/lib/active_support/notifications/fanout.rb +310 -105
- data/lib/active_support/notifications/instrumenter.rb +113 -48
- data/lib/active_support/notifications.rb +56 -29
- data/lib/active_support/number_helper/number_converter.rb +15 -8
- data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +5 -5
- data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
- data/lib/active_support/number_helper/rounding_helper.rb +12 -32
- data/lib/active_support/number_helper.rb +379 -304
- data/lib/active_support/option_merger.rb +11 -18
- data/lib/active_support/ordered_hash.rb +4 -4
- data/lib/active_support/ordered_options.rb +23 -3
- data/lib/active_support/parameter_filter.rb +104 -75
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/rails.rb +1 -4
- data/lib/active_support/railtie.rb +90 -6
- data/lib/active_support/reloader.rb +12 -4
- data/lib/active_support/rescuable.rb +18 -16
- data/lib/active_support/ruby_features.rb +7 -0
- data/lib/active_support/secure_compare_rotator.rb +58 -0
- data/lib/active_support/security_utils.rb +19 -12
- data/lib/active_support/string_inquirer.rb +5 -3
- data/lib/active_support/subscriber.rb +23 -47
- data/lib/active_support/syntax_error_proxy.rb +70 -0
- data/lib/active_support/tagged_logging.rb +84 -23
- data/lib/active_support/test_case.rb +166 -27
- data/lib/active_support/testing/assertions.rb +73 -20
- data/lib/active_support/testing/autorun.rb +0 -2
- data/lib/active_support/testing/constant_stubbing.rb +32 -0
- data/lib/active_support/testing/deprecation.rb +53 -2
- data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
- data/lib/active_support/testing/isolation.rb +30 -29
- data/lib/active_support/testing/method_call_assertions.rb +24 -11
- data/lib/active_support/testing/parallelization/server.rb +82 -0
- data/lib/active_support/testing/parallelization/worker.rb +103 -0
- data/lib/active_support/testing/parallelization.rb +16 -95
- data/lib/active_support/testing/parallelize_executor.rb +81 -0
- data/lib/active_support/testing/stream.rb +4 -6
- data/lib/active_support/testing/strict_warnings.rb +39 -0
- data/lib/active_support/testing/tagged_logging.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +89 -19
- data/lib/active_support/time_with_zone.rb +105 -70
- data/lib/active_support/values/time_zone.rb +59 -26
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +4 -11
- data/lib/active_support/xml_mini/libxml.rb +5 -5
- data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
- data/lib/active_support/xml_mini/nokogiri.rb +5 -5
- data/lib/active_support/xml_mini/nokogirisax.rb +2 -2
- data/lib/active_support/xml_mini/rexml.rb +9 -2
- data/lib/active_support/xml_mini.rb +7 -6
- data/lib/active_support.rb +40 -1
- metadata +127 -40
- data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
- data/lib/active_support/core_ext/hash/compact.rb +0 -5
- data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
- data/lib/active_support/core_ext/marshal.rb +0 -24
- data/lib/active_support/core_ext/module/reachable.rb +0 -6
- data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
- data/lib/active_support/core_ext/range/include_range.rb +0 -9
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -23
- data/lib/active_support/core_ext/range/overlaps.rb +0 -10
- data/lib/active_support/core_ext/uri.rb +0 -25
- data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
- data/lib/active_support/per_thread_registry.rb +0 -60
@@ -2,19 +2,21 @@
|
|
2
2
|
|
3
3
|
require "openssl"
|
4
4
|
require "base64"
|
5
|
-
require "active_support/core_ext/array/extract_options"
|
6
5
|
require "active_support/core_ext/module/attribute_accessors"
|
6
|
+
require "active_support/messages/codec"
|
7
|
+
require "active_support/messages/rotator"
|
7
8
|
require "active_support/message_verifier"
|
8
|
-
require "active_support/messages/metadata"
|
9
9
|
|
10
10
|
module ActiveSupport
|
11
|
+
# = Active Support Message Encryptor
|
12
|
+
#
|
11
13
|
# MessageEncryptor is a simple way to encrypt values which get stored
|
12
14
|
# somewhere you don't trust.
|
13
15
|
#
|
14
16
|
# The cipher text and initialization vector are base64 encoded and returned
|
15
17
|
# to you.
|
16
18
|
#
|
17
|
-
# This can be used in situations similar to the
|
19
|
+
# This can be used in situations similar to the MessageVerifier, but
|
18
20
|
# where you don't want users to be able to determine the value of the payload.
|
19
21
|
#
|
20
22
|
# len = ActiveSupport::MessageEncryptor.key_len
|
@@ -24,6 +26,12 @@ module ActiveSupport
|
|
24
26
|
# encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
|
25
27
|
# crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
|
26
28
|
#
|
29
|
+
# The +decrypt_and_verify+ method will raise an
|
30
|
+
# +ActiveSupport::MessageEncryptor::InvalidMessage+ exception if the data
|
31
|
+
# provided cannot be decrypted or verified.
|
32
|
+
#
|
33
|
+
# crypt.decrypt_and_verify('not encrypted data') # => ActiveSupport::MessageEncryptor::InvalidMessage
|
34
|
+
#
|
27
35
|
# === Confining messages to a specific purpose
|
28
36
|
#
|
29
37
|
# By default any message can be used throughout your app. But they can also be
|
@@ -79,13 +87,13 @@ module ActiveSupport
|
|
79
87
|
# the above should be combined into:
|
80
88
|
#
|
81
89
|
# crypt.rotate old_secret, cipher: "aes-256-cbc"
|
82
|
-
class MessageEncryptor
|
83
|
-
prepend Messages::Rotator
|
90
|
+
class MessageEncryptor < Messages::Codec
|
91
|
+
prepend Messages::Rotator
|
84
92
|
|
85
93
|
cattr_accessor :use_authenticated_message_encryption, instance_accessor: false, default: false
|
86
94
|
|
87
95
|
class << self
|
88
|
-
def default_cipher
|
96
|
+
def default_cipher # :nodoc:
|
89
97
|
if use_authenticated_message_encryption
|
90
98
|
"aes-256-gcm"
|
91
99
|
else
|
@@ -94,7 +102,7 @@ module ActiveSupport
|
|
94
102
|
end
|
95
103
|
end
|
96
104
|
|
97
|
-
module NullSerializer
|
105
|
+
module NullSerializer # :nodoc:
|
98
106
|
def self.load(value)
|
99
107
|
value
|
100
108
|
end
|
@@ -104,57 +112,140 @@ module ActiveSupport
|
|
104
112
|
end
|
105
113
|
end
|
106
114
|
|
107
|
-
module NullVerifier #:nodoc:
|
108
|
-
def self.verify(value)
|
109
|
-
value
|
110
|
-
end
|
111
|
-
|
112
|
-
def self.generate(value)
|
113
|
-
value
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
115
|
class InvalidMessage < StandardError; end
|
118
116
|
OpenSSLCipherError = OpenSSL::Cipher::CipherError
|
119
117
|
|
118
|
+
AUTH_TAG_LENGTH = 16 # :nodoc:
|
119
|
+
SEPARATOR = "--" # :nodoc:
|
120
|
+
|
120
121
|
# Initialize a new MessageEncryptor. +secret+ must be at least as long as
|
121
122
|
# the cipher key size. For the default 'aes-256-gcm' cipher, this is 256
|
122
123
|
# bits. If you are using a user-entered secret, you can generate a suitable
|
123
|
-
# key by using
|
124
|
+
# key by using ActiveSupport::KeyGenerator or a similar key
|
124
125
|
# derivation function.
|
125
126
|
#
|
126
|
-
#
|
127
|
-
# This allows you to specify keys to encrypt and sign
|
127
|
+
# The first additional parameter is used as the signature key for
|
128
|
+
# MessageVerifier. This allows you to specify keys to encrypt and sign
|
129
|
+
# data. Ignored when using an AEAD cipher like 'aes-256-gcm'.
|
128
130
|
#
|
129
131
|
# ActiveSupport::MessageEncryptor.new('secret', 'signature_secret')
|
130
132
|
#
|
131
|
-
# Options
|
132
|
-
#
|
133
|
-
#
|
134
|
-
#
|
135
|
-
#
|
136
|
-
#
|
137
|
-
|
138
|
-
|
139
|
-
|
133
|
+
# ==== Options
|
134
|
+
#
|
135
|
+
# [+:cipher+]
|
136
|
+
# Cipher to use. Can be any cipher returned by +OpenSSL::Cipher.ciphers+.
|
137
|
+
# Default is 'aes-256-gcm'.
|
138
|
+
#
|
139
|
+
# [+:digest+]
|
140
|
+
# Digest used for signing. Ignored when using an AEAD cipher like
|
141
|
+
# 'aes-256-gcm'.
|
142
|
+
#
|
143
|
+
# [+:serializer+]
|
144
|
+
# The serializer used to serialize message data. You can specify any
|
145
|
+
# object that responds to +dump+ and +load+, or you can choose from
|
146
|
+
# several preconfigured serializers: +:marshal+, +:json_allow_marshal+,
|
147
|
+
# +:json+, +:message_pack_allow_marshal+, +:message_pack+.
|
148
|
+
#
|
149
|
+
# The preconfigured serializers include a fallback mechanism to support
|
150
|
+
# multiple deserialization formats. For example, the +:marshal+ serializer
|
151
|
+
# will serialize using +Marshal+, but can deserialize using +Marshal+,
|
152
|
+
# ActiveSupport::JSON, or ActiveSupport::MessagePack. This makes it easy
|
153
|
+
# to migrate between serializers.
|
154
|
+
#
|
155
|
+
# The +:marshal+, +:json_allow_marshal+, and +:message_pack_allow_marshal+
|
156
|
+
# serializers support deserializing using +Marshal+, but the others do
|
157
|
+
# not. Beware that +Marshal+ is a potential vector for deserialization
|
158
|
+
# attacks in cases where a message signing secret has been leaked. <em>If
|
159
|
+
# possible, choose a serializer that does not support +Marshal+.</em>
|
160
|
+
#
|
161
|
+
# The +:message_pack+ and +:message_pack_allow_marshal+ serializers use
|
162
|
+
# ActiveSupport::MessagePack, which can roundtrip some Ruby types that are
|
163
|
+
# not supported by JSON, and may provide improved performance. However,
|
164
|
+
# these require the +msgpack+ gem.
|
165
|
+
#
|
166
|
+
# When using \Rails, the default depends on +config.active_support.message_serializer+.
|
167
|
+
# Otherwise, the default is +:marshal+.
|
168
|
+
#
|
169
|
+
# [+:url_safe+]
|
170
|
+
# By default, MessageEncryptor generates RFC 4648 compliant strings
|
171
|
+
# which are not URL-safe. In other words, they can contain "+" and "/".
|
172
|
+
# If you want to generate URL-safe strings (in compliance with "Base 64
|
173
|
+
# Encoding with URL and Filename Safe Alphabet" in RFC 4648), you can
|
174
|
+
# pass +true+.
|
175
|
+
#
|
176
|
+
# [+:force_legacy_metadata_serializer+]
|
177
|
+
# Whether to use the legacy metadata serializer, which serializes the
|
178
|
+
# message first, then wraps it in an envelope which is also serialized. This
|
179
|
+
# was the default in \Rails 7.0 and below.
|
180
|
+
#
|
181
|
+
# If you don't pass a truthy value, the default is set using
|
182
|
+
# +config.active_support.use_message_serializer_for_metadata+.
|
183
|
+
def initialize(secret, sign_secret = nil, **options)
|
184
|
+
super(**options)
|
140
185
|
@secret = secret
|
141
|
-
@sign_secret = sign_secret
|
142
186
|
@cipher = options[:cipher] || self.class.default_cipher
|
143
|
-
@
|
144
|
-
@verifier =
|
145
|
-
|
187
|
+
@aead_mode = new_cipher.authenticated?
|
188
|
+
@verifier = if !@aead_mode
|
189
|
+
MessageVerifier.new(sign_secret || secret, **options, serializer: NullSerializer)
|
190
|
+
end
|
146
191
|
end
|
147
192
|
|
148
193
|
# Encrypt and sign a message. We need to sign the message in order to avoid
|
149
194
|
# padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/.
|
150
|
-
|
151
|
-
|
195
|
+
#
|
196
|
+
# ==== Options
|
197
|
+
#
|
198
|
+
# [+:expires_at+]
|
199
|
+
# The datetime at which the message expires. After this datetime,
|
200
|
+
# verification of the message will fail.
|
201
|
+
#
|
202
|
+
# message = encryptor.encrypt_and_sign("hello", expires_at: Time.now.tomorrow)
|
203
|
+
# encryptor.decrypt_and_verify(message) # => "hello"
|
204
|
+
# # 24 hours later...
|
205
|
+
# encryptor.decrypt_and_verify(message) # => nil
|
206
|
+
#
|
207
|
+
# [+:expires_in+]
|
208
|
+
# The duration for which the message is valid. After this duration has
|
209
|
+
# elapsed, verification of the message will fail.
|
210
|
+
#
|
211
|
+
# message = encryptor.encrypt_and_sign("hello", expires_in: 24.hours)
|
212
|
+
# encryptor.decrypt_and_verify(message) # => "hello"
|
213
|
+
# # 24 hours later...
|
214
|
+
# encryptor.decrypt_and_verify(message) # => nil
|
215
|
+
#
|
216
|
+
# [+:purpose+]
|
217
|
+
# The purpose of the message. If specified, the same purpose must be
|
218
|
+
# specified when verifying the message; otherwise, verification will fail.
|
219
|
+
# (See #decrypt_and_verify.)
|
220
|
+
def encrypt_and_sign(value, **options)
|
221
|
+
create_message(value, **options)
|
152
222
|
end
|
153
223
|
|
154
224
|
# Decrypt and verify a message. We need to verify the message in order to
|
155
225
|
# avoid padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/.
|
156
|
-
|
157
|
-
|
226
|
+
#
|
227
|
+
# ==== Options
|
228
|
+
#
|
229
|
+
# [+:purpose+]
|
230
|
+
# The purpose that the message was generated with. If the purpose does not
|
231
|
+
# match, +decrypt_and_verify+ will return +nil+.
|
232
|
+
#
|
233
|
+
# message = encryptor.encrypt_and_sign("hello", purpose: "greeting")
|
234
|
+
# encryptor.decrypt_and_verify(message, purpose: "greeting") # => "hello"
|
235
|
+
# encryptor.decrypt_and_verify(message) # => nil
|
236
|
+
#
|
237
|
+
# message = encryptor.encrypt_and_sign("bye")
|
238
|
+
# encryptor.decrypt_and_verify(message) # => "bye"
|
239
|
+
# encryptor.decrypt_and_verify(message, purpose: "greeting") # => nil
|
240
|
+
#
|
241
|
+
def decrypt_and_verify(message, **options)
|
242
|
+
catch_and_raise :invalid_message_format, as: InvalidMessage do
|
243
|
+
catch_and_raise :invalid_message_serialization, as: InvalidMessage do
|
244
|
+
catch_and_ignore :invalid_message_content do
|
245
|
+
read_message(message, **options)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
158
249
|
end
|
159
250
|
|
160
251
|
# Given a cipher, returns the key length of the cipher to help generate the key of desired size
|
@@ -162,8 +253,28 @@ module ActiveSupport
|
|
162
253
|
OpenSSL::Cipher.new(cipher).key_len
|
163
254
|
end
|
164
255
|
|
256
|
+
def create_message(value, **options) # :nodoc:
|
257
|
+
sign(encrypt(serialize_with_metadata(value, **options)))
|
258
|
+
end
|
259
|
+
|
260
|
+
def read_message(message, **options) # :nodoc:
|
261
|
+
deserialize_with_metadata(decrypt(verify(message)), **options)
|
262
|
+
end
|
263
|
+
|
264
|
+
def inspect # :nodoc:
|
265
|
+
"#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
|
266
|
+
end
|
267
|
+
|
165
268
|
private
|
166
|
-
def
|
269
|
+
def sign(data)
|
270
|
+
@verifier ? @verifier.create_message(data) : data
|
271
|
+
end
|
272
|
+
|
273
|
+
def verify(data)
|
274
|
+
@verifier ? @verifier.read_message(data) : data
|
275
|
+
end
|
276
|
+
|
277
|
+
def encrypt(data)
|
167
278
|
cipher = new_cipher
|
168
279
|
cipher.encrypt
|
169
280
|
cipher.key = @secret
|
@@ -172,22 +283,25 @@ module ActiveSupport
|
|
172
283
|
iv = cipher.random_iv
|
173
284
|
cipher.auth_data = "" if aead_mode?
|
174
285
|
|
175
|
-
encrypted_data = cipher.update(
|
286
|
+
encrypted_data = cipher.update(data)
|
176
287
|
encrypted_data << cipher.final
|
177
288
|
|
178
|
-
|
179
|
-
|
180
|
-
|
289
|
+
parts = [encrypted_data, iv]
|
290
|
+
parts << cipher.auth_tag(AUTH_TAG_LENGTH) if aead_mode?
|
291
|
+
|
292
|
+
join_parts(parts)
|
181
293
|
end
|
182
294
|
|
183
|
-
def
|
295
|
+
def decrypt(encrypted_message)
|
184
296
|
cipher = new_cipher
|
185
|
-
encrypted_data, iv, auth_tag = encrypted_message
|
297
|
+
encrypted_data, iv, auth_tag = extract_parts(encrypted_message)
|
186
298
|
|
187
299
|
# Currently the OpenSSL bindings do not raise an error if auth_tag is
|
188
300
|
# truncated, which would allow an attacker to easily forge it. See
|
189
301
|
# https://github.com/ruby/openssl/issues/63
|
190
|
-
|
302
|
+
if aead_mode? && auth_tag.bytesize != AUTH_TAG_LENGTH
|
303
|
+
throw :invalid_message_format, "truncated auth_tag"
|
304
|
+
end
|
191
305
|
|
192
306
|
cipher.decrypt
|
193
307
|
cipher.key = @secret
|
@@ -199,29 +313,62 @@ module ActiveSupport
|
|
199
313
|
|
200
314
|
decrypted_data = cipher.update(encrypted_data)
|
201
315
|
decrypted_data << cipher.final
|
316
|
+
rescue OpenSSLCipherError => error
|
317
|
+
throw :invalid_message_format, error
|
318
|
+
end
|
202
319
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
320
|
+
def length_after_encode(length_before_encode)
|
321
|
+
if @url_safe
|
322
|
+
(4 * length_before_encode / 3.0).ceil # length without padding
|
323
|
+
else
|
324
|
+
4 * (length_before_encode / 3.0).ceil # length with padding
|
325
|
+
end
|
207
326
|
end
|
208
327
|
|
209
|
-
def
|
210
|
-
|
328
|
+
def length_of_encoded_iv
|
329
|
+
@length_of_encoded_iv ||= length_after_encode(new_cipher.iv_len)
|
211
330
|
end
|
212
331
|
|
213
|
-
|
332
|
+
def length_of_encoded_auth_tag
|
333
|
+
@length_of_encoded_auth_tag ||= length_after_encode(AUTH_TAG_LENGTH)
|
334
|
+
end
|
214
335
|
|
215
|
-
def
|
216
|
-
|
336
|
+
def join_parts(parts)
|
337
|
+
parts.map! { |part| encode(part) }.join(SEPARATOR)
|
217
338
|
end
|
218
339
|
|
219
|
-
def
|
220
|
-
|
221
|
-
|
340
|
+
def extract_part(encrypted_message, rindex, length)
|
341
|
+
index = rindex - length
|
342
|
+
|
343
|
+
if encrypted_message[index - SEPARATOR.length, SEPARATOR.length] == SEPARATOR
|
344
|
+
encrypted_message[index, length]
|
222
345
|
else
|
223
|
-
|
346
|
+
throw :invalid_message_format, "missing separator"
|
224
347
|
end
|
225
348
|
end
|
349
|
+
|
350
|
+
def extract_parts(encrypted_message)
|
351
|
+
parts = []
|
352
|
+
rindex = encrypted_message.length
|
353
|
+
|
354
|
+
if aead_mode?
|
355
|
+
parts << extract_part(encrypted_message, rindex, length_of_encoded_auth_tag)
|
356
|
+
rindex -= SEPARATOR.length + length_of_encoded_auth_tag
|
357
|
+
end
|
358
|
+
|
359
|
+
parts << extract_part(encrypted_message, rindex, length_of_encoded_iv)
|
360
|
+
rindex -= SEPARATOR.length + length_of_encoded_iv
|
361
|
+
|
362
|
+
parts << encrypted_message[0, rindex]
|
363
|
+
|
364
|
+
parts.reverse!.map! { |part| decode(part) }
|
365
|
+
end
|
366
|
+
|
367
|
+
def new_cipher
|
368
|
+
OpenSSL::Cipher.new(@cipher)
|
369
|
+
end
|
370
|
+
|
371
|
+
attr_reader :aead_mode
|
372
|
+
alias :aead_mode? :aead_mode
|
226
373
|
end
|
227
374
|
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/messages/rotation_coordinator"
|
4
|
+
|
5
|
+
module ActiveSupport
|
6
|
+
class MessageEncryptors < Messages::RotationCoordinator
|
7
|
+
##
|
8
|
+
# :attr_accessor: transitional
|
9
|
+
#
|
10
|
+
# If true, the first two rotation option sets are swapped when building
|
11
|
+
# message encryptors. For example, with the following configuration, message
|
12
|
+
# encryptors will encrypt messages using <tt>serializer: Marshal, url_safe: true</tt>,
|
13
|
+
# and will able to decrypt messages that were encrypted using any of the
|
14
|
+
# three option sets:
|
15
|
+
#
|
16
|
+
# encryptors = ActiveSupport::MessageEncryptors.new { ... }
|
17
|
+
# encryptors.rotate(serializer: JSON, url_safe: true)
|
18
|
+
# encryptors.rotate(serializer: Marshal, url_safe: true)
|
19
|
+
# encryptors.rotate(serializer: Marshal, url_safe: false)
|
20
|
+
# encryptors.transitional = true
|
21
|
+
#
|
22
|
+
# This can be useful when performing a rolling deploy of an application,
|
23
|
+
# wherein servers that have not yet been updated must still be able to
|
24
|
+
# decrypt messages from updated servers. In such a scenario, first perform a
|
25
|
+
# rolling deploy with the new rotation (e.g. <tt>serializer: JSON, url_safe: true</tt>)
|
26
|
+
# as the first rotation and <tt>transitional = true</tt>. Then, after all
|
27
|
+
# servers have been updated, perform a second rolling deploy with
|
28
|
+
# <tt>transitional = false</tt>.
|
29
|
+
|
30
|
+
##
|
31
|
+
# :method: initialize
|
32
|
+
# :call-seq: initialize(&secret_generator)
|
33
|
+
#
|
34
|
+
# Initializes a new instance. +secret_generator+ must accept a salt and a
|
35
|
+
# +secret_length+ kwarg, and return a suitable secret (string) or secrets
|
36
|
+
# (array of strings). +secret_generator+ may also accept other arbitrary
|
37
|
+
# kwargs. If #rotate is called with any options matching those kwargs, those
|
38
|
+
# options will be passed to +secret_generator+ instead of to the message
|
39
|
+
# encryptor.
|
40
|
+
#
|
41
|
+
# encryptors = ActiveSupport::MessageEncryptors.new do |salt, secret_length:, base:|
|
42
|
+
# MySecretGenerator.new(base).generate(salt, secret_length)
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# encryptors.rotate(base: "...")
|
46
|
+
|
47
|
+
##
|
48
|
+
# :method: []
|
49
|
+
# :call-seq: [](salt)
|
50
|
+
#
|
51
|
+
# Returns a MessageEncryptor configured with a secret derived from the
|
52
|
+
# given +salt+, and options from #rotate. MessageEncryptor instances will
|
53
|
+
# be memoized, so the same +salt+ will return the same instance.
|
54
|
+
|
55
|
+
##
|
56
|
+
# :method: []=
|
57
|
+
# :call-seq: []=(salt, encryptor)
|
58
|
+
#
|
59
|
+
# Overrides a MessageEncryptor instance associated with a given +salt+.
|
60
|
+
|
61
|
+
##
|
62
|
+
# :method: rotate
|
63
|
+
# :call-seq:
|
64
|
+
# rotate(**options)
|
65
|
+
# rotate(&block)
|
66
|
+
#
|
67
|
+
# Adds +options+ to the list of option sets. Messages will be encrypted
|
68
|
+
# using the first set in the list. When decrypting, however, each set will
|
69
|
+
# be tried, in order, until one succeeds.
|
70
|
+
#
|
71
|
+
# Notably, the +:secret_generator+ option can specify a different secret
|
72
|
+
# generator than the one initially specified. The secret generator must
|
73
|
+
# respond to +call+, accept a salt and a +secret_length+ kwarg, and return
|
74
|
+
# a suitable secret (string) or secrets (array of strings). The secret
|
75
|
+
# generator may also accept other arbitrary kwargs.
|
76
|
+
#
|
77
|
+
# If any options match the kwargs of the operative secret generator, those
|
78
|
+
# options will be passed to the secret generator instead of to the message
|
79
|
+
# encryptor.
|
80
|
+
#
|
81
|
+
# For fine-grained per-salt rotations, a block form is supported. The block
|
82
|
+
# will receive the salt, and should return an appropriate options Hash. The
|
83
|
+
# block may also return +nil+ to indicate that the rotation does not apply
|
84
|
+
# to the given salt. For example:
|
85
|
+
#
|
86
|
+
# encryptors = ActiveSupport::MessageEncryptors.new { ... }
|
87
|
+
#
|
88
|
+
# encryptors.rotate do |salt|
|
89
|
+
# case salt
|
90
|
+
# when :foo
|
91
|
+
# { serializer: JSON, url_safe: true }
|
92
|
+
# when :bar
|
93
|
+
# { serializer: Marshal, url_safe: true }
|
94
|
+
# end
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# encryptors.rotate(serializer: Marshal, url_safe: false)
|
98
|
+
#
|
99
|
+
# # Uses `serializer: JSON, url_safe: true`.
|
100
|
+
# # Falls back to `serializer: Marshal, url_safe: false`.
|
101
|
+
# encryptors[:foo]
|
102
|
+
#
|
103
|
+
# # Uses `serializer: Marshal, url_safe: true`.
|
104
|
+
# # Falls back to `serializer: Marshal, url_safe: false`.
|
105
|
+
# encryptors[:bar]
|
106
|
+
#
|
107
|
+
# # Uses `serializer: Marshal, url_safe: false`.
|
108
|
+
# encryptors[:baz]
|
109
|
+
|
110
|
+
##
|
111
|
+
# :method: rotate_defaults
|
112
|
+
# :call-seq: rotate_defaults
|
113
|
+
#
|
114
|
+
# Invokes #rotate with the default options.
|
115
|
+
|
116
|
+
##
|
117
|
+
# :method: clear_rotations
|
118
|
+
# :call-seq: clear_rotations
|
119
|
+
#
|
120
|
+
# Clears the list of option sets.
|
121
|
+
|
122
|
+
##
|
123
|
+
# :method: on_rotation
|
124
|
+
# :call-seq: on_rotation(&callback)
|
125
|
+
#
|
126
|
+
# Sets a callback to invoke when a message is decrypted using an option set
|
127
|
+
# other than the first.
|
128
|
+
#
|
129
|
+
# For example, this callback could log each time it is called, and thus
|
130
|
+
# indicate whether old option sets are still in use or can be removed from
|
131
|
+
# rotation.
|
132
|
+
|
133
|
+
##
|
134
|
+
private
|
135
|
+
def build(salt, secret_generator:, secret_generator_options:, **options)
|
136
|
+
secret_length = MessageEncryptor.key_len(*options[:cipher])
|
137
|
+
secret = secret_generator.call(salt, secret_length: secret_length, **secret_generator_options)
|
138
|
+
MessageEncryptor.new(*Array(secret), **options)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "serializer"
|
4
|
+
|
5
|
+
module ActiveSupport
|
6
|
+
module MessagePack
|
7
|
+
module CacheSerializer
|
8
|
+
include Serializer
|
9
|
+
extend self
|
10
|
+
|
11
|
+
def load(dumped)
|
12
|
+
super
|
13
|
+
rescue ActiveSupport::MessagePack::MissingClassError
|
14
|
+
# Treat missing class as cache miss => return nil
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def install_unregistered_type_handler
|
19
|
+
Extensions.install_unregistered_type_fallback(message_pack_factory)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|