From 945036fe55544cc0c9f60e1d6499fed57e56dc56 Mon Sep 17 00:00:00 2001 From: Dan Lorenc Date: Thu, 23 Dec 2021 20:48:36 -0600 Subject: [PATCH] Fix semantic bugs in attestation verifification. DSSE requires that the payload types match, but the Attestation spec requires that the payload type be the specific in-toto one. We don't actually do anything with the content here other than dump it in plain text, but semantically we're calling these attestations in the CLI so we should enforce that part of the specification as well. Signed-off-by: Dan Lorenc --- pkg/cosign/verify.go | 14 ++++-- pkg/cosign/verify_test.go | 91 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 pkg/cosign/verify_test.go diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 3b94ec083e6..8d78e356aa1 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -31,6 +31,7 @@ import ( "github.com/sigstore/cosign/pkg/blob" "github.com/sigstore/cosign/pkg/oci/static" + "github.com/sigstore/cosign/pkg/types" "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer" "github.com/google/go-containerregistry/pkg/name" @@ -106,8 +107,12 @@ func verifyOCISignature(ctx context.Context, verifier signature.Verifier, sig oc return verifier.VerifySignature(bytes.NewReader(signature), bytes.NewReader(payload), options.WithContext(ctx)) } -func verifyOCIAttestation(_ context.Context, verifier signature.Verifier, att oci.Signature) error { - // TODO(dekkagaijin): plumb through context +// For unit testing +type payloader interface { + Payload() ([]byte, error) +} + +func verifyOCIAttestation(_ context.Context, verifier signature.Verifier, att payloader) error { payload, err := att.Payload() if err != nil { return err @@ -115,9 +120,12 @@ func verifyOCIAttestation(_ context.Context, verifier signature.Verifier, att oc env := ssldsse.Envelope{} if err := json.Unmarshal(payload, &env); err != nil { - return nil + return err } + if env.PayloadType != types.IntotoPayloadType { + return fmt.Errorf("invalid payloadType %s on envelepe. Expected %s", env.PayloadType, types.IntotoPayloadType) + } dssev, err := ssldsse.NewEnvelopeVerifier(&dsse.VerifierAdapter{SignatureVerifier: verifier}) if err != nil { return err diff --git a/pkg/cosign/verify_test.go b/pkg/cosign/verify_test.go new file mode 100644 index 00000000000..9f39a292ac6 --- /dev/null +++ b/pkg/cosign/verify_test.go @@ -0,0 +1,91 @@ +// Copyright 2021 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 cosign + +import ( + "context" + "crypto" + "encoding/base64" + "encoding/json" + "io" + "testing" + + "github.com/in-toto/in-toto-golang/in_toto" + "github.com/pkg/errors" + "github.com/secure-systems-lab/go-securesystemslib/dsse" + "github.com/sigstore/cosign/pkg/types" + "github.com/sigstore/sigstore/pkg/signature" +) + +type mockVerifier struct { + shouldErr bool +} + +func (m *mockVerifier) PublicKey(opts ...signature.PublicKeyOption) (crypto.PublicKey, error) { + return nil, nil +} + +func (m *mockVerifier) VerifySignature(signature, message io.Reader, opts ...signature.VerifyOption) error { + if m.shouldErr { + return errors.New("failure") + } + return nil +} + +var _ signature.Verifier = (*mockVerifier)(nil) + +type mockAttestation struct { + payload interface{} +} + +var _ payloader = (*mockAttestation)(nil) + +func (m *mockAttestation) Annotations() (map[string]string, error) { + return nil, nil +} + +func (m *mockAttestation) Payload() ([]byte, error) { + return json.Marshal(m.payload) +} +func Test_verifyOCIAttestation(t *testing.T) { + stmt, err := json.Marshal(in_toto.ProvenanceStatement{}) + if err != nil { + t.Fatal(err) + } + valid := map[string]interface{}{ + "payloadType": types.IntotoPayloadType, + "payload": stmt, + "signatures": []dsse.Signature{{Sig: base64.StdEncoding.EncodeToString([]byte("foobar"))}}, + } + // Should Verify + if err := verifyOCIAttestation(context.TODO(), &mockVerifier{}, &mockAttestation{payload: valid}); err != nil { + t.Errorf("verifyOCIAttestation() error = %v", err) + } + + invalid := map[string]interface{}{ + "payloadType": "not valid type", + "payload": stmt, + "signatures": []dsse.Signature{{Sig: base64.StdEncoding.EncodeToString([]byte("foobar"))}}, + } + + // Should Not Verify + if err := verifyOCIAttestation(context.TODO(), &mockVerifier{}, &mockAttestation{payload: invalid}); err == nil { + t.Error("verifyOCIAttestation() expected invalid payload type error, got nil") + } + + if err := verifyOCIAttestation(context.TODO(), &mockVerifier{shouldErr: true}, &mockAttestation{payload: valid}); err == nil { + t.Error("verifyOCIAttestation() expected invalid payload type error, got nil") + } +}