1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
|
// Copyright The gittuf Authors
// SPDX-License-Identifier: Apache-2.0
package gitinterface
import (
"context"
"errors"
"fmt"
"io"
"strings"
"github.com/gittuf/gittuf/internal/signerverifier/gpg"
"github.com/gittuf/gittuf/internal/signerverifier/sigstore"
"github.com/gittuf/gittuf/internal/signerverifier/ssh"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/secure-systems-lab/go-securesystemslib/signerverifier"
)
var (
ErrTagAlreadyExists = errors.New("tag already exists")
)
// TagUsingSpecificKey creates a Git tag signed using the specified, PEM encoded
// SSH or GPG key. It is primarily intended for use with testing. As of now,
// gittuf is not expected to be used to create tags in developer workflows,
// though this may change with command compatibility.
func (r *Repository) TagUsingSpecificKey(target Hash, name, message string, signingKeyPEMBytes []byte) (Hash, error) {
gitConfig, err := r.GetGitConfig()
if err != nil {
return ZeroHash, err
}
goGitRepo, err := r.GetGoGitRepository()
if err != nil {
return ZeroHash, err
}
targetObj, err := goGitRepo.Object(plumbing.AnyObject, plumbing.NewHash(target.String()))
if err != nil {
return ZeroHash, err
}
if !strings.HasSuffix(message, "\n") {
message += "\n"
}
tag := &object.Tag{
Name: name,
Tagger: object.Signature{
Name: gitConfig["user.name"],
Email: gitConfig["user.email"],
When: r.clock.Now(),
},
Message: message,
TargetType: targetObj.Type(),
Target: targetObj.ID(),
}
tagContents, err := getTagBytesWithoutSignature(tag)
if err != nil {
return ZeroHash, err
}
signature, err := signGitObjectUsingKey(tagContents, signingKeyPEMBytes)
if err != nil {
return ZeroHash, err
}
tag.PGPSignature = signature
obj := goGitRepo.Storer.NewEncodedObject()
if err := tag.Encode(obj); err != nil {
return ZeroHash, err
}
tagID, err := goGitRepo.Storer.SetEncodedObject(obj)
if err != nil {
return ZeroHash, err
}
tagIDHash, err := NewHash(tagID.String())
if err != nil {
return ZeroHash, err
}
return tagIDHash, r.SetReference(TagReferenceName(name), tagIDHash)
}
// GetTagTarget returns the ID of the Git object a tag points to.
func (r *Repository) GetTagTarget(tagID Hash) (Hash, error) {
targetID, err := r.executor("rev-list", "-n", "1", tagID.String()).executeString()
if err != nil {
return ZeroHash, fmt.Errorf("unable to resolve tag's target ID: %w", err)
}
hash, err := NewHash(targetID)
if err != nil {
return ZeroHash, fmt.Errorf("invalid format for target ID: %w", err)
}
return hash, nil
}
// verifyTagSignature verifies a signature for the specified tag using the
// provided public key.
func (r *Repository) verifyTagSignature(ctx context.Context, tagID Hash, key *signerverifier.SSLibKey) error {
goGitRepo, err := r.GetGoGitRepository()
if err != nil {
return fmt.Errorf("error opening repository: %w", err)
}
tag, err := goGitRepo.TagObject(plumbing.NewHash(tagID.String()))
if err != nil {
return fmt.Errorf("unable to load commit object: %w", err)
}
switch key.KeyType {
case gpg.KeyType:
if _, err := tag.Verify(key.KeyVal.Public); err != nil {
return ErrIncorrectVerificationKey
}
return nil
case ssh.KeyType:
tagContents, err := getTagBytesWithoutSignature(tag)
if err != nil {
return errors.Join(ErrVerifyingSSHSignature, err)
}
tagSignature := []byte(tag.PGPSignature)
if err := verifySSHKeySignature(ctx, key, tagContents, tagSignature); err != nil {
return errors.Join(ErrIncorrectVerificationKey, err)
}
return nil
case sigstore.KeyType:
tagContents, err := getTagBytesWithoutSignature(tag)
if err != nil {
return errors.Join(ErrVerifyingSigstoreSignature, err)
}
tagSignature := []byte(tag.PGPSignature)
if err := verifyGitsignSignature(ctx, r, key, tagContents, tagSignature); err != nil {
return errors.Join(ErrIncorrectVerificationKey, err)
}
return nil
}
return ErrUnknownSigningMethod
}
func (r *Repository) ensureIsTag(tagID Hash) error {
objType, err := r.executor("cat-file", "-t", tagID.String()).executeString()
if err != nil {
return fmt.Errorf("unable to inspect if object is tag: %w", err)
} else if objType != "tag" {
return fmt.Errorf("requested Git ID '%s' is not a tag object", tagID.String())
}
return nil
}
func getTagBytesWithoutSignature(tag *object.Tag) ([]byte, error) {
tagEncoded := memory.NewStorage().NewEncodedObject()
if err := tag.EncodeWithoutSignature(tagEncoded); err != nil {
return nil, err
}
r, err := tagEncoded.Reader()
if err != nil {
return nil, err
}
return io.ReadAll(r)
}
|