code.gitea.io/gitea@v1.21.7/routers/api/v1/activitypub/reqsignature.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package activitypub
     5  
     6  import (
     7  	"crypto"
     8  	"crypto/x509"
     9  	"encoding/pem"
    10  	"fmt"
    11  	"io"
    12  	"net/http"
    13  	"net/url"
    14  
    15  	"code.gitea.io/gitea/modules/activitypub"
    16  	gitea_context "code.gitea.io/gitea/modules/context"
    17  	"code.gitea.io/gitea/modules/httplib"
    18  	"code.gitea.io/gitea/modules/setting"
    19  
    20  	ap "github.com/go-ap/activitypub"
    21  	"github.com/go-fed/httpsig"
    22  )
    23  
    24  func getPublicKeyFromResponse(b []byte, keyID *url.URL) (p crypto.PublicKey, err error) {
    25  	person := ap.PersonNew(ap.IRI(keyID.String()))
    26  	err = person.UnmarshalJSON(b)
    27  	if err != nil {
    28  		return nil, fmt.Errorf("ActivityStreams type cannot be converted to one known to have publicKey property: %w", err)
    29  	}
    30  	pubKey := person.PublicKey
    31  	if pubKey.ID.String() != keyID.String() {
    32  		return nil, fmt.Errorf("cannot find publicKey with id: %s in %s", keyID, string(b))
    33  	}
    34  	pubKeyPem := pubKey.PublicKeyPem
    35  	block, _ := pem.Decode([]byte(pubKeyPem))
    36  	if block == nil || block.Type != "PUBLIC KEY" {
    37  		return nil, fmt.Errorf("could not decode publicKeyPem to PUBLIC KEY pem block type")
    38  	}
    39  	p, err = x509.ParsePKIXPublicKey(block.Bytes)
    40  	return p, err
    41  }
    42  
    43  func fetch(iri *url.URL) (b []byte, err error) {
    44  	req := httplib.NewRequest(iri.String(), http.MethodGet)
    45  	req.Header("Accept", activitypub.ActivityStreamsContentType)
    46  	req.Header("User-Agent", "Gitea/"+setting.AppVer)
    47  	resp, err := req.Response()
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	defer resp.Body.Close()
    52  
    53  	if resp.StatusCode != http.StatusOK {
    54  		return nil, fmt.Errorf("url IRI fetch [%s] failed with status (%d): %s", iri, resp.StatusCode, resp.Status)
    55  	}
    56  	b, err = io.ReadAll(io.LimitReader(resp.Body, setting.Federation.MaxSize))
    57  	return b, err
    58  }
    59  
    60  func verifyHTTPSignatures(ctx *gitea_context.APIContext) (authenticated bool, err error) {
    61  	r := ctx.Req
    62  
    63  	// 1. Figure out what key we need to verify
    64  	v, err := httpsig.NewVerifier(r)
    65  	if err != nil {
    66  		return false, err
    67  	}
    68  	ID := v.KeyId()
    69  	idIRI, err := url.Parse(ID)
    70  	if err != nil {
    71  		return false, err
    72  	}
    73  	// 2. Fetch the public key of the other actor
    74  	b, err := fetch(idIRI)
    75  	if err != nil {
    76  		return false, err
    77  	}
    78  	pubKey, err := getPublicKeyFromResponse(b, idIRI)
    79  	if err != nil {
    80  		return false, err
    81  	}
    82  	// 3. Verify the other actor's key
    83  	algo := httpsig.Algorithm(setting.Federation.Algorithms[0])
    84  	authenticated = v.Verify(pubKey, algo) == nil
    85  	return authenticated, err
    86  }
    87  
    88  // ReqHTTPSignature function
    89  func ReqHTTPSignature() func(ctx *gitea_context.APIContext) {
    90  	return func(ctx *gitea_context.APIContext) {
    91  		if authenticated, err := verifyHTTPSignatures(ctx); err != nil {
    92  			ctx.ServerError("verifyHttpSignatures", err)
    93  		} else if !authenticated {
    94  			ctx.Error(http.StatusForbidden, "reqSignature", "request signature verification failed")
    95  		}
    96  	}
    97  }