From 9484880bcad3d4ac8bd52f649bc275bba9494abe Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 8 Mar 2021 11:43:38 -0500 Subject: [PATCH] Sign actor fetch request This fixes federation with Mastodon instances that have Authorized Fetch turned on by signing the GET request to fetch the actor when a blog is first followed. Ref T820 --- activitypub.go | 38 ++++++++++++++++++++++++++++++++++++-- app.go | 8 +++++++- routes.go | 7 ++++++- webfinger.go | 6 ++++-- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/activitypub.go b/activitypub.go index 0e69075..855f9a7 100644 --- a/activitypub.go +++ b/activitypub.go @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2020 A Bunch Tell LLC. + * Copyright © 2018-2021 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -17,10 +17,12 @@ import ( "encoding/base64" "encoding/json" "fmt" + "github.com/writeas/writefreely/config" "io/ioutil" "net/http" "net/http/httputil" "net/url" + "path/filepath" "strconv" "time" @@ -41,6 +43,17 @@ const ( apCacheTime = time.Minute ) +var instanceColl *Collection + +func initActivityPub(cfg *config.Config) { + ur, _ := url.Parse(cfg.App.Host) + instanceColl = &Collection{ + ID: 0, + Alias: ur.Host, + Title: ur.Host, + } +} + type RemoteUser struct { ID int64 ActorID string @@ -76,12 +89,17 @@ func handleFetchCollectionActivities(app *App, w http.ResponseWriter, r *http.Re vars := mux.Vars(r) alias := vars["alias"] + if alias == "" { + alias = filepath.Base(r.RequestURI) + } // TODO: enforce visibility // Get base Collection data var c *Collection var err error - if app.cfg.App.SingleUser { + if alias == r.Host { + c = instanceColl + } else if app.cfg.App.SingleUser { c, err = app.db.GetCollectionByID(1) } else { c, err = app.db.GetCollection(alias) @@ -546,6 +564,22 @@ func resolveIRI(hostName, url string) ([]byte, error) { r.Header.Add("Accept", "application/activity+json") r.Header.Set("User-Agent", ServerUserAgent(hostName)) + p := instanceColl.PersonObject() + h := sha256.New() + h.Write([]byte{}) + r.Header.Add("Digest", "SHA-256="+base64.StdEncoding.EncodeToString(h.Sum(nil))) + + // Sign using the 'Signature' header + privKey, err := activitypub.DecodePrivateKey(p.GetPrivKey()) + if err != nil { + return nil, err + } + signer := httpsig.NewSigner(p.PublicKey.ID, privKey, httpsig.RSASHA256, []string{"(request-target)", "date", "host", "digest"}) + err = signer.SignSigHeader(r) + if err != nil { + log.Error("Can't sign: %v", err) + } + if debugging { dump, err := httputil.DumpRequestOut(r, true) if err != nil { diff --git a/app.go b/app.go index 2aed437..a0e754e 100644 --- a/app.go +++ b/app.go @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2019 A Bunch Tell LLC. + * Copyright © 2018-2021 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -384,6 +384,8 @@ func Initialize(apper Apper, debug bool) (*App, error) { apper.App().InitDecoder() + apper.App().InitActivityPub() + err = ConnectToDatabase(apper.App()) if err != nil { return nil, fmt.Errorf("connect to DB: %s", err) @@ -499,6 +501,10 @@ func (app *App) InitDecoder() { app.formDecoder.RegisterConverter(sql.NullFloat64{}, converter.ConvertSQLNullFloat64) } +func (app *App) InitActivityPub() { + initActivityPub(app.cfg) +} + // ConnectToDatabase validates and connects to the configured database, then // tests the connection. func ConnectToDatabase(app *App) error { diff --git a/routes.go b/routes.go index bb1785f..d3c56ca 100644 --- a/routes.go +++ b/routes.go @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2019 A Bunch Tell LLC. + * Copyright © 2018-2021 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -12,6 +12,7 @@ package writefreely import ( "net/http" + "net/url" "path/filepath" "strings" @@ -125,9 +126,13 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { write.HandleFunc("/api/markdown", handler.All(handleRenderMarkdown)).Methods("POST") + instanceURL, _ := url.Parse(apper.App().Config().App.Host) + host := instanceURL.Host + // Handle collections write.HandleFunc("/api/collections", handler.All(newCollection)).Methods("POST") apiColls := write.PathPrefix("/api/collections/").Subrouter() + apiColls.HandleFunc("/"+host, handler.AllReader(fetchCollection)).Methods("GET") apiColls.HandleFunc("/{alias:[0-9a-zA-Z\\-]+}", handler.AllReader(fetchCollection)).Methods("GET") apiColls.HandleFunc("/{alias:[0-9a-zA-Z\\-]+}", handler.All(existingCollection)).Methods("POST", "DELETE") apiColls.HandleFunc("/{alias}/posts", handler.AllReader(fetchCollectionPosts)).Methods("GET") diff --git a/webfinger.go b/webfinger.go index 993272f..76a9b66 100644 --- a/webfinger.go +++ b/webfinger.go @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2020 A Bunch Tell LLC. + * Copyright © 2018-2021 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -32,7 +32,9 @@ var wfUserNotFoundErr = impart.HTTPError{http.StatusNotFound, "User not found."} func (wfr wfResolver) FindUser(username string, host, requestHost string, r []webfinger.Rel) (*webfinger.Resource, error) { var c *Collection var err error - if wfr.cfg.App.SingleUser { + if username == host { + c = instanceColl + } else if wfr.cfg.App.SingleUser { c, err = wfr.db.GetCollectionByID(1) } else { c, err = wfr.db.GetCollection(username)