Commit Graph

62 Commits

Author SHA1 Message Date
Petrik
5117da2b65 Don't show secrets for MessageVerifier#inspect and KeyGenerator#inspect
Before:

```ruby
ActiveSupport::MessageVerifier.new(secret).inspect
"#<ActiveSupport::MessageVerifier:0x0000000104888038 ... @secret=\"\\xAF\\bFh]LV}q\\nl\\xB2U\\xB3 ... >"
ActiveSupport::KeyGenerator.new(secret).inspect
"#<ActiveSupport::KeyGenerator:0x0000000104888038 ... @secret=\"\\xAF\\bFh]LV}q\\nl\\xB2U\\xB3 ... >"
```

After:

```ruby
ActiveSupport::MessageVerifier::Aes256Gcm(secret).inspect
"#<ActiveSupport::MessageVerifier:0x0000000104888038>"
ActiveSupport::KeyGenerator::Aes256Gcm(secret).inspect
"#<ActiveSupport::KeyGenerator:0x0000000104888038>"
```
2023-07-06 21:51:22 +02:00
Jonathan Hefner
9fbfd8100b Unify Message{Encryptor,Verifier} serializer config
In #42843 and #42846, several config settings were added to control the
default serializer for `MessageEncryptor` and `MessageVerifier`, and to
provide a migration path from a default `Marshal` serializer to a
default `JSON` serializer:

* `config.active_support.default_message_encryptor_serializer`
  * Supports `:marshal`, `:hybrid`, or `:json`.
* `config.active_support.default_message_verifier_serializer`
  * Supports `:marshal`, `:hybrid`, or `:json`.
* `config.active_support.fallback_to_marshal_deserialization`
  * Affects `:hybrid` for both `MessageEncryptor` and `MessageVerifier`.
* `config.active_support.use_marshal_serialization`
  * Affects `:hybrid` for both `MessageEncryptor` and `MessageVerifier`.

This commit unifies those config settings into a single setting,
`config.active_support.message_serializer`, which supports `:marshal`,
`:json_allow_marshal`, and `:json` values.  So, for example,

  ```ruby
  config.active_support.default_message_encryptor_serializer = :hybrid
  config.active_support.default_message_verifier_serializer = :hybrid
  config.active_support.fallback_to_marshal_deserialization = true
  config.active_support.use_marshal_serialization = false
  ```

becomes

  ```ruby
  config.active_support.message_serializer = :json_allow_marshal
  ```

and

  ```ruby
  config.active_support.default_message_encryptor_serializer = :hybrid
  config.active_support.default_message_verifier_serializer = :hybrid
  config.active_support.fallback_to_marshal_deserialization = false
  config.active_support.use_marshal_serialization = false
  ```

becomes

  ```ruby
  config.active_support.message_serializer = :json
  ```

This commit also replaces `ActiveSupport::JsonWithMarshalFallback` with
`ActiveSupport::Messages::SerializerWithFallback`, which implements a
generic mechanism for serializer fallback.  The `:marshal` serializer
uses this mechanism too, so

  ```ruby
  config.active_support.default_message_encryptor_serializer = :hybrid
  config.active_support.default_message_verifier_serializer = :hybrid
  config.active_support.fallback_to_marshal_deserialization = false
  config.active_support.use_marshal_serialization = true
  ```

becomes

  ```ruby
  config.active_support.message_serializer = :marshal
  ```

Additionally, the logging behavior of `JsonWithMarshalFallback` has been
replaced with notifications which include the names of the intended and
actual serializers, as well as the serialized and deserialized message
data.  This provides a more targeted means of tracking serializer
fallback events.  It also allows the user to "silence" such events, if
desired, without an additional config setting.

All of these changes make it easier to add migration paths for new
serializers such as `ActiveSupport::MessagePack`.
2023-05-08 12:09:45 -05:00
Jonathan Hefner
d3917f5fdd Use throw for message error handling control flow
There are multiple points of failure when processing a message with
`MessageEncryptor` or `MessageVerifier`, and there several ways we might
want to handle those failures.  For example, swallowing a failure with
`MessageVerifier#verified`, or raising a specific exception with
`MessageVerifier#verify`, or conditionally ignoring a failure when
rotations are configured.

