Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gitsign verify #262

Merged
merged 1 commit into from Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/e2e.yaml
Expand Up @@ -81,6 +81,7 @@ jobs:

# Verify commit
git verify-commit HEAD
gitsign verify --certificate-github-workflow-repository=${{ github.repository }} --certificate-github-workflow-sha=${{ github.sha }} --certificate-oidc-issuer="https://token.actions.githubusercontent.com"

# Extra debug info
git cat-file commit HEAD | sed -n '/BEGIN/, /END/p' | sed 's/^ //g' | sed 's/gpgsig //g' | sed 's/SIGNED MESSAGE/PKCS7/g' | openssl pkcs7 -print -print_certs -text
Expand All @@ -105,6 +106,7 @@ jobs:

# Verify commit
git verify-commit HEAD
gitsign verify --certificate-github-workflow-repository=${{ github.repository }} --certificate-github-workflow-sha=${{ github.sha }} --certificate-oidc-issuer="https://token.actions.githubusercontent.com"

# Extra debug info
git cat-file commit HEAD | sed -n '/BEGIN/, /END/p' | sed 's/^ //g' | sed 's/gpgsig //g' | sed 's/SIGNED MESSAGE/PKCS7/g' | openssl pkcs7 -print -print_certs -text
Expand Down
4 changes: 2 additions & 2 deletions .gitignore
Expand Up @@ -2,7 +2,7 @@
*.swp
dist/*
.vscode/*
gitsign
gitsign-credential-cache
/gitsign
wlynch marked this conversation as resolved.
Show resolved Hide resolved
/gitsign-credential-cache
!/cmd/gitsign-credential-cache
vendor
47 changes: 31 additions & 16 deletions README.md
Expand Up @@ -118,17 +118,6 @@ https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&...
This will redirect you through the Sigstore Keyless flow to authenticate and
sign the commit.

Commits can then be verified using `git verify-commit`:

```sh
$ git verify-commit HEAD
tlog index: 2801760
gitsign: Signature made using certificate ID 0xf805288664f2e851dcb34e6a03b1a5232eb574ae | CN=sigstore-intermediate,O=sigstore.dev
gitsign: Good signature from [billy@chainguard.dev]
Validated Git signature: true
Validated Rekor entry: true
```

### Signing Tags

Once configured, you can sign commits as usual with `git tag -s` (or
Expand All @@ -143,15 +132,41 @@ https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&...
This will redirect you through the Sigstore Keyless flow to authenticate and
sign the tag.

Tags can then be verified using `git verify-tag`:
### Verifying commits

Commits can be verified using `gitsign verify`:

```sh
$ git verify-tag v0.0.1
tlog index: 2802961
gitsign: Signature made using certificate ID 0xe56a5a962ed59f9e3730d6696137eceb8b4ee8ea | CN=sigstore-intermediate,O=sigstore.dev
gitsign: Good signature from [billy@chainguard.dev]
$ gitsign verify HEAD
tlog index: 16072348
gitsign: Signature made using certificate ID 0xa6c178d9292f70eb5c4ad9e274ead0158e75e484 | CN=sigstore-intermediate,O=sigstore.dev
gitsign: Good signature from [billy@chainguard.dev](https://accounts.google.com)
Validated Git signature: true
Validated Rekor entry: true
Validated Certificate claims: true
```

`HEAD` may be replaced with any
[Git revision](https://git-scm.com/docs/gitrevisions) (e.g. branch, tag, etc.).

**NOTE**: `gitsign verify` is preferred over
[`git verify-commit`](https://git-scm.com/docs/git-verify-commit) and
[`git verify-tag`](https://git-scm.com/docs/git-verify-tag). The git commands
do not pass through any expected identity information to the signing tools, so
they only verify cryptographic integrity and that the data exists on Rekor, but
not **who** put the data there.

Using these commands will still work, but a warning being displayed.

```sh
$ git verify-commit HEAD
tlog index: 16072349
gitsign: Signature made using certificate ID 0xa6c178d9292f70eb5c4ad9e274ead0158e75e484 | CN=sigstore-intermediate,O=sigstore.dev
gitsign: Good signature from [billy@chainguard.dev](https://accounts.google.com)
Validated Git signature: true
Validated Rekor entry: true
Validated Certificate claims: false
WARNING: git verify-commit does not verify cert claims. Prefer using `gitsign verify` instead.
```

### Private Sigstore
Expand Down
1 change: 1 addition & 0 deletions docs/cli/gitsign.md
Expand Up @@ -24,5 +24,6 @@ gitsign [flags]

* [gitsign attest](gitsign_attest.md) - add attestations to Git objects
* [gitsign show](gitsign_show.md) - Show source predicate information
* [gitsign verify](gitsign_verify.md) - Verify a commit
* [gitsign version](gitsign_version.md) - print Gitsign version

39 changes: 39 additions & 0 deletions docs/cli/gitsign_verify.md
@@ -0,0 +1,39 @@
## gitsign verify

Verify a commit

### Synopsis

Verify a commit.

verify verifies a commit against a set of certificate claims.
This should generally be used over git verify-commit, since verify will
check the identity included in the signature's certificate.

If no revision is specified, HEAD is used.

```
gitsign verify [commit] [flags]
```

### Options

```
--certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow.
--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.
--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
--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.
--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
--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.
--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.
--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.
--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.
-h, --help help for verify
--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
--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.
```

### SEE ALSO

* [gitsign](gitsign.md) - Keyless Git signing with Sigstore!

40 changes: 40 additions & 0 deletions internal/cert/verify.go
@@ -0,0 +1,40 @@
// Copyright 2023 The Sigstore Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cert

import (
"crypto/x509"

"github.com/sigstore/cosign/v2/pkg/cosign"
)

// Verifier verifies a given cert for a set of claims.
type Verifier interface {
Verify(cert *x509.Certificate) error
}

// CosignVerifier borrows its certificate verification logic from cosign.
type CosignVerifier struct {
opts *cosign.CheckOpts
}

func NewCosignVerifier(opts *cosign.CheckOpts) *CosignVerifier {
return &CosignVerifier{opts: opts}
}

func (v *CosignVerifier) Verify(cert *x509.Certificate) error {
_, err := cosign.ValidateAndUnpackCert(cert, v.opts)
return err
}
2 changes: 2 additions & 0 deletions internal/commands/root/root.go
Expand Up @@ -20,6 +20,7 @@ import (

"github.com/sigstore/gitsign/internal/commands/attest"
"github.com/sigstore/gitsign/internal/commands/show"
"github.com/sigstore/gitsign/internal/commands/verify"
"github.com/sigstore/gitsign/internal/commands/version"
"github.com/sigstore/gitsign/internal/config"
"github.com/sigstore/gitsign/internal/io"
Expand Down Expand Up @@ -88,6 +89,7 @@ func New(cfg *config.Config) *cobra.Command {
rootCmd.AddCommand(version.New(cfg))
rootCmd.AddCommand(show.New(cfg))
rootCmd.AddCommand(attest.New(cfg))
rootCmd.AddCommand(verify.New(cfg))
o.AddFlags(rootCmd)

return rootCmd
Expand Down
59 changes: 8 additions & 51 deletions internal/commands/root/verify.go
Expand Up @@ -18,19 +18,15 @@ package root
import (
"bytes"
"context"
"crypto/x509"
"errors"
"fmt"
"io"
"os"

"github.com/sigstore/gitsign/internal"
"github.com/sigstore/gitsign/internal/fulcio/fulcioroots"
"github.com/sigstore/gitsign/internal/commands/verify"
"github.com/sigstore/gitsign/internal/gitsign"
"github.com/sigstore/gitsign/internal/gpg"
gsio "github.com/sigstore/gitsign/internal/io"
"github.com/sigstore/gitsign/internal/rekor"
"github.com/sigstore/gitsign/pkg/git"
"github.com/sigstore/sigstore/pkg/cryptoutils"
)

// commandSign implements gitsign commit verification.
Expand Down Expand Up @@ -70,44 +66,15 @@ func commandVerify(o *options, s *gsio.Streams, args ...string) error {
return fmt.Errorf("failed to read signature data (detached: %T): %w", detached, err)
}

root, intermediate, err := fulcioroots.NewFromConfig(ctx, o.Config)
v, err := gitsign.NewVerifierWithCosignOpts(ctx, o.Config, nil)
if err != nil {
return fmt.Errorf("error getting certificate root: %w", err)
return err
}

tsa, err := x509.SystemCertPool()
if err != nil {
return fmt.Errorf("error getting system root pool: %w", err)
}
if path := o.Config.TimestampCert; path != "" {
f, err := os.Open(path)
if err != nil {
return err
}
cert, err := cryptoutils.LoadCertificatesFromPEM(f)
if err != nil {
return fmt.Errorf("error loading certs from %s: %w", path, err)
}
for _, c := range cert {
tsa.AddCert(c)
}
}

cv, err := git.NewCertVerifier(
git.WithRootPool(root),
git.WithIntermediatePool(intermediate),
git.WithTimestampCertPool(tsa),
)
if err != nil {
return fmt.Errorf("error creating git cert verifier: %w", err)
}

rekor, err := rekor.NewClient(o.Config.Rekor)
summary, err := v.Verify(ctx, data, sig, true)
if err != nil {
return fmt.Errorf("failed to create rekor client: %w", err)
return err
}

summary, err := git.Verify(ctx, cv, rekor, data, sig, detached)
if err != nil {
if summary != nil && summary.Cert != nil {
gpgout.EmitBadSig(summary.Cert)
Expand All @@ -118,20 +85,10 @@ func commandVerify(o *options, s *gsio.Streams, args ...string) error {
return fmt.Errorf("failed to verify signature: %w", err)
}

fpr := internal.CertHexFingerprint(summary.Cert)
verify.PrintSummary(s.Err, summary)
fmt.Fprintln(s.Err, "WARNING: git verify-commit does not verify cert claims. Prefer using `gitsign verify` instead.")

fmt.Fprintln(s.Err, "tlog index:", *summary.LogEntry.LogIndex)
fmt.Fprintf(s.Err, "gitsign: Signature made using certificate ID 0x%s | %v\n", fpr, summary.Cert.Issuer)
gpgout.EmitGoodSig(summary.Cert)

// TODO: Maybe split up signature checking and certificate checking so we can
// output something more meaningful.
fmt.Fprintf(s.Err, "gitsign: Good signature from %v\n", summary.Cert.EmailAddresses)

for _, c := range summary.Claims {
fmt.Fprintf(s.Err, "%s: %t\n", string(c.Key), c.Value)
}

gpgout.EmitTrustFully()

return nil
Expand Down