@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/timeutil"
"github.com/go-xorm/xorm"
"github.com/go-xorm/xorm"
@ -80,6 +81,12 @@ func GetGPGKeyByID(keyID int64) (*GPGKey, error) {
return key , nil
return key , nil
}
}
// GetGPGKeysByKeyID returns public key by given ID.
func GetGPGKeysByKeyID ( keyID string ) ( [ ] * GPGKey , error ) {
keys := make ( [ ] * GPGKey , 0 , 1 )
return keys , x . Where ( "key_id=?" , keyID ) . Find ( & keys )
}
// GetGPGImportByKeyID returns the import public armored key by given KeyID.
// GetGPGImportByKeyID returns the import public armored key by given KeyID.
func GetGPGImportByKeyID ( keyID string ) ( * GPGKeyImport , error ) {
func GetGPGImportByKeyID ( keyID string ) ( * GPGKeyImport , error ) {
key := new ( GPGKeyImport )
key := new ( GPGKeyImport )
@ -355,10 +362,13 @@ func DeleteGPGKey(doer *User, id int64) (err error) {
// CommitVerification represents a commit validation of signature
// CommitVerification represents a commit validation of signature
type CommitVerification struct {
type CommitVerification struct {
Verified bool
Reason string
SigningUser * User
SigningKey * GPGKey
Verified bool
Warning bool
Reason string
SigningUser * User
CommittingUser * User
SigningEmail string
SigningKey * GPGKey
}
}
// SignCommit represents a commit with validation of signature.
// SignCommit represents a commit with validation of signature.
@ -367,6 +377,17 @@ type SignCommit struct {
* UserCommit
* 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 readerFromBase64 ( s string ) ( io . Reader , error ) {
func readerFromBase64 ( s string ) ( io . Reader , error ) {
bs , err := base64 . StdEncoding . DecodeString ( s )
bs , err := base64 . StdEncoding . DecodeString ( s )
if err != nil {
if err != nil {
@ -424,49 +445,207 @@ func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error {
return pkey . VerifySignature ( h , s )
return pkey . VerifySignature ( h , s )
}
}
// ParseCommitWithSignature check if signature is good against keystore.
func ParseCommitWithSignature ( c * git . Commit ) * CommitVerification {
if c . Signature != nil && c . Committer != nil {
//Parsing signature
sig , err := extractSignature ( c . Signature . Signature )
if err != nil { //Skipping failed to extract sign
log . Error ( "SignatureRead err: %v" , err )
return & CommitVerification {
Verified : false ,
Reason : "gpg.error.extract_sign" ,
func hashAndVerify ( sig * packet . Signature , payload string , k * GPGKey , committer , signer * User , email string ) * CommitVerification {
//Generating hash of commit
hash , err := populateHash ( sig . Hash , [ ] byte ( payload ) )
if err != nil { //Skipping failed to generate hash
log . Error ( "PopulateHash: %v" , err )
return & CommitVerification {
CommittingUser : committer ,
Verified : false ,
Reason : "gpg.error.generate_hash" ,
}
}
if err := verifySign ( sig , hash , k ) ; err == nil {
return & CommitVerification { //Everything is ok
CommittingUser : committer ,
Verified : true ,
Reason : fmt . Sprintf ( "%s <%s> / %s" , signer . Name , signer . Email , k . KeyID ) ,
SigningUser : signer ,
SigningKey : k ,
SigningEmail : email ,
}
}
return nil
}
func hashAndVerifyWithSubKeys ( sig * packet . Signature , payload string , k * GPGKey , committer , signer * User , email string ) * CommitVerification {
commitVerification := hashAndVerify ( sig , payload , k , committer , signer , email )
if commitVerification != nil {
return commitVerification
}
//And test also SubsKey
for _ , sk := range k . SubsKey {
commitVerification := hashAndVerify ( sig , payload , sk , committer , signer , email )
if commitVerification != nil {
return commitVerification
}
}
return nil
}
func hashAndVerifyForKeyID ( sig * packet . Signature , payload string , committer * User , keyID , name , email string ) * CommitVerification {
if keyID == "" {
return nil
}
keys , err := GetGPGKeysByKeyID ( keyID )
if err != nil {
log . Error ( "GetGPGKeysByKeyID: %v" , err )
return & CommitVerification {
CommittingUser : committer ,
Verified : false ,
Reason : "gpg.error.failed_retrieval_gpg_keys" ,
}
}
if len ( keys ) == 0 {
return nil
}
for _ , key := range keys {
activated := false
if len ( email ) != 0 {
for _ , e := range key . Emails {
if e . IsActivated && strings . EqualFold ( e . Email , email ) {
activated = true
email = e . Email
break
}
}
} else {
for _ , e := range key . Emails {
if e . IsActivated {
activated = true
email = e . Email
break
}
}
}
if ! activated {
continue
}
signer := & User {
Name : name ,
Email : email ,
}
if key . OwnerID != 0 {
owner , err := GetUserByID ( key . OwnerID )
if err == nil {
signer = owner
} else if ! IsErrUserNotExist ( err ) {
log . Error ( "Failed to GetUserByID: %d for key ID: %d (%s) %v" , key . OwnerID , key . ID , key . KeyID , err )
return & CommitVerification {
CommittingUser : committer ,
Verified : false ,
Reason : "gpg.error.no_committer_account" ,
}
}
}
}
}
commitVerification := hashAndVerifyWithSubKeys ( sig , payload , key , committer , signer , email )
if commitVerification != nil {
return commitVerification
}
}
// This is a bad situation ... We have a key id that is in our database but the signature doesn't match.
return & CommitVerification {
CommittingUser : committer ,
Verified : false ,
Warning : true ,
Reason : BadSignature ,
}
}
// ParseCommitWithSignature check if signature is good against keystore.
func ParseCommitWithSignature ( c * git . Commit ) * CommitVerification {
var committer * User
if c . Committer != nil {
var err error
//Find Committer account
//Find Committer account
committer , err := GetUserByEmail ( c . Committer . Email ) //This find the user by primary email or activated email so commit will not be valid if email is not
if err != nil { //Skipping not user for commiter
committer , err = GetUserByEmail ( c . Committer . Email ) //This finds the user by primary email or activated email so commit will not be valid if email is not
if err != nil { //Skipping not user for commiter
committer = & User {
Name : c . Committer . Name ,
Email : c . Committer . Email ,
}
// We can expect this to often be an ErrUserNotExist. in the case
// We can expect this to often be an ErrUserNotExist. in the case
// it is not, however, it is important to log it.
// it is not, however, it is important to log it.
if ! IsErrUserNotExist ( err ) {
if ! IsErrUserNotExist ( err ) {
log . Error ( "GetUserByEmail: %v" , err )
log . Error ( "GetUserByEmail: %v" , err )
return & CommitVerification {
CommittingUser : committer ,
Verified : false ,
Reason : "gpg.error.no_committer_account" ,
}
}
}
return & CommitVerification {
Verified : false ,
Reason : "gpg.error.no_committer_account" ,
}
}
}
// If no signature just report the committer
if c . Signature == nil {
return & CommitVerification {
CommittingUser : committer ,
Verified : false , //Default value
Reason : "gpg.error.not_signed_commit" , //Default value
}
}
//Parsing signature
sig , err := extractSignature ( c . Signature . Signature )
if err != nil { //Skipping failed to extract sign
log . Error ( "SignatureRead err: %v" , err )
return & CommitVerification {
CommittingUser : committer ,
Verified : false ,
Reason : "gpg.error.extract_sign" ,
}
}
keyID := ""
if sig . IssuerKeyId != nil && ( * sig . IssuerKeyId ) != 0 {
keyID = fmt . Sprintf ( "%X" , * sig . IssuerKeyId )
}
if keyID == "" && sig . IssuerFingerprint != nil && len ( sig . IssuerFingerprint ) > 0 {
keyID = fmt . Sprintf ( "%X" , sig . IssuerFingerprint [ 12 : 20 ] )
}
defaultReason := NoKeyFound
// First check if the sig has a keyID and if so just look at that
if commitVerification := hashAndVerifyForKeyID (
sig ,
c . Signature . Payload ,
committer ,
keyID ,
setting . AppName ,
"" ) ; commitVerification != nil {
if commitVerification . Reason == BadSignature {
defaultReason = BadSignature
} else {
return commitVerification
}
}
}
// Now try to associate the signature with the committer, if present
if committer . ID != 0 {
keys , err := ListGPGKeys ( committer . ID )
keys , err := ListGPGKeys ( committer . ID )
if err != nil { //Skipping failed to get gpg keys of user
if err != nil { //Skipping failed to get gpg keys of user
log . Error ( "ListGPGKeys: %v" , err )
log . Error ( "ListGPGKeys: %v" , err )
return & CommitVerification {
return & CommitVerification {
Verified : false ,
Reason : "gpg.error.failed_retrieval_gpg_keys" ,
CommittingUser : committer ,
Verified : false ,
Reason : "gpg.error.failed_retrieval_gpg_keys" ,
}
}
}
}
for _ , k := range keys {
for _ , k := range keys {
//Pre-check (& optimization) that emails attached to key can be attached to the commiter email and can validate
//Pre-check (& optimization) that emails attached to key can be attached to the commiter email and can validate
canValidate := false
canValidate := false
lowerCommiterEmail := strings . ToLower ( c . Committer . Email )
email := ""
for _ , e := range k . Emails {
for _ , e := range k . Emails {
if e . IsActivated && strings . ToLower ( e . Email ) == lowerCommiterEmail {
if e . IsActivated && strings . EqualFold ( e . Email , c . Committer . Email ) {
canValidate = true
canValidate = true
email = e . Email
break
break
}
}
}
}
@ -474,56 +653,102 @@ func ParseCommitWithSignature(c *git.Commit) *CommitVerification {
continue //Skip this key
continue //Skip this key
}
}
//Generating hash of commit
hash , err := populateHash ( sig . Hash , [ ] byte ( c . Signature . Payload ) )
if err != nil { //Skipping ailed to generate hash
log . Error ( "PopulateHash: %v" , err )
return & CommitVerification {
Verified : false ,
Reason : "gpg.error.generate_hash" ,
}
}
//We get PK
if err := verifySign ( sig , hash , k ) ; err == nil {
return & CommitVerification { //Everything is ok
Verified : true ,
Reason : fmt . Sprintf ( "%s <%s> / %s" , c . Committer . Name , c . Committer . Email , k . KeyID ) ,
SigningUser : committer ,
SigningKey : k ,
}
commitVerification := hashAndVerifyWithSubKeys ( sig , c . Signature . Payload , k , committer , committer , email )
if commitVerification != nil {
return commitVerification
}
}
//And test also SubsKey
for _ , sk := range k . SubsKey {
//Generating hash of commit
hash , err := populateHash ( sig . Hash , [ ] byte ( c . Signature . Payload ) )
if err != nil { //Skipping ailed to generate hash
log . Error ( "PopulateHash: %v" , err )
return & CommitVerification {
Verified : false ,
Reason : "gpg.error.generate_hash" ,
}
}
if err := verifySign ( sig , hash , sk ) ; err == nil {
return & CommitVerification { //Everything is ok
Verified : true ,
Reason : fmt . Sprintf ( "%s <%s> / %s" , c . Committer . Name , c . Committer . Email , sk . KeyID ) ,
SigningUser : committer ,
SigningKey : sk ,
}
}
}
}
if setting . Repository . Signing . SigningKey != "" && setting . Repository . Signing . SigningKey != "default" && setting . Repository . Signing . SigningKey != "none" {
// OK we should try the default key
gpgSettings := git . GPGSettings {
Sign : true ,
KeyID : setting . Repository . Signing . SigningKey ,
Name : setting . Repository . Signing . SigningName ,
Email : setting . Repository . Signing . SigningEmail ,
}
if err := gpgSettings . LoadPublicKeyContent ( ) ; err != nil {
log . Error ( "Error getting default signing key: %s %v" , gpgSettings . KeyID , err )
} else if commitVerification := verifyWithGPGSettings ( & gpgSettings , sig , c . Signature . Payload , committer , keyID ) ; commitVerification != nil {
if commitVerification . Reason == BadSignature {
defaultReason = BadSignature
} else {
return commitVerification
}
}
}
}
return & CommitVerification { //Default at this stage
Verified : false ,
Reason : "gpg.error.no_gpg_keys_found" ,
}
defaultGPGSettings , err := c . GetRepositoryDefaultPublicGPGKey ( false )
if err != nil {
log . Error ( "Error getting default public gpg key: %v" , err )
} else if defaultGPGSettings . Sign {
if commitVerification := verifyWithGPGSettings ( defaultGPGSettings , sig , c . Signature . Payload , committer , keyID ) ; commitVerification != nil {
if commitVerification . Reason == BadSignature {
defaultReason = BadSignature
} else {
return commitVerification
}
}
}
}
}
return & CommitVerification {
Verified : false , //Default value
Reason : "gpg.error.not_signed_commit" , //Default value
return & CommitVerification { //Default at this stage
CommittingUser : committer ,
Verified : false ,
Warning : defaultReason != NoKeyFound ,
Reason : defaultReason ,
SigningKey : & GPGKey {
KeyID : keyID ,
} ,
}
}
func verifyWithGPGSettings ( gpgSettings * git . GPGSettings , sig * packet . Signature , payload string , committer * User , keyID string ) * CommitVerification {
// First try to find the key in the db
if commitVerification := hashAndVerifyForKeyID ( sig , payload , committer , gpgSettings . KeyID , gpgSettings . Name , gpgSettings . Email ) ; commitVerification != nil {
return commitVerification
}
}
// Otherwise we have to parse the key
ekey , err := checkArmoredGPGKeyString ( gpgSettings . PublicKeyContent )
if err != nil {
log . Error ( "Unable to get default signing key: %v" , err )
return & CommitVerification {
CommittingUser : committer ,
Verified : false ,
Reason : "gpg.error.generate_hash" ,
}
}
pubkey := ekey . PrimaryKey
content , err := base64EncPubKey ( pubkey )
if err != nil {
return & CommitVerification {
CommittingUser : committer ,
Verified : false ,
Reason : "gpg.error.generate_hash" ,
}
}
k := & GPGKey {
Content : content ,
CanSign : pubkey . CanSign ( ) ,
KeyID : pubkey . KeyIdString ( ) ,
}
if commitVerification := hashAndVerifyWithSubKeys ( sig , payload , k , committer , & User {
Name : gpgSettings . Name ,
Email : gpgSettings . Email ,
} , gpgSettings . Email ) ; commitVerification != nil {
return commitVerification
}
if keyID == k . KeyID {
// This is a bad situation ... We have a key id that matches our default key but the signature doesn't match.
return & CommitVerification {
CommittingUser : committer ,
Verified : false ,
Warning : true ,
Reason : BadSignature ,
}
}
return nil
}
}
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.