gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

authenticate.go (11016B)


      1 // GoToSocial
      2 // Copyright (C) GoToSocial Authors admin@gotosocial.org
      3 // SPDX-License-Identifier: AGPL-3.0-or-later
      4 //
      5 // This program is free software: you can redistribute it and/or modify
      6 // it under the terms of the GNU Affero General Public License as published by
      7 // the Free Software Foundation, either version 3 of the License, or
      8 // (at your option) any later version.
      9 //
     10 // This program is distributed in the hope that it will be useful,
     11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 // GNU Affero General Public License for more details.
     14 //
     15 // You should have received a copy of the GNU Affero General Public License
     16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18 package federation
     19 
     20 import (
     21 	"context"
     22 	"crypto/rsa"
     23 	"encoding/json"
     24 	"errors"
     25 	"fmt"
     26 	"net/http"
     27 	"net/url"
     28 
     29 	"codeberg.org/gruf/go-kv"
     30 	"github.com/go-fed/httpsig"
     31 	"github.com/superseriousbusiness/activity/streams"
     32 	"github.com/superseriousbusiness/gotosocial/internal/ap"
     33 	"github.com/superseriousbusiness/gotosocial/internal/config"
     34 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
     35 	"github.com/superseriousbusiness/gotosocial/internal/gtserror"
     36 	"github.com/superseriousbusiness/gotosocial/internal/log"
     37 )
     38 
     39 var (
     40 	errUnsigned       = errors.New("http request wasn't signed or http signature was invalid")
     41 	signingAlgorithms = []httpsig.Algorithm{
     42 		httpsig.RSA_SHA256, // Prefer common RSA_SHA256.
     43 		httpsig.RSA_SHA512, // Fall back to less common RSA_SHA512.
     44 		httpsig.ED25519,    // Try ED25519 as a long shot.
     45 	}
     46 )
     47 
     48 // AuthenticateFederatedRequest authenticates any kind of incoming federated
     49 // request from a remote server. This includes things like GET requests for
     50 // dereferencing our users or statuses etc, and POST requests for delivering
     51 // new Activities. The function returns the URL of the owner of the public key
     52 // used in the requesting http signature.
     53 //
     54 // 'Authenticate' in this case is defined as making sure that the http request
     55 // is actually signed by whoever claims to have signed it, by fetching the public
     56 // key from the signature and checking it against the remote public key.
     57 //
     58 // The provided username will be used to generate a transport for making remote
     59 // requests/derefencing the public key ID of the request signature. Ideally you
     60 // should pass in the username of the user *being requested*, so that the remote
     61 // server can decide how to handle the request based on who's making it. Ie., if
     62 // the request on this server is for https://example.org/users/some_username then
     63 // you should pass in the username 'some_username'. The remote server will then
     64 // know that this is the user making the dereferencing request, and they can decide
     65 // to allow or deny the request depending on their settings.
     66 //
     67 // Note that it is also valid to pass in an empty string here, in which case the
     68 // keys of the instance account will be used.
     69 //
     70 // Also note that this function *does not* dereference the remote account that
     71 // the signature key is associated with. Other functions should use the returned
     72 // URL to dereference the remote account, if required.
     73 func (f *federator) AuthenticateFederatedRequest(ctx context.Context, requestedUsername string) (*url.URL, gtserror.WithCode) {
     74 	// Thanks to the signature check middleware,
     75 	// we should already have an http signature
     76 	// verifier set on the context. If we don't,
     77 	// this is an unsigned request.
     78 	verifier := gtscontext.HTTPSignatureVerifier(ctx)
     79 	if verifier == nil {
     80 		err := gtserror.Newf("%w", errUnsigned)
     81 		errWithCode := gtserror.NewErrorUnauthorized(err, errUnsigned.Error(), "(verifier)")
     82 		return nil, errWithCode
     83 	}
     84 
     85 	// We should have the signature itself set too.
     86 	signature := gtscontext.HTTPSignature(ctx)
     87 	if signature == "" {
     88 		err := gtserror.Newf("%w", errUnsigned)
     89 		errWithCode := gtserror.NewErrorUnauthorized(err, errUnsigned.Error(), "(signature)")
     90 		return nil, errWithCode
     91 	}
     92 
     93 	// And finally the public key ID URI.
     94 	pubKeyID := gtscontext.HTTPSignaturePubKeyID(ctx)
     95 	if pubKeyID == nil {
     96 		err := gtserror.Newf("%w", errUnsigned)
     97 		errWithCode := gtserror.NewErrorUnauthorized(err, errUnsigned.Error(), "(pubKeyID)")
     98 		return nil, errWithCode
     99 	}
    100 
    101 	// At this point we know the request was signed,
    102 	// so now we need to validate the signature.
    103 
    104 	var (
    105 		pubKeyIDStr          = pubKeyID.String()
    106 		requestingAccountURI *url.URL
    107 		pubKey               interface{}
    108 		errWithCode          gtserror.WithCode
    109 	)
    110 
    111 	l := log.
    112 		WithContext(ctx).
    113 		WithFields(kv.Fields{
    114 			{"requestedUsername", requestedUsername},
    115 			{"pubKeyID", pubKeyIDStr},
    116 		}...)
    117 
    118 	if pubKeyID.Host == config.GetHost() {
    119 		l.Trace("public key is ours, no dereference needed")
    120 		requestingAccountURI, pubKey, errWithCode = f.derefDBOnly(ctx, pubKeyIDStr)
    121 	} else {
    122 		l.Trace("public key is not ours, checking if we need to dereference")
    123 		requestingAccountURI, pubKey, errWithCode = f.deref(ctx, requestedUsername, pubKeyIDStr, pubKeyID)
    124 	}
    125 
    126 	if errWithCode != nil {
    127 		return nil, errWithCode
    128 	}
    129 
    130 	// Ensure public key now defined.
    131 	if pubKey == nil {
    132 		err := gtserror.New("public key was nil")
    133 		return nil, gtserror.NewErrorInternalError(err)
    134 	}
    135 
    136 	// Try to authenticate using permitted algorithms in
    137 	// order of most -> least common. Return OK as soon
    138 	// as one passes.
    139 	for _, algo := range signingAlgorithms {
    140 		l.Tracef("trying %s", algo)
    141 
    142 		err := verifier.Verify(pubKey, algo)
    143 		if err == nil {
    144 			l.Tracef("authentication PASSED with %s", algo)
    145 			return requestingAccountURI, nil
    146 		}
    147 
    148 		l.Tracef("authentication NOT PASSED with %s: %q", algo, err)
    149 	}
    150 
    151 	// At this point no algorithms passed.
    152 	err := gtserror.Newf(
    153 		"authentication NOT PASSED for public key %s; tried algorithms %+v; signature value was '%s'",
    154 		pubKeyIDStr, signature, signingAlgorithms,
    155 	)
    156 
    157 	return nil, gtserror.NewErrorUnauthorized(err, err.Error())
    158 }
    159 
    160 // derefDBOnly tries to dereference the given public
    161 // key using only entries already in the database.
    162 func (f *federator) derefDBOnly(
    163 	ctx context.Context,
    164 	pubKeyIDStr string,
    165 ) (*url.URL, interface{}, gtserror.WithCode) {
    166 	reqAcct, err := f.db.GetAccountByPubkeyID(ctx, pubKeyIDStr)
    167 	if err != nil {
    168 		err = gtserror.Newf("db error getting account with pubKeyID %s: %w", pubKeyIDStr, err)
    169 		return nil, nil, gtserror.NewErrorInternalError(err)
    170 	}
    171 
    172 	reqAcctURI, err := url.Parse(reqAcct.URI)
    173 	if err != nil {
    174 		err = gtserror.Newf("error parsing account uri with pubKeyID %s: %w", pubKeyIDStr, err)
    175 		return nil, nil, gtserror.NewErrorInternalError(err)
    176 	}
    177 
    178 	return reqAcctURI, reqAcct.PublicKey, nil
    179 }
    180 
    181 // deref tries to dereference the given public key by first
    182 // checking in the database, and then (if no entries found)
    183 // calling the remote pub key URI and extracting the key.
    184 func (f *federator) deref(
    185 	ctx context.Context,
    186 	requestedUsername string,
    187 	pubKeyIDStr string,
    188 	pubKeyID *url.URL,
    189 ) (*url.URL, interface{}, gtserror.WithCode) {
    190 	l := log.
    191 		WithContext(ctx).
    192 		WithFields(kv.Fields{
    193 			{"requestedUsername", requestedUsername},
    194 			{"pubKeyID", pubKeyIDStr},
    195 		}...)
    196 
    197 	// Try a database only deref first. We may already
    198 	// have the requesting account cached locally.
    199 	reqAcctURI, pubKey, errWithCode := f.derefDBOnly(ctx, pubKeyIDStr)
    200 	if errWithCode == nil {
    201 		l.Trace("public key cached, no dereference needed")
    202 		return reqAcctURI, pubKey, nil
    203 	}
    204 
    205 	l.Trace("public key not cached, trying dereference")
    206 
    207 	// If we've tried to get this account before and we
    208 	// now have a tombstone for it (ie., it's been deleted
    209 	// from remote), don't try to dereference it again.
    210 	gone, err := f.CheckGone(ctx, pubKeyID)
    211 	if err != nil {
    212 		err := gtserror.Newf("error checking for tombstone for %s: %w", pubKeyIDStr, err)
    213 		return nil, nil, gtserror.NewErrorInternalError(err)
    214 	}
    215 
    216 	if gone {
    217 		err := gtserror.Newf("account with public key %s is gone", pubKeyIDStr)
    218 		return nil, nil, gtserror.NewErrorGone(err)
    219 	}
    220 
    221 	// Make an http call to get the pubkey.
    222 	pubKeyBytes, errWithCode := f.callForPubKey(ctx, requestedUsername, pubKeyID)
    223 	if errWithCode != nil {
    224 		return nil, nil, errWithCode
    225 	}
    226 
    227 	// Extract the key and the owner from the response.
    228 	pubKey, pubKeyOwner, err := parsePubKeyBytes(ctx, pubKeyBytes, pubKeyID)
    229 	if err != nil {
    230 		err := fmt.Errorf("error parsing public key %s: %w", pubKeyID, err)
    231 		return nil, nil, gtserror.NewErrorUnauthorized(err)
    232 	}
    233 
    234 	return pubKeyOwner, pubKey, nil
    235 }
    236 
    237 // callForPubKey handles the nitty gritty of actually
    238 // making a request for the given pubKeyID with a
    239 // transport created on behalf of requestedUsername.
    240 func (f *federator) callForPubKey(
    241 	ctx context.Context,
    242 	requestedUsername string,
    243 	pubKeyID *url.URL,
    244 ) ([]byte, gtserror.WithCode) {
    245 	// Use a transport to dereference the remote.
    246 	trans, err := f.transportController.NewTransportForUsername(
    247 		// We're on a hot path: don't retry if req fails.
    248 		gtscontext.SetFastFail(ctx),
    249 		requestedUsername,
    250 	)
    251 	if err != nil {
    252 		err = gtserror.Newf("error creating transport for %s: %w", requestedUsername, err)
    253 		return nil, gtserror.NewErrorInternalError(err)
    254 	}
    255 
    256 	// The actual http call to the remote server is
    257 	// made right here by the Dereference function.
    258 	pubKeyBytes, err := trans.Dereference(ctx, pubKeyID)
    259 	if err == nil {
    260 		// No problem.
    261 		return pubKeyBytes, nil
    262 	}
    263 
    264 	if gtserror.StatusCode(err) == http.StatusGone {
    265 		// 410 indicates remote public key no longer exists
    266 		// (account deleted, moved, etc). Add a tombstone
    267 		// to our database so that we can avoid trying to
    268 		// dereference it in future.
    269 		if err := f.HandleGone(ctx, pubKeyID); err != nil {
    270 			err := gtserror.Newf("error marking public key %s as gone: %w", pubKeyID, err)
    271 			return nil, gtserror.NewErrorInternalError(err)
    272 		}
    273 
    274 		err := gtserror.Newf("account with public key %s is gone", pubKeyID)
    275 		return nil, gtserror.NewErrorGone(err)
    276 	}
    277 
    278 	// Fall back to generic error.
    279 	err = gtserror.Newf("error dereferencing public key %s: %w", pubKeyID, err)
    280 	return nil, gtserror.NewErrorInternalError(err)
    281 }
    282 
    283 // parsePubKeyBytes extracts an rsa public key from the
    284 // given pubKeyBytes by trying to parse the pubKeyBytes
    285 // as an ActivityPub type. It will return the public key
    286 // itself, and the URI of the public key owner.
    287 func parsePubKeyBytes(
    288 	ctx context.Context,
    289 	pubKeyBytes []byte,
    290 	pubKeyID *url.URL,
    291 ) (*rsa.PublicKey, *url.URL, error) {
    292 	m := make(map[string]interface{})
    293 	if err := json.Unmarshal(pubKeyBytes, &m); err != nil {
    294 		return nil, nil, err
    295 	}
    296 
    297 	t, err := streams.ToType(ctx, m)
    298 	if err != nil {
    299 		return nil, nil, err
    300 	}
    301 
    302 	withPublicKey, ok := t.(ap.WithPublicKey)
    303 	if !ok {
    304 		err = gtserror.Newf("resource at %s with type %T could not be converted to ap.WithPublicKey", pubKeyID, t)
    305 		return nil, nil, err
    306 	}
    307 
    308 	pubKey, _, pubKeyOwnerID, err := ap.ExtractPublicKey(withPublicKey)
    309 	if err != nil {
    310 		err = gtserror.Newf("resource at %s with type %T did not contain recognizable public key", pubKeyID, t)
    311 		return nil, nil, err
    312 	}
    313 
    314 	return pubKey, pubKeyOwnerID, nil
    315 }