diff --git a/pkg/cosign/tuf/testutils.go b/pkg/cosign/tuf/testutils.go new file mode 100644 index 00000000000..729af872db6 --- /dev/null +++ b/pkg/cosign/tuf/testutils.go @@ -0,0 +1,126 @@ +// +// 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 tuf + +import ( + "context" + "crypto/x509" + "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/options" + "github.com/theupdateframework/go-tuf" +) + +type TestSigstoreRoot struct { + Rekor signature.Verifier + FulcioCertificate *x509.Certificate + // TODO: Include a CTFE key if/when cosign verifies SCT. +} + +// This creates a new sigstore TUF repo whose signers can be used to create dynamic +// signed Rekor entries. +func NewSigstoreTufRepo(t *testing.T, root TestSigstoreRoot) (tuf.LocalStore, *tuf.Repo) { + td := t.TempDir() + ctx := context.Background() + remote := tuf.FileSystemStore(td, nil) + r, err := tuf.NewRepo(remote) + if err != nil { + t.Error(err) + } + if err := r.Init(false); err != nil { + t.Error(err) + } + + for _, role := range []string{"root", "targets", "snapshot", "timestamp"} { + if _, err := r.GenKey(role); err != nil { + t.Error(err) + } + } + targetsPath := filepath.Join(td, "staged", "targets") + if err := os.MkdirAll(filepath.Dir(targetsPath), 0755); err != nil { + t.Error(err) + } + // Add the rekor key target + pk, err := root.Rekor.PublicKey(options.WithContext(ctx)) + if err != nil { + t.Error(err) + } + b, err := x509.MarshalPKIXPublicKey(pk) + if err != nil { + t.Error(err) + } + rekorPath := "rekor.pub" + rekorData := cryptoutils.PEMEncode(cryptoutils.PublicKeyPEMType, b) + if err := ioutil.WriteFile(filepath.Join(targetsPath, rekorPath), rekorData, 0600); err != nil { + t.Error(err) + } + scmRekor, err := json.Marshal(&sigstoreCustomMetadata{Sigstore: customMetadata{Usage: Rekor, Status: Active}}) + if err != nil { + t.Error(err) + } + if err := r.AddTarget("rekor.pub", scmRekor); err != nil { + t.Error(err) + } + // Add Fulcio Certificate information. + fulcioPath := "fulcio.crt.pem" + fulcioData := cryptoutils.PEMEncode(cryptoutils.CertificatePEMType, root.FulcioCertificate.Raw) + if err := ioutil.WriteFile(filepath.Join(targetsPath, fulcioPath), fulcioData, 0600); err != nil { + t.Error(err) + } + scmFulcio, err := json.Marshal(&sigstoreCustomMetadata{Sigstore: customMetadata{Usage: Fulcio, Status: Active}}) + if err != nil { + t.Error(err) + } + if err := r.AddTarget(fulcioPath, scmFulcio); err != nil { + t.Error(err) + } + if err := r.Snapshot(); err != nil { + t.Error(err) + } + if err := r.Timestamp(); err != nil { + t.Error(err) + } + if err := r.Commit(); err != nil { + t.Error(err) + } + // Serve remote repository. + s := httptest.NewServer(http.FileServer(http.Dir(filepath.Join(td, "repository")))) + defer s.Close() + + // Initialize with custom root. + tufRoot := t.TempDir() + t.Setenv("TUF_ROOT", tufRoot) + meta, err := remote.GetMeta() + if err != nil { + t.Error(err) + } + rootBytes, ok := meta["root.json"] + if !ok { + t.Error(err) + } + if err := Initialize(ctx, s.URL, rootBytes); err != nil { + t.Error(err) + } + return remote, r +} diff --git a/pkg/cosign/verify_test.go b/pkg/cosign/verify_test.go index 0ff80e7def6..bba5dfd06a9 100644 --- a/pkg/cosign/verify_test.go +++ b/pkg/cosign/verify_test.go @@ -15,8 +15,10 @@ package cosign import ( + "bytes" "context" "crypto" + "crypto/elliptic" "crypto/rand" "crypto/sha256" "crypto/x509" @@ -26,15 +28,22 @@ import ( "io" "strings" "testing" + "time" + "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer" + "github.com/go-openapi/strfmt" v1 "github.com/google/go-containerregistry/pkg/v1" "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/cosign/bundle" + ctuf "github.com/sigstore/cosign/pkg/cosign/tuf" "github.com/sigstore/cosign/pkg/oci/static" "github.com/sigstore/cosign/pkg/types" "github.com/sigstore/cosign/test" + rtypes "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/options" "github.com/stretchr/testify/require" ) @@ -167,8 +176,54 @@ func TestVerifyImageSignatureMultipleSubs(t *testing.T) { } } +func signEntry(ctx context.Context, t *testing.T, signer signature.Signer, entry bundle.RekorPayload) []byte { + payload, err := json.Marshal(entry) + if err != nil { + t.Fatalf("marshalling error: %v", err) + } + canonicalized, err := jsoncanonicalizer.Transform(payload) + if err != nil { + t.Fatalf("canonicalizing error: %v", err) + } + signature, err := signer.SignMessage(bytes.NewReader(canonicalized), options.WithContext(ctx)) + if err != nil { + t.Fatalf("signing error: %v", err) + } + return signature +} + +func CreateTestBundle(ctx context.Context, t *testing.T, rekor signature.Signer, leaf []byte) *bundle.RekorBundle { + // generate log ID according to rekor public key + pk, _ := rekor.PublicKey(nil) + keyID, _ := getLogID(pk) + pyld := bundle.RekorPayload{ + Body: base64.StdEncoding.EncodeToString(leaf), + IntegratedTime: time.Now().Unix(), + LogIndex: 693591, + LogID: keyID, + } + // Sign with root. + signature := signEntry(ctx, t, rekor, pyld) + b := &bundle.RekorBundle{ + SignedEntryTimestamp: strfmt.Base64(signature), + Payload: pyld, + } + return b +} + func TestVerifyImageSignatureWithNoChain(t *testing.T) { + ctx := context.Background() rootCert, rootKey, _ := test.GenerateRootCa() + sv, _, err := signature.NewECDSASignerVerifier(elliptic.P256(), rand.Reader, crypto.SHA256) + if err != nil { + t.Fatalf("creating signer: %v", err) + } + testSigstoreRoot := ctuf.TestSigstoreRoot{ + Rekor: sv, + FulcioCertificate: rootCert, + } + _, _ = ctuf.NewSigstoreTufRepo(t, testSigstoreRoot) + leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", rootCert, rootKey) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) @@ -179,14 +234,21 @@ func TestVerifyImageSignatureWithNoChain(t *testing.T) { h := sha256.Sum256(payload) signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) - ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, []byte{})) + // Create a fake bundle + pe, _ := proposedEntry(base64.StdEncoding.EncodeToString(signature), payload, pemLeaf) + entry, _ := rtypes.NewEntry(pe[0]) + leaf, _ := entry.Canonicalize(ctx) + rekorBundle := CreateTestBundle(ctx, t, sv, leaf) + + opts := []static.Option{static.WithCertChain(pemLeaf, []byte{}), static.WithBundle(rekorBundle)} + ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), opts...) + verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{RootCerts: rootPool}) if err != nil { t.Fatalf("unexpected error while verifying signature, expected no error, got %v", err) } - // TODO: Create fake bundle and test verification - if verified == true { - t.Fatalf("expected verified=false, got verified=true") + if verified == false { + t.Fatalf("expected verified=true, got verified=false") } }