mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-25 11:33:11 +00:00 
			
		
		
		
	- When the GPG key contains an error, such as an invalid signature or an email address that does not match the user.A page will be shown that says you must provide a signature for the token. - This page had two errors: one had the wrong translation key and the other tried to use an undefined variable [`.PaddedKeyID`](e81ccc406b/models/asymkey/gpg_key.go (L65-L72)), which is a function implemented on the `GPGKey` struct, given that we don't have that, we use [`KeyID`](e81ccc406b/routers/web/user/setting/keys.go (L102)) which is [the fingerprint of the publickey](https://pkg.go.dev/golang.org/x/crypto/openpgp/packet#PublicKey.KeyIdString) and is a valid way for opengpg to refer to a key. Before:  After:  Co-authored-by: zeripath <art27@cantab.net>
		
			
				
	
	
		
			282 lines
		
	
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			282 lines
		
	
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2017 The Gitea Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package asymkey
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"code.gitea.io/gitea/models/db"
 | |
| 	user_model "code.gitea.io/gitea/models/user"
 | |
| 	"code.gitea.io/gitea/modules/log"
 | |
| 	"code.gitea.io/gitea/modules/timeutil"
 | |
| 
 | |
| 	"github.com/keybase/go-crypto/openpgp"
 | |
| 	"github.com/keybase/go-crypto/openpgp/packet"
 | |
| 	"xorm.io/xorm"
 | |
| )
 | |
| 
 | |
| //   __________________  ________   ____  __.
 | |
| //  /  _____/\______   \/  _____/  |    |/ _|____ ___.__.
 | |
| // /   \  ___ |     ___/   \  ___  |      <_/ __ <   |  |
 | |
| // \    \_\  \|    |   \    \_\  \ |    |  \  ___/\___  |
 | |
| //	\______  /|____|    \______  / |____|__ \___  > ____|
 | |
| //				 \/                  \/          \/   \/\/
 | |
| 
 | |
| // GPGKey represents a GPG key.
 | |
| type GPGKey struct {
 | |
| 	ID                int64              `xorm:"pk autoincr"`
 | |
| 	OwnerID           int64              `xorm:"INDEX NOT NULL"`
 | |
| 	KeyID             string             `xorm:"INDEX CHAR(16) NOT NULL"`
 | |
| 	PrimaryKeyID      string             `xorm:"CHAR(16)"`
 | |
| 	Content           string             `xorm:"MEDIUMTEXT NOT NULL"`
 | |
| 	CreatedUnix       timeutil.TimeStamp `xorm:"created"`
 | |
| 	ExpiredUnix       timeutil.TimeStamp
 | |
| 	AddedUnix         timeutil.TimeStamp
 | |
| 	SubsKey           []*GPGKey `xorm:"-"`
 | |
| 	Emails            []*user_model.EmailAddress
 | |
| 	Verified          bool `xorm:"NOT NULL DEFAULT false"`
 | |
| 	CanSign           bool
 | |
| 	CanEncryptComms   bool
 | |
| 	CanEncryptStorage bool
 | |
| 	CanCertify        bool
 | |
| }
 | |
| 
 | |
| func init() {
 | |
| 	db.RegisterModel(new(GPGKey))
 | |
| }
 | |
| 
 | |
| // BeforeInsert will be invoked by XORM before inserting a record
 | |
| func (key *GPGKey) BeforeInsert() {
 | |
| 	key.AddedUnix = timeutil.TimeStampNow()
 | |
| }
 | |
| 
 | |
| // AfterLoad is invoked from XORM after setting the values of all fields of this object.
 | |
