From dac690656ea67133c9d68634582dc446b9afb1f6 Mon Sep 17 00:00:00 2001 From: Otto Bittner Date: Tue, 1 Aug 2023 16:48:13 +0200 Subject: [PATCH] api: add functions to transparently handle signatures upon API interaction (#2142) --- cli/internal/cmd/BUILD.bazel | 2 + cli/internal/cmd/configfetchmeasurements.go | 20 +- .../cmd/configfetchmeasurements_test.go | 32 ++- cli/internal/cmd/create.go | 2 +- cli/internal/cmd/upgradecheck.go | 106 +++---- cli/internal/cmd/upgradecheck_test.go | 60 +--- cli/internal/cmd/verifier_test.go | 12 +- cli/internal/kubernetes/upgrade.go | 2 +- .../internal/cmd/measurementsenvelope.go | 6 +- image/upload/internal/cmd/upload.go | 6 +- internal/api/attestationconfigapi/fetcher.go | 9 +- .../api/attestationconfigapi/fetcher_test.go | 2 +- internal/api/client/BUILD.bazel | 1 + internal/api/client/client.go | 69 +++++ internal/api/fetcher/BUILD.bazel | 1 + internal/api/fetcher/fetcher.go | 55 ++++ internal/api/versionsapi/cli/add.go | 29 +- internal/api/versionsapi/cli/list.go | 2 +- internal/api/versionsapi/cli/rm.go | 19 +- internal/api/versionsapi/client.go | 36 +-- internal/api/versionsapi/list.go | 8 +- internal/api/versionsapi/list_test.go | 8 +- internal/api/versionsapi/version.go | 103 ++++--- internal/api/versionsapi/version_test.go | 272 +++++++++--------- internal/attestation/measurements/BUILD.bazel | 1 - .../measurement-generator/BUILD.bazel | 1 + .../measurement-generator/generate.go | 15 +- .../attestation/measurements/measurements.go | 21 +- .../measurements/measurements_test.go | 26 +- internal/config/config.go | 2 +- internal/config/validation.go | 2 +- internal/imagefetcher/imagefetcher.go | 6 +- internal/osimage/archive/archive.go | 2 +- internal/osimage/aws/awsupload.go | 8 +- internal/osimage/azure/azureupload.go | 18 +- internal/osimage/gcp/gcpupload.go | 10 +- internal/osimage/imageinfo/imageinfo.go | 11 +- .../measurementsuploader.go | 12 +- internal/sigstore/BUILD.bazel | 2 - internal/sigstore/keyselect/BUILD.bazel | 23 ++ internal/sigstore/keyselect/keyselect.go | 28 ++ internal/sigstore/rekor.go | 8 +- internal/sigstore/sign_test.go | 7 +- internal/sigstore/verify.go | 53 ++-- internal/sigstore/verify_test.go | 61 ++-- 45 files changed, 707 insertions(+), 472 deletions(-) create mode 100644 internal/sigstore/keyselect/BUILD.bazel create mode 100644 internal/sigstore/keyselect/keyselect.go diff --git a/cli/internal/cmd/BUILD.bazel b/cli/internal/cmd/BUILD.bazel index 953c19782..88446a09e 100644 --- a/cli/internal/cmd/BUILD.bazel +++ b/cli/internal/cmd/BUILD.bazel @@ -80,6 +80,7 @@ go_library( "//internal/retry", "//internal/semver", "//internal/sigstore", + "//internal/sigstore/keyselect", "//internal/versions", "//operators/constellation-node-operator/api/v1alpha1", "//verify/verifyproto", @@ -162,6 +163,7 @@ go_test( "//internal/license", "//internal/logger", "//internal/semver", + "//internal/sigstore", "//internal/versions", "//operators/constellation-node-operator/api/v1alpha1", "//verify/verifyproto", diff --git a/cli/internal/cmd/configfetchmeasurements.go b/cli/internal/cmd/configfetchmeasurements.go index d2d586247..7f132b33f 100644 --- a/cli/internal/cmd/configfetchmeasurements.go +++ b/cli/internal/cmd/configfetchmeasurements.go @@ -21,6 +21,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/sigstore" + "github.com/edgelesssys/constellation/v2/internal/sigstore/keyselect" "github.com/spf13/afero" "github.com/spf13/cobra" ) @@ -69,11 +70,11 @@ func runConfigFetchMeasurements(cmd *cobra.Command, _ []string) error { cfm := &configFetchMeasurementsCmd{log: log, canFetchMeasurements: featureset.CanFetchMeasurements} fetcher := attestationconfigapi.NewFetcherWithClient(http.DefaultClient) - return cfm.configFetchMeasurements(cmd, sigstore.CosignVerifier{}, rekor, fileHandler, fetcher, http.DefaultClient) + return cfm.configFetchMeasurements(cmd, sigstore.NewCosignVerifier, rekor, fileHandler, fetcher, http.DefaultClient) } func (cfm *configFetchMeasurementsCmd) configFetchMeasurements( - cmd *cobra.Command, cosign cosignVerifier, rekor rekorVerifier, + cmd *cobra.Command, newCosignVerifier cosignVerifierConstructor, rekor rekorVerifier, fileHandler file.Handler, fetcher attestationconfigapi.Fetcher, client *http.Client, ) error { flags, err := cfm.parseFetchMeasurementsFlags(cmd) @@ -117,6 +118,15 @@ func (cfm *configFetchMeasurementsCmd) configFetchMeasurements( return err } + publicKey, err := keyselect.CosignPublicKeyForVersion(imageVersion) + if err != nil { + return fmt.Errorf("getting public key: %w", err) + } + cosign, err := newCosignVerifier(publicKey) + if err != nil { + return fmt.Errorf("creating cosign verifier: %w", err) + } + var fetchedMeasurements measurements.M var hash string if flags.insecure { @@ -147,7 +157,7 @@ func (cfm *configFetchMeasurementsCmd) configFetchMeasurements( return fmt.Errorf("fetching and verifying measurements: %w", err) } cfm.log.Debugf("Fetched and verified measurements, hash is %s", hash) - if err := sigstore.VerifyWithRekor(cmd.Context(), imageVersion, rekor, hash); err != nil { + if err := sigstore.VerifyWithRekor(cmd.Context(), publicKey, rekor, hash); err != nil { cmd.PrintErrf("Ignoring Rekor related error: %v\n", err) cmd.PrintErrln("Make sure the downloaded measurements are trustworthy!") } @@ -244,6 +254,4 @@ type rekorVerifier interface { VerifyEntry(context.Context, string, string) error } -type cosignVerifier interface { - VerifySignature(content, signature, publicKey []byte) error -} +type cosignVerifierConstructor func([]byte) (sigstore.Verifier, error) diff --git a/cli/internal/cmd/configfetchmeasurements_test.go b/cli/internal/cmd/configfetchmeasurements_test.go index 2d18ca183..e2fd938ca 100644 --- a/cli/internal/cmd/configfetchmeasurements_test.go +++ b/cli/internal/cmd/configfetchmeasurements_test.go @@ -24,6 +24,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/logger" + "github.com/edgelesssys/constellation/v2/internal/sigstore" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -102,12 +103,11 @@ func TestParseFetchMeasurementsFlags(t *testing.T) { } func TestUpdateURLs(t *testing.T) { - ver := versionsapi.Version{ - Ref: "foo", - Stream: "nightly", - Version: "v7.7.7", - Kind: versionsapi.VersionKindImage, - } + require := require.New(t) + + ver, err := versionsapi.NewVersion("foo", "nightly", "v7.7.7", versionsapi.VersionKindImage) + require.NoError(err) + testCases := map[string]struct { conf *config.Config flags *fetchMeasurementsFlags @@ -234,39 +234,43 @@ func TestConfigFetchMeasurements(t *testing.T) { }) testCases := map[string]struct { - cosign cosignVerifier + cosign cosignVerifierConstructor rekor rekorVerifier insecureFlag bool wantErr bool }{ "success": { - cosign: &stubCosignVerifier{}, + cosign: newStubCosignVerifier, rekor: singleUUIDVerifier(), }, "success without cosign": { insecureFlag: true, - cosign: &stubCosignVerifier{ - verifyError: assert.AnError, + cosign: func(_ []byte) (sigstore.Verifier, error) { + return &stubCosignVerifier{ + verifyError: assert.AnError, + }, nil }, rekor: singleUUIDVerifier(), }, "failing search should not result in error": { - cosign: &stubCosignVerifier{}, + cosign: newStubCosignVerifier, rekor: &stubRekorVerifier{ SearchByHashUUIDs: []string{}, SearchByHashError: assert.AnError, }, }, "failing verify should not result in error": { - cosign: &stubCosignVerifier{}, + cosign: newStubCosignVerifier, rekor: &stubRekorVerifier{ SearchByHashUUIDs: []string{"11111111111111111111111111111111111111111111111111111111111111111111111111111111"}, VerifyEntryError: assert.AnError, }, }, "signature verification failure": { - cosign: &stubCosignVerifier{ - verifyError: assert.AnError, + cosign: func(_ []byte) (sigstore.Verifier, error) { + return &stubCosignVerifier{ + verifyError: assert.AnError, + }, nil }, rekor: singleUUIDVerifier(), wantErr: true, diff --git a/cli/internal/cmd/create.go b/cli/internal/cmd/create.go index 92dec0b94..d99e71389 100644 --- a/cli/internal/cmd/create.go +++ b/cli/internal/cmd/create.go @@ -307,7 +307,7 @@ func validateCLIandConstellationVersionAreEqual(cliVersion semver.Semver, imageV return fmt.Errorf("parsing image version: %w", err) } - semImage, err := semver.New(parsedImageVersion.Version) + semImage, err := semver.New(parsedImageVersion.Version()) if err != nil { return fmt.Errorf("parsing image semantical version: %w", err) } diff --git a/cli/internal/cmd/upgradecheck.go b/cli/internal/cmd/upgradecheck.go index 403d3bd65..d5fc5df37 100644 --- a/cli/internal/cmd/upgradecheck.go +++ b/cli/internal/cmd/upgradecheck.go @@ -35,6 +35,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/kubernetes/kubectl" consemver "github.com/edgelesssys/constellation/v2/internal/semver" "github.com/edgelesssys/constellation/v2/internal/sigstore" + "github.com/edgelesssys/constellation/v2/internal/sigstore/keyselect" "github.com/edgelesssys/constellation/v2/internal/versions" "github.com/siderolabs/talos/pkg/machinery/config/encoder" "github.com/spf13/afero" @@ -86,7 +87,6 @@ func runUpgradeCheck(cmd *cobra.Command, _ []string) error { verListFetcher: versionfetcher, fileHandler: fileHandler, client: http.DefaultClient, - cosign: sigstore.CosignVerifier{}, rekor: rekor, flags: flags, cliVersion: constants.BinaryVersion(), @@ -301,7 +301,7 @@ func sortedMapKeys[T any](a map[string]T) []string { func filterImageUpgrades(currentVersion string, newVersions []versionsapi.Version) []versionsapi.Version { newImages := []versionsapi.Version{} for i := range newVersions { - if err := compatibility.IsValidUpgrade(currentVersion, newVersions[i].Version); err != nil { + if err := compatibility.IsValidUpgrade(currentVersion, newVersions[i].Version()); err != nil { continue } newImages = append(newImages, newVersions[i]) @@ -338,7 +338,6 @@ type versionCollector struct { verListFetcher versionListFetcher fileHandler file.Handler client *http.Client - cosign cosignVerifier rekor rekorVerifier flags upgradeCheckFlags versionsapi versionFetcher @@ -346,11 +345,30 @@ type versionCollector struct { log debugLog } -func (v *versionCollector) newMeasurements(ctx context.Context, csp cloudprovider.Provider, attestationVariant variant.Variant, images []versionsapi.Version) (map[string]measurements.M, error) { +func (v *versionCollector) newMeasurements(ctx context.Context, csp cloudprovider.Provider, attestationVariant variant.Variant, versions []versionsapi.Version) (map[string]measurements.M, error) { // get expected measurements for each image - upgrades, err := getCompatibleImageMeasurements(ctx, v.writer, v.client, v.cosign, v.rekor, csp, attestationVariant, images, v.log) - if err != nil { - return nil, fmt.Errorf("fetching measurements for compatible images: %w", err) + upgrades := make(map[string]measurements.M) + for _, version := range versions { + v.log.Debugf("Fetching measurements for image: %s", version) + shortPath := version.ShortPath() + + publicKey, err := keyselect.CosignPublicKeyForVersion(version) + if err != nil { + return nil, fmt.Errorf("getting public key: %w", err) + } + cosign, err := sigstore.NewCosignVerifier(publicKey) + if err != nil { + return nil, fmt.Errorf("setting public key: %w", err) + } + + measurements, err := getCompatibleImageMeasurements(ctx, v.writer, v.client, cosign, v.rekor, csp, attestationVariant, version, v.log) + if err != nil { + if _, err := fmt.Fprintf(v.writer, "Skipping compatible image %q: %s\n", shortPath, err); err != nil { + return nil, fmt.Errorf("writing to buffer: %w", err) + } + continue + } + upgrades[shortPath] = measurements } v.log.Debugf("Compatible image measurements are %v", upgrades) @@ -609,49 +627,41 @@ func getCurrentKubernetesVersion(ctx context.Context, checker upgradeChecker) (s } // getCompatibleImageMeasurements retrieves the expected measurements for each image. -func getCompatibleImageMeasurements(ctx context.Context, writer io.Writer, client *http.Client, cosign cosignVerifier, rekor rekorVerifier, - csp cloudprovider.Provider, attestationVariant variant.Variant, versions []versionsapi.Version, log debugLog, -) (map[string]measurements.M, error) { - upgrades := make(map[string]measurements.M) - for _, version := range versions { - log.Debugf("Fetching measurements for image: %s", version) - shortPath := version.ShortPath() - measurementsURL, signatureURL, err := versionsapi.MeasurementURL(version) - if err != nil { - return nil, err - } - - var fetchedMeasurements measurements.M - log.Debugf("Fetching for measurement url: %s", measurementsURL) - hash, err := fetchedMeasurements.FetchAndVerify( - ctx, client, cosign, - measurementsURL, - signatureURL, - version, - csp, - attestationVariant, - ) - if err != nil { - if _, err := fmt.Fprintf(writer, "Skipping compatible image %q: %s\n", shortPath, err); err != nil { - return nil, fmt.Errorf("writing to buffer: %w", err) - } - continue - } - - if err = sigstore.VerifyWithRekor(ctx, version, rekor, hash); err != nil { - if _, err := fmt.Fprintf(writer, "Warning: Unable to verify '%s' in Rekor.\n", hash); err != nil { - return nil, fmt.Errorf("writing to buffer: %w", err) - } - if _, err := fmt.Fprintf(writer, "Make sure measurements are correct.\n"); err != nil { - return nil, fmt.Errorf("writing to buffer: %w", err) - } - } - - upgrades[shortPath] = fetchedMeasurements - +func getCompatibleImageMeasurements(ctx context.Context, writer io.Writer, client *http.Client, cosign sigstore.Verifier, rekor rekorVerifier, + csp cloudprovider.Provider, attestationVariant variant.Variant, version versionsapi.Version, log debugLog, +) (measurements.M, error) { + measurementsURL, signatureURL, err := versionsapi.MeasurementURL(version) + if err != nil { + return nil, err } - return upgrades, nil + var fetchedMeasurements measurements.M + log.Debugf("Fetching for measurement url: %s", measurementsURL) + + hash, err := fetchedMeasurements.FetchAndVerify( + ctx, client, cosign, + measurementsURL, + signatureURL, + version, + csp, + attestationVariant, + ) + if err != nil { + return nil, fmt.Errorf("fetching measurements: %w", err) + } + + pubkey, err := keyselect.CosignPublicKeyForVersion(version) + if err != nil { + return nil, fmt.Errorf("getting public key: %w", err) + } + + if err = sigstore.VerifyWithRekor(ctx, pubkey, rekor, hash); err != nil { + if _, err := fmt.Fprintf(writer, "Warning: Unable to verify '%s' in Rekor.\nMake sure measurements are correct.\n", hash); err != nil { + return nil, fmt.Errorf("writing to buffer: %w", err) + } + } + + return fetchedMeasurements, nil } type versionFetcher interface { diff --git a/cli/internal/cmd/upgradecheck_test.go b/cli/internal/cmd/upgradecheck_test.go index 4ada414ec..b87b1d119 100644 --- a/cli/internal/cmd/upgradecheck_test.go +++ b/cli/internal/cmd/upgradecheck_test.go @@ -151,32 +151,23 @@ func TestGetCurrentImageVersion(t *testing.T) { func TestGetCompatibleImageMeasurements(t *testing.T) { assert := assert.New(t) + require := require.New(t) csp := cloudprovider.Azure attestationVariant := variant.AzureSEVSNP{} - zero := versionsapi.Version{ - Ref: "-", - Stream: "stable", - Version: "v0.0.0", - Kind: versionsapi.VersionKindImage, - } - one := versionsapi.Version{ - Ref: "-", - Stream: "stable", - Version: "v1.0.0", - Kind: versionsapi.VersionKindImage, - } - images := []versionsapi.Version{zero, one} + + versionZero, err := versionsapi.NewVersion("-", "stable", "v0.0.0", versionsapi.VersionKindImage) + require.NoError(err) client := newTestClient(func(req *http.Request) *http.Response { - if strings.HasSuffix(req.URL.String(), "v0.0.0/azure/measurements.json") { + if strings.HasSuffix(req.URL.String(), "v0.0.0/image/measurements.json") { return &http.Response{ StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader(`{"csp":"azure","image":"v0.0.0","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`)), + Body: io.NopCloser(strings.NewReader(`{"version": "v0.0.0","ref": "-","stream": "stable","list": [{"csp": "Azure","attestationVariant": "azure-sev-snp","measurements": {"0": {"expected": "0000000000000000000000000000000000000000000000000000000000000000","warnOnly": false}}}]}`)), Header: make(http.Header), } } - if strings.HasSuffix(req.URL.String(), "v0.0.0/azure/measurements.json.sig") { + if strings.HasSuffix(req.URL.String(), "v0.0.0/image/measurements.json.sig") { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader("MEQCIGRR7RaSMs892Ta06/Tz7LqPUxI05X4wQcP+nFFmZtmaAiBNl9X8mUKmUBfxg13LQBfmmpw6JwYQor5hOwM3NFVPAg==")), @@ -184,21 +175,6 @@ func TestGetCompatibleImageMeasurements(t *testing.T) { } } - if strings.HasSuffix(req.URL.String(), "v1.0.0/azure/measurements.json") { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader(`{"csp":"azure","image":"v1.0.0","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`)), - Header: make(http.Header), - } - } - if strings.HasSuffix(req.URL.String(), "v1.0.0/azure/measurements.json.sig") { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader("MEQCIFh8CVELp/Da2U2Jt404OXsUeDfqtrf3pqGRuvxnxhI8AiBTHF9tHEPwFedYG3Jgn2ELOxss+Ybc6135vEtClBrbpg==")), - Header: make(http.Header), - } - } - return &http.Response{ StatusCode: http.StatusNotFound, Body: io.NopCloser(strings.NewReader("Not found.")), @@ -206,7 +182,7 @@ func TestGetCompatibleImageMeasurements(t *testing.T) { } }) - upgrades, err := getCompatibleImageMeasurements(context.Background(), &bytes.Buffer{}, client, &stubCosignVerifier{}, singleUUIDVerifier(), csp, attestationVariant, images, logger.NewTest(t)) + upgrades, err := getCompatibleImageMeasurements(context.Background(), &bytes.Buffer{}, client, &stubCosignVerifier{}, singleUUIDVerifier(), csp, attestationVariant, versionZero, logger.NewTest(t)) assert.NoError(err) for _, measurement := range upgrades { @@ -215,18 +191,13 @@ func TestGetCompatibleImageMeasurements(t *testing.T) { } func TestUpgradeCheck(t *testing.T) { - v2_3 := versionsapi.Version{ - Ref: "-", - Stream: "stable", - Version: "v2.3.0", - Kind: versionsapi.VersionKindImage, - } - v2_5 := versionsapi.Version{ - Ref: "-", - Stream: "stable", - Version: "v2.5.0", - Kind: versionsapi.VersionKindImage, - } + require := require.New(t) + v2_3, err := versionsapi.NewVersion("-", "stable", "v2.3.0", versionsapi.VersionKindImage) + require.NoError(err) + + v2_5, err := versionsapi.NewVersion("-", "stable", "v2.5.0", versionsapi.VersionKindImage) + require.NoError(err) + collector := stubVersionCollector{ supportedServicesVersions: consemver.NewFromInt(2, 5, 0, ""), supportedImages: []versionsapi.Version{v2_3}, @@ -279,7 +250,6 @@ func TestUpgradeCheck(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { assert := assert.New(t) - require := require.New(t) fileHandler := file.NewHandler(afero.NewMemMapFs()) cfg := defaultConfigWithExpectedMeasurements(t, config.Default(), tc.csp) diff --git a/cli/internal/cmd/verifier_test.go b/cli/internal/cmd/verifier_test.go index 1efabcc71..1011b23ca 100644 --- a/cli/internal/cmd/verifier_test.go +++ b/cli/internal/cmd/verifier_test.go @@ -6,7 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only package cmd -import "context" +import ( + "context" + + "github.com/edgelesssys/constellation/v2/internal/sigstore" +) // singleUUIDVerifier constructs a RekorVerifier that returns a single UUID and no errors, // and should work for most tests on the happy path. @@ -39,6 +43,10 @@ type stubCosignVerifier struct { verifyError error } -func (v *stubCosignVerifier) VerifySignature(_, _, _ []byte) error { +func newStubCosignVerifier(_ []byte) (sigstore.Verifier, error) { + return &stubCosignVerifier{}, nil +} + +func (v *stubCosignVerifier) VerifySignature(_, _ []byte) error { return v.verifyError } diff --git a/cli/internal/kubernetes/upgrade.go b/cli/internal/kubernetes/upgrade.go index 713e79c58..dfb9ade9b 100644 --- a/cli/internal/kubernetes/upgrade.go +++ b/cli/internal/kubernetes/upgrade.go @@ -224,7 +224,7 @@ func (u *Upgrader) UpgradeNodeVersion(ctx context.Context, conf *config.Config, upgradeErrs := []error{} var upgradeErr *compatibility.InvalidUpgradeError - err = u.updateImage(&nodeVersion, imageReference, imageVersion.Version, force) + err = u.updateImage(&nodeVersion, imageReference, imageVersion.Version(), force) switch { case errors.As(err, &upgradeErr): upgradeErrs = append(upgradeErrs, fmt.Errorf("skipping image upgrades: %w", err)) diff --git a/image/upload/internal/cmd/measurementsenvelope.go b/image/upload/internal/cmd/measurementsenvelope.go index ff9c0a981..1f6c49b02 100644 --- a/image/upload/internal/cmd/measurementsenvelope.go +++ b/image/upload/internal/cmd/measurementsenvelope.go @@ -67,9 +67,9 @@ func runEnvelopeMeasurements(cmd *cobra.Command, _ []string) error { } enveloped := measurements.ImageMeasurementsV2{ - Ref: flags.version.Ref, - Stream: flags.version.Stream, - Version: flags.version.Version, + Ref: flags.version.Ref(), + Stream: flags.version.Stream(), + Version: flags.version.Version(), List: []measurements.ImageMeasurementsV2Entry{ { CSP: flags.csp, diff --git a/image/upload/internal/cmd/upload.go b/image/upload/internal/cmd/upload.go index 2052d7069..5fdd4bec7 100644 --- a/image/upload/internal/cmd/upload.go +++ b/image/upload/internal/cmd/upload.go @@ -43,9 +43,9 @@ func uploadImage(ctx context.Context, archiveC archivist, uploadC uploader, req } imageInfo := versionsapi.ImageInfo{ - Ref: req.Version.Ref, - Stream: req.Version.Stream, - Version: req.Version.Version, + Ref: req.Version.Ref(), + Stream: req.Version.Stream(), + Version: req.Version.Version(), List: imageReferences, } diff --git a/internal/api/attestationconfigapi/fetcher.go b/internal/api/attestationconfigapi/fetcher.go index ba3f89b92..b0b5ee5e7 100644 --- a/internal/api/attestationconfigapi/fetcher.go +++ b/internal/api/attestationconfigapi/fetcher.go @@ -43,7 +43,12 @@ func NewFetcher() Fetcher { // NewFetcherWithClient returns a new fetcher with custom http client. func NewFetcherWithClient(client apifetcher.HTTPClient) Fetcher { - return newFetcherWithClientAndVerifier(client, sigstore.CosignVerifier{}) + verifier, err := sigstore.NewCosignVerifier([]byte(cosignPublicKey)) + if err != nil { + // This relies on an embedded public key. If this key can not be validated, there is no way to recover from this. + panic(fmt.Errorf("creating cosign verifier: %w", err)) + } + return newFetcherWithClientAndVerifier(client, verifier) } func newFetcherWithClientAndVerifier(client apifetcher.HTTPClient, cosignVerifier sigstore.Verifier) Fetcher { @@ -73,7 +78,7 @@ func (f *fetcher) FetchAzureSEVSNPVersion(ctx context.Context, azureVersion Azur return fetchedVersion, fmt.Errorf("fetch version %s signature: %w", azureVersion.Version, err) } - err = f.verifier.VerifySignature(versionBytes, signature.Signature, []byte(cosignPublicKey)) + err = f.verifier.VerifySignature(versionBytes, signature.Signature) if err != nil { return fetchedVersion, fmt.Errorf("verify version %s signature: %w", azureVersion.Version, err) } diff --git a/internal/api/attestationconfigapi/fetcher_test.go b/internal/api/attestationconfigapi/fetcher_test.go index 2c39c1a45..77130b9ca 100644 --- a/internal/api/attestationconfigapi/fetcher_test.go +++ b/internal/api/attestationconfigapi/fetcher_test.go @@ -161,6 +161,6 @@ func (f *fakeConfigAPIHandler) RoundTrip(req *http.Request) (*http.Response, err type dummyVerifier struct{} -func (s dummyVerifier) VerifySignature(_, _, _ []byte) error { +func (s dummyVerifier) VerifySignature(_, _ []byte) error { return nil } diff --git a/internal/api/client/BUILD.bazel b/internal/api/client/BUILD.bazel index dc8c7f289..a77457c5a 100644 --- a/internal/api/client/BUILD.bazel +++ b/internal/api/client/BUILD.bazel @@ -7,6 +7,7 @@ go_library( visibility = ["//:__subpackages__"], deps = [ "//internal/logger", + "//internal/sigstore", "//internal/staticupload", "@com_github_aws_aws_sdk_go_v2_feature_s3_manager//:manager", "@com_github_aws_aws_sdk_go_v2_service_s3//:s3", diff --git a/internal/api/client/client.go b/internal/api/client/client.go index 65f3f9f48..a6e90a3ed 100644 --- a/internal/api/client/client.go +++ b/internal/api/client/client.go @@ -33,12 +33,14 @@ import ( "encoding/json" "errors" "fmt" + "path" "time" s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager" "github.com/aws/aws-sdk-go-v2/service/s3" s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/edgelesssys/constellation/v2/internal/logger" + "github.com/edgelesssys/constellation/v2/internal/sigstore" "github.com/edgelesssys/constellation/v2/internal/staticupload" "go.uber.org/zap" ) @@ -249,6 +251,50 @@ func Update(ctx context.Context, c *Client, obj APIObject) error { return nil } +// SignAndUpdate signs the given apiObject and updates the object and it's signature in the API. +// This function should be used in favor of manually managing signatures. +// The signing is specified as part of the signer argument. +func SignAndUpdate(ctx context.Context, c *Client, obj APIObject, signer sigstore.Signer) error { + data, err := json.Marshal(obj) + if err != nil { + return fmt.Errorf("marshaling %T: %w", obj, err) + } + + dataSignature, err := signer.Sign(data) + if err != nil { + return fmt.Errorf("sign version file: %w", err) + } + + signature := signature{ + Signed: obj, + Signature: dataSignature, + } + + if err := Update(ctx, c, obj); err != nil { + return fmt.Errorf("updating %T: %w", obj, err) + } + + if err := Update(ctx, c, signature); err != nil { + return fmt.Errorf("updating %T: %w", obj, err) + } + + return nil +} + +// DeleteWithSignature deletes the given apiObject and it's signature from the public Constellation API. +func DeleteWithSignature(ctx context.Context, c *Client, obj APIObject) error { + if err := Delete(ctx, c, obj); err != nil { + return fmt.Errorf("deleting %T: %w", obj, err) + } + + sig := signature{Signed: obj} + if err := Delete(ctx, c, sig); err != nil { + return fmt.Errorf("deleting %T: %w", sig, err) + } + + return nil +} + // Delete deletes the given apiObject from the public Constellation API. func Delete(ctx context.Context, c *Client, obj APIObject) error { if err := obj.ValidateRequest(); err != nil { @@ -303,3 +349,26 @@ type uploadClient interface { // CloseFunc is a function that closes the client. type CloseFunc func(ctx context.Context) error + +// signature wraps another APIObject and adds a signature to it. +type signature struct { + // Signed is the object that is signed. + Signed APIObject + // Signature is the signature of `Signed`. + Signature []byte `json:"signature"` +} + +// JSONPath returns the path to the JSON file for the request to the config api. +func (s signature) JSONPath() string { + return path.Join(s.Signed.JSONPath() + ".sig") +} + +// ValidateRequest validates the request. +func (s signature) ValidateRequest() error { + return s.Signed.ValidateRequest() +} + +// Validate is a No-Op at the moment. +func (s signature) Validate() error { + return s.Signed.Validate() +} diff --git a/internal/api/fetcher/BUILD.bazel b/internal/api/fetcher/BUILD.bazel index d49adcf52..089b96924 100644 --- a/internal/api/fetcher/BUILD.bazel +++ b/internal/api/fetcher/BUILD.bazel @@ -5,4 +5,5 @@ go_library( srcs = ["fetcher.go"], importpath = "github.com/edgelesssys/constellation/v2/internal/api/fetcher", visibility = ["//:__subpackages__"], + deps = ["//internal/sigstore"], ) diff --git a/internal/api/fetcher/fetcher.go b/internal/api/fetcher/fetcher.go index 1739e9fe2..0075c366c 100644 --- a/internal/api/fetcher/fetcher.go +++ b/internal/api/fetcher/fetcher.go @@ -22,6 +22,8 @@ import ( "encoding/json" "fmt" "net/http" + + "github.com/edgelesssys/constellation/v2/internal/sigstore" ) // NewHTTPClient returns a new http client. @@ -71,6 +73,32 @@ func Fetch[T apiObject](ctx context.Context, c HTTPClient, obj T) (T, error) { return newObj, nil } +// FetchAndVerify fetches the given apiObject, checks if it can fetch an accompanying signature and verifies if the signature matches the found object. +// The public key used to verify the signature is embedded in the verifier argument. +// FetchAndVerify uses a generic to return a new object of type T. +// Otherwise the caller would have to cast the interface type to a concrete object, which could fail. +func FetchAndVerify[T apiObject](ctx context.Context, c HTTPClient, obj T, cosignVerifier sigstore.Verifier) (T, error) { + fetchedObj, err := Fetch(ctx, c, obj) + if err != nil { + return fetchedObj, fmt.Errorf("fetching object: %w", err) + } + marshalledObj, err := json.Marshal(fetchedObj) + if err != nil { + return fetchedObj, fmt.Errorf("marshalling object: %w", err) + } + + signature, err := Fetch(ctx, c, signature{Signed: fetchedObj}) + if err != nil { + return fetchedObj, fmt.Errorf("fetching signature: %w", err) + } + + err = cosignVerifier.VerifySignature(marshalledObj, signature.Signature) + if err != nil { + return fetchedObj, fmt.Errorf("verifying signature: %w", err) + } + return fetchedObj, nil +} + // NotFoundError is an error that is returned when a resource is not found. type NotFoundError struct { err error @@ -94,3 +122,30 @@ type apiObject interface { Validate() error URL() (string, error) } + +// signature wraps another APIObject and adds a signature to it. +type signature struct { + // Signed is the object that is signed. + Signed apiObject `json:"-"` + // Signature is the signature of `Signed`. + Signature []byte `json:"signature"` +} + +// URL returns the URL for the request to the config api. +func (s signature) URL() (string, error) { + url, err := s.Signed.URL() + if err != nil { + return "", err + } + return url + ".sig", nil +} + +// ValidateRequest validates the request. +func (s signature) ValidateRequest() error { + return s.Signed.ValidateRequest() +} + +// Validate is a No-Op at the moment. +func (s signature) Validate() error { + return s.Signed.Validate() +} diff --git a/internal/api/versionsapi/cli/add.go b/internal/api/versionsapi/cli/add.go index c587c953c..89758d225 100644 --- a/internal/api/versionsapi/cli/add.go +++ b/internal/api/versionsapi/cli/add.go @@ -61,14 +61,9 @@ func runAdd(cmd *cobra.Command, _ []string) (retErr error) { } log.Debugf("Creating version struct") - ver := versionsapi.Version{ - Ref: flags.ref, - Stream: flags.stream, - Version: flags.version, - Kind: flags.kind, - } - if err := ver.Validate(); err != nil { - return err + ver, err := versionsapi.NewVersion(flags.ref, flags.stream, flags.version, flags.kind) + if err != nil { + return fmt.Errorf("creating version: %w", err) } log.Debugf("Creating versions API client") @@ -108,8 +103,8 @@ func ensureVersion(ctx context.Context, client *versionsapi.Client, kind version log *logger.Logger, ) error { verListReq := versionsapi.List{ - Ref: ver.Ref, - Stream: ver.Stream, + Ref: ver.Ref(), + Stream: ver.Stream(), Granularity: gran, Base: ver.WithGranularity(gran), Kind: kind, @@ -146,28 +141,28 @@ func ensureVersion(ctx context.Context, client *versionsapi.Client, kind version func updateLatest(ctx context.Context, client *versionsapi.Client, kind versionsapi.VersionKind, ver versionsapi.Version, log *logger.Logger) error { latest := versionsapi.Latest{ - Ref: ver.Ref, - Stream: ver.Stream, + Ref: ver.Ref(), + Stream: ver.Stream(), Kind: kind, } latest, err := client.FetchVersionLatest(ctx, latest) var notFoundErr *apiclient.NotFoundError if errors.As(err, ¬FoundErr) { - log.Debugf("Latest version for ref %q and stream %q not found", ver.Ref, ver.Stream) + log.Debugf("Latest version for ref %q and stream %q not found", ver.Ref(), ver.Stream()) } else if err != nil { return fmt.Errorf("fetching latest version: %w", err) } - if latest.Version == ver.Version { + if latest.Version == ver.Version() { log.Infof("Version %q is already latest version", ver) return nil } log.Infof("Setting %q as latest version", ver) latest = versionsapi.Latest{ - Ref: ver.Ref, - Stream: ver.Stream, - Version: ver.Version, + Ref: ver.Ref(), + Stream: ver.Stream(), + Version: ver.Version(), Kind: kind, } if err := client.UpdateVersionLatest(ctx, latest); err != nil { diff --git a/internal/api/versionsapi/cli/list.go b/internal/api/versionsapi/cli/list.go index ab23326a2..9f8f65970 100644 --- a/internal/api/versionsapi/cli/list.go +++ b/internal/api/versionsapi/cli/list.go @@ -91,7 +91,7 @@ func runList(cmd *cobra.Command, _ []string) error { log.Debugf("Printing versions as JSON") var vers []string for _, v := range patchVersions { - vers = append(vers, v.Version) + vers = append(vers, v.Version()) } raw, err := json.Marshal(vers) if err != nil { diff --git a/internal/api/versionsapi/cli/rm.go b/internal/api/versionsapi/cli/rm.go index 7f825a0ef..061a9e8d9 100644 --- a/internal/api/versionsapi/cli/rm.go +++ b/internal/api/versionsapi/cli/rm.go @@ -181,7 +181,7 @@ func deleteRef(ctx context.Context, clients rmImageClients, ref string, dryrun b for _, ver := range vers { if err := deleteImage(ctx, clients, ver, dryrun, log); err != nil { - retErr = errors.Join(retErr, fmt.Errorf("deleting images for version %s: %w", ver.Version, err)) + retErr = errors.Join(retErr, fmt.Errorf("deleting images for version %s: %w", ver.Version(), err)) } } @@ -197,9 +197,9 @@ func deleteImage(ctx context.Context, clients rmImageClients, ver versionsapi.Ve var retErr error imageInfo := versionsapi.ImageInfo{ - Ref: ver.Ref, - Stream: ver.Stream, - Version: ver.Version, + Ref: ver.Ref(), + Stream: ver.Stream(), + Version: ver.Version(), } imageInfo, err := clients.version.FetchImageInfo(ctx, imageInfo) var notFound *apiclient.NotFoundError @@ -291,14 +291,9 @@ func (f *rmFlags) validate() error { return nil } - ver := versionsapi.Version{ - Ref: f.ref, - Stream: f.stream, - Version: f.version, - Kind: versionsapi.VersionKindImage, - } - if err := ver.Validate(); err != nil { - return fmt.Errorf("invalid version: %w", err) + ver, err := versionsapi.NewVersion(f.ref, f.stream, f.version, versionsapi.VersionKindImage) + if err != nil { + return fmt.Errorf("creating version: %w", err) } f.ver = ver diff --git a/internal/api/versionsapi/client.go b/internal/api/versionsapi/client.go index bc9667c5f..43c4e318d 100644 --- a/internal/api/versionsapi/client.go +++ b/internal/api/versionsapi/client.go @@ -131,18 +131,18 @@ func (c *Client) DeleteRef(ctx context.Context, ref string) error { func (c *Client) DeleteVersion(ctx context.Context, ver Version) error { var retErr error - c.Client.Log.Debugf("Deleting version %s from minor version list", ver.Version) + c.Client.Log.Debugf("Deleting version %s from minor version list", ver.version) possibleNewLatest, err := c.deleteVersionFromMinorVersionList(ctx, ver) if err != nil { retErr = errors.Join(retErr, fmt.Errorf("removing from minor version list: %w", err)) } - c.Client.Log.Debugf("Checking latest version for %s", ver.Version) + c.Client.Log.Debugf("Checking latest version for %s", ver.version) if err := c.deleteVersionFromLatest(ctx, ver, possibleNewLatest); err != nil { retErr = errors.Join(retErr, fmt.Errorf("updating latest version: %w", err)) } - c.Client.Log.Debugf("Deleting artifact path %s for %s", ver.ArtifactPath(APIV1), ver.Version) + c.Client.Log.Debugf("Deleting artifact path %s for %s", ver.ArtifactPath(APIV1), ver.version) if err := c.Client.DeletePath(ctx, ver.ArtifactPath(APIV1)); err != nil { retErr = errors.Join(retErr, fmt.Errorf("deleting artifact path: %w", err)) } @@ -153,32 +153,32 @@ func (c *Client) DeleteVersion(ctx context.Context, ver Version) error { func (c *Client) deleteVersionFromMinorVersionList(ctx context.Context, ver Version, ) (*Latest, error) { minorList := List{ - Ref: ver.Ref, - Stream: ver.Stream, + Ref: ver.ref, + Stream: ver.stream, Granularity: GranularityMinor, Base: ver.WithGranularity(GranularityMinor), Kind: VersionKindImage, } - c.Client.Log.Debugf("Fetching minor version list for version %s", ver.Version) + c.Client.Log.Debugf("Fetching minor version list for version %s", ver.version) minorList, err := c.FetchVersionList(ctx, minorList) var notFoundErr *apiclient.NotFoundError if errors.As(err, ¬FoundErr) { - c.Client.Log.Warnf("Minor version list for version %s not found", ver.Version) + c.Client.Log.Warnf("Minor version list for version %s not found", ver.version) c.Client.Log.Warnf("Skipping update of minor version list") return nil, nil } else if err != nil { - return nil, fmt.Errorf("fetching minor version list for version %s: %w", ver.Version, err) + return nil, fmt.Errorf("fetching minor version list for version %s: %w", ver.version, err) } - if !minorList.Contains(ver.Version) { - c.Client.Log.Warnf("Version %s is not in minor version list %s", ver.Version, minorList.JSONPath()) + if !minorList.Contains(ver.version) { + c.Client.Log.Warnf("Version %s is not in minor version list %s", ver.version, minorList.JSONPath()) c.Client.Log.Warnf("Skipping update of minor version list") return nil, nil } semver.Sort(minorList.Versions) for i, v := range minorList.Versions { - if v == ver.Version { + if v == ver.version { minorList.Versions = append(minorList.Versions[:i], minorList.Versions[i+1:]...) break } @@ -187,8 +187,8 @@ func (c *Client) deleteVersionFromMinorVersionList(ctx context.Context, ver Vers var latest *Latest if len(minorList.Versions) != 0 { latest = &Latest{ - Ref: ver.Ref, - Stream: ver.Stream, + Ref: ver.ref, + Stream: ver.stream, Kind: VersionKindImage, Version: minorList.Versions[len(minorList.Versions)-1], } @@ -205,15 +205,15 @@ func (c *Client) deleteVersionFromMinorVersionList(ctx context.Context, ver Vers return latest, fmt.Errorf("updating minor version list %s: %w", minorList.JSONPath(), err) } - c.Client.Log.Debugf("Removed version %s from minor version list %s", ver.Version, minorList.JSONPath()) + c.Client.Log.Debugf("Removed version %s from minor version list %s", ver.version, minorList.JSONPath()) return latest, nil } func (c *Client) deleteVersionFromLatest(ctx context.Context, ver Version, possibleNewLatest *Latest, ) error { latest := Latest{ - Ref: ver.Ref, - Stream: ver.Stream, + Ref: ver.ref, + Stream: ver.stream, Kind: VersionKindImage, } c.Client.Log.Debugf("Fetching latest version from %s", latest.JSONPath()) @@ -226,8 +226,8 @@ func (c *Client) deleteVersionFromLatest(ctx context.Context, ver Version, possi return fmt.Errorf("fetching latest version: %w", err) } - if latest.Version != ver.Version { - c.Client.Log.Debugf("Latest version is %s, not the deleted version %s", latest.Version, ver.Version) + if latest.Version != ver.version { + c.Client.Log.Debugf("Latest version is %s, not the deleted version %s", latest.Version, ver.version) return nil } diff --git a/internal/api/versionsapi/list.go b/internal/api/versionsapi/list.go index 46580c522..9cef3ebaa 100644 --- a/internal/api/versionsapi/list.go +++ b/internal/api/versionsapi/list.go @@ -163,10 +163,10 @@ func (l List) StructuredVersions() []Version { versions := make([]Version, len(l.Versions)) for i, v := range l.Versions { versions[i] = Version{ - Ref: l.Ref, - Stream: l.Stream, - Version: v, - Kind: l.Kind, + ref: l.Ref, + stream: l.Stream, + version: v, + kind: l.Kind, } } return versions diff --git a/internal/api/versionsapi/list_test.go b/internal/api/versionsapi/list_test.go index 19529a572..20aac9ab0 100644 --- a/internal/api/versionsapi/list_test.go +++ b/internal/api/versionsapi/list_test.go @@ -344,10 +344,10 @@ func TestListStructuredVersions(t *testing.T) { verStrs := make([]string, len(versions)) for i, v := range versions { - assert.Equal(list.Ref, v.Ref) - assert.Equal(list.Stream, v.Stream) - assert.Equal(list.Kind, v.Kind) - verStrs[i] = v.Version + assert.Equal(list.Ref, v.Ref()) + assert.Equal(list.Stream, v.Stream()) + assert.Equal(list.Kind, v.Kind()) + verStrs[i] = v.version } assert.ElementsMatch(list.Versions, verStrs) diff --git a/internal/api/versionsapi/version.go b/internal/api/versionsapi/version.go index 9b7e7ca93..12d1e8100 100644 --- a/internal/api/versionsapi/version.go +++ b/internal/api/versionsapi/version.go @@ -28,11 +28,30 @@ const ReleaseRef = "-" // Notice that version is a meta object to the versions API and there isn't an // actual corresponding object in the S3 bucket. // Therefore, the version object doesn't have a URL or JSON path. +// +// Versions fields are private so the type can be used in other packages by +// defining private interfaces. type Version struct { - Ref string - Stream string - Version string - Kind VersionKind + ref string + stream string + version string + kind VersionKind +} + +// NewVersion creates a new Version object and validates it. +func NewVersion(ref, stream, version string, kind VersionKind) (Version, error) { + ver := Version{ + ref: ref, + stream: stream, + version: version, + kind: kind, + } + + if err := ver.Validate(); err != nil { + return Version{}, err + } + + return ver, nil } // NewVersionFromShortPath creates a new Version from a version short path. @@ -43,10 +62,10 @@ func NewVersionFromShortPath(shortPath string, kind VersionKind) (Version, error } ver := Version{ - Ref: ref, - Stream: stream, - Version: version, - Kind: kind, + ref: ref, + stream: stream, + version: version, + kind: kind, } if err := ver.Validate(); err != nil { @@ -56,27 +75,47 @@ func NewVersionFromShortPath(shortPath string, kind VersionKind) (Version, error return ver, nil } +// Ref returns the ref of the version. +func (v Version) Ref() string { + return v.ref +} + +// Stream returns the stream of the version. +func (v Version) Stream() string { + return v.stream +} + +// Version returns the version string of the version. +func (v Version) Version() string { + return v.version +} + +// Kind returns the kind of the version. +func (v Version) Kind() VersionKind { + return v.kind +} + // ShortPath returns the short path of the version. func (v Version) ShortPath() string { - return shortPath(v.Ref, v.Stream, v.Version) + return shortPath(v.ref, v.stream, v.version) } // Validate validates the version. func (v Version) Validate() error { var retErr error - if err := ValidateRef(v.Ref); err != nil { + if err := ValidateRef(v.ref); err != nil { retErr = errors.Join(retErr, err) } - if err := ValidateStream(v.Ref, v.Stream); err != nil { + if err := ValidateStream(v.ref, v.stream); err != nil { retErr = errors.Join(retErr, err) } - if !semver.IsValid(v.Version) { - retErr = errors.Join(retErr, fmt.Errorf("version %q is not a valid semantic version", v.Version)) + if !semver.IsValid(v.version) { + retErr = errors.Join(retErr, fmt.Errorf("version %q is not a valid semantic version", v.version)) } - if semver.Canonical(v.Version) != v.Version { - retErr = errors.Join(retErr, fmt.Errorf("version %q is not a canonical semantic version", v.Version)) + if semver.Canonical(v.version) != v.version { + retErr = errors.Join(retErr, fmt.Errorf("version %q is not a canonical semantic version", v.version)) } - if v.Kind == VersionKindUnknown { + if v.kind == VersionKindUnknown { retErr = errors.Join(retErr, errors.New("version kind is unknown")) } return retErr @@ -84,29 +123,29 @@ func (v Version) Validate() error { // Equal returns true if the versions are equal. func (v Version) Equal(other Version) bool { - return v.Ref == other.Ref && - v.Stream == other.Stream && - v.Version == other.Version && - v.Kind == other.Kind + return v.ref == other.ref && + v.stream == other.stream && + v.version == other.version && + v.kind == other.kind } // Major returns the major version corresponding to the version. // For example, if the version is "v1.2.3", the major version is "v1". func (v Version) Major() string { - return semver.Major(v.Version) + return semver.Major(v.version) } // MajorMinor returns the major and minor version corresponding to the version. // For example, if the version is "v1.2.3", the major and minor version is "v1.2". func (v Version) MajorMinor() string { - return semver.MajorMinor(v.Version) + return semver.MajorMinor(v.version) } // WithGranularity returns the version with the given granularity. // // For example, if the version is "v1.2.3" and the granularity is GranularityMajor, // the returned version is "v1". -// This is a helper function for Major() and MajorMinor() and v.Version. +// This is a helper function for Major() and MajorMinor() and v.version. // In case of an unknown granularity, an empty string is returned. func (v Version) WithGranularity(gran Granularity) string { switch gran { @@ -115,7 +154,7 @@ func (v Version) WithGranularity(gran Granularity) string { case GranularityMinor: return v.MajorMinor() case GranularityPatch: - return v.Version + return v.version default: return "" } @@ -144,11 +183,11 @@ func (v Version) ListPath(gran Granularity) string { } return path.Join( constants.CDNAPIPrefix, - "ref", v.Ref, - "stream", v.Stream, + "ref", v.ref, + "stream", v.stream, "versions", gran.String(), v.WithGranularity(gran), - v.Kind.String()+".json", + v.kind.String()+".json", ) } @@ -164,9 +203,9 @@ func (v Version) ArtifactPath(apiVer apiVersion) string { return path.Join( constants.CDNAPIBase, apiVer.String(), - "ref", v.Ref, - "stream", v.Stream, - v.Version, + "ref", v.ref, + "stream", v.stream, + v.version, ) } @@ -336,8 +375,8 @@ func ValidateStream(ref, stream string) error { // MeasurementURL builds the measurement and signature URLs for the given version. func MeasurementURL(version Version) (measurementURL, signatureURL *url.URL, err error) { - if version.Kind != VersionKindImage { - return &url.URL{}, &url.URL{}, fmt.Errorf("kind %q is not supported", version.Kind) + if version.kind != VersionKindImage { + return &url.URL{}, &url.URL{}, fmt.Errorf("kind %q is not supported", version.kind) } measurementPath, err := url.JoinPath(version.ArtifactsURL(APIV2), "image", constants.CDNMeasurementsFile) diff --git a/internal/api/versionsapi/version_test.go b/internal/api/versionsapi/version_test.go index ec42233bb..25f0f8ce0 100644 --- a/internal/api/versionsapi/version_test.go +++ b/internal/api/versionsapi/version_test.go @@ -27,40 +27,40 @@ func TestNewVersionFromShortPath(t *testing.T) { path: "v9.9.9", kind: VersionKindImage, wantVer: Version{ - Ref: ReleaseRef, - Stream: "stable", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9", + kind: VersionKindImage, }, }, "release debug image": { path: "stream/debug/v9.9.9", kind: VersionKindImage, wantVer: Version{ - Ref: ReleaseRef, - Stream: "debug", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: ReleaseRef, + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, }, }, "stable release cli": { path: "v9.9.9", kind: VersionKindCLI, wantVer: Version{ - Ref: ReleaseRef, - Stream: "stable", - Version: "v9.9.9", - Kind: VersionKindCLI, + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9", + kind: VersionKindCLI, }, }, "release debug cli": { path: "stream/debug/v9.9.9", kind: VersionKindCLI, wantVer: Version{ - Ref: ReleaseRef, - Stream: "debug", - Version: "v9.9.9", - Kind: VersionKindCLI, + ref: ReleaseRef, + stream: "debug", + version: "v9.9.9", + kind: VersionKindCLI, }, }, "unknown kind": { @@ -102,55 +102,55 @@ func TestVersionShortPath(t *testing.T) { }{ "stable release image": { ver: Version{ - Ref: ReleaseRef, - Stream: "stable", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9", + kind: VersionKindImage, }, want: "v9.9.9", }, "release debug image": { ver: Version{ - Ref: ReleaseRef, - Stream: "debug", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: ReleaseRef, + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, }, want: "stream/debug/v9.9.9", }, "branch image": { ver: Version{ - Ref: "foo", - Stream: "debug", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: "foo", + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, }, want: "ref/foo/stream/debug/v9.9.9", }, "stable release cli": { ver: Version{ - Ref: ReleaseRef, - Stream: "stable", - Version: "v9.9.9", - Kind: VersionKindCLI, + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9", + kind: VersionKindCLI, }, want: "v9.9.9", }, "release debug cli": { ver: Version{ - Ref: ReleaseRef, - Stream: "debug", - Version: "v9.9.9", - Kind: VersionKindCLI, + ref: ReleaseRef, + stream: "debug", + version: "v9.9.9", + kind: VersionKindCLI, }, want: "stream/debug/v9.9.9", }, "branch cli": { ver: Version{ - Ref: "foo", - Stream: "debug", - Version: "v9.9.9", - Kind: VersionKindCLI, + ref: "foo", + stream: "debug", + version: "v9.9.9", + kind: VersionKindCLI, }, want: "ref/foo/stream/debug/v9.9.9", }, @@ -173,71 +173,71 @@ func TestVersionValidate(t *testing.T) { }{ "valid image": { ver: Version{ - Ref: ReleaseRef, - Stream: "stable", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9", + kind: VersionKindImage, }, }, "invalid ref image": { ver: Version{ - Ref: "foo/bar", - Stream: "stable", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: "foo/bar", + stream: "stable", + version: "v9.9.9", + kind: VersionKindImage, }, wantErr: true, }, "invalid stream image": { ver: Version{ - Ref: ReleaseRef, - Stream: "foo/bar", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: ReleaseRef, + stream: "foo/bar", + version: "v9.9.9", + kind: VersionKindImage, }, wantErr: true, }, "invalid version image": { ver: Version{ - Ref: ReleaseRef, - Stream: "stable", - Version: "v9.9.9/foo", - Kind: VersionKindImage, + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9/foo", + kind: VersionKindImage, }, wantErr: true, }, "valid cli": { ver: Version{ - Ref: ReleaseRef, - Stream: "stable", - Version: "v9.9.9", - Kind: VersionKindCLI, + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9", + kind: VersionKindCLI, }, }, "invalid ref cli": { ver: Version{ - Ref: "foo/bar", - Stream: "stable", - Version: "v9.9.9", - Kind: VersionKindCLI, + ref: "foo/bar", + stream: "stable", + version: "v9.9.9", + kind: VersionKindCLI, }, wantErr: true, }, "invalid stream cli": { ver: Version{ - Ref: ReleaseRef, - Stream: "foo/bar", - Version: "v9.9.9", - Kind: VersionKindCLI, + ref: ReleaseRef, + stream: "foo/bar", + version: "v9.9.9", + kind: VersionKindCLI, }, wantErr: true, }, "invalid version cli": { ver: Version{ - Ref: ReleaseRef, - Stream: "stable", - Version: "v9.9.9/foo", - Kind: VersionKindCLI, + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9/foo", + kind: VersionKindCLI, }, wantErr: true, }, @@ -268,7 +268,7 @@ func TestVersionMajor(t *testing.T) { t.Run(version, func(t *testing.T) { assert := assert.New(t) - ver := Version{Version: version} + ver := Version{version: version} assert.Equal(major, ver.Major()) }) } @@ -285,7 +285,7 @@ func TestVersionMajorMinor(t *testing.T) { t.Run(version, func(t *testing.T) { assert := assert.New(t) - ver := Version{Version: version} + ver := Version{version: version} assert.Equal(major, ver.MajorMinor()) }) } @@ -333,7 +333,7 @@ func TestVersionWithGranularity(t *testing.T) { t.Run(tc.ver, func(t *testing.T) { assert := assert.New(t) - ver := Version{Version: tc.ver} + ver := Version{version: tc.ver} assert.Equal(tc.want, ver.WithGranularity(tc.gran)) }) } @@ -348,10 +348,10 @@ func TestVersionListPathURL(t *testing.T) { }{ "release image": { ver: Version{ - Ref: ReleaseRef, - Stream: "stable", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9", + kind: VersionKindImage, }, gran: GranularityMajor, wantPath: constants.CDNAPIPrefix + "/ref/" + ReleaseRef + "/stream/stable/versions/major/v9/image.json", @@ -359,10 +359,10 @@ func TestVersionListPathURL(t *testing.T) { }, "release with minor image": { ver: Version{ - Ref: ReleaseRef, - Stream: "stable", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9", + kind: VersionKindImage, }, gran: GranularityMinor, wantPath: constants.CDNAPIPrefix + "/ref/" + ReleaseRef + "/stream/stable/versions/minor/v9.9/image.json", @@ -370,10 +370,10 @@ func TestVersionListPathURL(t *testing.T) { }, "release with patch image": { ver: Version{ - Ref: ReleaseRef, - Stream: "stable", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9", + kind: VersionKindImage, }, gran: GranularityPatch, wantPath: "", @@ -381,10 +381,10 @@ func TestVersionListPathURL(t *testing.T) { }, "release with unknown image": { ver: Version{ - Ref: ReleaseRef, - Stream: "stable", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9", + kind: VersionKindImage, }, gran: GranularityUnknown, wantPath: "", @@ -392,10 +392,10 @@ func TestVersionListPathURL(t *testing.T) { }, "release debug stream image": { ver: Version{ - Ref: ReleaseRef, - Stream: "debug", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: ReleaseRef, + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, }, gran: GranularityMajor, wantPath: constants.CDNAPIPrefix + "/ref/" + ReleaseRef + "/stream/debug/versions/major/v9/image.json", @@ -403,10 +403,10 @@ func TestVersionListPathURL(t *testing.T) { }, "release debug stream with minor image": { ver: Version{ - Ref: ReleaseRef, - Stream: "debug", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: ReleaseRef, + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, }, gran: GranularityMinor, wantPath: constants.CDNAPIPrefix + "/ref/" + ReleaseRef + "/stream/debug/versions/minor/v9.9/image.json", @@ -414,10 +414,10 @@ func TestVersionListPathURL(t *testing.T) { }, "branch ref image": { ver: Version{ - Ref: "foo", - Stream: "debug", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: "foo", + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, }, gran: GranularityMajor, wantPath: constants.CDNAPIPrefix + "/ref/foo/stream/debug/versions/major/v9/image.json", @@ -425,10 +425,10 @@ func TestVersionListPathURL(t *testing.T) { }, "branch ref with minor image": { ver: Version{ - Ref: "foo", - Stream: "debug", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: "foo", + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, }, gran: GranularityMinor, wantPath: constants.CDNAPIPrefix + "/ref/foo/stream/debug/versions/minor/v9.9/image.json", @@ -463,10 +463,10 @@ func TestVersionArtifactURL(t *testing.T) { }{ "nightly-feature": { ver: Version{ - Ref: "feat-some-feature", - Stream: "nightly", - Version: "v2.6.0-pre.0.20230217095603-193dd48ca19f", - Kind: VersionKindImage, + ref: "feat-some-feature", + stream: "nightly", + version: "v2.6.0-pre.0.20230217095603-193dd48ca19f", + kind: VersionKindImage, }, csp: cloudprovider.GCP, wantMeasurementURL: constants.CDNRepositoryURL + "/" + constants.CDNAPIPrefixV2 + "/ref/feat-some-feature/stream/nightly/v2.6.0-pre.0.20230217095603-193dd48ca19f/image/measurements.json", @@ -474,7 +474,7 @@ func TestVersionArtifactURL(t *testing.T) { }, "fail for wrong kind": { ver: Version{ - Kind: VersionKindCLI, + kind: VersionKindCLI, }, wantErr: true, }, @@ -503,55 +503,55 @@ func TestVersionArtifactPathURL(t *testing.T) { }{ "release image": { ver: Version{ - Ref: ReleaseRef, - Stream: "stable", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9", + kind: VersionKindImage, }, wantPath: constants.CDNAPIPrefix + "/ref/" + ReleaseRef + "/stream/stable/v9.9.9", }, "release debug stream image": { ver: Version{ - Ref: ReleaseRef, - Stream: "debug", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: ReleaseRef, + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, }, wantPath: constants.CDNAPIPrefix + "/ref/" + ReleaseRef + "/stream/debug/v9.9.9", }, "branch ref image": { ver: Version{ - Ref: "foo", - Stream: "debug", - Version: "v9.9.9", - Kind: VersionKindImage, + ref: "foo", + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, }, wantPath: constants.CDNAPIPrefix + "/ref/foo/stream/debug/v9.9.9", }, "release cli": { ver: Version{ - Ref: ReleaseRef, - Stream: "stable", - Version: "v9.9.9", - Kind: VersionKindCLI, + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9", + kind: VersionKindCLI, }, wantPath: constants.CDNAPIPrefix + "/ref/" + ReleaseRef + "/stream/stable/v9.9.9", }, "release debug stream cli": { ver: Version{ - Ref: ReleaseRef, - Stream: "debug", - Version: "v9.9.9", - Kind: VersionKindCLI, + ref: ReleaseRef, + stream: "debug", + version: "v9.9.9", + kind: VersionKindCLI, }, wantPath: constants.CDNAPIPrefix + "/ref/" + ReleaseRef + "/stream/debug/v9.9.9", }, "branch ref cli": { ver: Version{ - Ref: "foo", - Stream: "debug", - Version: "v9.9.9", - Kind: VersionKindCLI, + ref: "foo", + stream: "debug", + version: "v9.9.9", + kind: VersionKindCLI, }, wantPath: constants.CDNAPIPrefix + "/ref/foo/stream/debug/v9.9.9", }, diff --git a/internal/attestation/measurements/BUILD.bazel b/internal/attestation/measurements/BUILD.bazel index 2de254aa7..07660450d 100644 --- a/internal/attestation/measurements/BUILD.bazel +++ b/internal/attestation/measurements/BUILD.bazel @@ -16,7 +16,6 @@ go_library( "//internal/api/versionsapi", "//internal/attestation/variant", "//internal/cloud/cloudprovider", - "//internal/sigstore", "@com_github_google_go_tpm//tpmutil", "@com_github_siderolabs_talos_pkg_machinery//config/encoder", "@in_gopkg_yaml_v3//:yaml_v3", diff --git a/internal/attestation/measurements/measurement-generator/BUILD.bazel b/internal/attestation/measurements/measurement-generator/BUILD.bazel index 80b8aaa28..35e8c8259 100644 --- a/internal/attestation/measurements/measurement-generator/BUILD.bazel +++ b/internal/attestation/measurements/measurement-generator/BUILD.bazel @@ -12,6 +12,7 @@ go_library( "//internal/attestation/variant", "//internal/cloud/cloudprovider", "//internal/sigstore", + "//internal/sigstore/keyselect", "@org_golang_x_tools//go/ast/astutil", ], ) diff --git a/internal/attestation/measurements/measurement-generator/generate.go b/internal/attestation/measurements/measurement-generator/generate.go index 670dead4a..0050760ad 100644 --- a/internal/attestation/measurements/measurement-generator/generate.go +++ b/internal/attestation/measurements/measurement-generator/generate.go @@ -28,6 +28,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/sigstore" + "github.com/edgelesssys/constellation/v2/internal/sigstore/keyselect" "golang.org/x/tools/go/ast/astutil" ) @@ -122,10 +123,20 @@ func mustGetMeasurements(ctx context.Context, verifier rekorVerifier, provider c panic(err) } + publicKey, err := keyselect.CosignPublicKeyForVersion(imageVersion) + if err != nil { + panic(fmt.Errorf("getting public key: %w", err)) + } + + cosignVerifier, err := sigstore.NewCosignVerifier(publicKey) + if err != nil { + panic(fmt.Errorf("creating cosign verifier: %w", err)) + } + log.Println("Fetching measurements from", measurementsURL, "and signature from", signatureURL) var fetchedMeasurements measurements.M hash, err := fetchedMeasurements.FetchAndVerify( - ctx, http.DefaultClient, sigstore.CosignVerifier{}, + ctx, http.DefaultClient, cosignVerifier, measurementsURL, signatureURL, imageVersion, @@ -135,7 +146,7 @@ func mustGetMeasurements(ctx context.Context, verifier rekorVerifier, provider c if err != nil { panic(err) } - if err := sigstore.VerifyWithRekor(ctx, imageVersion, verifier, hash); err != nil { + if err := sigstore.VerifyWithRekor(ctx, publicKey, verifier, hash); err != nil { panic(err) } return fetchedMeasurements diff --git a/internal/attestation/measurements/measurements.go b/internal/attestation/measurements/measurements.go index 981c44f3e..edf885cbf 100644 --- a/internal/attestation/measurements/measurements.go +++ b/internal/attestation/measurements/measurements.go @@ -29,7 +29,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" - "github.com/edgelesssys/constellation/v2/internal/sigstore" "github.com/google/go-tpm/tpmutil" "github.com/siderolabs/talos/pkg/machinery/config/encoder" "gopkg.in/yaml.v3" @@ -137,14 +136,10 @@ func (m *M) FetchAndVerify( measurementsURL, signatureURL *url.URL, version versionsapi.Version, csp cloudprovider.Provider, attestationVariant variant.Variant, ) (string, error) { - publicKey, err := sigstore.CosignPublicKeyForVersion(version) - if err != nil { - return "", fmt.Errorf("getting public key: %w", err) - } return m.fetchAndVerify( ctx, client, verifier, measurementsURL, signatureURL, - publicKey, version, csp, attestationVariant, + version, csp, attestationVariant, ) } @@ -153,7 +148,7 @@ func (m *M) FetchAndVerify( // The hash of the fetched measurements is returned. func (m *M) fetchAndVerify( ctx context.Context, client *http.Client, verifier cosignVerifier, - measurementsURL, signatureURL *url.URL, publicKey []byte, + measurementsURL, signatureURL *url.URL, version versionsapi.Version, csp cloudprovider.Provider, attestationVariant variant.Variant, ) (string, error) { measurementsRaw, err := getFromURL(ctx, client, measurementsURL) @@ -164,7 +159,7 @@ func (m *M) fetchAndVerify( if err != nil { return "", fmt.Errorf("failed to fetch signature: %w", err) } - if err := verifier.VerifySignature(measurementsRaw, signature, publicKey); err != nil { + if err := verifier.VerifySignature(measurementsRaw, signature); err != nil { return "", err } @@ -308,11 +303,9 @@ func (m *M) fromImageMeasurementsV2( measurements ImageMeasurementsV2, wantVersion versionsapi.Version, csp cloudprovider.Provider, attestationVariant variant.Variant, ) error { - gotVersion := versionsapi.Version{ - Ref: measurements.Ref, - Stream: measurements.Stream, - Version: measurements.Version, - Kind: versionsapi.VersionKindImage, + gotVersion, err := versionsapi.NewVersion(measurements.Ref, measurements.Stream, measurements.Version, versionsapi.VersionKindImage) + if err != nil { + return fmt.Errorf("invalid metadata version: %w", err) } if !wantVersion.Equal(gotVersion) { return fmt.Errorf("invalid measurement metadata: version mismatch: expected %s, got %s", wantVersion.ShortPath(), gotVersion.ShortPath()) @@ -553,5 +546,5 @@ func getFromURL(ctx context.Context, client *http.Client, sourceURL *url.URL) ([ } type cosignVerifier interface { - VerifySignature(content, signature, publicKey []byte) error + VerifySignature(content, signature []byte) error } diff --git a/internal/attestation/measurements/measurements_test.go b/internal/attestation/measurements/measurements_test.go index 57a064b00..d08c95030 100644 --- a/internal/attestation/measurements/measurements_test.go +++ b/internal/attestation/measurements/measurements_test.go @@ -332,6 +332,11 @@ func TestMeasurementsFetchAndVerify(t *testing.T) { // -----END ENCRYPTED COSIGN PRIVATE KEY----- cosignPublicKey := []byte("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu78QgxOOcao6U91CSzEXxrKhvFTt\nJHNy+eX6EMePtDm8CnDF9HSwnTlD0itGJ/XHPQA5YX10fJAqI1y+ehlFMw==\n-----END PUBLIC KEY-----") + v1Test, err := versionsapi.NewVersion("-", "stable", "v1.0.0-test", versionsapi.VersionKindImage) + require.NoError(t, err) + v1AnotherImage, err := versionsapi.NewVersion("-", "stable", "v1.0.0-another-image", versionsapi.VersionKindImage) + require.NoError(t, err) + testCases := map[string]struct { measurements string csp cloudprovider.Provider @@ -347,7 +352,7 @@ func TestMeasurementsFetchAndVerify(t *testing.T) { "json measurements": { measurements: `{"version":"v1.0.0-test","ref":"-","stream":"stable","list":[{"csp":"Unknown","attestationVariant":"dummy","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}]}`, csp: cloudprovider.Unknown, - imageVersion: versionsapi.Version{Ref: "-", Stream: "stable", Version: "v1.0.0-test", Kind: versionsapi.VersionKindImage}, + imageVersion: v1Test, measurementsStatus: http.StatusOK, signature: "MEUCIHuW2420EqN4Kj6OEaVMmufH7d01vyR1J+SWg8H4elyBAiEA1Ki5Hfq0iI70qpViYbrTFrd8e840NjtdAxGqJKiJgbA=", signatureStatus: http.StatusOK, @@ -359,7 +364,7 @@ func TestMeasurementsFetchAndVerify(t *testing.T) { "404 measurements": { measurements: `{"version":"v1.0.0-test","ref":"-","stream":"stable","list":[{"csp":"Unknown","attestationVariant":"dummy","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}]}`, csp: cloudprovider.Unknown, - imageVersion: versionsapi.Version{Ref: "-", Stream: "stable", Version: "v1.0.0-test", Kind: versionsapi.VersionKindImage}, + imageVersion: v1Test, measurementsStatus: http.StatusNotFound, signature: "MEUCIHuW2420EqN4Kj6OEaVMmufH7d01vyR1J+SWg8H4elyBAiEA1Ki5Hfq0iI70qpViYbrTFrd8e840NjtdAxGqJKiJgbA=", signatureStatus: http.StatusOK, @@ -368,7 +373,7 @@ func TestMeasurementsFetchAndVerify(t *testing.T) { "404 signature": { measurements: `{"version":"v1.0.0-test","ref":"-","stream":"stable","list":[{"csp":"Unknown","attestationVariant":"dummy","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}]}`, csp: cloudprovider.Unknown, - imageVersion: versionsapi.Version{Ref: "-", Stream: "stable", Version: "v1.0.0-test", Kind: versionsapi.VersionKindImage}, + imageVersion: v1Test, measurementsStatus: http.StatusOK, signature: "MEUCIHuW2420EqN4Kj6OEaVMmufH7d01vyR1J+SWg8H4elyBAiEA1Ki5Hfq0iI70qpViYbrTFrd8e840NjtdAxGqJKiJgbA=", signatureStatus: http.StatusNotFound, @@ -377,7 +382,7 @@ func TestMeasurementsFetchAndVerify(t *testing.T) { "broken signature": { measurements: `{"version":"v1.0.0-test","ref":"-","stream":"stable","list":[{"csp":"Unknown","attestationVariant":"dummy","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}]}`, csp: cloudprovider.Unknown, - imageVersion: versionsapi.Version{Ref: "-", Stream: "stable", Version: "v1.0.0-test", Kind: versionsapi.VersionKindImage}, + imageVersion: v1Test, measurementsStatus: http.StatusOK, signature: "AAAAAAA1RR91pWPw1BMWXTSmTBHg/JtfKerbZNQ9PJTWDdW0sgIhANQbETJGb67qzQmMVmcq007VUFbHRMtYWKZeeyRf0gVa", signatureStatus: http.StatusOK, @@ -386,7 +391,7 @@ func TestMeasurementsFetchAndVerify(t *testing.T) { "metadata CSP mismatch": { measurements: `{"version":"v1.0.0-test","ref":"-","stream":"stable","list":[{"csp":"Unknown","attestationVariant":"dummy","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}]}`, csp: cloudprovider.GCP, - imageVersion: versionsapi.Version{Ref: "-", Stream: "stable", Version: "v1.0.0-test", Kind: versionsapi.VersionKindImage}, + imageVersion: v1Test, measurementsStatus: http.StatusOK, signature: "MEUCIHuW2420EqN4Kj6OEaVMmufH7d01vyR1J+SWg8H4elyBAiEA1Ki5Hfq0iI70qpViYbrTFrd8e840NjtdAxGqJKiJgbA=", signatureStatus: http.StatusOK, @@ -395,7 +400,7 @@ func TestMeasurementsFetchAndVerify(t *testing.T) { "metadata image mismatch": { measurements: `{"version":"v1.0.0-test","ref":"-","stream":"stable","list":[{"csp":"Unknown","attestationVariant":"dummy","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}]}`, csp: cloudprovider.Unknown, - imageVersion: versionsapi.Version{Ref: "-", Stream: "stable", Version: "v1.0.0-another-image", Kind: versionsapi.VersionKindImage}, + imageVersion: v1AnotherImage, measurementsStatus: http.StatusOK, signature: "MEUCIHuW2420EqN4Kj6OEaVMmufH7d01vyR1J+SWg8H4elyBAiEA1Ki5Hfq0iI70qpViYbrTFrd8e840NjtdAxGqJKiJgbA=", signatureStatus: http.StatusOK, @@ -404,7 +409,7 @@ func TestMeasurementsFetchAndVerify(t *testing.T) { "not json": { measurements: "This is some content to be signed!\n", csp: cloudprovider.Unknown, - imageVersion: versionsapi.Version{Ref: "-", Stream: "stable", Version: "v1.0.0-test", Kind: versionsapi.VersionKindImage}, + imageVersion: v1Test, measurementsStatus: http.StatusOK, signature: "MEUCIQCGA/lSu5qCJgNNvgMaTKJ9rj6vQMecUDaQo3ukaiAfUgIgWoxXRoDKLY9naN7YgxokM7r2fwnyYk3M2WKJJO1g6yo=", signatureStatus: http.StatusOK, @@ -418,6 +423,7 @@ func TestMeasurementsFetchAndVerify(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { assert := assert.New(t) + require := require.New(t) if tc.attestationVariant == nil { tc.attestationVariant = variant.Dummy{} @@ -447,10 +453,12 @@ func TestMeasurementsFetchAndVerify(t *testing.T) { m := M{} + verifier, err := sigstore.NewCosignVerifier(cosignPublicKey) + require.NoError(err) + hash, err := m.fetchAndVerify( - context.Background(), client, sigstore.CosignVerifier{}, + context.Background(), client, verifier, measurementsURL, signatureURL, - cosignPublicKey, tc.imageVersion, tc.csp, tc.attestationVariant, diff --git a/internal/config/config.go b/internal/config/config.go index 25fa57067..198fbf370 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -578,7 +578,7 @@ func (c *Config) IsNamedLikeDebugImage() bool { if err != nil { return false } - return v.Stream == "debug" + return v.Stream() == "debug" } // GetProvider returns the configured cloud provider. diff --git a/internal/config/validation.go b/internal/config/validation.go index d6a138929..f97cdd44e 100644 --- a/internal/config/validation.go +++ b/internal/config/validation.go @@ -513,7 +513,7 @@ func validateImageCompatibilityHelper(binaryVersion consemver.Semver, fieldName, if err != nil { return err } - configuredVersion = imageVersion.Version + configuredVersion = imageVersion.Version() } return compatibility.BinaryWith(binaryVersion.String(), configuredVersion) diff --git a/internal/imagefetcher/imagefetcher.go b/internal/imagefetcher/imagefetcher.go index 4cab9bc47..3a1dce56e 100644 --- a/internal/imagefetcher/imagefetcher.go +++ b/internal/imagefetcher/imagefetcher.go @@ -51,9 +51,9 @@ func (f *Fetcher) FetchReference(ctx context.Context, } imgInfoReq := versionsapi.ImageInfo{ - Ref: ver.Ref, - Stream: ver.Stream, - Version: ver.Version, + Ref: ver.Ref(), + Stream: ver.Stream(), + Version: ver.Version(), } url, err := imgInfoReq.URL() diff --git a/internal/osimage/archive/archive.go b/internal/osimage/archive/archive.go index df12c9de3..696779dda 100644 --- a/internal/osimage/archive/archive.go +++ b/internal/osimage/archive/archive.go @@ -67,7 +67,7 @@ func (a *Archivist) Close(ctx context.Context) error { // Archive reads the OS image in img and uploads it as key. func (a *Archivist) Archive(ctx context.Context, version versionsapi.Version, csp, attestationVariant string, img io.Reader) (string, error) { - key, err := url.JoinPath(version.ArtifactPath(versionsapi.APIV1), version.Kind.String(), "csp", csp, attestationVariant, "image.raw") + key, err := url.JoinPath(version.ArtifactPath(versionsapi.APIV1), version.Kind().String(), "csp", csp, attestationVariant, "image.raw") if err != nil { return "", err } diff --git a/internal/osimage/aws/awsupload.go b/internal/osimage/aws/awsupload.go index 74e1d65b1..fee5fbd1d 100644 --- a/internal/osimage/aws/awsupload.go +++ b/internal/osimage/aws/awsupload.go @@ -73,7 +73,7 @@ func New(region, bucketName string, log *logger.Logger) (*Uploader, error) { // Upload uploads an OS image to AWS. func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) ([]versionsapi.ImageInfoEntry, error) { - blobName := fmt.Sprintf("image-%s-%s-%d.raw", req.Version.Stream, req.Version.Version, req.Timestamp.Unix()) + blobName := fmt.Sprintf("image-%s-%s-%d.raw", req.Version.Stream(), req.Version.Version(), req.Timestamp.Unix()) imageName := imageName(req.Version, req.AttestationVariant, req.Timestamp) allRegions := []string{u.region} allRegions = append(allRegions, replicationRegions...) @@ -479,10 +479,10 @@ func (u *Uploader) ensureImageDeleted(ctx context.Context, imageName, region str } func imageName(version versionsapi.Version, attestationVariant string, timestamp time.Time) string { - if version.Stream == "stable" { - return fmt.Sprintf("constellation-%s-%s", version.Version, attestationVariant) + if version.Stream() == "stable" { + return fmt.Sprintf("constellation-%s-%s", version.Version(), attestationVariant) } - return fmt.Sprintf("constellation-%s-%s-%s-%s", version.Stream, version.Version, attestationVariant, timestamp.Format(timestampFormat)) + return fmt.Sprintf("constellation-%s-%s-%s-%s", version.Stream(), version.Version(), attestationVariant, timestamp.Format(timestampFormat)) } func waitForSnapshotImport(ctx context.Context, ec2C ec2API, importTaskID string) (string, error) { diff --git a/internal/osimage/azure/azureupload.go b/internal/osimage/azure/azureupload.go index 6dc2ffef2..12c4a654f 100644 --- a/internal/osimage/azure/azureupload.go +++ b/internal/osimage/azure/azureupload.go @@ -95,9 +95,9 @@ func New(subscription, location, resourceGroup string, log *logger.Logger) (*Upl // Upload uploads an OS image to Azure. func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) ([]versionsapi.ImageInfoEntry, error) { formattedTime := req.Timestamp.Format(timestampFormat) - diskName := fmt.Sprintf("constellation-%s-%s-%s", req.Version.Stream, formattedTime, req.AttestationVariant) + diskName := fmt.Sprintf("constellation-%s-%s-%s", req.Version.Stream(), formattedTime, req.AttestationVariant) var sigName string - switch req.Version.Stream { + switch req.Version.Stream() { case "stable": sigName = sigNameStable case "debug": @@ -517,12 +517,12 @@ func uploadChunk(ctx context.Context, uploader azurePageblobAPI, chunk io.ReadSe func imageOffer(version versionsapi.Version) string { switch { - case version.Stream == "stable": + case version.Stream() == "stable": return "constellation" - case version.Stream == "debug" && version.Ref == "-": - return version.Version + case version.Stream() == "debug" && version.Ref() == "-": + return version.Version() } - return version.Ref + "-" + version.Stream + return version.Ref() + "-" + version.Stream() } // imageVersion determines the semantic version string used inside a sig image. @@ -530,10 +530,10 @@ func imageOffer(version versionsapi.Version) string { // Otherwise, the version is derived from the commit timestamp. func imageVersion(version versionsapi.Version, timestamp time.Time) (string, error) { switch { - case version.Stream == "stable": + case version.Stream() == "stable": fallthrough - case version.Stream == "debug" && version.Ref == "-": - return strings.TrimLeft(version.Version, "v"), nil + case version.Stream() == "debug" && version.Ref() == "-": + return strings.TrimLeft(version.Version(), "v"), nil } formattedTime := timestamp.Format(timestampFormat) diff --git a/internal/osimage/gcp/gcpupload.go b/internal/osimage/gcp/gcpupload.go index da8046f8c..2950fe74b 100644 --- a/internal/osimage/gcp/gcpupload.go +++ b/internal/osimage/gcp/gcpupload.go @@ -225,16 +225,16 @@ func (u *Uploader) blobURL(blobName string) string { } func (u *Uploader) imageName(version versionsapi.Version, attestationVariant string) string { - return strings.ReplaceAll(version.Version, ".", "-") + "-" + attestationVariant + "-" + version.Stream + return strings.ReplaceAll(version.Version(), ".", "-") + "-" + attestationVariant + "-" + version.Stream() } func (u *Uploader) imageFamily(version versionsapi.Version) string { - if version.Stream == "stable" { + if version.Stream() == "stable" { return "constellation" } - truncatedRef := version.Ref - if len(version.Ref) > 45 { - truncatedRef = version.Ref[:45] + truncatedRef := version.Ref() + if len(version.Ref()) > 45 { + truncatedRef = version.Ref()[:45] } return "constellation-" + truncatedRef } diff --git a/internal/osimage/imageinfo/imageinfo.go b/internal/osimage/imageinfo/imageinfo.go index df4203eeb..a1a8e4a7a 100644 --- a/internal/osimage/imageinfo/imageinfo.go +++ b/internal/osimage/imageinfo/imageinfo.go @@ -11,6 +11,7 @@ import ( "bytes" "context" "encoding/json" + "fmt" "net/url" s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager" @@ -67,13 +68,11 @@ func (a *Uploader) Close(ctx context.Context) error { // Upload marshals the image info to JSON and uploads it to S3. func (a *Uploader) Upload(ctx context.Context, imageInfo versionsapi.ImageInfo) (string, error) { - ver := versionsapi.Version{ - Ref: imageInfo.Ref, - Stream: imageInfo.Stream, - Version: imageInfo.Version, - Kind: versionsapi.VersionKindImage, + ver, err := versionsapi.NewVersion(imageInfo.Ref, imageInfo.Stream, imageInfo.Version, versionsapi.VersionKindImage) + if err != nil { + return "", fmt.Errorf("creating version: %w", err) } - key, err := url.JoinPath(ver.ArtifactPath(versionsapi.APIV2), ver.Kind.String(), "info.json") + key, err := url.JoinPath(ver.ArtifactPath(versionsapi.APIV2), ver.Kind().String(), "info.json") if err != nil { return "", err } diff --git a/internal/osimage/measurementsuploader/measurementsuploader.go b/internal/osimage/measurementsuploader/measurementsuploader.go index 9d994e432..d02590397 100644 --- a/internal/osimage/measurementsuploader/measurementsuploader.go +++ b/internal/osimage/measurementsuploader/measurementsuploader.go @@ -78,17 +78,15 @@ func (a *Uploader) Upload(ctx context.Context, rawMeasurement, signature io.Read return "", "", err } - ver := versionsapi.Version{ - Ref: measurements.Ref, - Stream: measurements.Stream, - Version: measurements.Version, - Kind: versionsapi.VersionKindImage, + ver, err := versionsapi.NewVersion(measurements.Ref, measurements.Stream, measurements.Version, versionsapi.VersionKindImage) + if err != nil { + return "", "", fmt.Errorf("creating version: %w", err) } - key, err := url.JoinPath(ver.ArtifactPath(versionsapi.APIV2), ver.Kind.String(), "measurements.json") + key, err := url.JoinPath(ver.ArtifactPath(versionsapi.APIV2), ver.Kind().String(), "measurements.json") if err != nil { return "", "", err } - sigKey, err := url.JoinPath(ver.ArtifactPath(versionsapi.APIV2), ver.Kind.String(), "measurements.json.sig") + sigKey, err := url.JoinPath(ver.ArtifactPath(versionsapi.APIV2), ver.Kind().String(), "measurements.json.sig") if err != nil { return "", "", err } diff --git a/internal/sigstore/BUILD.bazel b/internal/sigstore/BUILD.bazel index d374113d6..415f783c8 100644 --- a/internal/sigstore/BUILD.bazel +++ b/internal/sigstore/BUILD.bazel @@ -12,8 +12,6 @@ go_library( importpath = "github.com/edgelesssys/constellation/v2/internal/sigstore", visibility = ["//:__subpackages__"], deps = [ - "//internal/api/versionsapi", - "//internal/constants", "@com_github_sigstore_rekor//pkg/client", "@com_github_sigstore_rekor//pkg/generated/client", "@com_github_sigstore_rekor//pkg/generated/client/entries", diff --git a/internal/sigstore/keyselect/BUILD.bazel b/internal/sigstore/keyselect/BUILD.bazel new file mode 100644 index 000000000..cf7910b20 --- /dev/null +++ b/internal/sigstore/keyselect/BUILD.bazel @@ -0,0 +1,23 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "foo", + srcs = ["foo.go"], + importpath = "github.com/edgelesssys/constellation/v2/internal/sigstore/foo", + visibility = ["//:__subpackages__"], + deps = [ + "//internal/api/versionsapi", + "//internal/constants", + ], +) + +go_library( + name = "keyselect", + srcs = ["keyselect.go"], + importpath = "github.com/edgelesssys/constellation/v2/internal/sigstore/keyselect", + visibility = ["//:__subpackages__"], + deps = [ + "//internal/api/versionsapi", + "//internal/constants", + ], +) diff --git a/internal/sigstore/keyselect/keyselect.go b/internal/sigstore/keyselect/keyselect.go new file mode 100644 index 000000000..7e08e09a5 --- /dev/null +++ b/internal/sigstore/keyselect/keyselect.go @@ -0,0 +1,28 @@ +/* +Copyright (c) Edgeless Systems GmbH + +SPDX-License-Identifier: AGPL-3.0-only +*/ + +// Package keyselect is used to select the correct public key for signature verification. +// The content of keyselect must be kept separate from internal/sigstore because keyselect relies on internal/api/versionsapi. +// Since internal/api relies on internal/sigstore, we need to separate the functions to avoid import cycles. +package keyselect + +import ( + "fmt" + + "github.com/edgelesssys/constellation/v2/internal/api/versionsapi" + "github.com/edgelesssys/constellation/v2/internal/constants" +) + +// CosignPublicKeyForVersion returns the public key for the given version. +func CosignPublicKeyForVersion(ver versionsapi.Version) ([]byte, error) { + if err := ver.Validate(); err != nil { + return nil, fmt.Errorf("selecting public key: invalid version %s: %w", ver.ShortPath(), err) + } + if ver.Ref() == versionsapi.ReleaseRef && ver.Stream() == "stable" { + return []byte(constants.CosignPublicKeyReleases), nil + } + return []byte(constants.CosignPublicKeyDev), nil +} diff --git a/internal/sigstore/rekor.go b/internal/sigstore/rekor.go index 336534b69..a45377bd3 100644 --- a/internal/sigstore/rekor.go +++ b/internal/sigstore/rekor.go @@ -17,7 +17,6 @@ import ( "errors" "fmt" - "github.com/edgelesssys/constellation/v2/internal/api/versionsapi" "github.com/sigstore/rekor/pkg/client" genclient "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/client/entries" @@ -29,12 +28,7 @@ import ( ) // VerifyWithRekor checks if the hash of a signature is present in Rekor. -func VerifyWithRekor(ctx context.Context, version versionsapi.Version, verifier rekorVerifier, hash string) error { - publicKey, err := CosignPublicKeyForVersion(version) - if err != nil { - return fmt.Errorf("getting public key: %w", err) - } - +func VerifyWithRekor(ctx context.Context, publicKey []byte, verifier rekorVerifier, hash string) error { uuids, err := verifier.SearchByHash(ctx, hash) if err != nil { return fmt.Errorf("searching Rekor for hash: %w", err) diff --git a/internal/sigstore/sign_test.go b/internal/sigstore/sign_test.go index 1810cb0e3..fefbdabd3 100644 --- a/internal/sigstore/sign_test.go +++ b/internal/sigstore/sign_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestSignSignature(t *testing.T) { @@ -50,8 +51,10 @@ func TestSignSignature(t *testing.T) { assert.Error(err) } else { assert.NoError(err) - verifier := CosignVerifier{} - assert.NoError(verifier.VerifySignature(tc.content, signature, publicKey)) + + verifier, err := NewCosignVerifier(publicKey) + require.NoError(t, err) + assert.NoError(verifier.VerifySignature(tc.content, signature)) } }) diff --git a/internal/sigstore/verify.go b/internal/sigstore/verify.go index 30fc422dc..11f50703e 100644 --- a/internal/sigstore/verify.go +++ b/internal/sigstore/verify.go @@ -12,37 +12,47 @@ import ( "encoding/base64" "fmt" - "github.com/edgelesssys/constellation/v2/internal/api/versionsapi" - "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/sigstore/sigstore/pkg/cryptoutils" sigsig "github.com/sigstore/sigstore/pkg/signature" ) // Verifier checks if the signature of content can be verified. type Verifier interface { - VerifySignature(content, signature, publicKey []byte) error + VerifySignature(content, signature []byte) error } -// CosignVerifier checks if the signature of content can be verified -// using a cosign public key. -type CosignVerifier struct{} +// CosignVerifier wraps a public key that can be used for verifying signatures. +type CosignVerifier struct { + publicKey crypto.PublicKey +} + +// NewCosignVerifier unmarshalls and validates the given pem encoded public key and returns a new CosignVerifier. +func NewCosignVerifier(pem []byte) (Verifier, error) { + pubkey, err := cryptoutils.UnmarshalPEMToPublicKey(pem) + if err != nil { + return CosignVerifier{}, fmt.Errorf("unable to parse public key: %w", err) + } + if err := cryptoutils.ValidatePubKey(pubkey); err != nil { + return CosignVerifier{}, fmt.Errorf("unable to validate public key: %w", err) + } + + return CosignVerifier{pubkey}, nil +} // VerifySignature checks if the signature of content can be verified // using publicKey. // signature is expected to be base64 encoded. // publicKey is expected to be PEM encoded. -func (CosignVerifier) VerifySignature(content, signature, publicKey []byte) error { +func (c CosignVerifier) VerifySignature(content, signature []byte) error { + // LoadVerifier would also error if no public key is set. + // However, this error message should be easier to debug. + if c.publicKey == nil { + return fmt.Errorf("no public key set") + } + sigRaw := base64.NewDecoder(base64.StdEncoding, bytes.NewReader(signature)) - pubKeyRaw, err := cryptoutils.UnmarshalPEMToPublicKey(publicKey) - if err != nil { - return fmt.Errorf("unable to parse public key: %w", err) - } - if err := cryptoutils.ValidatePubKey(pubKeyRaw); err != nil { - return fmt.Errorf("unable to validate public key: %w", err) - } - - verifier, err := sigsig.LoadVerifier(pubKeyRaw, crypto.SHA256) + verifier, err := sigsig.LoadVerifier(c.publicKey, crypto.SHA256) if err != nil { return fmt.Errorf("unable to load verifier: %w", err) } @@ -53,14 +63,3 @@ func (CosignVerifier) VerifySignature(content, signature, publicKey []byte) erro return nil } - -// CosignPublicKeyForVersion returns the public key for the given version. -func CosignPublicKeyForVersion(ver versionsapi.Version) ([]byte, error) { - if err := ver.Validate(); err != nil { - return nil, fmt.Errorf("selecting public key: invalid version %s: %w", ver.ShortPath(), err) - } - if ver.Ref == versionsapi.ReleaseRef && ver.Stream == "stable" { - return []byte(constants.CosignPublicKeyReleases), nil - } - return []byte(constants.CosignPublicKeyDev), nil -} diff --git a/internal/sigstore/verify_test.go b/internal/sigstore/verify_test.go index 509410eaa..0b37e7ab1 100644 --- a/internal/sigstore/verify_test.go +++ b/internal/sigstore/verify_test.go @@ -10,8 +10,44 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +func TestNewCosignVerifier(t *testing.T) { + testCases := map[string]struct { + publicKey []byte + wantErr bool + }{ + "success": { + publicKey: []byte(`-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAElWUhon39eAqzEC+/GP03oY4/MQg+ +gCDlEzkuOCybCHf+q766bve799L7Y5y5oRsHY1MrUCUwYF/tL7Sg7EYMsA== +-----END PUBLIC KEY-----`), + }, + "broken public key": { + publicKey: []byte(`-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIthisIsNotAValidPublicAtAllUhon39eAqzEC+/GP03oY4/MQg+ +gCDlEzkuOCybCHf+q766bve799L7Y5y5oRsHY1MrUCUwYF/tL7Sg7EYMsA== +-----END PUBLIC KEY-----`), + wantErr: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + verifier, err := NewCosignVerifier(tc.publicKey) + if tc.wantErr { + assert.Error(err) + return + } + assert.NoError(err) + assert.NotEqual(verifier, CosignVerifier{}) + }) + } +} + func TestVerifySignature(t *testing.T) { testCases := map[string]struct { content []byte @@ -19,7 +55,7 @@ func TestVerifySignature(t *testing.T) { publicKey []byte wantErr bool }{ - "good verification": { + "success": { content: []byte("This is some content to be signed!\n"), signature: []byte("MEUCIQDzMN3yaiO9sxLGAaSA9YD8rLwzvOaZKWa/bzkcjImUFAIgXLLGzClYUd1dGbuEiY3O/g/eiwQYlyxqLQalxjFmz+8="), publicKey: []byte(`-----BEGIN PUBLIC KEY----- @@ -36,32 +72,15 @@ gCDlEzkuOCybCHf+q766bve799L7Y5y5oRsHY1MrUCUwYF/tL7Sg7EYMsA== -----END PUBLIC KEY-----`), wantErr: true, }, - "broken public key": { - content: []byte("This is some content to be signed!\n"), - signature: []byte("MEUCIQDzMN3yaiO9sxLGAaSA9YD8rLwzvOaZKWa/bzkcjImUFAIgXLLGzClYUd1dGbuEiY3O/g/eiwQYlyxqLQalxjFmz+8="), - publicKey: []byte(`-----BEGIN PUBLIC KEY----- - MFkwEwYHKoZIthisIsNotAValidPublicAtAllUhon39eAqzEC+/GP03oY4/MQg+ - gCDlEzkuOCybCHf+q766bve799L7Y5y5oRsHY1MrUCUwYF/tL7Sg7EYMsA== - -----END PUBLIC KEY-----`), - wantErr: true, - }, - "valid content and sig, but mismatching public key": { - content: []byte("This is some content to be signed!\n"), - signature: []byte("MEUCIQDzMN3yaiO9sxLGAaSA9YD8rLwzvOaZKWa/bzkcjImUFAIgXLLGzClYUd1dGbuEiY3O/g/eiwQYlyxqLQalxjFmz+8="), - publicKey: []byte(`-----BEGIN PUBLIC KEY----- - MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFARL653CK4xicoxqwr4M9A2A/3hz - hQaKKRsnjc2LITnxKYmQ4CYqTOAMfZ3agxpW/ndillUox4eDYcidZSXvWw== - -----END PUBLIC KEY-----`), - wantErr: true, - }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { assert := assert.New(t) - cosign := CosignVerifier{} - err := cosign.VerifySignature(tc.content, tc.signature, tc.publicKey) + cosign, err := NewCosignVerifier(tc.publicKey) + require.NoError(t, err) + err = cosign.VerifySignature(tc.content, tc.signature) if tc.wantErr { assert.Error(err) return