mirror of
				https://github.com/owncast/owncast.git
				synced 2025-11-04 05:17:27 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			154 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			154 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package inbox
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"crypto/x509"
 | 
						|
	"encoding/pem"
 | 
						|
	"fmt"
 | 
						|
	"net/http"
 | 
						|
	"net/url"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/pkg/errors"
 | 
						|
 | 
						|
	"github.com/go-fed/httpsig"
 | 
						|
	"github.com/owncast/owncast/activitypub/apmodels"
 | 
						|
	"github.com/owncast/owncast/activitypub/persistence"
 | 
						|
	"github.com/owncast/owncast/activitypub/resolvers"
 | 
						|
	"github.com/owncast/owncast/core/data"
 | 
						|
 | 
						|
	log "github.com/sirupsen/logrus"
 | 
						|
)
 | 
						|
 | 
						|
func handle(request apmodels.InboxRequest) {
 | 
						|
	if verified, err := Verify(request.Request); err != nil {
 | 
						|
		log.Debugln("Error in attempting to verify request", err)
 | 
						|
		return
 | 
						|
	} else if !verified {
 | 
						|
		log.Debugln("Request failed verification", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if err := resolvers.Resolve(context.Background(), request.Body, handleUpdateRequest, handleFollowInboxRequest, handleLikeRequest, handleAnnounceRequest, handleUndoInboxRequest, handleCreateRequest); err != nil {
 | 
						|
		log.Debugln("resolver error:", err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Verify will Verify the http signature of an inbound request as well as
 | 
						|
// check it against the list of blocked domains.
 | 
						|
// nolint: cyclop
 | 
						|
func Verify(request *http.Request) (bool, error) {
 | 
						|
	verifier, err := httpsig.NewVerifier(request)
 | 
						|
	if err != nil {
 | 
						|
		return false, errors.Wrap(err, "failed to create key verifier for request")
 | 
						|
	}
 | 
						|
	pubKeyID, err := url.Parse(verifier.KeyId())
 | 
						|
	if err != nil {
 | 
						|
		return false, errors.Wrap(err, "failed to parse key to get key ID")
 | 
						|
	}
 | 
						|
 | 
						|
	// Force federation only via servers using https.
 | 
						|
	if pubKeyID.Scheme != "https" {
 | 
						|
		return false, errors.New("federated servers must use https: " + pubKeyID.String())
 | 
						|
	}
 | 
						|
 | 
						|
	signature := request.Header.Get("signature")
 | 
						|
	if signature == "" {
 | 
						|
		return false, errors.New("http signature header not found in request")
 | 
						|
	}
 | 
						|
 | 
						|
	var algorithmString string
 | 
						|
	signatureComponents := strings.Split(signature, ",")
 | 
						|
	for _, component := range signatureComponents {
 | 
						|
		kv := strings.Split(component, "=")
 | 
						|
		if kv[0] == "algorithm" {
 | 
						|
			algorithmString = kv[1]
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	algorithmString = strings.Trim(algorithmString, "\"")
 | 
						|
	if algorithmString == "" {
 | 
						|
		return false, errors.New("Unable to determine algorithm to verify request")
 | 
						|
	}
 | 
						|
 | 
						|
	publicKey, err := resolvers.GetResolvedPublicKeyFromIRI(pubKeyID.String())
 | 
						|
	if err != nil {
 | 
						|
		return false, errors.Wrap(err, "failed to resolve actor from IRI to fetch key")
 | 
						|
	}
 | 
						|
 | 
						|
	var publicKeyActorIRI *url.URL
 | 
						|
	if ownerProp := publicKey.GetW3IDSecurityV1Owner(); ownerProp != nil {
 | 
						|
		publicKeyActorIRI = ownerProp.Get()
 | 
						|
	}
 | 
						|
 | 
						|
	if publicKeyActorIRI == nil {
 | 
						|
		return false, errors.New("public key owner IRI is empty")
 | 
						|
	}
 | 
						|
 | 
						|
	// Test to see if the actor is in the list of blocked federated domains.
 | 
						|
	if isBlockedDomain(publicKeyActorIRI.Hostname()) {
 | 
						|
		return false, errors.New("domain is blocked")
 | 
						|
	}
 | 
						|
 | 
						|
	// If actor is specifically blocked, then fail validation.
 | 
						|
	if blocked, err := isBlockedActor(publicKeyActorIRI); err != nil || blocked {
 | 
						|
		return false, err
 | 
						|
	}
 | 
						|
 | 
						|
	key := publicKey.GetW3IDSecurityV1PublicKeyPem().Get()
 | 
						|
	block, _ := pem.Decode([]byte(key))
 | 
						|
	if block == nil {
 | 
						|
		log.Errorln("failed to parse PEM block containing the public key")
 | 
						|
		return false, errors.New("failed to parse PEM block containing the public key")
 | 
						|
	}
 | 
						|
 | 
						|
	parsedKey, err := x509.ParsePKIXPublicKey(block.Bytes)
 | 
						|
	if err != nil {
 | 
						|
		log.Errorln("failed to parse DER encoded public key: " + err.Error())
 | 
						|
		return false, errors.Wrap(err, "failed to parse DER encoded public key")
 | 
						|
	}
 | 
						|
 | 
						|
	algos := []httpsig.Algorithm{
 | 
						|
		httpsig.Algorithm(algorithmString), // try stated algorithm first then other common algorithms
 | 
						|
		httpsig.RSA_SHA256,                 // <- used by almost all fedi software
 | 
						|
		httpsig.RSA_SHA512,
 | 
						|
	}
 | 
						|
 | 
						|
	// The verifier will verify the Digest in addition to the HTTP signature
 | 
						|
	triedAlgos := make(map[httpsig.Algorithm]error)
 | 
						|
	for _, algorithm := range algos {
 | 
						|
		if _, tried := triedAlgos[algorithm]; !tried {
 | 
						|
			err := verifier.Verify(parsedKey, algorithm)
 | 
						|
			if err == nil {
 | 
						|
				return true, nil
 | 
						|
			}
 | 
						|
			triedAlgos[algorithm] = err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return false, fmt.Errorf("http signature verification error(s) for: %s: %+v", pubKeyID.String(), triedAlgos)
 | 
						|
}
 | 
						|
 | 
						|
func isBlockedDomain(domain string) bool {
 | 
						|
	blockedDomains := data.GetBlockedFederatedDomains()
 | 
						|
 | 
						|
	for _, blockedDomain := range blockedDomains {
 | 
						|
		if strings.Contains(domain, blockedDomain) {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func isBlockedActor(actorIRI *url.URL) (bool, error) {
 | 
						|
	blockedactor, err := persistence.GetFollower(actorIRI.String())
 | 
						|
 | 
						|
	if blockedactor != nil && blockedactor.DisabledAt != nil {
 | 
						|
		return true, errors.Wrap(err, "remote actor is blocked")
 | 
						|
	}
 | 
						|
 | 
						|
	return false, nil
 | 
						|
}
 |