Skip to content

Commit

Permalink
Merge pull request rails#54040 from mrpasquini/md5_config
Browse files Browse the repository at this point in the history
Add config option for MD5 class to support FIPS compliance
  • Loading branch information
byroot authored Jan 4, 2025
2 parents 0989745 + bbc0cf1 commit e64e5d3
Show file tree
Hide file tree
Showing 20 changed files with 72 additions and 44 deletions.
7 changes: 7 additions & 0 deletions activestorage/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
* Add support for alternative MD5 implementation through `config.active_storage.checksum_implementation`.

Also automatically degrade to using the slower `Digest::MD5` implementation if `OpenSSL::Digest::MD5`
is found to be disabled because of OpenSSL FIPS mode.

*Matt Pasquini*, *Jean Boussier*

* A Blob will no longer autosave associated Attachment.

This fixes an issue where a record with an attachment would have
Expand Down
2 changes: 1 addition & 1 deletion activestorage/app/models/active_storage/blob.rb
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ def service
def compute_checksum_in_chunks(io)
raise ArgumentError, "io must be rewindable" unless io.respond_to?(:rewind)

OpenSSL::Digest::MD5.new.tap do |checksum|
ActiveStorage.checksum_implementation.new.tap do |checksum|
read_buffer = "".b
while io.read(5.megabytes, read_buffer)
checksum << read_buffer
Expand Down
9 changes: 9 additions & 0 deletions activestorage/lib/active_storage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,15 @@ module ActiveStorage

mattr_accessor :track_variants, default: false

singleton_class.attr_accessor :checksum_implementation
@checksum_implementation = OpenSSL::Digest::MD5
begin
@checksum_implementation.hexdigest("test")
rescue # OpenSSL may have MD5 disabled
require "digest/md5"
@checksum_implementation = Digest::MD5
end

mattr_accessor :video_preview_arguments, default: "-y -vframes 1 -f image2"

module Transformers
Expand Down
2 changes: 1 addition & 1 deletion activestorage/lib/active_storage/downloader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def download(key, file)
end

def verify_integrity_of(file, checksum:)
unless OpenSSL::Digest::MD5.file(file).base64digest == checksum
unless ActiveStorage.checksum_implementation.file(file).base64digest == checksum
raise ActiveStorage::IntegrityError
end
end
Expand Down
3 changes: 3 additions & 0 deletions activestorage/lib/active_storage/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ class Engine < Rails::Engine # :nodoc:
ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream"
ActiveStorage.video_preview_arguments = app.config.active_storage.video_preview_arguments || "-y -vframes 1 -f image2"
ActiveStorage.track_variants = app.config.active_storage.track_variants || false
if app.config.active_storage.checksum_implementation
ActiveStorage.checksum_implementation = app.config.active_storage.checksum_implementation
end
end
end

Expand Down
2 changes: 1 addition & 1 deletion activestorage/lib/active_storage/service/disk_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def make_path_for(key)
end

def ensure_integrity_of(key, checksum)
unless OpenSSL::Digest::MD5.file(path_for(key)).base64digest == checksum
unless ActiveStorage.checksum_implementation.file(path_for(key)).base64digest == checksum
delete key
raise ActiveStorage::IntegrityError
end
Expand Down
10 changes: 5 additions & 5 deletions activestorage/test/controllers/direct_uploads_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ActiveStorage::S3DirectUploadsControllerTest < ActionDispatch::Integration
end

