• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gittuf / gittuf / 29552153989

17 Jul 2026 03:20AM UTC coverage: 78.033% (+0.2%) from 77.791%
29552153989

push

github

web-flow
Merge pull request #1472 from pjbgf/sha256

feat(sha256): support SHA-256 object format

179 of 220 new or added lines in 11 files covered. (81.36%)

2 existing lines in 2 files now uncovered.

12721 of 16302 relevant lines covered (78.03%)

36.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.64
/pkg/gitinterface/tag.go
1
// Copyright The gittuf Authors
2
// SPDX-License-Identifier: Apache-2.0
3

4
package gitinterface
5

6
import (
7
        "context"
8
        "errors"
9
        "fmt"
10
        "io"
11
        "strings"
12

13
        "github.com/gittuf/gittuf/internal/signerverifier/gpg"
14
        "github.com/gittuf/gittuf/internal/signerverifier/sigstore"
15
        "github.com/gittuf/gittuf/internal/signerverifier/ssh"
16
        "github.com/go-git/go-git/v6/plumbing"
17
        "github.com/go-git/go-git/v6/plumbing/object"
18
        "github.com/go-git/go-git/v6/storage/memory"
19
        "github.com/secure-systems-lab/go-securesystemslib/signerverifier"
20
)
21

22
var ErrTagAlreadyExists = errors.New("tag already exists")
23

24
// TagUsingSpecificKey creates a Git tag signed using the specified, PEM encoded
25
// SSH or GPG key. It is primarily intended for use with testing. As of now,
26
// gittuf is not expected to be used to create tags in developer workflows,
27
// though this may change with command compatibility.
28
func (r *Repository) TagUsingSpecificKey(target Hash, name, message string, signingKeyPEMBytes []byte) (Hash, error) {
11✔
29
        gitConfig, err := r.GetGitConfig()
11✔
30
        if err != nil {
11✔
31
                return ZeroHash, err
×
32
        }
×
33

34
        goGitRepo, err := r.GetGoGitRepository()
11✔
35
        if err != nil {
11✔
36
                return ZeroHash, err
×
37
        }
×
38

39
        targetObj, err := goGitRepo.Object(plumbing.AnyObject, plumbing.NewHash(target.String()))
11✔
40
        if err != nil {
12✔
41
                return ZeroHash, err
1✔
42
        }
1✔
43

44
        if !strings.HasSuffix(message, "\n") {
10✔
45
                message += "\n"
×
46
        }
×
47

48
        tag := &object.Tag{
10✔
49
                Name: name,
10✔
50
                Tagger: object.Signature{
10✔
51
                        Name:  gitConfig["user.name"],
10✔
52
                        Email: gitConfig["user.email"],
10✔
53
                        When:  r.clock.Now(),
10✔
54
                },
10✔
55
                Message:    message,
10✔
56
                TargetType: targetObj.Type(),
10✔
57
                Target:     targetObj.ID(),
10✔
58
        }
10✔
59

10✔
60
        tagContents, err := getTagBytesWithoutSignature(tag)
10✔
61
        if err != nil {
10✔
62
                return ZeroHash, err
×
63
        }
×
64
        signature, err := signGitObjectUsingKey(tagContents, signingKeyPEMBytes)
10✔
65
        if err != nil {
10✔
66
                return ZeroHash, err
×
67
        }
×
68
        // Git appends tag signatures to the tag payload regardless of the object
69
        // format; only commits store the signature under a header named for the
70
        // hash algorithm (`gpgsig` / `gpgsig-sha256`).
71
        tag.Signature = signature
10✔
72

10✔
73
        obj := goGitRepo.Storer.NewEncodedObject()
10✔
74
        if err := tag.Encode(obj); err != nil {
10✔
75
                return ZeroHash, err
×
76
        }
×
77
        tagID, err := goGitRepo.Storer.SetEncodedObject(obj)
10✔
78
        if err != nil {
10✔
79
                return ZeroHash, err
×
80
        }
×
81

82
        tagIDHash, err := NewHash(tagID.String())
10✔
83
        if err != nil {
10✔
84
                return ZeroHash, err
×
85
        }
×
86

87
        return tagIDHash, r.SetReference(TagReferenceName(name), tagIDHash)
10✔
88
}
89

