generated from sigstore/sigstore-project-template
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This adds a new subcommand that mirrors cosign verify for certificate claim verification. Previously we relied on `git verify-commit` for commit verification. While this did check that the signature was valid and it exists in rekor, it did not check whether the identity was what was expected, because Git does not give controls over this via the commit signature interface. This command provides this functionality. Also adds a warning to the output of the `git verify-commit` output warning users that that verification mechanism may not be complete. Signed-off-by: Billy Lynch <billy@chainguard.dev>
- Loading branch information
Showing
12 changed files
with
564 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
## gitsign verify | ||
|
||
Show source predicate information | ||
|
||
### Synopsis | ||
|
||
Show source predicate information | ||
|
||
Prints an in-toto style predicate for the specified revision. | ||
If no revision is specified, HEAD is used. | ||
|
||
This command is experimental, and its CLI surface may change. | ||
|
||
``` | ||
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! | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
// 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 verify | ||
|
||
import ( | ||
"context" | ||
"encoding/pem" | ||
"fmt" | ||
"io" | ||
"os" | ||
|
||
gogit "github.com/go-git/go-git/v5" | ||
"github.com/go-git/go-git/v5/plumbing" | ||
cosignopts "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" | ||
"github.com/sigstore/cosign/v2/pkg/cosign" | ||
"github.com/sigstore/gitsign/internal" | ||
"github.com/sigstore/gitsign/internal/config" | ||
"github.com/sigstore/gitsign/internal/gitsign" | ||
"github.com/sigstore/gitsign/pkg/git" | ||
"github.com/sigstore/sigstore/pkg/cryptoutils" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
type options struct { | ||
Config *config.Config | ||
cosignopts.CertVerifyOptions | ||
} | ||
|
||
func (o *options) AddFlags(cmd *cobra.Command) { | ||
o.CertVerifyOptions.AddFlags(cmd) | ||
} | ||
|
||
func (o *options) Run(w io.Writer, args []string) error { | ||
ctx := context.Background() | ||
repo, err := gogit.PlainOpenWithOptions(".", &gogit.PlainOpenOptions{ | ||
DetectDotGit: true, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
revision := "HEAD" | ||
if len(args) > 0 { | ||
revision = args[0] | ||
} | ||
|
||
h, err := repo.ResolveRevision(plumbing.Revision(revision)) | ||
if err != nil { | ||
return fmt.Errorf("error resolving commit object: %w", err) | ||
} | ||
c, err := repo.CommitObject(*h) | ||
if err != nil { | ||
return fmt.Errorf("error reading commit object: %w", err) | ||
} | ||
|
||
sig := []byte(c.PGPSignature) | ||
p, _ := pem.Decode(sig) | ||
if p == nil || p.Type != "SIGNED MESSAGE" { | ||
return fmt.Errorf("unsupported signature type") | ||
} | ||
|
||
c2 := new(plumbing.MemoryObject) | ||
if err := c.EncodeWithoutSignature(c2); err != nil { | ||
return err | ||
} | ||
r, err := c2.Reader() | ||
if err != nil { | ||
return err | ||
} | ||
defer r.Close() | ||
data, err := io.ReadAll(r) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
v, err := gitsign.NewVerifierWithCosignOpts(ctx, o.Config, &o.CertVerifyOptions) | ||
if err != nil { | ||
return err | ||
} | ||
summary, err := v.Verify(ctx, data, sig, true) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
PrintSummary(os.Stdout, summary) | ||
|
||
return nil | ||
} | ||
|
||
func PrintSummary(w io.Writer, summary *git.VerificationSummary) { | ||
fpr := internal.CertHexFingerprint(summary.Cert) | ||
|
||
fmt.Fprintln(w, "tlog index:", *summary.LogEntry.LogIndex) | ||
fmt.Fprintf(w, "gitsign: Signature made using certificate ID 0x%s | %v\n", fpr, summary.Cert.Issuer) | ||
|
||
ce := cosign.CertExtensions{Cert: summary.Cert} | ||
fmt.Fprintf(w, "gitsign: Good signature from %v(%s)\n", cryptoutils.GetSubjectAlternateNames(summary.Cert), ce.GetIssuer()) | ||
|
||
for _, c := range summary.Claims { | ||
fmt.Fprintf(w, "%s: %t\n", string(c.Key), c.Value) | ||
} | ||
} | ||
|
||
func New(cfg *config.Config) *cobra.Command { | ||
o := &options{Config: cfg} | ||
|
||
cmd := &cobra.Command{ | ||
Use: "verify [commit]", | ||
SilenceUsage: true, | ||
Short: "Show source predicate information", | ||
Long: `Show source predicate information | ||
Prints an in-toto style predicate for the specified revision. | ||
If no revision is specified, HEAD is used. | ||
This command is experimental, and its CLI surface may change.`, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
// Simulate unknown flag errors. | ||
if o.Cert != "" { | ||
return fmt.Errorf("unknown flag: --certificate") | ||
} | ||
if o.CertChain != "" { | ||
return fmt.Errorf("unknown flag: --certificate-chain") | ||
} | ||
|
||
return o.Run(os.Stdout, args) | ||
}, | ||
} | ||
o.AddFlags(cmd) | ||
|
||
// Hide flags we don't implement. | ||
// --certificate: The cert should always come from the commit. | ||
_ = cmd.Flags().MarkHidden("certificate") | ||
// --certificate-chain: We only support reading from a TUF root at the moment. | ||
// TODO: add support for this. | ||
_ = cmd.Flags().MarkHidden("certificate-chain") | ||
|
||
return cmd | ||
} |
Oops, something went wrong.