Prior to this commit, the _internal_ logic of handling failures was
implemented using a mix of `nil` return values and raised exceptions.
This commit reimplements the internal logic using `throw` and a few
precisely targeted `rescue`s.  This accomplishes several things:

* Allow rotation of serializers for `MessageVerifier`.  Previously,
  errors from a `MessageVerifier`'s initial serializer were never
  rescued.  Thus, the serializer could not be rotated:

    ```ruby
    old_verifier = ActiveSupport::MessageVerifier.new("secret", serializer: Marshal)
    new_verifier = ActiveSupport::MessageVerifier.new("secret", serializer: JSON)
    new_verifier.rotate(serializer: Marshal)

    message = old_verifier.generate("message")

    new_verifier.verify(message)
    # BEFORE:
    # => raises JSON::ParserError
    # AFTER:
    # => "message"
    ```

* Allow rotation of serializers for `MessageEncryptor` when using a
  non-standard initial serializer.  Similar to `MessageVerifier`, the
  serializer could not be rotated when the initial serializer raised an
  error other than `TypeError` or `JSON::ParserError`, such as
  `Psych::SyntaxError` or a custom error.

* Raise `MessageEncryptor::InvalidMessage` from `decrypt_and_verify`
  regardless of cipher.  Previously, when a `MessageEncryptor` was using
  a non-AEAD cipher such as AES-256-CBC, a corrupt or tampered message
  would raise `MessageVerifier::InvalidSignature` due to reliance on
  `MessageVerifier` for verification.  Now, the verification mechanism
  is transparent to the user:

    ```ruby
    encryptor = ActiveSupport::MessageEncryptor.new("x" * 32, cipher: "aes-256-gcm")
    message = encryptor.encrypt_and_sign("message")
    encryptor.decrypt_and_verify(message.next)
    # => raises ActiveSupport::MessageEncryptor::InvalidMessage

    encryptor = ActiveSupport::MessageEncryptor.new("x" * 32, cipher: "aes-256-cbc")
    message = encryptor.encrypt_and_sign("message")
    encryptor.decrypt_and_verify(message.next)
    # BEFORE:
    # => raises ActiveSupport::MessageVerifier::InvalidSignature
    # AFTER:
    # => raises ActiveSupport::MessageEncryptor::InvalidMessage
    ```

* Support `nil` original value when using `MessageVerifier#verify`.
  Previously, `MessageVerifier#verify` did not work with `nil` original
  values, though both `MessageVerifier#verified` and
  `MessageEncryptor#decrypt_and_verify` do:

    ```ruby
    encryptor = ActiveSupport::MessageEncryptor.new("x" * 32)
    message = encryptor.encrypt_and_sign(nil)

    encryptor.decrypt_and_verify(message)
    # => nil

    verifier = ActiveSupport::MessageVerifier.new("secret")
    message = verifier.generate(nil)

    verifier.verified(message)
    # => nil

    verifier.verify(message)
    # BEFORE:
    # => raises ActiveSupport::MessageVerifier::InvalidSignature
    # AFTER:
    # => nil
    ```

* Improve performance of verifying a message when it has expired and one
  or more rotations have been configured:

    ```ruby
    # frozen_string_literal: true
    require "benchmark/ips"
    require "active_support/all"

    verifier = ActiveSupport::MessageVerifier.new("new secret")
    verifier.rotate("old secret")

    message = verifier.generate({ "data" => "x" * 100 }, expires_at: 1.day.ago)

    Benchmark.ips do |x|
      x.report("expired message") do
        verifier.verified(message)
      end
    end
    ```

  __Before__

    ```
    Warming up --------------------------------------
         expired message     1.442k i/100ms
    Calculating -------------------------------------
         expired message     14.403k (± 1.7%) i/s -     72.100k in   5.007382s
    ```

  __After__

    ```
    Warming up --------------------------------------
         expired message     1.995k i/100ms
    Calculating -------------------------------------
         expired message     19.992k (± 2.0%) i/s -    101.745k in   5.091421s
    ```

Fixes #47185.
2023-02-12 15:16:25 -06:00
Jonathan Hefner
b95fe24a99 Factor rotator tests into dedicated classes
This commit factors tests related to `ActiveSupport::Messages::Rotator`
into `MessageEncryptorRotatorTest` and `MessageVerifierRotatorTest`
classes, with a shared `MessageRotatorTests` module.  In particular,
this:

