summary refs log tree commit diff
diff options
context:
space:
mode:
authorpdp8 <pdp8@pdp8.info>2023-06-26 20:49:38 +0200
committerpdp8 <pdp8@pdp8.info>2023-06-26 20:49:38 +0200
commitdfaac96870ac6a86ebb0b5e5c9365e1e0ef6e5bc (patch)
tree07f04481328a85cfe3e3ea4b8bee1a6c654636d6
parent8453f524515941f3c0a65b5ca3b9354be76b4c33 (diff)
digest verification, fetch/send_signed refactored
-rw-r--r--client.rb18
-rw-r--r--helpers.rb20
-rw-r--r--server.rb57
3 files changed, 54 insertions, 41 deletions
diff --git a/client.rb b/client.rb
index 2e449bd..3b30861 100644
--- a/client.rb
+++ b/client.rb
@@ -66,7 +66,8 @@ post "/outbox" do
   File.open(outbox_path, "w+") { |f| f.puts create.to_json }
   File.open(notes_path, "w+") { |f| f.puts create["object"].to_json }
 
-  create["to"].each { |r| send_signed create, r }
+  recipients.delete "https://www.w3.org/ns/activitystreams#Public"
+  recipients.each { |r| send_signed create, r }
   redirect to(params['redirect'])
 end
 
@@ -92,6 +93,7 @@ post "/follow/*" do
   protected!
   mention = params['splat'][0]
   actor = actor(mention)
