2021-12-10 11:14:24 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2019-10-16 16:42:42 +03:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2021-12-10 11:14:24 +03:00
package asymkey
2019-10-16 16:42:42 +03:00
import (
2022-01-20 02:26:57 +03:00
"context"
2021-12-10 11:14:24 +03:00
"fmt"
2019-10-16 16:42:42 +03:00
"strings"
2021-12-10 11:14:24 +03:00
asymkey_model "code.gitea.io/gitea/models/asymkey"
2022-01-02 16:12:35 +03:00
"code.gitea.io/gitea/models/auth"
2021-09-24 14:32:56 +03:00
"code.gitea.io/gitea/models/db"
2022-06-12 18:51:54 +03:00
git_model "code.gitea.io/gitea/models/git"
2022-06-13 12:37:59 +03:00
issues_model "code.gitea.io/gitea/models/issues"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2019-10-16 16:42:42 +03:00
"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"
2019-12-15 14:06:31 +03:00
approved signingMode = "approved"
2020-01-15 11:32:57 +03:00
noKey signingMode = "nokey"
2019-10-16 16:42:42 +03:00
)
func signingModeFromStrings ( modeStrings [ ] string ) [ ] signingMode {
returnable := make ( [ ] signingMode , 0 , len ( modeStrings ) )
for _ , mode := range modeStrings {
2020-09-19 19:44:55 +03:00
signMode := signingMode ( strings . ToLower ( strings . TrimSpace ( mode ) ) )
2019-10-16 16:42:42 +03:00
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
2019-12-15 14:06:31 +03:00
case approved :
fallthrough
2019-10-16 16:42:42 +03:00
case commitsSigned :
returnable = append ( returnable , signMode )
}
}
if len ( returnable ) == 0 {
return [ ] signingMode { never }
}
return returnable
}
2021-12-10 11:14:24 +03:00
// ErrWontSign explains the first reason why a commit would not be signed
// There may be other reasons - this is just the first reason found
type ErrWontSign struct {
Reason signingMode
}
func ( e * ErrWontSign ) Error ( ) string {
return fmt . Sprintf ( "wont sign: %s" , e . Reason )
}
// IsErrWontSign checks if an error is a ErrWontSign
func IsErrWontSign ( err error ) bool {
_ , ok := err . ( * ErrWontSign )
return ok
}
2020-09-19 19:44:55 +03:00
// SigningKey returns the KeyID and git Signature for the repo
2022-01-20 02:26:57 +03:00
func SigningKey ( ctx context . Context , repoPath string ) ( string , * git . Signature ) {
2019-10-16 16:42:42 +03:00
if setting . Repository . Signing . SigningKey == "none" {
2020-09-19 19:44:55 +03:00
return "" , nil
2019-10-16 16:42:42 +03:00
}
if setting . Repository . Signing . SigningKey == "default" || setting . Repository . Signing . SigningKey == "" {
// Can ignore the error here as it means that commit.gpgsign is not set
2022-04-01 05:55:30 +03:00
value , _ , _ := git . NewCommand ( ctx , "config" , "--get" , "commit.gpgsign" ) . RunStdString ( & git . RunOpts { Dir : repoPath } )
2019-10-16 16:42:42 +03:00
sign , valid := git . ParseBool ( strings . TrimSpace ( value ) )
if ! sign || ! valid {
2020-09-19 19:44:55 +03:00
return "" , nil
2019-10-16 16:42:42 +03:00
}
2022-04-01 05:55:30 +03:00
signingKey , _ , _ := git . NewCommand ( ctx , "config" , "--get" , "user.signingkey" ) . RunStdString ( & git . RunOpts { Dir : repoPath } )
signingName , _ , _ := git . NewCommand ( ctx , "config" , "--get" , "user.name" ) . RunStdString ( & git . RunOpts { Dir : repoPath } )
signingEmail , _ , _ := git . NewCommand ( ctx , "config" , "--get" , "user.email" ) . RunStdString ( & git . RunOpts { Dir : repoPath } )
2020-09-19 19:44:55 +03:00
return strings . TrimSpace ( signingKey ) , & git . Signature {
Name : strings . TrimSpace ( signingName ) ,
Email : strings . TrimSpace ( signingEmail ) ,
}
2019-10-16 16:42:42 +03:00
}
2020-09-19 19:44:55 +03:00
return setting . Repository . Signing . SigningKey , & git . Signature {
Name : setting . Repository . Signing . SigningName ,
Email : setting . Repository . Signing . SigningEmail ,
}
2019-10-16 16:42:42 +03:00
}
// PublicSigningKey gets the public signing key within a provided repository directory
2022-01-20 02:26:57 +03:00
func PublicSigningKey ( ctx context . Context , repoPath string ) ( string , error ) {
signingKey , _ := SigningKey ( ctx , repoPath )
2019-10-16 16:42:42 +03:00
if signingKey == "" {
return "" , nil
}
2022-01-20 02:26:57 +03:00
content , stderr , err := process . GetManager ( ) . ExecDir ( ctx , - 1 , repoPath ,
2019-10-16 16:42:42 +03:00
"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
2022-01-20 02:26:57 +03:00
func SignInitialCommit ( ctx context . Context , repoPath string , u * user_model . User ) ( bool , string , * git . Signature , error ) {
2019-10-16 16:42:42 +03:00
rules := signingModeFromStrings ( setting . Repository . Signing . InitialCommit )
2022-01-20 02:26:57 +03:00
signingKey , sig := SigningKey ( ctx , repoPath )
2019-10-16 16:42:42 +03:00
if signingKey == "" {
2020-09-19 19:44:55 +03:00
return false , "" , nil , & ErrWontSign { noKey }
2019-10-16 16:42:42 +03:00
}
2020-07-06 15:07:07 +03:00
Loop :
2019-10-16 16:42:42 +03:00
for _ , rule := range rules {
switch rule {
case never :
2020-09-19 19:44:55 +03:00
return false , "" , nil , & ErrWontSign { never }
2019-10-16 16:42:42 +03:00
case always :
2020-07-06 15:07:07 +03:00
break Loop
2019-10-16 16:42:42 +03:00
case pubkey :
2022-03-22 18:22:54 +03:00
keys , err := asymkey_model . ListGPGKeys ( ctx , u . ID , db . ListOptions { } )
2020-01-15 11:32:57 +03:00
if err != nil {
2020-09-19 19:44:55 +03:00
return false , "" , nil , err
2020-01-15 11:32:57 +03:00
}
if len ( keys ) == 0 {
2020-09-19 19:44:55 +03:00
return false , "" , nil , & ErrWontSign { pubkey }
2019-10-16 16:42:42 +03:00
}
case twofa :
2022-01-02 16:12:35 +03:00
twofaModel , err := auth . GetTwoFactorByUID ( u . ID )
if err != nil && ! auth . IsErrTwoFactorNotEnrolled ( err ) {
2020-09-19 19:44:55 +03:00
return false , "" , nil , err
2020-01-15 11:32:57 +03:00
}
if twofaModel == nil {
2020-09-19 19:44:55 +03:00
return false , "" , nil , & ErrWontSign { twofa }
2019-10-16 16:42:42 +03:00
}
}
}
2020-09-19 19:44:55 +03:00
return true , signingKey , sig , nil
2019-10-16 16:42:42 +03:00
}
// SignWikiCommit determines if we should sign the commits to this repository wiki
2022-01-20 02:26:57 +03:00
func SignWikiCommit ( ctx context . Context , repoWikiPath string , u * user_model . User ) ( bool , string , * git . Signature , error ) {
2019-10-16 16:42:42 +03:00
rules := signingModeFromStrings ( setting . Repository . Signing . Wiki )
2022-01-20 02:26:57 +03:00
signingKey , sig := SigningKey ( ctx , repoWikiPath )
2019-10-16 16:42:42 +03:00
if signingKey == "" {
2020-09-19 19:44:55 +03:00
return false , "" , nil , & ErrWontSign { noKey }
2019-10-16 16:42:42 +03:00
}
2020-07-06 15:07:07 +03:00
Loop :
2019-10-16 16:42:42 +03:00
for _ , rule := range rules {
switch rule {
case never :
2020-09-19 19:44:55 +03:00
return false , "" , nil , & ErrWontSign { never }
2019-10-16 16:42:42 +03:00
case always :
2020-07-06 15:07:07 +03:00
break Loop
2019-10-16 16:42:42 +03:00
case pubkey :
2022-03-22 18:22:54 +03:00
keys , err := asymkey_model . ListGPGKeys ( ctx , u . ID , db . ListOptions { } )
2020-01-15 11:32:57 +03:00
if err != nil {
2020-09-19 19:44:55 +03:00
return false , "" , nil , err
2020-01-15 11:32:57 +03:00
}
if len ( keys ) == 0 {
2020-09-19 19:44:55 +03:00
return false , "" , nil , & ErrWontSign { pubkey }
2019-10-16 16:42:42 +03:00
}
case twofa :
2022-01-02 16:12:35 +03:00
twofaModel , err := auth . GetTwoFactorByUID ( u . ID )
if err != nil && ! auth . IsErrTwoFactorNotEnrolled ( err ) {
2020-09-19 19:44:55 +03:00
return false , "" , nil , err
2020-01-15 11:32:57 +03:00
}
if twofaModel == nil {
2020-09-19 19:44:55 +03:00
return false , "" , nil , & ErrWontSign { twofa }
2019-10-16 16:42:42 +03:00
}
case parentSigned :
2022-03-29 22:13:41 +03:00
gitRepo , err := git . OpenRepository ( ctx , repoWikiPath )
2019-10-16 16:42:42 +03:00
if err != nil {
2020-09-19 19:44:55 +03:00
return false , "" , nil , err
2019-10-16 16:42:42 +03:00
}
2019-11-13 10:01:19 +03:00
defer gitRepo . Close ( )
2019-10-16 16:42:42 +03:00
commit , err := gitRepo . GetCommit ( "HEAD" )
if err != nil {
2020-09-19 19:44:55 +03:00
return false , "" , nil , err
2019-10-16 16:42:42 +03:00
}
if commit . Signature == nil {
2020-09-19 19:44:55 +03:00
return false , "" , nil , & ErrWontSign { parentSigned }
2019-10-16 16:42:42 +03:00
}
2021-12-10 11:14:24 +03:00
verification := asymkey_model . ParseCommitWithSignature ( commit )
2019-10-16 16:42:42 +03:00
if ! verification . Verified {
2020-09-19 19:44:55 +03:00
return false , "" , nil , & ErrWontSign { parentSigned }
2019-10-16 16:42:42 +03:00
}
}
}
2020-09-19 19:44:55 +03:00
return true , signingKey , sig , nil
2019-10-16 16:42:42 +03:00
}
// SignCRUDAction determines if we should sign a CRUD commit to this repository
2022-01-20 02:26:57 +03:00
func SignCRUDAction ( ctx context . Context , repoPath string , u * user_model . User , tmpBasePath , parentCommit string ) ( bool , string , * git . Signature , error ) {
2019-10-16 16:42:42 +03:00
rules := signingModeFromStrings ( setting . Repository . Signing . CRUDActions )
2022-01-20 02:26:57 +03:00
signingKey , sig := SigningKey ( ctx , repoPath )
2019-10-16 16:42:42 +03:00
if signingKey == "" {
2020-09-19 19:44:55 +03:00
return false , "" , nil , & ErrWontSign { noKey }
2019-10-16 16:42:42 +03:00
}
2020-07-06 15:07:07 +03:00
Loop :
2019-10-16 16:42:42 +03:00
for _ , rule := range rules {
switch rule {
case never :
2020-09-19 19:44:55 +03:00
return false , "" , nil , & ErrWontSign { never }
2019-10-16 16:42:42 +03:00
case always :
2020-07-06 15:07:07 +03:00
break Loop
2019-10-16 16:42:42 +03:00
case pubkey :
2022-03-22 18:22:54 +03:00
keys , err := asymkey_model . ListGPGKeys ( ctx , u . ID , db . ListOptions { } )
2020-01-15 11:32:57 +03:00
if err != nil {
2020-09-19 19:44:55 +03:00
return false , "" , nil , err
2020-01-15 11:32:57 +03:00
}
if len ( keys ) == 0 {
2020-09-19 19:44:55 +03:00
return false , "" , nil , & ErrWontSign { pubkey }
2019-10-16 16:42:42 +03:00
}
case twofa :
2022-01-02 16:12:35 +03:00
twofaModel , err := auth . GetTwoFactorByUID ( u . ID )
if err != nil && ! auth . IsErrTwoFactorNotEnrolled ( err ) {
2020-09-19 19:44:55 +03:00
return false , "" , nil , err
2020-01-15 11:32:57 +03:00
}
if twofaModel == nil {
2020-09-19 19:44:55 +03:00
return false , "" , nil , & ErrWontSign { twofa }
2019-10-16 16:42:42 +03:00
}
case parentSigned :
2022-03-29 22:13:41 +03:00
gitRepo , err := git . OpenRepository ( ctx , tmpBasePath )
2019-10-16 16:42:42 +03:00
if err != nil {
2020-09-19 19:44:55 +03:00
return false , "" , nil , err
2019-10-16 16:42:42 +03:00
}
2019-11-13 10:01:19 +03:00
defer gitRepo . Close ( )
2019-10-16 16:42:42 +03:00
commit , err := gitRepo . GetCommit ( parentCommit )
if err != nil {
2020-09-19 19:44:55 +03:00
return false , "" , nil , err
2019-10-16 16:42:42 +03:00
}
if commit . Signature == nil {
2020-09-19 19:44:55 +03:00
return false , "" , nil , & ErrWontSign { parentSigned }
2019-10-16 16:42:42 +03:00
}
2021-12-10 11:14:24 +03:00
verification := asymkey_model . ParseCommitWithSignature ( commit )
2019-10-16 16:42:42 +03:00
if ! verification . Verified {
2020-09-19 19:44:55 +03:00
return false , "" , nil , & ErrWontSign { parentSigned }
2019-10-16 16:42:42 +03:00
}
}
}
2020-09-19 19:44:55 +03:00
return true , signingKey , sig , nil
2019-10-16 16:42:42 +03:00
}
2021-12-10 11:14:24 +03:00
// SignMerge determines if we should sign a PR merge commit to the base repository
2022-06-13 12:37:59 +03:00
func SignMerge ( ctx context . Context , pr * issues_model . PullRequest , u * user_model . User , tmpBasePath , baseCommit , headCommit string ) ( bool , string , * git . Signature , error ) {
2022-11-19 11:12:33 +03:00
if err := pr . LoadBaseRepo ( ctx ) ; err != nil {
2021-12-10 11:14:24 +03:00
log . Error ( "Unable to get Base Repo for pull request" )
return false , "" , nil , err
}
repo := pr . BaseRepo
2022-01-20 02:26:57 +03:00
signingKey , signer := SigningKey ( ctx , repo . RepoPath ( ) )
2021-12-10 11:14:24 +03:00
if signingKey == "" {
return false , "" , nil , & ErrWontSign { noKey }
}
rules := signingModeFromStrings ( setting . Repository . Signing . Merges )
var gitRepo * git . Repository
var err error
Loop :
for _ , rule := range rules {
switch rule {
case never :
return false , "" , nil , & ErrWontSign { never }
case always :
break Loop
case pubkey :
2022-03-22 18:22:54 +03:00
keys , err := asymkey_model . ListGPGKeys ( ctx , u . ID , db . ListOptions { } )
2021-12-10 11:14:24 +03:00
if err != nil {
return false , "" , nil , err
}
if len ( keys ) == 0 {
return false , "" , nil , & ErrWontSign { pubkey }
}
case twofa :
2022-01-02 16:12:35 +03:00
twofaModel , err := auth . GetTwoFactorByUID ( u . ID )
if err != nil && ! auth . IsErrTwoFactorNotEnrolled ( err ) {
2021-12-10 11:14:24 +03:00
return false , "" , nil , err
}
if twofaModel == nil {
return false , "" , nil , & ErrWontSign { twofa }
}
case approved :
2022-06-12 18:51:54 +03:00
protectedBranch , err := git_model . GetProtectedBranchBy ( ctx , repo . ID , pr . BaseBranch )
2021-12-10 11:14:24 +03:00
if err != nil {
return false , "" , nil , err
}
if protectedBranch == nil {
return false , "" , nil , & ErrWontSign { approved }
}
2022-06-13 12:37:59 +03:00
if issues_model . GetGrantedApprovalsCount ( ctx , protectedBranch , pr ) < 1 {
2021-12-10 11:14:24 +03:00
return false , "" , nil , & ErrWontSign { approved }
}
case baseSigned :
if gitRepo == nil {
2022-03-29 22:13:41 +03:00
gitRepo , err = git . OpenRepository ( ctx , tmpBasePath )
2021-12-10 11:14:24 +03:00
if err != nil {
return false , "" , nil , err
}
defer gitRepo . Close ( )
}
commit , err := gitRepo . GetCommit ( baseCommit )
if err != nil {
return false , "" , nil , err
}
verification := asymkey_model . ParseCommitWithSignature ( commit )
if ! verification . Verified {
return false , "" , nil , & ErrWontSign { baseSigned }
}
case headSigned :
if gitRepo == nil {
2022-03-29 22:13:41 +03:00
gitRepo , err = git . OpenRepository ( ctx , tmpBasePath )
2021-12-10 11:14:24 +03:00
if err != nil {
return false , "" , nil , err
}
defer gitRepo . Close ( )
}
commit , err := gitRepo . GetCommit ( headCommit )
if err != nil {
return false , "" , nil , err
}
verification := asymkey_model . ParseCommitWithSignature ( commit )
if ! verification . Verified {
return false , "" , nil , & ErrWontSign { headSigned }
}
case commitsSigned :
if gitRepo == nil {
2022-03-29 22:13:41 +03:00
gitRepo , err = git . OpenRepository ( ctx , tmpBasePath )
2021-12-10 11:14:24 +03:00
if err != nil {
return false , "" , nil , err
}
defer gitRepo . Close ( )
}
commit , err := gitRepo . GetCommit ( headCommit )
if err != nil {
return false , "" , nil , err
}
verification := asymkey_model . ParseCommitWithSignature ( commit )
if ! verification . Verified {
return false , "" , nil , & ErrWontSign { commitsSigned }
}
// need to work out merge-base
mergeBaseCommit , _ , err := gitRepo . GetMergeBase ( "" , baseCommit , headCommit )
if err != nil {
return false , "" , nil , err
}
commitList , err := commit . CommitsBeforeUntil ( mergeBaseCommit )
if err != nil {
return false , "" , nil , err
}
for _ , commit := range commitList {
verification := asymkey_model . ParseCommitWithSignature ( commit )
if ! verification . Verified {
return false , "" , nil , & ErrWontSign { commitsSigned }
}
}
}
}
return true , signingKey , signer , nil
}