mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	enable mirror, usestdlibbars and perfsprint part of: https://github.com/go-gitea/gitea/issues/34083 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
		
			
				
	
	
		
			204 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			204 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
package asymkey
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"hash"
 | 
						|
 | 
						|
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						|
	user_model "code.gitea.io/gitea/models/user"
 | 
						|
	"code.gitea.io/gitea/modules/log"
 | 
						|
 | 
						|
	"github.com/ProtonMail/go-crypto/openpgp/packet"
 | 
						|
)
 | 
						|
 | 
						|
//   __________________  ________   ____  __.
 | 
						|
//  /  _____/\______   \/  _____/  |    |/ _|____ ___.__.
 | 
						|
// /   \  ___ |     ___/   \  ___  |      <_/ __ <   |  |
 | 
						|
// \    \_\  \|    |   \    \_\  \ |    |  \  ___/\___  |
 | 
						|
//  \______  /|____|    \______  / |____|__ \___  > ____|
 | 
						|
//         \/                  \/          \/   \/\/
 | 
						|
// _________                        .__  __
 | 
						|
// \_   ___ \  ____   _____   _____ |__|/  |_
 | 
						|
// /    \  \/ /  _ \ /     \ /     \|  \   __\
 | 
						|
// \     \___(  <_> )  Y Y  \  Y Y  \  ||  |
 | 
						|
//  \______  /\____/|__|_|  /__|_|  /__||__|
 | 
						|
//         \/             \/      \/
 | 
						|
// ____   ____           .__  _____.__               __  .__
 | 
						|
// \   \ /   /___________|__|/ ____\__| ____ _____ _/  |_|__| ____   ____
 | 
						|
//  \   Y   // __ \_  __ \  \   __\|  |/ ___\\__  \\   __\  |/  _ \ /    \
 | 
						|
//   \     /\  ___/|  | \/  ||  |  |  \  \___ / __ \|  | |  (  <_> )   |  \
 | 
						|
//    \___/  \___  >__|  |__||__|  |__|\___  >____  /__| |__|\____/|___|  /
 | 
						|
//               \/                        \/     \/                    \/
 | 
						|
 | 
						|
// This file provides functions relating commit verification
 | 
						|
 | 
						|
// CommitVerification represents a commit validation of signature
 | 
						|
type CommitVerification struct {
 | 
						|
	Verified       bool
 | 
						|
	Warning        bool
 | 
						|
	Reason         string
 | 
						|
	SigningUser    *user_model.User
 | 
						|
	CommittingUser *user_model.User
 | 
						|
	SigningEmail   string
 | 
						|
	SigningKey     *GPGKey
 | 
						|
	SigningSSHKey  *PublicKey
 | 
						|
	TrustStatus    string
 | 
						|
}
 | 
						|
 | 
						|
// SignCommit represents a commit with validation of signature.
 | 
						|
type SignCommit struct {
 | 
						|
	Verification *CommitVerification
 | 
						|
	*user_model.UserCommit
 | 
						|
}
 | 
						|
 | 
						|
const (
 | 
						|
	// BadSignature is used as the reason when the signature has a KeyID that is in the db
 | 
						|
	// but no key that has that ID verifies the signature. This is a suspicious failure.
 | 
						|
	BadSignature = "gpg.error.probable_bad_signature"
 | 
						|
	// BadDefaultSignature is used as the reason when the signature has a KeyID that matches the
 | 
						|
	// default Key but is not verified by the default key. This is a suspicious failure.
 | 
						|
	BadDefaultSignature = "gpg.error.probable_bad_default_signature"
 | 
						|
	// NoKeyFound is used as the reason when no key can be found to verify the signature.
 | 
						|
	NoKeyFound = "gpg.error.no_gpg_keys_found"
 | 
						|
)
 | 
						|
 | 
						|
func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error {
 | 
						|
	// Check if key can sign
 | 
						|
	if !k.CanSign {
 | 
						|
		return errors.New("key can not sign")
 | 
						|
	}
 | 
						|
	// Decode key
 | 
						|
	pkey, err := base64DecPubKey(k.Content)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return pkey.VerifySignature(h, s)
 | 
						|
}
 | 
						|
 | 
						|
