mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Allow addition of gpg keyring with multiple keys (#12487)
Related #6778 Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
		@@ -106,12 +106,12 @@ func GetGPGImportByKeyID(keyID string) (*GPGKeyImport, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// checkArmoredGPGKeyString checks if the given key string is a valid GPG armored key.
 | 
					// checkArmoredGPGKeyString checks if the given key string is a valid GPG armored key.
 | 
				
			||||||
// The function returns the actual public key on success
 | 
					// The function returns the actual public key on success
 | 
				
			||||||
func checkArmoredGPGKeyString(content string) (*openpgp.Entity, error) {
 | 
					func checkArmoredGPGKeyString(content string) (openpgp.EntityList, error) {
 | 
				
			||||||
	list, err := openpgp.ReadArmoredKeyRing(strings.NewReader(content))
 | 
						list, err := openpgp.ReadArmoredKeyRing(strings.NewReader(content))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, ErrGPGKeyParsing{err}
 | 
							return nil, ErrGPGKeyParsing{err}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return list[0], nil
 | 
						return list, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//addGPGKey add key, import and subkeys to database
 | 
					//addGPGKey add key, import and subkeys to database
 | 
				
			||||||
@@ -152,38 +152,40 @@ func addGPGSubKey(e Engine, key *GPGKey) (err error) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AddGPGKey adds new public key to database.
 | 
					// AddGPGKey adds new public key to database.
 | 
				
			||||||
func AddGPGKey(ownerID int64, content string) (*GPGKey, error) {
 | 
					func AddGPGKey(ownerID int64, content string) ([]*GPGKey, error) {
 | 
				
			||||||
	ekey, err := checkArmoredGPGKeyString(content)
 | 
						ekeys, err := checkArmoredGPGKeyString(content)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Key ID cannot be duplicated.
 | 
					 | 
				
			||||||
	has, err := x.Where("key_id=?", ekey.PrimaryKey.KeyIdString()).
 | 
					 | 
				
			||||||
		Get(new(GPGKey))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	} else if has {
 | 
					 | 
				
			||||||
		return nil, ErrGPGKeyIDAlreadyUsed{ekey.PrimaryKey.KeyIdString()}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	//Get DB session
 | 
					 | 
				
			||||||
	sess := x.NewSession()
 | 
						sess := x.NewSession()
 | 
				
			||||||
	defer sess.Close()
 | 
						defer sess.Close()
 | 
				
			||||||
	if err = sess.Begin(); err != nil {
 | 
						if err = sess.Begin(); err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						keys := make([]*GPGKey, 0, len(ekeys))
 | 
				
			||||||
 | 
						for _, ekey := range ekeys {
 | 
				
			||||||
 | 
							// Key ID cannot be duplicated.
 | 
				
			||||||
 | 
							has, err := sess.Where("key_id=?", ekey.PrimaryKey.KeyIdString()).
 | 
				
			||||||
 | 
								Get(new(GPGKey))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							} else if has {
 | 
				
			||||||
 | 
								return nil, ErrGPGKeyIDAlreadyUsed{ekey.PrimaryKey.KeyIdString()}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	key, err := parseGPGKey(ownerID, ekey)
 | 
							//Get DB session
 | 
				
			||||||
	if err != nil {
 | 
					
 | 
				
			||||||
		return nil, err
 | 
							key, err := parseGPGKey(ownerID, ekey)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err = addGPGKey(sess, key, content); err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							keys = append(keys, key)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return keys, sess.Commit()
 | 
				
			||||||
	if err = addGPGKey(sess, key, content); err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return key, sess.Commit()
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//base64EncPubKey encode public key content to base 64
 | 
					//base64EncPubKey encode public key content to base 64
 | 
				
			||||||
@@ -221,7 +223,11 @@ func GPGKeyToEntity(k *GPGKey) (*openpgp.Entity, error) {
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return checkArmoredGPGKeyString(impKey.Content)
 | 
						keys, err := checkArmoredGPGKeyString(impKey.Content)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return keys[0], err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//parseSubGPGKey parse a sub Key
 | 
					//parseSubGPGKey parse a sub Key
 | 
				
			||||||
@@ -761,7 +767,7 @@ func verifyWithGPGSettings(gpgSettings *git.GPGSettings, sig *packet.Signature,
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Otherwise we have to parse the key
 | 
						// Otherwise we have to parse the key
 | 
				
			||||||
	ekey, err := checkArmoredGPGKeyString(gpgSettings.PublicKeyContent)
 | 
						ekeys, err := checkArmoredGPGKeyString(gpgSettings.PublicKeyContent)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error("Unable to get default signing key: %v", err)
 | 
							log.Error("Unable to get default signing key: %v", err)
 | 
				
			||||||
		return &CommitVerification{
 | 
							return &CommitVerification{
 | 
				
			||||||
@@ -770,22 +776,9 @@ func verifyWithGPGSettings(gpgSettings *git.GPGSettings, sig *packet.Signature,
 | 
				
			|||||||
			Reason:         "gpg.error.generate_hash",
 | 
								Reason:         "gpg.error.generate_hash",
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	pubkey := ekey.PrimaryKey
 | 
						for _, ekey := range ekeys {
 | 
				
			||||||
	content, err := base64EncPubKey(pubkey)
 | 
							pubkey := ekey.PrimaryKey
 | 
				
			||||||
	if err != nil {
 | 
							content, err := base64EncPubKey(pubkey)
 | 
				
			||||||
		return &CommitVerification{
 | 
					 | 
				
			||||||
			CommittingUser: committer,
 | 
					 | 
				
			||||||
			Verified:       false,
 | 
					 | 
				
			||||||
			Reason:         "gpg.error.generate_hash",
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	k := &GPGKey{
 | 
					 | 
				
			||||||
		Content: content,
 | 
					 | 
				
			||||||
		CanSign: pubkey.CanSign(),
 | 
					 | 
				
			||||||
		KeyID:   pubkey.KeyIdString(),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	for _, subKey := range ekey.Subkeys {
 | 
					 | 
				
			||||||
		content, err := base64EncPubKey(subKey.PublicKey)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return &CommitVerification{
 | 
								return &CommitVerification{
 | 
				
			||||||
				CommittingUser: committer,
 | 
									CommittingUser: committer,
 | 
				
			||||||
@@ -793,25 +786,40 @@ func verifyWithGPGSettings(gpgSettings *git.GPGSettings, sig *packet.Signature,
 | 
				
			|||||||
				Reason:         "gpg.error.generate_hash",
 | 
									Reason:         "gpg.error.generate_hash",
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		k.SubsKey = append(k.SubsKey, &GPGKey{
 | 
							k := &GPGKey{
 | 
				
			||||||
			Content: content,
 | 
								Content: content,
 | 
				
			||||||
			CanSign: subKey.PublicKey.CanSign(),
 | 
								CanSign: pubkey.CanSign(),
 | 
				
			||||||
			KeyID:   subKey.PublicKey.KeyIdString(),
 | 
								KeyID:   pubkey.KeyIdString(),
 | 
				
			||||||
		})
 | 
							}
 | 
				
			||||||
	}
 | 
							for _, subKey := range ekey.Subkeys {
 | 
				
			||||||
	if commitVerification := hashAndVerifyWithSubKeys(sig, payload, k, committer, &User{
 | 
								content, err := base64EncPubKey(subKey.PublicKey)
 | 
				
			||||||
		Name:  gpgSettings.Name,
 | 
								if err != nil {
 | 
				
			||||||
		Email: gpgSettings.Email,
 | 
									return &CommitVerification{
 | 
				
			||||||
	}, gpgSettings.Email); commitVerification != nil {
 | 
										CommittingUser: committer,
 | 
				
			||||||
		return commitVerification
 | 
										Verified:       false,
 | 
				
			||||||
	}
 | 
										Reason:         "gpg.error.generate_hash",
 | 
				
			||||||
	if keyID == k.KeyID {
 | 
									}
 | 
				
			||||||
		// This is a bad situation ... We have a key id that matches our default key but the signature doesn't match.
 | 
								}
 | 
				
			||||||
		return &CommitVerification{
 | 
								k.SubsKey = append(k.SubsKey, &GPGKey{
 | 
				
			||||||
			CommittingUser: committer,
 | 
									Content: content,
 | 
				
			||||||
			Verified:       false,
 | 
									CanSign: subKey.PublicKey.CanSign(),
 | 
				
			||||||
			Warning:        true,
 | 
									KeyID:   subKey.PublicKey.KeyIdString(),
 | 
				
			||||||
			Reason:         BadSignature,
 | 
								})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if commitVerification := hashAndVerifyWithSubKeys(sig, payload, k, committer, &User{
 | 
				
			||||||
 | 
								Name:  gpgSettings.Name,
 | 
				
			||||||
 | 
								Email: gpgSettings.Email,
 | 
				
			||||||
 | 
							}, gpgSettings.Email); commitVerification != nil {
 | 
				
			||||||
 | 
								return commitVerification
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if keyID == k.KeyID {
 | 
				
			||||||
 | 
								// This is a bad situation ... We have a key id that matches our default key but the signature doesn't match.
 | 
				
			||||||
 | 
								return &CommitVerification{
 | 
				
			||||||
 | 
									CommittingUser: committer,
 | 
				
			||||||
 | 
									Verified:       false,
 | 
				
			||||||
 | 
									Warning:        true,
 | 
				
			||||||
 | 
									Reason:         BadSignature,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -102,7 +102,8 @@ Av844q/BfRuVsJsK1NDNG09LC30B0l3LKBqlrRmRTUMHtgchdX2dY+p7GPOoSzlR
 | 
				
			|||||||
MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
 | 
					MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
 | 
				
			||||||
=i9b7
 | 
					=i9b7
 | 
				
			||||||
-----END PGP PUBLIC KEY BLOCK-----`
 | 
					-----END PGP PUBLIC KEY BLOCK-----`
 | 
				
			||||||
	ekey, err := checkArmoredGPGKeyString(testGPGArmor)
 | 
						keys, err := checkArmoredGPGKeyString(testGPGArmor)
 | 
				
			||||||
 | 
						ekey := keys[0]
 | 
				
			||||||
	assert.NoError(t, err, "Could not parse a valid GPG armored key", ekey)
 | 
						assert.NoError(t, err, "Could not parse a valid GPG armored key", ekey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pubkey := ekey.PrimaryKey
 | 
						pubkey := ekey.PrimaryKey
 | 
				
			||||||
@@ -219,9 +220,9 @@ Q0KHb+QcycSgbDx0ZAvdIacuKvBBcbxrsmFUI4LR+oIup0G9gUc0roPvr014jYQL
 | 
				
			|||||||
=zHo9
 | 
					=zHo9
 | 
				
			||||||
-----END PGP PUBLIC KEY BLOCK-----`
 | 
					-----END PGP PUBLIC KEY BLOCK-----`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	key, err := AddGPGKey(1, testEmailWithUpperCaseLetters)
 | 
						keys, err := AddGPGKey(1, testEmailWithUpperCaseLetters)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						key := keys[0]
 | 
				
			||||||
	if assert.Len(t, key.Emails, 1) {
 | 
						if assert.Len(t, key.Emails, 1) {
 | 
				
			||||||
		assert.Equal(t, "user1@example.com", key.Emails[0].Email)
 | 
							assert.Equal(t, "user1@example.com", key.Emails[0].Email)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -371,8 +372,9 @@ epiDVQ==
 | 
				
			|||||||
=VSKJ
 | 
					=VSKJ
 | 
				
			||||||
-----END PGP PUBLIC KEY BLOCK-----
 | 
					-----END PGP PUBLIC KEY BLOCK-----
 | 
				
			||||||
`
 | 
					`
 | 
				
			||||||
	ekey, err := checkArmoredGPGKeyString(testIssue6599)
 | 
						keys, err := checkArmoredGPGKeyString(testIssue6599)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						ekey := keys[0]
 | 
				
			||||||
	expire := getExpiryTime(ekey)
 | 
						expire := getExpiryTime(ekey)
 | 
				
			||||||
	assert.Equal(t, time.Unix(1586105389, 0), expire)
 | 
						assert.Equal(t, time.Unix(1586105389, 0), expire)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -118,12 +118,12 @@ func GetGPGKey(ctx *context.APIContext) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// CreateUserGPGKey creates new GPG key to given user by ID.
 | 
					// CreateUserGPGKey creates new GPG key to given user by ID.
 | 
				
			||||||
func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) {
 | 
					func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) {
 | 
				
			||||||
	key, err := models.AddGPGKey(uid, form.ArmoredKey)
 | 
						keys, err := models.AddGPGKey(uid, form.ArmoredKey)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		HandleAddGPGKeyError(ctx, err)
 | 
							HandleAddGPGKeyError(ctx, err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ctx.JSON(http.StatusCreated, convert.ToGPGKey(key))
 | 
						ctx.JSON(http.StatusCreated, convert.ToGPGKey(keys[0]))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// swagger:parameters userCurrentPostGPGKey
 | 
					// swagger:parameters userCurrentPostGPGKey
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,7 +41,7 @@ func KeysPost(ctx *context.Context, form auth.AddKeyForm) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	switch form.Type {
 | 
						switch form.Type {
 | 
				
			||||||
	case "gpg":
 | 
						case "gpg":
 | 
				
			||||||
		key, err := models.AddGPGKey(ctx.User.ID, form.Content)
 | 
							keys, err := models.AddGPGKey(ctx.User.ID, form.Content)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			ctx.Data["HasGPGError"] = true
 | 
								ctx.Data["HasGPGError"] = true
 | 
				
			||||||
			switch {
 | 
								switch {
 | 
				
			||||||
@@ -63,7 +63,15 @@ func KeysPost(ctx *context.Context, form auth.AddKeyForm) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		ctx.Flash.Success(ctx.Tr("settings.add_gpg_key_success", key.KeyID))
 | 
							keyIDs := ""
 | 
				
			||||||
 | 
							for _, key := range keys {
 | 
				
			||||||
 | 
								keyIDs += key.KeyID
 | 
				
			||||||
 | 
								keyIDs += ", "
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(keyIDs) > 0 {
 | 
				
			||||||
 | 
								keyIDs = keyIDs[:len(keyIDs)-2]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ctx.Flash.Success(ctx.Tr("settings.add_gpg_key_success", keyIDs))
 | 
				
			||||||
		ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
 | 
							ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
 | 
				
			||||||
	case "ssh":
 | 
						case "ssh":
 | 
				
			||||||
		content, err := models.CheckPublicKeyString(form.Content)
 | 
							content, err := models.CheckPublicKeyString(form.Content)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user