From 303abb06ffaac26f12a362ad686a48904c4879c9 Mon Sep 17 00:00:00 2001 From: Hayden Blauzvern Date: Wed, 13 Apr 2022 04:51:44 +0000 Subject: [PATCH] Add intermediate CA certificate pool for Fulcio This separates roots and intermediates from the TUF targets. This will be used to configure the default intermediate certificates when none are found. In particular, this will be used by verify-blob when fetching an entry from Rekor. An intermediate CA certificate will be added to the v3 TUF root. Signed-off-by: Hayden Blauzvern --- cmd/cosign/cli/fulcio/fulcio.go | 4 ++ .../cli/fulcio/fulcioroots/fulcioroots.go | 62 ++++++++++++++----- .../fulcio/fulcioroots/fulcioroots_test.go | 24 +++++-- cmd/cosign/cli/verify/verify_blob.go | 7 ++- 4 files changed, 74 insertions(+), 23 deletions(-) diff --git a/cmd/cosign/cli/fulcio/fulcio.go b/cmd/cosign/cli/fulcio/fulcio.go index 338cdd4d1ed..d7eedabafb8 100644 --- a/cmd/cosign/cli/fulcio/fulcio.go +++ b/cmd/cosign/cli/fulcio/fulcio.go @@ -157,6 +157,10 @@ func GetRoots() *x509.CertPool { return fulcioroots.Get() } +func GetIntermediates() *x509.CertPool { + return fulcioroots.GetIntermediates() +} + func NewClient(fulcioURL string) (api.Client, error) { fulcioServer, err := url.Parse(fulcioURL) if err != nil { diff --git a/cmd/cosign/cli/fulcio/fulcioroots/fulcioroots.go b/cmd/cosign/cli/fulcio/fulcioroots/fulcioroots.go index 0485721b379..9b48f2b2d74 100644 --- a/cmd/cosign/cli/fulcio/fulcioroots/fulcioroots.go +++ b/cmd/cosign/cli/fulcio/fulcioroots/fulcioroots.go @@ -16,6 +16,7 @@ package fulcioroots import ( + "bytes" "context" "crypto/x509" "os" @@ -23,11 +24,13 @@ import ( "github.com/pkg/errors" "github.com/sigstore/cosign/pkg/cosign/tuf" + "github.com/sigstore/sigstore/pkg/cryptoutils" ) var ( - rootsOnce sync.Once - roots *x509.CertPool + rootsOnce sync.Once + roots *x509.CertPool + intermediates *x509.CertPool ) // This is the root in the fulcio project. @@ -43,7 +46,7 @@ const ( func Get() *x509.CertPool { rootsOnce.Do(func() { var err error - roots, err = initRoots() + roots, intermediates, err = initRoots() if err != nil { panic(err) } @@ -51,37 +54,68 @@ func Get() *x509.CertPool { return roots } -func initRoots() (*x509.CertPool, error) { - cp := x509.NewCertPool() +func GetIntermediates() *x509.CertPool { + rootsOnce.Do(func() { + var err error + roots, intermediates, err = initRoots() + if err != nil { + panic(err) + } + }) + return intermediates +} + +func initRoots() (*x509.CertPool, *x509.CertPool, error) { + rootPool := x509.NewCertPool() + intermediatePool := x509.NewCertPool() + rootEnv := os.Getenv(altRoot) if rootEnv != "" { raw, err := os.ReadFile(rootEnv) if err != nil { - return nil, errors.Wrap(err, "error reading root PEM file") + return nil, nil, errors.Wrap(err, "error reading root PEM file") + } + certs, err := cryptoutils.UnmarshalCertificatesFromPEM(raw) + if err != nil { + return nil, nil, errors.Wrap(err, "error unmarshalling certificates") } - if !cp.AppendCertsFromPEM(raw) { - return nil, errors.New("error creating root cert pool") + for _, cert := range certs { + // root certificates are self-signed + if bytes.Equal(cert.RawSubject, cert.RawIssuer) { + rootPool.AddCert(cert) + } else { + intermediatePool.AddCert(cert) + } } } else { tufClient, err := tuf.NewFromEnv(context.Background()) if err != nil { - return nil, errors.Wrap(err, "initializing tuf") + return nil, nil, errors.Wrap(err, "initializing tuf") } defer tufClient.Close() // Retrieve from the embedded or cached TUF root. If expired, a network // call is made to update the root. targets, err := tufClient.GetTargetsByMeta(tuf.Fulcio, []string{fulcioTargetStr, fulcioV1TargetStr}) if err != nil { - return nil, errors.New("error getting targets") + return nil, nil, errors.New("error getting targets") } if len(targets) == 0 { - return nil, errors.New("none of the Fulcio roots have been found") + return nil, nil, errors.New("none of the Fulcio roots have been found") } for _, t := range targets { - if !cp.AppendCertsFromPEM(t.Target) { - return nil, errors.New("error creating root cert pool") + certs, err := cryptoutils.UnmarshalCertificatesFromPEM(t.Target) + if err != nil { + return nil, nil, errors.Wrap(err, "error unmarshalling certificates") + } + for _, cert := range certs { + // root certificates are self-signed + if bytes.Equal(cert.RawSubject, cert.RawIssuer) { + rootPool.AddCert(cert) + } else { + intermediatePool.AddCert(cert) + } } } } - return cp, nil + return rootPool, intermediatePool, nil } diff --git a/cmd/cosign/cli/fulcio/fulcioroots/fulcioroots_test.go b/cmd/cosign/cli/fulcio/fulcioroots/fulcioroots_test.go index fba782e2d45..9071db8cd4a 100644 --- a/cmd/cosign/cli/fulcio/fulcioroots/fulcioroots_test.go +++ b/cmd/cosign/cli/fulcio/fulcioroots/fulcioroots_test.go @@ -23,23 +23,35 @@ import ( ) func TestGetFulcioRoots(t *testing.T) { - rootCert, _, _ := test.GenerateRootCa() - pemCert, _ := cryptoutils.MarshalCertificateToPEM(rootCert) + rootCert, rootPriv, _ := test.GenerateRootCa() + rootPemCert, _ := cryptoutils.MarshalCertificateToPEM(rootCert) + subCert, _, _ := test.GenerateSubordinateCa(rootCert, rootPriv) + subPemCert, _ := cryptoutils.MarshalCertificateToPEM(subCert) + + var chain []byte + chain = append(chain, subPemCert...) + chain = append(chain, rootPemCert...) tmpCertFile, err := os.CreateTemp(t.TempDir(), "cosign_fulcio_root_*.cert") if err != nil { t.Fatalf("failed to create temp cert file: %v", err) } defer tmpCertFile.Close() - if _, err := tmpCertFile.Write(pemCert); err != nil { + if _, err := tmpCertFile.Write(chain); err != nil { t.Fatalf("failed to write cert file: %v", err) } os.Setenv("SIGSTORE_ROOT_FILE", tmpCertFile.Name()) defer os.Unsetenv("SIGSTORE_ROOT_FILE") - certPool := Get() + rootCertPool := Get() + // ignore deprecation error because certificates do not contain from SystemCertPool + if len(rootCertPool.Subjects()) != 1 { // nolint:staticcheck + t.Errorf("expected 1 root certificate, got 0") + } + + subCertPool := GetIntermediates() // ignore deprecation error because certificates do not contain from SystemCertPool - if len(certPool.Subjects()) == 0 { // nolint:staticcheck - t.Errorf("expected 1 or more certificates, got 0") + if len(subCertPool.Subjects()) != 1 { // nolint:staticcheck + t.Errorf("expected 1 intermediate certificate, got 0") } } diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go index 8cc570be31f..dcc783e0a54 100644 --- a/cmd/cosign/cli/verify/verify_blob.go +++ b/cmd/cosign/cli/verify/verify_blob.go @@ -198,9 +198,10 @@ func verifySigByUUID(ctx context.Context, ko sign.KeyOpts, rClient *client.Rekor } co := &cosign.CheckOpts{ - RootCerts: fulcio.GetRoots(), - CertEmail: certEmail, - CertOidcIssuer: certOidcIssuer, + RootCerts: fulcio.GetRoots(), + IntermediateCerts: fulcio.GetIntermediates(), + CertEmail: certEmail, + CertOidcIssuer: certOidcIssuer, } cert := certs[0] verifier, err := cosign.ValidateAndUnpackCert(cert, co)