func hashAndVerify(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
 | 
						|
	// Generating hash of commit
 | 
						|
	hash, err := populateHash(sig.Hash, []byte(payload))
 | 
						|
	if err != nil { // Skipping as failed to generate hash
 | 
						|
		log.Error("PopulateHash: %v", err)
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	// We will ignore errors in verification as they don't need to be propagated up
 | 
						|
	err = verifySign(sig, hash, k)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
	return k, nil
 | 
						|
}
 | 
						|
 | 
						|
func hashAndVerifyWithSubKeys(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
 | 
						|
	verified, err := hashAndVerify(sig, payload, k)
 | 
						|
	if err != nil || verified != nil {
 | 
						|
		return verified, err
 | 
						|
	}
 | 
						|
	for _, sk := range k.SubsKey {
 | 
						|
		verified, err := hashAndVerify(sig, payload, sk)
 | 
						|
		if err != nil || verified != nil {
 | 
						|
			return verified, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func HashAndVerifyWithSubKeysCommitVerification(sig *packet.Signature, payload string, k *GPGKey, committer, signer *user_model.User, email string) *CommitVerification {
 | 
						|
	key, err := hashAndVerifyWithSubKeys(sig, payload, k)
 | 
						|
	if err != nil { // Skipping failed to generate hash
 | 
						|
		return &CommitVerification{
 | 
						|
			CommittingUser: committer,
 | 
						|
			Verified:       false,
 | 
						|
			Reason:         "gpg.error.generate_hash",
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if key != nil {
 | 
						|
		return &CommitVerification{ // Everything is ok
 | 
						|
			CommittingUser: committer,
 | 
						|
			Verified:       true,
 | 
						|
			Reason:         fmt.Sprintf("%s / %s", signer.Name, key.KeyID),
 | 
						|
			SigningUser:    signer,
 | 
						|
			SigningKey:     key,
 | 
						|
			SigningEmail:   email,
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository
 | 
						|
// There are several trust models in Gitea
 | 
						|
func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error), keyMap *map[string]bool) error {
 | 
						|
	if !verification.Verified {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	// In the Committer trust model a signature is trusted if it matches the committer
 | 
						|
	// - it doesn't matter if they're a collaborator, the owner, Gitea or Github
 | 
						|
	// NB: This model is commit verification only
 | 
						|
	if repoTrustModel == repo_model.CommitterTrustModel {
 | 
						|
		// default to "unmatched"
 | 
						|
		verification.TrustStatus = "unmatched"
 | 
						|
 | 
						|
		// We can only verify against users in our database but the default key will match
 | 
						|
		// against by email if it is not in the db.
 | 
						|
		if (verification.SigningUser.ID != 0 &&
 | 
						|
			verification.CommittingUser.ID == verification.SigningUser.ID) ||
 | 
						|
			(verification.SigningUser.ID == 0 && verification.CommittingUser.ID == 0 &&
 | 
						|
				verification.SigningUser.Email == verification.CommittingUser.Email) {
 | 
						|
			verification.TrustStatus = "trusted"
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Now we drop to the more nuanced trust models...
 | 
						|
	verification.TrustStatus = "trusted"
 | 
						|
 | 
						|
	if verification.SigningUser.ID == 0 {
 | 
						|
		// This commit is signed by the default key - but this key is not assigned to a user in the DB.
 | 
						|
 | 
						|
		// However in the repo_model.CollaboratorCommitterTrustModel we cannot mark this as trusted
 | 
						|
		// unless the default key matches the email of a non-user.
 | 
						|
		if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && (verification.CommittingUser.ID != 0 ||
 | 
						|
			verification.SigningUser.Email != verification.CommittingUser.Email) {
 | 
						|
			verification.TrustStatus = "untrusted"
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Check we actually have a GPG SigningKey
 | 
						|
	var err error
 | 
						|
	if verification.SigningKey != nil {
 | 
						|
		var isMember bool
 | 
						|
		if keyMap != nil {
 | 
						|
			var has bool
 | 
						|
			isMember, has = (*keyMap)[verification.SigningKey.KeyID]
 | 
						|
			if !has {
 | 
						|
				isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
 | 
						|
				(*keyMap)[verification.SigningKey.KeyID] = isMember
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
 | 
						|
		}
 | 
						|
 | 
						|
		if !isMember {
 | 
						|
			verification.TrustStatus = "untrusted"
 | 
						|
			if verification.CommittingUser.ID != verification.SigningUser.ID {
 | 
						|
				// The committing user and the signing user are not the same
 | 
						|
				// This should be marked as questionable unless the signing user is a collaborator/team member etc.
 | 
						|
				verification.TrustStatus = "unmatched"
 | 
						|
			}
 | 
						|
		} else if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && verification.CommittingUser.ID != verification.SigningUser.ID {
 | 
						|
			// The committing user and the signing user are not the same and our trustmodel states that they must match
 | 
						|
			verification.TrustStatus = "unmatched"
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return err
 | 
						|
}
 |