2021-05-08 12:25:55 +00:00
/ *
GoToSocial
2021-12-20 17:42:19 +00:00
Copyright ( C ) 2021 - 2022 GoToSocial Authors admin @ gotosocial . org
2021-05-08 12:25:55 +00:00
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
* /
package federation
import (
"context"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"net/url"
2021-05-21 13:48:26 +00:00
"strings"
2021-05-08 12:25:55 +00:00
"github.com/go-fed/httpsig"
2021-11-13 16:29:43 +00:00
"github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/activity/streams/vocab"
2021-12-20 14:19:53 +00:00
"github.com/superseriousbusiness/gotosocial/internal/ap"
2021-12-07 12:31:39 +00:00
"github.com/superseriousbusiness/gotosocial/internal/config"
2022-04-26 16:10:11 +00:00
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
2021-05-08 12:25:55 +00:00
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
2022-07-19 08:47:55 +00:00
"github.com/superseriousbusiness/gotosocial/internal/log"
2022-11-11 11:18:38 +00:00
"github.com/superseriousbusiness/gotosocial/internal/transport"
2021-05-08 12:25:55 +00:00
)
/ *
2022-09-02 09:58:42 +00:00
publicKeyer is BORROWED DIRECTLY FROM https : //github.com/go-fed/apcore/blob/master/ap/util.go
Thank you @ cj @ mastodon . technology ! < 3
2021-05-08 12:25:55 +00:00
* /
type publicKeyer interface {
GetW3IDSecurityV1PublicKey ( ) vocab . W3IDSecurityV1PublicKeyProperty
}
/ *
2022-09-02 09:58:42 +00:00
getPublicKeyFromResponse is adapted from https : //github.com/go-fed/apcore/blob/master/ap/util.go
Thank you @ cj @ mastodon . technology ! < 3
2021-05-08 12:25:55 +00:00
* /
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
}
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
2021-06-27 14:52:18 +00:00
// the URL of the owner of the public key used in the requesting http signature.
2021-05-08 12:25:55 +00:00
//
2021-06-27 14:52:18 +00:00
// 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.
//
2021-05-08 12:25:55 +00:00
// 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.
//
// 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.
2022-04-26 16:10:11 +00:00
func ( f * federator ) AuthenticateFederatedRequest ( ctx context . Context , requestedUsername string ) ( * url . URL , gtserror . WithCode ) {
2021-06-27 14:52:18 +00:00
var publicKey interface { }
var pkOwnerURI * url . URL
var err error
2021-07-05 11:23:03 +00:00
// thanks to signaturecheck.go in the security package, we should already have a signature verifier set on the context
2021-12-20 14:19:53 +00:00
vi := ctx . Value ( ap . ContextRequestingPublicKeyVerifier )
2021-07-05 11:23:03 +00:00
if vi == nil {
2022-04-26 16:10:11 +00:00
err := errors . New ( "http request wasn't signed or http signature was invalid" )
2022-06-08 18:38:03 +00:00
errWithCode := gtserror . NewErrorUnauthorized ( err , err . Error ( ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-05-08 12:25:55 +00:00
}
2021-07-05 11:23:03 +00:00
verifier , ok := vi . ( httpsig . Verifier )
if ! ok {
2022-04-26 16:10:11 +00:00
err := errors . New ( "http request wasn't signed or http signature was invalid" )
2022-06-08 18:38:03 +00:00
errWithCode := gtserror . NewErrorUnauthorized ( err , err . Error ( ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-05-08 12:25:55 +00:00
}
2021-09-16 09:35:09 +00:00
// we should have the signature itself set too
2021-12-20 14:19:53 +00:00
si := ctx . Value ( ap . ContextRequestingPublicKeySignature )
2022-04-26 16:10:11 +00:00
if si == nil {
err := errors . New ( "http request wasn't signed or http signature was invalid" )
2022-06-08 18:38:03 +00:00
errWithCode := gtserror . NewErrorUnauthorized ( err , err . Error ( ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-09-16 09:35:09 +00:00
}
signature , ok := si . ( string )
if ! ok {
2022-04-26 16:10:11 +00:00
err := errors . New ( "http request wasn't signed or http signature was invalid" )
2022-06-08 18:38:03 +00:00
errWithCode := gtserror . NewErrorUnauthorized ( err , err . Error ( ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-09-16 09:35:09 +00:00
}
2022-04-26 16:10:11 +00:00
// now figure out who actually signed it
2021-07-05 11:23:03 +00:00
requestingPublicKeyID , err := url . Parse ( verifier . KeyId ( ) )
2021-06-27 14:52:18 +00:00
if err != nil {
2022-04-26 16:10:11 +00:00
errWithCode := gtserror . NewErrorBadRequest ( err , fmt . Sprintf ( "couldn't parse public key URL %s" , verifier . KeyId ( ) ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-06-27 14:52:18 +00:00
}
2022-09-02 09:58:42 +00:00
var (
requestingLocalAccount * gtsmodel . Account
requestingRemoteAccount * gtsmodel . Account
requestingHost = requestingPublicKeyID . Host
)
2022-05-30 12:41:24 +00:00
if host := config . GetHost ( ) ; strings . EqualFold ( requestingHost , host ) {
2021-05-23 16:07:04 +00:00
// LOCAL ACCOUNT REQUEST
2021-05-21 13:48:26 +00:00
// the request is coming from INSIDE THE HOUSE so skip the remote dereferencing
2022-07-19 08:47:55 +00:00
log . Tracef ( "proceeding without dereference for local public key %s" , requestingPublicKeyID )
2022-09-02 09:58:42 +00:00
requestingLocalAccount , err = f . db . GetAccountByPubkeyID ( ctx , requestingPublicKeyID . String ( ) )
if err != nil {
2022-04-26 16:10:11 +00:00
errWithCode := gtserror . NewErrorInternalError ( fmt . Errorf ( "couldn't get account with public key uri %s from the database: %s" , requestingPublicKeyID . String ( ) , err ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-05-21 13:48:26 +00:00
}
2022-09-02 09:58:42 +00:00
2021-05-21 13:48:26 +00:00
publicKey = requestingLocalAccount . PublicKey
2022-09-02 09:58:42 +00:00
2021-05-21 13:48:26 +00:00
pkOwnerURI , err = url . Parse ( requestingLocalAccount . URI )
if err != nil {
2022-04-26 16:10:11 +00:00
errWithCode := gtserror . NewErrorBadRequest ( err , fmt . Sprintf ( "couldn't parse public key owner URL %s" , requestingLocalAccount . URI ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-05-21 13:48:26 +00:00
}
2022-09-02 09:58:42 +00:00
} else if requestingRemoteAccount , err = f . db . GetAccountByPubkeyID ( ctx , requestingPublicKeyID . String ( ) ) ; err == nil {
2021-05-23 16:07:04 +00:00
// REMOTE ACCOUNT REQUEST WITH KEY CACHED LOCALLY
// this is a remote account and we already have the public key for it so use that
2022-07-19 08:47:55 +00:00
log . Tracef ( "proceeding without dereference for cached public key %s" , requestingPublicKeyID )
2021-05-23 16:07:04 +00:00
publicKey = requestingRemoteAccount . PublicKey
pkOwnerURI , err = url . Parse ( requestingRemoteAccount . URI )
if err != nil {
2022-04-26 16:10:11 +00:00
errWithCode := gtserror . NewErrorBadRequest ( err , fmt . Sprintf ( "couldn't parse public key owner URL %s" , requestingRemoteAccount . URI ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-05-23 16:07:04 +00:00
}
2021-05-21 13:48:26 +00:00
} else {
2021-05-23 16:07:04 +00:00
// 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
2022-11-11 11:18:38 +00:00
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 ( errWithCode )
return nil , errWithCode
}
if gone {
errWithCode := gtserror . NewErrorGone ( fmt . Errorf ( "account with public key %s is gone" , requestingPublicKeyID ) )
log . Debug ( errWithCode )
return nil , errWithCode
}
2022-07-19 08:47:55 +00:00
log . Tracef ( "proceeding with dereference for uncached public key %s" , requestingPublicKeyID )
2022-11-11 11:18:38 +00:00
trans , err := f . transportController . NewTransportForUsername ( ctx , requestedUsername )
2021-05-21 13:48:26 +00:00
if err != nil {
2022-04-26 16:10:11 +00:00
errWithCode := gtserror . NewErrorInternalError ( fmt . Errorf ( "error creating transport for %s: %s" , requestedUsername , err ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-05-21 13:48:26 +00:00
}
2021-05-08 12:25:55 +00:00
2021-05-21 13:48:26 +00:00
// The actual http call to the remote server is made right here in the Dereference function.
2022-11-11 11:18:38 +00:00
b , err := trans . Dereference ( ctx , requestingPublicKeyID )
2021-05-21 13:48:26 +00:00
if err != nil {
2022-11-11 11:18:38 +00:00
if errors . Is ( err , transport . ErrGone ) {
// 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 ( errWithCode )
return nil , errWithCode
}
errWithCode := gtserror . NewErrorGone ( fmt . Errorf ( "account with public key %s is gone" , requestingPublicKeyID ) )
log . Debug ( errWithCode )
return nil , errWithCode
}
2022-06-08 18:38:03 +00:00
errWithCode := gtserror . NewErrorUnauthorized ( fmt . Errorf ( "error dereferencing public key %s: %s" , requestingPublicKeyID , err ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-05-21 13:48:26 +00:00
}
2021-05-08 12:25:55 +00:00
2021-05-21 13:48:26 +00:00
// if the key isn't in the response, we can't authenticate the request
2021-10-04 13:24:19 +00:00
requestingPublicKey , err := getPublicKeyFromResponse ( ctx , b , requestingPublicKeyID )
2021-05-21 13:48:26 +00:00
if err != nil {
2022-06-08 18:38:03 +00:00
errWithCode := gtserror . NewErrorUnauthorized ( fmt . Errorf ( "error parsing public key %s: %s" , requestingPublicKeyID , err ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-05-21 13:48:26 +00:00
}
2021-05-08 12:25:55 +00:00
2021-05-21 13:48:26 +00:00
// we should be able to get the actual key embedded in the vocab.W3IDSecurityV1PublicKey
pkPemProp := requestingPublicKey . GetW3IDSecurityV1PublicKeyPem ( )
if pkPemProp == nil || ! pkPemProp . IsXMLSchemaString ( ) {
2022-06-08 18:38:03 +00:00
errWithCode := gtserror . NewErrorUnauthorized ( errors . New ( "publicKeyPem property is not provided or it is not embedded as a value" ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-05-21 13:48:26 +00:00
}
2021-05-08 12:25:55 +00:00
2021-05-21 13:48:26 +00:00
// 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" {
2022-06-08 18:38:03 +00:00
errWithCode := gtserror . NewErrorUnauthorized ( errors . New ( "could not decode publicKeyPem to PUBLIC KEY pem block type" ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-05-21 13:48:26 +00:00
}
2021-05-08 12:25:55 +00:00
2021-05-21 13:48:26 +00:00
publicKey , err = x509 . ParsePKIXPublicKey ( block . Bytes )
if err != nil {
2022-06-08 18:38:03 +00:00
errWithCode := gtserror . NewErrorUnauthorized ( fmt . Errorf ( "could not parse public key %s from block bytes: %s" , requestingPublicKeyID , err ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-05-21 13:48:26 +00:00
}
// all good! we just need the URI of the key owner to return
pkOwnerProp := requestingPublicKey . GetW3IDSecurityV1Owner ( )
if pkOwnerProp == nil || ! pkOwnerProp . IsIRI ( ) {
2022-06-08 18:38:03 +00:00
errWithCode := gtserror . NewErrorUnauthorized ( errors . New ( "publicKeyOwner property is not provided or it is not embedded as a value" ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-05-21 13:48:26 +00:00
}
pkOwnerURI = pkOwnerProp . GetIRI ( )
2021-05-08 12:25:55 +00:00
}
2021-08-10 11:32:39 +00:00
// after all that, public key should be defined
2021-05-21 13:48:26 +00:00
if publicKey == nil {
2022-04-26 16:10:11 +00:00
errWithCode := gtserror . NewErrorInternalError ( errors . New ( "returned public key was empty" ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-05-08 12:25:55 +00:00
}
// do the actual authentication here!
2021-08-10 11:32:39 +00:00
algos := [ ] httpsig . Algorithm {
httpsig . RSA_SHA256 ,
2022-04-26 16:10:11 +00:00
httpsig . RSA_SHA512 ,
2021-08-10 11:32:39 +00:00
httpsig . ED25519 ,
}
for _ , algo := range algos {
2022-07-19 08:47:55 +00:00
log . Tracef ( "trying algo: %s" , algo )
2021-09-16 09:35:09 +00:00
err := verifier . Verify ( publicKey , algo )
if err == nil {
2022-07-19 08:47:55 +00:00
log . Tracef ( "authentication for %s PASSED with algorithm %s" , pkOwnerURI , algo )
2022-04-26 16:10:11 +00:00
return pkOwnerURI , nil
2021-08-10 11:32:39 +00:00
}
2022-07-19 08:47:55 +00:00
log . Tracef ( "authentication for %s NOT PASSED with algorithm %s: %s" , pkOwnerURI , algo , err )
2021-05-08 12:25:55 +00:00
}
2022-06-08 18:38:03 +00:00
errWithCode := gtserror . NewErrorUnauthorized ( fmt . Errorf ( "authentication not passed for public key owner %s; signature value was '%s'" , pkOwnerURI , signature ) )
2022-07-19 08:47:55 +00:00
log . Debug ( errWithCode )
2022-04-26 16:10:11 +00:00
return nil , errWithCode
2021-05-08 12:25:55 +00:00
}