* Replaces the `test_rotating_serializer` test in the
  `MessageEncryptorTest` class because it did not actually rotate the
  serializer.

* Removes the `test_on_rotation_is_called_and_returns_modified_messages`
  test from the `MessageEncryptorWithJsonSerializerTest` class because
  its only purpose is to override a test of the same name in the
  `MessageEncryptorTest` parent class, because the expected value must
  use string keys instead of symbol keys due to the serializer.

* Removes the `test_on_rotation_can_be_passed_at_the_constructor_level`
  test from the `MessageEncryptorWithJsonSerializerTest` class for the
  same reason as above.

* Removes the `test_on_rotation_option_takes_precedence_over_the_one_given_in_constructor`
  test from the `MessageEncryptorWithJsonSerializerTest` class for the
  same reason as above.

* Removes the `test_on_rotation_is_called_and_verified_returns_message`
  test from the `JsonSerializeMarshalFallbackMessageVerifierTest` class
  because it is rotating the secret and digest, and is not specific to
  the serializer.

* Removes the `test_on_rotation_is_called_and_verified_returns_message`
  test from the `DefaultMarshalSerializerMessageVerifierTest` class for
  the same reason as above.

* Adds thorough test coverage for the `on_rotation` option for both
  `MessageVerifier` and `MessageEncryptor` (previous test coverage was
  mostly just for `MessageEncryptor`), including coverage for when the
  `on_rotation` proc should _not_ be called.

* Adds test coverage for rotating the `url_safe` option, for both
  `MessageVerifier` and `MessageEncryptor`.
2023-02-08 15:15:20 -06:00
Jonathan Hefner
c103a4328a Refactor message metadata tests
Prior to this commit, there were several places to potentially add a new
message metadata test:

* `SharedMessageMetadataTests` module
* `MessageEncryptorMetadataTest` class
  (includes `SharedMessageMetadataTests`)
* `MessageEncryptorMetadataMarshalTest` class
  (subclasses `MessageEncryptorMetadataTest`)
* `MessageEncryptorMetadataJSONTest` class
  (subclasses `MessageEncryptorMetadataTest`)
* `MessageEncryptorMetadataJsonWithMarshalFallbackTest` class
  (subclasses `MessageEncryptorMetadataTest`)
* `MessageVerifierMetadataTest` class
  (includes `SharedMessageMetadataTests`)
* `MessageVerifierMetadataMarshalTest` class
  (subclasses `MessageVerifierMetadataTest`)
* `MessageVerifierMetadataJsonWithMarshalFallbackTest` class
  (subclasses `MessageVerifierMetadataTest`)
* `MessageVerifierMetadataJsonTest` class
  (subclasses `MessageVerifierMetadataTest`)
* `MessageVerifierMetadataCustomJSONTest` class
  (subclasses `MessageVerifierMetadataTest`)
* `MessageEncryptorMetadataNullSerializerTest` class
  (subclasses `MessageVerifierMetadataTest`)

This commit refactors the message metadata tests, reducing the list to:

* `MessageMetadataTests` module
* `MessageEncryptorMetadataTest` class (includes `MessageMetadataTests`)
* `MessageVerifierMetadataTest` class (includes `MessageMetadataTests`)

This makes it easier to add new tests, as well as new testing scenarios
(e.g. new encryptor / verifier configurations).

Additionally, this commit fixes two tests that were ineffective:

* The `test_passing_expires_in_less_than_a_second_is_not_expired` test
  (which is now simply a part of the "message expires with :expires_in"
  test) did not use the `with_usec: true` option.  Therefore, the time
  resulting from `travel 0.5.seconds` was truncated, effectively
  traveling 0 seconds.
