From 501add44c0701aa92db4f41141602b73c929d79a Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Mon, 4 Mar 2024 08:25:17 -0700 Subject: [PATCH] add additional packages to support verify command Signed-off-by: Meredith Lancaster --- .../{github => api}/attestation.go | 2 +- pkg/cmd/attestation/api/client.go | 98 ++++++++++ pkg/cmd/attestation/api/client_test.go | 176 +++++++++++++++++ pkg/cmd/attestation/api/mock-client.go | 71 +++++++ .../attestation/api/mock_apiClient_test.go | 152 +++++++++++++++ pkg/cmd/attestation/artifact/artifact.go | 7 +- pkg/cmd/attestation/attestation.go | 5 +- pkg/cmd/attestation/github/client.go | 19 -- pkg/cmd/attestation/logger/logger.go | 74 ++++++- .../test/data/sigstore-js-2.1.0-bundle.json | 61 ++++++ .../test/data/sigstore-js-2.1.0.tgz | Bin 0 -> 37137 bytes .../sigstore-js-2.1.0_with_2_bundles.jsonl | 2 + ...eBundle-invalid-mismatched-signatures.json | 48 +++++ .../sigstoreBundle-invalid-signature.json | 48 +++++ pkg/cmd/attestation/test/output.go | 16 ++ .../attestation/verification/attestation.go | 112 +++++++++++ .../verification/attestation_test.go | 49 +++++ .../embed/tuf-repo.github.com/root.json | 163 ++++++++++++++++ pkg/cmd/attestation/verification/policy.go | 10 +- .../attestation/verification/policy_test.go | 27 --- pkg/cmd/attestation/verification/sigstore.go | 22 ++- .../attestation/verification/sigstore_test.go | 95 ++++----- pkg/cmd/attestation/verification/tuf.go | 55 ++++++ pkg/cmd/attestation/verification/tuf_test.go | 18 ++ pkg/cmd/attestation/verify/options.go | 40 +--- pkg/cmd/attestation/verify/options_test.go | 36 +--- pkg/cmd/attestation/verify/policy.go | 24 +-- pkg/cmd/attestation/verify/policy_test.go | 29 +++ pkg/cmd/attestation/verify/verify.go | 44 +++-- pkg/cmd/attestation/verify/verify_test.go | 182 ++++++++++++++++++ pkg/cmd/root/root.go | 2 +- 31 files changed, 1477 insertions(+), 210 deletions(-) rename pkg/cmd/attestation/{github => api}/attestation.go (97%) create mode 100644 pkg/cmd/attestation/api/client.go create mode 100644 pkg/cmd/attestation/api/client_test.go create mode 100644 pkg/cmd/attestation/api/mock-client.go create mode 100644 pkg/cmd/attestation/api/mock_apiClient_test.go delete mode 100644 pkg/cmd/attestation/github/client.go create mode 100644 pkg/cmd/attestation/test/data/sigstore-js-2.1.0-bundle.json create mode 100644 pkg/cmd/attestation/test/data/sigstore-js-2.1.0.tgz create mode 100644 pkg/cmd/attestation/test/data/sigstore-js-2.1.0_with_2_bundles.jsonl create mode 100644 pkg/cmd/attestation/test/data/sigstoreBundle-invalid-mismatched-signatures.json create mode 100644 pkg/cmd/attestation/test/data/sigstoreBundle-invalid-signature.json create mode 100644 pkg/cmd/attestation/test/output.go create mode 100644 pkg/cmd/attestation/verification/attestation.go create mode 100644 pkg/cmd/attestation/verification/attestation_test.go create mode 100644 pkg/cmd/attestation/verification/embed/tuf-repo.github.com/root.json delete mode 100644 pkg/cmd/attestation/verification/policy_test.go create mode 100644 pkg/cmd/attestation/verification/tuf.go create mode 100644 pkg/cmd/attestation/verification/tuf_test.go create mode 100644 pkg/cmd/attestation/verify/policy_test.go create mode 100644 pkg/cmd/attestation/verify/verify_test.go diff --git a/pkg/cmd/attestation/github/attestation.go b/pkg/cmd/attestation/api/attestation.go similarity index 97% rename from pkg/cmd/attestation/github/attestation.go rename to pkg/cmd/attestation/api/attestation.go index 4f48dd4ad..2b96a51fc 100644 --- a/pkg/cmd/attestation/github/attestation.go +++ b/pkg/cmd/attestation/api/attestation.go @@ -1,4 +1,4 @@ -package github +package api import ( "fmt" diff --git a/pkg/cmd/attestation/api/client.go b/pkg/cmd/attestation/api/client.go new file mode 100644 index 000000000..26d5d83c1 --- /dev/null +++ b/pkg/cmd/attestation/api/client.go @@ -0,0 +1,98 @@ +package api + +import ( + "fmt" + "io" + "net/http" + "strings" + + "github.com/cli/cli/v2/api" +) + +const ( + DefaultLimit = 30 + maxLimitForFlag = 1000 + maxLimitForFetch = 100 +) + +type apiClient interface { + REST(hostname, method, p string, body io.Reader, data interface{}) error + RESTWithNext(hostname, method, p string, body io.Reader, data interface{}) (string, error) +} + +type Client interface { + GetByRepoAndDigest(repo, digest string, limit int) ([]*Attestation, error) + GetByOwnerAndDigest(owner, digest string, limit int) ([]*Attestation, error) +} + +type LiveClient struct { + host string + api apiClient +} + +func NewLiveClient() *LiveClient { + liveAPIClient := api.NewClientFromHTTP(http.DefaultClient) + return &LiveClient{ + host: "https://api.github.com", + api: liveAPIClient, + } +} + +func (c *LiveClient) BuildRepoAndDigestURL(repo, digest string) string { + repo = strings.Trim(repo, "/") + return fmt.Sprintf(GetAttestationByRepoAndSubjectDigestPath, repo, digest) +} + +// GetByRepoAndDigest fetches the attestation by repo and digest +func (c *LiveClient) GetByRepoAndDigest(repo, digest string, limit int) ([]*Attestation, error) { + url := c.BuildRepoAndDigestURL(repo, digest) + return c.getAttestations(url, repo, digest, limit) +} + +func (c *LiveClient) BuildOwnerAndDigestURL(owner, digest string) string { + owner = strings.Trim(owner, "/") + return fmt.Sprintf(GetAttestationByOwnerAndSubjectDigestPath, owner, digest) +} + +// GetByOwnerAndDigest fetches attestation by owner and digest +func (c *LiveClient) GetByOwnerAndDigest(owner, digest string, limit int) ([]*Attestation, error) { + url := c.BuildOwnerAndDigestURL(owner, digest) + return c.getAttestations(url, owner, digest, limit) +} + +func (c *LiveClient) getAttestations(url, name, digest string, limit int) ([]*Attestation, error) { + perPage := limit + if perPage <= 0 || perPage > maxLimitForFlag { + return nil, fmt.Errorf("limit must be greater than 0 and less than or equal to %d", maxLimitForFlag) + } + + if perPage > maxLimitForFetch { + perPage = maxLimitForFetch + } + + // ref: https://github.com/cli/go-gh/blob/d32c104a9a25c9de3d7c7b07a43ae0091441c858/example_gh_test.go#L96 + url = fmt.Sprintf("%s?per_page=%d", url, perPage) + + var attestations []*Attestation + var resp AttestationsResponse + var err error + // if no attestation or less than limit, then keep fetching + for url != "" && len(attestations) < limit { + url, err = c.api.RESTWithNext(c.host, http.MethodGet, url, nil, resp) + if err != nil { + return nil, err + } + + attestations = append(attestations, resp.Attestations...) + } + + if len(resp.Attestations) == 0 { + return nil, newErrNoAttestations(name, digest) + } + + if len(attestations) > limit { + return attestations[:limit], nil + } + + return attestations, nil +} diff --git a/pkg/cmd/attestation/api/client_test.go b/pkg/cmd/attestation/api/client_test.go new file mode 100644 index 000000000..662f72f99 --- /dev/null +++ b/pkg/cmd/attestation/api/client_test.go @@ -0,0 +1,176 @@ +package api + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + testRepo = "github/example" + testOwner = "github" + testDigest = "sha256:12313213" +) + +func NewClientWithMockGHClient() Client { + fetcher := mockDataGenerator{ + NumAttestations: 5, + } + + return &LiveClient{ + api: mockAPIClient{ + OnREST: fetcher.OnRESTSuccess, + OnRESTWithNext: fetcher.OnRESTSuccessWithNextPage, + }, + } +} + +func NewClientWithMockGHClientWithNextPage() Client { + fetcher := mockDataGenerator{ + NumAttestations: 5, + } + + return &LiveClient{ + api: mockAPIClient{ + OnREST: fetcher.OnRESTSuccess, + OnRESTWithNext: fetcher.OnRESTSuccessWithNextPage, + }, + } +} + +func TestGetURL(t *testing.T) { + c := LiveClient{} + + testData := []struct { + repo string + digest string + expected string + }{ + {repo: "/github/example/", digest: "sha256:12313213", expected: "repos/github/example/attestations/sha256:12313213"}, + {repo: "/github/example", digest: "sha256:12313213", expected: "repos/github/example/attestations/sha256:12313213"}, + } + + for _, data := range testData { + s := c.BuildRepoAndDigestURL(data.repo, data.digest) + assert.Equal(t, data.expected, s) + } +} + +func TestGetByDigest(t *testing.T) { + c := NewClientWithMockGHClient() + attestations, err := c.GetByRepoAndDigest(testRepo, testDigest, DefaultLimit) + require.NoError(t, err) + + assert.Equal(t, len(attestations), 5) + bundle := (attestations)[0].Bundle + assert.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1") + + attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, DefaultLimit) + require.NoError(t, err) + + assert.Equal(t, len(attestations), 5) + bundle = (attestations)[0].Bundle + assert.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1") +} + +func TestGetByDigestGreaterThanLimit(t *testing.T) { + c := NewClientWithMockGHClient() + + limit := 3 + // The method should return five results when the limit is not set + attestations, err := c.GetByRepoAndDigest(testRepo, testDigest, limit) + require.NoError(t, err) + + assert.Equal(t, len(attestations), 3) + bundle := (attestations)[0].Bundle + assert.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1") + + attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, limit) + require.NoError(t, err) + + assert.Equal(t, len(attestations), limit) + bundle = (attestations)[0].Bundle + assert.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1") +} + +func TestGetByDigestWithNextPage(t *testing.T) { + c := NewClientWithMockGHClientWithNextPage() + attestations, err := c.GetByRepoAndDigest(testRepo, testDigest, DefaultLimit) + require.NoError(t, err) + + assert.Equal(t, len(attestations), 10) + bundle := (attestations)[0].Bundle + assert.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1") + + attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, DefaultLimit) + require.NoError(t, err) + + assert.Equal(t, len(attestations), 10) + bundle = (attestations)[0].Bundle + assert.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1") +} + +func TestGetByDigestGreaterThanLimitWithNextPage(t *testing.T) { + c := NewClientWithMockGHClientWithNextPage() + + limit := 7 + // The method should return five results when the limit is not set + attestations, err := c.GetByRepoAndDigest(testRepo, testDigest, limit) + require.NoError(t, err) + + assert.Equal(t, len(attestations), limit) + bundle := (attestations)[0].Bundle + assert.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1") + + attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, limit) + require.NoError(t, err) + + assert.Equal(t, len(attestations), limit) + bundle = (attestations)[0].Bundle + assert.Equal(t, bundle.GetMediaType(), "application/vnd.dev.sigstore.bundle+json;version=0.1") +} + +func TestGetByDigest_NoAttestationsFound(t *testing.T) { + fetcher := mockDataGenerator{ + NumAttestations: 5, + } + + c := LiveClient{ + api: mockAPIClient{ + OnREST: fetcher.OnRESTNoAttestations, + OnRESTWithNext: fetcher.OnRESTSuccessWithNextPage, + }, + } + + attestations, err := c.GetByRepoAndDigest(testRepo, testDigest, DefaultLimit) + assert.Error(t, err) + assert.IsType(t, ErrNoAttestations{}, err) + assert.Nil(t, attestations) + + attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, DefaultLimit) + assert.Error(t, err) + assert.IsType(t, ErrNoAttestations{}, err) + assert.Nil(t, attestations) +} + +func TestGetByDigest_Error(t *testing.T) { + fetcher := mockDataGenerator{ + NumAttestations: 5, + } + + c := LiveClient{ + api: mockAPIClient{ + OnREST: fetcher.OnRESTError, + OnRESTWithNext: fetcher.OnRESTSuccessWithNextPage, + }, + } + + attestations, err := c.GetByRepoAndDigest(testRepo, testDigest, DefaultLimit) + assert.Error(t, err) + assert.Nil(t, attestations) + + attestations, err = c.GetByOwnerAndDigest(testOwner, testDigest, DefaultLimit) + assert.Error(t, err) + assert.Nil(t, attestations) +} diff --git a/pkg/cmd/attestation/api/mock-client.go b/pkg/cmd/attestation/api/mock-client.go new file mode 100644 index 000000000..96a64e4fc --- /dev/null +++ b/pkg/cmd/attestation/api/mock-client.go @@ -0,0 +1,71 @@ +package api + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/sigstore/sigstore-go/pkg/bundle" +) + +type MockClient struct { + OnGetByRepoAndDigest func(repo, digest string, limit int) ([]*Attestation, error) + OnGetByOwnerAndDigest func(owner, digest string, limit int) ([]*Attestation, error) +} + +func (m MockClient) GetByRepoAndDigest(repo, digest string, limit int) ([]*Attestation, error) { + return m.OnGetByRepoAndDigest(repo, digest, limit) +} + +func (m MockClient) GetByOwnerAndDigest(owner, digest string, limit int) ([]*Attestation, error) { + return m.OnGetByOwnerAndDigest(owner, digest, limit) +} + +func makeTestAttestation() Attestation { + bundleBytes, err := os.ReadFile("../test/data/sigstore-js-2.1.0-bundle.json") + if err != nil { + panic(err) + } + + var b *bundle.ProtobufBundle + err = json.Unmarshal(bundleBytes, &b) + if err != nil { + panic(err) + } + + return Attestation{Bundle: b} +} + +func OnGetByRepoAndDigestSuccess(repo, digest string, limit int) ([]*Attestation, error) { + att1 := makeTestAttestation() + att2 := makeTestAttestation() + return []*Attestation{&att1, &att2}, nil +} + +func OnGetByRepoAndDigestFailure(repo, digest string, limit int) ([]*Attestation, error) { + return nil, fmt.Errorf("failed to fetch by repo and digest") +} + +func OnGetByOwnerAndDigestSuccess(owner, digest string, limit int) ([]*Attestation, error) { + att1 := makeTestAttestation() + att2 := makeTestAttestation() + return []*Attestation{&att1, &att2}, nil +} + +func OnGetByOwnerAndDigestFailure(owner, digest string, limit int) ([]*Attestation, error) { + return nil, fmt.Errorf("failed to fetch by owner and digest") +} + +func NewTestClient() *MockClient { + return &MockClient{ + OnGetByRepoAndDigest: OnGetByRepoAndDigestSuccess, + OnGetByOwnerAndDigest: OnGetByOwnerAndDigestSuccess, + } +} + +func NewFailTestClient() *MockClient { + return &MockClient{ + OnGetByRepoAndDigest: OnGetByRepoAndDigestFailure, + OnGetByOwnerAndDigest: OnGetByOwnerAndDigestFailure, + } +} diff --git a/pkg/cmd/attestation/api/mock_apiClient_test.go b/pkg/cmd/attestation/api/mock_apiClient_test.go new file mode 100644 index 000000000..08f557f05 --- /dev/null +++ b/pkg/cmd/attestation/api/mock_apiClient_test.go @@ -0,0 +1,152 @@ +package api + +import ( + "errors" + "fmt" + "io" + "strings" +) + +type mockAPIClient struct { + OnREST func(hostname, method, p string, body io.Reader, data interface{}) error + OnRESTWithNext func(hostname, method, p string, body io.Reader, data interface{}) (string, error) +} + +func (m mockAPIClient) REST(hostname, method, p string, body io.Reader, data interface{}) error { + return m.OnREST(hostname, method, p, body, data) +} + +func (m mockAPIClient) RESTWithNext(hostname, method, p string, body io.Reader, data interface{}) (string, error) { + return m.OnRESTWithNext(hostname, method, p, body, data) +} + +type mockDataGenerator struct { + NumAttestations int +} + +func (m mockDataGenerator) OnRESTSuccess(hostname, method, p string, body io.Reader, data interface{}) error { + return m.OnRESTSuccessHelper(hostname, method, p, body, data, false) +} + +func (m mockDataGenerator) OnRESTSuccessWithNextPage(hostname, method, p string, body io.Reader, data interface{}) (string, error) { + // if path doesn't contain after, it means first time hitting the mock server + // so return the first page and return the link header in the response + if !strings.Contains(p, "after") { + return m.OnRESTWithNextSuccessHelper(hostname, method, p, body, data, true) + } + + // if path contain after, it means second time hitting the mock server and will not return the link header + return m.OnRESTWithNextSuccessHelper(hostname, method, p, body, data, false) +} + +func (m mockDataGenerator) OnRESTSuccessHelper(hostname, method, p string, body io.Reader, data interface{}, hasNext bool) error { + atts := make([]*Attestation, m.NumAttestations) + for j := 0; j < m.NumAttestations; j++ { + att := makeTestAttestation() + atts[j] = &att + } + + var resp AttestationsResponse + resp.Attestations = atts + + data = resp + + // // Convert the attestations to JSON + // jsonResponse, err := json.Marshal(resp) + // if err != nil { + // return err + // } + + // // Create a buffer containing the JSON response + // responseReader := bytes.NewBuffer(jsonResponse) + + // linkHeader := "" + // if hasNext { + // // Create a link header with the next page + // linkHeader = fmt.Sprintf("<%s&after=2>; rel=\"next\"", p) + // } + + return nil +} + +func (m mockDataGenerator) OnRESTWithNextSuccessHelper(hostname, method, p string, body io.Reader, data interface{}, hasNext bool) (string, error) { + atts := make([]*Attestation, m.NumAttestations) + for j := 0; j < m.NumAttestations; j++ { + att := makeTestAttestation() + atts[j] = &att + } + + var resp AttestationsResponse + resp.Attestations = atts + + data = resp + + // // Convert the attestations to JSON + // jsonResponse, err := json.Marshal(resp) + // if err != nil { + // return err + // } + + // // Create a buffer containing the JSON response + // responseReader := bytes.NewBuffer(jsonResponse) + + // b, err := io.ReadAll(resp.Body) + // if err != nil { + // return "", err + // } + + // err = json.Unmarshal(b, &data) + // if err != nil { + // return "", err + // } + + + linkHeader := "" + if hasNext { + // Create a link header with the next page + linkHeader = fmt.Sprintf("<%s&after=2>; rel=\"next\"", p) + } + + return linkHeader, nil +} + +func (m mockDataGenerator) OnRESTNoAttestations(hostname, method, p string, body io.Reader, data interface{}) error { + var resp AttestationsResponse + resp.Attestations = make([]*Attestation, 0) + + data = resp + + // Convert the attestations to JSON + // jsonResponse, err := json.Marshal(resp) + // if err != nil { + // return err + // } + + // // Create a buffer containing the JSON response + // responseReader := bytes.NewBuffer(jsonResponse) + + return nil +} + +func (m mockDataGenerator) OnRESTWithNextNoAttestations(hostname, method, p string, body io.Reader, data interface{}) (string, error) { + var resp AttestationsResponse + resp.Attestations = make([]*Attestation, 0) + + data = resp + + // Convert the attestations to JSON + // data, err := json.Marshal(resp) + // if err != nil { + // return err + // } + + return "", nil +} + +func (m mockDataGenerator) OnRESTError(hostname, method, p string, body io.Reader, data interface{}) error { + return errors.New("failed to get attestations") +} + +func (m mockDataGenerator) OnRESTWithNextError(hostname, method, p string, body io.Reader, data interface{}) (string, error) { + return "", errors.New("failed to get attestations") +} diff --git a/pkg/cmd/attestation/artifact/artifact.go b/pkg/cmd/attestation/artifact/artifact.go index 90906da30..dd39eacd5 100644 --- a/pkg/cmd/attestation/artifact/artifact.go +++ b/pkg/cmd/attestation/artifact/artifact.go @@ -63,11 +63,16 @@ func NewDigestedArtifact(client oci.Client, reference, digestAlg string) (artifa return digestLocalFileArtifact(normalized, digestAlg) } -// Digest returns the digest of the artifact +// Digest returns the artifact's digest func (a *DigestedArtifact) Digest() string { return a.digest } +// Algorithm returns the artifact's algorithm +func (a *DigestedArtifact) Algorithm() string { + return a.digestAlg +} + // DigestWithAlg returns the digest:algorithm of the artifact func (a *DigestedArtifact) DigestWithAlg() string { return fmt.Sprintf("%s:%s", a.digestAlg, a.digest) diff --git a/pkg/cmd/attestation/attestation.go b/pkg/cmd/attestation/attestation.go index 7441d6105..a97026956 100644 --- a/pkg/cmd/attestation/attestation.go +++ b/pkg/cmd/attestation/attestation.go @@ -2,8 +2,9 @@ package attestation import ( "github.com/cli/cli/v2/pkg/cmd/attestation/verify" - "github.com/cli/cli/v2/pkg/iostreams" - + "github.com/cli/cli/v2/pkg/cmdutil" + + "github.com/MakeNowJust/heredoc" "github.com/spf13/cobra" ) diff --git a/pkg/cmd/attestation/github/client.go b/pkg/cmd/attestation/github/client.go deleted file mode 100644 index 13688a5f4..000000000 --- a/pkg/cmd/attestation/github/client.go +++ /dev/null @@ -1,19 +0,0 @@ -package github - -import "github.com/cli/cli/v2/api" - -type Client interface { - GetByRepoAndDigest(repo, digest string, limit int) ([]*Attestation, error) - GetByOwnerAndDigest(owner, digest string, limit int) ([]*Attestation, error) -} - -type LiveClient struct { - apiClient api.Client -} - -func NewLiveClient() (*LiveClient, error) { - apiClient := api.NewClientFromHTTP(httpClient) - return &LiveClient{ - apiClient: apiClient, - }, nil -} diff --git a/pkg/cmd/attestation/logger/logger.go b/pkg/cmd/attestation/logger/logger.go index 0e5beb62e..8fcd2511f 100644 --- a/pkg/cmd/attestation/logger/logger.go +++ b/pkg/cmd/attestation/logger/logger.go @@ -2,21 +2,89 @@ package logger import ( "fmt" + "log" "github.com/cli/cli/v2/pkg/iostreams" + "github.com/cli/go-gh/v2/pkg/tableprinter" ) type Logger struct { + ColorScheme *iostreams.ColorScheme IO *iostreams.IOStreams - Quiet bool - Verbose bool + quiet bool + verbose bool +} + +func NewLogger(isQuiet, isVerbose bool) *Logger { + io := iostreams.System() + colorScheme := io.ColorScheme() + return &Logger{ + ColorScheme: colorScheme, + IO: io, + quiet: isQuiet, + verbose: isVerbose, + } +} + +// NewDefaultLogger returns a Logger that with the default logging settings +func NewDefaultLogger() *Logger { + isQuiet := false + isVerbose := false + + return NewLogger(isQuiet, isVerbose) +} + +// Printf writes the formatted arguments to the stdout writer. +func (l *Logger) Printf(f string, v ...interface{}) (int, error) { + if l.quiet || !l.IO.IsStdoutTTY() { + return 0, nil + } + return fmt.Fprintf(l.IO.ErrOut, f, v...) +} + +// Println writes the arguments to the stdout writer with a newline at the end. +func (l *Logger) Println(v ...interface{}) (int, error) { + if l.quiet || !l.IO.IsStdoutTTY() { + return 0, nil + } + return fmt.Fprintln(l.IO.ErrOut, v...) } func (l *Logger) VerbosePrint(msg string) (int, error) { - if !l.verbose || !opts.IO.IsStdoutTTY() { + if !l.verbose || !l.IO.IsStdoutTTY() { return 0, nil } return fmt.Fprintf(l.IO.ErrOut, msg) } +func (l *Logger) VerbosePrintf(f string, v ...interface{}) (int, error) { + if !l.verbose || !l.IO.IsStdoutTTY() { + return 0, nil + } + + return fmt.Fprintf(l.IO.ErrOut, f, v...) +} + +func (l *Logger) PrintTableToStdOut(headers []string, rows [][]string) { + if rows == nil { + return + } + t := tableprinter.New(l.IO.Out, l.IO.IsStdoutTTY(), l.IO.TerminalWidth()) + + if headers != nil { + // Print the header row in green + t.AddHeader(headers, tableprinter.WithColor(l.ColorScheme.Green)) + } + + for _, row := range rows { + for _, field := range row { + t.AddField(field, tableprinter.WithTruncate(nil)) + } + t.EndRow() + } + + if err := t.Render(); err != nil { + log.Fatal(err) + } +} diff --git a/pkg/cmd/attestation/test/data/sigstore-js-2.1.0-bundle.json b/pkg/cmd/attestation/test/data/sigstore-js-2.1.0-bundle.json new file mode 100644 index 000000000..d91176e09 --- /dev/null +++ b/pkg/cmd/attestation/test/data/sigstore-js-2.1.0-bundle.json @@ -0,0 +1,61 @@ +{ + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "x509CertificateChain": { + "certificates": [ + { + "rawBytes": "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" + } + ] + }, + "tlogEntries": [ + { + "logIndex": "33351527", + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + }, + "kindVersion": { + "kind": "intoto", + "version": "0.0.2" + }, + "integratedTime": "1693323623", + "inclusionPromise": { + "signedEntryTimestamp": "MEYCIQDhWvNSLvnq5ZS3qTIgC7K2uQFeA0g8FEEjNo1UQxeubAIhALDrD1uIiUkk3tQNp/4gKT/9j8zEyyxi9Ti+qaD8q2vI" + }, + "inclusionProof": { + "logIndex": "29188096", + "rootHash": "fbEijQTFeiQCldZqez/u/WcrNPk4nxXI5ihofHYjFUA=", + "treeSize": "29188099", + "hashes": [ + "z7VKeAC2d2x18Vtxt7n40GS3gtc1xfRwjjxxzpy/Trw=", + "/Kd8ZuCLZ7MukSwpjaSgxKFl5X2vdHWk6/qpNrkiUJo=", + "vvkKs7IShUI15nVb9c5olPgBnL9r4uP7+d5KzV0hSjs=", + "Fg4p0WJZw8Lghrpxkx1SXgzfroTeIIrEgEbfgr9IdXY=", + "bH8NahqE+hQ58Qxhg0V5fYusFQ1xsaQ/rK6slWVRT5k=", + "HplNgZ3/afEHq52zcfL2s1AKisOYDdMCWK1jOu1tGyw=", + "uOPuC2YDmwZe989ZN3Lgh5CKMXh9HETrSNgf0jV0WkM=", + "eVsvWKnEZ1+Xo3Ba15DfEiIhhmlrEaIeb+VfYmx8KhY=", + "uLuBRins5nkqq2rqd17R27pQTUF+xetttC6MsmlUzd0=", + "jRUq4D8O+FI47Wbw96s7yHCu4qzWUxpIVfxQEeprDmc=", + "rXEsmEJN4PEoTU8US4qVtdIsGB1MCiRlGOepoiC99kM=" + ], + "checkpoint": { + "envelope": "rekor.sigstore.dev - 2605736670972794746\n29188099\nfbEijQTFeiQCldZqez/u/WcrNPk4nxXI5ihofHYjFUA=\nTimestamp: 1693323623528968756\n\n— rekor.sigstore.dev wNI9ajBEAiAK0YTbxRlhOZeeP+844Y+W7iz+hsFIF8x2NYsmuaxifAIgYUFSurlKwN7j5jCwpqSVrkbouQoYIYlyWU9Om16svmI=\n" + } + }, + "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7ImVudmVsb3BlIjp7InBheWxvYWRUeXBlIjoiYXBwbGljYXRpb24vdm5kLmluLXRvdG8ranNvbiIsInNpZ25hdHVyZXMiOlt7InB1YmxpY0tleSI6IkxTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVWQwUkVORFFtcDFaMEYzU1VKQlowbFZRMHBNYVhCVGREQTVTMHhHWXpCS1dXWjFSSEpUWVc0dkwweHpkME5uV1VsTGIxcEplbW93UlVGM1RYY0tUbnBGVmsxQ1RVZEJNVlZGUTJoTlRXTXliRzVqTTFKMlkyMVZkVnBIVmpKTlVqUjNTRUZaUkZaUlVVUkZlRlo2WVZka2VtUkhPWGxhVXpGd1ltNVNiQXBqYlRGc1drZHNhR1JIVlhkSWFHTk9UV3BOZDA5RVNUVk5WRlV3VFVSSmVsZG9ZMDVOYWsxM1QwUkpOVTFVVlRGTlJFbDZWMnBCUVUxR2EzZEZkMWxJQ2t0dldrbDZhakJEUVZGWlNVdHZXa2w2YWpCRVFWRmpSRkZuUVVWUVptMDJURkJZVVdWS1ZFTTRPVlZQY1dsT1YyMXVXbGxIYlZnMFZETnBURnBIYVRBS1JWWTBZbVpQYjAwNE5raDZZVGswV0hGNWRYZDRRVzlYY0VOUVpXTkdRMFZpUVdVNGJESmtaeTlsY2pOUE9VeEZSbkZQUTBKV2IzZG5aMVpYVFVFMFJ3cEJNVlZrUkhkRlFpOTNVVVZCZDBsSVowUkJWRUpuVGxaSVUxVkZSRVJCUzBKblozSkNaMFZHUWxGalJFRjZRV1JDWjA1V1NGRTBSVVpuVVZWbGNYQkRDbGhJY2pOd1kxVmhURE5GUmt0U0swdHpiWFZMVVhGdmQwaDNXVVJXVWpCcVFrSm5kMFp2UVZVek9WQndlakZaYTBWYVlqVnhUbXB3UzBaWGFYaHBORmtLV2tRNGQxbDNXVVJXVWpCU1FWRklMMEpHYTNkV05GcFdZVWhTTUdOSVRUWk1lVGx1WVZoU2IyUlhTWFZaTWpsMFRETk9jRm96VGpCaU0wcHNURE5PY0FwYU0wNHdZak5LYkV4WGNIcE1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU0wcHNZa2RXYUdNeVZYVmxWekZ6VVVoS2JGcHVUWFpoUjFab0NscElUWFppVjBad1ltcEJOVUpuYjNKQ1owVkZRVmxQTDAxQlJVSkNRM1J2WkVoU2QyTjZiM1pNTTFKMllUSldkVXh0Um1wa1IyeDJZbTVOZFZveWJEQUtZVWhXYVdSWVRteGpiVTUyWW01U2JHSnVVWFZaTWpsMFRVSkpSME5wYzBkQlVWRkNaemM0ZDBGUlNVVkNTRUl4WXpKbmQwNW5XVXRMZDFsQ1FrRkhSQXAyZWtGQ1FYZFJiMDFxV210TlZGa3hUVlJOZWs5RVdtMWFiVVpvVG5wcmQxbHFSbXBOZWtwdFQxUkpNMDVVVVRCYWFrVjZUV3BLYkU1RVJUVk9SRUZXQ2tKbmIzSkNaMFZGUVZsUEwwMUJSVVZDUVdSVFdsZDRiRmxZVG14TlEwbEhRMmx6UjBGUlVVSm5OemgzUVZGVlJVWklUbkJhTTA0d1lqTktiRXd6VG5BS1dqTk9NR0l6U214TVYzQjZUVUl3UjBOcGMwZEJVVkZDWnpjNGQwRlJXVVZFTTBwc1dtNU5kbUZIVm1oYVNFMTJZbGRHY0dKcVFUZENaMjl5UW1kRlJRcEJXVTh2VFVGRlNVSkRNRTFMTW1nd1pFaENlazlwT0haa1J6bHlXbGMwZFZsWFRqQmhWemwxWTNrMWJtRllVbTlrVjBveFl6SldlVmt5T1hWa1IxWjFDbVJETldwaU1qQjNXbEZaUzB0M1dVSkNRVWRFZG5wQlFrTlJVbGhFUmxadlpFaFNkMk42YjNaTU1tUndaRWRvTVZscE5XcGlNakIyWXpKc2JtTXpVbllLWTIxVmRtTXliRzVqTTFKMlkyMVZkR0Z1VFhaTWJXUndaRWRvTVZscE9UTmlNMHB5V20xNGRtUXpUWFpqYlZaeldsZEdlbHBUTlRWaVYzaEJZMjFXYlFwamVUbHZXbGRHYTJONU9YUlpWMngxVFVSblIwTnBjMGRCVVZGQ1p6YzRkMEZSYjBWTFozZHZUV3BhYTAxVVdURk5WRTE2VDBSYWJWcHRSbWhPZW10M0NsbHFSbXBOZWtwdFQxUkpNMDVVVVRCYWFrVjZUV3BLYkU1RVJUVk9SRUZrUW1kdmNrSm5SVVZCV1U4dlRVRkZURUpCT0UxRVYyUndaRWRvTVZscE1XOEtZak5PTUZwWFVYZE9kMWxMUzNkWlFrSkJSMFIyZWtGQ1JFRlJjRVJEWkc5a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFpqTW14dVl6TlNkZ3BqYlZWMll6SnNibU16VW5aamJWVjBZVzVOZDA5QldVdExkMWxDUWtGSFJIWjZRVUpFVVZGeFJFTm5lVTV0VVhoT2FsVjRUWHBOTkU1dFdtMVpWMFV6Q2s5VVFtbE5WMDE2VFcxWk5VMXFZekZPUkZKdFRWUk5lVTF0VlRCTlZHc3dUVUk0UjBOcGMwZEJVVkZDWnpjNGQwRlJORVZGVVhkUVkyMVdiV041T1c4S1dsZEdhMk41T1hSWlYyeDFUVUpyUjBOcGMwZEJVVkZDWnpjNGQwRlJPRVZEZDNkS1RrUnJNVTVVWXpCT1ZGVXhUVU56UjBOcGMwZEJVVkZDWnpjNGR3cEJVa0ZGU0ZGM1ltRklVakJqU0UwMlRIazVibUZZVW05a1YwbDFXVEk1ZEV3elRuQmFNMDR3WWpOS2JFMUNaMGREYVhOSFFWRlJRbWMzT0hkQlVrVkZDa05uZDBsT2VrVjNUMVJaZWs1VVRYZGFVVmxMUzNkWlFrSkJSMFIyZWtGQ1JXZFNXRVJHVm05a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFlLWXpKc2JtTXpVblpqYlZWMll6SnNibU16VW5aamJWVjBZVzVOZGt4dFpIQmtSMmd4V1drNU0ySXpTbkphYlhoMlpETk5kbU50Vm5OYVYwWjZXbE0xTlFwaVYzaEJZMjFXYldONU9XOWFWMFpyWTNrNWRGbFhiSFZOUkdkSFEybHpSMEZSVVVKbk56aDNRVkpOUlV0bmQyOU5hbHByVFZSWk1VMVVUWHBQUkZwdENscHRSbWhPZW10M1dXcEdhazE2U20xUFZFa3pUbFJSTUZwcVJYcE5ha3BzVGtSRk5VNUVRVlZDWjI5eVFtZEZSVUZaVHk5TlFVVlZRa0ZaVFVKSVFqRUtZekpuZDFkbldVdExkMWxDUWtGSFJIWjZRVUpHVVZKTlJFVndiMlJJVW5kamVtOTJUREprY0dSSGFERlphVFZxWWpJd2RtTXliRzVqTTFKMlkyMVZkZ3BqTW14dVl6TlNkbU50VlhSaGJrMTJXVmRPTUdGWE9YVmplVGw1WkZjMWVreDZXWGROVkZFd1QwUm5NazVxV1haWldGSXdXbGN4ZDJSSVRYWk5WRUZYQ2tKbmIzSkNaMFZGUVZsUEwwMUJSVmRDUVdkTlFtNUNNVmx0ZUhCWmVrTkNhV2RaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamhDU0c5QlpVRkNNa0ZPTURrS1RVZHlSM2g0UlhsWmVHdGxTRXBzYms1M1MybFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKcGEwaDZLM1ozUVVGQlVVUkJSV04zVWxGSlp3cGFSVzg0WXpCbFExcElSV2cwZFhwNlNucEdlamxVSzBWbVUxUk9WSFJDTWtaSlNERTRkbGh3YTA5elEwbFJSRVV4VFZSMGFUbFNiMFJ1VWs4elUwVlVDakZhYTJGa05rWnZWSGd2YXpaNmRGRmpkMGxFVUcxdVVuaFVRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXVRVVJDYTBGcVFrSXdObVp0VGxoNE5sUnZZVU1LYkVabk1tdFBlRzVtVEVkbmNuWnZVak5HTlVkcVJIUjJSRUpDT0cwNVUxZFJUbnBNTWpFeGFsbHRVeTluSzFsaVlubFZRMDFESzJGa05tcEpTeXRsWmdwbE5GaFBTV3hvVEdOWGVHVmFZa0owVFdwTFUzSlFlRzF0TkdwU00wSkdVVkZQUWxJck9ISXlOME5wYjNsb2VHOVRjWFpNZFhjOVBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdCIsInNpZyI6IlRVVlJRMGxEWVhSb2JsUkljMlJtV25GSWNUTnBXRXhxVFdVd1ZGVTViRXhaYmxJeGVtazNNM0JSZFZobU5VdElRV2xDTWpOS2FDdG5VMll4VVVWRGJrRlRSMlZ3TW1VeVRVZHVVRmhwYjFWTVZqUjJRMGxUU2tKdWEwcGFaejA5In1dfSwiaGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjFiZDg4ZTA1NGY2OGE3ZDE3MzkzNjcyYjAzZmExOGZkNjRmMGRjZDVmY2M1NDAzNWMxOWZlNjQwMWNmMmNmNWUifSwicGF5bG9hZEhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4Y2ViNGFiODEyNzczMTQ3M2E5ZWM4MTE0MGNiNjg0OWNmOGU5NzBjZGEzMmJhZWYwOTlkZjQ4YmEzMjY0NDQyIn19fX0=" + } + ], + "timestampVerificationData": null + }, + "dsseEnvelope": { + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicGtnOm5wbS9zaWdzdG9yZUAyLjEuMCIsImRpZ2VzdCI6eyJzaGE1MTIiOiI5MGYyMjNmOTkyZTRjODhkZDA2OGNkMmE1ZmM1N2Y5ZDJiMzA3OTgzNDNkZDZlMzhmMjljMjQwZTA0YmEwOTBlZjgzMWY4NDQ5MDg0N2M0ZTgyYjkyMzJjNzhlOGEyNTg0NjNiMWU1NWMwZjc0NjlmNzMwMjY1MDA4ZmE2NjMzZiJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjEiLCJwcmVkaWNhdGUiOnsiYnVpbGREZWZpbml0aW9uIjp7ImJ1aWxkVHlwZSI6Imh0dHBzOi8vc2xzYS1mcmFtZXdvcmsuZ2l0aHViLmlvL2dpdGh1Yi1hY3Rpb25zLWJ1aWxkdHlwZXMvd29ya2Zsb3cvdjEiLCJleHRlcm5hbFBhcmFtZXRlcnMiOnsid29ya2Zsb3ciOnsicmVmIjoicmVmcy9oZWFkcy9tYWluIiwicmVwb3NpdG9yeSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS9zaWdzdG9yZS1qcyIsInBhdGgiOiIuZ2l0aHViL3dvcmtmbG93cy9yZWxlYXNlLnltbCJ9fSwiaW50ZXJuYWxQYXJhbWV0ZXJzIjp7ImdpdGh1YiI6eyJldmVudF9uYW1lIjoicHVzaCIsInJlcG9zaXRvcnlfaWQiOiI0OTU1NzQ1NTUiLCJyZXBvc2l0b3J5X293bmVyX2lkIjoiNzEwOTYzNTMifX0sInJlc29sdmVkRGVwZW5kZW5jaWVzIjpbeyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL3NpZ3N0b3JlL3NpZ3N0b3JlLWpzQHJlZnMvaGVhZHMvbWFpbiIsImRpZ2VzdCI6eyJnaXRDb21taXQiOiIyNmQxNjUxMzM4NmZmYWE3OTBiMWMzMmY5Mjc1NDRmMTMyMmU0MTk0In19XX0sInJ1bkRldGFpbHMiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hY3Rpb25zL3J1bm5lci9naXRodWItaG9zdGVkIn0sIm1ldGFkYXRhIjp7Imludm9jYXRpb25JZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS9zaWdzdG9yZS1qcy9hY3Rpb25zL3J1bnMvNjAxNDQ4ODY2Ni9hdHRlbXB0cy8xIn19fX0=", + "payloadType": "application/vnd.in-toto+json", + "signatures": [ + { + "sig": "MEQCICathnTHsdfZqHq3iXLjMe0TU9lLYnR1zi73pQuXf5KHAiB23Jh+gSf1QECnASGep2e2MGnPXioULV4vCISJBnkJZg==", + "keyid": "" + } + ] + } + } diff --git a/pkg/cmd/attestation/test/data/sigstore-js-2.1.0.tgz b/pkg/cmd/attestation/test/data/sigstore-js-2.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..390b823fd12e377849e020aa2915f7ed9e77f924 GIT binary patch literal 37137 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 literal 0 HcmV?d00001 diff --git a/pkg/cmd/attestation/test/data/sigstore-js-2.1.0_with_2_bundles.jsonl b/pkg/cmd/attestation/test/data/sigstore-js-2.1.0_with_2_bundles.jsonl new file mode 100644 index 000000000..a4aca34d0 --- /dev/null +++ b/pkg/cmd/attestation/test/data/sigstore-js-2.1.0_with_2_bundles.jsonl @@ -0,0 +1,2 @@ +{"mediaType":"application/vnd.dev.sigstore.bundle+json;version=0.1","verificationMaterial":{"x509CertificateChain":{"certificates":[{"rawBytes":"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"}]},"tlogEntries":[{"logIndex":"33351527","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"intoto","version":"0.0.2"},"integratedTime":"1693323623","inclusionPromise":{"signedEntryTimestamp":"MEYCIQDhWvNSLvnq5ZS3qTIgC7K2uQFeA0g8FEEjNo1UQxeubAIhALDrD1uIiUkk3tQNp/4gKT/9j8zEyyxi9Ti+qaD8q2vI"},"inclusionProof":{"logIndex":"29188096","rootHash":"fbEijQTFeiQCldZqez/u/WcrNPk4nxXI5ihofHYjFUA=","treeSize":"29188099","hashes":["z7VKeAC2d2x18Vtxt7n40GS3gtc1xfRwjjxxzpy/Trw=","/Kd8ZuCLZ7MukSwpjaSgxKFl5X2vdHWk6/qpNrkiUJo=","vvkKs7IShUI15nVb9c5olPgBnL9r4uP7+d5KzV0hSjs=","Fg4p0WJZw8Lghrpxkx1SXgzfroTeIIrEgEbfgr9IdXY=","bH8NahqE+hQ58Qxhg0V5fYusFQ1xsaQ/rK6slWVRT5k=","HplNgZ3/afEHq52zcfL2s1AKisOYDdMCWK1jOu1tGyw=","uOPuC2YDmwZe989ZN3Lgh5CKMXh9HETrSNgf0jV0WkM=","eVsvWKnEZ1+Xo3Ba15DfEiIhhmlrEaIeb+VfYmx8KhY=","uLuBRins5nkqq2rqd17R27pQTUF+xetttC6MsmlUzd0=","jRUq4D8O+FI47Wbw96s7yHCu4qzWUxpIVfxQEeprDmc=","rXEsmEJN4PEoTU8US4qVtdIsGB1MCiRlGOepoiC99kM="],"checkpoint":{"envelope":"rekor.sigstore.dev - 2605736670972794746\n29188099\nfbEijQTFeiQCldZqez/u/WcrNPk4nxXI5ihofHYjFUA=\nTimestamp: 1693323623528968756\n\n— rekor.sigstore.dev wNI9ajBEAiAK0YTbxRlhOZeeP+844Y+W7iz+hsFIF8x2NYsmuaxifAIgYUFSurlKwN7j5jCwpqSVrkbouQoYIYlyWU9Om16svmI=\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7ImVudmVsb3BlIjp7InBheWxvYWRUeXBlIjoiYXBwbGljYXRpb24vdm5kLmluLXRvdG8ranNvbiIsInNpZ25hdHVyZXMiOlt7InB1YmxpY0tleSI6IkxTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVWQwUkVORFFtcDFaMEYzU1VKQlowbFZRMHBNYVhCVGREQTVTMHhHWXpCS1dXWjFSSEpUWVc0dkwweHpkME5uV1VsTGIxcEplbW93UlVGM1RYY0tUbnBGVmsxQ1RVZEJNVlZGUTJoTlRXTXliRzVqTTFKMlkyMVZkVnBIVmpKTlVqUjNTRUZaUkZaUlVVUkZlRlo2WVZka2VtUkhPWGxhVXpGd1ltNVNiQXBqYlRGc1drZHNhR1JIVlhkSWFHTk9UV3BOZDA5RVNUVk5WRlV3VFVSSmVsZG9ZMDVOYWsxM1QwUkpOVTFVVlRGTlJFbDZWMnBCUVUxR2EzZEZkMWxJQ2t0dldrbDZhakJEUVZGWlNVdHZXa2w2YWpCRVFWRmpSRkZuUVVWUVptMDJURkJZVVdWS1ZFTTRPVlZQY1dsT1YyMXVXbGxIYlZnMFZETnBURnBIYVRBS1JWWTBZbVpQYjAwNE5raDZZVGswV0hGNWRYZDRRVzlYY0VOUVpXTkdRMFZpUVdVNGJESmtaeTlsY2pOUE9VeEZSbkZQUTBKV2IzZG5aMVpYVFVFMFJ3cEJNVlZrUkhkRlFpOTNVVVZCZDBsSVowUkJWRUpuVGxaSVUxVkZSRVJCUzBKblozSkNaMFZHUWxGalJFRjZRV1JDWjA1V1NGRTBSVVpuVVZWbGNYQkRDbGhJY2pOd1kxVmhURE5GUmt0U0swdHpiWFZMVVhGdmQwaDNXVVJXVWpCcVFrSm5kMFp2UVZVek9WQndlakZaYTBWYVlqVnhUbXB3UzBaWGFYaHBORmtLV2tRNGQxbDNXVVJXVWpCU1FWRklMMEpHYTNkV05GcFdZVWhTTUdOSVRUWk1lVGx1WVZoU2IyUlhTWFZaTWpsMFRETk9jRm96VGpCaU0wcHNURE5PY0FwYU0wNHdZak5LYkV4WGNIcE1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU0wcHNZa2RXYUdNeVZYVmxWekZ6VVVoS2JGcHVUWFpoUjFab0NscElUWFppVjBad1ltcEJOVUpuYjNKQ1owVkZRVmxQTDAxQlJVSkNRM1J2WkVoU2QyTjZiM1pNTTFKMllUSldkVXh0Um1wa1IyeDJZbTVOZFZveWJEQUtZVWhXYVdSWVRteGpiVTUyWW01U2JHSnVVWFZaTWpsMFRVSkpSME5wYzBkQlVWRkNaemM0ZDBGUlNVVkNTRUl4WXpKbmQwNW5XVXRMZDFsQ1FrRkhSQXAyZWtGQ1FYZFJiMDFxV210TlZGa3hUVlJOZWs5RVdtMWFiVVpvVG5wcmQxbHFSbXBOZWtwdFQxUkpNMDVVVVRCYWFrVjZUV3BLYkU1RVJUVk9SRUZXQ2tKbmIzSkNaMFZGUVZsUEwwMUJSVVZDUVdSVFdsZDRiRmxZVG14TlEwbEhRMmx6UjBGUlVVSm5OemgzUVZGVlJVWklUbkJhTTA0d1lqTktiRXd6VG5BS1dqTk9NR0l6U214TVYzQjZUVUl3UjBOcGMwZEJVVkZDWnpjNGQwRlJXVVZFTTBwc1dtNU5kbUZIVm1oYVNFMTJZbGRHY0dKcVFUZENaMjl5UW1kRlJRcEJXVTh2VFVGRlNVSkRNRTFMTW1nd1pFaENlazlwT0haa1J6bHlXbGMwZFZsWFRqQmhWemwxWTNrMWJtRllVbTlrVjBveFl6SldlVmt5T1hWa1IxWjFDbVJETldwaU1qQjNXbEZaUzB0M1dVSkNRVWRFZG5wQlFrTlJVbGhFUmxadlpFaFNkMk42YjNaTU1tUndaRWRvTVZscE5XcGlNakIyWXpKc2JtTXpVbllLWTIxVmRtTXliRzVqTTFKMlkyMVZkR0Z1VFhaTWJXUndaRWRvTVZscE9UTmlNMHB5V20xNGRtUXpUWFpqYlZaeldsZEdlbHBUTlRWaVYzaEJZMjFXYlFwamVUbHZXbGRHYTJONU9YUlpWMngxVFVSblIwTnBjMGRCVVZGQ1p6YzRkMEZSYjBWTFozZHZUV3BhYTAxVVdURk5WRTE2VDBSYWJWcHRSbWhPZW10M0NsbHFSbXBOZWtwdFQxUkpNMDVVVVRCYWFrVjZUV3BLYkU1RVJUVk9SRUZrUW1kdmNrSm5SVVZCV1U4dlRVRkZURUpCT0UxRVYyUndaRWRvTVZscE1XOEtZak5PTUZwWFVYZE9kMWxMUzNkWlFrSkJSMFIyZWtGQ1JFRlJjRVJEWkc5a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFpqTW14dVl6TlNkZ3BqYlZWMll6SnNibU16VW5aamJWVjBZVzVOZDA5QldVdExkMWxDUWtGSFJIWjZRVUpFVVZGeFJFTm5lVTV0VVhoT2FsVjRUWHBOTkU1dFdtMVpWMFV6Q2s5VVFtbE5WMDE2VFcxWk5VMXFZekZPUkZKdFRWUk5lVTF0VlRCTlZHc3dUVUk0UjBOcGMwZEJVVkZDWnpjNGQwRlJORVZGVVhkUVkyMVdiV041T1c4S1dsZEdhMk41T1hSWlYyeDFUVUpyUjBOcGMwZEJVVkZDWnpjNGQwRlJPRVZEZDNkS1RrUnJNVTVVWXpCT1ZGVXhUVU56UjBOcGMwZEJVVkZDWnpjNGR3cEJVa0ZGU0ZGM1ltRklVakJqU0UwMlRIazVibUZZVW05a1YwbDFXVEk1ZEV3elRuQmFNMDR3WWpOS2JFMUNaMGREYVhOSFFWRlJRbWMzT0hkQlVrVkZDa05uZDBsT2VrVjNUMVJaZWs1VVRYZGFVVmxMUzNkWlFrSkJSMFIyZWtGQ1JXZFNXRVJHVm05a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFlLWXpKc2JtTXpVblpqYlZWMll6SnNibU16VW5aamJWVjBZVzVOZGt4dFpIQmtSMmd4V1drNU0ySXpTbkphYlhoMlpETk5kbU50Vm5OYVYwWjZXbE0xTlFwaVYzaEJZMjFXYldONU9XOWFWMFpyWTNrNWRGbFhiSFZOUkdkSFEybHpSMEZSVVVKbk56aDNRVkpOUlV0bmQyOU5hbHByVFZSWk1VMVVUWHBQUkZwdENscHRSbWhPZW10M1dXcEdhazE2U20xUFZFa3pUbFJSTUZwcVJYcE5ha3BzVGtSRk5VNUVRVlZDWjI5eVFtZEZSVUZaVHk5TlFVVlZRa0ZaVFVKSVFqRUtZekpuZDFkbldVdExkMWxDUWtGSFJIWjZRVUpHVVZKTlJFVndiMlJJVW5kamVtOTJUREprY0dSSGFERlphVFZxWWpJd2RtTXliRzVqTTFKMlkyMVZkZ3BqTW14dVl6TlNkbU50VlhSaGJrMTJXVmRPTUdGWE9YVmplVGw1WkZjMWVreDZXWGROVkZFd1QwUm5NazVxV1haWldGSXdXbGN4ZDJSSVRYWk5WRUZYQ2tKbmIzSkNaMFZGUVZsUEwwMUJSVmRDUVdkTlFtNUNNVmx0ZUhCWmVrTkNhV2RaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamhDU0c5QlpVRkNNa0ZPTURrS1RVZHlSM2g0UlhsWmVHdGxTRXBzYms1M1MybFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKcGEwaDZLM1ozUVVGQlVVUkJSV04zVWxGSlp3cGFSVzg0WXpCbFExcElSV2cwZFhwNlNucEdlamxVSzBWbVUxUk9WSFJDTWtaSlNERTRkbGh3YTA5elEwbFJSRVV4VFZSMGFUbFNiMFJ1VWs4elUwVlVDakZhYTJGa05rWnZWSGd2YXpaNmRGRmpkMGxFVUcxdVVuaFVRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXVRVVJDYTBGcVFrSXdObVp0VGxoNE5sUnZZVU1LYkVabk1tdFBlRzVtVEVkbmNuWnZVak5HTlVkcVJIUjJSRUpDT0cwNVUxZFJUbnBNTWpFeGFsbHRVeTluSzFsaVlubFZRMDFESzJGa05tcEpTeXRsWmdwbE5GaFBTV3hvVEdOWGVHVmFZa0owVFdwTFUzSlFlRzF0TkdwU00wSkdVVkZQUWxJck9ISXlOME5wYjNsb2VHOVRjWFpNZFhjOVBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdCIsInNpZyI6IlRVVlJRMGxEWVhSb2JsUkljMlJtV25GSWNUTnBXRXhxVFdVd1ZGVTViRXhaYmxJeGVtazNNM0JSZFZobU5VdElRV2xDTWpOS2FDdG5VMll4VVVWRGJrRlRSMlZ3TW1VeVRVZHVVRmhwYjFWTVZqUjJRMGxUU2tKdWEwcGFaejA5In1dfSwiaGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjFiZDg4ZTA1NGY2OGE3ZDE3MzkzNjcyYjAzZmExOGZkNjRmMGRjZDVmY2M1NDAzNWMxOWZlNjQwMWNmMmNmNWUifSwicGF5bG9hZEhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4Y2ViNGFiODEyNzczMTQ3M2E5ZWM4MTE0MGNiNjg0OWNmOGU5NzBjZGEzMmJhZWYwOTlkZjQ4YmEzMjY0NDQyIn19fX0="}],"timestampVerificationData":null},"dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicGtnOm5wbS9zaWdzdG9yZUAyLjEuMCIsImRpZ2VzdCI6eyJzaGE1MTIiOiI5MGYyMjNmOTkyZTRjODhkZDA2OGNkMmE1ZmM1N2Y5ZDJiMzA3OTgzNDNkZDZlMzhmMjljMjQwZTA0YmEwOTBlZjgzMWY4NDQ5MDg0N2M0ZTgyYjkyMzJjNzhlOGEyNTg0NjNiMWU1NWMwZjc0NjlmNzMwMjY1MDA4ZmE2NjMzZiJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjEiLCJwcmVkaWNhdGUiOnsiYnVpbGREZWZpbml0aW9uIjp7ImJ1aWxkVHlwZSI6Imh0dHBzOi8vc2xzYS1mcmFtZXdvcmsuZ2l0aHViLmlvL2dpdGh1Yi1hY3Rpb25zLWJ1aWxkdHlwZXMvd29ya2Zsb3cvdjEiLCJleHRlcm5hbFBhcmFtZXRlcnMiOnsid29ya2Zsb3ciOnsicmVmIjoicmVmcy9oZWFkcy9tYWluIiwicmVwb3NpdG9yeSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS9zaWdzdG9yZS1qcyIsInBhdGgiOiIuZ2l0aHViL3dvcmtmbG93cy9yZWxlYXNlLnltbCJ9fSwiaW50ZXJuYWxQYXJhbWV0ZXJzIjp7ImdpdGh1YiI6eyJldmVudF9uYW1lIjoicHVzaCIsInJlcG9zaXRvcnlfaWQiOiI0OTU1NzQ1NTUiLCJyZXBvc2l0b3J5X293bmVyX2lkIjoiNzEwOTYzNTMifX0sInJlc29sdmVkRGVwZW5kZW5jaWVzIjpbeyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL3NpZ3N0b3JlL3NpZ3N0b3JlLWpzQHJlZnMvaGVhZHMvbWFpbiIsImRpZ2VzdCI6eyJnaXRDb21taXQiOiIyNmQxNjUxMzM4NmZmYWE3OTBiMWMzMmY5Mjc1NDRmMTMyMmU0MTk0In19XX0sInJ1bkRldGFpbHMiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hY3Rpb25zL3J1bm5lci9naXRodWItaG9zdGVkIn0sIm1ldGFkYXRhIjp7Imludm9jYXRpb25JZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS9zaWdzdG9yZS1qcy9hY3Rpb25zL3J1bnMvNjAxNDQ4ODY2Ni9hdHRlbXB0cy8xIn19fX0=","payloadType":"application/vnd.in-toto+json","signatures":[{"sig":"MEQCICathnTHsdfZqHq3iXLjMe0TU9lLYnR1zi73pQuXf5KHAiB23Jh+gSf1QECnASGep2e2MGnPXioULV4vCISJBnkJZg==","keyid":""}]}} +{"mediaType":"application/vnd.dev.sigstore.bundle+json;version=0.1","verificationMaterial":{"x509CertificateChain":{"certificates":[{"rawBytes":"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"}]},"tlogEntries":[{"logIndex":"33351527","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"intoto","version":"0.0.2"},"integratedTime":"1693323623","inclusionPromise":{"signedEntryTimestamp":"MEYCIQDhWvNSLvnq5ZS3qTIgC7K2uQFeA0g8FEEjNo1UQxeubAIhALDrD1uIiUkk3tQNp/4gKT/9j8zEyyxi9Ti+qaD8q2vI"},"inclusionProof":{"logIndex":"29188096","rootHash":"fbEijQTFeiQCldZqez/u/WcrNPk4nxXI5ihofHYjFUA=","treeSize":"29188099","hashes":["z7VKeAC2d2x18Vtxt7n40GS3gtc1xfRwjjxxzpy/Trw=","/Kd8ZuCLZ7MukSwpjaSgxKFl5X2vdHWk6/qpNrkiUJo=","vvkKs7IShUI15nVb9c5olPgBnL9r4uP7+d5KzV0hSjs=","Fg4p0WJZw8Lghrpxkx1SXgzfroTeIIrEgEbfgr9IdXY=","bH8NahqE+hQ58Qxhg0V5fYusFQ1xsaQ/rK6slWVRT5k=","HplNgZ3/afEHq52zcfL2s1AKisOYDdMCWK1jOu1tGyw=","uOPuC2YDmwZe989ZN3Lgh5CKMXh9HETrSNgf0jV0WkM=","eVsvWKnEZ1+Xo3Ba15DfEiIhhmlrEaIeb+VfYmx8KhY=","uLuBRins5nkqq2rqd17R27pQTUF+xetttC6MsmlUzd0=","jRUq4D8O+FI47Wbw96s7yHCu4qzWUxpIVfxQEeprDmc=","rXEsmEJN4PEoTU8US4qVtdIsGB1MCiRlGOepoiC99kM="],"checkpoint":{"envelope":"rekor.sigstore.dev - 2605736670972794746\n29188099\nfbEijQTFeiQCldZqez/u/WcrNPk4nxXI5ihofHYjFUA=\nTimestamp: 1693323623528968756\n\n— rekor.sigstore.dev wNI9ajBEAiAK0YTbxRlhOZeeP+844Y+W7iz+hsFIF8x2NYsmuaxifAIgYUFSurlKwN7j5jCwpqSVrkbouQoYIYlyWU9Om16svmI=\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7ImVudmVsb3BlIjp7InBheWxvYWRUeXBlIjoiYXBwbGljYXRpb24vdm5kLmluLXRvdG8ranNvbiIsInNpZ25hdHVyZXMiOlt7InB1YmxpY0tleSI6IkxTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVWQwUkVORFFtcDFaMEYzU1VKQlowbFZRMHBNYVhCVGREQTVTMHhHWXpCS1dXWjFSSEpUWVc0dkwweHpkME5uV1VsTGIxcEplbW93UlVGM1RYY0tUbnBGVmsxQ1RVZEJNVlZGUTJoTlRXTXliRzVqTTFKMlkyMVZkVnBIVmpKTlVqUjNTRUZaUkZaUlVVUkZlRlo2WVZka2VtUkhPWGxhVXpGd1ltNVNiQXBqYlRGc1drZHNhR1JIVlhkSWFHTk9UV3BOZDA5RVNUVk5WRlV3VFVSSmVsZG9ZMDVOYWsxM1QwUkpOVTFVVlRGTlJFbDZWMnBCUVUxR2EzZEZkMWxJQ2t0dldrbDZhakJEUVZGWlNVdHZXa2w2YWpCRVFWRmpSRkZuUVVWUVptMDJURkJZVVdWS1ZFTTRPVlZQY1dsT1YyMXVXbGxIYlZnMFZETnBURnBIYVRBS1JWWTBZbVpQYjAwNE5raDZZVGswV0hGNWRYZDRRVzlYY0VOUVpXTkdRMFZpUVdVNGJESmtaeTlsY2pOUE9VeEZSbkZQUTBKV2IzZG5aMVpYVFVFMFJ3cEJNVlZrUkhkRlFpOTNVVVZCZDBsSVowUkJWRUpuVGxaSVUxVkZSRVJCUzBKblozSkNaMFZHUWxGalJFRjZRV1JDWjA1V1NGRTBSVVpuVVZWbGNYQkRDbGhJY2pOd1kxVmhURE5GUmt0U0swdHpiWFZMVVhGdmQwaDNXVVJXVWpCcVFrSm5kMFp2UVZVek9WQndlakZaYTBWYVlqVnhUbXB3UzBaWGFYaHBORmtLV2tRNGQxbDNXVVJXVWpCU1FWRklMMEpHYTNkV05GcFdZVWhTTUdOSVRUWk1lVGx1WVZoU2IyUlhTWFZaTWpsMFRETk9jRm96VGpCaU0wcHNURE5PY0FwYU0wNHdZak5LYkV4WGNIcE1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU0wcHNZa2RXYUdNeVZYVmxWekZ6VVVoS2JGcHVUWFpoUjFab0NscElUWFppVjBad1ltcEJOVUpuYjNKQ1owVkZRVmxQTDAxQlJVSkNRM1J2WkVoU2QyTjZiM1pNTTFKMllUSldkVXh0Um1wa1IyeDJZbTVOZFZveWJEQUtZVWhXYVdSWVRteGpiVTUyWW01U2JHSnVVWFZaTWpsMFRVSkpSME5wYzBkQlVWRkNaemM0ZDBGUlNVVkNTRUl4WXpKbmQwNW5XVXRMZDFsQ1FrRkhSQXAyZWtGQ1FYZFJiMDFxV210TlZGa3hUVlJOZWs5RVdtMWFiVVpvVG5wcmQxbHFSbXBOZWtwdFQxUkpNMDVVVVRCYWFrVjZUV3BLYkU1RVJUVk9SRUZXQ2tKbmIzSkNaMFZGUVZsUEwwMUJSVVZDUVdSVFdsZDRiRmxZVG14TlEwbEhRMmx6UjBGUlVVSm5OemgzUVZGVlJVWklUbkJhTTA0d1lqTktiRXd6VG5BS1dqTk9NR0l6U214TVYzQjZUVUl3UjBOcGMwZEJVVkZDWnpjNGQwRlJXVVZFTTBwc1dtNU5kbUZIVm1oYVNFMTJZbGRHY0dKcVFUZENaMjl5UW1kRlJRcEJXVTh2VFVGRlNVSkRNRTFMTW1nd1pFaENlazlwT0haa1J6bHlXbGMwZFZsWFRqQmhWemwxWTNrMWJtRllVbTlrVjBveFl6SldlVmt5T1hWa1IxWjFDbVJETldwaU1qQjNXbEZaUzB0M1dVSkNRVWRFZG5wQlFrTlJVbGhFUmxadlpFaFNkMk42YjNaTU1tUndaRWRvTVZscE5XcGlNakIyWXpKc2JtTXpVbllLWTIxVmRtTXliRzVqTTFKMlkyMVZkR0Z1VFhaTWJXUndaRWRvTVZscE9UTmlNMHB5V20xNGRtUXpUWFpqYlZaeldsZEdlbHBUTlRWaVYzaEJZMjFXYlFwamVUbHZXbGRHYTJONU9YUlpWMngxVFVSblIwTnBjMGRCVVZGQ1p6YzRkMEZSYjBWTFozZHZUV3BhYTAxVVdURk5WRTE2VDBSYWJWcHRSbWhPZW10M0NsbHFSbXBOZWtwdFQxUkpNMDVVVVRCYWFrVjZUV3BLYkU1RVJUVk9SRUZrUW1kdmNrSm5SVVZCV1U4dlRVRkZURUpCT0UxRVYyUndaRWRvTVZscE1XOEtZak5PTUZwWFVYZE9kMWxMUzNkWlFrSkJSMFIyZWtGQ1JFRlJjRVJEWkc5a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFpqTW14dVl6TlNkZ3BqYlZWMll6SnNibU16VW5aamJWVjBZVzVOZDA5QldVdExkMWxDUWtGSFJIWjZRVUpFVVZGeFJFTm5lVTV0VVhoT2FsVjRUWHBOTkU1dFdtMVpWMFV6Q2s5VVFtbE5WMDE2VFcxWk5VMXFZekZPUkZKdFRWUk5lVTF0VlRCTlZHc3dUVUk0UjBOcGMwZEJVVkZDWnpjNGQwRlJORVZGVVhkUVkyMVdiV041T1c4S1dsZEdhMk41T1hSWlYyeDFUVUpyUjBOcGMwZEJVVkZDWnpjNGQwRlJPRVZEZDNkS1RrUnJNVTVVWXpCT1ZGVXhUVU56UjBOcGMwZEJVVkZDWnpjNGR3cEJVa0ZGU0ZGM1ltRklVakJqU0UwMlRIazVibUZZVW05a1YwbDFXVEk1ZEV3elRuQmFNMDR3WWpOS2JFMUNaMGREYVhOSFFWRlJRbWMzT0hkQlVrVkZDa05uZDBsT2VrVjNUMVJaZWs1VVRYZGFVVmxMUzNkWlFrSkJSMFIyZWtGQ1JXZFNXRVJHVm05a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFlLWXpKc2JtTXpVblpqYlZWMll6SnNibU16VW5aamJWVjBZVzVOZGt4dFpIQmtSMmd4V1drNU0ySXpTbkphYlhoMlpETk5kbU50Vm5OYVYwWjZXbE0xTlFwaVYzaEJZMjFXYldONU9XOWFWMFpyWTNrNWRGbFhiSFZOUkdkSFEybHpSMEZSVVVKbk56aDNRVkpOUlV0bmQyOU5hbHByVFZSWk1VMVVUWHBQUkZwdENscHRSbWhPZW10M1dXcEdhazE2U20xUFZFa3pUbFJSTUZwcVJYcE5ha3BzVGtSRk5VNUVRVlZDWjI5eVFtZEZSVUZaVHk5TlFVVlZRa0ZaVFVKSVFqRUtZekpuZDFkbldVdExkMWxDUWtGSFJIWjZRVUpHVVZKTlJFVndiMlJJVW5kamVtOTJUREprY0dSSGFERlphVFZxWWpJd2RtTXliRzVqTTFKMlkyMVZkZ3BqTW14dVl6TlNkbU50VlhSaGJrMTJXVmRPTUdGWE9YVmplVGw1WkZjMWVreDZXWGROVkZFd1QwUm5NazVxV1haWldGSXdXbGN4ZDJSSVRYWk5WRUZYQ2tKbmIzSkNaMFZGUVZsUEwwMUJSVmRDUVdkTlFtNUNNVmx0ZUhCWmVrTkNhV2RaUzB0M1dVSkNRVWhYWlZGSlJVRm5VamhDU0c5QlpVRkNNa0ZPTURrS1RVZHlSM2g0UlhsWmVHdGxTRXBzYms1M1MybFRiRFkwTTJwNWRDODBaVXRqYjBGMlMyVTJUMEZCUVVKcGEwaDZLM1ozUVVGQlVVUkJSV04zVWxGSlp3cGFSVzg0WXpCbFExcElSV2cwZFhwNlNucEdlamxVSzBWbVUxUk9WSFJDTWtaSlNERTRkbGh3YTA5elEwbFJSRVV4VFZSMGFUbFNiMFJ1VWs4elUwVlVDakZhYTJGa05rWnZWSGd2YXpaNmRGRmpkMGxFVUcxdVVuaFVRVXRDWjJkeGFHdHFUMUJSVVVSQmQwNXVRVVJDYTBGcVFrSXdObVp0VGxoNE5sUnZZVU1LYkVabk1tdFBlRzVtVEVkbmNuWnZVak5HTlVkcVJIUjJSRUpDT0cwNVUxZFJUbnBNTWpFeGFsbHRVeTluSzFsaVlubFZRMDFESzJGa05tcEpTeXRsWmdwbE5GaFBTV3hvVEdOWGVHVmFZa0owVFdwTFUzSlFlRzF0TkdwU00wSkdVVkZQUWxJck9ISXlOME5wYjNsb2VHOVRjWFpNZFhjOVBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdCIsInNpZyI6IlRVVlJRMGxEWVhSb2JsUkljMlJtV25GSWNUTnBXRXhxVFdVd1ZGVTViRXhaYmxJeGVtazNNM0JSZFZobU5VdElRV2xDTWpOS2FDdG5VMll4VVVWRGJrRlRSMlZ3TW1VeVRVZHVVRmhwYjFWTVZqUjJRMGxUU2tKdWEwcGFaejA5In1dfSwiaGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjFiZDg4ZTA1NGY2OGE3ZDE3MzkzNjcyYjAzZmExOGZkNjRmMGRjZDVmY2M1NDAzNWMxOWZlNjQwMWNmMmNmNWUifSwicGF5bG9hZEhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4Y2ViNGFiODEyNzczMTQ3M2E5ZWM4MTE0MGNiNjg0OWNmOGU5NzBjZGEzMmJhZWYwOTlkZjQ4YmEzMjY0NDQyIn19fX0="}],"timestampVerificationData":null},"dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicGtnOm5wbS9zaWdzdG9yZUAyLjEuMCIsImRpZ2VzdCI6eyJzaGE1MTIiOiI5MGYyMjNmOTkyZTRjODhkZDA2OGNkMmE1ZmM1N2Y5ZDJiMzA3OTgzNDNkZDZlMzhmMjljMjQwZTA0YmEwOTBlZjgzMWY4NDQ5MDg0N2M0ZTgyYjkyMzJjNzhlOGEyNTg0NjNiMWU1NWMwZjc0NjlmNzMwMjY1MDA4ZmE2NjMzZiJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjEiLCJwcmVkaWNhdGUiOnsiYnVpbGREZWZpbml0aW9uIjp7ImJ1aWxkVHlwZSI6Imh0dHBzOi8vc2xzYS1mcmFtZXdvcmsuZ2l0aHViLmlvL2dpdGh1Yi1hY3Rpb25zLWJ1aWxkdHlwZXMvd29ya2Zsb3cvdjEiLCJleHRlcm5hbFBhcmFtZXRlcnMiOnsid29ya2Zsb3ciOnsicmVmIjoicmVmcy9oZWFkcy9tYWluIiwicmVwb3NpdG9yeSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS9zaWdzdG9yZS1qcyIsInBhdGgiOiIuZ2l0aHViL3dvcmtmbG93cy9yZWxlYXNlLnltbCJ9fSwiaW50ZXJuYWxQYXJhbWV0ZXJzIjp7ImdpdGh1YiI6eyJldmVudF9uYW1lIjoicHVzaCIsInJlcG9zaXRvcnlfaWQiOiI0OTU1NzQ1NTUiLCJyZXBvc2l0b3J5X293bmVyX2lkIjoiNzEwOTYzNTMifX0sInJlc29sdmVkRGVwZW5kZW5jaWVzIjpbeyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL3NpZ3N0b3JlL3NpZ3N0b3JlLWpzQHJlZnMvaGVhZHMvbWFpbiIsImRpZ2VzdCI6eyJnaXRDb21taXQiOiIyNmQxNjUxMzM4NmZmYWE3OTBiMWMzMmY5Mjc1NDRmMTMyMmU0MTk0In19XX0sInJ1bkRldGFpbHMiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9hY3Rpb25zL3J1bm5lci9naXRodWItaG9zdGVkIn0sIm1ldGFkYXRhIjp7Imludm9jYXRpb25JZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS9zaWdzdG9yZS1qcy9hY3Rpb25zL3J1bnMvNjAxNDQ4ODY2Ni9hdHRlbXB0cy8xIn19fX0=","payloadType":"application/vnd.in-toto+json","signatures":[{"sig":"MEQCICathnTHsdfZqHq3iXLjMe0TU9lLYnR1zi73pQuXf5KHAiB23Jh+gSf1QECnASGep2e2MGnPXioULV4vCISJBnkJZg==","keyid":""}]}} diff --git a/pkg/cmd/attestation/test/data/sigstoreBundle-invalid-mismatched-signatures.json b/pkg/cmd/attestation/test/data/sigstoreBundle-invalid-mismatched-signatures.json new file mode 100644 index 000000000..43e9bfac3 --- /dev/null +++ b/pkg/cmd/attestation/test/data/sigstoreBundle-invalid-mismatched-signatures.json @@ -0,0 +1,48 @@ +{ + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "tlogEntries": [ + { + "logIndex": "6755099", + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + }, + "kindVersion": { + "kind": "intoto", + "version": "0.0.2" + }, + "integratedTime": "1667953968", + "inclusionPromise": { + "signedEntryTimestamp": "MEUCIACiV07L0UdN6++1ljz5fbK9L5w3epXGvb/tRRmg+LlOAiEA+ksZQyovogZd2Y2YH0A9iXle3Cee90F/TU2h9DcdkbA=" + }, + "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7ImVudmVsb3BlIjp7InBheWxvYWRUeXBlIjoidGV4dC9wbGFpbiIsInNpZ25hdHVyZXMiOlt7InB1YmxpY0tleSI6IkxTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVU52YWtORFFXbGxaMEYzU1VKQlowbFZXbXMyUWpGRE56UjZURXBMTDBOblMwMWhOV3AyVmtaWk9IZE5kME5uV1VsTGIxcEplbW93UlVGM1RYY0tUbnBGVmsxQ1RVZEJNVlZGUTJoTlRXTXliRzVqTTFKMlkyMVZkVnBIVmpKTlVqUjNTRUZaUkZaUlVVUkZlRlo2WVZka2VtUkhPWGxhVXpGd1ltNVNiQXBqYlRGc1drZHNhR1JIVlhkSWFHTk9UV3BKZUUxVVFUVk5SRUY2VFdwUk0xZG9ZMDVOYWtsNFRWUkJOVTFFUVRCTmFsRXpWMnBCUVUxR2EzZEZkMWxJQ2t0dldrbDZhakJEUVZGWlNVdHZXa2w2YWpCRVFWRmpSRkZuUVVWb1psWkVla0ZoZWxjM1RERjVPREE1TVdObFNtWmFUbU16VmxWTll6VlpRVEpNWTNvS1pqUjNhRXBQVjJ4RGNuZExXVE5IUXpKTlVpdEVWbEp6YVZGck1HRTBWRUpWYkhWT2F6Sk5hM1pOZHpodE9VdEllalpQUTBGVldYZG5aMFpEVFVFMFJ3cEJNVlZrUkhkRlFpOTNVVVZCZDBsSVowUkJWRUpuVGxaSVUxVkZSRVJCUzBKblozSkNaMFZHUWxGalJFRjZRV1JDWjA1V1NGRTBSVVpuVVZWd2FFSjFDa2xLTjBGbWVucGlMMVpTUmt0WWNIRm5aMUUzUW1kQmQwaDNXVVJXVWpCcVFrSm5kMFp2UVZVek9WQndlakZaYTBWYVlqVnhUbXB3UzBaWGFYaHBORmtLV2tRNGQwaDNXVVJXVWpCU1FWRklMMEpDVlhkRk5FVlNXVzVLY0ZsWE5VRmFSMVp2V1ZjeGJHTnBOV3BpTWpCM1RFRlpTMHQzV1VKQ1FVZEVkbnBCUWdwQlVWRmxZVWhTTUdOSVRUWk1lVGx1WVZoU2IyUlhTWFZaTWpsMFRESjRkbG95YkhWTU1qbG9aRmhTYjAxSlIweENaMjl5UW1kRlJVRmtXalZCWjFGRENrSklNRVZsZDBJMVFVaGpRVE5VTUhkaGMySklSVlJLYWtkU05HTnRWMk16UVhGS1MxaHlhbVZRU3pNdmFEUndlV2RET0hBM2J6UkJRVUZIUlZkak0wUUthMmRCUVVKQlRVRlRSRUpIUVdsRlFTdFliemxHZGl0dVpHTlpNazlzTldJeVRUaEZTSGhUTTBob2IwSmlTalpTUWpKVVkzWXZURTAwZVZGRFNWRkVaQXBzY1c1U2JFbzVWWEJWTkdwT2VXaGhRbE5FWjBSMGVEY3JPRTVSTVVkdU56RTBjMlJZVVdGdlJtcEJTMEpuWjNGb2EycFBVRkZSUkVGM1RuQkJSRUp0Q2tGcVJVRnViWGRXWlZGMVJYTnhaMVV4U0hKS1RrSlZaSEIxTlhVMlozZFhSSFExVGxWR09YcFphWFI1YUZWTlQwZFdTMWRRUmxKek1YcFJlVEkyYldjS1JtZ3hjMEZxUlVGM1ZqRlRabGRtTnk5QlNUWllRVzVZYUdNMFZrSllRaTlKVFhkVlFuSmxjbEJ4WVVwS2NWRTNSblF4ZGtKQ2JpOXFSRkpaYVhOelJ3cDFjVWx5V1VGWFN3b3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0iLCJzaWciOiJUVVZWUTBsQ1ZUSldiMFUxV0c5VE9EQkZWamhxVHpCU2RsRmhWRzFsUWtWTFIwVjBWVUZKYTJKa1FsUnNXV3QxUVdsRlFXNDJkazl6VkRsNlJWcHpSemhpV1ROdlJUVTBXVWhsTHpWQmNFTXZXVFZwVFZCWWRqaGhjREIyUlZFOSJ9XX0sImhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI5ODQ2YjRlMDU0ZWYwYzVlZWIwNzA1NDQ0N2I2OWQ2ODgxZWRkNmIwMTZlOWU4Nzg0M2YyMzVhOWMyMDFkMzAzIn0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiNjhlNjU2YjI1MWU2N2U4MzU4YmVmODQ4M2FiMGQ1MWM2NjE5ZjNlN2ExYTlmMGU3NTgzOGQ0MWZmMzY4ZjcyOCJ9fX19" + } + ], + "timestampVerificationData": { + "rfc3161Timestamps": [] + }, + "x509CertificateChain": { + "certificates": [ + { + "rawBytes": "MIICoDCCAiagAwIBAgIUSkFzCwp0c8PRty4So7bizLRExZswCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjIxMTA5MDAyOTI1WhcNMjIxMTA5MDAzOTI1WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEj/l/5Pi+TWUkamLY1UH9yq0XgeH818M38IQm6rYdeT0JLLRozmzqGGUjoAFw1G69fBTAP0ae22V93BFT2JGYjqOCAUUwggFBMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUZzrPkPRD8aCDHvxscd7v6T5fOZMwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0RAQH/BBUwE4ERYnJpYW5AZGVoYW1lci5jb20wLAYKKwYBBAGDvzABAQQeaHR0cHM6Ly9naXRodWIuY29tL2xvZ2luL29hdXRoMIGKBgorBgEEAdZ5AgQCBHwEegB4AHYA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGEWcqsQgAABAMARzBFAiEA0XEwx48N/TZneFxEV7RwbVIhROPOMJYo1XEoN4sd98wCIGlL4RnI8JVHdYbzbZLHUIr7+5I1Dl//zUJBh4A3YPnzMAoGCCqGSM49BAMDA2gAMGUCMQDyFvX31sp+olif92nr/w52KLmtPoWVHxtGeaxuNAtQ7DFdtSDvXGenUT+fqALnAikCMD/hbwNuQl9kr2nlZ96GlT3eMCl78o0D2QKjx2Do1JC6lqG2hDM61FG6Znx7T5VWfQ==" + }, + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + } + }, + "dsseEnvelope": { + "payload": "aGVsbG8sIHdvcmxkIQ==", + "payloadType": "text/plain", + "signatures": [ + { + "sig": "MEYCIQDBfvwEED9oUrvTjZ7cJrZkE073IqG8V8r5I1wMY3YAJwIhAJ+UXICfI5rs7Jl2oFEAK8KG+asRWQA+ZkRA1jW2lUYO", + "keyid": "" + } + ] + } +} diff --git a/pkg/cmd/attestation/test/data/sigstoreBundle-invalid-signature.json b/pkg/cmd/attestation/test/data/sigstoreBundle-invalid-signature.json new file mode 100644 index 000000000..b1275cebc --- /dev/null +++ b/pkg/cmd/attestation/test/data/sigstoreBundle-invalid-signature.json @@ -0,0 +1,48 @@ +{ + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "tlogEntries": [ + { + "logIndex": "6751924", + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + }, + "kindVersion": { + "kind": "intoto", + "version": "0.0.2" + }, + "integratedTime": "1667948287", + "inclusionPromise": { + "signedEntryTimestamp": "MEQCIEzguFRaGzOpMw9JJGUfqSJQ11qlzpcyVCkZfZYPwpLCAiBzdU4LnjtVKYCfyoTImFh3OLFWeOKygtS47Z8fp1GYHg==" + }, + "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7ImVudmVsb3BlIjp7InBheWxvYWRUeXBlIjoidGV4dC9wbGFpbiIsInNpZ25hdHVyZXMiOlt7InB1YmxpY0tleSI6IkxTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVU51ZWtORFFXbFhaMEYzU1VKQlowbFZWa2gzWldoUGRFZHVORXRUUkRGSU9GSkpOVGd4VFdaaWVXVjNkME5uV1VsTGIxcEplbW93UlVGM1RYY0tUbnBGVmsxQ1RVZEJNVlZGUTJoTlRXTXliRzVqTTFKMlkyMVZkVnBIVmpKTlVqUjNTRUZaUkZaUlVVUkZlRlo2WVZka2VtUkhPWGxhVXpGd1ltNVNiQXBqYlRGc1drZHNhR1JIVlhkSWFHTk9UV3BKZUUxVVFUUk5ha2t4VDBSQk1sZG9ZMDVOYWtsNFRWUkJORTFxVFhkUFJFRXlWMnBCUVUxR2EzZEZkMWxJQ2t0dldrbDZhakJEUVZGWlNVdHZXa2w2YWpCRVFWRmpSRkZuUVVWSFp6WklhbmgwTWxWT2FVb3hhM2QzY1RWWVVVbEpkMDFhYmtwbVZsRXpZa1l3TVhVS1drdDBaVTFrWTFZdk0zRm9RMjFYVDJWamIzaFNjWGR5WWxsVWMyaEhaemxPZVZoalFtSjJaVFo2UzNkYVZsUk1aWEZQUTBGVlVYZG5aMFpCVFVFMFJ3cEJNVlZrUkhkRlFpOTNVVVZCZDBsSVowUkJWRUpuVGxaSVUxVkZSRVJCUzBKblozSkNaMFZHUWxGalJFRjZRV1JDWjA1V1NGRTBSVVpuVVZVM1YzQlNDall3YzBOd1oyWjFNRFIzWTNOcWRrTkdlSFF3WmsxcmQwaDNXVVJXVWpCcVFrSm5kMFp2UVZVek9WQndlakZaYTBWYVlqVnhUbXB3UzBaWGFYaHBORmtLV2tRNGQwaDNXVVJXVWpCU1FWRklMMEpDVlhkRk5FVlNXVzVLY0ZsWE5VRmFSMVp2V1ZjeGJHTnBOV3BpTWpCM1RFRlpTMHQzV1VKQ1FVZEVkbnBCUWdwQlVWRmxZVWhTTUdOSVRUWk1lVGx1WVZoU2IyUlhTWFZaTWpsMFRESjRkbG95YkhWTU1qbG9aRmhTYjAxSlIwcENaMjl5UW1kRlJVRmtXalZCWjFGRENrSkljMFZsVVVJelFVaFZRVE5VTUhkaGMySklSVlJLYWtkU05HTnRWMk16UVhGS1MxaHlhbVZRU3pNdmFEUndlV2RET0hBM2J6UkJRVUZIUlZkWVkxSUtPRUZCUVVKQlRVRlNha0pGUVdsQ1VsUnlSMFUxV1RGRmJsbHVhV0ZLUWl0dWMzWTRPVlpoV1hnelVWcHFiMk5GYVc0emNqa3hkMlpyUVVsblRYTnpLd3BtYzNOMU5WTk1VV3R1TjFkRVZFdFlaMjkzTjFONFlraFpVMXBxTTNscmVFRnlWbTUxZWtWM1EyZFpTVXR2V2tsNmFqQkZRWGROUkdGQlFYZGFVVWw0Q2tGUWFsTkhaR1JNU1haNVZVMUhTV3RhSzNVMlNtaEZPWEF4VG1wME0yUkZkSGRaYTAxNFptNUZWakpyTjAxSU1VSldiWGhuT1ZCelNtcHhlV05tYVNzS1pVRkpkMFJoUzI0eVEyUlBlRXR6ZUdObldVNXBORWgyYVVWdVduRjRiV1ZFZVc4eVdVWkpkSHB3U0daSlRWRnRZMUpUYkRreFZXVlBVME00SzFCMVJ3cG5kMDFMQ2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIiwic2lnIjoiVFVWVlEwbERWV2hCVm1WM1puZExiR3MxWmxaNmNGSkVWVkJvUlhjNVR6aEpNbkI0UXpWdVZHNVFabGxFUW5OUFFXbEZRVEJhUm5Gek9UbFJaMUk1YlVGMFJrMVhkRmR5VDJwdFZVTTBOM3BuWVc5dmJFdEpiMHhJTDA5M1pFMDkifV19LCJoYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZGNiNDkyNTljODY2MDdjMzQ2MzVkYWJiNDQzMWYwNjVlOWE3YTczNDcwNGNiNzNmMGFhMGY2YWFhMzg5NmEwNCJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjY4ZTY1NmIyNTFlNjdlODM1OGJlZjg0ODNhYjBkNTFjNjYxOWYzZTdhMWE5ZjBlNzU4MzhkNDFmZjM2OGY3MjgifX19fQ==" + } + ], + "timestampVerificationData": { + "rfc3161Timestamps": [] + }, + "x509CertificateChain": { + "certificates": [ + { + "rawBytes": "MIICnzCCAiWgAwIBAgIUVHwehOtGn4KSD1H8RI581MfbyewwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjIxMTA4MjI1ODA2WhcNMjIxMTA4MjMwODA2WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGg6Hjxt2UNiJ1kwwq5XQIIwMZnJfVQ3bF01uZKteMdcV/3qhCmWOecoxRqwrbYTshGg9NyXcBbve6zKwZVTLeqOCAUQwggFAMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU7WpR60sCpgfu04wcsjvCFxt0fMkwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0RAQH/BBUwE4ERYnJpYW5AZGVoYW1lci5jb20wLAYKKwYBBAGDvzABAQQeaHR0cHM6Ly9naXRodWIuY29tL2xvZ2luL29hdXRoMIGJBgorBgEEAdZ5AgQCBHsEeQB3AHUA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGEWXcR8AAABAMARjBEAiBRTrGE5Y1EnYniaJB+nsv89VaYx3QZjocEin3r91wfkAIgMss+fssu5SLQkn7WDTKXgow7SxbHYSZj3ykxArVnuzEwCgYIKoZIzj0EAwMDaAAwZQIxAPjSGddLIvyUMGIkZ+u6JhE9p1Njt3dEtwYkMxfnEV2k7MH1BVmxg9PsJjqycfi+eAIwDaKn2CdOxKsxcgYNi4HviEnZqxmeDyo2YFItzpHfIMQmcRSl91UeOSC8+PuGgwMK" + }, + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + } + }, + "dsseEnvelope": { + "payload": "aGVsbG8sIHdvcmxkIQ==", + "payloadType": "text/plain", + "signatures": [ + { + "sig": "NEUCICUhAVewfwKlk5fVzpRDUPhEw9O8I2pxC5nTnPfYDBsOAiEA0ZFqs99QgR9mAtFMWtWrOjmUC47zgaoolKIoLH/OwdM=", + "keyid": "" + } + ] + } +} diff --git a/pkg/cmd/attestation/test/output.go b/pkg/cmd/attestation/test/output.go new file mode 100644 index 000000000..fff3decd3 --- /dev/null +++ b/pkg/cmd/attestation/test/output.go @@ -0,0 +1,16 @@ +package test + +import "os" + +func SuppressAndRestoreOutput() func() { + null, _ := os.Open(os.DevNull) + stdOut := os.Stdout + stdErr := os.Stderr + os.Stdout = null + os.Stderr = null + return func() { + defer null.Close() + os.Stdout = stdOut + os.Stderr = stdErr + } +} diff --git a/pkg/cmd/attestation/verification/attestation.go b/pkg/cmd/attestation/verification/attestation.go new file mode 100644 index 000000000..eca86adf7 --- /dev/null +++ b/pkg/cmd/attestation/verification/attestation.go @@ -0,0 +1,112 @@ +package verification + +import ( + "bufio" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/cli/cli/v2/pkg/cmd/attestation/api" + protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" + "github.com/sigstore/sigstore-go/pkg/bundle" +) + +var ErrLocalAttestations = errors.New("failed to load local attestations") + +type FetchAttestationsConfig struct { + APIClient api.Client + BundlePath string + Digest string + Limit int + Owner string + Repo string +} + +func (c *FetchAttestationsConfig) IsBundleProvided() bool { + return c.BundlePath != "" +} + +func GetAttestations(c FetchAttestationsConfig) ([]*api.Attestation, error) { + if c.IsBundleProvided() { + return GetLocalAttestations(c.BundlePath) + } + return getRemoteAttestations(c) +} + +// GetLocalAttestations returns a slice of attestations read from a local bundle file. +func GetLocalAttestations(path string) ([]*api.Attestation, error) { + fileExt := filepath.Ext(path) + switch fileExt { + case ".json": + attestations, err := loadBundleFromJSONFile(path) + if err != nil { + return nil, fmt.Errorf("bundle could not be loaded from JSON file: %w", err) + } + return attestations, nil + case ".jsonl": + attestations, err := loadBundlesFromJSONLinesFile(path) + if err != nil { + return nil, fmt.Errorf("bundles could not be loaded from JSON lines file: %w", err) + } + return attestations, nil + } + return nil, ErrLocalAttestations +} + +func loadBundleFromJSONFile(path string) ([]*api.Attestation, error) { + localAttestation, err := bundle.LoadJSONFromPath(path) + if err != nil { + return nil, err + } + + return []*api.Attestation{{Bundle: localAttestation}}, nil +} + +func loadBundlesFromJSONLinesFile(path string) ([]*api.Attestation, error) { + file, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("could not open file: %w", err) + } + defer file.Close() + + attestations := []*api.Attestation{} + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + b := scanner.Bytes() + var bundle bundle.ProtobufBundle + bundle.Bundle = new(protobundle.Bundle) + err = bundle.UnmarshalJSON(b) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal bundle from JSON: %w", err) + } + a := api.Attestation{Bundle: &bundle} + attestations = append(attestations, &a) + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return attestations, nil +} + +func getRemoteAttestations(c FetchAttestationsConfig) ([]*api.Attestation, error) { + // check if Repo is set first because if Repo has been set, Owner will be set using the value of Repo. + // If Repo is not set, the field will remain empty. It will not be populated using the value of Owner. + if c.Repo != "" { + attestations, err := c.APIClient.GetByRepoAndDigest(c.Repo, c.Digest, c.Limit) + if err != nil { + return nil, fmt.Errorf("failed to fetch attestations from %s: %w", c.Repo, err) + } + return attestations, nil + } else if c.Owner != "" { + attestations, err := c.APIClient.GetByOwnerAndDigest(c.Owner, c.Digest, c.Limit) + if err != nil { + return nil, fmt.Errorf("failed to fetch attestations from %s: %w", c.Owner, err) + } + return attestations, nil + } + return nil, fmt.Errorf("owner or repo must be provided") +} diff --git a/pkg/cmd/attestation/verification/attestation_test.go b/pkg/cmd/attestation/verification/attestation_test.go new file mode 100644 index 000000000..8c4cf34df --- /dev/null +++ b/pkg/cmd/attestation/verification/attestation_test.go @@ -0,0 +1,49 @@ +package verification + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLoadBundlesFromJSONLinesFile(t *testing.T) { + path := "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl" + attestations, err := loadBundlesFromJSONLinesFile(path) + + assert.NoError(t, err) + assert.Len(t, attestations, 2) +} + +func TestLoadBundleFromJSONFile(t *testing.T) { + path := "../test/data/sigstore-js-2.1.0-bundle.json" + attestations, err := loadBundleFromJSONFile(path) + + assert.NoError(t, err) + assert.Len(t, attestations, 1) +} + +func TestGetLocalAttestations(t *testing.T) { + t.Run("with JSON file containing one bundle", func(t *testing.T) { + path := "../test/data/sigstore-js-2.1.0-bundle.json" + attestations, err := GetLocalAttestations(path) + + assert.NoError(t, err) + assert.Len(t, attestations, 1) + }) + + t.Run("with JSON lines file containing multiple bundles", func(t *testing.T) { + path := "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl" + attestations, err := GetLocalAttestations(path) + + assert.NoError(t, err) + assert.Len(t, attestations, 2) + }) + + t.Run("with file with unrecognized extension", func(t *testing.T) { + path := "../test/data/sigstore-js-2.1.0-bundles.tgz" + attestations, err := GetLocalAttestations(path) + + assert.ErrorIs(t, err, ErrLocalAttestations) + assert.Nil(t, attestations) + }) +} diff --git a/pkg/cmd/attestation/verification/embed/tuf-repo.github.com/root.json b/pkg/cmd/attestation/verification/embed/tuf-repo.github.com/root.json new file mode 100644 index 000000000..2990a2665 --- /dev/null +++ b/pkg/cmd/attestation/verification/embed/tuf-repo.github.com/root.json @@ -0,0 +1,163 @@ +{ + "signatures": [ + { + "keyid": "a10513a5ab61acd0c6b6fbe0504856ead18f3b17c4fabbe3fa848c79a5a187cf", + "sig": "304402203c8f5f7443f7052923e82f9ca0b1bb61a33498444076a2f43e1285a47f6e562d022014de99a7e5413440896b6804944e3c49390cfe6e617211b8dc42a8e67675bc13" + }, + { + "keyid": "d6a89e23fb22801a0d1186bf1bdd007e228f65a8aa9964d24d06cb5fbb0ce91c", + "sig": "3044022009a20307f974af7e05cc9564eea497f45062e3b21272d1062713b3d22c868298022059d032ad973a28bdbd03959cf96b21398b6b6e2ca618c17ce6c13712246343a2" + }, + { + "keyid": "539dde44014c850fe6eeb8b299eb7dae2e1f4bf83454b949e98aa73542cdc65a", + "sig": "3045022100edd270d36d0c8468b9a1f2ef1c81a270c72ffd50c65bca0ed1ebd424df09f64b022002b27ffafd7bc5bdfc25281b5b9b597cf2d67d4eeb4af2ff45eb3e666b860c21" + }, + { + "keyid": "88737ccdac7b49cc237e9aaead81be2a40278b886a693d8149a19cf543f093d3", + "sig": "30460221008d7d95434e576d5876b2db30fd645505ca546618bbc7a8e4b39f64e6a36df9ad022100c00a5294e4ddd02d48d28918b87a06bdfdeccd0618ecdcec29bb2597a05fe474" + }, + { + "keyid": "5e01c9a0b2641a8965a4a74e7df0bc7b2d8278a2c3ca0cf7a3f2f783d3c69800", + "sig": "30450220215fb3d19d94560a3a2a6067a71c92daf867d13700c9500c4c32d8009a48a634022100df9fb6cee786313bf6c363daac4de39b3dd531f211f81d2391c41bd2d0f91a80" + }, + { + "keyid": "4f4d1dd75f2d7f3860e3a068d7bed90dec5f0faafcbe1ace7fb7d95d29e07228", + "sig": "304502204091ac5e61b6462d262ecc8442781dd09843bed39942a95a4884c8c6a2c212ef022100dcee86392748f48950d04d539ac1a6643ed1f0b4bd6856f8aeb5a135826c846f" + }, + { + "keyid": "eb8eff37f93af2faaba519f341decec3cecd3eeafcace32966db9723842c8a62", + "sig": "30460221009188548601a43b501223caeefca4876ae892e18a85c885004b4c8aeeb05a4421022100abdcc72d94597f8297d6297897ff96f285168dbe6b3d57f846dbc7a2948d2935" + }, + { + "keyid": "8b498a80a1b7af188c10c9abdf6aade81d14faaffcde2abcd6063baa673ebd12", + "sig": "3046022100b440561545d48759dc4140cda9f8af7c9405a101d6136dd0a26edd6562b7064f022100cafa917ed90350494e47d226b64a8ec63ef5ceebb8ba4d2dec2ce018e4ad402a" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": true, + "expires": "2024-06-23T08:29:18Z", + "keys": { + "4f4d1dd75f2d7f3860e3a068d7bed90dec5f0faafcbe1ace7fb7d95d29e07228": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENki7aZVips5SgRzCd/Om0CGzQKY/\nnv84giqVDmdwb2ys82Z6soFLasvYYEEQcwqaC170n9gr93wHUgPc796uJA==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@ashtom" + }, + "539dde44014c850fe6eeb8b299eb7dae2e1f4bf83454b949e98aa73542cdc65a": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAElD0o2sOZN9n3RKQ7PtMLAoXj+2Ai\nn4PKT/pfnzDlNLrD3VTQwCc4sR4t+OLu4KQ+qk+kXkR9YuBsu3bdJZ1OWw==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@nerdneha" + }, + "5e01c9a0b2641a8965a4a74e7df0bc7b2d8278a2c3ca0cf7a3f2f783d3c69800": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC9RNAsuDCNO6T7qA7Y5F8orw2tIW\nr7rUr4ffxvzTMrbkVtjR/trtE0q0+T0zQ8TWLyI6EYMwb947ej2ItfkOyA==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@jacobdepriest" + }, + "88737ccdac7b49cc237e9aaead81be2a40278b886a693d8149a19cf543f093d3": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBagkskNOpOTbetTX5CdnvMy+LiWn\nonRrNrqAHL4WgiebH7Uig7GLhC3bkeA/qgb926/vr9qhOPG9Buj2HatrPw==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@gregose" + }, + "8b498a80a1b7af188c10c9abdf6aade81d14faaffcde2abcd6063baa673ebd12": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7IEoVNwrprchXGhT5sAhSax7SOd3\n8duuISghCzfmHdKJWSbV2wJRamRiUVRtmA83K/qm5cT20WXMCT5QeM/D3A==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@trevrosen" + }, + "a10513a5ab61acd0c6b6fbe0504856ead18f3b17c4fabbe3fa848c79a5a187cf": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC2wJ3xscyXxBLybJ9FVjwkyQMe53\nRHUz77AjMO8MzVaT8xw6ZvJqdNZiytYtigWULlINxw6frNsWJKb/f7lC8A==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@kommendorkapten" + }, + "d6a89e23fb22801a0d1186bf1bdd007e228f65a8aa9964d24d06cb5fbb0ce91c": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDdORwcruW3gqAgaLjH/nNdGMB4kQ\nAvA+wD6DyO4P/wR8ee2ce83NZHq1ZADKhve0rlYKaKy3CqyQ5SmlZ36Zhw==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@krukow" + }, + "eb8eff37f93af2faaba519f341decec3cecd3eeafcace32966db9723842c8a62": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENynVdQnM9h7xU71G7PiJpQaDemub\nkbjsjYwLlPJTQVuxQO8WeIpJf8MEh5rf01t2dDIuCsZ5gRx+QvDv0UzfsA==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@mph4" + }, + "eb9799b483affac9da87ef4c9ea467928415c961349e607e5e6e485679b07f8f": { + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENKNcNcX+d73lS1TRFb9Vnp8JvOoh\nzYQ+in43iGenbG8RGo9L/6FJ2hoRbVU6xskvyuErcdPbCdI4GxrQ5i8hkw==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-online-uri": "azurekms://production-tuf-root.vault.azure.net/keys/Online-Key/aaf375fd8ed24acb949a5cc173700b05" + } + }, + "roles": { + "root": { + "keyids": [ + "a10513a5ab61acd0c6b6fbe0504856ead18f3b17c4fabbe3fa848c79a5a187cf", + "4f4d1dd75f2d7f3860e3a068d7bed90dec5f0faafcbe1ace7fb7d95d29e07228", + "88737ccdac7b49cc237e9aaead81be2a40278b886a693d8149a19cf543f093d3", + "5e01c9a0b2641a8965a4a74e7df0bc7b2d8278a2c3ca0cf7a3f2f783d3c69800", + "d6a89e23fb22801a0d1186bf1bdd007e228f65a8aa9964d24d06cb5fbb0ce91c", + "eb8eff37f93af2faaba519f341decec3cecd3eeafcace32966db9723842c8a62", + "8b498a80a1b7af188c10c9abdf6aade81d14faaffcde2abcd6063baa673ebd12", + "539dde44014c850fe6eeb8b299eb7dae2e1f4bf83454b949e98aa73542cdc65a" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "eb9799b483affac9da87ef4c9ea467928415c961349e607e5e6e485679b07f8f" + ], + "threshold": 1, + "x-tuf-on-ci-expiry-period": 21, + "x-tuf-on-ci-signing-period": 7 + }, + "targets": { + "keyids": [ + "a10513a5ab61acd0c6b6fbe0504856ead18f3b17c4fabbe3fa848c79a5a187cf", + "4f4d1dd75f2d7f3860e3a068d7bed90dec5f0faafcbe1ace7fb7d95d29e07228", + "88737ccdac7b49cc237e9aaead81be2a40278b886a693d8149a19cf543f093d3", + "5e01c9a0b2641a8965a4a74e7df0bc7b2d8278a2c3ca0cf7a3f2f783d3c69800", + "d6a89e23fb22801a0d1186bf1bdd007e228f65a8aa9964d24d06cb5fbb0ce91c", + "eb8eff37f93af2faaba519f341decec3cecd3eeafcace32966db9723842c8a62", + "8b498a80a1b7af188c10c9abdf6aade81d14faaffcde2abcd6063baa673ebd12", + "539dde44014c850fe6eeb8b299eb7dae2e1f4bf83454b949e98aa73542cdc65a" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "eb9799b483affac9da87ef4c9ea467928415c961349e607e5e6e485679b07f8f" + ], + "threshold": 1, + "x-tuf-on-ci-expiry-period": 7, + "x-tuf-on-ci-signing-period": 6 + } + }, + "spec_version": "1.0.31", + "version": 1, + "x-tuf-on-ci-expiry-period": 240, + "x-tuf-on-ci-signing-period": 60 + } +} diff --git a/pkg/cmd/attestation/verification/policy.go b/pkg/cmd/attestation/verification/policy.go index eb384fe4e..91b54c75e 100644 --- a/pkg/cmd/attestation/verification/policy.go +++ b/pkg/cmd/attestation/verification/policy.go @@ -1,18 +1,20 @@ -package verify +package verification import ( "encoding/hex" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" + "github.com/sigstore/sigstore-go/pkg/verify" ) // BuildDigestPolicyOption builds a verify.ArtifactPolicyOption // from the given artifact digest and digest algorithm -func BuildDigestPolicyOption(artifactDigest, digestAlgorithm string) (verify.ArtifactPolicyOption, error) { +func BuildDigestPolicyOption(a artifact.DigestedArtifact) (verify.ArtifactPolicyOption, error) { // sigstore-go expects the artifact digest to be decoded from hex - decoded, err := hex.DecodeString(artifactDigest) + decoded, err := hex.DecodeString(a.Digest()) if err != nil { return nil, err } - return verify.WithArtifactDigest(digestAlgorithm, decoded), nil + return verify.WithArtifactDigest(a.Algorithm(), decoded), nil } diff --git a/pkg/cmd/attestation/verification/policy_test.go b/pkg/cmd/attestation/verification/policy_test.go deleted file mode 100644 index 121723428..000000000 --- a/pkg/cmd/attestation/verification/policy_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package verify - -import ( - "testing" - - "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" - "github.com/cli/cli/v2/pkg/cmd/attestation/github" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestBuildPolicy(t *testing.T) { - opts := &VerifyOpts{ - ArtifactPath: "../../test/data/public-good/sigstore-js-2.1.0.tgz", - BundlePath: "../../test/data/public-good/sigstore-js-2.1.0-bundle.json", - GitHubClient: github.NewTestClient(), - OIDCIssuer: GitHubOIDCIssuer, - SAN: "fake san", - Owner: "github", - } - - artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm) - require.NoError(t, err) - - _, err = buildVerifyPolicy(opts, artifact.Digest()) - assert.NoError(t, err) -} diff --git a/pkg/cmd/attestation/verification/sigstore.go b/pkg/cmd/attestation/verification/sigstore.go index 824f276f8..f68821d6f 100644 --- a/pkg/cmd/attestation/verification/sigstore.go +++ b/pkg/cmd/attestation/verification/sigstore.go @@ -1,9 +1,10 @@ -package verify +package verification import ( "fmt" - "github.com/cli/cli/v2/pkg/cmd/attestation/github" + "github.com/cli/cli/v2/pkg/cmd/attestation/api" + "github.com/cli/cli/v2/pkg/cmd/attestation/logger" "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore-go/pkg/root" @@ -16,9 +17,20 @@ const ( GitHubIssuerOrg = "GitHub, Inc." ) +// AttestationProcessingResult captures processing a given attestation's signature verification and policy evaluation +type AttestationProcessingResult struct { + Attestation *api.Attestation + VerificationResult *verify.VerificationResult +} + +type SigstoreResults struct { + VerifyResults []*AttestationProcessingResult + Error error +} + type SigstoreConfig struct { CustomTrustedRoot string - Logger *output.Logger + Logger *logger.Logger NoPublicGood bool } @@ -28,7 +40,7 @@ type SigstoreVerifier struct { customVerifier *verify.SignedEntityVerifier policy verify.PolicyBuilder onlyVerifyWithGithub bool - Logger *output.Logger + Logger *logger.Logger } // NewSigstoreVerifier creates a new SigstoreVerifier struct @@ -91,7 +103,7 @@ func (v *SigstoreVerifier) chooseVerifier(b *bundle.ProtobufBundle) (*verify.Sig return nil, "", fmt.Errorf("leaf certificate issuer is not recognized") } -func (v *SigstoreVerifier) Verify(attestations []*github.Attestation) *SigstoreResults { +func (v *SigstoreVerifier) Verify(attestations []*api.Attestation) *SigstoreResults { // initialize the processing results before attempting to verify // with multiple verifiers results := make([]*AttestationProcessingResult, len(attestations)) diff --git a/pkg/cmd/attestation/verification/sigstore_test.go b/pkg/cmd/attestation/verification/sigstore_test.go index 4fb3e8e07..aa9e2235b 100644 --- a/pkg/cmd/attestation/verification/sigstore_test.go +++ b/pkg/cmd/attestation/verification/sigstore_test.go @@ -1,53 +1,49 @@ -package verify +package verification import ( "testing" + "github.com/cli/cli/v2/pkg/cmd/attestation/api" "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" - "github.com/cli/cli/v2/pkg/cmd/attestation/github" + "github.com/cli/cli/v2/pkg/cmd/attestation/logger" "github.com/cli/cli/v2/pkg/cmd/attestation/test" + "github.com/sigstore/sigstore-go/pkg/verify" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -var logger = output.NewDefaultLogger() - -var publicGoodOpts = Options{ - ArtifactPath: test.NormalizeRelativePath("../../test/data/public-good/sigstore-js-2.1.0.tgz"), - BundlePath: test.NormalizeRelativePath("../../test/data/public-good/sigstore-js-2.1.0-bundle.json"), - DigestAlgorithm: "sha512", - GitHubClient: github.NewTestClient(), - Logger: logger, - OIDCIssuer: GitHubOIDCIssuer, - Owner: "sigstore", - Limit: 30, - OCIClient: oci.NewMockClient(), - SANRegex: "^https://github.com/sigstore/", -} +var ( + artifactPath = test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz") + bundlePath = test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json") + ociClient = oci.NewMockClient() +) func TestVerify_PublicGoodSuccess(t *testing.T) { - t.Skip() res := test.SuppressAndRestoreOutput() defer res() - artifact, err := artifact.NewDigestedArtifact(publicGoodOpts.OCIClient, publicGoodOpts.ArtifactPath, publicGoodOpts.DigestAlgorithm) + artifact, err := artifact.NewDigestedArtifact(ociClient, artifactPath, "sha512") require.NoError(t, err) - attestations, err := getAttestations(&publicGoodOpts, artifact.Digest()) - require.NoError(t, err) + c := FetchAttestationsConfig{ + APIClient: api.NewTestClient(), + BundlePath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"), + Digest: artifact.DigestWithAlg(), + Owner: "sigstore", + } - policy, err := buildVerifyPolicy(&publicGoodOpts, artifact.Digest()) + attestations, err := GetAttestations(c) require.NoError(t, err) config := SigstoreConfig{ - CustomTrustedRoot: publicGoodOpts.CustomTrustedRoot, - Logger: publicGoodOpts.Logger, - NoPublicGood: publicGoodOpts.NoPublicGood, + Logger: logger.NewDefaultLogger(), + NoPublicGood: false, } - v, err := NewSigstoreVerifier(config, policy) + v, err := NewSigstoreVerifier(config, verify.PolicyBuilder{}) require.NoError(t, err) resp := v.Verify(attestations) @@ -62,32 +58,27 @@ func TestVerify_PublicGoodSuccess(t *testing.T) { } func TestVerify_PublicGoodFail_WithNoPublicGoodFlag(t *testing.T) { - t.Skip() res := test.SuppressAndRestoreOutput() defer res() - opts := publicGoodOpts - opts.NoPublicGood = true - - err := opts.AreFlagsValid() + artifact, err := artifact.NewDigestedArtifact(ociClient, artifactPath, "sha512") require.NoError(t, err) - artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm) - require.NoError(t, err) + c := FetchAttestationsConfig{ + BundlePath: bundlePath, + Digest: artifact.Digest(), + Owner: "sigstore", + } - attestations, err := getAttestations(&opts, artifact.Digest()) - require.NoError(t, err) - - policy, err := buildVerifyPolicy(&publicGoodOpts, artifact.Digest()) + attestations, err := GetAttestations(c) require.NoError(t, err) config := SigstoreConfig{ - CustomTrustedRoot: opts.CustomTrustedRoot, - Logger: opts.Logger, - NoPublicGood: opts.NoPublicGood, + Logger: logger.NewDefaultLogger(), + NoPublicGood: true, } - v, err := NewSigstoreVerifier(config, policy) + v, err := NewSigstoreVerifier(config, verify.PolicyBuilder{}) require.NoError(t, err) resp := v.Verify(attestations) @@ -99,28 +90,24 @@ func TestVerify_Failure(t *testing.T) { res := test.SuppressAndRestoreOutput() defer res() - opts := publicGoodOpts - opts.DigestAlgorithm = "sha256" - - err := opts.AreFlagsValid() + artifact, err := artifact.NewDigestedArtifact(ociClient, artifactPath, "sha512") require.NoError(t, err) - artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm) - require.NoError(t, err) + c := FetchAttestationsConfig{ + BundlePath: bundlePath, + Digest: artifact.Digest(), + Owner: "sigstore", + } - attestations, err := getAttestations(&opts, artifact.Digest()) - require.NoError(t, err) - - policy, err := buildVerifyPolicy(&opts, artifact.Digest()) + attestations, err := GetAttestations(c) require.NoError(t, err) config := SigstoreConfig{ - CustomTrustedRoot: opts.CustomTrustedRoot, - Logger: opts.Logger, - NoPublicGood: opts.NoPublicGood, + Logger: logger.NewDefaultLogger(), + NoPublicGood: true, } - v, err := NewSigstoreVerifier(config, policy) + v, err := NewSigstoreVerifier(config, verify.PolicyBuilder{}) require.NoError(t, err) resp := v.Verify(attestations) diff --git a/pkg/cmd/attestation/verification/tuf.go b/pkg/cmd/attestation/verification/tuf.go new file mode 100644 index 000000000..2f24ec119 --- /dev/null +++ b/pkg/cmd/attestation/verification/tuf.go @@ -0,0 +1,55 @@ +package verification + +import ( + "embed" + "fmt" + "os" + + "github.com/sigstore/sigstore-go/pkg/tuf" +) + +//go:embed embed +var embeddedRepos embed.FS + +const GitHubTUFMirror = "https://tuf-repo.github.com" + +// readEmbeddedRoot reads the embedded trust anchor for the given URL +func readEmbeddedRoot(url string) ([]byte, error) { + // the embed file system always uses forward slashes, even on Windows + p := fmt.Sprintf("embed/%s/root.json", tuf.URLToPath(url)) + + b, err := embeddedRepos.ReadFile(p) + if err != nil { + return nil, err + } + + return b, nil +} + +func DefaultOptionsWithCacheSetting() *tuf.Options { + opts := tuf.DefaultOptions() + + // The CODESPACES environment variable will be set to true in a Codespaces workspace + if os.Getenv("CODESPACES") == "true" { + // if the tool is being used in a Codespace, disable the local cache + // because there is a permissions issue preventing the tuf library + // from writing the Sigstore cache to the home directory + opts.DisableLocalCache = true + } + + return opts +} + +func GitHubTUFOptions() (*tuf.Options, error) { + opts := DefaultOptionsWithCacheSetting() + + // replace root and mirror url + root, err := readEmbeddedRoot(GitHubTUFMirror) + if err != nil { + return nil, err + } + opts.Root = root + opts.RepositoryBaseURL = GitHubTUFMirror + + return opts, nil +} diff --git a/pkg/cmd/attestation/verification/tuf_test.go b/pkg/cmd/attestation/verification/tuf_test.go new file mode 100644 index 000000000..89e52a7bc --- /dev/null +++ b/pkg/cmd/attestation/verification/tuf_test.go @@ -0,0 +1,18 @@ +package verification + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGitHubTUFOptions(t *testing.T) { + os.Setenv("CODESPACES", "true") + opts, err := GitHubTUFOptions() + assert.NoError(t, err) + + assert.Equal(t, GitHubTUFMirror, opts.RepositoryBaseURL) + assert.NotNil(t, opts.Root) + assert.True(t, opts.DisableLocalCache) +} diff --git a/pkg/cmd/attestation/verify/options.go b/pkg/cmd/attestation/verify/options.go index bffce4b52..19aa1c23e 100644 --- a/pkg/cmd/attestation/verify/options.go +++ b/pkg/cmd/attestation/verify/options.go @@ -7,15 +7,8 @@ import ( "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/digest" "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" - "github.com/cli/cli/v2/pkg/cmd/attestation/github" -) - -const ( - // OfflineMode is used when the user provides a bundle path - OfflineMode = "offline" - // OnlineMode is used when the user does not provide a bundle path - // An owner or repo scope is used to fetch attestations from GitHub - OnlineMode = "online" + "github.com/cli/cli/v2/pkg/cmd/attestation/api" + "github.com/cli/cli/v2/pkg/cmd/attestation/logger" ) // Options captures the options for the verify command @@ -34,35 +27,12 @@ type Options struct { SAN string SANRegex string Verbose bool - GitHubClient github.Client - Logger *output.Logger + APIClient api.Client + Logger *logger.Logger Limit int OCIClient oci.Client } -// Mode returns a string indicating either online or offline mode -func (opts *Options) Mode() string { - if opts.BundlePath == "" { - return OnlineMode - } - return OfflineMode -} - -// ConfigureGitHubClient configures a live GitHub client if the tool -// is running in online mode -func (opts *Options) ConfigureGitHubClient(version, date string) error { - if opts.Mode() == OfflineMode { - return nil - } - - client, err := github.NewLiveClient(version, date) - if err != nil { - return err - } - opts.GitHubClient = client - return nil -} - // ConfigureOCIClient configures an OCI client func (opts *Options) ConfigureOCIClient() { opts.OCIClient = oci.NewLiveClient() @@ -71,7 +41,7 @@ func (opts *Options) ConfigureOCIClient() { // ConfigureLogger configures a logger using configuration provided // through the options func (opts *Options) ConfigureLogger() { - opts.Logger = output.NewLogger(opts.Quiet, opts.Verbose) + opts.Logger = logger.NewLogger(opts.Quiet, opts.Verbose) } // Clean cleans the file path option values diff --git a/pkg/cmd/attestation/verify/options_test.go b/pkg/cmd/attestation/verify/options_test.go index 25fd3cb35..6caf6f625 100644 --- a/pkg/cmd/attestation/verify/options_test.go +++ b/pkg/cmd/attestation/verify/options_test.go @@ -9,8 +9,8 @@ import ( ) var ( - publicGoodArtifactPath = test.NormalizeRelativePath("../../test/data/public-good/sigstore-js-2.1.0.tgz") - publicGoodBundlePath = test.NormalizeRelativePath("../../test/data/public-good/sigstore-js-2.1.0-bundle.json") + publicGoodArtifactPath = test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz") + publicGoodBundlePath = test.NormalizeRelativePath("../test/data/psigstore-js-2.1.0-bundle.json") ) func TestAreFlagsValid(t *testing.T) { @@ -195,35 +195,3 @@ func TestClean(t *testing.T) { opts.Clean() assert.Equal(t, validBundlePath, opts.BundlePath) } - -func TestMode(t *testing.T) { - t.Run("run in offline mode when bundle is provided", func(t *testing.T) { - opts := Options{ - ArtifactPath: publicGoodArtifactPath, - BundlePath: publicGoodBundlePath, - } - - assert.Equal(t, OfflineMode, opts.Mode()) - }) - - t.Run("run in offline mode when bundle and repo are provided", func(t *testing.T) { - opts := &Options{ - ArtifactPath: publicGoodArtifactPath, - BundlePath: publicGoodBundlePath, - DigestAlgorithm: "sha512", - Repo: "sigstore/sigstore-js", - } - - assert.Equal(t, OfflineMode, opts.Mode()) - }) - - t.Run("run in online mode when repo are provided", func(t *testing.T) { - opts := &Options{ - ArtifactPath: publicGoodArtifactPath, - DigestAlgorithm: "sha512", - Repo: "sigstore/sigstore-js", - } - - assert.Equal(t, OnlineMode, opts.Mode()) - }) -} diff --git a/pkg/cmd/attestation/verify/policy.go b/pkg/cmd/attestation/verify/policy.go index 4b31c154e..6967ff222 100644 --- a/pkg/cmd/attestation/verify/policy.go +++ b/pkg/cmd/attestation/verify/policy.go @@ -6,6 +6,7 @@ import ( "github.com/sigstore/sigstore-go/pkg/verify" "github.com/sigstore/sigstore-go/pkg/fulcio/certificate" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" "github.com/cli/cli/v2/pkg/cmd/attestation/verification" ) @@ -16,19 +17,20 @@ const ( GitHubRunner = "github-hosted" ) -func buildSANMatcher(opts *Options) (verify.SubjectAlternativeNameMatcher, error) { - if opts.SAN != "" || opts.SANRegex != "" { - sanMatcher, err := verify.NewSANMatcher(opts.SAN, "", opts.SANRegex) - if err != nil { - return verify.SubjectAlternativeNameMatcher{}, err - } - return sanMatcher, nil +func buildSANMatcher(san, sanRegex string) (verify.SubjectAlternativeNameMatcher, error) { + if san == "" && sanRegex == "" { + return verify.SubjectAlternativeNameMatcher{}, nil } - return verify.SubjectAlternativeNameMatcher{}, nil + + sanMatcher, err := verify.NewSANMatcher(san, "", sanRegex) + if err != nil { + return verify.SubjectAlternativeNameMatcher{}, err + } + return sanMatcher, nil } func buildCertificateIdentityOption(opts *Options, runnerEnv string) (verify.PolicyOption, error) { - sanMatcher, err := buildSANMatcher(opts) + sanMatcher, err := buildSANMatcher(opts.SAN, opts.SANRegex) if err != nil { return nil, err } @@ -67,8 +69,8 @@ func buildVerifyCertIdOption(opts *Options) (verify.PolicyOption, error) { return withAnyRunner, nil } -func buildVerifyPolicy(opts *Options, artifactDigest string) (verify.PolicyBuilder, error) { - artifactDigestPolicyOption, err := verification.BuildDigestPolicyOption(artifactDigest, opts.DigestAlgorithm) +func buildVerifyPolicy(opts *Options, a artifact.DigestedArtifact) (verify.PolicyBuilder, error) { + artifactDigestPolicyOption, err := verification.BuildDigestPolicyOption(a) if err != nil { return verify.PolicyBuilder{}, err } diff --git a/pkg/cmd/attestation/verify/policy_test.go b/pkg/cmd/attestation/verify/policy_test.go new file mode 100644 index 000000000..a7e3faf22 --- /dev/null +++ b/pkg/cmd/attestation/verify/policy_test.go @@ -0,0 +1,29 @@ +package verify + +import ( + "testing" + + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBuildPolicy(t *testing.T) { + ociClient := oci.NewMockClient() + artifactPath := "../test/data/sigstore-js-2.1.0.tgz" + digestAlg := "sha256" + + artifact, err := artifact.NewDigestedArtifact(ociClient, artifactPath, digestAlg) + require.NoError(t, err) + + opts := &Options{ + ArtifactPath: artifactPath, + Owner: "sigstore", + SANRegex: "^https://github.com/sigstore/", + } + + _, err = buildVerifyPolicy(opts, *artifact) + assert.NoError(t, err) +} diff --git a/pkg/cmd/attestation/verify/verify.go b/pkg/cmd/attestation/verify/verify.go index f94dd4b63..3a9a47f41 100644 --- a/pkg/cmd/attestation/verify/verify.go +++ b/pkg/cmd/attestation/verify/verify.go @@ -1,17 +1,25 @@ package verify import ( + "encoding/json" + "errors" + "fmt" "os" - "github.com/cli/cli/v2/api" - "github.com/cli/cli/v2/pkg/cmd/attestation/github" + "github.com/cli/cli/v2/pkg/cmdutil" + "github.com/cli/cli/v2/pkg/cmd/attestation/api" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" + "github.com/cli/cli/v2/pkg/cmd/attestation/logger" + "github.com/cli/cli/v2/pkg/cmd/attestation/verification" "github.com/MakeNowJust/heredoc" "github.com/spf13/cobra" ) +var ErrNoMatchingSLSAPredicate = fmt.Errorf("the attestation does not have the expected SLSA predicate type: %s", SLSAPredicateType) + func NewVerifyCmd(f *cmdutil.Factory) *cobra.Command { - opts := &verify.Options{} + opts := &Options{} verifyCmd := &cobra.Command{ Use: "verify ", Args: cobra.ExactArgs(1), @@ -60,6 +68,8 @@ func NewVerifyCmd(f *cmdutil.Factory) *cobra.Command { // If an error is returned, its message will be printed to the terminal // along with information about how use the command PreRunE: func(cmd *cobra.Command, args []string) error { + opts.APIClient = api.NewLiveClient() + // Create a logger for use throughout the verify command opts.ConfigureLogger() @@ -86,7 +96,7 @@ func NewVerifyCmd(f *cmdutil.Factory) *cobra.Command { // when RunE is used, the command usage will be printed // We only want to print the error, not usage Run: func(cmd *cobra.Command, args []string) { - if err := runVerify(opts); err != nil { + if err := RunVerify(opts); err != nil { opts.Logger.Println(opts.Logger.ColorScheme.Redf("Failed to verify the artifact: %s", err.Error())) os.Exit(1) } @@ -99,24 +109,25 @@ func NewVerifyCmd(f *cmdutil.Factory) *cobra.Command { verifyCmd.Flags().StringVarP(&opts.Owner, "owner", "o", "", "GitHub organization to scope attestation lookup by") verifyCmd.Flags().StringVarP(&opts.Repo, "repo", "R", "", "Repository name in the format /") verifyCmd.MarkFlagsMutuallyExclusive("owner", "repo") + verifyCmd.MarkFlagsOneRequired("owner", "repo") verifyCmd.Flags().BoolVarP(&opts.NoPublicGood, "no-public-good", "", false, "Only verify attestations signed with GitHub's Sigstore instance") verifyCmd.Flags().BoolVarP(&opts.JsonResult, "json-result", "j", false, "Output verification result as JSON lines") verifyCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "If set to true, the CLI will not print any diagnostic logging.") verifyCmd.Flags().BoolVarP(&opts.Verbose, "verbose", "v", false, "If set to true, the CLI will print verbose diagnostic logging.") verifyCmd.MarkFlagsMutuallyExclusive("quiet", "verbose") verifyCmd.Flags().StringVarP(&opts.CustomTrustedRoot, "custom-trusted-root", "", "", "Path to a custom trustedroot.json file to use for verification") - verifyCmd.Flags().IntVarP(&opts.Limit, "limit", "L", github.DefaultLimit, "Maximum number of attestations to fetch") + verifyCmd.Flags().IntVarP(&opts.Limit, "limit", "L", api.DefaultLimit, "Maximum number of attestations to fetch") // policy enforcement flags verifyCmd.Flags().BoolVarP(&opts.DenySelfHostedRunner, "deny-self-hosted-runners", "", false, "Fail verification for attestations generated on self-hosted runners.") verifyCmd.Flags().StringVarP(&opts.SAN, "cert-identity", "", "", "Enforce that the certificate's subject alternative name matches the provided value exactly") verifyCmd.Flags().StringVarP(&opts.SANRegex, "cert-identity-regex", "i", "", "Enforce that the certificate's subject alternative name matches the provided regex") verifyCmd.MarkFlagsMutuallyExclusive("cert-identity", "cert-identity-regex") - verifyCmd.Flags().StringVarP(&opts.OIDCIssuer, "cert-oidc-issuer", "", verify.GitHubOIDCIssuer, "Issuer of the OIDC token") + verifyCmd.Flags().StringVarP(&opts.OIDCIssuer, "cert-oidc-issuer", "", GitHubOIDCIssuer, "Issuer of the OIDC token") return verifyCmd } -func runVerify(opts *Options) error { +func RunVerify(opts *Options) error { artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm) if err != nil { return fmt.Errorf("failed to digest artifact: %s", err) @@ -124,26 +135,33 @@ func runVerify(opts *Options) error { opts.Logger.Printf("Verifying attestations for the artifact found at %s\n", artifact.URL) - attestations, err := getAttestations(opts, artifact.DigestWithAlg()) + c := verification.FetchAttestationsConfig{ + APIClient: opts.APIClient, + Digest: artifact.DigestWithAlg(), + Limit: opts.Limit, + Owner: opts.Owner, + Repo: opts.Repo, + } + attestations, err := verification.GetAttestations(c) if err != nil { - if ok := errors.Is(err, github.ErrNoAttestations{}); ok { + if ok := errors.Is(err, api.ErrNoAttestations{}); ok { return fmt.Errorf("no attestations found for subject: %s", artifact.DigestWithAlg()) } return fmt.Errorf("failed to fetch attestations for subject: %s", artifact.DigestWithAlg()) } - policy, err := buildVerifyPolicy(opts, artifact.Digest()) + policy, err := buildVerifyPolicy(opts, *artifact) if err != nil { return fmt.Errorf("failed to build policy: %w", err) } - config := SigstoreConfig{ + config := verification.SigstoreConfig{ CustomTrustedRoot: opts.CustomTrustedRoot, Logger: opts.Logger, NoPublicGood: opts.NoPublicGood, } - sv, err := NewSigstoreVerifier(config, policy) + sv, err := verification.NewSigstoreVerifier(config, policy) if err != nil { return err } @@ -189,7 +207,7 @@ func runVerify(opts *Options) error { return nil } -func verifySLSAPredicateType(logger *output.Logger, apr []*AttestationProcessingResult) error { +func verifySLSAPredicateType(logger *logger.Logger, apr []*verification.AttestationProcessingResult) error { logger.VerbosePrintf("Evaluating attestations have valid SLSA predicate type...\n") for _, result := range apr { diff --git a/pkg/cmd/attestation/verify/verify_test.go b/pkg/cmd/attestation/verify/verify_test.go new file mode 100644 index 000000000..e263ddffe --- /dev/null +++ b/pkg/cmd/attestation/verify/verify_test.go @@ -0,0 +1,182 @@ +package verify + +import ( + "testing" + + "github.com/cli/cli/v2/pkg/cmd/attestation/api" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" + "github.com/cli/cli/v2/pkg/cmd/attestation/logger" + "github.com/cli/cli/v2/pkg/cmd/attestation/test" + "github.com/cli/cli/v2/pkg/cmd/attestation/verification" + + "github.com/in-toto/in-toto-golang/in_toto" + "github.com/sigstore/sigstore-go/pkg/verify" + + "github.com/stretchr/testify/assert" +) + +const ( + SigstoreSanValue = "https://github.com/sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main" + SigstoreSanRegex = "^https://github.com/sigstore/sigstore-js/" +) + +func TestRunVerify(t *testing.T) { + res := test.SuppressAndRestoreOutput() + defer res() + + logger := logger.NewDefaultLogger() + + publicGoodOpts := Options{ + ArtifactPath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz"), + BundlePath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"), + DigestAlgorithm: "sha512", + APIClient: api.NewTestClient(), + Logger: logger, + OCIClient: oci.NewMockClient(), + OIDCIssuer: GitHubOIDCIssuer, + Owner: "sigstore", + SANRegex: "^https://github.com/sigstore/", + } + + t.Run("with valid artifact and bundle", func(t *testing.T) { + assert.Nil(t, RunVerify(&publicGoodOpts)) + }) + + t.Run("with failing OCI artifact fetch", func(t *testing.T) { + opts := publicGoodOpts + opts.ArtifactPath = "oci://ghcr.io/github/test" + opts.OCIClient = oci.NewReferenceFailClient() + + err := RunVerify(&opts) + assert.Error(t, err) + assert.ErrorContains(t, err, "failed to digest artifact") + }) + + t.Run("with missing artifact path", func(t *testing.T) { + opts := publicGoodOpts + opts.ArtifactPath = "../test/data/non-existent-artifact.zip" + assert.Error(t, RunVerify(&opts)) + }) + + t.Run("with missing bundle path", func(t *testing.T) { + opts := publicGoodOpts + opts.BundlePath = "../test/data/non-existent-sigstoreBundle.json" + assert.Error(t, RunVerify(&opts)) + }) + + t.Run("with invalid signature", func(t *testing.T) { + opts := publicGoodOpts + opts.BundlePath = "../test/data/sigstoreBundle-invalid-signature.json" + + err := RunVerify(&opts) + assert.Error(t, err) + assert.ErrorContains(t, err, "at least one attestation failed to verify") + assert.ErrorContains(t, err, "verifying with issuer \"sigstore.dev\"") + }) + + t.Run("with owner", func(t *testing.T) { + opts := publicGoodOpts + opts.BundlePath = "" + opts.Owner = "github" + + assert.Nil(t, RunVerify(&opts)) + }) + + t.Run("with repo", func(t *testing.T) { + opts := publicGoodOpts + opts.BundlePath = "" + opts.Repo = "github/example" + + assert.Nil(t, RunVerify(&opts)) + }) + + t.Run("with invalid repo", func(t *testing.T) { + opts := publicGoodOpts + opts.BundlePath = "" + opts.Repo = "wrong/example" + opts.APIClient = api.NewFailTestClient() + + err := RunVerify(&opts) + assert.Error(t, err) + assert.ErrorContains(t, err, "failed to fetch attestations for subject") + }) + + t.Run("with invalid owner", func(t *testing.T) { + opts := publicGoodOpts + opts.BundlePath = "" + opts.APIClient = api.NewFailTestClient() + opts.Owner = "wrong-owner" + + err := RunVerify(&opts) + assert.Error(t, err) + assert.ErrorContains(t, err, "failed to fetch attestations for subject") + }) + + t.Run("with invalid OIDC issuer", func(t *testing.T) { + opts := publicGoodOpts + opts.OIDCIssuer = "not-a-real-issuer" + assert.Error(t, RunVerify(&opts)) + }) + + t.Run("with SAN enforcement", func(t *testing.T) { + opts := Options{ + ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz", + BundlePath: "../test/data/sigstore-js-2.1.0-bundle.json", + APIClient: api.NewTestClient(), + DigestAlgorithm: "sha512", + Logger: logger, + OIDCIssuer: GitHubOIDCIssuer, + Owner: "sigstore", + SAN: SigstoreSanValue, + } + assert.Nil(t, RunVerify(&opts)) + }) + + t.Run("with invalid SAN", func(t *testing.T) { + opts := publicGoodOpts + opts.SAN = "fake san" + assert.Error(t, RunVerify(&opts)) + }) + + t.Run("with SAN regex enforcement", func(t *testing.T) { + opts := publicGoodOpts + opts.SANRegex = SigstoreSanRegex + assert.Nil(t, RunVerify(&opts)) + }) + + t.Run("with invalid SAN regex", func(t *testing.T) { + opts := publicGoodOpts + opts.SANRegex = "^https://github.com/sigstore/not-real/" + assert.Error(t, RunVerify(&opts)) + }) + + t.Run("with no matching OIDC issuer", func(t *testing.T) { + opts := publicGoodOpts + opts.OIDCIssuer = "some-other-issuer" + + assert.Error(t, RunVerify(&opts)) + }) + + t.Run("with valid artifact and JSON lines file containing multiple Sigstore bundles", func(t *testing.T) { + opts := publicGoodOpts + opts.BundlePath = "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl" + assert.Nil(t, RunVerify(&opts)) + }) +} + +func TestVerifySLSAPredicateType_InvalidPredicate(t *testing.T) { + statement := &in_toto.Statement{} + statement.PredicateType = "some-other-predicate-type" + + apr := []*verification.AttestationProcessingResult{ + { + VerificationResult: &verify.VerificationResult{ + Statement: statement, + }, + }, + } + + err := verifySLSAPredicateType(logger.NewDefaultLogger(), apr) + assert.Error(t, err) + assert.ErrorIs(t, err, ErrNoMatchingSLSAPredicate) +} diff --git a/pkg/cmd/root/root.go b/pkg/cmd/root/root.go index b97dc09f0..b9600145f 100644 --- a/pkg/cmd/root/root.go +++ b/pkg/cmd/root/root.go @@ -125,7 +125,7 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) (*cobra.Command, cmd.AddCommand(actionsCmd.NewCmdActions(f)) cmd.AddCommand(aliasCmd.NewCmdAlias(f)) cmd.AddCommand(authCmd.NewCmdAuth(f)) - cmd.AddCommand(attestationCmd.NewCmdAttestation(f.IOStreams, version, buildDate)) + cmd.AddCommand(attestationCmd.NewCmdAttestation(f)) cmd.AddCommand(configCmd.NewCmdConfig(f)) cmd.AddCommand(creditsCmd.NewCmdCredits(f, nil)) cmd.AddCommand(gistCmd.NewCmdGist(f))