mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-27 00:23:41 +09:00 
			
		
		
		
	
		
			
				
	
	
		
			185 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			185 lines
		
	
	
		
			6.2 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"
 | |
| )
 | |
| 
 | |
| // 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 // if Verified, then SigningUser is non-nil
 | |
| 	CommittingUser *user_model.User // if Verified, then CommittingUser is non-nil
 | |
| 	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
 | |
| }
 |