about summary refs log tree commit diff
path: root/app/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'app/controllers')
-rw-r--r--app/controllers/accounts_controller.rb1
-rw-r--r--app/controllers/api/subscriptions_controller.rb2
-rw-r--r--app/controllers/concerns/signature_verification.rb87
-rw-r--r--app/controllers/stream_entries_controller.rb1
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'