summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--application.rb193
-rw-r--r--outbox/230401T23:00:01.json4
-rw-r--r--outbox/230402T23:00:01.json10
-rw-r--r--pdp84
4 files changed, 133 insertions, 78 deletions
diff --git a/application.rb b/application.rb
index cd8c2ea..ab42c1f 100644
--- a/application.rb
+++ b/application.rb
@@ -1,8 +1,10 @@
-# TODO check POST to outbox with mastinator.com
require 'json'
require 'net/http'
require 'uri'
require 'base64'
+require 'securerandom'
+require 'fileutils'
+require 'digest/sha2'
USER = "pdp8"
WWW_DOMAIN = "pdp8.info"
@@ -11,47 +13,64 @@ SOCIAL_DOMAIN = "social.#{WWW_DOMAIN}"
ACCOUNT = "#{USER}@#{SOCIAL_DOMAIN}"
SOCIAL_URL = "https://#{SOCIAL_DOMAIN}"
-ACTOR = URI.join(SOCIAL_URL, USER)
+ACTOR = File.join(SOCIAL_URL, USER)
MATRIX = "@#{USER}:matrix.#{WWW_DOMAIN}"
class Application
def call(env)
code = 404
- type = "application/activity+json"
+ type = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
+ response = "not allowed"
+ # p env["rack.input"].read
+ # if env["CONTENT_TYPE"] =~ /json/
+ # puts env["REMOTE_ADDR"]
case env['REQUEST_METHOD']
when 'POST'
+ input = env["rack.input"].read
case env["REQUEST_URI"]
when "/inbox" # receive from server
+ puts "POST INBOX"
if verify(env)
- save JSON.parse(env["rack.input"].gets), "inbox"
- code = 200
- response = "OK"
+ begin
+ # unless input.match(/<!DOCTYPE html>/)
+ object = JSON.parse(input)
+ # puts object
+ case object["type"]
+ when "Create"
+ File.open(File.join("inbox", SecureRandom.uuid + ".json"), "w+") { |f| f.puts input }
+ else
+ puts input
+ end
+ code = 200
+ response = "OK"
+ # end
+ rescue => e
+ # puts e.to_s
+ puts "Verification ERROR: "
+ # puts input
+ response = "invalid json"
+ end
else
code = 401
response = "not verified"
end
when "/outbox" # receive from client
+ puts "POST OUTBOX"
# TODO auth
if auth(env)
- input = JSON.parse(env["rack.input"].gets)
- input["type"] ? activity = input : activity = activity(input) # expand object to create activity
- save activity, "outbox"
- deliver ["to", "bto", "cc", "bcc", "audience"].collect { |d| activity[d] }.flatten.uniq
- case activity["type"]
- when "Create"
- when "Update"
- when "Delete"
- when "Follow"
- when "Remove"
- when "Like"
- when "Block"
- when "Undo"
- end
+ input = JSON.parse(input)
+ input["type"] == "Create" ? activity = input : activity = activity(input) # expand object to create activity
+ add_id activity
+ save activity # , "outbox"
+ FileUtils.ln_s File.join('..', path(activity)), "outbox"
+ code, response = deliver activity, ["to", "bto", "cc", "bcc", "audience"].collect { |d|
+ activity[d]
+ }.flatten.uniq.compact
code = 200
response = "OK"
else
@@ -71,6 +90,7 @@ class Application
code = 200
when "/#{USER}"
+ # TODO serve html
response = File.read(USER)
code = 200
@@ -81,18 +101,25 @@ class Application
end
end
+ # else
+ # response = "Cannot serve Content-type: " + env["CONTENT_TYPE"]
+ # end
[code, { "Content-Type" => type }, [response]]
end
+ def add_id object
+ object["id"] = File.join(SOCIAL_URL, object["type"].downcase, SecureRandom.uuid + ".json")
+ end
+
def activity object
- date = Time.now.strftime("%Y-%m-%dT%H:%M:%S.%3N")
- object["id"] = "TODO"
+ date = Time.now.strftime("%Y-%m-%dT%H:%M:%S")
object["attributedTo"] = ACTOR
object["published"] = date
+ add_id object
+ save object
{
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Create",
- "id" => "https://example.net/~mallory/87374",
"actor" => ACTOR,
"object" => object,
"published" => date,
@@ -101,40 +128,62 @@ class Application
}
end
- def save activity, dir
- date = Time.now.strftime("%Y-%m-%dT%H:%M:%S.%3N")
- File.open(File.join(dir, date), "w+") { |f| f.puts activity.to_json }
+ def path object
+ object["id"].sub(SOCIAL_URL, '').sub('/', '')
+ end
+
+ def save object
+ path = path object
+ FileUtils.mkdir_p File.dirname(path)
+ File.open(path, "w+") { |f| f.puts object.to_json }
+ end
+
+ def inbox uri
+ http = Net::HTTP.new(uri.host, uri.port)
+ http.use_ssl = true
+ header = { 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' }
+ request = Net::HTTP::Get.new(uri.request_uri, header)
+ response = http.request(request)
+ JSON.parse(response.body)["inbox"]
end
- def deliver addr
- p addr
+ def deliver object, urls
+ # https://github.com/mastodon/mastodon/blob/main/app/lib/request.rb
keypair = OpenSSL::PKey::RSA.new(File.read('private.pem'))
- addr.each do |url|
+ urls.each do |url|
date = Time.now.utc.httpdate
uri = URI.parse(url)
- signed_string = "(request-target): post /inbox\nhost: #{uri.host}\ndate: #{date}"
- signed_string = keypair.sign(OpenSSL::Digest::SHA256.new, signed_string)
- signature = Base64.urlsafe_encode64(signed_string).encode("UTF-8")
- signed_header = 'keyId="#{url}",headers="(request-target) host date",signature="' + signature + '"'
-
- inbox_url = JSON.parse(Net::HTTP.get(uri,
- { 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' }))["inbox"]
- inbox = URI.parse(inbox_url)
- http = Net::HTTP.new(inbox.host, inbox.port)
+
+ sha256 = OpenSSL::Digest::SHA256.new
+ body = object.to_json
+ digest = "SHA-256=" + sha256.base64digest(body)
+
+ signed_string = "(request-target): post /inbox\nhost: #{uri.host}\ndate: #{date}\ndigest: #{digest}"
+ 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",signature="' + signature + '"'
+
+ uri = URI.parse(get(url)["inbox"])
+ http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
- # http.verify_mode = OpenSSL::SSL::VERIFY_NONE
header = {
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
- 'Host' => inbox.host,
+ 'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
+ 'Host' => uri.host,
'Date' => date,
+ 'Digest' => digest,
'Signature' => signed_header,
}
- request = Net::HTTP::Post.new(inbox.request_uri, header)
- request.body = document.to_json
+ puts signed_header
+ request = Net::HTTP::Post.new(uri.request_uri, header)
+ request.body = body
response = http.request(request)
- puts(response.body, response.code)
+ # puts(response.body, response.code)
+ puts(response.code)
+ # puts(response.body["signed_string"])
+ # puts(response.body["signature"])
end
+ # [response.code, response.body]
end
def ordered_collection dir
@@ -150,34 +199,56 @@ class Application
end
def verify env
+ # https://github.com/mastodon/mastodon/blob/main/app/controllers/concerns/signature_verification.rb
+ puts env
+ # puts env.select { |k, v| k.match(/^HTTP_/) }
+ # puts env["HTTP_SIGNATURE"] # .split(',').each do |pair|
begin
- signature_header = env["HTTP_SIGNATURE"].split(',').each do |pair|
- pair.gsub('"', '').split('=')
- end.to_h
- key_id = signature_header['keyId']
- headers = signature_header['headers']
- signature = Base64.urlsafe_decode64(signature_header['signature'].encode("ascii-8bit"))
-
- uri = URI(key_id)
- res = Net::HTTP.get_response(uri)
- actor = JSON.parse(res.body)
+ signature_params = {}
+ env["HTTP_SIGNATURE"].split(',').each do |pair|
+ k, v = pair.split('=')
+ signature_params[k] = v.gsub('"', '')
+ end
+
+ # puts signature_params
+ key_id = signature_params['keyId']
+ headers = signature_params['headers']
+ signature = Base64.decode64(signature_params['signature'])
+
+ actor = get key_id
key = OpenSSL::PKey::RSA.new(actor['publicKey']['publicKeyPem'])
- comparison = headers.split(' ').map do |signed_header_name|
- if signed_header_name == '(request-target)'
+ 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}: #{env["CONTENT_TYPE"]}"
else
- "#{signed_header_name}: #{env["HTTP_" + signed_header_name.upcase]}"
+ "#{signed_params_name}: #{env["HTTP_" + signed_params_name.upcase]}"
end
- end.join("\n").encode("ascii-8bit")
-
- key.verify(OpenSSL::Digest::SHA256.new, signature, comparison)
- rescue
+ end.join("\n")
+
+ puts comparison
+ # key.verify(OpenSSL::Digest::SHA256.new, signature, comparison)
+ key.verify(OpenSSL::Digest.new('SHA256'), signature, comparison)
+ rescue => e
+ puts e.class
+ # puts e.message
false
end
end
- def auth
+ def get url
+ uri = URI(url)
+ http = Net::HTTP.new(uri.host, uri.port)
+ http.use_ssl = true
+ header = { 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' }
+ request = Net::HTTP::Get.new(uri.request_uri, header)
+ response = http.request(request)
+ JSON.parse(response.body)
+ end
+
+ def auth env
true
end
end
diff --git a/outbox/230401T23:00:01.json b/outbox/230401T23:00:01.json
deleted file mode 100644
index baeb7f8..0000000
--- a/outbox/230401T23:00:01.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "type": "Note",
- "name": "A Simple Note"
-}
diff --git a/outbox/230402T23:00:01.json b/outbox/230402T23:00:01.json
deleted file mode 100644
index bd50adb..0000000
--- a/outbox/230402T23:00:01.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "type": "Note",
- "name": "Another Simple Note",
- "tag": [
- {
- "type": "Hashtag",
- "name": "#test"
- }
- ]
-}
diff --git a/pdp8 b/pdp8
index ceb25c9..7eca15f 100644
--- a/pdp8
+++ b/pdp8
@@ -33,10 +33,8 @@
}
],
"publicKey": {
- "@context": "https://w3id.org/security/v1",
- "@type": "Key",
"id": "https://social.pdp8.info/pdp8#main-key",
"owner": "https://social.pdp8.info/pdp8",
- "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4pZMYXoh8G+iEguDpKGD\n+eq+uDdhx/ch2x7rq00aPDDeHp40CG8bW1ZRC4WIOTUOgK4MeMDoaXT9/vWgr7xT\n/Qm95SEyZWBKqasBsp2uGkDxl23C6dB2eeshuAwt308Qzm2DeTrKPAw/XBAyWHDD\nfan2nWrtXcDJaeXhD/QE/w7Qiz5F2GCb/E/o46SwEyOJi13WxI9Jtuzh76xmwNsd\nwVWIBSu4zn0hg/wv+xtq/c/KLO4ZL54YiJXxRwrkDN7Xdnd18FwFuZ7fT8+kfiqF\nBnvle0OTKxumW46U7ivaylnqoSOvsYK6oyop/m2rl9Nh3sGdcmOsLoFVDg4gOjDf\niQIDAQAB\n-----END PUBLIC KEY-----\n"
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArDawzSl+XcJ+96sIrx+E\nsDoUQzSvoKazCgw7qOMaOGi7XxJ8riBvdRBlJ4zOEfQaxcaQgGn5JntOofqkeWvk\nIykOAzYfwY6HoUm7i1eZME2quO+CkMMq9SX9/DOqggOYtiVC9DX5FxXe5YHK7Q/n\nbo1iB6rgVS43wT0PnI6uduY4cUlvhRkX4Iht0N1GTrBlGKloRQ96KTzp+U9xF7bp\nKO87Y4yftv+d6L3ZZBfTRgWOtDXG8E4Vdvsq0aPQNBtazq0fwtBbk2G4mZtCMqyT\nvLZh8w+YPn1ICoQsKukU/q7eG29UJCz/QdZndkuv5iIm+H/c8gicGllw9rNQP2G0\nBQIDAQAB\n-----END PUBLIC KEY-----\n"
}
}