diff --git a/test/e2e_test.go b/test/e2e_test.go index d6205305b2d..ed393958ad1 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -131,6 +131,62 @@ func TestSignVerify(t *testing.T) { mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar", "baz": "bat"}, "", false), t) } +func TestSignVerifyCertBundle(t *testing.T) { + td := t.TempDir() + err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) + if err != nil { + t.Fatal(err) + } + + repo, stop := reg(t) + defer stop() + + imgName := path.Join(repo, "cosign-e2e") + + _, _, cleanup := mkimage(t, imgName) + defer cleanup() + + caCertFile, _ /* caPrivKeyFile */, caIntermediateCertFile, _ /* caIntermediatePrivKeyFile */, certFile, privKeyFile, pubkeyFile, err := generateCertificateBundleFiles(td, true, "foobar") + + ctx := context.Background() + // Verify should fail at first + must(verifyCertBundle(pubkeyFile, caCertFile, caIntermediateCertFile, certFile, imgName, true, nil, "", false), t) + // So should download + mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) + + // Now sign the image + ko := options.KeyOpts{ + KeyRef: privKeyFile, + PassFunc: passFunc, + RekorURL: rekorURL, + SkipConfirmation: true, + } + so := options.SignOptions{ + Upload: true, + TlogUpload: true, + } + must(sign.SignCmd(ro, ko, so, []string{imgName}), t) + + // Now verify and download should work! + must(verifyCertBundle(pubkeyFile, caCertFile, caIntermediateCertFile, certFile, imgName, true, nil, "", false), t) + must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) + + // Look for a specific annotation + mustErr(verifyCertBundle(pubkeyFile, caCertFile, caIntermediateCertFile, certFile, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t) + + so.AnnotationOptions = options.AnnotationOptions{ + Annotations: []string{"foo=bar"}, + } + // Sign the image with an annotation + must(sign.SignCmd(ro, ko, so, []string{imgName}), t) + + // It should match this time. + must(verifyCertBundle(pubkeyFile, caCertFile, caIntermediateCertFile, certFile, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t) + + // But two doesn't work + mustErr(verifyCertBundle(pubkeyFile, caCertFile, caIntermediateCertFile, certFile, imgName, true, map[string]interface{}{"foo": "bar", "baz": "bat"}, "", false), t) +} + func TestSignVerifyClean(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) diff --git a/test/helpers.go b/test/helpers.go index 36bf24dbd8b..fb41aacbb9a 100644 --- a/test/helpers.go +++ b/test/helpers.go @@ -87,6 +87,28 @@ var verify = func(keyRef, imageRef string, checkClaims bool, annotations map[str return cmd.Exec(context.Background(), args) } +var verifyCertBundle = func(keyRef, caCertFile, caIntermediates, certFile, imageRef string, checkClaims bool, annotations map[string]interface{}, attachment string, skipTlogVerify bool) error { + cmd := cliverify.VerifyCommand{ + KeyRef: keyRef, + RekorURL: rekorURL, + CheckClaims: checkClaims, + Annotations: sigs.AnnotationsMap{Annotations: annotations}, + Attachment: attachment, + HashAlgorithm: crypto.SHA256, + MaxWorkers: 10, + IgnoreTlog: skipTlogVerify, + CertVerifyOptions: options.CertVerifyOptions{ + Cert: certFile, + CAIntermediates: caIntermediates, + CARoots: caCertFile, + }, + } + + args := []string{imageRef} + + return cmd.Exec(context.Background(), args) +} + var verifyTSA = func(keyRef, imageRef string, checkClaims bool, annotations map[string]interface{}, attachment, tsaCertChain string, skipTlogVerify bool) error { cmd := cliverify.VerifyCommand{ KeyRef: keyRef, @@ -453,13 +475,77 @@ func downloadAndSetEnv(t *testing.T, url, envVar, dir string) error { t.Setenv(envVar, f.Name()) return nil } -func GenerateCertificateBundle(td string, genIntermediate bool, outputSuffix string) ( + +func generateCertificateBundleFiles(td string, genIntermediate bool, outputSuffix string) ( + caCertFile string, + caPrivKeyFile string, + caIntermediateCertFile string, + caIntermediatePrivKeyFile string, + certFile string, + keyFile string, + pubKeyFile string, + err error, +) { + caCertBuf, caPrivKeyBuf, caIntermediateCertBuf, caIntermediatePrivKeyBuf, certBuf, keyBuf, pubkey, err := generateCertificateBundle(genIntermediate) + if err != nil { + err = fmt.Errorf("error generating certificate bundle: %w", err) + return + } + err = os.WriteFile(filepath.Join(td, fmt.Sprintf("caCert%s.pem", outputSuffix)), caCertBuf.Bytes(), 0600) + if err != nil { + err = fmt.Errorf("error writing caCert to file: %w", err) + return + } + err = os.WriteFile(filepath.Join(td, fmt.Sprintf("caPrivKey%s.pem", outputSuffix)), caPrivKeyBuf.Bytes(), 0600) + if err != nil { + err = fmt.Errorf("error writing caPrivKey to file: %w", err) + return + } + if genIntermediate { + err = os.WriteFile(filepath.Join(td, fmt.Sprintf("caIntermediateCert%s.pem", outputSuffix)), caIntermediateCertBuf.Bytes(), 0600) + if err != nil { + err = fmt.Errorf("error writing caIntermediateCert to file: %w", err) + return + } + err = os.WriteFile(filepath.Join(td, fmt.Sprintf("caIntermediatePrivKey%s.pem", outputSuffix)), caIntermediatePrivKeyBuf.Bytes(), 0600) + if err != nil { + err = fmt.Errorf("error writing caIntermediatePrivKey to file: %w", err) + return + } + } + err = os.WriteFile(filepath.Join(td, fmt.Sprintf("cert%s.pem", outputSuffix)), certBuf.Bytes(), 0600) + if err != nil { + err = fmt.Errorf("error writing cert to file: %w", err) + return + } + err = os.WriteFile(filepath.Join(td, fmt.Sprintf("key%s.pem", outputSuffix)), keyBuf.Bytes(), 0600) + if err != nil { + err = fmt.Errorf("error writing key to file: %w", err) + return + } + // write the public key to a file + pubKeyFile = filepath.Join(td, fmt.Sprintf("pubkey%s.pem", outputSuffix)) + pubKeyBuf := &bytes.Buffer{} + pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubkey) + if err != nil { + err = fmt.Errorf("error marshalling public key: %w", err) + return + } + err = pem.Encode(pubKeyBuf, &pem.Block{ + Type: "PUBLIC KEY", + Bytes: pubKeyBytes, + }) + return +} + +func generateCertificateBundle(genIntermediate bool) ( caCertBuf *bytes.Buffer, caPrivKeyBuf *bytes.Buffer, caIntermediateCertBuf *bytes.Buffer, caIntermediatePrivKeyBuf *bytes.Buffer, certBuf *bytes.Buffer, keyBuf *bytes.Buffer, + pubkeyBuf *bytes.Buffer, err error, ) { // set up our CA certificate @@ -488,7 +574,7 @@ func GenerateCertificateBundle(td string, genIntermediate bool, outputSuffix str if err != nil { log.Fatal(err) } - + pubkey := &caPrivKey.PublicKey // create the CA caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey) if err != nil { @@ -510,7 +596,20 @@ func GenerateCertificateBundle(td string, genIntermediate bool, outputSuffix str Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey), }) if err != nil { - log.Fatalf("unable to create encode to buffer: %v", err) //nolint:gocritic + log.Fatalf("unable to PEM encode private key to buffer: %v", err) //nolint:gocritic + } + pubkeyBuf = &bytes.Buffer{} + // PEM encode to pubkeyBuf the public key of caPrivKey + pubkeyBytes, err := x509.MarshalPKIXPublicKey(pubkey) + if err != nil { + log.Fatalf("failed to marshal public key: %v", err) + } + err = pem.Encode(pubkeyBuf, &pem.Block{ + Type: "PUBLIC KEY", + Bytes: pubkeyBytes, + }) + if err != nil { + log.Fatalf("failed to PME-encode public key to buffer: %v", err) } // generate intermediate CA if requested @@ -562,7 +661,7 @@ func GenerateCertificateBundle(td string, genIntermediate bool, outputSuffix str Bytes: x509.MarshalPKCS1PrivateKey(caIntermediatePrivKey), }) if err != nil { - log.Fatalf("unable to create caIntermediatePrivKey %s: %v", td, err) //nolint:gocritic + log.Fatalf("unable to PEM encode caIntermediatePrivKey: %v", err) } } // set up our server certificate @@ -611,13 +710,5 @@ func GenerateCertificateBundle(td string, genIntermediate bool, outputSuffix str log.Fatalf("failed to encode cert: %v", err) } - keyBuf = &bytes.Buffer{} - err = pem.Encode(keyBuf, &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), - }) - if err != nil { - log.Fatalf("failed to encode private key to buffer: %v", err) - } - return caCertBuf, caPrivKeyBuf, caIntermediateCertBuf, caIntermediatePrivKeyBuf, certBuf, keyBuf, nil + return caCertBuf, caPrivKeyBuf, caIntermediateCertBuf, caIntermediatePrivKeyBuf, certBuf, keyBuf, pubkeyBuf, nil } diff --git a/test/helpers_test.go b/test/helpers_test.go index 28bb35a2f0d..a40588a2f18 100644 --- a/test/helpers_test.go +++ b/test/helpers_test.go @@ -5,8 +5,24 @@ package test import "testing" func TestGenerateCertificateBundle(t *testing.T) { - _, _, _, _, _, _, err := GenerateCertificateBundle("functest", true, "foobar") - if err != nil { - t.Fatalf("Error generating certificate bundle: %v", err) + for _, test := range []struct { + name string + genIntermediate bool + }{ + { + name: "without intermediate", + genIntermediate: false, + }, + { + name: "with intermediate", + genIntermediate: true, + }, + } { + t.Run(test.name, func(t *testing.T) { + _, _, _, _, _, _, _, err := generateCertificateBundle(true) + if err != nil { + t.Fatalf("Error generating certificate bundle: %v", err) + } + }) } }