test "creating new direct upload" do
checksum = OpenSSL::Digest::MD5.base64digest("Hello")
checksum = ActiveStorage.checksum_implementation.base64digest("Hello")
metadata = {
"foo" => "bar",
"my_key_1" => "my_value_1",
Expand Down Expand Up @@ -61,7 +61,7 @@ class ActiveStorage::GCSDirectUploadsControllerTest < ActionDispatch::Integratio
end

test "creating new direct upload" do
checksum = OpenSSL::Digest::MD5.base64digest("Hello")
checksum = ActiveStorage.checksum_implementation.base64digest("Hello")
metadata = {
"foo" => "bar",
"my_key_1" => "my_value_1",
Expand Down Expand Up @@ -106,7 +106,7 @@ class ActiveStorage::AzureStorageDirectUploadsControllerTest < ActionDispatch::I
end

test "creating new direct upload" do
checksum = OpenSSL::Digest::MD5.base64digest("Hello")
checksum = ActiveStorage.checksum_implementation.base64digest("Hello")
metadata = {
"foo" => "bar",
"my_key_1" => "my_value_1",
Expand Down Expand Up @@ -136,7 +136,7 @@ class ActiveStorage::AzureStorageDirectUploadsControllerTest < ActionDispatch::I

class ActiveStorage::DiskDirectUploadsControllerTest < ActionDispatch::IntegrationTest
test "creating new direct upload" do
checksum = OpenSSL::Digest::MD5.base64digest("Hello")
checksum = ActiveStorage.checksum_implementation.base64digest("Hello")
metadata = {
"foo" => "bar",
"my_key_1" => "my_value_1",
Expand All @@ -161,7 +161,7 @@ class ActiveStorage::DiskDirectUploadsControllerTest < ActionDispatch::Integrati
end

test "creating new direct upload does not include root in json" do
checksum = OpenSSL::Digest::MD5.base64digest("Hello")
checksum = ActiveStorage.checksum_implementation.base64digest("Hello")
metadata = {
"foo" => "bar",
"my_key_1" => "my_value_1",
Expand Down
10 changes: 5 additions & 5 deletions activestorage/test/controllers/disk_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest

test "directly uploading blob with integrity" do
data = "Something else entirely!"
blob = create_blob_before_direct_upload byte_size: data.size, checksum: OpenSSL::Digest::MD5.base64digest(data)
blob = create_blob_before_direct_upload byte_size: data.size, checksum: ActiveStorage.checksum_implementation.base64digest(data)

put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "text/plain" }
assert_response :no_content
Expand All @@ -83,7 +83,7 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest

test "directly uploading blob without integrity" do
data = "Something else entirely!"
blob = create_blob_before_direct_upload byte_size: data.size, checksum: OpenSSL::Digest::MD5.base64digest("bad data")
blob = create_blob_before_direct_upload byte_size: data.size, checksum: ActiveStorage.checksum_implementation.base64digest("bad data")

put blob.service_url_for_direct_upload, params: data
assert_response :unprocessable_entity
Expand All @@ -92,7 +92,7 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest

test "directly uploading blob with mismatched content type" do
data = "Something else entirely!"
blob = create_blob_before_direct_upload byte_size: data.size, checksum: OpenSSL::Digest::MD5.base64digest(data)
blob = create_blob_before_direct_upload byte_size: data.size, checksum: ActiveStorage.checksum_implementation.base64digest(data)

put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "application/octet-stream" }
assert_response :unprocessable_entity
Expand All @@ -102,7 +102,7 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest
test "directly uploading blob with different but equivalent content type" do
data = "Something else entirely!"
blob = create_blob_before_direct_upload(
byte_size: data.size, checksum: OpenSSL::Digest::MD5.base64digest(data), content_type: "application/x-gzip")
byte_size: data.size, checksum: ActiveStorage.checksum_implementation.base64digest(data), content_type: "application/x-gzip")

put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "application/x-gzip" }
assert_response :no_content
Expand All @@ -111,7 +111,7 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest

test "directly uploading blob with mismatched content length" do
data = "Something else entirely!"
blob = create_blob_before_direct_upload byte_size: data.size - 1, checksum: OpenSSL::Digest::MD5.base64digest(data)
blob = create_blob_before_direct_upload byte_size: data.size - 1, checksum: ActiveStorage.checksum_implementation.base64digest(data)

put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "text/plain" }
assert_response :unprocessable_entity
Expand Down
2 changes: 1 addition & 1 deletion activestorage/test/models/attachment_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class ActiveStorage::AttachmentTest < ActiveSupport::TestCase
test "attaching a blob doesn't touch the record" do
data = "Something else entirely!"
io = StringIO.new(data)
blob = create_blob_before_direct_upload byte_size: data.size, checksum: OpenSSL::Digest::MD5.base64digest(data)
blob = create_blob_before_direct_upload byte_size: data.size, checksum: ActiveStorage.checksum_implementation.base64digest(data)
blob.upload(io)

