code.gitea.io/gitea@v1.22.3/models/asymkey/gpg_key.go (about)

     1  // Copyright 2017 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package asymkey
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"strings"
    10  	"time"
    11  
    12  	"code.gitea.io/gitea/models/db"
    13  	user_model "code.gitea.io/gitea/models/user"
    14  	"code.gitea.io/gitea/modules/timeutil"
    15  
    16  	"github.com/keybase/go-crypto/openpgp"
    17  	"github.com/keybase/go-crypto/openpgp/packet"
    18  	"xorm.io/builder"
    19  )
    20  
    21  // GPGKey represents a GPG key.
    22  type GPGKey struct {
    23  	ID                int64              `xorm:"pk autoincr"`
    24  	OwnerID           int64              `xorm:"INDEX NOT NULL"`
    25  	KeyID             string             `xorm:"INDEX CHAR(16) NOT NULL"`
    26  	PrimaryKeyID      string             `xorm:"CHAR(16)"`
    27  	Content           string             `xorm:"MEDIUMTEXT NOT NULL"`
    28  	CreatedUnix       timeutil.TimeStamp `xorm:"created"`
    29  	ExpiredUnix       timeutil.TimeStamp
    30  	AddedUnix         timeutil.TimeStamp
    31  	SubsKey           []*GPGKey `xorm:"-"`
    32  	Emails            []*user_model.EmailAddress
    33  	Verified          bool `xorm:"NOT NULL DEFAULT false"`
    34  	CanSign           bool
    35  	CanEncryptComms   bool
    36  	CanEncryptStorage bool
    37  	CanCertify        bool
    38  }
    39  
    40  func init() {
    41  	db.RegisterModel(new(GPGKey))
    42  }
    43  
    44  // BeforeInsert will be invoked by XORM before inserting a record
    45  func (key *GPGKey) BeforeInsert() {
    46  	key.AddedUnix = timeutil.TimeStampNow()
    47  }
    48  
    49  func (key *GPGKey) LoadSubKeys(ctx context.Context) error {
    50  	if err := db.GetEngine(ctx).Where("primary_key_id=?", key.KeyID).Find(&key.SubsKey); err != nil {
    51  		return fmt.Errorf("find Sub GPGkeys[%s]: %v", key.KeyID, err)
    52  	}
    53  	return nil
    54  }
    55  
    56  // PaddedKeyID show KeyID padded to 16 characters
    57  func (key *GPGKey) PaddedKeyID() string {
    58  	return PaddedKeyID(key.KeyID)
    59  }
    60  
    61  // PaddedKeyID show KeyID padded to 16 characters
    62  func PaddedKeyID(keyID string) string {
    63  	if len(keyID) > 15 {
    64  		return keyID
    65  	}
    66  	zeros := "0000000000000000"
    67  	return zeros[0:16-len(keyID)] + keyID
    68  }
    69  
    70  type FindGPGKeyOptions struct {
    71  	db.ListOptions
    72  	OwnerID        int64
    73  	KeyID          string
    74  	IncludeSubKeys bool
    75  }
    76  
    77  func (opts FindGPGKeyOptions) ToConds() builder.Cond {
    78  	cond := builder.NewCond()
    79  	if !opts.IncludeSubKeys {
    80  		cond = cond.And(builder.Eq{"primary_key_id": ""})
    81  	}
    82  
    83  	if opts.OwnerID > 0 {
    84  		cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
    85  	}
    86  	if opts.KeyID != "" {
    87  		cond = cond.And(builder.Eq{"key_id": opts.KeyID})
    88  	}
    89  	return cond
    90  }
    91  
    92  func GetGPGKeyForUserByID(ctx context.Context, ownerID, keyID int64) (*GPGKey, error) {
    93  	key := new(GPGKey)
    94  	has, err := db.GetEngine(ctx).Where("id=? AND owner_id=?", keyID, ownerID).Get(key)
    95  	if err != nil {
    96  		return nil, err
    97  	} else if !has {
    98  		return nil, ErrGPGKeyNotExist{keyID}
    99  	}
   100  	return key, nil
   101  }
   102  
   103  // GPGKeyToEntity retrieve the imported key and the traducted entity
   104  func GPGKeyToEntity(ctx context.Context, k *GPGKey) (*openpgp.Entity, error) {
   105  	impKey, err := GetGPGImportByKeyID(ctx, k.KeyID)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	keys, err := checkArmoredGPGKeyString(impKey.Content)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	return keys[0], err
   114  }
   115  
   116  // parseSubGPGKey parse a sub Key
   117  func parseSubGPGKey(ownerID int64, primaryID string, pubkey *packet.PublicKey, expiry time.Time) (*GPGKey, error) {
   118  	content, err := base64EncPubKey(pubkey)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	return &GPGKey{
   123  		OwnerID:           ownerID,
   124  		KeyID:             pubkey.KeyIdString(),
   125  		PrimaryKeyID:      primaryID,
   126  		Content:           content,
   127  		CreatedUnix:       timeutil.TimeStamp(pubkey.CreationTime.Unix()),
   128  		ExpiredUnix:       timeutil.TimeStamp(expiry.Unix()),
   129  		CanSign:           pubkey.CanSign(),
   130  		CanEncryptComms:   pubkey.PubKeyAlgo.CanEncrypt(),
   131  		CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(),
   132  		CanCertify:        pubkey.PubKeyAlgo.CanSign(),
   133  	}, nil
   134  }
   135  
   136  // parseGPGKey parse a PrimaryKey entity (primary key + subs keys + self-signature)
   137  func parseGPGKey(ctx context.Context, ownerID int64, e *openpgp.Entity, verified bool) (*GPGKey, error) {
   138  	pubkey := e.PrimaryKey
   139  	expiry := getExpiryTime(e)
   140  
   141  	// Parse Subkeys
   142  	subkeys := make([]*GPGKey, len(e.Subkeys))
   143  	for i, k := range e.Subkeys {
   144  		subs, err := parseSubGPGKey(ownerID, pubkey.KeyIdString(), k.PublicKey, expiry)
   145  		if err != nil {
   146  			return nil, ErrGPGKeyParsing{ParseError: err}
   147  		}
   148  		subkeys[i] = subs
   149  	}
   150  
   151  	// Check emails
   152  	userEmails, err := user_model.GetEmailAddresses(ctx, ownerID)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  
   157  	emails := make([]*user_model.EmailAddress, 0, len(e.Identities))
   158  	for _, ident := range e.Identities {
   159  		if ident.Revocation != nil {
   160  			continue
   161  		}
   162  		email := strings.ToLower(strings.TrimSpace(ident.UserId.Email))
   163  		for _, e := range userEmails {
   164  			if e.IsActivated && e.LowerEmail == email {
   165  				emails = append(emails, e)
   166  				break
   167  			}
   168  		}
   169  	}
   170  
   171  	if !verified {
   172  		// In the case no email as been found
   173  		if len(emails) == 0 {
   174  			failedEmails := make([]string, 0, len(e.Identities))
   175  			for _, ident := range e.Identities {
   176  				failedEmails = append(failedEmails, ident.UserId.Email)
   177  			}
   178  			return nil, ErrGPGNoEmailFound{failedEmails, e.PrimaryKey.KeyIdString()}
   179  		}
   180  	}
   181  
   182  	content, err := base64EncPubKey(pubkey)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  	return &GPGKey{
   187  		OwnerID:           ownerID,
   188  		KeyID:             pubkey.KeyIdString(),
   189  		PrimaryKeyID:      "",
   190  		Content:           content,
   191  		CreatedUnix:       timeutil.TimeStamp(pubkey.CreationTime.Unix()),
   192  		ExpiredUnix:       timeutil.TimeStamp(expiry.Unix()),
   193  		Emails:            emails,
   194  		SubsKey:           subkeys,
   195  		Verified:          verified,
   196  		CanSign:           pubkey.CanSign(),
   197  		CanEncryptComms:   pubkey.PubKeyAlgo.CanEncrypt(),
   198  		CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(),
   199  		CanCertify:        pubkey.PubKeyAlgo.CanSign(),
   200  	}, nil
   201  }
   202  
   203  // deleteGPGKey does the actual key deletion
   204  func deleteGPGKey(ctx context.Context, keyID string) (int64, error) {
   205  	if keyID == "" {
   206  		return 0, fmt.Errorf("empty KeyId forbidden") // Should never happen but just to be sure
   207  	}
   208  	// Delete imported key
   209  	n, err := db.GetEngine(ctx).Where("key_id=?", keyID).Delete(new(GPGKeyImport))
   210  	if err != nil {
   211  		return n, err
   212  	}
   213  	return db.GetEngine(ctx).Where("key_id=?", keyID).Or("primary_key_id=?", keyID).Delete(new(GPGKey))
   214  }
   215  
   216  // DeleteGPGKey deletes GPG key information in database.
   217  func DeleteGPGKey(ctx context.Context, doer *user_model.User, id int64) (err error) {
   218  	key, err := GetGPGKeyForUserByID(ctx, doer.ID, id)
   219  	if err != nil {
   220  		if IsErrGPGKeyNotExist(err) {
   221  			return nil
   222  		}
   223  		return fmt.Errorf("GetPublicKeyByID: %w", err)
   224  	}
   225  
   226  	ctx, committer, err := db.TxContext(ctx)
   227  	if err != nil {
   228  		return err
   229  	}
   230  	defer committer.Close()
   231  
   232  	if _, err = deleteGPGKey(ctx, key.KeyID); err != nil {
   233  		return err
   234  	}
   235  
   236  	return committer.Commit()
   237  }
   238  
   239  func checkKeyEmails(ctx context.Context, email string, keys ...*GPGKey) (bool, string) {
   240  	uid := int64(0)
   241  	var userEmails []*user_model.EmailAddress
   242  	var user *user_model.User
   243  	for _, key := range keys {
   244  		for _, e := range key.Emails {
   245  			if e.IsActivated && (email == "" || strings.EqualFold(e.Email, email)) {
   246  				return true, e.Email
   247  			}
   248  		}
   249  		if key.Verified && key.OwnerID != 0 {
   250  			if uid != key.OwnerID {
   251  				userEmails, _ = user_model.GetEmailAddresses(ctx, key.OwnerID)
   252  				uid = key.OwnerID
   253  				user = &user_model.User{ID: uid}
   254  				_, _ = user_model.GetUser(ctx, user)
   255  			}
   256  			for _, e := range userEmails {
   257  				if e.IsActivated && (email == "" || strings.EqualFold(e.Email, email)) {
   258  					return true, e.Email
   259  				}
   260  			}
   261  			if user.KeepEmailPrivate && strings.EqualFold(email, user.GetEmail()) {
   262  				return true, user.GetEmail()
   263  			}
   264  		}
   265  	}
   266  	return false, email
   267  }