mirror of
https://github.com/go-gitea/gitea
synced 2024-11-14 14:14:25 +00:00
f53e46c721
This fixes a very rare bug when Gitea and another AP server (confirmed to happen with Mastodon) are running on the same machine, Gitea fails to verify incoming HTTP signatures. This is because the other AP server creates the sig with the public Gitea domain as the Host. However, when Gitea receives the request, the Host header is instead localhost, so the signature verification fails. Manually changing the host header to the correct value and trying the verification again fixes the bug.
116 lines
3.1 KiB
Go
116 lines
3.1 KiB
Go
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
// Use of this source code is governed by a MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package activitypub
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"code.gitea.io/gitea/modules/activitypub"
|
|
gitea_context "code.gitea.io/gitea/modules/context"
|
|
"code.gitea.io/gitea/modules/httplib"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
|
|
ap "github.com/go-ap/activitypub"
|
|
"github.com/go-fed/httpsig"
|
|
)
|
|
|
|
func getPublicKeyFromResponse(b []byte, keyID *url.URL) (p crypto.PublicKey, err error) {
|
|
person := ap.PersonNew(ap.IRI(keyID.String()))
|
|
err = person.UnmarshalJSON(b)
|
|
if err != nil {
|
|
err = fmt.Errorf("ActivityStreams type cannot be converted to one known to have publicKey property: %v", err)
|
|
return
|
|
}
|
|
pubKey := person.PublicKey
|
|
if pubKey.ID.String() != keyID.String() {
|
|
err = fmt.Errorf("cannot find publicKey with id: %s in %s", keyID, string(b))
|
|
return
|
|
}
|
|
pubKeyPem := pubKey.PublicKeyPem
|
|
block, _ := pem.Decode([]byte(pubKeyPem))
|
|
if block == nil || block.Type != "PUBLIC KEY" {
|
|
err = fmt.Errorf("could not decode publicKeyPem to PUBLIC KEY pem block type")
|
|
return
|
|
}
|
|
p, err = x509.ParsePKIXPublicKey(block.Bytes)
|
|
return
|
|
}
|
|
|
|
func fetch(iri *url.URL) (b []byte, err error) {
|
|
req := httplib.NewRequest(iri.String(), http.MethodGet)
|
|
req.Header("Accept", activitypub.ActivityStreamsContentType)
|
|
req.Header("Accept-Charset", "utf-8")
|
|
req.Header("Date", strings.ReplaceAll(time.Now().UTC().Format(time.RFC1123), "UTC", "GMT"))
|
|
resp, err := req.Response()
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
err = fmt.Errorf("url IRI fetch [%s] failed with status (%d): %s", iri, resp.StatusCode, resp.Status)
|
|
return
|
|
}
|
|
b, err = io.ReadAll(resp.Body)
|
|
return
|
|
}
|
|
|
|
func verifyHTTPSignatures(ctx *gitea_context.APIContext) (authenticated bool, err error) {
|
|
r := ctx.Req
|
|
|
|
// 1. Figure out what key we need to verify
|
|
v, err := httpsig.NewVerifier(r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
ID := v.KeyId()
|
|
idIRI, err := url.Parse(ID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
// 2. Fetch the public key of the other actor
|
|
b, err := fetch(idIRI)
|
|
if err != nil {
|
|
return
|
|
}
|
|
pubKey, err := getPublicKeyFromResponse(b, idIRI)
|
|
if err != nil {
|
|
return
|
|
}
|
|
// 3. Verify the other actor's key
|
|
algo := httpsig.Algorithm(setting.Federation.Algorithms[0])
|
|
authenticated = v.Verify(pubKey, algo) == nil
|
|
if authenticated {
|
|
return
|
|
}
|
|
// 4. When Gitea and the other ActivityPub server are running on the same machine, the Host header is sometimes incorrect
|
|
r.Header["Host"] = []string{setting.Domain}
|
|
v, err = httpsig.NewVerifier(r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
authenticated = v.Verify(pubKey, algo) == nil
|
|
return
|
|
}
|
|
|
|
// ReqSignature function
|
|
func ReqSignature() func(ctx *gitea_context.APIContext) {
|
|
return func(ctx *gitea_context.APIContext) {
|
|
if authenticated, err := verifyHTTPSignatures(ctx); err != nil {
|
|
ctx.Error(http.StatusInternalServerError, "verifyHttpSignatures", err)
|
|
} else if !authenticated {
|
|
ctx.Error(http.StatusForbidden, "reqSignature", "request signature verification failed")
|
|
}
|
|
}
|
|
}
|