mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-31 06:21:11 +00:00 
			
		
		
		
	Fix parts of issue #8221 and part of PR #4767
Is linked to https://codeberg.org/forgejo/forgejo/pulls/8274
The commit 555f6e57ad fixes timeout forgejo/forgejo#8274 (Kommentar)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8708
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Michael Jerger <michael.jerger@meissa-gmbh.de>
Co-committed-by: Michael Jerger <michael.jerger@meissa-gmbh.de>
		
	
			
		
			
				
	
	
		
			235 lines
		
	
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			235 lines
		
	
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2024, 2025 The Forgejo Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package federation
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"crypto/x509"
 | |
| 	"database/sql"
 | |
| 	"encoding/pem"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"net/url"
 | |
| 
 | |
| 	"forgejo.org/models/forgefed"
 | |
| 	"forgejo.org/models/user"
 | |
| 	"forgejo.org/modules/activitypub"
 | |
| 	fm "forgejo.org/modules/forgefed"
 | |
| 
 | |
| 	ap "github.com/go-ap/activitypub"
 | |
| )
 | |
| 
 | |
| // Factory function for ActorID. Created struct is asserted to be valid
 | |
| func NewActorIDFromKeyID(ctx context.Context, uri string) (fm.ActorID, error) {
 | |
| 	parsedURI, err := url.Parse(uri)
 | |
| 	parsedURI.Fragment = ""
 | |
| 	if err != nil {
 | |
| 		return fm.ActorID{}, err
 | |
| 	}
 | |
| 
 | |
| 	actionsUser := user.NewAPServerActor()
 | |
| 	clientFactory, err := activitypub.GetClientFactory(ctx)
 | |
| 	if err != nil {
 | |
| 		return fm.ActorID{}, err
 | |
| 	}
 | |
| 
 | |
| 	apClient, err := clientFactory.WithKeys(ctx, actionsUser, actionsUser.KeyID())
 | |
| 	if err != nil {
 | |
| 		return fm.ActorID{}, err
 | |
| 	}
 | |
| 
 | |
| 	userResponse, err := apClient.GetBody(parsedURI.String())
 | |
| 	if err != nil {
 | |
| 		return fm.ActorID{}, err
 | |
| 	}
 | |
| 
 | |
| 	var actor ap.Actor
 | |
| 	err = actor.UnmarshalJSON(userResponse)
 | |
| 	if err != nil {
 | |
| 		return fm.ActorID{}, err
 | |
| 	}
 | |
| 
 | |
| 	result, err := fm.NewActorID(actor.PublicKey.Owner.String())
 | |
| 	return result, err
 | |
| }
 | |
| 
 | |
