Files
gitocean/services/federation/signature_service.go
T
famfoandGusted 5f432e32c8 chore(federation): re-enable nilnil lint (#11253)
First round of patches to re-enable some lints from my side.

This PR also refactors the general key fetching code quite a bit due to the way it currently worked
with relying on some values being nil sometimes.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11253
Reviewed-by: elle <0xllx0@noreply.codeberg.org>
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: famfo <famfo@famfo.xyz>
Co-committed-by: famfo <famfo@famfo.xyz>
2026-04-13 22:05:29 +02:00

195 lines
4.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"
"forgejo.org/modules/log"
ap "github.com/go-ap/activitypub"
)
func FindOrCreateActorKey(ctx context.Context, keyID string) (pubKey any, err error) {
log.Trace("KeyID: %v", keyID)
keyURL, err := url.Parse(keyID)
if err != nil {
return nil, err
}
// Check for existing user key
_, federatedUser, err := user.FindFederatedUserByKeyID(ctx, keyURL.String())
if err != nil {
if !user.IsErrFederatedUserNotExists(err) {
return nil, err
}
// Check for existing federation host key
federationHost, err := forgefed.FindFederationHostByKeyID(ctx, keyURL.String())
if err != nil {
if !forgefed.IsErrFederationHostNotFound(err) {
return nil, err
}
} else if federationHost.PublicKey.Valid {
pubKey, err := x509.ParsePKIXPublicKey(federationHost.PublicKey.V)
if err != nil {
return nil, err
}
return pubKey, nil
}
} else if federatedUser.PublicKey.Valid {
pubKey, err := x509.ParsePKIXPublicKey(federatedUser.PublicKey.V)
if err != nil {
return nil, err
}
return pubKey, nil
}
// Fetch missing key
pubKey, pubKeyBytes, actor, err := fetchKeyFromAp(ctx, *keyURL)
if err != nil {
return nil, err
}
switch actor.Type {
case ap.PersonType:
_, federatedUser, _, err := FindOrCreateFederatedUser(ctx, actor.ID.String())
if err != nil {
return nil, err
}
err = updateFederatedUserKey(ctx, federatedUser, pubKeyBytes, actor)
if err != nil {
return nil, err
}
case ap.ApplicationType:
federationHost, err := FindOrCreateFederationHost(ctx, actor.ID.String())
if err != nil {
return nil, err
}
err = updateFederationHostKey(ctx, federationHost, pubKeyBytes, actor)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("Fetched actortype (%s) is unhandled", actor.Type)
}
return pubKey, nil
}
func updateFederatedUserKey(ctx context.Context, federatedUser *user.FederatedUser, pubKeyBytes []byte, actor *ap.Actor) (err error) {
if actor.Type != ap.PersonType {
return fmt.Errorf("Fetched user type (%s) is not of user type Person", actor.Type)
}
if federatedUser.NormalizedOriginalURL != actor.ID.String() {
return fmt.Errorf("federated user (%s) does not match the stored one %s", actor.ID, federatedUser.NormalizedOriginalURL)
}
federatedUser.KeyID = sql.NullString{
String: actor.PublicKey.ID.String(),
Valid: true,
}
federatedUser.PublicKey = sql.Null[sql.RawBytes]{
V: pubKeyBytes,
Valid: true,
}
return user.UpdateFederatedUser(ctx, federatedUser)
}
func updateFederationHostKey(ctx context.Context, federationHost *forgefed.FederationHost, pubKeyBytes []byte, actor *ap.Actor) (err error) {
if actor.Type != ap.ApplicationType {
return fmt.Errorf("Fetched user type (%s) is not of user type Application", actor.Type)
}
federationHost.KeyID = sql.NullString{
String: actor.PublicKey.ID.String(),
Valid: true,
}
federationHost.PublicKey = sql.Null[sql.RawBytes]{
V: pubKeyBytes,
Valid: true,
}
err = forgefed.UpdateFederationHost(ctx, federationHost)
if err != nil {
return err
}
return nil
}
func fetchKeyFromAp(ctx context.Context, keyURL url.URL) (pubKey any, pubKeyBytes []byte, apPerson *ap.Actor, err error) {
log.Trace("keyURL %v", keyURL)
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
}
actor := ap.ActorNew(ap.IRI(keyURL.String()), ap.ActorType)
err = actor.UnmarshalJSON(b)
if err != nil {
return nil, nil, nil, fmt.Errorf("ActivityStreams object cannot be converted to actor: %w", err)
}
pubKeyFromAp := actor.PublicKey
if pubKeyFromAp.PublicKeyPem == "" {
return nil, nil, nil, ErrKeyNotFound{KeyID: keyURL.String()}
}
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
}
log.Trace("For %v fetched pubKey %v", keyURL, pubKey)
return pubKey, pubKeyBytes, actor, 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
}