user = User.create!(
Expand Down
4 changes: 2 additions & 2 deletions activestorage/test/models/blob_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase

assert_equal data, blob.download
assert_equal data.length, blob.byte_size
assert_equal OpenSSL::Digest::MD5.base64digest(data), blob.checksum
assert_equal ActiveStorage.checksum_implementation.base64digest(data), blob.checksum
end

test "create_and_upload extracts content type from data" do
Expand Down Expand Up @@ -188,7 +188,7 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase

test "open without integrity" do
create_blob(data: "Hello, world!").tap do |blob|
blob.update! checksum: OpenSSL::Digest::MD5.base64digest("Goodbye, world!")
blob.update! checksum: ActiveStorage.checksum_implementation.base64digest("Goodbye, world!")

assert_raises ActiveStorage::IntegrityError do
blob.open { |file| flunk "Expected integrity check to fail" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class ActiveStorage::Service::AzureStoragePublicServiceTest < ActiveSupport::Tes
test "direct upload" do
key = SecureRandom.base58(24)
data = "Something else entirely!"
checksum = OpenSSL::Digest::MD5.base64digest(data)
checksum = ActiveStorage.checksum_implementation.base64digest(data)
content_type = "text/xml"
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: content_type, content_length: data.size, checksum: checksum)

Expand Down
10 changes: 5 additions & 5 deletions activestorage/test/service/azure_storage_service_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class ActiveStorage::Service::AzureStorageServiceTest < ActiveSupport::TestCase
test "direct upload with content type" do
key = SecureRandom.base58(24)
data = "Something else entirely!"
checksum = OpenSSL::Digest::MD5.base64digest(data)
checksum = ActiveStorage.checksum_implementation.base64digest(data)
content_type = "text/xml"
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: content_type, content_length: data.size, checksum: checksum)

Expand All @@ -34,7 +34,7 @@ class ActiveStorage::Service::AzureStorageServiceTest < ActiveSupport::TestCase
test "direct upload with content disposition" do
key = SecureRandom.base58(24)
data = "Something else entirely!"
checksum = OpenSSL::Digest::MD5.base64digest(data)
checksum = ActiveStorage.checksum_implementation.base64digest(data)
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum)

uri = URI.parse url
Expand All @@ -56,7 +56,7 @@ class ActiveStorage::Service::AzureStorageServiceTest < ActiveSupport::TestCase
key = SecureRandom.base58(24)
data = "Foobar"

@service.upload(key, StringIO.new(data), checksum: OpenSSL::Digest::MD5.base64digest(data), filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain")
@service.upload(key, StringIO.new(data), checksum: ActiveStorage.checksum_implementation.base64digest(data), filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain")

url = @service.url(key, expires_in: 2.minutes, disposition: :attachment, content_type: nil, filename: ActiveStorage::Filename.new("test.html"))
response = Net::HTTP.get_response(URI(url))
Expand All @@ -70,7 +70,7 @@ class ActiveStorage::Service::AzureStorageServiceTest < ActiveSupport::TestCase
key = SecureRandom.base58(24)
data = "Foobar"

@service.upload(key, StringIO.new(data), checksum: OpenSSL::Digest::MD5.base64digest(data), filename: ActiveStorage::Filename.new("test.txt"), disposition: :inline)
@service.upload(key, StringIO.new(data), checksum: ActiveStorage.checksum_implementation.base64digest(data), filename: ActiveStorage::Filename.new("test.txt"), disposition: :inline)

assert_equal("inline; filename=\"test.txt\"; filename*=UTF-8''test.txt", @service.client.get_blob_properties(@service.container, key).properties[:content_disposition])

Expand All @@ -85,7 +85,7 @@ class ActiveStorage::Service::AzureStorageServiceTest < ActiveSupport::TestCase
key = SecureRandom.base58(24)
data = "Foobar"

@service.upload(key, StringIO.new(data), checksum: OpenSSL::Digest::MD5.base64digest(data), filename: ActiveStorage::Filename.new("test.txt"), custom_metadata: { "foo" => "baz" })
@service.upload(key, StringIO.new(data), checksum: ActiveStorage.checksum_implementation.base64digest(data), filename: ActiveStorage::Filename.new("test.txt"), custom_metadata: { "foo" => "baz" })
url = @service.url(key, expires_in: 2.minutes, disposition: :inline, content_type: "text/html", filename: ActiveStorage::Filename.new("test.html"))

response = Net::HTTP.get_response(URI(url))
Expand Down
2 changes: 1 addition & 1 deletion activestorage/test/service/gcs_public_service_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class ActiveStorage::Service::GCSPublicServiceTest < ActiveSupport::TestCase
test "direct upload" do
key = SecureRandom.base58(24)
data = "Something else entirely!"
checksum = OpenSSL::Digest::MD5.base64digest(data)
checksum = ActiveStorage.checksum_implementation.base64digest(data)
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum)

uri = URI.parse url
Expand Down
10 changes: 5 additions & 5 deletions activestorage/test/service/gcs_service_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class ActiveStorage::Service::GCSServiceTest < ActiveSupport::TestCase
test "direct upload" do
key = SecureRandom.base58(24)
data = "Something else entirely!"
checksum = OpenSSL::Digest::MD5.base64digest(data)
checksum = ActiveStorage.checksum_implementation.base64digest(data)
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum)

