diff --git a/internal/db/gtsmodel/account.go b/internal/db/gtsmodel/account.go index 8ee05f45b..181b061df 100644 --- a/internal/db/gtsmodel/account.go +++ b/internal/db/gtsmodel/account.go @@ -46,8 +46,12 @@ type Account struct { // ID of the avatar as a media attachment AvatarMediaAttachmentID string + // For a non-local account, where can the header be fetched? + AvatarRemoteURL string // ID of the header as a media attachment HeaderMediaAttachmentID string + // For a non-local account, where can the header be fetched? + HeaderRemoteURL string // DisplayName for this account. Can be empty, then just the Username will be used for display purposes. DisplayName string // a key/value map of fields that this account has added to their profile diff --git a/internal/federation/federatingprotocol.go b/internal/federation/federatingprotocol.go index 0f1bd830e..841ed8153 100644 --- a/internal/federation/federatingprotocol.go +++ b/internal/federation/federatingprotocol.go @@ -138,27 +138,29 @@ func (f *federatingProtocol) AuthenticatePostInbox(ctx context.Context, w http.R return ctx, false, fmt.Errorf("error creating transport: %s", err) } - requestingPublicKeyID, err := AuthenticateFederatedRequest(transport, r) + publicKeyOwnerURI, err := AuthenticateFederatedRequest(transport, r) if err != nil { l.Debugf("request not authenticated: %s", err) return ctx, false, fmt.Errorf("not authenticated: %s", err) } requestingAccount := >smodel.Account{} - if err := f.db.GetWhere("public_key_uri", requestingPublicKeyID.String(), requestingAccount); err != nil { + if err := f.db.GetWhere("uri", publicKeyOwnerURI.String(), requestingAccount); err != nil { // there's been a proper error so return it if _, ok := err.(db.ErrNoEntries); !ok { - return ctx, false, fmt.Errorf("error getting requesting account with public key id %s: %s", requestingPublicKeyID.String(), err) + return ctx, false, fmt.Errorf("error getting requesting account with public key id %s: %s", publicKeyOwnerURI.String(), err) } + // we just don't know this account (yet) so try to dereference it // TODO: slow-fed - person, err := DereferenceAccount(transport, requestingPublicKeyID) + person, err := DereferenceAccount(transport, publicKeyOwnerURI) if err != nil { - return ctx, false, fmt.Errorf("error dereferencing account with public key id %s: %s", requestingPublicKeyID.String(), err) + return ctx, false, fmt.Errorf("error dereferencing account with public key id %s: %s", publicKeyOwnerURI.String(), err) } + a, err := f.typeConverter.ASPersonToAccount(person) if err != nil { - return ctx, false, fmt.Errorf("error converting person with public key id %s to account: %s", requestingPublicKeyID.String(), err) + return ctx, false, fmt.Errorf("error converting person with public key id %s to account: %s", publicKeyOwnerURI.String(), err) } requestingAccount = a } diff --git a/internal/federation/util.go b/internal/federation/util.go index 997550b9f..9bc47d90f 100644 --- a/internal/federation/util.go +++ b/internal/federation/util.go @@ -20,7 +20,6 @@ package federation import ( "context" - "crypto" "crypto/x509" "encoding/json" "encoding/pem" @@ -45,30 +44,30 @@ type publicKeyer interface { } /* - getPublicKeyFromResponse is BORROWED DIRECTLY FROM https://github.com/go-fed/apcore/blob/master/ap/util.go + 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) (p crypto.PublicKey, err error) { +func getPublicKeyFromResponse(c context.Context, b []byte, keyID *url.URL) (vocab.W3IDSecurityV1PublicKey, error) { m := make(map[string]interface{}) - err = json.Unmarshal(b, &m) - if err != nil { - return + if err := json.Unmarshal(b, &m); err != nil { + return nil, err } - var t vocab.Type - t, err = streams.ToType(c, m) + + t, err := streams.ToType(c, m) if err != nil { - return + return nil, err } + pker, ok := t.(publicKeyer) if !ok { - err = fmt.Errorf("ActivityStreams type cannot be converted to one known to have publicKey property: %T", t) - return + return nil, fmt.Errorf("ActivityStreams type cannot be converted to one known to have publicKey property: %T", t) } + pkp := pker.GetW3IDSecurityV1PublicKey() if pkp == nil { - err = fmt.Errorf("publicKey property is not provided") - return + 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() { @@ -78,7 +77,7 @@ func getPublicKeyFromResponse(c context.Context, b []byte, keyID *url.URL) (p cr var pkID *url.URL pkID, err = pub.GetId(pkValue) if err != nil { - return + return nil, err } if pkID.String() != keyID.String() { continue @@ -86,24 +85,12 @@ func getPublicKeyFromResponse(c context.Context, b []byte, keyID *url.URL) (p cr pkpFound = pkValue break } + if pkpFound == nil { - err = fmt.Errorf("cannot find publicKey with id: %s", keyID) - return + return nil, fmt.Errorf("cannot find publicKey with id: %s", keyID) } - pkPemProp := pkpFound.GetW3IDSecurityV1PublicKeyPem() - if pkPemProp == nil || !pkPemProp.IsXMLSchemaString() { - err = fmt.Errorf("publicKeyPem property is not provided or it is not embedded as a value") - return - } - pubKeyPem := pkPemProp.Get() - var block *pem.Block - block, _ = pem.Decode([]byte(pubKeyPem)) - if block == nil || block.Type != "PUBLIC KEY" { - err = fmt.Errorf("could not decode publicKeyPem to PUBLIC KEY pem block type") - return - } - p, err = x509.ParsePKIXPublicKey(block.Bytes) - return + + return pkpFound, nil } // AuthenticateFederatedRequest authenticates any kind of federated request from a remote server. This includes things like @@ -121,7 +108,7 @@ func getPublicKeyFromResponse(c context.Context, b []byte, keyID *url.URL) (p cr // dereferencing request, and they can decide to allow or deny the request depending on their settings. // // Note that this function *does not* dereference the remote account that the signature key is associated with, but it will -// return the public key URL associated with that account, so that other functions can dereference it with that, as required. +// return the owner of the public key, so that other functions can dereference it with that, as required. func AuthenticateFederatedRequest(transport pub.Transport, r *http.Request) (*url.URL, error) { verifier, err := httpsig.NewVerifier(r) if err != nil { @@ -147,20 +134,42 @@ func AuthenticateFederatedRequest(transport pub.Transport, r *http.Request) (*ur return nil, fmt.Errorf("error getting key %s from response %s: %s", requestingPublicKeyID.String(), string(b), err) } + 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") + } + pkOwnerURI := pkOwnerProp.GetIRI() + + 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") + } + + 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") + } + + p, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("could not parse public key from block bytes: %s", err) + } + // do the actual authentication here! algo := httpsig.RSA_SHA256 // TODO: make this more robust - if err := verifier.Verify(requestingPublicKey, algo); err != nil { + if err := verifier.Verify(p, algo); err != nil { return nil, fmt.Errorf("error verifying key %s: %s", requestingPublicKeyID.String(), err) } // all good! - return requestingPublicKeyID, nil + return pkOwnerURI, nil } -func DereferenceAccount(transport pub.Transport, publicKeyID *url.URL) (vocab.ActivityStreamsPerson, error) { - b, err := transport.Dereference(context.Background(), publicKeyID) +func DereferenceAccount(transport pub.Transport, id *url.URL) (vocab.ActivityStreamsPerson, error) { + b, err := transport.Dereference(context.Background(), id) if err != nil { - return nil, fmt.Errorf("error deferencing %s: %s", publicKeyID.String(), err) + return nil, fmt.Errorf("error deferencing %s: %s", id.String(), err) } m := make(map[string]interface{}) diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go index 9b1615645..6593de5b7 100644 --- a/internal/typeutils/astointernal.go +++ b/internal/typeutils/astointernal.go @@ -1,10 +1,77 @@ package typeutils import ( + "errors" + "time" + "github.com/go-fed/activity/streams/vocab" + "github.com/google/uuid" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" ) func (c *converter) ASPersonToAccount(person vocab.ActivityStreamsPerson) (*gtsmodel.Account, error) { - return nil, nil + + + + acct := >smodel.Account{ + URI: "", + URL: "", + ID: "", + Username: "", + Domain: "", + AvatarMediaAttachmentID: "", + AvatarRemoteURL: "", + HeaderMediaAttachmentID: "", + HeaderRemoteURL: "", + DisplayName: "", + Fields: nil, + Note: "", + Memorial: false, + MovedToAccountID: "", + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + Bot: false, + Reason: "", + Locked: false, + Discoverable: true, + Privacy: "", + Sensitive: false, + Language: "", + LastWebfingeredAt: time.Now(), + InboxURI: "", + OutboxURI: "", + FollowingURI: "", + FollowersURI: "", + FeaturedCollectionURI: "", + ActorType: gtsmodel.ActivityStreamsPerson, + AlsoKnownAs: "", + PrivateKey: nil, + PublicKey: nil, + PublicKeyURI: "", + SensitizedAt: time.Time{}, + SilencedAt: time.Time{}, + SuspendedAt: time.Time{}, + HideCollections: false, + SuspensionOrigin: "", + } + + // ID + // Generate a new uuid for our particular database. + // This is distinct from the AP ID of the person. + id := uuid.NewString() + acct.ID = id + + // Username + // We need this one so bail if it's not set. + username := person.GetActivityStreamsPreferredUsername() + if username == nil || username.GetXMLSchemaString() == "" { + return nil, errors.New("preferredusername was empty") + } + acct.Username = username.GetXMLSchemaString() + + // Domain + // We need this one as well + acct.Domain = domain + + return acct, nil }