* The `test_verify_with_use_standard_json_time_format_as_false` test
  (which is now named "expiration works with
  ActiveSupport.use_standard_json_time_format = false") did not specify
  an expiration time.  Therefore, the conditional branch that it was
  supposed to test was never exercised.
2022-12-28 14:22:48 -06:00
Jonathan Hefner
7094d0fc43 Rename :urlsafe option to :url_safe
Although Ruby provides `Base64.urlsafe_encode64` and
`Base64.urlsafe_decode64` methods, the technical term is "URL-safe",
with "URL" and "safe" as separate words.

For better readability, this commit renames the `:urlsafe` option for
`MessageEncryptor` and `MessageVerifier` to `:url_safe`.
2022-07-08 15:36:24 -05:00
Jonathan Hefner
73ef37902e Replace MessageVerifier :urlsafe option tests
The `test_urlsafe` test could not fail when `urlsafe: false` because the
serialized input data did not contain a bit sequence that would encode
to a non-URL-safe character.  The `test_no_padding` *could* fail when
`urlsafe: false`, except when the default serializer uses JSON, because
the serialized input data would be a multiple of 3 bytes, thus not
requiring any padding.

This commit replaces those tests with a falsifiable test using a
carefully chosen input string.
2022-06-28 14:19:28 -05:00
Shouichi Kamiya
08afa160a5 Fix urlsafe MessageVerifier not to include padding
urlsafe option was introduced to MessageVerifier in
09c3f36a962a7ffd350dfda643d2f980734cb5c9 but it can generate strings
containing padding character ("=") which is not urlsafe.

Fix not to pad when base64 encode.
2022-06-22 15:15:02 +09:00
Shouichi Kamiya
09c3f36a96 Add urlsafe option to MessageVerifier initializer
MessageVerifier uses Base64.strict_encode64 and generated strings are
not urlsafe. Though the goal is to make MessageVerifier generated
strings urlsafe, We can not simply switch to Base64.urlsafe_encode64
because it will be a breaking change. Thus, as a first step, urlsafe
option is added to the MessageVerifier initializer.
2022-06-21 21:40:35 +09:00
Aaron Patterson
ea9f0103fd
Revert "Revert "Merge pull request #42843 from buckley-w-david/message-verifier-default-serializer""
This reverts commit fd4e63cc28863b9d1b7cca2da80bb0b1e879c2d3.
2022-03-01 15:14:43 -08:00
Aaron Patterson
fd4e63cc28
Revert "Merge pull request #42843 from buckley-w-david/message-verifier-default-serializer"
This reverts commit a40d7815ac92c3d24a7e0fab9a4f487860937ec7, reversing
changes made to ad2529be4b5942b6f38ab83b9796e1976685edca.
2022-03-01 13:58:40 -08:00
Saba Kiaei
5256c90327 Switch ActiveSupport::MessageVerifier's default serialization to JSON 2022-03-01 13:02:17 -05:00
Vipul A M
c16c12c890 Fix parsing of jsonified expiry date when on AS::Message::Metadata, when ActiveSupport.use_standard_json_time_format = false
Fixes #39548
2020-06-06 02:12:19 +05:30
Michael Grosser
203998c916
allow running each test with pure ruby path/to/test.rb
also:
 - makes test dependencies obvious
 - makes tests runnable from within subfolders
2019-12-18 08:49:19 -06:00
Christian Gregg
6e0aa1887d
Fix Messages::Metadata#fresh? to handle parse_json_times = true
When `ActiveSupport.parse_json_times` is `true`,
`ActiveSupport::Messages::Medata.verify` will fail with a `TypeError` as
it does not pass a `String` to `Time.iso8601` as is expected.

This leads to hard-failure on retrieval of signed cookies with expiries.
2019-10-19 00:25:35 +02:00
Akira Matsuda
91712c5080 Fix Class#new + keyword arguments warnings 2019-09-09 01:02:39 +09:00
Akira Matsuda
af99f45b58 Revert "Class#new also separates keyword arguments and optional hash"
This reverts commit 72c1aa21c1f88dbe4c60413fb937df41987068e5.

This causes CI fails on Ruby < 2.7
2019-09-08 09:44:47 +09:00
Akira Matsuda
72c1aa21c1 Class#new also separates keyword arguments and optional hash 2019-09-08 08:58:01 +09:00
Ryuta Kamizono
0770c025bd Do not enforce no braces for hash argument
Non-kwargs parameters should be to be braced for https://github.com/ruby/ruby/pull/2395.
See https://bugs.ruby-lang.org/issues/14183 for details.

`Style/BracesAroundHashParameters` cop conflicts with that.

This removes `Style/BracesAroundHashParameters` cop and auto-correct to
following changes.

d94263f...5665fb5
2019-09-04 08:49:36 +09:00
Akira Matsuda
87b428c8e0 Passing in a Hash instance as kwargs parameters requires the "double splat" prefix
See https://bugs.ruby-lang.org/issues/14183 for details
2019-09-03 17:48:34 +09:00
Akira Matsuda
e69b5f500b Passing in a Hash instance as non-kwargs parameters has to be braced in Ruby 3.0 syntax
See https://bugs.ruby-lang.org/issues/14183 for details
2019-09-03 17:38:55 +09:00
Daniel Colson
a1ac18671a Replace assert ! with assert_not
This autocorrects the violations after adding a custom cop in
3305c78dcd.
2018-04-19 08:11:33 -04:00
Kasper Timm Hansen
b5aa2e0c49
Remove advanced key generator rotations from verifier/encryptor.
Noticed that verifiers and encryptors never once mentioned key generators
and salts but only concerned themselves with generated secrets.

Clears up the confusing naming around raw_key and secret as well. And
makes the rotation API follow the constructor signature to the letter.
2017-09-24 21:00:03 +02:00
Kasper Timm Hansen
20ba2e762c
Infer options from the primary verifier.
Spares users from passing in non-changing values explicitly.

[ Michael Coyne & Kasper Timm Hansen ]
2017-09-24 19:46:10 +02:00
Michael Coyne
39f8ca64ce Add key rotation message Encryptor and Verifier
Both classes now have a rotate method where new instances are added for
each call. When decryption or verification fails the next rotation
instance is tried.
2017-09-23 17:16:21 -04:00
Kasper Timm Hansen
e9275965f2
Perform self-serialization once metadata is involved.
Adds support for metadata even when using ActiveSupport::MessageEncryptor::NullSerializer.
2017-08-13 20:40:59 +02:00
Kasper Timm Hansen
b5ba8d715a Merge pull request #29907 from deivid-rodriguez/fix_flaky_message_verifier_test
Fix test failure in message verifier tests
2017-07-24 12:49:29 +02:00
David Rodríguez
e9a631aca9 Fix test failure in message verifier tests
Without this, I get the following result on my machine

```
# Running:

F

Failure:
MessageVerifierTest#test_backward_compatibility_messages_signed_without_metadata [/home/deivid/Code/rails/activesupport/test/message_verifier_test.rb:91]:
--- expected
+++ actual
@@ -1 +1 @@
-{:some=>"data", :now=>2010-01-01 00:00:00 +0100}
+{:some=>"data", :now=>2010-01-01 00:00:00 +0000}

bin/test test/message_verifier_test.rb:89
```
2017-07-24 11:58:18 +02:00
Assain
db5e6912da add metadata tests: verify method 2017-07-24 14:57:24 +05:30
Assain
3bf3653a69 add metadata support to message verifier 2017-07-19 23:43:42 +05:30
Koichi ITO
ac717d65a3 [Active Support] rubocop -a --only Layout/EmptyLineAfterMagicComment 2017-07-11 13:12:32 +09:00
Kir Shatrov
72950568dd Use frozen-string-literal in ActiveSupport 2017-07-09 15:08:29 +03:00
Matthew Draper
87b3e226d6 Revert "Merge pull request #29540 from kirs/rubocop-frozen-string"
This reverts commit 3420a14590c0e6915d8b6c242887f74adb4120f9, reversing
changes made to afb66a5a598ce4ac74ad84b125a5abf046dcf5aa.
2017-07-02 02:15:17 +09:30
Kir Shatrov
cfade1ec7e Enforce frozen string in Rubocop 2017-07-01 02:11:03 +03:00
Akira Matsuda
a46b2f8911 assert_equal takes expectation first 2016-12-26 11:04:56 +09:00
Xavier Noria
b326e82dc0 applies remaining conventions across the project 2016-08-06 20:20:22 +02:00
Xavier Noria
411ccbdab2 remove redundant curlies from hash arguments 2016-08-06 19:44:11 +02:00
Xavier Noria
5c315a8fa6 modernizes hash syntax in activesupport 2016-08-06 19:38:33 +02:00
Xavier Noria
a731125f12 applies new string literal convention in activesupport/test
The current code base is not uniform. After some discussion,
we have chosen to go with double quotes by default.
2016-08-06 18:10:53 +02:00
Roque Pinel
4092f997f0 Fix the message verifier encoding issue
```ruby
verifier = ActiveSupport::MessageVerifier.new('secret')
verifier.verify("\xff") # => ArgumentError: invalid byte sequence in UTF-8
```
2015-06-14 11:35:27 -04:00
claudiob
e428ddecec Remove "rescue" clause around "require 'openssl'"
Some `require 'openssl'` statements were surrounded by `rescue` blocks to deal with Ruby versions that did not support `OpenSSL::Digest::SHA1` or `OpenSSL::PKCS5`.

[As @jeremy explains](a6a0904fcb (commitcomment-8826666)) in the original commit:

> If jruby didn't have jruby-openssl gem, the require wouldn't work. Not sure whether either of these are still relevant today.

According to the [release notes for JRuby 1.7.13](http://www.jruby.org/2014/06/24/jruby-1-7-13.html):

> jruby-openssl 0.9.5 bundled

which means the above `rescue` block is not needed anymore.

All the Ruby versions supported by the current version of Rails provide those OpenSSL libraries, so Travis CI should also be happy by removing the `rescue` blocks.

---

Just to confirm, with JRuby:

    $ ruby --version #=> jruby 1.7.16.1 (1.9.3p392) 2014-10-28 4e93f31 on Java HotSpot(TM) 64-Bit Server VM 1.8.0_20-b26 +jit [darwin-x86_64]
    $ irb
    irb(main):001:0> require 'openssl' #=> true
    irb(main):002:0> OpenSSL::Digest::SHA1 #=> OpenSSL::Digest::SHA1
    irb(main):003:0> OpenSSL::PKCS5 # => OpenSSL::PKCS5

And with Ruby 2.1:

    $ ruby --version #=> ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin13.0]
    $ irb
    irb(main):001:0> require 'openssl' #=> true
    irb(main):002:0> OpenSSL::Digest::SHA1 #=> OpenSSL::Digest::SHA1
    irb(main):003:0> OpenSSL::PKCS5 #=> OpenSSL::PKCS5
2014-12-03 21:58:02 -08:00
Logan Leger
7ad541f955 Add #verified and #valid_message? to MessageVerifier
This commit adds a `#verified` method to
`ActiveSupport::MessageVerifier` which will return either `false` when
it encounters an error or the message. `#verify` continues to raise an
`InvalidSignature` exception on error.

This commit also adds a convenience boolean method on `MessageVerifier`
as a way to check if a message is valid without performing the
decoding.
2014-12-01 21:43:16 -06:00
Kostiantyn Kahanskyi
4bf9d1938b MessageVerifier raises an appropriate exception if the secret is nil
Otherwise this will lead to another error later on 
when generating a signature:
TypeError (no implicit conversion of nil into String).
2014-09-12 15:09:00 +02:00
Vipul A M
1f80e8d685 PR #10635 introduces rescue from ArgumentError thrown by Base64.strict_decode64.
This broke natural order of things for `StaleSessionCheck#stale_session_check!` which tried auto_loading a class based on `ArgumentError` message , and later retrying the `Marshal#load` of class, successfully allowing auto_loading.

  This PR tries to fix this behavior by forwarding `ArgumentError` 's not raised  by `Base64.strict_decode64` , as is, ahead to `StaleSessionCheck#stale_session_check!`
2013-12-12 22:15:42 +05:30
Rafael Mendonça França
016eb65d7e ✂️
[ci skip]
2013-12-02 20:02:06 -02:00
Ryan Glover
a64d4e85e3 Standardize all JSON encoded times to use 3 decimal fractional seconds 2013-11-07 10:43:51 -05:00
Ryan Davis
adc049b06f Fixed bad tests to clean up after themselves. 2013-05-03 15:37:18 -07:00
José Valim
6a6fc4e1db Remove deprecations from Active Support. 2011-12-20 15:18:42 +01:00
Willem van Bergen
6d520803ee Test deprecation warning when not using an options hash as second parameter. 2011-09-15 15:51:30 -04:00
Willem van Bergen
41fea03342 Use an options hash to specify digest/cipher algorithm and a serializer for MessageVerifier and MessageEncryptor. 2011-09-15 14:27:12 -04:00