uri = URI.parse url
Expand All @@ -36,7 +36,7 @@ class ActiveStorage::Service::GCSServiceTest < ActiveSupport::TestCase
test "direct upload with content disposition" do
key = SecureRandom.base58(24)
data = "Something else entirely!"
checksum = OpenSSL::Digest::MD5.base64digest(data)
checksum = ActiveStorage.checksum_implementation.base64digest(data)
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum)

uri = URI.parse url
Expand Down Expand Up @@ -91,7 +91,7 @@ class ActiveStorage::Service::GCSServiceTest < ActiveSupport::TestCase
key = SecureRandom.base58(24)
data = "Something else entirely!"

@service.upload(key, StringIO.new(data), checksum: OpenSSL::Digest::MD5.base64digest(data), disposition: :attachment, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain")
@service.upload(key, StringIO.new(data), checksum: ActiveStorage.checksum_implementation.base64digest(data), disposition: :attachment, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain")

url = @service.url(key, expires_in: 2.minutes, disposition: :inline, content_type: "text/html", filename: ActiveStorage::Filename.new("test.html"))
response = Net::HTTP.get_response(URI(url))
Expand All @@ -105,7 +105,7 @@ class ActiveStorage::Service::GCSServiceTest < ActiveSupport::TestCase
key = SecureRandom.base58(24)
data = "Something else entirely!"

@service.upload(key, StringIO.new(data), checksum: OpenSSL::Digest::MD5.base64digest(data), content_type: "text/plain")
@service.upload(key, StringIO.new(data), checksum: ActiveStorage.checksum_implementation.base64digest(data), content_type: "text/plain")

url = @service.url(key, expires_in: 2.minutes, disposition: :inline, content_type: "text/html", filename: ActiveStorage::Filename.new("test.html"))
response = Net::HTTP.get_response(URI(url))
Expand Down Expand Up @@ -148,7 +148,7 @@ class ActiveStorage::Service::GCSServiceTest < ActiveSupport::TestCase
test "update custom_metadata" do
key = SecureRandom.base58(24)
data = "Something else entirely!"
@service.upload(key, StringIO.new(data), checksum: OpenSSL::Digest::MD5.base64digest(data), disposition: :attachment, filename: ActiveStorage::Filename.new("test.html"), content_type: "text/html", custom_metadata: { "foo" => "baz" })
@service.upload(key, StringIO.new(data), checksum: ActiveStorage.checksum_implementation.base64digest(data), disposition: :attachment, filename: ActiveStorage::Filename.new("test.html"), content_type: "text/html", custom_metadata: { "foo" => "baz" })

@service.update_metadata(key, disposition: :inline, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain", custom_metadata: { "foo" => "bar" })
url = @service.url(key, expires_in: 2.minutes, disposition: :attachment, content_type: "text/html", filename: ActiveStorage::Filename.new("test.html"))
Expand Down
6 changes: 3 additions & 3 deletions activestorage/test/service/mirror_service_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase
key = SecureRandom.base58(24)
data = "Something else entirely!"
io = StringIO.new(data)
checksum = OpenSSL::Digest::MD5.base64digest(data)
checksum = ActiveStorage.checksum_implementation.base64digest(data)

assert_performed_jobs 1, only: ActiveStorage::MirrorJob do
@service.upload key, io.tap(&:read), checksum: checksum
Expand All @@ -49,7 +49,7 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase
test "downloading from primary service" do
key = SecureRandom.base58(24)
data = "Something else entirely!"
checksum = OpenSSL::Digest::MD5.base64digest(data)
checksum = ActiveStorage.checksum_implementation.base64digest(data)

@service.primary.upload key, StringIO.new(data), checksum: checksum

Expand All @@ -68,7 +68,7 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase
test "mirroring a file from the primary service to secondary services where it doesn't exist" do
key = SecureRandom.base58(24)
data = "Something else entirely!"
checksum = OpenSSL::Digest::MD5.base64digest(data)
checksum = ActiveStorage.checksum_implementation.base64digest(data)

@service.primary.upload key, StringIO.new(data), checksum: checksum
@service.mirrors.third.upload key, StringIO.new("Surprise!")
Expand Down
2 changes: 1 addition & 1 deletion activestorage/test/service/s3_public_service_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class ActiveStorage::Service::S3PublicServiceTest < ActiveSupport::TestCase
test "direct upload" do
key = SecureRandom.base58(24)
data = "Something else entirely!"
checksum = OpenSSL::Digest::MD5.base64digest(data)
checksum = ActiveStorage.checksum_implementation.base64digest(data)
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum)

uri = URI.parse url
Expand Down
Loading

0 comments on commit e64e5d3

Please sign in to comment.