...

Source file src/github.com/sigstore/cosign/v2/test/e2e_test.go

Documentation: github.com/sigstore/cosign/v2/test

     1  //
     2  // Copyright 2021 The Sigstore Authors.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  //go:build e2e && !cross
    17  
    18  package test
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"crypto"
    24  	"encoding/json"
    25  	"fmt"
    26  	"io"
    27  	"net/http"
    28  	"net/http/httptest"
    29  	"net/url"
    30  	"os"
    31  	"path"
    32  	"path/filepath"
    33  	"testing"
    34  	"time"
    35  
    36  	"github.com/google/go-cmp/cmp"
    37  	"github.com/google/go-containerregistry/pkg/name"
    38  	"github.com/google/go-containerregistry/pkg/v1/remote"
    39  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    40  	k8s "k8s.io/client-go/kubernetes"
    41  	"k8s.io/client-go/tools/clientcmd"
    42  
    43  	// Initialize all known client auth plugins
    44  	_ "k8s.io/client-go/plugin/pkg/client/auth"
    45  
    46  	"github.com/sigstore/cosign/v2/cmd/cosign/cli"
    47  	"github.com/sigstore/cosign/v2/cmd/cosign/cli/attach"
    48  	"github.com/sigstore/cosign/v2/cmd/cosign/cli/attest"
    49  	"github.com/sigstore/cosign/v2/cmd/cosign/cli/dockerfile"
    50  	"github.com/sigstore/cosign/v2/cmd/cosign/cli/download"
    51  	"github.com/sigstore/cosign/v2/cmd/cosign/cli/generate"
    52  	"github.com/sigstore/cosign/v2/cmd/cosign/cli/manifest"
    53  	"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
    54  	"github.com/sigstore/cosign/v2/cmd/cosign/cli/publickey"
    55  	"github.com/sigstore/cosign/v2/cmd/cosign/cli/sign"
    56  	cliverify "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify"
    57  	"github.com/sigstore/cosign/v2/internal/pkg/cosign/fulcio/fulcioroots"
    58  	"github.com/sigstore/cosign/v2/pkg/cosign"
    59  	"github.com/sigstore/cosign/v2/pkg/cosign/bundle"
    60  	"github.com/sigstore/cosign/v2/pkg/cosign/env"
    61  	"github.com/sigstore/cosign/v2/pkg/cosign/kubernetes"
    62  	"github.com/sigstore/cosign/v2/pkg/oci/mutate"
    63  	ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote"
    64  	"github.com/sigstore/sigstore/pkg/signature/payload"
    65  	tsaclient "github.com/sigstore/timestamp-authority/pkg/client"
    66  	"github.com/sigstore/timestamp-authority/pkg/server"
    67  	"github.com/spf13/viper"
    68  )
    69  
    70  func TestSignVerify(t *testing.T) {
    71  	td := t.TempDir()
    72  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
    73  	if err != nil {
    74  		t.Fatal(err)
    75  	}
    76  
    77  	repo, stop := reg(t)
    78  	defer stop()
    79  
    80  	imgName := path.Join(repo, "cosign-e2e")
    81  
    82  	_, _, cleanup := mkimage(t, imgName)
    83  	defer cleanup()
    84  
    85  	_, privKeyPath, pubKeyPath := keypair(t, td)
    86  
    87  	ctx := context.Background()
    88  	// Verify should fail at first
    89  	mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t)
    90  	// So should download
    91  	mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
    92  
    93  	// Now sign the image
    94  	ko := options.KeyOpts{
    95  		KeyRef:           privKeyPath,
    96  		PassFunc:         passFunc,
    97  		RekorURL:         rekorURL,
    98  		SkipConfirmation: true,
    99  	}
   100  	so := options.SignOptions{
   101  		Upload:     true,
   102  		TlogUpload: true,
   103  	}
   104  	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
   105  
   106  	// Now verify and download should work!
   107  	must(verify(pubKeyPath, imgName, true, nil, "", false), t)
   108  	must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
   109  
   110  	// Look for a specific annotation
   111  	mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t)
   112  
   113  	so.AnnotationOptions = options.AnnotationOptions{
   114  		Annotations: []string{"foo=bar"},
   115  	}
   116  	// Sign the image with an annotation
   117  	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
   118  
   119  	// It should match this time.
   120  	must(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t)
   121  
   122  	// But two doesn't work
   123  	mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar", "baz": "bat"}, "", false), t)
   124  }
   125  
   126  func TestSignVerifyClean(t *testing.T) {
   127  	td := t.TempDir()
   128  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
   129  	if err != nil {
   130  		t.Fatal(err)
   131  	}
   132  
   133  	repo, stop := reg(t)
   134  	defer stop()
   135  
   136  	imgName := path.Join(repo, "cosign-e2e")
   137  
   138  	_, _, _ = mkimage(t, imgName)
   139  
   140  	_, privKeyPath, pubKeyPath := keypair(t, td)
   141  
   142  	ctx := context.Background()
   143  
   144  	// Now sign the image
   145  	ko := options.KeyOpts{
   146  		KeyRef:           privKeyPath,
   147  		PassFunc:         passFunc,
   148  		RekorURL:         rekorURL,
   149  		SkipConfirmation: true,
   150  	}
   151  	so := options.SignOptions{
   152  		Upload:     true,
   153  		TlogUpload: true,
   154  	}
   155  	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
   156  
   157  	// Now verify and download should work!
   158  	must(verify(pubKeyPath, imgName, true, nil, "", false), t)
   159  	must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
   160  
   161  	// Now clean signature from the given image
   162  	must(cli.CleanCmd(ctx, options.RegistryOptions{}, "all", imgName, true), t)
   163  
   164  	// It doesn't work
   165  	mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t)
   166  }
   167  
   168  func TestImportSignVerifyClean(t *testing.T) {
   169  	td := t.TempDir()
   170  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
   171  	if err != nil {
   172  		t.Fatal(err)
   173  	}
   174  
   175  	repo, stop := reg(t)
   176  	defer stop()
   177  
   178  	imgName := path.Join(repo, "cosign-e2e")
   179  
   180  	_, _, _ = mkimage(t, imgName)
   181  
   182  	_, privKeyPath, pubKeyPath := importKeyPair(t, td)
   183  
   184  	ctx := context.Background()
   185  
   186  	// Now sign the image
   187  	ko := options.KeyOpts{
   188  		KeyRef:           privKeyPath,
   189  		PassFunc:         passFunc,
   190  		RekorURL:         rekorURL,
   191  		SkipConfirmation: true,
   192  	}
   193  	so := options.SignOptions{
   194  		Upload:     true,
   195  		TlogUpload: true,
   196  	}
   197  	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
   198  
   199  	// Now verify and download should work!
   200  	must(verify(pubKeyPath, imgName, true, nil, "", false), t)
   201  	must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
   202  
   203  	// Now clean signature from the given image
   204  	must(cli.CleanCmd(ctx, options.RegistryOptions{}, "all", imgName, true), t)
   205  
   206  	// It doesn't work
   207  	mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t)
   208  }
   209  
   210  func TestAttestVerify(t *testing.T) {
   211  	attestVerify(t,
   212  		"slsaprovenance",
   213  		`{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`,
   214  		`predicate: builder: id: "2"`,
   215  		`predicate: builder: id: "1"`,
   216  	)
   217  }
   218  
   219  func TestAttestVerifySPDXJSON(t *testing.T) {
   220  	attestationBytes, err := os.ReadFile("./testdata/bom-go-mod.spdx.json")
   221  	if err != nil {
   222  		t.Fatal(err)
   223  	}
   224  	attestVerify(t,
   225  		"spdxjson",
   226  		string(attestationBytes),
   227  		`predicate: spdxVersion: "SPDX-2.2"`,
   228  		`predicate: spdxVersion: "SPDX-9.9"`,
   229  	)
   230  }
   231  
   232  func TestAttestVerifyCycloneDXJSON(t *testing.T) {
   233  	attestationBytes, err := os.ReadFile("./testdata/bom-go-mod.cyclonedx.json")
   234  	if err != nil {
   235  		t.Fatal(err)
   236  	}
   237  	attestVerify(t,
   238  		"cyclonedx",
   239  		string(attestationBytes),
   240  		`predicate: specVersion: "1.4"`,
   241  		`predicate: specVersion: "7.7"`,
   242  	)
   243  }
   244  
   245  func TestAttestVerifyURI(t *testing.T) {
   246  	attestationBytes, err := os.ReadFile("./testdata/test-result.json")
   247  	if err != nil {
   248  		t.Fatal(err)
   249  	}
   250  	attestVerify(t,
   251  		"https://example.com/TestResult/v1",
   252  		string(attestationBytes),
   253  		`predicate: passed: true`,
   254  		`predicate: passed: false"`,
   255  	)
   256  }
   257  
   258  func attestVerify(t *testing.T, predicateType, attestation, goodCue, badCue string) {
   259  	repo, stop := reg(t)
   260  	defer stop()
   261  	td := t.TempDir()
   262  
   263  	var imgName, attestationPath string
   264  	if _, err := url.ParseRequestURI(predicateType); err == nil {
   265  		// If the predicate type is URI, it cannot be included as image name and path.
   266  		imgName = path.Join(repo, "cosign-attest-uri-e2e-image")
   267  		attestationPath = filepath.Join(td, "cosign-attest-uri-e2e-attestation")
   268  	} else {
   269  		imgName = path.Join(repo, fmt.Sprintf("cosign-attest-%s-e2e-image", predicateType))
   270  		attestationPath = filepath.Join(td, fmt.Sprintf("cosign-attest-%s-e2e-attestation", predicateType))
   271  	}
   272  
   273  	_, _, cleanup := mkimage(t, imgName)
   274  	defer cleanup()
   275  
   276  	_, privKeyPath, pubKeyPath := keypair(t, td)
   277  
   278  	ctx := context.Background()
   279  
   280  	// Verify should fail at first
   281  	verifyAttestation := cliverify.VerifyAttestationCommand{
   282  		KeyRef:     pubKeyPath,
   283  		IgnoreTlog: true,
   284  		MaxWorkers: 10,
   285  	}
   286  
   287  	// Fail case when using without type and policy flag
   288  	mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t)
   289  
   290  	if err := os.WriteFile(attestationPath, []byte(attestation), 0600); err != nil {
   291  		t.Fatal(err)
   292  	}
   293  
   294  	// Now attest the image
   295  	ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
   296  	attestCmd := attest.AttestCommand{
   297  		KeyOpts:        ko,
   298  		PredicatePath:  attestationPath,
   299  		PredicateType:  predicateType,
   300  		Timeout:        30 * time.Second,
   301  		RekorEntryType: "dsse",
   302  	}
   303  	must(attestCmd.Exec(ctx, imgName), t)
   304  
   305  	// Use cue to verify attestation
   306  	policyPath := filepath.Join(td, "policy.cue")
   307  	verifyAttestation.PredicateType = predicateType
   308  	verifyAttestation.Policies = []string{policyPath}
   309  
   310  	// Fail case
   311  	if err := os.WriteFile(policyPath, []byte(badCue), 0600); err != nil {
   312  		t.Fatal(err)
   313  	}
   314  	mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t)
   315  
   316  	// Success case
   317  	if err := os.WriteFile(policyPath, []byte(goodCue), 0600); err != nil {
   318  		t.Fatal(err)
   319  	}
   320  	must(verifyAttestation.Exec(ctx, []string{imgName}), t)
   321  
   322  	// Look for a specific annotation
   323  	mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t)
   324  }
   325  
   326  func TestAttestationDownload(t *testing.T) {
   327  	repo, stop := reg(t)
   328  	defer stop()
   329  	td := t.TempDir()
   330  
   331  	imgName := path.Join(repo, "cosign-attest-download-e2e")
   332  
   333  	_, _, cleanup := mkimage(t, imgName)
   334  	defer cleanup()
   335  
   336  	_, privKeyPath, _ := keypair(t, td)
   337  	ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
   338  
   339  	ctx := context.Background()
   340  
   341  	slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
   342  	slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
   343  	if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
   344  		t.Fatal(err)
   345  	}
   346  
   347  	vulnAttestation := `
   348  	{
   349      "invocation": {
   350        "parameters": null,
   351        "uri": "invocation.example.com/cosign-testing",
   352        "event_id": "",
   353        "builder.id": ""
   354      },
   355      "scanner": {
   356        "uri": "fakescanner.example.com/cosign-testing",
   357        "version": "",
   358        "db": {
   359          "uri": "",
   360          "version": ""
   361        },
   362        "result": null
   363      },
   364      "metadata": {
   365        "scanStartedOn": "2022-04-12T00:00:00Z",
   366        "scanFinishedOn": "2022-04-12T00:10:00Z"
   367      }
   368  }
   369  `
   370  	vulnAttestationPath := filepath.Join(td, "attestation.vuln.json")
   371  	if err := os.WriteFile(vulnAttestationPath, []byte(vulnAttestation), 0600); err != nil {
   372  		t.Fatal(err)
   373  	}
   374  
   375  	ref, err := name.ParseReference(imgName)
   376  	if err != nil {
   377  		t.Fatal(err)
   378  	}
   379  	regOpts := options.RegistryOptions{}
   380  	ociremoteOpts, err := regOpts.ClientOpts(ctx)
   381  	if err != nil {
   382  		t.Fatal(err)
   383  	}
   384  
   385  	// Attest to create a slsa attestation
   386  	attestCommand := attest.AttestCommand{
   387  		KeyOpts:        ko,
   388  		PredicatePath:  slsaAttestationPath,
   389  		PredicateType:  "slsaprovenance",
   390  		Timeout:        30 * time.Second,
   391  		Replace:        true,
   392  		RekorEntryType: "dsse",
   393  	}
   394  	must(attestCommand.Exec(ctx, imgName), t)
   395  
   396  	// Attest to create a vuln attestation
   397  	attestCommand = attest.AttestCommand{
   398  		KeyOpts:        ko,
   399  		PredicatePath:  vulnAttestationPath,
   400  		PredicateType:  "vuln",
   401  		Timeout:        30 * time.Second,
   402  		Replace:        true,
   403  		RekorEntryType: "dsse",
   404  	}
   405  	must(attestCommand.Exec(ctx, imgName), t)
   406  
   407  	// Call download.AttestationCmd() to ensure success
   408  	attOpts := options.AttestationDownloadOptions{}
   409  	must(download.AttestationCmd(ctx, regOpts, attOpts, imgName), t)
   410  
   411  	attestations, err := cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...)
   412  	if err != nil {
   413  		t.Fatal(err)
   414  	}
   415  	if len(attestations) != 2 {
   416  		t.Fatal(fmt.Errorf("expected len(attestations) == 2, got %d", len(attestations)))
   417  	}
   418  }
   419  
   420  func TestAttestationDownloadWithPredicateType(t *testing.T) {
   421  	repo, stop := reg(t)
   422  	defer stop()
   423  	td := t.TempDir()
   424  
   425  	imgName := path.Join(repo, "cosign-attest-download-predicate-type-e2e")
   426  
   427  	_, _, cleanup := mkimage(t, imgName)
   428  	defer cleanup()
   429  
   430  	_, privKeyPath, _ := keypair(t, td)
   431  	ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
   432  
   433  	ctx := context.Background()
   434  
   435  	slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
   436  	slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
   437  	if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
   438  		t.Fatal(err)
   439  	}
   440  
   441  	vulnAttestation := `
   442  	{
   443      "invocation": {
   444        "parameters": null,
   445        "uri": "invocation.example.com/cosign-testing",
   446        "event_id": "",
   447        "builder.id": ""
   448      },
   449      "scanner": {
   450        "uri": "fakescanner.example.com/cosign-testing",
   451        "version": "",
   452        "db": {
   453          "uri": "",
   454          "version": ""
   455        },
   456        "result": null
   457      },
   458      "metadata": {
   459        "scanStartedOn": "2022-04-12T00:00:00Z",
   460        "scanFinishedOn": "2022-04-12T00:10:00Z"
   461      }
   462  }
   463  `
   464  	vulnAttestationPath := filepath.Join(td, "attestation.vuln.json")
   465  	if err := os.WriteFile(vulnAttestationPath, []byte(vulnAttestation), 0600); err != nil {
   466  		t.Fatal(err)
   467  	}
   468  
   469  	ref, err := name.ParseReference(imgName)
   470  	if err != nil {
   471  		t.Fatal(err)
   472  	}
   473  	regOpts := options.RegistryOptions{}
   474  	ociremoteOpts, err := regOpts.ClientOpts(ctx)
   475  	if err != nil {
   476  		t.Fatal(err)
   477  	}
   478  
   479  	// Attest to create a slsa attestation
   480  	attestCommand := attest.AttestCommand{
   481  		KeyOpts:        ko,
   482  		PredicatePath:  slsaAttestationPath,
   483  		PredicateType:  "slsaprovenance",
   484  		Timeout:        30 * time.Second,
   485  		Replace:        true,
   486  		RekorEntryType: "dsse",
   487  	}
   488  	must(attestCommand.Exec(ctx, imgName), t)
   489  
   490  	// Attest to create a vuln attestation
   491  	attestCommand = attest.AttestCommand{
   492  		KeyOpts:        ko,
   493  		PredicatePath:  vulnAttestationPath,
   494  		PredicateType:  "vuln",
   495  		Timeout:        30 * time.Second,
   496  		Replace:        true,
   497  		RekorEntryType: "dsse",
   498  	}
   499  	must(attestCommand.Exec(ctx, imgName), t)
   500  
   501  	// Call download.AttestationCmd() to ensure success with --predicate-type
   502  	attOpts := options.AttestationDownloadOptions{
   503  		PredicateType: "vuln",
   504  	}
   505  	must(download.AttestationCmd(ctx, regOpts, attOpts, imgName), t)
   506  
   507  	predicateType, _ := options.ParsePredicateType(attOpts.PredicateType)
   508  	attestations, err := cosign.FetchAttestationsForReference(ctx, ref, predicateType, ociremoteOpts...)
   509  	if err != nil {
   510  		t.Fatal(err)
   511  	}
   512  	if len(attestations) != 1 {
   513  		t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations)))
   514  	}
   515  }
   516  
   517  func TestAttestationDownloadWithBadPredicateType(t *testing.T) {
   518  	repo, stop := reg(t)
   519  	defer stop()
   520  	td := t.TempDir()
   521  
   522  	imgName := path.Join(repo, "cosign-attest-download-bad-type-e2e")
   523  
   524  	_, _, cleanup := mkimage(t, imgName)
   525  	defer cleanup()
   526  
   527  	_, privKeyPath, _ := keypair(t, td)
   528  	ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
   529  
   530  	ctx := context.Background()
   531  
   532  	slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
   533  	slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
   534  	if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
   535  		t.Fatal(err)
   536  	}
   537  
   538  	regOpts := options.RegistryOptions{}
   539  
   540  	// Attest to create a slsa attestation
   541  	attestCommand := attest.AttestCommand{
   542  		KeyOpts:        ko,
   543  		PredicatePath:  slsaAttestationPath,
   544  		PredicateType:  "slsaprovenance",
   545  		Timeout:        30 * time.Second,
   546  		Replace:        true,
   547  		RekorEntryType: "dsse",
   548  	}
   549  	must(attestCommand.Exec(ctx, imgName), t)
   550  
   551  	// Call download.AttestationCmd() to ensure failure with non-existant --predicate-type
   552  	attOpts := options.AttestationDownloadOptions{
   553  		PredicateType: "vuln",
   554  	}
   555  	mustErr(download.AttestationCmd(ctx, regOpts, attOpts, imgName), t)
   556  }
   557  
   558  func TestAttestationReplaceCreate(t *testing.T) {
   559  	repo, stop := reg(t)
   560  	defer stop()
   561  	td := t.TempDir()
   562  
   563  	imgName := path.Join(repo, "cosign-attest-replace-e2e")
   564  
   565  	_, _, cleanup := mkimage(t, imgName)
   566  	defer cleanup()
   567  
   568  	_, privKeyPath, _ := keypair(t, td)
   569  	ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
   570  
   571  	ctx := context.Background()
   572  
   573  	slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
   574  	slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
   575  	if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
   576  		t.Fatal(err)
   577  	}
   578  
   579  	ref, err := name.ParseReference(imgName)
   580  	if err != nil {
   581  		t.Fatal(err)
   582  	}
   583  	regOpts := options.RegistryOptions{}
   584  	ociremoteOpts, err := regOpts.ClientOpts(ctx)
   585  	if err != nil {
   586  		t.Fatal(err)
   587  	}
   588  
   589  	// Attest with replace=true to create an attestation
   590  	attestCommand := attest.AttestCommand{
   591  		KeyOpts:        ko,
   592  		PredicatePath:  slsaAttestationPath,
   593  		PredicateType:  "slsaprovenance",
   594  		Timeout:        30 * time.Second,
   595  		Replace:        true,
   596  		RekorEntryType: "dsse",
   597  	}
   598  	must(attestCommand.Exec(ctx, imgName), t)
   599  
   600  	// Download and count the attestations
   601  	attOpts := options.AttestationDownloadOptions{}
   602  	attestations, err := cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...)
   603  	if err != nil {
   604  		t.Fatal(err)
   605  	}
   606  	if len(attestations) != 1 {
   607  		t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations)))
   608  	}
   609  }
   610  
   611  func TestAttestationReplace(t *testing.T) {
   612  	repo, stop := reg(t)
   613  	defer stop()
   614  	td := t.TempDir()
   615  
   616  	imgName := path.Join(repo, "cosign-attest-replace-e2e")
   617  
   618  	_, _, cleanup := mkimage(t, imgName)
   619  	defer cleanup()
   620  
   621  	_, privKeyPath, _ := keypair(t, td)
   622  	ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
   623  
   624  	ctx := context.Background()
   625  
   626  	slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
   627  	slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
   628  	if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
   629  		t.Fatal(err)
   630  	}
   631  
   632  	ref, err := name.ParseReference(imgName)
   633  	if err != nil {
   634  		t.Fatal(err)
   635  	}
   636  	regOpts := options.RegistryOptions{}
   637  	ociremoteOpts, err := regOpts.ClientOpts(ctx)
   638  	if err != nil {
   639  		t.Fatal(err)
   640  	}
   641  
   642  	// Attest once with replace=false creating an attestation
   643  	attestCommand := attest.AttestCommand{
   644  		KeyOpts:        ko,
   645  		PredicatePath:  slsaAttestationPath,
   646  		PredicateType:  "slsaprovenance",
   647  		Timeout:        30 * time.Second,
   648  		RekorEntryType: "dsse",
   649  	}
   650  	must(attestCommand.Exec(ctx, imgName), t)
   651  
   652  	// Download and count the attestations
   653  	attOpts := options.AttestationDownloadOptions{}
   654  	attestations, err := cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...)
   655  	if err != nil {
   656  		t.Fatal(err)
   657  	}
   658  	if len(attestations) != 1 {
   659  		t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations)))
   660  	}
   661  
   662  	// Attest again with replace=true, replacing the previous attestation
   663  	attestCommand = attest.AttestCommand{
   664  		KeyOpts:        ko,
   665  		PredicatePath:  slsaAttestationPath,
   666  		PredicateType:  "slsaprovenance",
   667  		Replace:        true,
   668  		Timeout:        30 * time.Second,
   669  		RekorEntryType: "dsse",
   670  	}
   671  	must(attestCommand.Exec(ctx, imgName), t)
   672  	attestations, err = cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...)
   673  
   674  	// Download and count the attestations
   675  	if err != nil {
   676  		t.Fatal(err)
   677  	}
   678  	if len(attestations) != 1 {
   679  		t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations)))
   680  	}
   681  
   682  	// Attest once more replace=true using a different predicate, to ensure it adds a new attestation
   683  	attestCommand = attest.AttestCommand{
   684  		KeyOpts:        ko,
   685  		PredicatePath:  slsaAttestationPath,
   686  		PredicateType:  "custom",
   687  		Replace:        true,
   688  		Timeout:        30 * time.Second,
   689  		RekorEntryType: "dsse",
   690  	}
   691  	must(attestCommand.Exec(ctx, imgName), t)
   692  
   693  	// Download and count the attestations
   694  	attestations, err = cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...)
   695  	if err != nil {
   696  		t.Fatal(err)
   697  	}
   698  	if len(attestations) != 2 {
   699  		t.Fatal(fmt.Errorf("expected len(attestations) == 2, got %d", len(attestations)))
   700  	}
   701  }
   702  
   703  func TestAttestationRFC3161Timestamp(t *testing.T) {
   704  	// TSA server needed to create timestamp
   705  	viper.Set("timestamp-signer", "memory")
   706  	viper.Set("timestamp-signer-hash", "sha256")
   707  	apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second)
   708  	server := httptest.NewServer(apiServer.GetHandler())
   709  	t.Cleanup(server.Close)
   710  
   711  	repo, stop := reg(t)
   712  	defer stop()
   713  	td := t.TempDir()
   714  
   715  	imgName := path.Join(repo, "cosign-attest-timestamp-e2e")
   716  
   717  	_, _, cleanup := mkimage(t, imgName)
   718  	defer cleanup()
   719  
   720  	_, privKeyPath, pubKeyPath := keypair(t, td)
   721  	ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
   722  
   723  	ctx := context.Background()
   724  
   725  	slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
   726  	slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
   727  	if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
   728  		t.Fatal(err)
   729  	}
   730  
   731  	ref, err := name.ParseReference(imgName)
   732  	if err != nil {
   733  		t.Fatal(err)
   734  	}
   735  	regOpts := options.RegistryOptions{}
   736  	ociremoteOpts, err := regOpts.ClientOpts(ctx)
   737  	if err != nil {
   738  		t.Fatal(err)
   739  	}
   740  
   741  	// Attest with TSA and skipping tlog creating an attestation
   742  	attestCommand := attest.AttestCommand{
   743  		KeyOpts:        ko,
   744  		PredicatePath:  slsaAttestationPath,
   745  		PredicateType:  "slsaprovenance",
   746  		Timeout:        30 * time.Second,
   747  		TSAServerURL:   server.URL + "/api/v1/timestamp",
   748  		TlogUpload:     false,
   749  		RekorEntryType: "dsse",
   750  	}
   751  	must(attestCommand.Exec(ctx, imgName), t)
   752  
   753  	// Download and count the attestations
   754  	attOpts := options.AttestationDownloadOptions{}
   755  	attestations, err := cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...)
   756  	if err != nil {
   757  		t.Fatal(err)
   758  	}
   759  	if len(attestations) != 1 {
   760  		t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations)))
   761  	}
   762  
   763  	client, err := tsaclient.GetTimestampClient(server.URL)
   764  	if err != nil {
   765  		t.Error(err)
   766  	}
   767  
   768  	chain, err := client.Timestamp.GetTimestampCertChain(nil)
   769  	if err != nil {
   770  		t.Fatalf("unexpected error getting timestamp chain: %v", err)
   771  	}
   772  
   773  	file, err := os.CreateTemp(os.TempDir(), "tempfile")
   774  	if err != nil {
   775  		t.Fatalf("error creating temp file: %v", err)
   776  	}
   777  	defer os.Remove(file.Name())
   778  	_, err = file.WriteString(chain.Payload)
   779  	if err != nil {
   780  		t.Fatalf("error writing chain payload to temp file: %v", err)
   781  	}
   782  
   783  	verifyAttestation := cliverify.VerifyAttestationCommand{
   784  		KeyRef:           pubKeyPath,
   785  		TSACertChainPath: file.Name(),
   786  		IgnoreTlog:       true,
   787  		PredicateType:    "slsaprovenance",
   788  		MaxWorkers:       10,
   789  	}
   790  
   791  	must(verifyAttestation.Exec(ctx, []string{imgName}), t)
   792  }
   793  
   794  func TestRekorBundle(t *testing.T) {
   795  	td := t.TempDir()
   796  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
   797  	if err != nil {
   798  		t.Fatal(err)
   799  	}
   800  
   801  	repo, stop := reg(t)
   802  	defer stop()
   803  
   804  	imgName := path.Join(repo, "cosign-e2e")
   805  
   806  	_, _, cleanup := mkimage(t, imgName)
   807  	defer cleanup()
   808  
   809  	_, privKeyPath, pubKeyPath := keypair(t, td)
   810  
   811  	ko := options.KeyOpts{
   812  		KeyRef:           privKeyPath,
   813  		PassFunc:         passFunc,
   814  		RekorURL:         rekorURL,
   815  		SkipConfirmation: true,
   816  	}
   817  	so := options.SignOptions{
   818  		Upload:     true,
   819  		TlogUpload: true,
   820  	}
   821  
   822  	// Sign the image
   823  	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
   824  	// Make sure verify works
   825  	must(verify(pubKeyPath, imgName, true, nil, "", false), t)
   826  
   827  	// Make sure offline verification works with bundling
   828  	must(verifyOffline(pubKeyPath, imgName, true, nil, ""), t)
   829  }
   830  
   831  func TestRekorOutput(t *testing.T) {
   832  	td := t.TempDir()
   833  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
   834  	if err != nil {
   835  		t.Fatal(err)
   836  	}
   837  
   838  	repo, stop := reg(t)
   839  	defer stop()
   840  
   841  	imgName := path.Join(repo, "cosign-e2e")
   842  	bundlePath := filepath.Join(td, "bundle.sig")
   843  
   844  	_, _, cleanup := mkimage(t, imgName)
   845  	defer cleanup()
   846  
   847  	_, privKeyPath, pubKeyPath := keypair(t, td)
   848  
   849  	ko := options.KeyOpts{
   850  		KeyRef:     privKeyPath,
   851  		PassFunc:   passFunc,
   852  		RekorURL:   rekorURL,
   853  		BundlePath: bundlePath,
   854  	}
   855  	so := options.SignOptions{
   856  		Upload:     true,
   857  		TlogUpload: true,
   858  	}
   859  
   860  	// Sign the image
   861  	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
   862  	// Make sure verify works
   863  	must(verify(pubKeyPath, imgName, true, nil, "", false), t)
   864  
   865  	if file, err := os.ReadFile(bundlePath); err != nil {
   866  		t.Fatal(err)
   867  	} else {
   868  		var localCosignPayload cosign.LocalSignedPayload
   869  		if err := json.Unmarshal(file, &localCosignPayload); err != nil {
   870  			t.Fatal(err)
   871  		}
   872  	}
   873  	// Make sure offline verification works with bundling
   874  	must(verifyOffline(pubKeyPath, imgName, true, nil, ""), t)
   875  }
   876  
   877  func TestFulcioBundle(t *testing.T) {
   878  	td := t.TempDir()
   879  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
   880  	if err != nil {
   881  		t.Fatal(err)
   882  	}
   883  
   884  	repo, stop := reg(t)
   885  	defer stop()
   886  
   887  	imgName := path.Join(repo, "cosign-e2e")
   888  
   889  	_, _, cleanup := mkimage(t, imgName)
   890  	defer cleanup()
   891  
   892  	_, privKeyPath, pubKeyPath := keypair(t, td)
   893  
   894  	ko := options.KeyOpts{
   895  		KeyRef:           privKeyPath,
   896  		PassFunc:         passFunc,
   897  		RekorURL:         rekorURL,
   898  		FulcioURL:        fulcioURL,
   899  		SkipConfirmation: true,
   900  	}
   901  	so := options.SignOptions{
   902  		Upload:           true,
   903  		TlogUpload:       true,
   904  		IssueCertificate: true,
   905  	}
   906  
   907  	// Sign the image
   908  	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
   909  	// Make sure verify works
   910  	must(verify(pubKeyPath, imgName, true, nil, "", false), t)
   911  
   912  	// Make sure offline verification works with bundling
   913  	// use rekor prod since we have hardcoded the public key
   914  	must(verifyOffline(pubKeyPath, imgName, true, nil, ""), t)
   915  }
   916  
   917  func TestRFC3161Timestamp(t *testing.T) {
   918  	// TSA server needed to create timestamp
   919  	viper.Set("timestamp-signer", "memory")
   920  	viper.Set("timestamp-signer-hash", "sha256")
   921  	apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second)
   922  	server := httptest.NewServer(apiServer.GetHandler())
   923  	t.Cleanup(server.Close)
   924  
   925  	client, err := tsaclient.GetTimestampClient(server.URL)
   926  	if err != nil {
   927  		t.Error(err)
   928  	}
   929  
   930  	chain, err := client.Timestamp.GetTimestampCertChain(nil)
   931  	if err != nil {
   932  		t.Fatalf("unexpected error getting timestamp chain: %v", err)
   933  	}
   934  
   935  	file, err := os.CreateTemp(os.TempDir(), "tempfile")
   936  	if err != nil {
   937  		t.Fatalf("error creating temp file: %v", err)
   938  	}
   939  	defer os.Remove(file.Name())
   940  	_, err = file.WriteString(chain.Payload)
   941  	if err != nil {
   942  		t.Fatalf("error writing chain payload to temp file: %v", err)
   943  	}
   944  
   945  	repo, stop := reg(t)
   946  	defer stop()
   947  	td := t.TempDir()
   948  
   949  	imgName := path.Join(repo, "cosign-e2e")
   950  
   951  	_, _, cleanup := mkimage(t, imgName)
   952  	defer cleanup()
   953  
   954  	_, privKeyPath, pubKeyPath := keypair(t, td)
   955  
   956  	ko := options.KeyOpts{
   957  		KeyRef:       privKeyPath,
   958  		PassFunc:     passFunc,
   959  		TSAServerURL: server.URL + "/api/v1/timestamp",
   960  	}
   961  	so := options.SignOptions{
   962  		Upload:     true,
   963  		TlogUpload: false,
   964  	}
   965  
   966  	// Sign the image
   967  	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
   968  	// Make sure verify works against the TSA server
   969  	must(verifyTSA(pubKeyPath, imgName, true, nil, "", file.Name(), true), t)
   970  }
   971  
   972  func TestRekorBundleAndRFC3161Timestamp(t *testing.T) {
   973  	td := t.TempDir()
   974  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
   975  	if err != nil {
   976  		t.Fatal(err)
   977  	}
   978  
   979  	// TSA server needed to create timestamp
   980  	viper.Set("timestamp-signer", "memory")
   981  	viper.Set("timestamp-signer-hash", "sha256")
   982  	apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second)
   983  	server := httptest.NewServer(apiServer.GetHandler())
   984  	t.Cleanup(server.Close)
   985  
   986  	client, err := tsaclient.GetTimestampClient(server.URL)
   987  	if err != nil {
   988  		t.Error(err)
   989  	}
   990  
   991  	chain, err := client.Timestamp.GetTimestampCertChain(nil)
   992  	if err != nil {
   993  		t.Fatalf("unexpected error getting timestamp chain: %v", err)
   994  	}
   995  
   996  	file, err := os.CreateTemp(os.TempDir(), "tempfile")
   997  	if err != nil {
   998  		t.Fatalf("error creating temp file: %v", err)
   999  	}
  1000  	defer os.Remove(file.Name())
  1001  	_, err = file.WriteString(chain.Payload)
  1002  	if err != nil {
  1003  		t.Fatalf("error writing chain payload to temp file: %v", err)
  1004  	}
  1005  
  1006  	repo, stop := reg(t)
  1007  	defer stop()
  1008  
  1009  	imgName := path.Join(repo, "cosign-e2e")
  1010  
  1011  	_, _, cleanup := mkimage(t, imgName)
  1012  	defer cleanup()
  1013  
  1014  	_, privKeyPath, pubKeyPath := keypair(t, td)
  1015  
  1016  	ko := options.KeyOpts{
  1017  		KeyRef:           privKeyPath,
  1018  		PassFunc:         passFunc,
  1019  		TSAServerURL:     server.URL + "/api/v1/timestamp",
  1020  		RekorURL:         rekorURL,
  1021  		SkipConfirmation: true,
  1022  	}
  1023  	so := options.SignOptions{
  1024  		Upload:     true,
  1025  		TlogUpload: true,
  1026  	}
  1027  
  1028  	// Sign the image
  1029  	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
  1030  	// Make sure verify works against the Rekor and TSA clients
  1031  	must(verifyTSA(pubKeyPath, imgName, true, nil, "", file.Name(), false), t)
  1032  }
  1033  
  1034  func TestDuplicateSign(t *testing.T) {
  1035  	td := t.TempDir()
  1036  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
  1037  	if err != nil {
  1038  		t.Fatal(err)
  1039  	}
  1040  
  1041  	repo, stop := reg(t)
  1042  	defer stop()
  1043  
  1044  	imgName := path.Join(repo, "cosign-e2e")
  1045  
  1046  	ref, _, cleanup := mkimage(t, imgName)
  1047  	defer cleanup()
  1048  
  1049  	_, privKeyPath, pubKeyPath := keypair(t, td)
  1050  
  1051  	ctx := context.Background()
  1052  	// Verify should fail at first
  1053  	mustErr(verify(pubKeyPath, imgName, true, nil, "", true), t)
  1054  	// So should download
  1055  	mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
  1056  
  1057  	// Now sign the image
  1058  	ko := options.KeyOpts{
  1059  		KeyRef:   privKeyPath,
  1060  		PassFunc: passFunc,
  1061  	}
  1062  	so := options.SignOptions{
  1063  		Upload: true,
  1064  	}
  1065  	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
  1066  
  1067  	// Now verify and download should work!
  1068  	// Ignore the tlog, because uploading to the tlog causes new signatures with new timestamp entries to be appended.
  1069  	must(verify(pubKeyPath, imgName, true, nil, "", true), t)
  1070  	must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
  1071  
  1072  	// Signing again should work just fine...
  1073  	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
  1074  
  1075  	se, err := ociremote.SignedEntity(ref, ociremote.WithRemoteOptions(registryClientOpts(ctx)...))
  1076  	must(err, t)
  1077  	sigs, err := se.Signatures()
  1078  	must(err, t)
  1079  	signatures, err := sigs.Get()
  1080  	must(err, t)
  1081  
  1082  	if len(signatures) > 1 {
  1083  		t.Errorf("expected there to only be one signature, got %v", signatures)
  1084  	}
  1085  }
  1086  
  1087  func TestKeyURLVerify(t *testing.T) {
  1088  	// TODO: re-enable once distroless images are being signed by the new client
  1089  	t.Skip()
  1090  	// Verify that an image can be verified via key url
  1091  	keyRef := "https://raw.githubusercontent.com/GoogleContainerTools/distroless/main/cosign.pub"
  1092  	img := "gcr.io/distroless/base:latest"
  1093  
  1094  	must(verify(keyRef, img, true, nil, "", false), t)
  1095  }
  1096  
  1097  func TestGenerateKeyPairEnvVar(t *testing.T) {
  1098  	t.Setenv("COSIGN_PASSWORD", "foo")
  1099  	keys, err := cosign.GenerateKeyPair(generate.GetPass)
  1100  	if err != nil {
  1101  		t.Fatal(err)
  1102  	}
  1103  	if _, err := cosign.LoadPrivateKey(keys.PrivateBytes, []byte("foo")); err != nil {
  1104  		t.Fatal(err)
  1105  	}
  1106  }
  1107  
  1108  func TestGenerateKeyPairK8s(t *testing.T) {
  1109  	td := t.TempDir()
  1110  	wd, err := os.Getwd()
  1111  	if err != nil {
  1112  		t.Fatal(err)
  1113  	}
  1114  	if err := os.Chdir(td); err != nil {
  1115  		t.Fatal(err)
  1116  	}
  1117  	defer func() {
  1118  		os.Chdir(wd)
  1119  	}()
  1120  	password := "foo"
  1121  	t.Setenv("COSIGN_PASSWORD", password)
  1122  	ctx := context.Background()
  1123  	name := "cosign-secret"
  1124  	namespace := "default"
  1125  	if err := kubernetes.KeyPairSecret(ctx, fmt.Sprintf("k8s://%s/%s", namespace, name), generate.GetPass); err != nil {
  1126  		t.Fatal(err)
  1127  	}
  1128  	// make sure the secret actually exists
  1129  
  1130  	cfg, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
  1131  		clientcmd.NewDefaultClientConfigLoadingRules(), nil).ClientConfig()
  1132  	if err != nil {
  1133  		t.Fatal(err)
  1134  	}
  1135  	client, err := k8s.NewForConfig(cfg)
  1136  	if err != nil {
  1137  		t.Fatal(err)
  1138  	}
  1139  	s, err := client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{})
  1140  	if err != nil {
  1141  		t.Fatal(err)
  1142  	}
  1143  	if v, ok := s.Data["cosign.password"]; !ok || string(v) != password {
  1144  		t.Fatalf("password is incorrect, got %v expected %v", v, "foo")
  1145  	}
  1146  	// Clean up the secret (so tests can be re-run locally)
  1147  	err = client.CoreV1().Secrets(namespace).Delete(ctx, name, metav1.DeleteOptions{})
  1148  	if err != nil {
  1149  		t.Fatal(err)
  1150  	}
  1151  }
  1152  
  1153  func TestMultipleSignatures(t *testing.T) {
  1154  	td := t.TempDir()
  1155  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
  1156  	if err != nil {
  1157  		t.Fatal(err)
  1158  	}
  1159  
  1160  	repo, stop := reg(t)
  1161  	defer stop()
  1162  
  1163  	td1 := t.TempDir()
  1164  	td2 := t.TempDir()
  1165  
  1166  	imgName := path.Join(repo, "cosign-e2e")
  1167  
  1168  	_, _, cleanup := mkimage(t, imgName)
  1169  	defer cleanup()
  1170  
  1171  	_, priv1, pub1 := keypair(t, td1)
  1172  	_, priv2, pub2 := keypair(t, td2)
  1173  
  1174  	// Verify should fail at first for both keys
  1175  	mustErr(verify(pub1, imgName, true, nil, "", false), t)
  1176  	mustErr(verify(pub2, imgName, true, nil, "", false), t)
  1177  
  1178  	// Now sign the image with one key
  1179  	ko := options.KeyOpts{
  1180  		KeyRef:           priv1,
  1181  		PassFunc:         passFunc,
  1182  		RekorURL:         rekorURL,
  1183  		SkipConfirmation: true,
  1184  	}
  1185  	so := options.SignOptions{
  1186  		Upload:     true,
  1187  		TlogUpload: true,
  1188  	}
  1189  	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
  1190  	// Now verify should work with that one, but not the other
  1191  	must(verify(pub1, imgName, true, nil, "", false), t)
  1192  	mustErr(verify(pub2, imgName, true, nil, "", false), t)
  1193  
  1194  	// Now sign with the other key too
  1195  	ko.KeyRef = priv2
  1196  	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
  1197  
  1198  	// Now verify should work with both
  1199  	must(verify(pub1, imgName, true, nil, "", false), t)
  1200  	must(verify(pub2, imgName, true, nil, "", false), t)
  1201  }
  1202  
  1203  func TestSignBlob(t *testing.T) {
  1204  	td := t.TempDir()
  1205  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
  1206  	if err != nil {
  1207  		t.Fatal(err)
  1208  	}
  1209  	blob := "someblob"
  1210  	td1 := t.TempDir()
  1211  	td2 := t.TempDir()
  1212  	bp := filepath.Join(td1, blob)
  1213  
  1214  	if err := os.WriteFile(bp, []byte(blob), 0644); err != nil {
  1215  		t.Fatal(err)
  1216  	}
  1217  
  1218  	_, privKeyPath1, pubKeyPath1 := keypair(t, td1)
  1219  	_, _, pubKeyPath2 := keypair(t, td2)
  1220  
  1221  	ctx := context.Background()
  1222  
  1223  	ko1 := options.KeyOpts{
  1224  		KeyRef: pubKeyPath1,
  1225  	}
  1226  	ko2 := options.KeyOpts{
  1227  		KeyRef: pubKeyPath2,
  1228  	}
  1229  	// Verify should fail on a bad input
  1230  	cmd1 := cliverify.VerifyBlobCmd{
  1231  		KeyOpts:    ko1,
  1232  		SigRef:     "badsig",
  1233  		IgnoreTlog: true,
  1234  	}
  1235  	cmd2 := cliverify.VerifyBlobCmd{
  1236  		KeyOpts:    ko2,
  1237  		SigRef:     "badsig",
  1238  		IgnoreTlog: true,
  1239  	}
  1240  	mustErr(cmd1.Exec(ctx, blob), t)
  1241  	mustErr(cmd2.Exec(ctx, blob), t)
  1242  
  1243  	// Now sign the blob with one key
  1244  	ko := options.KeyOpts{
  1245  		KeyRef:   privKeyPath1,
  1246  		PassFunc: passFunc,
  1247  	}
  1248  	sig, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", false)
  1249  	if err != nil {
  1250  		t.Fatal(err)
  1251  	}
  1252  	// Now verify should work with that one, but not the other
  1253  	cmd1.SigRef = string(sig)
  1254  	cmd2.SigRef = string(sig)
  1255  	must(cmd1.Exec(ctx, bp), t)
  1256  	mustErr(cmd2.Exec(ctx, bp), t)
  1257  }
  1258  
  1259  func TestSignBlobBundle(t *testing.T) {
  1260  	blob := "someblob"
  1261  	td1 := t.TempDir()
  1262  	bp := filepath.Join(td1, blob)
  1263  	bundlePath := filepath.Join(td1, "bundle.sig")
  1264  
  1265  	if err := os.WriteFile(bp, []byte(blob), 0644); err != nil {
  1266  		t.Fatal(err)
  1267  	}
  1268  
  1269  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td1)
  1270  	if err != nil {
  1271  		t.Fatal(err)
  1272  	}
  1273  
  1274  	_, privKeyPath1, pubKeyPath1 := keypair(t, td1)
  1275  
  1276  	ctx := context.Background()
  1277  
  1278  	ko1 := options.KeyOpts{
  1279  		KeyRef:     pubKeyPath1,
  1280  		BundlePath: bundlePath,
  1281  	}
  1282  	// Verify should fail on a bad input
  1283  	verifyBlobCmd := cliverify.VerifyBlobCmd{
  1284  		KeyOpts:    ko1,
  1285  		IgnoreTlog: true,
  1286  	}
  1287  	mustErr(verifyBlobCmd.Exec(ctx, bp), t)
  1288  
  1289  	// Now sign the blob with one key
  1290  	ko := options.KeyOpts{
  1291  		KeyRef:           privKeyPath1,
  1292  		PassFunc:         passFunc,
  1293  		BundlePath:       bundlePath,
  1294  		RekorURL:         rekorURL,
  1295  		SkipConfirmation: true,
  1296  	}
  1297  	if _, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", false); err != nil {
  1298  		t.Fatal(err)
  1299  	}
  1300  	// Now verify should work
  1301  	must(verifyBlobCmd.Exec(ctx, bp), t)
  1302  
  1303  	// Now we turn on the tlog and sign again
  1304  	if _, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", true); err != nil {
  1305  		t.Fatal(err)
  1306  	}
  1307  
  1308  	// Point to a fake rekor server to make sure offline verification of the tlog entry works
  1309  	verifyBlobCmd.RekorURL = "notreal"
  1310  	verifyBlobCmd.IgnoreTlog = false
  1311  	must(verifyBlobCmd.Exec(ctx, bp), t)
  1312  }
  1313  
  1314  func TestSignBlobRFC3161TimestampBundle(t *testing.T) {
  1315  	td := t.TempDir()
  1316  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
  1317  	if err != nil {
  1318  		t.Fatal(err)
  1319  	}
  1320  	// TSA server needed to create timestamp
  1321  	viper.Set("timestamp-signer", "memory")
  1322  	viper.Set("timestamp-signer-hash", "sha256")
  1323  	apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second)
  1324  	server := httptest.NewServer(apiServer.GetHandler())
  1325  	t.Cleanup(server.Close)
  1326  
  1327  	blob := "someblob"
  1328  	bp := filepath.Join(td, blob)
  1329  	bundlePath := filepath.Join(td, "bundle.sig")
  1330  	tsPath := filepath.Join(td, "rfc3161Timestamp.json")
  1331  
  1332  	if err := os.WriteFile(bp, []byte(blob), 0644); err != nil {
  1333  		t.Fatal(err)
  1334  	}
  1335  
  1336  	client, err := tsaclient.GetTimestampClient(server.URL)
  1337  	if err != nil {
  1338  		t.Error(err)
  1339  	}
  1340  
  1341  	chain, err := client.Timestamp.GetTimestampCertChain(nil)
  1342  	if err != nil {
  1343  		t.Fatalf("unexpected error getting timestamp chain: %v", err)
  1344  	}
  1345  
  1346  	file, err := os.CreateTemp(os.TempDir(), "tempfile")
  1347  	if err != nil {
  1348  		t.Fatalf("error creating temp file: %v", err)
  1349  	}
  1350  	defer os.Remove(file.Name())
  1351  	_, err = file.WriteString(chain.Payload)
  1352  	if err != nil {
  1353  		t.Fatalf("error writing chain payload to temp file: %v", err)
  1354  	}
  1355  
  1356  	_, privKeyPath1, pubKeyPath1 := keypair(t, td)
  1357  
  1358  	ctx := context.Background()
  1359  
  1360  	ko1 := options.KeyOpts{
  1361  		KeyRef:               pubKeyPath1,
  1362  		BundlePath:           bundlePath,
  1363  		RFC3161TimestampPath: tsPath,
  1364  		TSACertChainPath:     file.Name(),
  1365  	}
  1366  	// Verify should fail on a bad input
  1367  	verifyBlobCmd := cliverify.VerifyBlobCmd{
  1368  		KeyOpts:    ko1,
  1369  		IgnoreTlog: true,
  1370  	}
  1371  	mustErr(verifyBlobCmd.Exec(ctx, bp), t)
  1372  
  1373  	// Now sign the blob with one key
  1374  	ko := options.KeyOpts{
  1375  		KeyRef:               privKeyPath1,
  1376  		PassFunc:             passFunc,
  1377  		BundlePath:           bundlePath,
  1378  		RFC3161TimestampPath: tsPath,
  1379  		TSAServerURL:         server.URL + "/api/v1/timestamp",
  1380  		RekorURL:             rekorURL,
  1381  		SkipConfirmation:     true,
  1382  	}
  1383  	if _, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", false); err != nil {
  1384  		t.Fatal(err)
  1385  	}
  1386  	// Now verify should work
  1387  	must(verifyBlobCmd.Exec(ctx, bp), t)
  1388  
  1389  	// Now we turn on the tlog and sign again
  1390  	if _, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", true); err != nil {
  1391  		t.Fatal(err)
  1392  	}
  1393  	// Point to a fake rekor server to make sure offline verification of the tlog entry works
  1394  	verifyBlobCmd.RekorURL = "notreal"
  1395  	verifyBlobCmd.IgnoreTlog = false
  1396  	must(verifyBlobCmd.Exec(ctx, bp), t)
  1397  }
  1398  
  1399  func TestGenerate(t *testing.T) {
  1400  	repo, stop := reg(t)
  1401  	defer stop()
  1402  
  1403  	imgName := path.Join(repo, "cosign-e2e")
  1404  	_, desc, cleanup := mkimage(t, imgName)
  1405  	defer cleanup()
  1406  
  1407  	// Generate the payload for the image, and check the digest.
  1408  	b := bytes.Buffer{}
  1409  	must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, nil, &b), t)
  1410  	ss := payload.SimpleContainerImage{}
  1411  	must(json.Unmarshal(b.Bytes(), &ss), t)
  1412  
  1413  	equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t)
  1414  
  1415  	// Now try with some annotations.
  1416  	b.Reset()
  1417  	a := map[string]interface{}{"foo": "bar"}
  1418  	must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, a, &b), t)
  1419  	must(json.Unmarshal(b.Bytes(), &ss), t)
  1420  
  1421  	equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t)
  1422  	equals(ss.Optional["foo"], "bar", t)
  1423  }
  1424  
  1425  func TestSaveLoad(t *testing.T) {
  1426  	td := t.TempDir()
  1427  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
  1428  	if err != nil {
  1429  		t.Fatal(err)
  1430  	}
  1431  	tests := []struct {
  1432  		description     string
  1433  		getSignedEntity func(t *testing.T, n string) (name.Reference, *remote.Descriptor, func())
  1434  	}{
  1435  		{
  1436  			description:     "save and load an image",
  1437  			getSignedEntity: mkimage,
  1438  		},
  1439  		{
  1440  			description:     "save and load an image index",
  1441  			getSignedEntity: mkimageindex,
  1442  		},
  1443  	}
  1444  	for i, test := range tests {
  1445  		t.Run(test.description, func(t *testing.T) {
  1446  			repo, stop := reg(t)
  1447  			defer stop()
  1448  			keysDir := t.TempDir()
  1449  
  1450  			imgName := path.Join(repo, fmt.Sprintf("save-load-%d", i))
  1451  
  1452  			_, _, cleanup := test.getSignedEntity(t, imgName)
  1453  			defer cleanup()
  1454  
  1455  			_, privKeyPath, pubKeyPath := keypair(t, keysDir)
  1456  
  1457  			ctx := context.Background()
  1458  			// Now sign the image and verify it
  1459  			ko := options.KeyOpts{
  1460  				KeyRef:           privKeyPath,
  1461  				PassFunc:         passFunc,
  1462  				RekorURL:         rekorURL,
  1463  				SkipConfirmation: true,
  1464  			}
  1465  			so := options.SignOptions{
  1466  				Upload:     true,
  1467  				TlogUpload: true,
  1468  			}
  1469  			must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
  1470  			must(verify(pubKeyPath, imgName, true, nil, "", false), t)
  1471  
  1472  			// save the image to a temp dir
  1473  			imageDir := t.TempDir()
  1474  			must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t)
  1475  
  1476  			// verify the local image using a local key
  1477  			must(verifyLocal(pubKeyPath, imageDir, true, nil, ""), t)
  1478  
  1479  			// load the image from the temp dir into a new image and verify the new image
  1480  			imgName2 := path.Join(repo, fmt.Sprintf("save-load-%d-2", i))
  1481  			must(cli.LoadCmd(ctx, options.LoadOptions{Directory: imageDir}, imgName2), t)
  1482  			must(verify(pubKeyPath, imgName2, true, nil, "", false), t)
  1483  		})
  1484  	}
  1485  }
  1486  
  1487  func TestSaveLoadAttestation(t *testing.T) {
  1488  	td := t.TempDir()
  1489  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
  1490  	if err != nil {
  1491  		t.Fatal(err)
  1492  	}
  1493  
  1494  	repo, stop := reg(t)
  1495  	defer stop()
  1496  
  1497  	imgName := path.Join(repo, "save-load")
  1498  
  1499  	_, _, cleanup := mkimage(t, imgName)
  1500  	defer cleanup()
  1501  
  1502  	_, privKeyPath, pubKeyPath := keypair(t, td)
  1503  
  1504  	ctx := context.Background()
  1505  	// Now sign the image and verify it
  1506  	ko := options.KeyOpts{
  1507  		KeyRef:           privKeyPath,
  1508  		PassFunc:         passFunc,
  1509  		RekorURL:         rekorURL,
  1510  		SkipConfirmation: true,
  1511  	}
  1512  	so := options.SignOptions{
  1513  		Upload:     true,
  1514  		TlogUpload: true,
  1515  	}
  1516  	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
  1517  	must(verify(pubKeyPath, imgName, true, nil, "", false), t)
  1518  
  1519  	// now, append an attestation to the image
  1520  	slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
  1521  	slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
  1522  	if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
  1523  		t.Fatal(err)
  1524  	}
  1525  
  1526  	// Now attest the image
  1527  	ko = options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
  1528  	attestCommand := attest.AttestCommand{
  1529  		KeyOpts:        ko,
  1530  		PredicatePath:  slsaAttestationPath,
  1531  		PredicateType:  "slsaprovenance",
  1532  		Timeout:        30 * time.Second,
  1533  		RekorEntryType: "dsse",
  1534  	}
  1535  	must(attestCommand.Exec(ctx, imgName), t)
  1536  
  1537  	// save the image to a temp dir
  1538  	imageDir := t.TempDir()
  1539  	must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t)
  1540  
  1541  	// load the image from the temp dir into a new image and verify the new image
  1542  	imgName2 := path.Join(repo, "save-load-2")
  1543  	must(cli.LoadCmd(ctx, options.LoadOptions{Directory: imageDir}, imgName2), t)
  1544  	must(verify(pubKeyPath, imgName2, true, nil, "", false), t)
  1545  	// Use cue to verify attestation on the new image
  1546  	policyPath := filepath.Join(td, "policy.cue")
  1547  	verifyAttestation := cliverify.VerifyAttestationCommand{
  1548  		KeyRef:     pubKeyPath,
  1549  		IgnoreTlog: true,
  1550  		MaxWorkers: 10,
  1551  	}
  1552  	verifyAttestation.PredicateType = "slsaprovenance"
  1553  	verifyAttestation.Policies = []string{policyPath}
  1554  	// Success case (remote)
  1555  	cuePolicy := `predicate: builder: id: "2"`
  1556  	if err := os.WriteFile(policyPath, []byte(cuePolicy), 0600); err != nil {
  1557  		t.Fatal(err)
  1558  	}
  1559  	must(verifyAttestation.Exec(ctx, []string{imgName2}), t)
  1560  	// Success case (local)
  1561  	verifyAttestation.LocalImage = true
  1562  	must(verifyAttestation.Exec(ctx, []string{imageDir}), t)
  1563  }
  1564  
  1565  func TestAttachSBOM(t *testing.T) {
  1566  	td := t.TempDir()
  1567  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
  1568  	if err != nil {
  1569  		t.Fatal(err)
  1570  	}
  1571  
  1572  	repo, stop := reg(t)
  1573  	defer stop()
  1574  	ctx := context.Background()
  1575  
  1576  	imgName := path.Join(repo, "sbom-image")
  1577  	img, _, cleanup := mkimage(t, imgName)
  1578  	defer cleanup()
  1579  
  1580  	out := bytes.Buffer{}
  1581  
  1582  	_, errPl := download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{Platform: "darwin/amd64"}, img.Name(), &out)
  1583  	if errPl == nil {
  1584  		t.Fatalf("Expected error when passing Platform to single arch image")
  1585  	}
  1586  	_, err = download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{}, img.Name(), &out)
  1587  	if err == nil {
  1588  		t.Fatal("Expected error")
  1589  	}
  1590  	t.Log(out.String())
  1591  	out.Reset()
  1592  
  1593  	// Upload it!
  1594  	must(attach.SBOMCmd(ctx, options.RegistryOptions{}, options.RegistryExperimentalOptions{}, "./testdata/bom-go-mod.spdx", "spdx", imgName), t)
  1595  
  1596  	sboms, err := download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{}, imgName, &out)
  1597  	if err != nil {
  1598  		t.Fatal(err)
  1599  	}
  1600  	t.Log(out.String())
  1601  	if len(sboms) != 1 {
  1602  		t.Fatalf("Expected one sbom, got %d", len(sboms))
  1603  	}
  1604  	want, err := os.ReadFile("./testdata/bom-go-mod.spdx")
  1605  	if err != nil {
  1606  		t.Fatal(err)
  1607  	}
  1608  	if diff := cmp.Diff(string(want), sboms[0]); diff != "" {
  1609  		t.Errorf("diff: %s", diff)
  1610  	}
  1611  
  1612  	// Generate key pairs to sign the sbom
  1613  	td1 := t.TempDir()
  1614  	td2 := t.TempDir()
  1615  	_, privKeyPath1, pubKeyPath1 := keypair(t, td1)
  1616  	_, _, pubKeyPath2 := keypair(t, td2)
  1617  
  1618  	// Verify should fail on a bad input
  1619  	mustErr(verify(pubKeyPath1, imgName, true, nil, "sbom", false), t)
  1620  	mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom", false), t)
  1621  
  1622  	// Now sign the sbom with one key
  1623  	ko1 := options.KeyOpts{
  1624  		KeyRef:   privKeyPath1,
  1625  		PassFunc: passFunc,
  1626  		RekorURL: rekorURL,
  1627  	}
  1628  	so := options.SignOptions{
  1629  		Upload:     true,
  1630  		TlogUpload: true,
  1631  		Attachment: "sbom",
  1632  	}
  1633  	must(sign.SignCmd(ro, ko1, so, []string{imgName}), t)
  1634  
  1635  	// Now verify should work with that one, but not the other
  1636  	must(verify(pubKeyPath1, imgName, true, nil, "sbom", false), t)
  1637  	mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom", false), t)
  1638  }
  1639  
  1640  func TestNoTlog(t *testing.T) {
  1641  	repo, stop := reg(t)
  1642  	defer stop()
  1643  	td := t.TempDir()
  1644  
  1645  	imgName := path.Join(repo, "cosign-e2e")
  1646  
  1647  	_, _, cleanup := mkimage(t, imgName)
  1648  	defer cleanup()
  1649  
  1650  	_, privKeyPath, pubKeyPath := keypair(t, td)
  1651  
  1652  	// Verify should fail at first
  1653  	mustErr(verify(pubKeyPath, imgName, true, nil, "", true), t)
  1654  
  1655  	// Now sign the image without the tlog
  1656  	ko := options.KeyOpts{
  1657  		KeyRef:   privKeyPath,
  1658  		PassFunc: passFunc,
  1659  		RekorURL: rekorURL,
  1660  	}
  1661  	so := options.SignOptions{
  1662  		Upload: true,
  1663  	}
  1664  	must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
  1665  
  1666  	// Now verify should work!
  1667  	must(verify(pubKeyPath, imgName, true, nil, "", true), t)
  1668  }
  1669  
  1670  func TestGetPublicKeyCustomOut(t *testing.T) {
  1671  	td := t.TempDir()
  1672  	keys, privKeyPath, _ := keypair(t, td)
  1673  	ctx := context.Background()
  1674  
  1675  	outFile := "output.pub"
  1676  	outPath := filepath.Join(td, outFile)
  1677  	outWriter, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0600)
  1678  	must(err, t)
  1679  
  1680  	pk := publickey.Pkopts{
  1681  		KeyRef: privKeyPath,
  1682  	}
  1683  	must(publickey.GetPublicKey(ctx, pk, publickey.NamedWriter{Name: outPath, Writer: outWriter}, passFunc), t)
  1684  
  1685  	output, err := os.ReadFile(outPath)
  1686  	must(err, t)
  1687  	equals(keys.PublicBytes, output, t)
  1688  }
  1689  
  1690  // If a signature has a bundle, but *not for that signature*, cosign verification should fail.
  1691  // This test is pretty long, so here are the basic points:
  1692  //  1. Sign image1 with a keypair, store entry in rekor
  1693  //  2. Sign image2 with keypair, DO NOT store entry in rekor
  1694  //  3. Take the bundle from image1 and store it on the signature in image2
  1695  //  4. Verification of image2 should now fail, since the bundle is for a different signature
  1696  func TestInvalidBundle(t *testing.T) {
  1697  	td := t.TempDir()
  1698  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
  1699  	if err != nil {
  1700  		t.Fatal(err)
  1701  	}
  1702  
  1703  	regName, stop := reg(t)
  1704  	defer stop()
  1705  
  1706  	img1 := path.Join(regName, "cosign-e2e")
  1707  
  1708  	imgRef, _, cleanup := mkimage(t, img1)
  1709  	defer cleanup()
  1710  
  1711  	_, privKeyPath, pubKeyPath := keypair(t, td)
  1712  
  1713  	ctx := context.Background()
  1714  
  1715  	// Sign image1 and store the entry in rekor
  1716  	// (we're just using it for its bundle)
  1717  	remoteOpts := ociremote.WithRemoteOptions(registryClientOpts(ctx)...)
  1718  	ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL}
  1719  	so := options.SignOptions{
  1720  		Upload:           true,
  1721  		TlogUpload:       true,
  1722  		SkipConfirmation: true,
  1723  	}
  1724  	must(sign.SignCmd(ro, ko, so, []string{img1}), t)
  1725  	// verify image1
  1726  	must(verify(pubKeyPath, img1, true, nil, "", false), t)
  1727  	// extract the bundle from image1
  1728  	si, err := ociremote.SignedImage(imgRef, remoteOpts)
  1729  	must(err, t)
  1730  	imgSigs, err := si.Signatures()
  1731  	must(err, t)
  1732  	sigs, err := imgSigs.Get()
  1733  	must(err, t)
  1734  	if l := len(sigs); l != 1 {
  1735  		t.Error("expected one signature")
  1736  	}
  1737  	bund, err := sigs[0].Bundle()
  1738  	must(err, t)
  1739  	if bund == nil {
  1740  		t.Fail()
  1741  	}
  1742  
  1743  	// Now, we move on to image2
  1744  	// Sign image2 and DO NOT store the entry in rekor
  1745  	img2 := path.Join(regName, "unrelated")
  1746  	imgRef2, _, cleanup := mkimage(t, img2)
  1747  	defer cleanup()
  1748  	so = options.SignOptions{
  1749  		Upload:     true,
  1750  		TlogUpload: false,
  1751  	}
  1752  	must(sign.SignCmd(ro, ko, so, []string{img2}), t)
  1753  	must(verify(pubKeyPath, img2, true, nil, "", true), t)
  1754  
  1755  	si2, err := ociremote.SignedEntity(imgRef2, remoteOpts)
  1756  	must(err, t)
  1757  	sigs2, err := si2.Signatures()
  1758  	must(err, t)
  1759  	gottenSigs2, err := sigs2.Get()
  1760  	must(err, t)
  1761  	if len(gottenSigs2) != 1 {
  1762  		t.Fatal("there should be one signature")
  1763  	}
  1764  	sigsTag, err := ociremote.SignatureTag(imgRef2)
  1765  	if err != nil {
  1766  		t.Fatal(err)
  1767  	}
  1768  
  1769  	// At this point, we would mutate the signature to add the bundle annotation
  1770  	// since we don't have a function for it at the moment, mock this by deleting the signature
  1771  	// and pushing a new signature with the additional bundle annotation
  1772  	if err := remote.Delete(sigsTag); err != nil {
  1773  		t.Fatal(err)
  1774  	}
  1775  	mustErr(verify(pubKeyPath, img2, true, nil, "", false), t)
  1776  
  1777  	newSig, err := mutate.Signature(gottenSigs2[0], mutate.WithBundle(bund))
  1778  	must(err, t)
  1779  	si2, err = ociremote.SignedEntity(imgRef2, remoteOpts)
  1780  	must(err, t)
  1781  	newImage, err := mutate.AttachSignatureToEntity(si2, newSig)
  1782  	must(err, t)
  1783  	if err := ociremote.WriteSignatures(sigsTag.Repository, newImage); err != nil {
  1784  		t.Fatal(err)
  1785  	}
  1786  
  1787  	// veriyfing image2 now should fail
  1788  	cmd := cliverify.VerifyCommand{
  1789  		KeyRef:        pubKeyPath,
  1790  		RekorURL:      rekorURL,
  1791  		CheckClaims:   true,
  1792  		HashAlgorithm: crypto.SHA256,
  1793  		MaxWorkers:    10,
  1794  	}
  1795  	args := []string{img2}
  1796  	mustErr(cmd.Exec(context.Background(), args), t)
  1797  }
  1798  
  1799  func TestAttestBlobSignVerify(t *testing.T) {
  1800  	blob := "someblob"
  1801  	predicate := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
  1802  	predicateType := "slsaprovenance"
  1803  
  1804  	td1 := t.TempDir()
  1805  	t.Cleanup(func() {
  1806  		os.RemoveAll(td1)
  1807  	})
  1808  
  1809  	bp := filepath.Join(td1, blob)
  1810  	if err := os.WriteFile(bp, []byte(blob), 0644); err != nil {
  1811  		t.Fatal(err)
  1812  	}
  1813  
  1814  	anotherBlob := filepath.Join(td1, "another-blob")
  1815  	if err := os.WriteFile(anotherBlob, []byte("another-blob"), 0644); err != nil {
  1816  		t.Fatal(err)
  1817  	}
  1818  
  1819  	predicatePath := filepath.Join(td1, "predicate")
  1820  	if err := os.WriteFile(predicatePath, []byte(predicate), 0644); err != nil {
  1821  		t.Fatal(err)
  1822  	}
  1823  
  1824  	outputSignature := filepath.Join(td1, "signature")
  1825  
  1826  	_, privKeyPath1, pubKeyPath1 := keypair(t, td1)
  1827  
  1828  	ctx := context.Background()
  1829  	ko := options.KeyOpts{
  1830  		KeyRef: pubKeyPath1,
  1831  	}
  1832  	blobVerifyAttestationCmd := cliverify.VerifyBlobAttestationCommand{
  1833  		KeyOpts:       ko,
  1834  		SignaturePath: outputSignature,
  1835  		PredicateType: predicateType,
  1836  		IgnoreTlog:    true,
  1837  		CheckClaims:   true,
  1838  	}
  1839  	// Verify should fail on a bad input
  1840  	mustErr(blobVerifyAttestationCmd.Exec(ctx, bp), t)
  1841  
  1842  	// Now attest the blob with the private key
  1843  	ko = options.KeyOpts{
  1844  		KeyRef:   privKeyPath1,
  1845  		PassFunc: passFunc,
  1846  	}
  1847  	attestBlobCmd := attest.AttestBlobCommand{
  1848  		KeyOpts:         ko,
  1849  		PredicatePath:   predicatePath,
  1850  		PredicateType:   predicateType,
  1851  		OutputSignature: outputSignature,
  1852  		RekorEntryType:  "dsse",
  1853  	}
  1854  	must(attestBlobCmd.Exec(ctx, bp), t)
  1855  
  1856  	// Now verify should work
  1857  	must(blobVerifyAttestationCmd.Exec(ctx, bp), t)
  1858  
  1859  	// Make sure we fail with the wrong predicate type
  1860  	blobVerifyAttestationCmd.PredicateType = "custom"
  1861  	mustErr(blobVerifyAttestationCmd.Exec(ctx, bp), t)
  1862  
  1863  	// Make sure we fail with the wrong blob (set the predicate type back)
  1864  	blobVerifyAttestationCmd.PredicateType = predicateType
  1865  	mustErr(blobVerifyAttestationCmd.Exec(ctx, anotherBlob), t)
  1866  }
  1867  
  1868  func TestOffline(t *testing.T) {
  1869  	td := t.TempDir()
  1870  	err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
  1871  	if err != nil {
  1872  		t.Fatal(err)
  1873  	}
  1874  
  1875  	regName, stop := reg(t)
  1876  	defer stop()
  1877  
  1878  	img1 := path.Join(regName, "cosign-e2e")
  1879  
  1880  	imgRef, _, cleanup := mkimage(t, img1)
  1881  	defer cleanup()
  1882  
  1883  	_, privKeyPath, pubKeyPath := keypair(t, td)
  1884  
  1885  	ctx := context.Background()
  1886  
  1887  	// Sign image1 and store the entry in rekor
  1888  	ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL}
  1889  	so := options.SignOptions{
  1890  		Upload:           true,
  1891  		TlogUpload:       true,
  1892  		SkipConfirmation: true,
  1893  	}
  1894  	must(sign.SignCmd(ro, ko, so, []string{img1}), t)
  1895  	// verify image1 online and offline
  1896  	must(verify(pubKeyPath, img1, true, nil, "", false), t)
  1897  	verifyCmd := &cliverify.VerifyCommand{
  1898  		KeyRef:      pubKeyPath,
  1899  		RekorURL:    "notreal",
  1900  		Offline:     true,
  1901  		CheckClaims: true,
  1902  		MaxWorkers:  10,
  1903  	}
  1904  	must(verifyCmd.Exec(ctx, []string{img1}), t)
  1905  
  1906  	// Get signatures
  1907  	si, err := ociremote.SignedEntity(imgRef)
  1908  	must(err, t)
  1909  	sigs, err := si.Signatures()
  1910  	must(err, t)
  1911  	gottenSigs, err := sigs.Get()
  1912  	must(err, t)
  1913  
  1914  	fakeBundle := &bundle.RekorBundle{
  1915  		SignedEntryTimestamp: []byte(""),
  1916  		Payload: bundle.RekorPayload{
  1917  			Body: "",
  1918  		},
  1919  	}
  1920  	newSig, err := mutate.Signature(gottenSigs[0], mutate.WithBundle(fakeBundle))
  1921  	must(err, t)
  1922  
  1923  	sigsTag, err := ociremote.SignatureTag(imgRef)
  1924  	if err := remote.Delete(sigsTag); err != nil {
  1925  		t.Fatal(err)
  1926  	}
  1927  
  1928  	si, err = ociremote.SignedEntity(imgRef)
  1929  	must(err, t)
  1930  	newImage, err := mutate.AttachSignatureToEntity(si, newSig)
  1931  	must(err, t)
  1932  
  1933  	mustErr(verify(pubKeyPath, img1, true, nil, "", false), t)
  1934  	if err := ociremote.WriteSignatures(sigsTag.Repository, newImage); err != nil {
  1935  		t.Fatal(err)
  1936  	}
  1937  
  1938  	// Confirm offline verification fails
  1939  	mustErr(verifyCmd.Exec(ctx, []string{img1}), t)
  1940  }
  1941  
  1942  func TestDockerfileVerify(t *testing.T) {
  1943  	td := t.TempDir()
  1944  
  1945  	// set up SIGSTORE_ variables to point to keys for the local instances
  1946  	err := setLocalEnv(t, td)
  1947  	if err != nil {
  1948  		t.Fatal(err)
  1949  	}
  1950  
  1951  	// unset the roots that were generated for timestamp signing, they won't work here
  1952  	err = fulcioroots.ReInit()
  1953  	if err != nil {
  1954  		t.Fatal(err)
  1955  	}
  1956  
  1957  	identityToken, err := getOIDCToken()
  1958  	if err != nil {
  1959  		t.Fatal(err)
  1960  	}
  1961  
  1962  	// create some images
  1963  	repo, stop := reg(t)
  1964  	defer stop()
  1965  	signedImg1 := path.Join(repo, "cosign-e2e-dockerfile-signed1")
  1966  	_, _, cleanup1 := mkimage(t, signedImg1)
  1967  	defer cleanup1()
  1968  	signedImg2 := path.Join(repo, "cosign-e2e-dockerfile-signed2")
  1969  	_, _, cleanup2 := mkimage(t, signedImg2)
  1970  	defer cleanup2()
  1971  	unsignedImg := path.Join(repo, "cosign-e2e-dockerfile-unsigned")
  1972  	_, _, cleanupUnsigned := mkimage(t, unsignedImg)
  1973  	defer cleanupUnsigned()
  1974  
  1975  	// sign the images using --identity-token
  1976  	ko := options.KeyOpts{
  1977  		FulcioURL:        fulcioURL,
  1978  		RekorURL:         rekorURL,
  1979  		IDToken:          identityToken,
  1980  		SkipConfirmation: true,
  1981  	}
  1982  	so := options.SignOptions{
  1983  		Upload:           true,
  1984  		TlogUpload:       true,
  1985  		SkipConfirmation: true,
  1986  	}
  1987  	ctx := context.Background()
  1988  	must(sign.SignCmd(ro, ko, so, []string{signedImg1}), t)
  1989  	must(sign.SignCmd(ro, ko, so, []string{signedImg2}), t)
  1990  
  1991  	// create the dockerfiles
  1992  	singleStageDockerfileContents := fmt.Sprintf(`
  1993  FROM %s
  1994  `, signedImg1)
  1995  	singleStageDockerfile := mkfile(singleStageDockerfileContents, td, t)
  1996  
  1997  	unsignedBuildStageDockerfileContents := fmt.Sprintf(`
  1998  FROM %s
  1999  
  2000  FROM %s
  2001  
  2002  FROM %s
  2003  `, signedImg1, unsignedImg, signedImg2)
  2004  	unsignedBuildStageDockerfile := mkfile(unsignedBuildStageDockerfileContents, td, t)
  2005  
  2006  	fromAsDockerfileContents := fmt.Sprintf(`
  2007  FROM --platform=linux/amd64 %s AS base
  2008  `, signedImg1)
  2009  	fromAsDockerfile := mkfile(fromAsDockerfileContents, td, t)
  2010  
  2011  	withArgDockerfileContents := `
  2012  ARG test_image
  2013  
  2014  FROM ${test_image}
  2015  `
  2016  	withArgDockerfile := mkfile(withArgDockerfileContents, td, t)
  2017  
  2018  	withLowercaseDockerfileContents := fmt.Sprintf(`
  2019  from %s
  2020  `, signedImg1)
  2021  	withLowercaseDockerfile := mkfile(withLowercaseDockerfileContents, td, t)
  2022  
  2023  	issuer := os.Getenv("OIDC_URL")
  2024  
  2025  	tests := []struct {
  2026  		name       string
  2027  		dockerfile string
  2028  		baseOnly   bool
  2029  		env        map[string]string
  2030  		wantErr    bool
  2031  	}{
  2032  		{
  2033  			name:       "verify single stage",
  2034  			dockerfile: singleStageDockerfile,
  2035  		},
  2036  		{
  2037  			name:       "verify unsigned build stage",
  2038  			dockerfile: unsignedBuildStageDockerfile,
  2039  			wantErr:    true,
  2040  		},
  2041  		{
  2042  			name:       "verify base image only",
  2043  			dockerfile: unsignedBuildStageDockerfile,
  2044  			baseOnly:   true,
  2045  		},
  2046  		{
  2047  			name:       "verify from as",
  2048  			dockerfile: fromAsDockerfile,
  2049  		},
  2050  		{
  2051  			name:       "verify with arg",
  2052  			dockerfile: withArgDockerfile,
  2053  			env:        map[string]string{"test_image": signedImg1},
  2054  		},
  2055  		{
  2056  			name:       "verify image exists but is unsigned",
  2057  			dockerfile: withArgDockerfile,
  2058  			env:        map[string]string{"test_image": unsignedImg},
  2059  			wantErr:    true,
  2060  		},
  2061  		{
  2062  			name:       "verify with lowercase",
  2063  			dockerfile: withLowercaseDockerfile,
  2064  		},
  2065  	}
  2066  	for _, test := range tests {
  2067  		t.Run(test.name, func(t *testing.T) {
  2068  			cmd := dockerfile.VerifyDockerfileCommand{
  2069  				VerifyCommand: cliverify.VerifyCommand{
  2070  					CertVerifyOptions: options.CertVerifyOptions{
  2071  						CertOidcIssuer: issuer,
  2072  						CertIdentity:   certID,
  2073  					},
  2074  					RekorURL: rekorURL,
  2075  				},
  2076  				BaseOnly: test.baseOnly,
  2077  			}
  2078  			args := []string{test.dockerfile}
  2079  			for k, v := range test.env {
  2080  				t.Setenv(k, v)
  2081  			}
  2082  			if test.wantErr {
  2083  				mustErr(cmd.Exec(ctx, args), t)
  2084  			} else {
  2085  				must(cmd.Exec(ctx, args), t)
  2086  			}
  2087  		})
  2088  	}
  2089  }
  2090  
  2091  func TestManifestVerify(t *testing.T) {
  2092  	td := t.TempDir()
  2093  
  2094  	// set up SIGSTORE_ variables to point to keys for the local instances
  2095  	err := setLocalEnv(t, td)
  2096  	if err != nil {
  2097  		t.Fatal(err)
  2098  	}
  2099  
  2100  	// unset the roots that were generated for timestamp signing, they won't work here
  2101  	err = fulcioroots.ReInit()
  2102  	if err != nil {
  2103  		t.Fatal(err)
  2104  	}
  2105  
  2106  	identityToken, err := getOIDCToken()
  2107  	if err != nil {
  2108  		t.Fatal(err)
  2109  	}
  2110  
  2111  	// create some images
  2112  	repo, stop := reg(t)
  2113  	defer stop()
  2114  	signedImg := path.Join(repo, "cosign-e2e-manifest-signed")
  2115  	_, _, cleanup := mkimage(t, signedImg)
  2116  	defer cleanup()
  2117  	unsignedImg := path.Join(repo, "cosign-e2e-manifest-unsigned")
  2118  	_, _, cleanupUnsigned := mkimage(t, unsignedImg)
  2119  	defer cleanupUnsigned()
  2120  
  2121  	// sign the images using --identity-token
  2122  	ko := options.KeyOpts{
  2123  		FulcioURL:        fulcioURL,
  2124  		RekorURL:         rekorURL,
  2125  		IDToken:          identityToken,
  2126  		SkipConfirmation: true,
  2127  	}
  2128  	so := options.SignOptions{
  2129  		Upload:           true,
  2130  		TlogUpload:       true,
  2131  		SkipConfirmation: true,
  2132  	}
  2133  	ctx := context.Background()
  2134  	must(sign.SignCmd(ro, ko, so, []string{signedImg}), t)
  2135  
  2136  	// create the manifests
  2137  	manifestTemplate := `
  2138  apiVersion: v1
  2139  kind: Pod
  2140  metadata:
  2141    name: single-pod
  2142  spec:
  2143    containers:
  2144      - name: %s
  2145        image: %s
  2146  `
  2147  	signedManifestContents := fmt.Sprintf(manifestTemplate, "signed-img", signedImg)
  2148  	signedManifest := mkfileWithExt(signedManifestContents, td, ".yaml", t)
  2149  	unsignedManifestContents := fmt.Sprintf(manifestTemplate, "unsigned-img", unsignedImg)
  2150  	unsignedManifest := mkfileWithExt(unsignedManifestContents, td, ".yaml", t)
  2151  
  2152  	issuer := os.Getenv("OIDC_URL")
  2153  
  2154  	tests := []struct {
  2155  		name     string
  2156  		manifest string
  2157  		wantErr  bool
  2158  	}{
  2159  		{
  2160  			name:     "signed manifest",
  2161  			manifest: signedManifest,
  2162  		},
  2163  		{
  2164  			name:     "unsigned manifest",
  2165  			manifest: unsignedManifest,
  2166  			wantErr:  true,
  2167  		},
  2168  	}
  2169  	for _, test := range tests {
  2170  		t.Run(test.name, func(t *testing.T) {
  2171  			cmd := manifest.VerifyManifestCommand{
  2172  				VerifyCommand: cliverify.VerifyCommand{
  2173  					CertVerifyOptions: options.CertVerifyOptions{
  2174  						CertOidcIssuer: issuer,
  2175  						CertIdentity:   certID,
  2176  					},
  2177  					RekorURL: rekorURL,
  2178  				},
  2179  			}
  2180  			args := []string{test.manifest}
  2181  			if test.wantErr {
  2182  				mustErr(cmd.Exec(ctx, args), t)
  2183  			} else {
  2184  				must(cmd.Exec(ctx, args), t)
  2185  			}
  2186  		})
  2187  	}
  2188  }
  2189  
  2190  // getOIDCToken gets an OIDC token from the mock OIDC server.
  2191  func getOIDCToken() (string, error) {
  2192  	issuer := os.Getenv("OIDC_URL")
  2193  	resp, err := http.Get(issuer + "/token")
  2194  	if err != nil {
  2195  		return "", err
  2196  	}
  2197  	defer resp.Body.Close()
  2198  	body, err := io.ReadAll(resp.Body)
  2199  	if err != nil {
  2200  		return "", err
  2201  	}
  2202  	return string(body), nil
  2203  }
  2204  
  2205  func setLocalEnv(t *testing.T, dir string) error {
  2206  	// fulcio repo is downloaded to the user's home directory by e2e_test.sh
  2207  	home, err := os.UserHomeDir()
  2208  	if err != nil {
  2209  		return fmt.Errorf("error getting home directory: %w", err)
  2210  	}
  2211  	t.Setenv(env.VariableSigstoreCTLogPublicKeyFile.String(), path.Join(home, "fulcio/config/ctfe/pubkey.pem"))
  2212  	err = downloadAndSetEnv(t, fulcioURL+"/api/v1/rootCert", env.VariableSigstoreRootFile.String(), dir)
  2213  	if err != nil {
  2214  		return fmt.Errorf("error setting %s env var: %w", env.VariableSigstoreRootFile.String(), err)
  2215  	}
  2216  	err = downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), dir)
  2217  	if err != nil {
  2218  		return fmt.Errorf("error setting %s env var: %w", env.VariableSigstoreRekorPublicKey.String(), err)
  2219  	}
  2220  	return nil
  2221  }
  2222  
  2223  func downloadAndSetEnv(t *testing.T, url, envVar, dir string) error {
  2224  	resp, err := http.Get(url)
  2225  	if err != nil {
  2226  		return fmt.Errorf("error downloading file: %w", err)
  2227  	}
  2228  	defer resp.Body.Close()
  2229  	f, err := os.CreateTemp(dir, "")
  2230  	if err != nil {
  2231  		return fmt.Errorf("error creating temp file: %w", err)
  2232  	}
  2233  	defer f.Close()
  2234  	_, err = io.Copy(f, resp.Body)
  2235  	if err != nil {
  2236  		return fmt.Errorf("error writing to file: %w", err)
  2237  	}
  2238  	t.Setenv(envVar, f.Name())
  2239  	return nil
  2240  }
  2241  

View as plain text