| func (key *GPGKey) AfterLoad(session *xorm.Session) {
 | |
| 	err := session.Where("primary_key_id=?", key.KeyID).Find(&key.SubsKey)
 | |
| 	if err != nil {
 | |
| 		log.Error("Find Sub GPGkeys[%s]: %v", key.KeyID, err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // PaddedKeyID show KeyID padded to 16 characters
 | |
| func (key *GPGKey) PaddedKeyID() string {
 | |
| 	return PaddedKeyID(key.KeyID)
 | |
| }
 | |
| 
 | |
| // PaddedKeyID show KeyID padded to 16 characters
 | |
| func PaddedKeyID(keyID string) string {
 | |
| 	if len(keyID) > 15 {
 | |
| 		return keyID
 | |
| 	}
 | |
| 	zeros := "0000000000000000"
 | |
| 	return zeros[0:16-len(keyID)] + keyID
 | |
| }
 | |
| 
 | |
| // ListGPGKeys returns a list of public keys belongs to given user.
 | |
| func ListGPGKeys(ctx context.Context, uid int64, listOptions db.ListOptions) ([]*GPGKey, error) {
 | |
| 	sess := db.GetEngine(ctx).Table(&GPGKey{}).Where("owner_id=? AND primary_key_id=''", uid)
 | |
| 	if listOptions.Page != 0 {
 | |
| 		sess = db.SetSessionPagination(sess, &listOptions)
 | |
| 	}
 | |
| 
 | |
| 	keys := make([]*GPGKey, 0, 2)
 | |
| 	return keys, sess.Find(&keys)
 | |
| }
 | |
| 
 | |
| // CountUserGPGKeys return number of gpg keys a user own
 | |
| func CountUserGPGKeys(userID int64) (int64, error) {
 | |
| 	return db.GetEngine(db.DefaultContext).Where("owner_id=? AND primary_key_id=''", userID).Count(&GPGKey{})
 | |
| }
 | |
| 
 | |
| // GetGPGKeyByID returns public key by given ID.
 | |
| func GetGPGKeyByID(keyID int64) (*GPGKey, error) {
 | |
| 	key := new(GPGKey)
 | |
| 	has, err := db.GetEngine(db.DefaultContext).ID(keyID).Get(key)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	} else if !has {
 | |
| 		return nil, ErrGPGKeyNotExist{keyID}
 | |
| 	}
 | |
| 	return key, nil
 | |
| }
 | |
| 
 | |
| // GetGPGKeysByKeyID returns public key by given ID.
 | |
| func GetGPGKeysByKeyID(keyID string) ([]*GPGKey, error) {
 | |
| 	keys := make([]*GPGKey, 0, 1)
 | |
| 	return keys, db.GetEngine(db.DefaultContext).Where("key_id=?", keyID).Find(&keys)
 | |
| }
 | |
| 
 | |
| // GPGKeyToEntity retrieve the imported key and the traducted entity
 | |
| func GPGKeyToEntity(k *GPGKey) (*openpgp.Entity, error) {
 | |
| 	impKey, err := GetGPGImportByKeyID(k.KeyID)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	keys, err := checkArmoredGPGKeyString(impKey.Content)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return keys[0], err
 | |
| }
 | |
| 
 | |
| // parseSubGPGKey parse a sub Key
 | |
| func parseSubGPGKey(ownerID int64, primaryID string, pubkey *packet.PublicKey, expiry time.Time) (*GPGKey, error) {
 | |
| 	content, err := base64EncPubKey(pubkey)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return &GPGKey{
 | |
| 		OwnerID:           ownerID,
 | |
| 		KeyID:             pubkey.KeyIdString(),
 | |
| 		PrimaryKeyID:      primaryID,
 | |
| 		Content:           content,
 | |
| 		CreatedUnix:       timeutil.TimeStamp(pubkey.CreationTime.Unix()),
 | |
| 		ExpiredUnix:       timeutil.TimeStamp(expiry.Unix()),
 | |
| 		CanSign:           pubkey.CanSign(),
 | |
| 		CanEncryptComms:   pubkey.PubKeyAlgo.CanEncrypt(),
 | |
| 		CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(),
 | |
| 		CanCertify:        pubkey.PubKeyAlgo.CanSign(),
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // parseGPGKey parse a PrimaryKey entity (primary key + subs keys + self-signature)
 | |
| func parseGPGKey(ownerID int64, e *openpgp.Entity, verified bool) (*GPGKey, error) {
 | |
| 	pubkey := e.PrimaryKey
 | |
| 	expiry := getExpiryTime(e)
 | |
| 
 | |
| 	// Parse Subkeys
 | |
| 	subkeys := make([]*GPGKey, len(e.Subkeys))
 | |
| 	for i, k := range e.Subkeys {
 | |
| 		subs, err := parseSubGPGKey(ownerID, pubkey.KeyIdString(), k.PublicKey, expiry)
 | |
| 		if err != nil {
 | |
| 			return nil, ErrGPGKeyParsing{ParseError: err}
 | |
| 		}
 | |
| 		subkeys[i] = subs
 | |
| 	}
 | |
| 
 | |
| 	// Check emails
 | |
| 	userEmails, err := user_model.GetEmailAddresses(ownerID)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	emails := make([]*user_model.EmailAddress, 0, len(e.Identities))
 | |
| 	for _, ident := range e.Identities {
 | |
| 		if ident.Revocation != nil {
 | |
| 			continue
 | |
| 		}
 | |
| 		email := strings.ToLower(strings.TrimSpace(ident.UserId.Email))
 | |
| 		for _, e := range userEmails {
 | |
| 			if e.IsActivated && e.LowerEmail == email {
 | |
| 				emails = append(emails, e)
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if !verified {
 | |
| 		// In the case no email as been found
 | |
| 		if len(emails) == 0 {
 | |
| 			failedEmails := make([]string, 0, len(e.Identities))
 | |
| 			for _, ident := range e.Identities {
 | |
| 				failedEmails = append(failedEmails, ident.UserId.Email)
 | |
| 			}
 | |
| 			return nil, ErrGPGNoEmailFound{failedEmails, e.PrimaryKey.KeyIdString()}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	content, err := base64EncPubKey(pubkey)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return &GPGKey{
 | |
| 		OwnerID:           ownerID,
 | |
| 		KeyID:             pubkey.KeyIdString(),
 | |
| 		PrimaryKeyID:      "",
 | |
| 		Content:           content,
 | |
| 		CreatedUnix:       timeutil.TimeStamp(pubkey.CreationTime.Unix()),
 | |
| 		ExpiredUnix:       timeutil.TimeStamp(expiry.Unix()),
 | |
| 		Emails:            emails,
 | |
| 		SubsKey:           subkeys,
 | |
| 		Verified:          verified,
 | |
| 		CanSign:           pubkey.CanSign(),
 | |
| 		CanEncryptComms:   pubkey.PubKeyAlgo.CanEncrypt(),
 | |
| 		CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(),
 | |
| 		CanCertify:        pubkey.PubKeyAlgo.CanSign(),
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // deleteGPGKey does the actual key deletion
 | |
| func deleteGPGKey(ctx context.Context, keyID string) (int64, error) {
 | |
| 	if keyID == "" {
 | |
| 		return 0, fmt.Errorf("empty KeyId forbidden") // Should never happen but just to be sure
 | |
| 	}
 | |
| 	// Delete imported key
 | |
| 	n, err := db.GetEngine(ctx).Where("key_id=?", keyID).Delete(new(GPGKeyImport))
 | |
| 	if err != nil {
 | |
| 		return n, err
 | |
| 	}
 | |
| 	return db.GetEngine(ctx).Where("key_id=?", keyID).Or("primary_key_id=?", keyID).Delete(new(GPGKey))
 | |
| }
 | |
| 
 | |
| // DeleteGPGKey deletes GPG key information in database.
 | |
| func DeleteGPGKey(doer *user_model.User, id int64) (err error) {
 | |
| 	key, err := GetGPGKeyByID(id)
 | |
| 	if err != nil {
 | |
| 		if IsErrGPGKeyNotExist(err) {
 | |
| 			return nil
 | |
| 		}
 | |
| 		return fmt.Errorf("GetPublicKeyByID: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// Check if user has access to delete this key.
 | |
| 	if !doer.IsAdmin && doer.ID != key.OwnerID {
 | |
| 		return ErrGPGKeyAccessDenied{doer.ID, key.ID}
 | |
| 	}
 | |
| 
 | |
| 	ctx, committer, err := db.TxContext(db.DefaultContext)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer committer.Close()
 | |
| 
 | |
| 	if _, err = deleteGPGKey(ctx, key.KeyID); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return committer.Commit()
 | |
| }
 | |
| 
 | |
| func checkKeyEmails(email string, keys ...*GPGKey) (bool, string) {
 | |
| 	uid := int64(0)
 | |
| 	var userEmails []*user_model.EmailAddress
 | |
| 	var user *user_model.User
 | |
| 	for _, key := range keys {
 | |
| 		for _, e := range key.Emails {
 | |
| 			if e.IsActivated && (email == "" || strings.EqualFold(e.Email, email)) {
 | |
| 				return true, e.Email
 | |
| 			}
 | |
| 		}
 | |
| 		if key.Verified && key.OwnerID != 0 {
 | |
| 			if uid != key.OwnerID {
 | |
| 				userEmails, _ = user_model.GetEmailAddresses(key.OwnerID)
 | |
| 				uid = key.OwnerID
 | |
| 				user = &user_model.User{ID: uid}
 | |
| 				_, _ = user_model.GetUser(user)
 | |
| 			}
 | |
| 			for _, e := range userEmails {
 | |
| 				if e.IsActivated && (email == "" || strings.EqualFold(e.Email, email)) {
 | |
| 					return true, e.Email
 | |
| 				}
 | |
| 			}
 | |
| 			if user.KeepEmailPrivate && strings.EqualFold(email, user.GetEmail()) {
 | |
| 				return true, user.GetEmail()
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return false, email
 | |
| }
 |