dd12bf3210
This commit updates the MD4 digest used in `MessageEncryptorsTest` because OpenSSL 3 moves `MD4` digests as legacy provider. https://www.openssl.org/news/openssl-3.0-notes.html Major changes between OpenSSL 3.0.0 and OpenSSL 3.0.1 [14 Dec 2021] > Moved the EVP digests MD2, MD4, MDC2, WHIRLPOOL and RIPEMD-160 to the legacy provider. Since Rails CI migrates from Debian 11 "bullseye" based Ruby 3.1 and 3.2 images to Debian 12 "bookworm" based ones, that supports OpenSSL 3, `MessageEncryptorsTest` raises `EVP_DigestSignInit: unsupported (OpenSSL::HMACError)` and `Digest initialization failed: initialization error (OpenSSL::Digest::DigestError)` https://packages.debian.org/bookworm/openssl > Package: openssl (3.0.9-1) `SHA256` is listed as `Provided:` via `openssl list -digest-algorithms` output, so it is safe to use `SHA256` digest in the `MessageEncryptorsTest`. ``` { 2.16.840.1.101.3.4.2.1, SHA-256, SHA2-256, SHA256 } @ default ``` ``` $ openssl version OpenSSL 3.0.8 7 Feb 2023 (Library: OpenSSL 3.0.8 7 Feb 2023) $ openssl list -digest-algorithms Legacy: RSA-MD4 => MD4 RSA-MD5 => MD5 RSA-RIPEMD160 => RIPEMD160 RSA-SHA1 => SHA1 RSA-SHA1-2 => RSA-SHA1 RSA-SHA224 => SHA224 RSA-SHA256 => SHA256 RSA-SHA3-224 => SHA3-224 RSA-SHA3-256 => SHA3-256 RSA-SHA3-384 => SHA3-384 RSA-SHA3-512 => SHA3-512 RSA-SHA384 => SHA384 RSA-SHA512 => SHA512 RSA-SHA512/224 => SHA512-224 RSA-SHA512/256 => SHA512-256 RSA-SM3 => SM3 BLAKE2b512 BLAKE2s256 id-rsassa-pkcs1-v1_5-with-sha3-224 => SHA3-224 id-rsassa-pkcs1-v1_5-with-sha3-256 => SHA3-256 id-rsassa-pkcs1-v1_5-with-sha3-384 => SHA3-384 id-rsassa-pkcs1-v1_5-with-sha3-512 => SHA3-512 MD4 md4WithRSAEncryption => MD4 MD5 MD5-SHA1 md5WithRSAEncryption => MD5 ripemd => RIPEMD160 RIPEMD160 ripemd160WithRSA => RIPEMD160 rmd160 => RIPEMD160 SHA1 sha1WithRSAEncryption => SHA1 SHA224 sha224WithRSAEncryption => SHA224 SHA256 sha256WithRSAEncryption => SHA256 SHA3-224 SHA3-256 SHA3-384 SHA3-512 SHA384 sha384WithRSAEncryption => SHA384 SHA512 SHA512-224 sha512-224WithRSAEncryption => SHA512-224 SHA512-256 sha512-256WithRSAEncryption => SHA512-256 sha512WithRSAEncryption => SHA512 SHAKE128 SHAKE256 SM3 sm3WithRSAEncryption => SM3 ssl3-md5 => MD5 ssl3-sha1 => SHA1 whirlpool Provided: { 2.16.840.1.101.3.4.2.10, SHA3-512 } @ default { 1.3.6.1.4.1.1722.12.2.2.8, BLAKE2S-256, BLAKE2s256 } @ default { 1.2.156.10197.1.401, SM3 } @ default { 2.16.840.1.101.3.4.2.8, SHA3-256 } @ default { 2.16.840.1.101.3.4.2.7, SHA3-224 } @ default { 2.16.840.1.101.3.4.2.2, SHA-384, SHA2-384, SHA384 } @ default { 2.16.840.1.101.3.4.2.3, SHA-512, SHA2-512, SHA512 } @ default { 2.16.840.1.101.3.4.2.5, SHA-512/224, SHA2-512/224, SHA512-224 } @ default { 2.16.840.1.101.3.4.2.12, SHAKE-256, SHAKE256 } @ default { 2.16.840.1.101.3.4.2.1, SHA-256, SHA2-256, SHA256 } @ default { 1.3.14.3.2.26, SHA-1, SHA1, SSL3-SHA1 } @ default { 2.16.840.1.101.3.4.2.9, SHA3-384 } @ default { 2.16.840.1.101.3.4.2.11, SHAKE-128, SHAKE128 } @ default MD5-SHA1 @ default { 1.3.36.3.2.1, RIPEMD, RIPEMD-160, RIPEMD160, RMD160 } @ default { 1.2.840.113549.2.5, MD5, SSL3-MD5 } @ default { 2.16.840.1.101.3.4.2.4, SHA-224, SHA2-224, SHA224 } @ default { 1.3.6.1.4.1.1722.12.2.1.16, BLAKE2B-512, BLAKE2b512 } @ default { 2.16.840.1.101.3.4.2.6, SHA-512/256, SHA2-512/256, SHA512-256 } @ default { KECCAK-KMAC-128, KECCAK-KMAC128 } @ default { KECCAK-KMAC-256, KECCAK-KMAC256 } @ default NULL @ default $ ``` Fix #48483
223 lines
8.7 KiB
Ruby
223 lines
8.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module RotationCoordinatorTests
|
|
extend ActiveSupport::Concern
|
|
|
|
included do
|
|
setup do
|
|
@coordinator = make_coordinator.rotate_defaults
|
|
end
|
|
|
|
test "builds working codecs" do
|
|
codec = @coordinator["salt"]
|
|
other_codec = @coordinator["other salt"]
|
|
|
|
assert_equal "message", roundtrip("message", codec)
|
|
assert_nil roundtrip("message", codec, other_codec)
|
|
end
|
|
|
|
test "memoizes codecs" do
|
|
assert_same @coordinator["salt"], @coordinator["salt"]
|
|
end
|
|
|
|
test "can override codecs" do
|
|
@coordinator["other salt"] = @coordinator["salt"]
|
|
assert_same @coordinator["salt"], @coordinator["other salt"]
|
|
end
|
|
|
|
test "configures codecs with rotations" do
|
|
@coordinator.rotate(digest: "MD5")
|
|
codec = @coordinator["salt"]
|
|
obsolete_codec = (make_coordinator.rotate(digest: "MD5"))["salt"]
|
|
|
|
assert_equal "message", roundtrip("message", obsolete_codec, codec)
|
|
assert_nil roundtrip("message", codec, obsolete_codec)
|
|
end
|
|
|
|
test "raises when building a codec and no rotations are configured" do
|
|
assert_raises { make_coordinator["salt"] }
|
|
end
|
|
|
|
test "#rotate supports a block" do
|
|
coordinator = make_coordinator.rotate do |salt|
|
|
{ digest: salt == "salt" ? "SHA1" : "MD5" }
|
|
end
|
|
|
|
sha1_coordinator = make_coordinator.rotate(digest: "SHA1")
|
|
md5_coordinator = make_coordinator.rotate(digest: "MD5")
|
|
|
|
assert_equal "message", roundtrip("message", coordinator["salt"], sha1_coordinator["salt"])
|
|
assert_nil roundtrip("message", coordinator["salt"], md5_coordinator["salt"])
|
|
|
|
assert_equal "message", roundtrip("message", coordinator["other salt"], md5_coordinator["other salt"])
|
|
assert_nil roundtrip("message", coordinator["other salt"], sha1_coordinator["other salt"])
|
|
end
|
|
|
|
test "#rotate block receives salt in its original form" do
|
|
coordinator = make_coordinator.rotate do |salt|
|
|
assert_equal :salt, salt
|
|
{}
|
|
end
|
|
|
|
coordinator[:salt]
|
|
end
|
|
|
|
test "#rotate raises when both a block and options are provided" do
|
|
assert_raises ArgumentError do
|
|
make_coordinator.rotate(digest: "MD5") { {} }
|
|
end
|
|
end
|
|
|
|
test "#rotate block can return nil to skip a rotation for specific salts" do
|
|
coordinator = make_coordinator.rotate(digest: "SHA1")
|
|
coordinator.rotate do |salt|
|
|
{ digest: "MD5" } if salt == "salt"
|
|
end
|
|
|
|
sha1_coordinator = make_coordinator.rotate(digest: "SHA1")
|
|
md5_coordinator = make_coordinator.rotate(digest: "MD5")
|
|
|
|
assert_equal "message", roundtrip("message", sha1_coordinator["salt"], coordinator["salt"])
|
|
assert_equal "message", roundtrip("message", md5_coordinator["salt"], coordinator["salt"])
|
|
|
|
assert_equal "message", roundtrip("message", sha1_coordinator["other salt"], coordinator["other salt"])
|
|
assert_nil roundtrip("message", md5_coordinator["other salt"], coordinator["other salt"])
|
|
end
|
|
|
|
test "raises when building a codec and no rotations are configured for a specific salt" do
|
|
coordinator = make_coordinator.rotate do |salt|
|
|
{ digest: "MD5" } if salt == "salt"
|
|
end
|
|
|
|
assert_nothing_raised { coordinator["salt"] }
|
|
error = assert_raises { coordinator["other salt"] }
|
|
assert_match "other salt", error.message
|
|
end
|
|
|
|
test "#transitional swaps the first two rotations when enabled" do
|
|
coordinator = make_coordinator.rotate(digest: "SHA1")
|
|
coordinator.rotate(digest: "MD5")
|
|
coordinator.rotate(digest: "SHA256")
|
|
coordinator.transitional = true
|
|
|
|
codec = coordinator["salt"]
|
|
sha1_codec = (make_coordinator.rotate(digest: "SHA1"))["salt"]
|
|
md5_codec = (make_coordinator.rotate(digest: "MD5"))["salt"]
|
|
sha256_codec = (make_coordinator.rotate(digest: "SHA256"))["salt"]
|
|
|
|
assert_equal "message", roundtrip("message", codec, md5_codec)
|
|
assert_nil roundtrip("message", codec, sha1_codec)
|
|
|
|
assert_equal "message", roundtrip("message", sha1_codec, codec)
|
|
assert_equal "message", roundtrip("message", md5_codec, codec)
|
|
assert_equal "message", roundtrip("message", sha256_codec, codec)
|
|
end
|
|
|
|
test "#transitional works with a single rotation" do
|
|
@coordinator.transitional = true
|
|
|
|
assert_nothing_raised do
|
|
codec = @coordinator["salt"]
|
|
assert_equal "message", roundtrip("message", codec)
|
|
|
|
different_codec = (make_coordinator.rotate(digest: "MD5"))["salt"]
|
|
assert_nil roundtrip("message", different_codec, codec)
|
|
end
|
|
end
|
|
|
|
test "#transitional treats a nil first rotation as a new rotation" do
|
|
coordinator = make_coordinator
|
|
coordinator.rotate do |salt| # (3) Finally, one salt upgraded to SHA1
|
|
{ digest: "SHA1" } if salt == "salt"
|
|
end
|
|
coordinator.rotate(digest: "MD5") # (2) Then, everything upgraded to MD5
|
|
coordinator.rotate(digest: "SHA256") # (1) Originally, everything used SHA256
|
|
coordinator.transitional = true
|
|
|
|
sha1_coordinator = make_coordinator.rotate(digest: "SHA1")
|
|
md5_coordinator = make_coordinator.rotate(digest: "MD5")
|
|
|
|
# "salt" encodes with MD5 and can decode SHA1 (i.e. [SHA1, MD5, SHA256] => [MD5, SHA1, SHA256])
|
|
assert_equal "message", roundtrip("message", coordinator["salt"], md5_coordinator["salt"])
|
|
assert_equal "message", roundtrip("message", sha1_coordinator["salt"], coordinator["salt"])
|
|
|
|
# "other salt" encodes with MD5 and cannot decode SHA1 (i.e. [nil, MD5, SHA256] => [MD5, SHA256])
|
|
assert_equal "message", roundtrip("message", coordinator["other salt"], md5_coordinator["other salt"])
|
|
assert_nil roundtrip("message", sha1_coordinator["other salt"], coordinator["other salt"])
|
|
end
|
|
|
|
test "#transitional swaps the first rotation with the next non-nil rotation" do
|
|
coordinator = make_coordinator
|
|
coordinator.rotate(digest: "SHA1") # (3) Finally, everything upgraded to SHA1
|
|
coordinator.rotate do |salt| # (2) Then, one salt upgraded to SHA1
|
|
{ digest: "SHA1" } if salt == "salt"
|
|
end
|
|
coordinator.rotate(digest: "MD5") # (1) Originally, everything used MD5
|
|
coordinator.transitional = true
|
|
|
|
sha1_coordinator = make_coordinator.rotate(digest: "SHA1")
|
|
md5_coordinator = make_coordinator.rotate(digest: "MD5")
|
|
|
|
# "salt" encodes with SHA1 and can decode SHA1 (i.e. [SHA1, SHA1, MD5] => [SHA1, MD5])
|
|
assert_equal "message", roundtrip("message", coordinator["salt"], sha1_coordinator["salt"])
|
|
assert_equal "message", roundtrip("message", sha1_coordinator["salt"], coordinator["salt"])
|
|
|
|
# "other salt" encodes with MD5 and can decode SHA1 (i.e. [SHA1, nil, MD5] => [MD5, SHA1])
|
|
assert_equal "message", roundtrip("message", coordinator["other salt"], md5_coordinator["other salt"])
|
|
assert_equal "message", roundtrip("message", sha1_coordinator["other salt"], coordinator["other salt"])
|
|
end
|
|
|
|
test "can clear rotations" do
|
|
@coordinator.clear_rotations.rotate(digest: "MD5")
|
|
codec = @coordinator["salt"]
|
|
similar_codec = (make_coordinator.rotate(digest: "MD5"))["salt"]
|
|
|
|
assert_equal "message", roundtrip("message", codec, similar_codec)
|
|
end
|
|
|
|
test "configures codecs with on_rotation" do
|
|
rotated = 0
|
|
@coordinator.on_rotation { rotated += 1 }
|
|
@coordinator.rotate(digest: "MD5")
|
|
codec = @coordinator["salt"]
|
|
obsolete_codec = (make_coordinator.rotate(digest: "MD5"))["salt"]
|
|
|
|
assert_equal "message", roundtrip("message", obsolete_codec, codec)
|
|
assert_equal 1, rotated
|
|
end
|
|
|
|
test "rotation options are deduped" do
|
|
coordinator = make_coordinator
|
|
coordinator.rotate(digest: "SHA1") # (3) Finally, everything upgraded to SHA1
|
|
coordinator.rotate do |salt| # (2) Then, one salt upgraded to SHA1
|
|
{ digest: "SHA1" } if salt == "salt"
|
|
end
|
|
coordinator.rotate(digest: "MD5") # (1) Originally, everything used MD5
|
|
|
|
rotated = 0
|
|
coordinator.on_rotation { rotated += 1 }
|
|
|
|
codec = coordinator["salt"]
|
|
md5_codec = (make_coordinator.rotate(digest: "MD5"))["salt"]
|
|
|
|
assert_equal "message", roundtrip("message", md5_codec, codec)
|
|
assert_equal 1, rotated # SHA1 tried only once
|
|
end
|
|
|
|
test "prevents adding a rotation after rotations have been applied" do
|
|
@coordinator["salt"]
|
|
assert_raises { @coordinator.rotate(digest: "MD5") }
|
|
end
|
|
|
|
test "prevents clearing rotations after rotations have been applied" do
|
|
@coordinator["salt"]
|
|
assert_raises { @coordinator.clear_rotations }
|
|
end
|
|
|
|
test "prevents changing on_rotation after on_rotation has been applied" do
|
|
@coordinator["salt"]
|
|
assert_raises { @coordinator.on_rotation { "this block will not be evaluated" } }
|
|
end
|
|
end
|
|
end
|