Skip to content

Commit b17948d

Browse files
authored
Add verify-tag command for Git tag signature verification (#659)
This commit adds a new 'verify-tag' command to gitsign that allows users to verify signatures on Git tags. Previously, gitsign only supported verification of commit signatures through the 'verify' command. The verify-tag command follows the same verification process as the commit verification, but operates on tag objects instead. It supports all the same identity verification options as the verify command. Resolves: #658 Signed-off-by: haya14busa <haya14busa@gmail.com>
1 parent 5192110 commit b17948d

File tree

5 files changed

+222
-7
lines changed

5 files changed

+222
-7
lines changed

docs/cli/gitsign.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ gitsign [flags]
2626
* [gitsign initialize](gitsign_initialize.md) - Initializes Sigstore root to retrieve trusted certificate and key targets for verification.
2727
* [gitsign show](gitsign_show.md) - Show source predicate information
2828
* [gitsign verify](gitsign_verify.md) - Verify a commit
29+
* [gitsign verify-tag](gitsign_verify-tag.md) - Verify a tag
2930
* [gitsign version](gitsign_version.md) - print Gitsign version
3031

docs/cli/gitsign_verify-tag.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
## gitsign verify-tag
2+
3+
Verify a tag
4+
5+
### Synopsis
6+
7+
Verify a tag.
8+
9+
verify-tag verifies a tag against a set of certificate claims.
10+
This should generally be used over git verify-tag, since verify-tag will
11+
check the identity included in the signature's certificate.
12+
13+
```
14+
gitsign verify-tag <tag> [flags]
15+
```
16+
17+
### Options
18+
19+
```
20+
--certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow.
21+
--certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon.
22+
--certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon
23+
--certificate-github-workflow-sha string contains the sha claim from the GitHub OIDC Identity token that contains the commit SHA that the workflow run was based upon.
24+
--certificate-github-workflow-trigger string contains the event_name claim from the GitHub OIDC Identity token that contains the name of the event that triggered the workflow run
25+
--certificate-identity string The identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows.
26+
--certificate-identity-regexp string A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows.
27+
--certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows.
28+
--certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows.
29+
-h, --help help for verify-tag
30+
--insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log
31+
--sct string path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. If a certificate contains an SCT, verification will check both the detached and embedded SCTs.
32+
```
33+
34+
### SEE ALSO
35+
36+
* [gitsign](gitsign.md) - Keyless Git signing with Sigstore!
37+

docs/verification.md

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
### How we sign
66

77
In offline Rekor storage mode Gitsign will store a HashedRekord in Rekor
8-
corresponding to the commit content.
8+
corresponding to the commit or tag content.
99

1010
Unfortunately this is a bit complex to query manually. Roughly this is:
1111

@@ -19,19 +19,34 @@ signature) attributes.
1919

2020
### How we verify
2121

22+
For commits:
2223
1. Recompute and compare commit content checksum from commit.
2324
2. Get Rekor LogEntry from signature.
2425
3. Verify Certificate against commit content checksum (ignoring cert NotAfter time).
2526
4. (if present) Verify signature against TSA cert.
2627
5. Verify Rekor LogEntry inclusion (offline).
2728

28-
### What's stored in the commit signature
29+
For tags:
30+
1. Recompute and compare tag content checksum from tag.
31+
2. Get Rekor LogEntry from signature.
32+
3. Verify Certificate against tag content checksum (ignoring cert NotAfter time).
33+
4. (if present) Verify signature against TSA cert.
34+
5. Verify Rekor LogEntry inclusion (offline).
2935