90
// GetTagTarget returns the ID of the Git object a tag points to.
91
func (r *Repository) GetTagTarget(tagID Hash) (Hash, error) {
2✔
92
        targetID, err := r.executor("rev-list", "-n", "1", tagID.String()).executeString()
2✔
93
        if err != nil {
3✔
94
                return ZeroHash, fmt.Errorf("unable to resolve tag's target ID: %w", err)
1✔
95
        }
1✔
96

97
        hash, err := NewHash(targetID)
1✔
98
        if err != nil {
1✔
99
                return ZeroHash, fmt.Errorf("invalid format for target ID: %w", err)
×
100
        }
×
101

102
        return hash, nil
1✔
103
}
104

105
// verifyTagSignature verifies a signature for the specified tag using the
106
// provided public key.
107
func (r *Repository) verifyTagSignature(ctx context.Context, tagID Hash, key *signerverifier.SSLibKey) error {
13✔
108
        goGitRepo, err := r.GetGoGitRepository()
13✔
109
        if err != nil {
13✔
110
                return fmt.Errorf("error opening repository: %w", err)
×
111
        }
×
112

113
        tag, err := goGitRepo.TagObject(plumbing.NewHash(tagID.String()))
13✔
114
        if err != nil {
13✔
NEW
115
                return fmt.Errorf("unable to load tag object: %w", err)
×
NEW
116
        }
×
117

118
        tagContents, err := getTagBytesWithoutSignature(tag)
13✔
119
        if err != nil {
13✔
NEW
120
                return fmt.Errorf("unable to encode tag contents for verification: %w", err)
×
NEW
121
        }
×
122

123
        // Git appends tag signatures to the tag payload regardless of the object
124
        // format, so the signature is always in the Signature field (unlike
125
        // commits, where the header depends on the hash algorithm).
126
        tagSignature := tag.Signature
13✔
127

13✔
128
        if signatureBlockCount(tagSignature) > 1 {
13✔
NEW
129
                return errors.Join(ErrIncorrectVerificationKey, ErrMultipleSignatures)
×
UNCOV
130
        }
×
131

132
        switch key.KeyType {
13✔
133
        case gpg.KeyType:
3✔
134
                verifier, err := gpg.NewVerifierFromKey(key)
3✔
135
                if err != nil {
3✔
NEW
136
                        return errors.Join(ErrIncorrectVerificationKey, err)
×
NEW
137
                }
×
138
                if err := verifier.Verify(ctx, tagContents, []byte(tagSignature)); err != nil {
4✔
139
                        return ErrIncorrectVerificationKey
1✔
140
                }
1✔
141

142
                return nil
2✔
143
        case ssh.KeyType:
8✔
144
                if err := verifySSHKeySignature(ctx, key, tagContents, []byte(tagSignature)); err != nil {
9✔
145
                        return errors.Join(ErrIncorrectVerificationKey, err)
1✔
146
                }
1✔
147

148
                return nil
7✔
149
        case sigstore.KeyType:
×
NEW
150
                if err := verifyGitsignSignature(ctx, r, key, tagContents, []byte(tagSignature)); err != nil {
×
151
                        return errors.Join(ErrIncorrectVerificationKey, err)
×
152
                }
×
153

154
                return nil
×
155
        }
156

157
        return ErrUnknownSigningMethod
2✔
158
}
159

160
func (r *Repository) ensureIsTag(tagID Hash) error {
4✔
161
        objType, err := r.executor("cat-file", "-t", tagID.String()).executeString()
4✔
162
        if err != nil {
5✔
163
                return fmt.Errorf("unable to inspect if object is tag: %w", err)
1✔
164
        } else if objType != "tag" {
6✔
165
                return fmt.Errorf("requested Git ID '%s' is not a tag object", tagID.String())
2✔
166
        }
2✔
167

168
        return nil
1✔
169
}
170

171
func getTagBytesWithoutSignature(tag *object.Tag) ([]byte, error) {
25✔
172
        tagEncoded := memory.NewStorage().NewEncodedObject()
25✔
173
        if err := tag.EncodeWithoutSignature(tagEncoded); err != nil {
25✔
174
                return nil, err
×
175
        }
×
176
        r, err := tagEncoded.Reader()
25✔
177
        if err != nil {
25✔
178
                return nil, err
×
179
        }
×
180

181
        return io.ReadAll(r)
25✔
182
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc