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