Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to signature verification #9667

Merged
merged 4 commits into from
Jan 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 31 additions & 14 deletions app/controllers/concerns/signature_verification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,23 +60,26 @@ def signed_request_account
signature = Base64.decode64(signature_params['signature'])
compare_signed_string = build_signed_string(signature_params['headers'])

if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
@signed_request_account = account
@signed_request_account
elsif account.possibly_stale?
account = account.refresh!
return account unless verify_signature(account, signature, compare_signed_string).nil?

if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
@signed_request_account = account
@signed_request_account
else
@signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri}"
@signed_request_account = nil
end
else
@signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri}"
account_stoplight = Stoplight("source:#{request.ip}") { account.possibly_stale? ? account.refresh! : account_refresh_key(account) }
.with_fallback { nil }
.with_threshold(1)
.with_cool_off_time(5.minutes.seconds)
.with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) }

account = account_stoplight.run

if account.nil?
@signature_verification_failure_reason = "Public key not found for key #{signature_params['keyId']}"
@signed_request_account = nil
return
end

return account unless verify_signature(account, signature, compare_signed_string).nil?

@signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri}"
@signed_request_account = nil
end

def request_body
Expand All @@ -85,6 +88,15 @@ def request_body

private

def verify_signature(account, signature, compare_signed_string)
if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
@signed_request_account = account
@signed_request_account
end
rescue OpenSSL::PKey::RSAError
nil
end

def build_signed_string(signed_headers)
signed_headers = 'date' if signed_headers.blank?

Expand Down Expand Up @@ -131,4 +143,9 @@ def account_from_key_id(key_id)
account
end
end

def account_refresh_key(account)
return if account.local? || !account.activitypub?
ActivityPub::FetchRemoteAccountService.new.call(account.uri, only_key: true)
end
end
8 changes: 4 additions & 4 deletions app/services/activitypub/fetch_remote_account_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ class ActivityPub::FetchRemoteAccountService < BaseService

SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze

# Does a WebFinger roundtrip on each call
def call(uri, id: true, prefetched_body: nil, break_on_redirect: false)
# Does a WebFinger roundtrip on each call, unless `only_key` is true
def call(uri, id: true, prefetched_body: nil, break_on_redirect: false, only_key: false)
return ActivityPub::TagManager.instance.uri_to_resource(uri, Account) if ActivityPub::TagManager.instance.local_uri?(uri)

@json = if prefetched_body.nil?
Expand All @@ -21,9 +21,9 @@ def call(uri, id: true, prefetched_body: nil, break_on_redirect: false)
@username = @json['preferredUsername']
@domain = Addressable::URI.parse(@uri).normalized_host

return unless verified_webfinger?
return unless only_key || verified_webfinger?

ActivityPub::ProcessAccountService.new.call(@username, @domain, @json)
ActivityPub::ProcessAccountService.new.call(@username, @domain, @json, only_key: only_key)
rescue Oj::ParseError
nil
end
Expand Down
10 changes: 6 additions & 4 deletions app/services/activitypub/process_account_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ def call(username, domain, json, options = {})

after_protocol_change! if protocol_changed?
after_key_change! if key_changed? && !@options[:signed_with_known_key]
check_featured_collection! if @account.featured_collection_url.present?
check_links! unless @account.fields.empty?
unless @options[:only_key]
check_featured_collection! if @account.featured_collection_url.present?
check_links! unless @account.fields.empty?
end

@account
rescue Oj::ParseError
Expand All @@ -54,11 +56,11 @@ def create_account
end

def update_account
@account.last_webfingered_at = Time.now.utc
@account.last_webfingered_at = Time.now.utc unless @options[:only_key]
@account.protocol = :activitypub

set_immediate_attributes!
set_fetchable_attributes!
set_fetchable_attributes! unless @options[:only_keys]

@account.save_with_optional_media!
end
Expand Down