diff options
author | Daniel Baumann <daniel@debian.org> | 2024-10-18 20:33:49 +0200 |
---|---|---|
committer | Daniel Baumann <daniel@debian.org> | 2024-12-12 23:57:56 +0100 |
commit | e68b9d00a6e05b3a941f63ffb696f91e554ac5ec (patch) | |
tree | 97775d6c13b0f416af55314eb6a89ef792474615 /models/asymkey/gpg_key.go | |
parent | Initial commit. (diff) | |
download | forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.tar.xz forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.zip |
Adding upstream version 9.0.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to 'models/asymkey/gpg_key.go')
-rw-r--r-- | models/asymkey/gpg_key.go | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/models/asymkey/gpg_key.go b/models/asymkey/gpg_key.go new file mode 100644 index 0000000..6e2914e --- /dev/null +++ b/models/asymkey/gpg_key.go @@ -0,0 +1,273 @@ +// 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/timeutil" + + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/ProtonMail/go-crypto/openpgp/packet" + "xorm.io/builder" +) + +// 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() +} + +func (key *GPGKey) LoadSubKeys(ctx context.Context) error { + if err := db.GetEngine(ctx).Where("primary_key_id=?", key.KeyID).Find(&key.SubsKey); err != nil { + return fmt.Errorf("find Sub GPGkeys[%s]: %v", key.KeyID, err) + } + return nil +} + +// 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 +} + +type FindGPGKeyOptions struct { + db.ListOptions + OwnerID int64 + KeyID string + IncludeSubKeys bool +} + +func (opts FindGPGKeyOptions) ToConds() builder.Cond { + cond := builder.NewCond() + if !opts.IncludeSubKeys { + cond = cond.And(builder.Eq{"primary_key_id": ""}) + } + + if opts.OwnerID > 0 { + cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) + } + if opts.KeyID != "" { + cond = cond.And(builder.Eq{"key_id": opts.KeyID}) + } + return cond +} + +func GetGPGKeyForUserByID(ctx context.Context, ownerID, keyID int64) (*GPGKey, error) { + key := new(GPGKey) + has, err := db.GetEngine(ctx).Where("id=? AND owner_id=?", keyID, ownerID).Get(key) + if err != nil { + return nil, err + } else if !has { + return nil, ErrGPGKeyNotExist{keyID} + } + return key, nil +} + +// GPGKeyToEntity retrieve the imported key and the traducted entity +func GPGKeyToEntity(ctx context.Context, k *GPGKey) (*openpgp.Entity, error) { + impKey, err := GetGPGImportByKeyID(ctx, 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(ctx context.Context, 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 { + subKeyExpiry := expiry + if k.Sig.KeyLifetimeSecs != nil { + subKeyExpiry = k.PublicKey.CreationTime.Add(time.Duration(*k.Sig.KeyLifetimeSecs) * time.Second) + } + + subs, err := parseSubGPGKey(ownerID, pubkey.KeyIdString(), k.PublicKey, subKeyExpiry) + if err != nil { + return nil, ErrGPGKeyParsing{ParseError: err} + } + subkeys[i] = subs + } + + // Check emails + userEmails, err := user_model.GetEmailAddresses(ctx, ownerID) + if err != nil { + return nil, err + } + + emails := make([]*user_model.EmailAddress, 0, len(e.Identities)) + for _, ident := range e.Identities { + // Check if the identity is revoked. + if ident.Revoked(time.Now()) { + 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(ctx context.Context, doer *user_model.User, id int64) (err error) { + key, err := GetGPGKeyForUserByID(ctx, doer.ID, id) + if err != nil { + if IsErrGPGKeyNotExist(err) { + return nil + } + return fmt.Errorf("GetPublicKeyByID: %w", err) + } + + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + + if _, err = deleteGPGKey(ctx, key.KeyID); err != nil { + return err + } + + return committer.Commit() +} + +func checkKeyEmails(ctx context.Context, 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(ctx, key.OwnerID) + uid = key.OwnerID + user = &user_model.User{ID: uid} + _, _ = user_model.GetUser(ctx, 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 +} |