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:
		
							parent
							
								
									ae23bbdae3
								
							
						
					
					
						commit
						7c2cf236f8
					
				
					 4 changed files with 86 additions and 68 deletions
				
			
		|  | @ -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) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue