diff options
Diffstat (limited to 'models/repo_sign.go')
-rw-r--r-- | models/repo_sign.go | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/models/repo_sign.go b/models/repo_sign.go new file mode 100644 index 0000000000..bac69f76a8 --- /dev/null +++ b/models/repo_sign.go @@ -0,0 +1,303 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package models + +import ( + "strings" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/process" + "code.gitea.io/gitea/modules/setting" +) + +type signingMode string + +const ( + never signingMode = "never" + always signingMode = "always" + pubkey signingMode = "pubkey" + twofa signingMode = "twofa" + parentSigned signingMode = "parentsigned" + baseSigned signingMode = "basesigned" + headSigned signingMode = "headsigned" + commitsSigned signingMode = "commitssigned" +) + +func signingModeFromStrings(modeStrings []string) []signingMode { + returnable := make([]signingMode, 0, len(modeStrings)) + for _, mode := range modeStrings { + signMode := signingMode(strings.ToLower(mode)) + switch signMode { + case never: + return []signingMode{never} + case always: + return []signingMode{always} + case pubkey: + fallthrough + case twofa: + fallthrough + case parentSigned: + fallthrough + case baseSigned: + fallthrough + case headSigned: + fallthrough + case commitsSigned: + returnable = append(returnable, signMode) + } + } + if len(returnable) == 0 { + return []signingMode{never} + } + return returnable +} + +func signingKey(repoPath string) string { + if setting.Repository.Signing.SigningKey == "none" { + return "" + } + + if setting.Repository.Signing.SigningKey == "default" || setting.Repository.Signing.SigningKey == "" { + // Can ignore the error here as it means that commit.gpgsign is not set + value, _ := git.NewCommand("config", "--get", "commit.gpgsign").RunInDir(repoPath) + sign, valid := git.ParseBool(strings.TrimSpace(value)) + if !sign || !valid { + return "" + } + + signingKey, _ := git.NewCommand("config", "--get", "user.signingkey").RunInDir(repoPath) + return strings.TrimSpace(signingKey) + } + + return setting.Repository.Signing.SigningKey +} + +// PublicSigningKey gets the public signing key within a provided repository directory +func PublicSigningKey(repoPath string) (string, error) { + signingKey := signingKey(repoPath) + if signingKey == "" { + return "", nil + } + + content, stderr, err := process.GetManager().ExecDir(-1, repoPath, + "gpg --export -a", "gpg", "--export", "-a", signingKey) + if err != nil { + log.Error("Unable to get default signing key in %s: %s, %s, %v", repoPath, signingKey, stderr, err) + return "", err + } + return content, nil +} + +// SignInitialCommit determines if we should sign the initial commit to this repository +func SignInitialCommit(repoPath string, u *User) (bool, string) { + rules := signingModeFromStrings(setting.Repository.Signing.InitialCommit) + signingKey := signingKey(repoPath) + if signingKey == "" { + return false, "" + } + + for _, rule := range rules { + switch rule { + case never: + return false, "" + case always: + break + case pubkey: + keys, err := ListGPGKeys(u.ID) + if err != nil || len(keys) == 0 { + return false, "" + } + case twofa: + twofa, err := GetTwoFactorByUID(u.ID) + if err != nil || twofa == nil { + return false, "" + } + } + } + return true, signingKey +} + +// SignWikiCommit determines if we should sign the commits to this repository wiki +func (repo *Repository) SignWikiCommit(u *User) (bool, string) { + rules := signingModeFromStrings(setting.Repository.Signing.Wiki) + signingKey := signingKey(repo.WikiPath()) + if signingKey == "" { + return false, "" + } + + for _, rule := range rules { + switch rule { + case never: + return false, "" + case always: + break + case pubkey: + keys, err := ListGPGKeys(u.ID) + if err != nil || len(keys) == 0 { + return false, "" + } + case twofa: + twofa, err := GetTwoFactorByUID(u.ID) + if err != nil || twofa == nil { + return false, "" + } + case parentSigned: + gitRepo, err := git.OpenRepository(repo.WikiPath()) + if err != nil { + return false, "" + } + commit, err := gitRepo.GetCommit("HEAD") + if err != nil { + return false, "" + } + if commit.Signature == nil { + return false, "" + } + verification := ParseCommitWithSignature(commit) + if !verification.Verified { + return false, "" + } + } + } + return true, signingKey +} + +// SignCRUDAction determines if we should sign a CRUD commit to this repository +func (repo *Repository) SignCRUDAction(u *User, tmpBasePath, parentCommit string) (bool, string) { + rules := signingModeFromStrings(setting.Repository.Signing.CRUDActions) + signingKey := signingKey(repo.RepoPath()) + if signingKey == "" { + return false, "" + } + + for _, rule := range rules { + switch rule { + case never: + return false, "" + case always: + break + case pubkey: + keys, err := ListGPGKeys(u.ID) + if err != nil || len(keys) == 0 { + return false, "" + } + case twofa: + twofa, err := GetTwoFactorByUID(u.ID) + if err != nil || twofa == nil { + return false, "" + } + case parentSigned: + gitRepo, err := git.OpenRepository(tmpBasePath) + if err != nil { + return false, "" + } + commit, err := gitRepo.GetCommit(parentCommit) + if err != nil { + return false, "" + } + if commit.Signature == nil { + return false, "" + } + verification := ParseCommitWithSignature(commit) + if !verification.Verified { + return false, "" + } + } + } + return true, signingKey +} + +// SignMerge determines if we should sign a merge commit to this repository +func (repo *Repository) SignMerge(u *User, tmpBasePath, baseCommit, headCommit string) (bool, string) { + rules := signingModeFromStrings(setting.Repository.Signing.Merges) + signingKey := signingKey(repo.RepoPath()) + if signingKey == "" { + return false, "" + } + var gitRepo *git.Repository + var err error + + for _, rule := range rules { + switch rule { + case never: + return false, "" + case always: + break + case pubkey: + keys, err := ListGPGKeys(u.ID) + if err != nil || len(keys) == 0 { + return false, "" + } + case twofa: + twofa, err := GetTwoFactorByUID(u.ID) + if err != nil || twofa == nil { + return false, "" + } + case baseSigned: + if gitRepo == nil { + gitRepo, err = git.OpenRepository(tmpBasePath) + if err != nil { + return false, "" + } + } + commit, err := gitRepo.GetCommit(baseCommit) + if err != nil { + return false, "" + } + verification := ParseCommitWithSignature(commit) + if !verification.Verified { + return false, "" + } + case headSigned: + if gitRepo == nil { + gitRepo, err = git.OpenRepository(tmpBasePath) + if err != nil { + return false, "" + } + } + commit, err := gitRepo.GetCommit(headCommit) + if err != nil { + return false, "" + } + verification := ParseCommitWithSignature(commit) + if !verification.Verified { + return false, "" + } + case commitsSigned: + if gitRepo == nil { + gitRepo, err = git.OpenRepository(tmpBasePath) + if err != nil { + return false, "" + } + } + commit, err := gitRepo.GetCommit(headCommit) + if err != nil { + return false, "" + } + verification := ParseCommitWithSignature(commit) + if !verification.Verified { + return false, "" + } + // need to work out merge-base + mergeBaseCommit, _, err := gitRepo.GetMergeBase("", baseCommit, headCommit) + if err != nil { + return false, "" + } + commitList, err := commit.CommitsBeforeUntil(mergeBaseCommit) + if err != nil { + return false, "" + } + for e := commitList.Front(); e != nil; e = e.Next() { + commit = e.Value.(*git.Commit) + verification := ParseCommitWithSignature(commit) + if !verification.Verified { + return false, "" + } + } + } + } + return true, signingKey +} |