36+
### What's stored in the signature
37+
38+
For commits:
3039
- Commit content checksum (sha256)
3140
- Commit signing time (untrusted system time)
3241
- Protobuf encoded [Rekor TransparencyLogEntry](https://github.com/sigstore/protobuf-specs/blob/91485b44360d343dadd98fb7297a500f05e0b5b1/protos/sigstore_rekor.proto#L91)
3342
- (optional) TSA signature + cert
3443

44+
For tags:
45+
- Tag content checksum (sha256)
46+
- Tag signing time (untrusted system time)
47+
- Protobuf encoded [Rekor TransparencyLogEntry](https://github.com/sigstore/protobuf-specs/blob/91485b44360d343dadd98fb7297a500f05e0b5b1/protos/sigstore_rekor.proto#L91)
48+
- (optional) TSA signature + cert
49+
3550
Sample encoded TransparencyLogEntry:
3651

3752
```
@@ -108,13 +123,18 @@ commit signature.
108123

109124
### What's stored in Rekor
110125

111-
HashedRekord containing:
112-
126+
For commits, a HashedRekord containing:
113127
- Commit content checksum
114128
- Fulcio certificate
115129
- Public Key
116130
- [Signer Identity info](https://github.com/sigstore/fulcio/blob/main/docs/oidc.md)
117131

132+
For tags, a HashedRekord containing:
133+
- Tag content checksum
134+
- Fulcio certificate
135+
- Public Key
136+
- [Signer Identity info](https://github.com/sigstore/fulcio/blob/main/docs/oidc.md)
137+
118138
## Online Verification
119139

120140
Note: Gitsign is in the process of migrating clients to offline verification, but this section
@@ -248,17 +268,28 @@ Note that for Git tags, the annotated tag object SHA is what is used (i.e. the
248268
output of `git rev-parse <tag>`), **not** the SHA of the underlying tagged
249269
commit.
250270

251-
### What's stored in the commit signature
271+
### What's stored in the signature
252272

273+
For commits:
253274
- Commit content checksum (sha256)
254275
- Commit signing time (untrusted system time)
255276
- (optional) TSA signature + cert
256277

257-
### What's stored in Rekor
278+
For tags:
279+
- Tag content checksum (sha256)
280+
- Tag signing time (untrusted system time)
281+
- (optional) TSA signature + cert
258282

259-
HashedRekord containing:
283+
### What's stored in Rekor
260284

285+
For commits, a HashedRekord containing:
261286
- Commit SHA checksum
287+
- Fulcio certificate
288+
- Public Key
289+
- [Signer Identity info](https://github.com/sigstore/fulcio/blob/main/docs/oidc.md)
290+
291+
For tags, a HashedRekord containing:
292+
- Tag SHA checksum
262293
- Fulcio certificate
263294
- Public Key
264295
- [Signer Identity info](https://github.com/sigstore/fulcio/blob/main/docs/oidc.md)

internal/commands/root/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/sigstore/gitsign/internal/commands/initialize"
2323
"github.com/sigstore/gitsign/internal/commands/show"
2424
"github.com/sigstore/gitsign/internal/commands/verify"
25+
verifytag "github.com/sigstore/gitsign/internal/commands/verify-tag"
2526
"github.com/sigstore/gitsign/internal/commands/version"
2627
"github.com/sigstore/gitsign/internal/config"
2728
"github.com/sigstore/gitsign/internal/io"
@@ -92,6 +93,7 @@ func New(cfg *config.Config) *cobra.Command {
9293
rootCmd.AddCommand(show.New(cfg))
9394
rootCmd.AddCommand(attest.New(cfg))
9495
rootCmd.AddCommand(verify.New(cfg))
96+
rootCmd.AddCommand(verifytag.New(cfg))
9597
rootCmd.AddCommand(initialize.New())
9698
o.AddFlags(rootCmd)
9799

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright 2023 The Sigstore Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package verifytag
16+
17+
import (
18+
"context"
19+
"encoding/pem"
20+
"fmt"
21+
"io"
22+
"os"
23+
24+
gogit "github.com/go-git/go-git/v5"
25+
"github.com/go-git/go-git/v5/plumbing"
26+
cosignopts "github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
27+
"github.com/sigstore/gitsign/internal/commands/verify"
28+
"github.com/sigstore/gitsign/internal/config"
29+
"github.com/sigstore/gitsign/internal/gitsign"
30+
"github.com/spf13/cobra"
31+
)
32+
33+
type options struct {
34+
Config *config.Config
35+
cosignopts.CertVerifyOptions
36+
}
37+
38+
func (o *options) AddFlags(cmd *cobra.Command) {
39+
o.CertVerifyOptions.AddFlags(cmd)
40+
}
41+
42+
func (o *options) Run(_ io.Writer, args []string) error {
43+
ctx := context.Background()
44+
repo, err := gogit.PlainOpenWithOptions(".", &gogit.PlainOpenOptions{
45+
DetectDotGit: true,
46+
})
47+
if err != nil {
48+
return err
49+
}
50+
51+
if len(args) == 0 {
52+
return fmt.Errorf("tag reference is required")
53+
}
54+
tagRef := args[0]
55+
56+
// Resolve the tag reference
57+
ref, err := repo.Reference(plumbing.ReferenceName(fmt.Sprintf("refs/tags/%s", tagRef)), true)
58+
if err != nil {
59+
return fmt.Errorf("error resolving tag reference: %w", err)
60+
}
61+
62+
// Get the tag object
63+
tagObj, err := repo.TagObject(ref.Hash())
64+
if err != nil {
65+
return fmt.Errorf("error reading tag object: %w", err)
66+
}
67+
68+
// Extract the signature
69+
sig := []byte(tagObj.PGPSignature)
70+
p, _ := pem.Decode(sig)
71+
if p == nil || p.Type != "SIGNED MESSAGE" {
72+
return fmt.Errorf("unsupported signature type")
73+
}
74+
75+
// Get the tag data without the signature
76+
tagData := new(plumbing.MemoryObject)
77+
if err := tagObj.EncodeWithoutSignature(tagData); err != nil {
78+
return err
79+
}
80+
r, err := tagData.Reader()
81+
if err != nil {
82+
return err
83+
}
84+
defer r.Close()
85+
data, err := io.ReadAll(r)
86+
if err != nil {
87+
return err
88+
}
89+
90+
// Verify the signature
91+
v, err := gitsign.NewVerifierWithCosignOpts(ctx, o.Config, &o.CertVerifyOptions)
92+
if err != nil {
93+
return err
94+
}
95+
summary, err := v.Verify(ctx, data, sig, true)
96+
if err != nil {
97+
return err
98+
}
99+
100+
// Import the internal package just for the PrintSummary function
101+
verify.PrintSummary(os.Stdout, summary)
102+
103+
return nil
104+
}
105+
106+
func New(cfg *config.Config) *cobra.Command {
107+
o := &options{Config: cfg}
108+
109+
cmd := &cobra.Command{
110+
Use: "verify-tag <tag>",
111+
Args: cobra.ExactArgs(1),
112+
SilenceUsage: true,
113+
Short: "Verify a tag",
114+
Long: `Verify a tag.
115+
116+
verify-tag verifies a tag against a set of certificate claims.
117+
This should generally be used over git verify-tag, since verify-tag will
118+
check the identity included in the signature's certificate.`,
119+
RunE: func(_ *cobra.Command, args []string) error {
120+
// Simulate unknown flag errors.
121+
if o.Cert != "" {
122+
return fmt.Errorf("unknown flag: --certificate")
123+
}
124+
if o.CertChain != "" {
125+
return fmt.Errorf("unknown flag: --certificate-chain")
126+
}
127+
128+
return o.Run(os.Stdout, args)
129+
},
130+
}
131+
o.AddFlags(cmd)
132+
133+
// Hide flags we don't implement.
134+
// --certificate: The cert should always come from the tag.
135+
_ = cmd.Flags().MarkHidden("certificate")
136+
// --certificate-chain: We only support reading from a TUF root at the moment.
137+
// TODO: add support for this.
138+
_ = cmd.Flags().MarkHidden("certificate-chain")
139+
// --ca-intermediates and --ca-roots
140+
_ = cmd.Flags().MarkHidden("ca-intermediates")
141+
_ = cmd.Flags().MarkHidden("ca-roots")
142+
143+
return cmd
144+
}

0 commit comments

Comments
 (0)