From 20c2ce9c55fc2d05bf01604c491edfe46e43e4dc Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Wed, 10 Apr 2024 17:14:30 -0400 Subject: [PATCH] Require inclusion proof for all bundle versions newer than v0.1 (#146) Signed-off-by: Cody Soyland --- cmd/conformance/main.go | 2 +- pkg/bundle/bundle.go | 58 +++++++++++++++----- pkg/bundle/bundle_test.go | 96 +++++++++++++++++++++++++++++++++ pkg/bundle/signature_content.go | 4 +- 4 files changed, 145 insertions(+), 15 deletions(-) create mode 100644 pkg/bundle/bundle_test.go diff --git a/cmd/conformance/main.go b/cmd/conformance/main.go index 1fe536b0..58f49683 100644 --- a/cmd/conformance/main.go +++ b/cmd/conformance/main.go @@ -154,7 +154,7 @@ func main() { } pb := protobundle.Bundle{ - MediaType: bundle.SigstoreBundleMediaType01, + MediaType: "application/vnd.dev.sigstore.bundle+json;version=0.1", VerificationMaterial: &protobundle.VerificationMaterial{ Content: &protobundle.VerificationMaterial_X509CertificateChain{ X509CertificateChain: &protocommon.X509CertificateChain{ diff --git a/pkg/bundle/bundle.go b/pkg/bundle/bundle.go index bd54575c..e0fd5e53 100644 --- a/pkg/bundle/bundle.go +++ b/pkg/bundle/bundle.go @@ -33,14 +33,8 @@ import ( "github.com/sigstore/sigstore-go/pkg/verify" ) -const SigstoreBundleMediaType01 = "application/vnd.dev.sigstore.bundle+json;version=0.1" -const SigstoreBundleMediaType02 = "application/vnd.dev.sigstore.bundle+json;version=0.2" -const SigstoreBundleMediaType03Legacy = "application/vnd.dev.sigstore.bundle+json;version=0.3" -const SigstoreBundleMediaType03 = "application/vnd.dev.sigstore.bundle.v0.3+json" -const IntotoMediaType = "application/vnd.in-toto+json" - var ErrValidation = errors.New("validation error") -var ErrIncorrectMediaType = fmt.Errorf("%w: unsupported media type", ErrValidation) +var ErrUnsupportedMediaType = fmt.Errorf("%w: unsupported media type", ErrValidation) var ErrMissingVerificationMaterial = fmt.Errorf("%w: missing verification material", ErrValidation) var ErrUnimplemented = errors.New("unimplemented") var ErrInvalidAttestation = fmt.Errorf("%w: invalid attestation", ErrValidation) @@ -74,33 +68,71 @@ func NewProtobufBundle(pbundle *protobundle.Bundle) (*ProtobufBundle, error) { } func (b *ProtobufBundle) validate() error { + bundleVersion, err := getBundleVersion(b.Bundle.MediaType) + if err != nil { + return fmt.Errorf("error getting bundle version: %w", err) + } + + // if bundle version is < 0.1, return error + if semver.Compare(bundleVersion, "v0.1") < 0 { + return fmt.Errorf("%w: bundle version %s is not supported", ErrUnsupportedMediaType, bundleVersion) + } + + // fetch tlog entries, as next check needs to check them for inclusion proof/promise entries, err := b.TlogEntries() if err != nil { return err } - switch b.Bundle.MediaType { - case SigstoreBundleMediaType01: + // if bundle version == v0.1, require inclusion promise + if semver.Compare(bundleVersion, "v0.1") == 0 { if len(entries) > 0 && !b.hasInclusionPromise { return errors.New("inclusion promises missing in bundle (required for bundle v0.1)") } - case SigstoreBundleMediaType02: + } else { + // if bundle version >= v0.2, require inclusion proof if len(entries) > 0 && !b.hasInclusionProof { return errors.New("inclusion proof missing in bundle (required for bundle v0.2)") } - case SigstoreBundleMediaType03, SigstoreBundleMediaType03Legacy: + } + + // if bundle version >= v0.3, require verification material to not be X.509 certificate chain (only single certificate is allowed) + if semver.Compare(bundleVersion, "v0.3") >= 0 { certs := b.Bundle.VerificationMaterial.GetX509CertificateChain() if certs != nil { return errors.New("verification material cannot be X.509 certificate chain (for bundle v0.3)") } - default: - return ErrIncorrectMediaType + } + + // if bundle version is newer than v0.3, return error as this version is not supported + if semver.Compare(bundleVersion, "v0.3") > 0 { + return fmt.Errorf("%w: bundle version %s is not yet supported", ErrUnsupportedMediaType, bundleVersion) } return nil } +func getBundleVersion(mediaType string) (string, error) { + switch mediaType { + case "application/vnd.dev.sigstore.bundle+json;version=0.1": + return "v0.1", nil + case "application/vnd.dev.sigstore.bundle+json;version=0.2": + return "v0.2", nil + case "application/vnd.dev.sigstore.bundle+json;version=0.3": + return "v0.3", nil + } + if strings.HasPrefix(mediaType, "application/vnd.dev.sigstore.bundle.v") && strings.HasSuffix(mediaType, "+json") { + version := strings.TrimPrefix(mediaType, "application/vnd.dev.sigstore.bundle.") + version = strings.TrimSuffix(version, "+json") + if semver.IsValid(version) { + return version, nil + } + return "", fmt.Errorf("%w: invalid bundle version: %s", ErrUnsupportedMediaType, version) + } + return "", fmt.Errorf("%w: %s", ErrUnsupportedMediaType, mediaType) +} + func LoadJSONFromPath(path string) (*ProtobufBundle, error) { var bundle ProtobufBundle bundle.Bundle = new(protobundle.Bundle) diff --git a/pkg/bundle/bundle_test.go b/pkg/bundle/bundle_test.go new file mode 100644 index 00000000..f1d94196 --- /dev/null +++ b/pkg/bundle/bundle_test.go @@ -0,0 +1,96 @@ +// Copyright 2023 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bundle + +import ( + "fmt" + "testing" +) + +func Test_getBundleVersion(t *testing.T) { + tests := []struct { + mediaType string + want string + wantErr bool + }{ + { + mediaType: "application/vnd.dev.sigstore.bundle+json;version=0.1", + want: "v0.1", + wantErr: false, + }, + { + mediaType: "application/vnd.dev.sigstore.bundle+json;version=0.2", + want: "v0.2", + wantErr: false, + }, + { + mediaType: "application/vnd.dev.sigstore.bundle+json;version=0.3", + want: "v0.3", + wantErr: false, + }, + { + mediaType: "application/vnd.dev.sigstore.bundle.v0.3+json", + want: "v0.3", + wantErr: false, + }, + { + mediaType: "application/vnd.dev.sigstore.bundle.v0.3.1+json", + want: "v0.3.1", + wantErr: false, + }, + { + mediaType: "application/vnd.dev.sigstore.bundle.v0.4+json", + want: "v0.4", + wantErr: false, + }, + { + mediaType: "application/vnd.dev.sigstore.bundle+json", + want: "", + wantErr: true, + }, + { + mediaType: "garbage", + want: "", + wantErr: true, + }, + { + mediaType: "application/vnd.dev.sigstore.bundle.vgarbage+json", + want: "", + wantErr: true, + }, + { + mediaType: "application/vnd.dev.sigstore.bundle.v0.3.1.1.1.1+json", + want: "", + wantErr: true, + }, + { + mediaType: "", + want: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("mediatype:%s", tt.mediaType), func(t *testing.T) { + got, err := getBundleVersion(tt.mediaType) + if (err != nil) != tt.wantErr { + t.Errorf("getBundleVersion() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("getBundleVersion() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/bundle/signature_content.go b/pkg/bundle/signature_content.go index 8fd7c761..187c14e6 100644 --- a/pkg/bundle/signature_content.go +++ b/pkg/bundle/signature_content.go @@ -23,6 +23,8 @@ import ( "github.com/sigstore/sigstore-go/pkg/verify" ) +const IntotoMediaType = "application/vnd.in-toto+json" + type MessageSignature struct { digest []byte digestAlgorithm string @@ -51,7 +53,7 @@ type Envelope struct { func (e *Envelope) Statement() (*in_toto.Statement, error) { if e.PayloadType != IntotoMediaType { - return nil, ErrIncorrectMediaType + return nil, ErrUnsupportedMediaType } var statement *in_toto.Statement