| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- // 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 // FIXME: need to refactor it to a new name like "SigningGPGKey", it is also used in some templates
- 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
- }
|