+  return 502 unless actor
   follow = { "@context" => "https://www.w3.org/ns/activitystreams",
              "id" => File.join(SOCIAL_URL, "following", mention + ".json"),
              "type" => "Follow",
@@ -105,6 +107,7 @@ post "/unfollow/*" do
   protected!
   mention = params['splat'][0]
   actor = actor(mention)
+  return 502 unless actor
   following_path = File.join("public", "following", mention + ".json")
   if File.exists?(following_path)
     undo = { "@context" => "https://www.w3.org/ns/activitystreams",
@@ -144,12 +147,12 @@ end
 helpers do
 
   def protected!
-    redirect("/login.html") unless session['client']
+    halt 403 unless session['client']
   end
 
   def items
     nr = 0
-    files = Dir[File.join(@dir, '*.json')] + Dir['public/objects/*.json']
+    files = Dir[File.join(@dir, '*.json')] + Dir['public/notes/*.json']
     @items = files.sort.collect do |file|
       item = JSON.parse(File.read(file))
       mention = mention(item['attributedTo'])
@@ -199,7 +202,9 @@ helpers do
   def mention actor
     person = people.select{|p| p[1] == actor} 
     if person.empty?
-      mention = "#{fetch(actor)["preferredUsername"]}@#{URI(actor).host}"
+      a = fetch(actor)
+      return nil unless a
+      mention = "#{a["preferredUsername"]}@#{URI(actor).host}"
       File.open('cache/people.tsv','a'){|f| f.puts "#{mention}\t#{actor}"}
       mention
     else
@@ -212,8 +217,9 @@ helpers do
     actors = people.select{|p| p[0] == mention} 
     if actors.empty?
       user, server = mention.split("@")
-      actor = fetch("https://#{server}/.well-known/webfinger?resource=acct:#{mention}",
-          "application/jrd+json")["links"].select { |l|
+      a = fetch("https://#{server}/.well-known/webfinger?resource=acct:#{mention}", "application/jrd+json")
+      return nil unless a
+      actor = a["links"].select { |l|
         l["rel"] == "self"
       }[0]["href"]
       File.open('cache/people.tsv','a'){|f| f.puts "#{mention}\t#{actor}"}
diff --git a/helpers.rb b/helpers.rb
index 66dca3d..dca18e7 100644
--- a/helpers.rb
+++ b/helpers.rb
@@ -1,10 +1,13 @@
 helpers do
 
+  def curl ext, url
+    response = `/run/current-system/sw/bin/curl -ifsSL #{ext} #{url}`
+    $?.success? ? response : nil
+  end
+
   def fetch url, accept = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
-    p url
-    response = `/run/current-system/sw/bin/curl --fail-with-body -sSL -H 'Accept: #{accept}' #{url}`
-    halt 400 unless $?.success?
-    JSON.parse(response)
+    response = curl("-H 'Accept: #{accept}'", url)
+    response ? JSON.parse(response) : nil
   end
 
   # https://github.com/mastodon/mastodon/blob/main/app/lib/request.rb
@@ -12,20 +15,21 @@ helpers do
 
     keypair = OpenSSL::PKey::RSA.new(File.read('private.pem'))
     date = Time.now.utc.httpdate
-    sha256 = OpenSSL::Digest::SHA256.new
     body = object.to_json
+    sha256 = OpenSSL::Digest::SHA256.new
     digest = "SHA-256=" + sha256.base64digest(body)
     host = URI.parse(url).host
     inbox = fetch(url)["inbox"]
+    return false unless inbox
     request_uri = URI(inbox).request_uri
 
     signed_string = "(request-target): post #{request_uri}\nhost: #{host}\ndate: #{date}\ndigest: #{digest}\ncontent-type: application/activity+json"
     signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string))
     signed_header = 'keyId="' + ACTOR + '#main-key",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="' + signature + '"'
 
-    #p url
-    puts `/run/current-system/sw/bin/curl --fail-with-body -sSL -X POST -H 'Content-Type: application/activity+json' -H 'Host: #{host}' -H 'Date: #{date}' -H 'Digest: #{digest}' -H 'Signature: #{signed_header}' -d '#{body}' #{inbox}`
-    #puts `/run/current-system/sw/bin/curl -iL -X POST -H 'Content-Type: application/activity+json' -H 'Host: #{host}' -H 'Date: #{date}' -H 'Digest: #{digest}' -H 'Signature: #{signed_header}' -d '#{body}' #{inbox}`
+    curl "-X POST -H 'Content-Type: application/activity+json' -H 'Host: #{host}' -H 'Date: #{date}' -H 'Digest: #{digest}' -H 'Signature: #{signed_header}' -d '#{body}'", inbox
+    $?.success?
+
   end
 
 end
diff --git a/server.rb b/server.rb
index b5076f3..75c8ede 100644
--- a/server.rb
+++ b/server.rb
@@ -79,36 +79,39 @@ helpers do
 
   # https://github.com/mastodon/mastodon/blob/main/app/controllers/concerns/signature_verification.rb
   def verify!
-    # TODO verify digest
-    begin
-      signature_params = {}
-      request.env["HTTP_SIGNATURE"].split(',').each do |pair|
-        k, v = pair.split('=')
-        signature_params[k] = v.gsub('"', '')
-      end
-
-      key_id = signature_params['keyId']
-      headers = signature_params['headers']
-      signature = Base64.decode64(signature_params['signature'])
 
-      actor = fetch key_id
-      key = OpenSSL::PKey::RSA.new(actor['publicKey']['publicKeyPem'])
+    # verify digest
+    request.body.rewind # in case someone already read it
+    body = request.body.read
+    sha256 = OpenSSL::Digest::SHA256.new
+    digest = "SHA-256=" + sha256.base64digest(body)
+    halt 403 unless digest == request.env["HTTP_DIGEST"]
+
+    signature_params = {}
+    request.env["HTTP_SIGNATURE"].split(',').each do |pair|
+      k, v = pair.split('=')
+      signature_params[k] = v.gsub('"', '')
+    end
 
-      comparison = headers.split(' ').map do |signed_params_name|
-        if signed_params_name == '(request-target)'
-          '(request-target): post /inbox'
-        elsif signed_params_name == 'content-type'
-          "#{signed_params_name}: #{request.env["CONTENT_TYPE"]}"
-        else
-          "#{signed_params_name}: #{request.env["HTTP_" + signed_params_name.upcase]}"
-        end
-      end.join("\n")
+    key_id = signature_params['keyId']
+    headers = signature_params['headers']
+    signature = Base64.decode64(signature_params['signature'])
+
+    actor = fetch key_id
+    halt 403 unless actor
+    key = OpenSSL::PKey::RSA.new(actor['publicKey']['publicKeyPem'])
+
+    comparison = headers.split(' ').map do |signed_params_name|
+      if signed_params_name == '(request-target)'
+        '(request-target): post /inbox'
+      elsif signed_params_name == 'content-type'
+        "#{signed_params_name}: #{request.env["CONTENT_TYPE"]}"
+      else
+        "#{signed_params_name}: #{request.env["HTTP_" + signed_params_name.upcase]}"
+      end
+    end.join("\n")
 
-      halt 400 unless key.verify(OpenSSL::Digest.new('SHA256'), signature, comparison)
-    rescue => e
-      p request.env["HTTP_SIGNATURE"], e
-      halt 400
-    end
+    halt 403 unless key.verify(OpenSSL::Digest.new('SHA256'), signature, comparison)
   end
 
   def create object