diff options
Diffstat (limited to 'app/controllers')
-rw-r--r-- | app/controllers/accounts_controller.rb | 1 | ||||
-rw-r--r-- | app/controllers/api/subscriptions_controller.rb | 2 | ||||
-rw-r--r-- | app/controllers/concerns/signature_verification.rb | 87 | ||||
-rw-r--r-- | app/controllers/stream_entries_controller.rb | 1 |
4 files changed, 90 insertions, 1 deletions
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 11402ab79..69b520df1 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -2,6 +2,7 @@ class AccountsController < ApplicationController include AccountControllerConcern + include SignatureVerification def show respond_to do |format| diff --git a/app/controllers/api/subscriptions_controller.rb b/app/controllers/api/subscriptions_controller.rb index d3ea98676..89007f3d6 100644 --- a/app/controllers/api/subscriptions_controller.rb +++ b/app/controllers/api/subscriptions_controller.rb @@ -42,7 +42,7 @@ class Api::SubscriptionsController < Api::BaseController end def lease_seconds_or_default - (params['hub.lease_seconds'] || 86_400).to_i.seconds + (params['hub.lease_seconds'] || 1.day).to_i.seconds end def set_account diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb new file mode 100644 index 000000000..abe845d93 --- /dev/null +++ b/app/controllers/concerns/signature_verification.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +# Implemented according to HTTP signatures (Draft 6) +# <https://tools.ietf.org/html/draft-cavage-http-signatures-06> +module SignatureVerification + extend ActiveSupport::Concern + + def signed_request? + request.headers['Signature'].present? + end + + def signed_request_account + return @signed_request_account if defined?(@signed_request_account) + + unless signed_request? + @signed_request_account = nil + return + end + + raw_signature = request.headers['Signature'] + signature_params = {} + + raw_signature.split(',').each do |part| + parsed_parts = part.match(/([a-z]+)="([^"]+)"/i) + next if parsed_parts.nil? || parsed_parts.size != 3 + signature_params[parsed_parts[1]] = parsed_parts[2] + end + + if incompatible_signature?(signature_params) + @signed_request_account = nil + return + end + + account = ResolveRemoteAccountService.new.call(signature_params['keyId'].gsub(/\Aacct:/, '')) + + if account.nil? + @signed_request_account = nil + return + end + + 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 + else + @signed_request_account = nil + end + end + + private + + def build_signed_string(signed_headers) + signed_headers = 'date' if signed_headers.blank? + + signed_headers.split(' ').map do |signed_header| + if signed_header == Request::REQUEST_TARGET + "#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}" + else + "#{signed_header}: #{request.headers[to_header_name(signed_header)]}" + end + end.join("\n") + end + + def matches_time_window? + begin + time_sent = DateTime.httpdate(request.headers['Date']) + rescue ArgumentError + return false + end + + (Time.now.utc - time_sent).abs <= 30 + end + + def to_header_name(name) + name.split(/-/).map(&:capitalize).join('-') + end + + def incompatible_signature?(signature_params) + signature_params['keyId'].blank? || + signature_params['signature'].blank? || + signature_params['algorithm'].blank? || + signature_params['algorithm'] != 'rsa-sha256' || + !signature_params['keyId'].start_with?('acct:') + end +end diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb index 314d59619..54a435238 100644 --- a/app/controllers/stream_entries_controller.rb +++ b/app/controllers/stream_entries_controller.rb @@ -2,6 +2,7 @@ class StreamEntriesController < ApplicationController include Authorization + include SignatureVerification layout 'public' |