diff --git a/cmd/app/http_test.go b/cmd/app/http_test.go index 50fb760cf..123b81c50 100644 --- a/cmd/app/http_test.go +++ b/cmd/app/http_test.go @@ -18,6 +18,7 @@ package app import ( "context" "crypto" + "crypto/x509" "errors" "fmt" "net" @@ -112,6 +113,6 @@ type TrivialCertificateAuthority struct { func (tca *TrivialCertificateAuthority) CreateCertificate(context.Context, identity.Principal, crypto.PublicKey) (*ca.CodeSigningCertificate, error) { return nil, errors.New("CreateCertificate always fails for testing") } -func (tca *TrivialCertificateAuthority) Root(ctx context.Context) ([]byte, error) { - return []byte("not a certificate"), nil +func (tca *TrivialCertificateAuthority) TrustBundle(ctx context.Context) ([][]*x509.Certificate, error) { + return [][]*x509.Certificate{}, nil } diff --git a/pkg/ca/baseca/baseca.go b/pkg/ca/baseca/baseca.go index f3ce2d961..8ff8c51fb 100644 --- a/pkg/ca/baseca/baseca.go +++ b/pkg/ca/baseca/baseca.go @@ -28,7 +28,6 @@ import ( ctx509 "github.com/google/certificate-transparency-go/x509" "github.com/sigstore/fulcio/pkg/ca" "github.com/sigstore/fulcio/pkg/identity" - "github.com/sigstore/sigstore/pkg/cryptoutils" ) var ( @@ -140,7 +139,7 @@ func (bca *BaseCA) CreateCertificate(ctx context.Context, principal identity.Pri return ca.CreateCSCFromDER(finalCertBytes, certChain) } -func (bca *BaseCA) Root(ctx context.Context) ([]byte, error) { +func (bca *BaseCA) TrustBundle(ctx context.Context) ([][]*x509.Certificate, error) { certs, _ := bca.GetSignerWithChain() - return cryptoutils.MarshalCertificatesToPEM(certs) + return [][]*x509.Certificate{certs}, nil } diff --git a/pkg/ca/baseca/baseca_test.go b/pkg/ca/baseca/baseca_test.go index 943be6b52..a3e74b953 100644 --- a/pkg/ca/baseca/baseca_test.go +++ b/pkg/ca/baseca/baseca_test.go @@ -41,21 +41,20 @@ func TestBaseCARoot(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCA() subCert, _, _ := test.GenerateSubordinateCA(rootCert, rootKey) certChain := []*x509.Certificate{subCert, rootCert} - pemChain, err := cryptoutils.MarshalCertificatesToPEM(certChain) - if err != nil { - t.Fatalf("unexpected error marshalling cert chain: %v", err) - } bca := BaseCA{ SignerWithChain: &ca.SignerCerts{Certs: certChain, Signer: signer}, } - rootBytes, err := bca.Root(context.TODO()) + rootChains, err := bca.TrustBundle(context.TODO()) if err != nil { t.Fatalf("unexpected error reading root: %v", err) } + if len(rootChains) != 1 { + t.Fatalf("unexpected number of chains: %d", len(rootChains)) + } - if !reflect.DeepEqual(pemChain, rootBytes) { + if !reflect.DeepEqual(certChain, rootChains[0]) { t.Fatal("expected cert chains to be equivalent") } } diff --git a/pkg/ca/ca.go b/pkg/ca/ca.go index 0f7cefc80..31e02aa55 100644 --- a/pkg/ca/ca.go +++ b/pkg/ca/ca.go @@ -18,6 +18,7 @@ package ca import ( "context" "crypto" + "crypto/x509" "github.com/sigstore/fulcio/pkg/identity" ) @@ -26,5 +27,5 @@ import ( // fetching the CA trust bundle. type CertificateAuthority interface { CreateCertificate(context.Context, identity.Principal, crypto.PublicKey) (*CodeSigningCertificate, error) - Root(ctx context.Context) ([]byte, error) + TrustBundle(ctx context.Context) ([][]*x509.Certificate, error) } diff --git a/pkg/ca/googleca/v1/googleca.go b/pkg/ca/googleca/v1/googleca.go index 01a061e52..ba9582b1d 100644 --- a/pkg/ca/googleca/v1/googleca.go +++ b/pkg/ca/googleca/v1/googleca.go @@ -42,7 +42,7 @@ type CertAuthorityService struct { client *privateca.CertificateAuthorityClient // protected by once - cachedRoots []byte + cachedRoots [][]*x509.Certificate cachedRootsOnce sync.Once } @@ -136,27 +136,39 @@ func Req(parent string, pemBytes []byte, cert *x509.Certificate) (*privatecapb.C }, nil } -func (c *CertAuthorityService) Root(ctx context.Context) ([]byte, error) { - c.cachedRootsOnce.Do(func() { - var pems string - cas := c.client.ListCertificateAuthorities(ctx, &privatecapb.ListCertificateAuthoritiesRequest{ - Parent: c.parent, - }) - for { - c, done := cas.Next() - if done == iterator.Done { - break - } - if done != nil { - break - } - pems += strings.Join(c.PemCaCertificates, "") - } - c.cachedRoots = []byte(pems) +func (c *CertAuthorityService) TrustBundle(ctx context.Context) ([][]*x509.Certificate, error) { + // if we've already successfully fetched the CA info, just use the cached value + if c.cachedRoots != nil { + return c.cachedRoots, nil + } + + // fetch the latest values for the specified CA + var roots [][]*x509.Certificate + cas := c.client.ListCertificateAuthorities(ctx, &privatecapb.ListCertificateAuthoritiesRequest{ + Parent: c.parent, }) - if len(c.cachedRoots) == 0 { - return c.cachedRoots, fmt.Errorf("error fetching root certificates") + for { + ca, done := cas.Next() + if done == iterator.Done { + break + } else if done != nil { + // if the iterator returns an issue for some reason, exit + return [][]*x509.Certificate{}, done + } + // if we fail to parse the PEM content, return an error + caCerts, err := cryptoutils.LoadCertificatesFromPEM(strings.NewReader(strings.Join(ca.PemCaCertificates, ""))) + if err != nil { + return [][]*x509.Certificate{}, fmt.Errorf("failed parsing PemCACertificates response: %w", err) + } + if len(roots) == 0 { + return [][]*x509.Certificate{}, fmt.Errorf("error fetching root certificates") + } + roots = append(roots, caCerts) } + c.cachedRootsOnce.Do(func() { + c.cachedRoots = roots + }) + return c.cachedRoots, nil } diff --git a/pkg/ca/kmsca/kmsca_test.go b/pkg/ca/kmsca/kmsca_test.go index 2fcdfeb94..d59f4d7d7 100644 --- a/pkg/ca/kmsca/kmsca_test.go +++ b/pkg/ca/kmsca/kmsca_test.go @@ -30,7 +30,6 @@ import ( "github.com/sigstore/fulcio/pkg/test" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature/kms/fake" - _ "github.com/sigstore/sigstore/pkg/signature/kms/fake" ) func TestNewKMSCA(t *testing.T) { @@ -40,7 +39,8 @@ func TestNewKMSCA(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCA() subCert, subKey, _ := test.GenerateSubordinateCA(rootCert, rootKey) - pemChain, err := cryptoutils.MarshalCertificatesToPEM([]*x509.Certificate{subCert, rootCert}) + chain := []*x509.Certificate{subCert, rootCert} + pemChain, err := cryptoutils.MarshalCertificatesToPEM(chain) if err != nil { t.Fatalf("error marshalling cert chain: %v", err) } @@ -55,11 +55,14 @@ func TestNewKMSCA(t *testing.T) { } // Expect certificate chain from Root matches provided certificate chain - rootBytes, err := ca.Root(context.TODO()) + rootChains, err := ca.TrustBundle(context.TODO()) if err != nil { t.Fatalf("error fetching root: %v", err) } - if !reflect.DeepEqual(rootBytes, pemChain) { + if len(rootChains) != 1 { + t.Fatalf("unexpected number of chains: %d", len(rootChains)) + } + if !reflect.DeepEqual(rootChains[0], chain) { t.Fatal("cert chains do not match") } diff --git a/pkg/ca/tinkca/tinkca_test.go b/pkg/ca/tinkca/tinkca_test.go index a9b770659..4003fff3b 100644 --- a/pkg/ca/tinkca/tinkca_test.go +++ b/pkg/ca/tinkca/tinkca_test.go @@ -51,7 +51,8 @@ func TestNewTinkCA(t *testing.T) { } rootCert, _ := test.GenerateRootCAFromSigner(khsigner) - pemChain, err := cryptoutils.MarshalCertificatesToPEM([]*x509.Certificate{rootCert}) + chain := []*x509.Certificate{rootCert} + pemChain, err := cryptoutils.MarshalCertificatesToPEM(chain) if err != nil { t.Fatalf("error marshalling cert chain: %v", err) } @@ -79,11 +80,14 @@ func TestNewTinkCA(t *testing.T) { } // Expect certificate chain from Root matches provided certificate chain - rootBytes, err := ca.Root(context.TODO()) + rootChains, err := ca.TrustBundle(context.TODO()) if err != nil { t.Fatalf("error fetching root: %v", err) } - if !reflect.DeepEqual(rootBytes, pemChain) { + if len(rootChains) != 1 { + t.Fatalf("unexpected number of chains: %d", len(rootChains)) + } + if !reflect.DeepEqual(rootChains[0], chain) { t.Fatal("cert chains do not match") } diff --git a/pkg/server/grpc_server.go b/pkg/server/grpc_server.go index 20b1977cf..de5a03e4e 100644 --- a/pkg/server/grpc_server.go +++ b/pkg/server/grpc_server.go @@ -234,17 +234,28 @@ func (g *grpcCAServer) CreateSigningCertificate(ctx context.Context, request *fu func (g *grpcCAServer) GetTrustBundle(ctx context.Context, _ *fulciogrpc.GetTrustBundleRequest) (*fulciogrpc.TrustBundle, error) { logger := log.ContextLogger(ctx) - root, err := g.ca.Root(ctx) + trustBundle, err := g.ca.TrustBundle(ctx) if err != nil { - logger.Error("Error retrieving root cert: ", err) + logger.Error("Error retrieving trust bundle: ", err) return nil, handleFulcioGRPCError(ctx, codes.Internal, err, genericCAError) } - return &fulciogrpc.TrustBundle{ - Chains: []*fulciogrpc.CertificateChain{{ - Certificates: []string{string(root)}, - }}, - }, nil + resp := &fulciogrpc.TrustBundle{ + Chains: []*fulciogrpc.CertificateChain{}, + } + + for _, chain := range trustBundle { + certChain := &fulciogrpc.CertificateChain{} + for _, cert := range chain { + certPEM, err := cryptoutils.MarshalCertificateToPEM(cert) + if err != nil { + return nil, handleFulcioGRPCError(ctx, codes.Internal, err, genericCAError) + } + certChain.Certificates = append(certChain.Certificates, string(certPEM)) + } + resp.Chains = append(resp.Chains, certChain) + } + return resp, nil } func (g *grpcCAServer) GetConfiguration(ctx context.Context, _ *fulciogrpc.GetConfigurationRequest) (*fulciogrpc.Configuration, error) { diff --git a/pkg/server/grpc_server_test.go b/pkg/server/grpc_server_test.go index 581a70963..ba0734a8b 100644 --- a/pkg/server/grpc_server_test.go +++ b/pkg/server/grpc_server_test.go @@ -1463,6 +1463,6 @@ type FailingCertificateAuthority struct { func (fca *FailingCertificateAuthority) CreateCertificate(context.Context, identity.Principal, crypto.PublicKey) (*ca.CodeSigningCertificate, error) { return nil, errors.New("CreateCertificate always fails for testing") } -func (fca *FailingCertificateAuthority) Root(ctx context.Context) ([]byte, error) { - return nil, errors.New("Root always fails for testing") +func (fca *FailingCertificateAuthority) TrustBundle(ctx context.Context) ([][]*x509.Certificate, error) { + return nil, errors.New("TrustBundle always fails for testing") }