mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[chore] Refactor AP authentication, other small bits of tidying up (#1874)
This commit is contained in:
@ -19,298 +19,297 @@ package federation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"crypto/rsa"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"codeberg.org/gruf/go-kv"
|
||||
"github.com/go-fed/httpsig"
|
||||
"github.com/superseriousbusiness/activity/pub"
|
||||
"github.com/superseriousbusiness/activity/streams"
|
||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
)
|
||||
|
||||
/*
|
||||
publicKeyer is BORROWED DIRECTLY FROM https://github.com/go-fed/apcore/blob/master/ap/util.go
|
||||
Thank you @cj@mastodon.technology ! <3
|
||||
*/
|
||||
type publicKeyer interface {
|
||||
GetW3IDSecurityV1PublicKey() vocab.W3IDSecurityV1PublicKeyProperty
|
||||
}
|
||||
|
||||
/*
|
||||
getPublicKeyFromResponse is adapted from https://github.com/go-fed/apcore/blob/master/ap/util.go
|
||||
Thank you @cj@mastodon.technology ! <3
|
||||
*/
|
||||
func getPublicKeyFromResponse(c context.Context, b []byte, keyID *url.URL) (vocab.W3IDSecurityV1PublicKey, error) {
|
||||
m := make(map[string]interface{})
|
||||
if err := json.Unmarshal(b, &m); err != nil {
|
||||
return nil, err
|
||||
var (
|
||||
errUnsigned = errors.New("http request wasn't signed or http signature was invalid")
|
||||
signingAlgorithms = []httpsig.Algorithm{
|
||||
httpsig.RSA_SHA256, // Prefer common RSA_SHA256.
|
||||
httpsig.RSA_SHA512, // Fall back to less common RSA_SHA512.
|
||||
httpsig.ED25519, // Try ED25519 as a long shot.
|
||||
}
|
||||
)
|
||||
|
||||
t, err := streams.ToType(c, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pker, ok := t.(publicKeyer)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("ActivityStreams type cannot be converted to one known to have publicKey property: %T", t)
|
||||
}
|
||||
|
||||
pkp := pker.GetW3IDSecurityV1PublicKey()
|
||||
if pkp == nil {
|
||||
return nil, errors.New("publicKey property is not provided")
|
||||
}
|
||||
|
||||
var pkpFound vocab.W3IDSecurityV1PublicKey
|
||||
for pkpIter := pkp.Begin(); pkpIter != pkp.End(); pkpIter = pkpIter.Next() {
|
||||
if !pkpIter.IsW3IDSecurityV1PublicKey() {
|
||||
continue
|
||||
}
|
||||
pkValue := pkpIter.Get()
|
||||
var pkID *url.URL
|
||||
pkID, err = pub.GetId(pkValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pkID.String() != keyID.String() {
|
||||
continue
|
||||
}
|
||||
pkpFound = pkValue
|
||||
break
|
||||
}
|
||||
|
||||
if pkpFound == nil {
|
||||
return nil, fmt.Errorf("cannot find publicKey with id: %s", keyID)
|
||||
}
|
||||
|
||||
return pkpFound, nil
|
||||
}
|
||||
|
||||
// AuthenticateFederatedRequest authenticates any kind of incoming federated request from a remote server. This includes things like
|
||||
// GET requests for dereferencing our users or statuses etc, and POST requests for delivering new Activities. The function returns
|
||||
// the URL of the owner of the public key used in the requesting http signature.
|
||||
// AuthenticateFederatedRequest authenticates any kind of incoming federated
|
||||
// request from a remote server. This includes things like GET requests for
|
||||
// dereferencing our users or statuses etc, and POST requests for delivering
|
||||
// new Activities. The function returns the URL of the owner of the public key
|
||||
// used in the requesting http signature.
|
||||
//
|
||||
// Authenticate in this case is defined as making sure that the http request is actually signed by whoever claims
|
||||
// to have signed it, by fetching the public key from the signature and checking it against the remote public key.
|
||||
// 'Authenticate' in this case is defined as making sure that the http request
|
||||
// is actually signed by whoever claims to have signed it, by fetching the public
|
||||
// key from the signature and checking it against the remote public key.
|
||||
//
|
||||
// The provided username will be used to generate a transport for making remote requests/derefencing the public key ID of the request signature.
|
||||
// Ideally you should pass in the username of the user *being requested*, so that the remote server can decide how to handle the request based on who's making it.
|
||||
// Ie., if the request on this server is for https://example.org/users/some_username then you should pass in the username 'some_username'.
|
||||
// The remote server will then know that this is the user making the dereferencing request, and they can decide to allow or deny the request depending on their settings.
|
||||
// The provided username will be used to generate a transport for making remote
|
||||
// requests/derefencing the public key ID of the request signature. Ideally you
|
||||
// should pass in the username of the user *being requested*, so that the remote
|
||||
// server can decide how to handle the request based on who's making it. Ie., if
|
||||
// the request on this server is for https://example.org/users/some_username then
|
||||
// you should pass in the username 'some_username'. The remote server will then
|
||||
// know that this is the user making the dereferencing request, and they can decide
|
||||
// to allow or deny the request depending on their settings.
|
||||
//
|
||||
// Note that it is also valid to pass in an empty string here, in which case the keys of the instance account will be used.
|
||||
// Note that it is also valid to pass in an empty string here, in which case the
|
||||
// keys of the instance account will be used.
|
||||
//
|
||||
// Also note that this function *does not* dereference the remote account that the signature key is associated with.
|
||||
// Other functions should use the returned URL to dereference the remote account, if required.
|
||||
// Also note that this function *does not* dereference the remote account that
|
||||
// the signature key is associated with. Other functions should use the returned
|
||||
// URL to dereference the remote account, if required.
|
||||
func (f *federator) AuthenticateFederatedRequest(ctx context.Context, requestedUsername string) (*url.URL, gtserror.WithCode) {
|
||||
var publicKey interface{}
|
||||
var pkOwnerURI *url.URL
|
||||
var err error
|
||||
|
||||
// thanks to signaturecheck.go in the security package, we should already have a signature verifier set on the context
|
||||
vi := ctx.Value(ap.ContextRequestingPublicKeyVerifier)
|
||||
if vi == nil {
|
||||
err := errors.New("http request wasn't signed or http signature was invalid")
|
||||
errWithCode := gtserror.NewErrorUnauthorized(err, err.Error())
|
||||
log.Debug(ctx, errWithCode)
|
||||
// Thanks to the signature check middleware,
|
||||
// we should already have an http signature
|
||||
// verifier set on the context. If we don't,
|
||||
// this is an unsigned request.
|
||||
verifier := gtscontext.HTTPSignatureVerifier(ctx)
|
||||
if verifier == nil {
|
||||
err := gtserror.Newf("%w", errUnsigned)
|
||||
errWithCode := gtserror.NewErrorUnauthorized(err, errUnsigned.Error(), "(verifier)")
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
verifier, ok := vi.(httpsig.Verifier)
|
||||
if !ok {
|
||||
err := errors.New("http request wasn't signed or http signature was invalid")
|
||||
errWithCode := gtserror.NewErrorUnauthorized(err, err.Error())
|
||||
log.Debug(ctx, errWithCode)
|
||||
// We should have the signature itself set too.
|
||||
signature := gtscontext.HTTPSignature(ctx)
|
||||
if signature == "" {
|
||||
err := gtserror.Newf("%w", errUnsigned)
|
||||
errWithCode := gtserror.NewErrorUnauthorized(err, errUnsigned.Error(), "(signature)")
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
// we should have the signature itself set too
|
||||
si := ctx.Value(ap.ContextRequestingPublicKeySignature)
|
||||
if si == nil {
|
||||
err := errors.New("http request wasn't signed or http signature was invalid")
|
||||
errWithCode := gtserror.NewErrorUnauthorized(err, err.Error())
|
||||
log.Debug(ctx, errWithCode)
|
||||
// And finally the public key ID URI.
|
||||
pubKeyID := gtscontext.HTTPSignaturePubKeyID(ctx)
|
||||
if pubKeyID == nil {
|
||||
err := gtserror.Newf("%w", errUnsigned)
|
||||
errWithCode := gtserror.NewErrorUnauthorized(err, errUnsigned.Error(), "(pubKeyID)")
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
signature, ok := si.(string)
|
||||
if !ok {
|
||||
err := errors.New("http request wasn't signed or http signature was invalid")
|
||||
errWithCode := gtserror.NewErrorUnauthorized(err, err.Error())
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
// now figure out who actually signed it
|
||||
requestingPublicKeyID, err := url.Parse(verifier.KeyId())
|
||||
if err != nil {
|
||||
errWithCode := gtserror.NewErrorBadRequest(err, fmt.Sprintf("couldn't parse public key URL %s", verifier.KeyId()))
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
}
|
||||
// At this point we know the request was signed,
|
||||
// so now we need to validate the signature.
|
||||
|
||||
var (
|
||||
requestingLocalAccount *gtsmodel.Account
|
||||
requestingRemoteAccount *gtsmodel.Account
|
||||
requestingHost = requestingPublicKeyID.Host
|
||||
pubKeyIDStr = pubKeyID.String()
|
||||
requestingAccountURI *url.URL
|
||||
pubKey interface{}
|
||||
errWithCode gtserror.WithCode
|
||||
)
|
||||
|
||||
if host := config.GetHost(); strings.EqualFold(requestingHost, host) {
|
||||
// LOCAL ACCOUNT REQUEST
|
||||
// the request is coming from INSIDE THE HOUSE so skip the remote dereferencing
|
||||
log.Tracef(ctx, "proceeding without dereference for local public key %s", requestingPublicKeyID)
|
||||
l := log.
|
||||
WithContext(ctx).
|
||||
WithFields(kv.Fields{
|
||||
{"requestedUsername", requestedUsername},
|
||||
{"pubKeyID", pubKeyIDStr},
|
||||
}...)
|
||||
|
||||
requestingLocalAccount, err = f.db.GetAccountByPubkeyID(ctx, requestingPublicKeyID.String())
|
||||
if err != nil {
|
||||
errWithCode := gtserror.NewErrorInternalError(fmt.Errorf("couldn't get account with public key uri %s from the database: %s", requestingPublicKeyID.String(), err))
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
publicKey = requestingLocalAccount.PublicKey
|
||||
|
||||
pkOwnerURI, err = url.Parse(requestingLocalAccount.URI)
|
||||
if err != nil {
|
||||
errWithCode := gtserror.NewErrorBadRequest(err, fmt.Sprintf("couldn't parse public key owner URL %s", requestingLocalAccount.URI))
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
}
|
||||
} else if requestingRemoteAccount, err = f.db.GetAccountByPubkeyID(ctx, requestingPublicKeyID.String()); err == nil {
|
||||
// REMOTE ACCOUNT REQUEST WITH KEY CACHED LOCALLY
|
||||
// this is a remote account and we already have the public key for it so use that
|
||||
log.Tracef(ctx, "proceeding without dereference for cached public key %s", requestingPublicKeyID)
|
||||
publicKey = requestingRemoteAccount.PublicKey
|
||||
pkOwnerURI, err = url.Parse(requestingRemoteAccount.URI)
|
||||
if err != nil {
|
||||
errWithCode := gtserror.NewErrorBadRequest(err, fmt.Sprintf("couldn't parse public key owner URL %s", requestingRemoteAccount.URI))
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
}
|
||||
if pubKeyID.Host == config.GetHost() {
|
||||
l.Trace("public key is ours, no dereference needed")
|
||||
requestingAccountURI, pubKey, errWithCode = f.derefDBOnly(ctx, pubKeyIDStr)
|
||||
} else {
|
||||
// REMOTE ACCOUNT REQUEST WITHOUT KEY CACHED LOCALLY
|
||||
// the request is remote and we don't have the public key yet,
|
||||
// so we need to authenticate the request properly by dereferencing the remote key
|
||||
gone, err := f.CheckGone(ctx, requestingPublicKeyID)
|
||||
if err != nil {
|
||||
errWithCode := gtserror.NewErrorInternalError(fmt.Errorf("error checking for tombstone for %s: %s", requestingPublicKeyID, err))
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
if gone {
|
||||
errWithCode := gtserror.NewErrorGone(fmt.Errorf("account with public key %s is gone", requestingPublicKeyID))
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
log.Tracef(ctx, "proceeding with dereference for uncached public key %s", requestingPublicKeyID)
|
||||
trans, err := f.transportController.NewTransportForUsername(gtscontext.SetFastFail(ctx), requestedUsername)
|
||||
if err != nil {
|
||||
errWithCode := gtserror.NewErrorInternalError(fmt.Errorf("error creating transport for %s: %s", requestedUsername, err))
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
// The actual http call to the remote server is made right here in the Dereference function.
|
||||
b, err := trans.Dereference(ctx, requestingPublicKeyID)
|
||||
if err != nil {
|
||||
if gtserror.StatusCode(err) == http.StatusGone {
|
||||
// if we get a 410 error it means the account that owns this public key has been deleted;
|
||||
// we should add a tombstone to our database so that we can avoid trying to deref it in future
|
||||
if err := f.HandleGone(ctx, requestingPublicKeyID); err != nil {
|
||||
errWithCode := gtserror.NewErrorInternalError(fmt.Errorf("error marking account with public key %s as gone: %s", requestingPublicKeyID, err))
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
}
|
||||
errWithCode := gtserror.NewErrorGone(fmt.Errorf("account with public key %s is gone", requestingPublicKeyID))
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
errWithCode := gtserror.NewErrorUnauthorized(fmt.Errorf("error dereferencing public key %s: %s", requestingPublicKeyID, err))
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
// if the key isn't in the response, we can't authenticate the request
|
||||
requestingPublicKey, err := getPublicKeyFromResponse(ctx, b, requestingPublicKeyID)
|
||||
if err != nil {
|
||||
errWithCode := gtserror.NewErrorUnauthorized(fmt.Errorf("error parsing public key %s: %s", requestingPublicKeyID, err))
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
// we should be able to get the actual key embedded in the vocab.W3IDSecurityV1PublicKey
|
||||
pkPemProp := requestingPublicKey.GetW3IDSecurityV1PublicKeyPem()
|
||||
if pkPemProp == nil || !pkPemProp.IsXMLSchemaString() {
|
||||
errWithCode := gtserror.NewErrorUnauthorized(errors.New("publicKeyPem property is not provided or it is not embedded as a value"))
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
// and decode the PEM so that we can parse it as a golang public key
|
||||
pubKeyPem := pkPemProp.Get()
|
||||
block, _ := pem.Decode([]byte(pubKeyPem))
|
||||
if block == nil || block.Type != "PUBLIC KEY" {
|
||||
errWithCode := gtserror.NewErrorUnauthorized(errors.New("could not decode publicKeyPem to PUBLIC KEY pem block type"))
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
publicKey, err = x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
errWithCode := gtserror.NewErrorUnauthorized(fmt.Errorf("could not parse public key %s from block bytes: %s", requestingPublicKeyID, err))
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
// all good! we just need the URI of the key owner to return
|
||||
pkOwnerProp := requestingPublicKey.GetW3IDSecurityV1Owner()
|
||||
if pkOwnerProp == nil || !pkOwnerProp.IsIRI() {
|
||||
errWithCode := gtserror.NewErrorUnauthorized(errors.New("publicKeyOwner property is not provided or it is not embedded as a value"))
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
}
|
||||
pkOwnerURI = pkOwnerProp.GetIRI()
|
||||
l.Trace("public key is not ours, checking if we need to dereference")
|
||||
requestingAccountURI, pubKey, errWithCode = f.deref(ctx, requestedUsername, pubKeyIDStr, pubKeyID)
|
||||
}
|
||||
|
||||
// after all that, public key should be defined
|
||||
if publicKey == nil {
|
||||
errWithCode := gtserror.NewErrorInternalError(errors.New("returned public key was empty"))
|
||||
log.Debug(ctx, errWithCode)
|
||||
if errWithCode != nil {
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
// do the actual authentication here!
|
||||
algos := []httpsig.Algorithm{
|
||||
httpsig.RSA_SHA256,
|
||||
httpsig.RSA_SHA512,
|
||||
httpsig.ED25519,
|
||||
// Ensure public key now defined.
|
||||
if pubKey == nil {
|
||||
err := gtserror.New("public key was nil")
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
for _, algo := range algos {
|
||||
log.Tracef(ctx, "trying algo: %s", algo)
|
||||
err := verifier.Verify(publicKey, algo)
|
||||
// Try to authenticate using permitted algorithms in
|
||||
// order of most -> least common. Return OK as soon
|
||||
// as one passes.
|
||||
for _, algo := range signingAlgorithms {
|
||||
l.Tracef("trying %s", algo)
|
||||
|
||||
err := verifier.Verify(pubKey, algo)
|
||||
if err == nil {
|
||||
log.Tracef(ctx, "authentication for %s PASSED with algorithm %s", pkOwnerURI, algo)
|
||||
return pkOwnerURI, nil
|
||||
l.Tracef("authentication PASSED with %s", algo)
|
||||
return requestingAccountURI, nil
|
||||
}
|
||||
log.Tracef(ctx, "authentication for %s NOT PASSED with algorithm %s: %s", pkOwnerURI, algo, err)
|
||||
|
||||
l.Tracef("authentication NOT PASSED with %s: %q", algo, err)
|
||||
}
|
||||
|
||||
errWithCode := gtserror.NewErrorUnauthorized(fmt.Errorf("authentication not passed for public key owner %s; signature value was '%s'", pkOwnerURI, signature))
|
||||
log.Debug(ctx, errWithCode)
|
||||
return nil, errWithCode
|
||||
// At this point no algorithms passed.
|
||||
err := gtserror.Newf(
|
||||
"authentication NOT PASSED for public key %s; tried algorithms %+v; signature value was '%s'",
|
||||
pubKeyIDStr, signature, signingAlgorithms,
|
||||
)
|
||||
|
||||
return nil, gtserror.NewErrorUnauthorized(err, err.Error())
|
||||
}
|
||||
|
||||
// derefDBOnly tries to dereference the given public
|
||||
// key using only entries already in the database.
|
||||
func (f *federator) derefDBOnly(
|
||||
ctx context.Context,
|
||||
pubKeyIDStr string,
|
||||
) (*url.URL, interface{}, gtserror.WithCode) {
|
||||
reqAcct, err := f.db.GetAccountByPubkeyID(ctx, pubKeyIDStr)
|
||||
if err != nil {
|
||||
err = gtserror.Newf("db error getting account with pubKeyID %s: %w", pubKeyIDStr, err)
|
||||
return nil, nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
reqAcctURI, err := url.Parse(reqAcct.URI)
|
||||
if err != nil {
|
||||
err = gtserror.Newf("error parsing account uri with pubKeyID %s: %w", pubKeyIDStr, err)
|
||||
return nil, nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return reqAcctURI, reqAcct.PublicKey, nil
|
||||
}
|
||||
|
||||
// deref tries to dereference the given public key by first
|
||||
// checking in the database, and then (if no entries found)
|
||||
// calling the remote pub key URI and extracting the key.
|
||||
func (f *federator) deref(
|
||||
ctx context.Context,
|
||||
requestedUsername string,
|
||||
pubKeyIDStr string,
|
||||
pubKeyID *url.URL,
|
||||
) (*url.URL, interface{}, gtserror.WithCode) {
|
||||
l := log.
|
||||
WithContext(ctx).
|
||||
WithFields(kv.Fields{
|
||||
{"requestedUsername", requestedUsername},
|
||||
{"pubKeyID", pubKeyIDStr},
|
||||
}...)
|
||||
|
||||
// Try a database only deref first. We may already
|
||||
// have the requesting account cached locally.
|
||||
reqAcctURI, pubKey, errWithCode := f.derefDBOnly(ctx, pubKeyIDStr)
|
||||
if errWithCode == nil {
|
||||
l.Trace("public key cached, no dereference needed")
|
||||
return reqAcctURI, pubKey, nil
|
||||
}
|
||||
|
||||
l.Trace("public key not cached, trying dereference")
|
||||
|
||||
// If we've tried to get this account before and we
|
||||
// now have a tombstone for it (ie., it's been deleted
|
||||
// from remote), don't try to dereference it again.
|
||||
gone, err := f.CheckGone(ctx, pubKeyID)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error checking for tombstone for %s: %w", pubKeyIDStr, err)
|
||||
return nil, nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if gone {
|
||||
err := gtserror.Newf("account with public key %s is gone", pubKeyIDStr)
|
||||
return nil, nil, gtserror.NewErrorGone(err)
|
||||
}
|
||||
|
||||
// Make an http call to get the pubkey.
|
||||
pubKeyBytes, errWithCode := f.callForPubKey(ctx, requestedUsername, pubKeyID)
|
||||
if errWithCode != nil {
|
||||
return nil, nil, errWithCode
|
||||
}
|
||||
|
||||
// Extract the key and the owner from the response.
|
||||
pubKey, pubKeyOwner, err := parsePubKeyBytes(ctx, pubKeyBytes, pubKeyID)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("error parsing public key %s: %w", pubKeyID, err)
|
||||
return nil, nil, gtserror.NewErrorUnauthorized(err)
|
||||
}
|
||||
|
||||
return pubKeyOwner, pubKey, nil
|
||||
}
|
||||
|
||||
// callForPubKey handles the nitty gritty of actually
|
||||
// making a request for the given pubKeyID with a
|
||||
// transport created on behalf of requestedUsername.
|
||||
func (f *federator) callForPubKey(
|
||||
ctx context.Context,
|
||||
requestedUsername string,
|
||||
pubKeyID *url.URL,
|
||||
) ([]byte, gtserror.WithCode) {
|
||||
// Use a transport to dereference the remote.
|
||||
trans, err := f.transportController.NewTransportForUsername(
|
||||
// We're on a hot path: don't retry if req fails.
|
||||
gtscontext.SetFastFail(ctx),
|
||||
requestedUsername,
|
||||
)
|
||||
if err != nil {
|
||||
err = gtserror.Newf("error creating transport for %s: %w", requestedUsername, err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// The actual http call to the remote server is
|
||||
// made right here by the Dereference function.
|
||||
pubKeyBytes, err := trans.Dereference(ctx, pubKeyID)
|
||||
if err == nil {
|
||||
// No problem.
|
||||
return pubKeyBytes, nil
|
||||
}
|
||||
|
||||
if gtserror.StatusCode(err) == http.StatusGone {
|
||||
// 410 indicates remote public key no longer exists
|
||||
// (account deleted, moved, etc). Add a tombstone
|
||||
// to our database so that we can avoid trying to
|
||||
// dereference it in future.
|
||||
if err := f.HandleGone(ctx, pubKeyID); err != nil {
|
||||
err := gtserror.Newf("error marking public key %s as gone: %w", pubKeyID, err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
err := gtserror.Newf("account with public key %s is gone", pubKeyID)
|
||||
return nil, gtserror.NewErrorGone(err)
|
||||
}
|
||||
|
||||
// Fall back to generic error.
|
||||
err = gtserror.Newf("error dereferencing public key %s: %w", pubKeyID, err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// parsePubKeyBytes extracts an rsa public key from the
|
||||
// given pubKeyBytes by trying to parse the pubKeyBytes
|
||||
// as an ActivityPub type. It will return the public key
|
||||
// itself, and the URI of the public key owner.
|
||||
func parsePubKeyBytes(
|
||||
ctx context.Context,
|
||||
pubKeyBytes []byte,
|
||||
pubKeyID *url.URL,
|
||||
) (*rsa.PublicKey, *url.URL, error) {
|
||||
m := make(map[string]interface{})
|
||||
if err := json.Unmarshal(pubKeyBytes, &m); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
t, err := streams.ToType(ctx, m)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
withPublicKey, ok := t.(ap.WithPublicKey)
|
||||
if !ok {
|
||||
err = gtserror.Newf("resource at %s with type %T could not be converted to ap.WithPublicKey", pubKeyID, t)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pubKey, _, pubKeyOwnerID, err := ap.ExtractPublicKey(withPublicKey)
|
||||
if err != nil {
|
||||
err = gtserror.Newf("resource at %s with type %T did not contain recognizable public key", pubKeyID, t)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return pubKey, pubKeyOwnerID, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user