sigs.k8s.io/release-sdk@v0.11.1-0.20240417074027-8061fb5e4952/sign/sign_test.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes 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  
    17  package sign_test
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"log"
    23  	"net/http"
    24  	"os"
    25  	"path/filepath"
    26  	"sync"
    27  	"testing"
    28  
    29  	"github.com/google/go-containerregistry/pkg/name"
    30  	"github.com/google/go-containerregistry/pkg/v1/remote/transport"
    31  	"github.com/sigstore/cosign/v2/pkg/cosign"
    32  	"github.com/sigstore/rekor/pkg/generated/models"
    33  	"github.com/stretchr/testify/require"
    34  	"sigs.k8s.io/release-sdk/sign"
    35  	"sigs.k8s.io/release-sdk/sign/signfakes"
    36  )
    37  
    38  var errTest = errors.New("error")
    39  
    40  func TestUploadBlob(t *testing.T) {
    41  	t.Parallel()
    42  
    43  	for _, tc := range []struct {
    44  		prepare func(*signfakes.FakeImpl)
    45  		assert  func(error)
    46  	}{
    47  		{ // Success
    48  			prepare: func(_ *signfakes.FakeImpl) {
    49  			},
    50  			assert: func(err error) {
    51  				require.Nil(t, err)
    52  			},
    53  		},
    54  	} {
    55  		mock := &signfakes.FakeImpl{}
    56  		tc.prepare(mock)
    57  
    58  		sut := sign.New(sign.Default())
    59  		sut.SetImpl(mock)
    60  
    61  		err := sut.UploadBlob("")
    62  		tc.assert(err)
    63  	}
    64  }
    65  
    66  func TestSignImage(t *testing.T) {
    67  	t.Parallel()
    68  	// Some of these tests require a real IDentity token
    69  	token := "DUMMYTOKEN"
    70  
    71  	for _, tc := range []struct {
    72  		fakeReference *FakeReferenceStub
    73  		prepare       func(*signfakes.FakeImpl)
    74  		assert        func(*sign.SignedObject, error)
    75  	}{
    76  		{ // Success
    77  			fakeReference: &FakeReferenceStub{
    78  				image:      "gcr.io/fake/honk:99.99.99",
    79  				registry:   "gcr.io",
    80  				repository: "fake/honk",
    81  			},
    82  			prepare: func(mock *signfakes.FakeImpl) {
    83  				mock.VerifyImageInternalReturns(&sign.SignedObject{}, nil)
    84  				mock.SignImageInternalReturns(nil)
    85  				mock.TokenFromProvidersReturns(token, nil)
    86  				m := &sync.Map{}
    87  				m.Store("gcr.io/fake/honk:99.99.99", true)
    88  				mock.ImagesSignedReturns(m, nil)
    89  				mock.DigestReturns("sha256:honk69059c8e84bed02f4c4385d432808e2c8055eb5087f7fea74e286b736a", nil)
    90  				mock.NewWithContextReturns(&testRoundTripper{}, nil)
    91  			},
    92  			assert: func(obj *sign.SignedObject, err error) {
    93  				require.NoError(t, err)
    94  				require.NotNil(t, obj)
    95  				require.NotEmpty(t, obj.Image().Reference())
    96  				require.NotEmpty(t, obj.Image().Digest())
    97  				require.NotEmpty(t, obj.Image().Signature())
    98  				require.Equal(t, obj.Image().Reference(), "gcr.io/fake/honk:99.99.99")
    99  				require.Equal(t, obj.Image().Digest(), "sha256:honk69059c8e84bed02f4c4385d432808e2c8055eb5087f7fea74e286b736a")
   100  				require.Equal(t, obj.Image().Signature(), "gcr.io/fake/honk:sha256-honk69059c8e84bed02f4c4385d432808e2c8055eb5087f7fea74e286b736a.sig")
   101  			},
   102  		},
   103  		{ // Failure on Verify
   104  			fakeReference: &FakeReferenceStub{
   105  				image:      "gcr.io/fake/honk:99.99.99",
   106  				registry:   "gcr.io",
   107  				repository: "fake/honk",
   108  			},
   109  			prepare: func(mock *signfakes.FakeImpl) {
   110  				m := &sync.Map{}
   111  				m.Store("gcr.io/fake/honk:99.99.99", true)
   112  				mock.ImagesSignedReturns(m, nil)
   113  				mock.VerifyImageInternalReturns(nil, errTest)
   114  				mock.SignImageInternalReturns(nil)
   115  				mock.TokenFromProvidersReturns(token, nil)
   116  			},
   117  			assert: func(obj *sign.SignedObject, err error) {
   118  				require.Error(t, err)
   119  				require.Nil(t, obj)
   120  			},
   121  		},
   122  		{ // Failure on Sign
   123  			fakeReference: &FakeReferenceStub{
   124  				image:      "gcr.io/fake/honk:99.99.99",
   125  				registry:   "gcr.io",
   126  				repository: "fake/honk",
   127  			},
   128  			prepare: func(mock *signfakes.FakeImpl) {
   129  				mock.VerifyImageInternalReturns(&sign.SignedObject{}, nil)
   130  				mock.SignImageInternalReturns(errTest)
   131  				mock.TokenFromProvidersReturns(token, nil)
   132  			},
   133  			assert: func(obj *sign.SignedObject, err error) {
   134  				require.Error(t, err)
   135  				require.Nil(t, obj)
   136  			},
   137  		},
   138  		{ // Failure getting identity token
   139  			fakeReference: &FakeReferenceStub{
   140  				image:      "gcr.io/fake/honk:99.99.99",
   141  				registry:   "gcr.io",
   142  				repository: "fake/honk",
   143  			},
   144  			prepare: func(mock *signfakes.FakeImpl) {
   145  				mock.TokenFromProvidersReturns(token, errTest)
   146  			},
   147  			assert: func(obj *sign.SignedObject, err error) {
   148  				require.Error(t, err)
   149  				require.Nil(t, obj)
   150  			},
   151  		},
   152  	} {
   153  		mock := &signfakes.FakeImpl{}
   154  		mock.ParseReferenceReturns(tc.fakeReference, nil)
   155  		tc.prepare(mock)
   156  
   157  		opts := sign.Default()
   158  		opts.Verbose = true
   159  
   160  		sut := sign.New(opts)
   161  		sut.SetImpl(mock)
   162  
   163  		obj, err := sut.SignImage(tc.fakeReference.image)
   164  		tc.assert(obj, err)
   165  	}
   166  }
   167  
   168  func TestSignFile(t *testing.T) {
   169  	t.Parallel()
   170  
   171  	opts := sign.Default()
   172  	opts.PrivateKeyPath = "/dummy/cosign.key"
   173  	opts.PublicKeyPath = "/dummy/cosign.pub"
   174  
   175  	// Create temporary directory for files.
   176  	tempDir, err := os.MkdirTemp("", "k8s-test-file-")
   177  	require.Nil(t, err)
   178  	defer func() {
   179  		require.Nil(t, os.RemoveAll(tempDir))
   180  	}()
   181  
   182  	// Create temporary file for test.
   183  	tempFile := filepath.Join(tempDir, "test-file")
   184  	require.Nil(t, os.WriteFile(tempFile, []byte("dummy-content"), 0o644))
   185  
   186  	for _, tc := range []struct {
   187  		path    string
   188  		options *sign.Options
   189  		prepare func(*signfakes.FakeImpl)
   190  		assert  func(*sign.SignedObject, error)
   191  	}{
   192  		{ // Success
   193  			path:    tempFile,
   194  			options: opts,
   195  			prepare: func(mock *signfakes.FakeImpl) {
   196  				mock.VerifyFileInternalReturns(nil)
   197  				mock.SignFileInternalReturns(nil)
   198  			},
   199  			assert: func(obj *sign.SignedObject, err error) {
   200  				require.Nil(t, err)
   201  				require.NotNil(t, obj)
   202  				require.NotEmpty(t, obj.File().Path())
   203  				require.NotEmpty(t, obj.File().CertificatePath())
   204  				require.NotEmpty(t, obj.File().SignaturePath())
   205  			},
   206  		},
   207  		{ // Success custom sig and cert.
   208  			path: tempFile,
   209  			options: &sign.Options{
   210  				PrivateKeyPath:        opts.PrivateKeyPath,
   211  				OutputSignaturePath:   "/tmp/test-file.sig",
   212  				OutputCertificatePath: "/tmp/test-file.cert",
   213  			},
   214  			prepare: func(mock *signfakes.FakeImpl) {
   215  				mock.VerifyFileInternalReturns(nil)
   216  				mock.SignFileInternalReturns(nil)
   217  			},
   218  			assert: func(obj *sign.SignedObject, err error) {
   219  				require.Nil(t, err)
   220  				require.NotNil(t, obj)
   221  				require.NotEmpty(t, obj.File().Path())
   222  				require.NotEmpty(t, obj.File().CertificatePath())
   223  				require.NotEmpty(t, obj.File().SignaturePath())
   224  			},
   225  		},
   226  		{ // File does not exist.
   227  			path:    "/dummy/test-no-file",
   228  			options: opts,
   229  			prepare: func(mock *signfakes.FakeImpl) {
   230  				mock.PayloadBytesReturns(nil, errTest)
   231  			},
   232  			assert: func(obj *sign.SignedObject, err error) {
   233  				require.Nil(t, obj)
   234  				require.ErrorContains(t, err, "file retrieve sha256:")
   235  			},
   236  		},
   237  		{ // File does can't sign.
   238  			path:    tempFile,
   239  			options: opts,
   240  			prepare: func(mock *signfakes.FakeImpl) {
   241  				mock.SignFileInternalReturns(errTest)
   242  			},
   243  			assert: func(obj *sign.SignedObject, err error) {
   244  				require.Nil(t, obj)
   245  				require.ErrorContains(t, err, "sign file:")
   246  			},
   247  		},
   248  		{ // Default sig and cert file test
   249  			path:    tempFile,
   250  			options: opts,
   251  			prepare: func(mock *signfakes.FakeImpl) {
   252  				mock.VerifyFileInternalReturns(nil)
   253  				mock.SignFileInternalReturns(nil)
   254  			},
   255  			assert: func(obj *sign.SignedObject, err error) {
   256  				require.Nil(t, err)
   257  
   258  				require.NotNil(t, obj)
   259  				require.NotEmpty(t, obj.File().Path())
   260  				require.NotEmpty(t, obj.File().CertificatePath())
   261  				require.NotEmpty(t, obj.File().SignaturePath())
   262  
   263  				require.Equal(t, obj.File().Path()+".cert", obj.File().CertificatePath())
   264  				require.Equal(t, obj.File().Path()+".sig", obj.File().SignaturePath())
   265  			},
   266  		},
   267  		{ // Verify failed.
   268  			path:    tempFile,
   269  			options: opts,
   270  			prepare: func(mock *signfakes.FakeImpl) {
   271  				mock.VerifyFileInternalReturns(errTest)
   272  			},
   273  			assert: func(obj *sign.SignedObject, err error) {
   274  				require.Nil(t, obj)
   275  				require.NotNil(t, err)
   276  				require.ErrorContains(t, err, "verifying signed file:")
   277  			},
   278  		},
   279  	} {
   280  		mock := &signfakes.FakeImpl{}
   281  		tc.prepare(mock)
   282  
   283  		opts := tc.options
   284  		opts.Verbose = true
   285  
   286  		sut := sign.New(opts)
   287  		sut.SetImpl(mock)
   288  
   289  		obj, err := sut.SignFile(tc.path)
   290  		tc.assert(obj, err)
   291  	}
   292  }
   293  
   294  func TestVerifyImage(t *testing.T) {
   295  	t.Parallel()
   296  
   297  	for _, tc := range []struct {
   298  		fakeReference *FakeReferenceStub
   299  		prepare       func(*signfakes.FakeImpl)
   300  		assert        func(*sign.SignedObject, error)
   301  	}{
   302  		{ // Success
   303  			fakeReference: &FakeReferenceStub{
   304  				image:      "gcr.io/fake/honk:99.99.99",
   305  				registry:   "gcr.io",
   306  				repository: "fake/honk",
   307  			},
   308  			prepare: func(mock *signfakes.FakeImpl) {
   309  				mock.VerifyImageInternalReturns(&sign.SignedObject{}, nil)
   310  				m := &sync.Map{}
   311  				m.Store("gcr.io/fake/honk:99.99.99", true)
   312  				mock.ImagesSignedReturns(m, nil)
   313  				mock.DigestReturns("sha256:honk69059c8e84bed02f4c4385d432808e2c8055eb5087f7fea74e286b736a", nil)
   314  				mock.NewWithContextReturns(&testRoundTripper{}, nil)
   315  			},
   316  			assert: func(_ *sign.SignedObject, err error) {
   317  				require.Nil(t, err)
   318  			},
   319  		},
   320  		{ // Failure on Verify
   321  			fakeReference: &FakeReferenceStub{
   322  				image:      "gcr.io/fake/honk:99.99.99",
   323  				registry:   "gcr.io",
   324  				repository: "fake/honk",
   325  			},
   326  			prepare: func(mock *signfakes.FakeImpl) {
   327  				mock.VerifyImageInternalReturns(nil, errTest)
   328  				mock.SetenvReturns(nil)
   329  				m := &sync.Map{}
   330  				m.Store("gcr.io/fake/honk:99.99.99", true)
   331  				mock.ImagesSignedReturns(m, nil)
   332  			},
   333  			assert: func(obj *sign.SignedObject, err error) {
   334  				require.NotNil(t, err)
   335  				require.Nil(t, obj)
   336  			},
   337  		},
   338  		{ // Skip on no signatures listed
   339  			fakeReference: &FakeReferenceStub{
   340  				image:      "gcr.io/fake/honk:99.99.99",
   341  				registry:   "gcr.io",
   342  				repository: "fake/honk",
   343  			},
   344  			prepare: func(mock *signfakes.FakeImpl) {
   345  				m := &sync.Map{}
   346  				m.Store("gcr.io/fake/honk:99.99.99", false)
   347  				mock.ImagesSignedReturns(m, nil)
   348  			},
   349  			assert: func(obj *sign.SignedObject, err error) {
   350  				require.Nil(t, err)
   351  				require.Nil(t, obj)
   352  			},
   353  		},
   354  	} {
   355  		mock := &signfakes.FakeImpl{}
   356  		mock.ParseReferenceReturns(tc.fakeReference, nil)
   357  		tc.prepare(mock)
   358  
   359  		sut := sign.New(sign.Default())
   360  		sut.SetImpl(mock)
   361  
   362  		obj, err := sut.VerifyImage(tc.fakeReference.image)
   363  		tc.assert(obj, err)
   364  	}
   365  }
   366  
   367  func TestVerifyFile(t *testing.T) {
   368  	t.Parallel()
   369  
   370  	// Create temporary directory for files.
   371  	tempDir, err := os.MkdirTemp("", "k8s-test-file-")
   372  	require.Nil(t, err)
   373  	defer func() {
   374  		require.Nil(t, os.RemoveAll(tempDir))
   375  	}()
   376  
   377  	// Create temporary file for test.
   378  	tempFile := filepath.Join(tempDir, "test-file")
   379  
   380  	payload := []byte("honk")
   381  	payloadSha256 := "4de18cc93efe15c1d1cc2407cfc9f054b4d9217975538ac005dba541acee1954"
   382  	uuid := "uuid"
   383  	var logindex int64 = 1
   384  	uuids := []models.LogEntryAnon{
   385  		{
   386  			LogID:    &uuid,
   387  			LogIndex: &logindex,
   388  		},
   389  	}
   390  	require.Nil(t, os.WriteFile(tempFile, payload, 0o644))
   391  
   392  	for _, tc := range []struct {
   393  		path    string
   394  		options *sign.Options
   395  		prepare func(*signfakes.FakeImpl)
   396  		assert  func(*sign.SignedObject, error)
   397  	}{
   398  		{ // Success
   399  			path:    tempFile,
   400  			options: sign.Default(),
   401  			prepare: func(mock *signfakes.FakeImpl) {
   402  				mock.PayloadBytesReturns(payload, nil)
   403  				mock.FindTlogEntryReturns(uuids, nil)
   404  			},
   405  			assert: func(obj *sign.SignedObject, err error) {
   406  				require.NotNil(t, obj.File)
   407  				require.Equal(t, obj.File().Path(), tempFile)
   408  				require.Equal(t, obj.File().SHA256(), payloadSha256)
   409  				require.Nil(t, err)
   410  			},
   411  		},
   412  		{ // File not signed
   413  			path:    tempFile,
   414  			options: sign.Default(),
   415  			prepare: func(mock *signfakes.FakeImpl) {
   416  				mock.PayloadBytesReturns(nil, nil)
   417  				mock.FindTlogEntryReturns(nil, nil)
   418  			},
   419  			assert: func(obj *sign.SignedObject, err error) {
   420  				require.Nil(t, obj)
   421  				require.Nil(t, err)
   422  			},
   423  		},
   424  		{ // File tlog not found
   425  			path:    tempFile,
   426  			options: sign.Default(),
   427  			prepare: func(mock *signfakes.FakeImpl) {
   428  				mock.PayloadBytesReturns(payload, nil)
   429  				mock.FindTlogEntryReturns(uuids, nil)
   430  				mock.VerifyFileInternalReturns(errTest)
   431  			},
   432  			assert: func(obj *sign.SignedObject, err error) {
   433  				require.Nil(t, obj)
   434  				require.NotNil(t, err)
   435  				require.ErrorContains(t, err, "verify file reference")
   436  			},
   437  		},
   438  		{ // File tlog error
   439  			path:    tempFile,
   440  			options: sign.Default(),
   441  			prepare: func(mock *signfakes.FakeImpl) {
   442  				mock.PayloadBytesReturns(payload, nil)
   443  				mock.FindTlogEntryReturns(nil, errTest)
   444  			},
   445  			assert: func(obj *sign.SignedObject, err error) {
   446  				require.Nil(t, obj)
   447  				require.NotNil(t, err)
   448  				require.ErrorContains(t, err, "find rekor tlog entries")
   449  			},
   450  		},
   451  	} {
   452  		mock := &signfakes.FakeImpl{}
   453  		tc.prepare(mock)
   454  
   455  		tmpDir := t.TempDir()
   456  		_, pubFile := generateKeyFile(t, tmpDir, nil)
   457  		opts := tc.options
   458  		opts.Verbose = true
   459  		opts.PublicKeyPath = pubFile
   460  
   461  		sut := sign.New(opts)
   462  		sut.SetImpl(mock)
   463  
   464  		obj, err := sut.VerifyFile(tc.path, false)
   465  		tc.assert(obj, err)
   466  	}
   467  }
   468  
   469  func generateKeyFile(t *testing.T, tmpDir string, pf cosign.PassFunc) (privFile, pubFile string) {
   470  	t.Helper()
   471  
   472  	tmpPrivFile, err := os.CreateTemp(tmpDir, "cosign_test_*.key")
   473  	if err != nil {
   474  		t.Fatalf("failed to create temp key file: %v", err)
   475  	}
   476  	defer tmpPrivFile.Close()
   477  	tmpPubFile, err := os.CreateTemp(tmpDir, "cosign_test_*.pub")
   478  	if err != nil {
   479  		t.Fatalf("failed to create temp pub file: %v", err)
   480  	}
   481  	defer tmpPubFile.Close()
   482  
   483  	// Generate a valid keypair.
   484  	keys, err := cosign.GenerateKeyPair(pf)
   485  	if err != nil {
   486  		t.Fatalf("failed to generate keypair: %v", err)
   487  	}
   488  
   489  	if _, err := tmpPrivFile.Write(keys.PrivateBytes); err != nil {
   490  		t.Fatalf("failed to write key file: %v", err)
   491  	}
   492  	if _, err := tmpPubFile.Write(keys.PublicBytes); err != nil {
   493  		t.Fatalf("failed to write pub file: %v", err)
   494  	}
   495  	return tmpPrivFile.Name(), tmpPubFile.Name()
   496  }
   497  
   498  func TestIsImageSigned(t *testing.T) {
   499  	t.Parallel()
   500  
   501  	for _, tc := range []struct {
   502  		prepare func(*signfakes.FakeImpl)
   503  		assert  func(bool, error)
   504  	}{
   505  		{ // Success, signed
   506  			prepare: func(mock *signfakes.FakeImpl) {
   507  				m := &sync.Map{}
   508  				m.Store("", true)
   509  				mock.ImagesSignedReturns(m, nil)
   510  			},
   511  			assert: func(signed bool, err error) {
   512  				require.True(t, signed)
   513  				require.Nil(t, err)
   514  			},
   515  		},
   516  		{ // Success, not signed
   517  			prepare: func(mock *signfakes.FakeImpl) {
   518  				m := &sync.Map{}
   519  				m.Store("", false)
   520  				mock.ImagesSignedReturns(m, nil)
   521  			},
   522  			assert: func(signed bool, err error) {
   523  				require.False(t, signed)
   524  				require.Nil(t, err)
   525  			},
   526  		},
   527  		{ // failure ImagesSigned errors
   528  			prepare: func(mock *signfakes.FakeImpl) {
   529  				mock.ImagesSignedReturns(nil, errTest)
   530  			},
   531  			assert: func(_ bool, err error) {
   532  				require.Error(t, err)
   533  			},
   534  		},
   535  		{ // failure ref not part of the result
   536  			prepare: func(mock *signfakes.FakeImpl) {
   537  				m := &sync.Map{}
   538  				mock.ImagesSignedReturns(m, nil)
   539  			},
   540  			assert: func(_ bool, err error) {
   541  				require.Error(t, err)
   542  			},
   543  		},
   544  		{ // failure on interface conversion
   545  			prepare: func(mock *signfakes.FakeImpl) {
   546  				m := &sync.Map{}
   547  				m.Store("", 1)
   548  				mock.ImagesSignedReturns(m, nil)
   549  			},
   550  			assert: func(_ bool, err error) {
   551  				require.Error(t, err)
   552  			},
   553  		},
   554  	} {
   555  		mock := &signfakes.FakeImpl{}
   556  		tc.prepare(mock)
   557  
   558  		sut := sign.New(sign.Default())
   559  		sut.SetImpl(mock)
   560  
   561  		res, err := sut.IsImageSigned("")
   562  		tc.assert(res, err)
   563  	}
   564  }
   565  
   566  // FakeReferenceStub implements the name.Reference to we use in the testing
   567  //
   568  //	type FakeReferenceStub interface {
   569  //		fmt.Stringer
   570  //		// Context accesses the Repository context of the reference.
   571  //		Context() name.Repository
   572  //		// Identifier accesses the type-specific portion of the reference.
   573  //		Identifier() string
   574  //		// Name is the fully-qualified reference name.
   575  //		Name() string
   576  //		// Scope is the scope needed to access this reference.
   577  //		Scope(string)
   578  //	}
   579  type FakeReferenceStub struct {
   580  	image      string
   581  	registry   string
   582  	repository string
   583  }
   584  
   585  func (fr *FakeReferenceStub) Context() name.Repository {
   586  	reg, err := name.NewRepository(fr.repository, name.WithDefaultRegistry(fr.registry))
   587  	if err != nil {
   588  		log.Fatal(err)
   589  	}
   590  	return reg
   591  }
   592  
   593  func (*FakeReferenceStub) Identifier() string {
   594  	return ""
   595  }
   596  
   597  func (*FakeReferenceStub) Scope(s string) string {
   598  	return s
   599  }
   600  
   601  func (fr *FakeReferenceStub) Name() string {
   602  	return fr.image
   603  }
   604  
   605  func (fr *FakeReferenceStub) String() string {
   606  	return fr.image
   607  }
   608  
   609  type testRoundTripper struct{}
   610  
   611  func (t *testRoundTripper) RoundTrip(*http.Request) (*http.Response, error) {
   612  	return nil, nil
   613  }
   614  
   615  func TestImagesSigned(t *testing.T) {
   616  	t.Parallel()
   617  
   618  	fakeRef := &FakeReferenceStub{
   619  		image:      "gcr.io/fake/honk:99.99.99",
   620  		registry:   "gcr.io",
   621  		repository: "fake/honk",
   622  	}
   623  
   624  	for _, tc := range []struct {
   625  		prepare func(*signfakes.FakeImpl)
   626  		assert  func(*sync.Map, error)
   627  	}{
   628  		{ // Success, signed
   629  			prepare: func(mock *signfakes.FakeImpl) {
   630  				mock.ParseReferenceReturns(fakeRef, nil)
   631  				mock.NewWithContextReturns(&testRoundTripper{}, nil)
   632  			},
   633  			assert: func(res *sync.Map, err error) {
   634  				require.Nil(t, err)
   635  
   636  				signed, ok := res.Load("")
   637  				require.True(t, ok)
   638  				require.True(t, signed.(bool))
   639  			},
   640  		},
   641  		{ // Success, unsigned
   642  			prepare: func(mock *signfakes.FakeImpl) {
   643  				mock.ParseReferenceReturns(fakeRef, nil)
   644  				mock.NewWithContextReturns(&testRoundTripper{}, nil)
   645  				mock.DigestReturnsOnCall(1, "", &transport.Error{
   646  					Errors: []transport.Diagnostic{
   647  						{Code: transport.ManifestUnknownErrorCode},
   648  					},
   649  				})
   650  			},
   651  			assert: func(res *sync.Map, err error) {
   652  				require.Nil(t, err)
   653  
   654  				signed, ok := res.Load("")
   655  				require.True(t, ok)
   656  				require.False(t, signed.(bool))
   657  			},
   658  		},
   659  		{ // failure on ParseReference
   660  			prepare: func(mock *signfakes.FakeImpl) {
   661  				mock.ParseReferenceReturns(nil, errTest)
   662  			},
   663  			assert: func(res *sync.Map, err error) {
   664  				require.NotNil(t, err)
   665  				require.Nil(t, res)
   666  			},
   667  		},
   668  		{ // failure on NewWithContext
   669  			prepare: func(mock *signfakes.FakeImpl) {
   670  				mock.ParseReferenceReturns(fakeRef, nil)
   671  				mock.NewWithContextReturns(nil, errTest)
   672  			},
   673  			assert: func(res *sync.Map, err error) {
   674  				require.NotNil(t, err)
   675  				require.Nil(t, res)
   676  			},
   677  		},
   678  		{ // failure on first Digest
   679  			prepare: func(mock *signfakes.FakeImpl) {
   680  				mock.ParseReferenceReturns(fakeRef, nil)
   681  				mock.NewWithContextReturns(&testRoundTripper{}, nil)
   682  				mock.DigestReturns("", errTest)
   683  			},
   684  			assert: func(res *sync.Map, err error) {
   685  				require.NotNil(t, err)
   686  				require.NotNil(t, res) // partial results are possible
   687  			},
   688  		},
   689  		{ // failure on second Digest
   690  			prepare: func(mock *signfakes.FakeImpl) {
   691  				mock.ParseReferenceReturns(fakeRef, nil)
   692  				mock.NewWithContextReturns(&testRoundTripper{}, nil)
   693  				mock.DigestReturnsOnCall(1, "", errTest)
   694  			},
   695  			assert: func(res *sync.Map, err error) {
   696  				require.NotNil(t, err)
   697  				require.NotNil(t, res) // partial results are possible
   698  			},
   699  		},
   700  	} {
   701  		mock := &signfakes.FakeImpl{}
   702  		tc.prepare(mock)
   703  
   704  		sut := sign.New(sign.Default())
   705  		sut.SetImpl(mock)
   706  
   707  		res, err := sut.ImagesSigned(context.TODO(), "")
   708  		tc.assert(res, err)
   709  	}
   710  }