diff options
Diffstat (limited to 'internal/federation/authenticate.go')
-rw-r--r-- | internal/federation/authenticate.go | 77 |
1 files changed, 28 insertions, 49 deletions
diff --git a/internal/federation/authenticate.go b/internal/federation/authenticate.go index c4183144b..0cb8db6dc 100644 --- a/internal/federation/authenticate.go +++ b/internal/federation/authenticate.go @@ -25,7 +25,6 @@ import ( "encoding/pem" "errors" "fmt" - "net/http" "net/url" "strings" @@ -35,6 +34,7 @@ import ( "github.com/go-fed/httpsig" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/util" ) /* @@ -115,34 +115,30 @@ func getPublicKeyFromResponse(c context.Context, b []byte, keyID *url.URL) (voca // // 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(requestedUsername string, r *http.Request) (*url.URL, error) { +func (f *federator) AuthenticateFederatedRequest(ctx context.Context, requestedUsername string) (*url.URL, bool, error) { + l := f.log.WithField("func", "AuthenticateFederatedRequest") var publicKey interface{} var pkOwnerURI *url.URL var err error - // set this extra field for signature validation - r.Header.Set("host", f.config.Host) - - verifier, err := httpsig.NewVerifier(r) - if err != nil { - return nil, fmt.Errorf("could not create http sig verifier: %s", err) + // thanks to signaturecheck.go in the security package, we should already have a signature verifier set on the context + vi := ctx.Value(util.APRequestingPublicKeyVerifier) + if vi == nil { + l.Debug("request wasn't signed") + return nil, false, nil // request wasn't signed } - // The key ID should be given in the signature so that we know where to fetch it from the remote server. - // This will be something like https://example.org/users/whatever_requesting_user#main-key - requestingPublicKeyID, err := url.Parse(verifier.KeyId()) - if err != nil { - return nil, fmt.Errorf("could not parse key id into a url: %s", err) + verifier, ok := vi.(httpsig.Verifier) + if !ok { + l.Debug("couldn't extract sig verifier") + return nil, false, nil // couldn't extract the verifier } - // if the domain is blocked we want to make as few calls towards it as possible, so already bail here if that's the case! - blockedDomain, err := f.blockedDomain(requestingPublicKeyID.Host) + requestingPublicKeyID, err := url.Parse(verifier.KeyId()) if err != nil { - return nil, fmt.Errorf("could not tell if domain %s was blocked or not: %s", requestingPublicKeyID.Host, err) - } - if blockedDomain { - return nil, fmt.Errorf("host %s was domain blocked, aborting auth", requestingPublicKeyID.Host) + l.Debug("couldn't parse public key URL") + return nil, false, nil // couldn't parse the public key ID url } requestingRemoteAccount := >smodel.Account{} @@ -152,12 +148,12 @@ func (f *federator) AuthenticateFederatedRequest(requestedUsername string, r *ht // LOCAL ACCOUNT REQUEST // the request is coming from INSIDE THE HOUSE so skip the remote dereferencing if err := f.db.GetWhere([]db.Where{{Key: "public_key_uri", Value: requestingPublicKeyID.String()}}, requestingLocalAccount); err != nil { - return nil, fmt.Errorf("couldn't get local account with public key uri %s from the database: %s", requestingPublicKeyID.String(), err) + return nil, false, fmt.Errorf("couldn't get local account with public key uri %s from the database: %s", requestingPublicKeyID.String(), err) } publicKey = requestingLocalAccount.PublicKey pkOwnerURI, err = url.Parse(requestingLocalAccount.URI) if err != nil { - return nil, fmt.Errorf("error parsing url %s: %s", requestingLocalAccount.URI, err) + return nil, false, fmt.Errorf("error parsing url %s: %s", requestingLocalAccount.URI, err) } } else if err := f.db.GetWhere([]db.Where{{Key: "public_key_uri", Value: requestingPublicKeyID.String()}}, requestingRemoteAccount); err == nil { // REMOTE ACCOUNT REQUEST WITH KEY CACHED LOCALLY @@ -165,7 +161,7 @@ func (f *federator) AuthenticateFederatedRequest(requestedUsername string, r *ht publicKey = requestingRemoteAccount.PublicKey pkOwnerURI, err = url.Parse(requestingRemoteAccount.URI) if err != nil { - return nil, fmt.Errorf("error parsing url %s: %s", requestingRemoteAccount.URI, err) + return nil, false, fmt.Errorf("error parsing url %s: %s", requestingRemoteAccount.URI, err) } } else { // REMOTE ACCOUNT REQUEST WITHOUT KEY CACHED LOCALLY @@ -173,72 +169,55 @@ func (f *federator) AuthenticateFederatedRequest(requestedUsername string, r *ht // so we need to authenticate the request properly by dereferencing the remote key transport, err := f.GetTransportForUser(requestedUsername) if err != nil { - return nil, fmt.Errorf("transport err: %s", err) + return nil, false, fmt.Errorf("transport err: %s", err) } // The actual http call to the remote server is made right here in the Dereference function. b, err := transport.Dereference(context.Background(), requestingPublicKeyID) if err != nil { - return nil, fmt.Errorf("error deferencing key %s: %s", requestingPublicKeyID.String(), err) + return nil, false, fmt.Errorf("error deferencing key %s: %s", requestingPublicKeyID.String(), err) } // if the key isn't in the response, we can't authenticate the request requestingPublicKey, err := getPublicKeyFromResponse(context.Background(), b, requestingPublicKeyID) if err != nil { - return nil, fmt.Errorf("error getting key %s from response %s: %s", requestingPublicKeyID.String(), string(b), err) + return nil, false, fmt.Errorf("error getting key %s from response %s: %s", requestingPublicKeyID.String(), string(b), err) } // we should be able to get the actual key embedded in the vocab.W3IDSecurityV1PublicKey pkPemProp := requestingPublicKey.GetW3IDSecurityV1PublicKeyPem() if pkPemProp == nil || !pkPemProp.IsXMLSchemaString() { - return nil, errors.New("publicKeyPem property is not provided or it is not embedded as a value") + return nil, false, errors.New("publicKeyPem property is not provided or it is not embedded as a value") } // 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" { - return nil, errors.New("could not decode publicKeyPem to PUBLIC KEY pem block type") + return nil, false, errors.New("could not decode publicKeyPem to PUBLIC KEY pem block type") } publicKey, err = x509.ParsePKIXPublicKey(block.Bytes) if err != nil { - return nil, fmt.Errorf("could not parse public key from block bytes: %s", err) + return nil, false, fmt.Errorf("could not parse public key from block bytes: %s", err) } // all good! we just need the URI of the key owner to return pkOwnerProp := requestingPublicKey.GetW3IDSecurityV1Owner() if pkOwnerProp == nil || !pkOwnerProp.IsIRI() { - return nil, errors.New("publicKeyOwner property is not provided or it is not embedded as a value") + return nil, false, errors.New("publicKeyOwner property is not provided or it is not embedded as a value") } pkOwnerURI = pkOwnerProp.GetIRI() } if publicKey == nil { - return nil, errors.New("returned public key was empty") + return nil, false, errors.New("returned public key was empty") } // do the actual authentication here! algo := httpsig.RSA_SHA256 // TODO: make this more robust if err := verifier.Verify(publicKey, algo); err != nil { - return nil, fmt.Errorf("error verifying key %s: %s", requestingPublicKeyID.String(), err) - } - - return pkOwnerURI, nil -} - -func (f *federator) blockedDomain(host string) (bool, error) { - b := >smodel.DomainBlock{} - err := f.db.GetWhere([]db.Where{{Key: "domain", Value: host, CaseInsensitive: true}}, b) - if err == nil { - // block exists - return true, nil - } - - if _, ok := err.(db.ErrNoEntries); ok { - // there are no entries so there's no block - return false, nil + return nil, false, nil } - // there's an actual error - return false, err + return pkOwnerURI, true, nil } |