| func FindOrCreateFederatedUserKey(ctx context.Context, keyID string) (pubKey any, err error) {
 | |
| 	var federatedUser *user.FederatedUser
 | |
| 	var keyURL *url.URL
 | |
| 
 | |
| 	keyURL, err = url.Parse(keyID)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// Try if the signing actor is an already known federated user
 | |
| 	_, federatedUser, err = user.FindFederatedUserByKeyID(ctx, keyURL.String())
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if federatedUser == nil {
 | |
| 		rawActorID, err := NewActorIDFromKeyID(ctx, keyID)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		_, federatedUser, _, err = FindOrCreateFederatedUser(ctx, rawActorID.AsURI())
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	} else {
 | |
| 		_, err = forgefed.GetFederationHost(ctx, federatedUser.FederationHostID)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if federatedUser.PublicKey.Valid {
 | |
| 		pubKey, err := x509.ParsePKIXPublicKey(federatedUser.PublicKey.V)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		return pubKey, nil
 | |
| 	}
 | |
| 
 | |
| 	// Fetch missing public key
 | |
| 	pubKey, pubKeyBytes, apPerson, err := fetchKeyFromAp(ctx, *keyURL)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if apPerson.Type == ap.ActivityVocabularyType("Person") {
 | |
| 		// Check federatedUser.id = person.id
 | |
| 		if federatedUser.ExternalID != apPerson.ID.String() {
 | |
| 			return nil, fmt.Errorf("federated user fetched (%v) does not match the stored one %v", apPerson, federatedUser)
 | |
| 		}
 | |
| 		// update federated user
 | |
| 		federatedUser.KeyID = sql.NullString{
 | |
| 			String: apPerson.PublicKey.ID.String(),
 | |
| 			Valid:  true,
 | |
| 		}
 | |
| 		federatedUser.PublicKey = sql.Null[sql.RawBytes]{
 | |
| 			V:     pubKeyBytes,
 | |
| 			Valid: true,
 | |
| 		}
 | |
| 		err = user.UpdateFederatedUser(ctx, federatedUser)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		return pubKey, nil
 | |
| 	}
 | |
| 	return nil, nil
 | |
| }
 | |
| 
 | |
| func FindOrCreateFederationHostKey(ctx context.Context, keyID string) (pubKey any, err error) {
 | |
| 	keyURL, err := url.Parse(keyID)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	rawActorID, err := NewActorIDFromKeyID(ctx, keyID)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// Is there an already known federation host?
 | |
| 	federationHost, err := forgefed.FindFederationHostByKeyID(ctx, keyURL.String())
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if federationHost == nil {
 | |
| 		federationHost, err = FindOrCreateFederationHost(ctx, rawActorID.AsURI())
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Is there an already an key?
 | |
| 	if federationHost.PublicKey.Valid {
 | |
| 		pubKey, err := x509.ParsePKIXPublicKey(federationHost.PublicKey.V)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		return pubKey, nil
 | |
| 	}
 | |
| 
 | |
| 	// If not, fetch missing public key
 | |
| 	pubKey, pubKeyBytes, apPerson, err := fetchKeyFromAp(ctx, *keyURL)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if apPerson.Type == ap.ActivityVocabularyType("Application") {
 | |
| 		// Check federationhost.id = person.id
 | |
| 		if federationHost.HostPort != rawActorID.HostPort || federationHost.HostFqdn != rawActorID.Host ||
 | |
| 			federationHost.HostSchema != rawActorID.HostSchema {
 | |
| 			return nil, fmt.Errorf("federation host fetched (%v) does not match the stored one %v", apPerson, federationHost)
 | |
| 		}
 | |
| 		// update federation host
 | |
| 		federationHost.KeyID = sql.NullString{
 | |
| 			String: apPerson.PublicKey.ID.String(),
 | |
| 			Valid:  true,
 | |
| 		}
 | |
| 		federationHost.PublicKey = sql.Null[sql.RawBytes]{
 | |
| 			V:     pubKeyBytes,
 | |
| 			Valid: true,
 | |
| 		}
 | |
| 		err = forgefed.UpdateFederationHost(ctx, federationHost)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		return pubKey, nil
 | |
| 	}
 | |
| 	return nil, nil
 | |
| }
 | |
| 
 | |
| func fetchKeyFromAp(ctx context.Context, keyURL url.URL) (pubKey any, pubKeyBytes []byte, apPerson *ap.Person, err error) {
 | |
| 	actionsUser := user.NewAPServerActor()
 | |
| 
 | |
| 	clientFactory, err := activitypub.GetClientFactory(ctx)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, nil, err
 | |
| 	}
 | |
| 
 | |
| 	apClient, err := clientFactory.WithKeys(ctx, actionsUser, actionsUser.KeyID())
 | |
| 	if err != nil {
 | |
| 		return nil, nil, nil, err
 | |
| 	}
 | |
| 
 | |
| 	b, err := apClient.GetBody(keyURL.String())
 | |
| 	if err != nil {
 | |
| 		return nil, nil, nil, err
 | |
| 	}
 | |
| 
 | |
| 	person := ap.PersonNew(ap.IRI(keyURL.String()))
 | |
| 	err = person.UnmarshalJSON(b)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, nil, fmt.Errorf("ActivityStreams type cannot be converted to one known to have publicKey property: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	pubKeyFromAp := person.PublicKey
 | |
| 	if pubKeyFromAp.ID.String() != keyURL.String() {
 | |
| 		return nil, nil, nil, fmt.Errorf("cannot find publicKey with id: %v in %v", keyURL, string(b))
 | |
| 	}
 | |
| 
 | |
| 	pubKeyBytes, err = decodePublicKeyPem(pubKeyFromAp.PublicKeyPem)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, nil, err
 | |
| 	}
 | |
| 
 | |
| 	pubKey, err = x509.ParsePKIXPublicKey(pubKeyBytes)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, nil, err
 | |
| 	}
 | |
| 
 | |
| 	return pubKey, pubKeyBytes, person, err
 | |
| }
 | |
| 
 | |
| func decodePublicKeyPem(pubKeyPem string) ([]byte, error) {
 | |
| 	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 block.Bytes, nil
 | |
| }
 |