Skip to content

proposal: x/crypto/x509roots/fallback: export certificate bundle #69898

Open
@stapelberg

Description

@stapelberg

Proposal Details

The x/crypto/x509roots package was added in #43958 and #57792 (cc @rolandshoemaker @rsc @FiloSottile who were involved in prior discussion).

One feature was discussed in issue #43958 but did not make it into the package as currently released: accessing the x509 fallback root certificate bundle programmatically, i.e. not just using it for verification in the current process, but e.g. exporting it to a file for later usage by a different process on a different machine.

Motivation / use-case

In my case, the gokrazy packer (a Go program) creates a self-contained root file system image to be run (with the Linux kernel) on a Raspberry Pi (or similar), PC or (Cloud or on-prem) VM that only contains other Go programs. A gokrazy root file system contains no C runtime or similar — similar to FROM scratch Docker containers.

The gokrazy packer can easily be run on Linux, where we just copy the system root file into the resulting image. But on macOS and Windows, we don’t have a system root file that we can copy. That’s where we currently use github.com/breml/rootcerts.

Ideally, we would programmatically create a roots file (at “gokrazy packer time”) that the Go runtime would then load (at “Raspberry Pi run time”).

Background: Why is the x/crypto/x509roots/nss parser not sufficient?

One might wonder: Why is the x/crypto/x509roots/nss parser not sufficient for this use-case?

I originally thought that using nss.Parse might actually make implementing breml/rootcerts easier, but it turns out that breml/rootcerts already uses an approach that does not require nss.Parse: https://github.com/breml/rootcerts/blob/7000414306b0b352acb0de167dc22ebe5a584085/generate_data.go#L34

I considered doing the http.Get in the gokrazy packer, but then my program requires internet access (undesirable, especially when running in isolated CI/CD environments) and can fail when the source is slow or unavailable. So, I’ll need a cached copy and then have to deal with keeping it up-to-date.

Instead of dealing with cache management on my user’s disk, it might be better to obtain the root certs at go:generate time and embed them into my application. But then I’m effectively doing myself the work that breml/rootcerts is currently doing for me, and have not gained anything.

I think the key observation is: obtaining the root certs is not the tricky part, but updating/distributing the root certs is an annoying problem to solve. If I could just access the fallback store that the x/crypto module already contains, GitHub’s dependabot would from time to time submit a PR to update the x/crypto dependency, and that would be the easiest solution in terms of how much infrastructure I would need to maintain.

Updated Proposal (based on discussion)

Move the embedded fallback root bundle into the new x509roots/fallback/bundle package, which exposes those roots with the following API:

type Root struct {
  Certificate []byte // DER-encoded certificate
  Constraint func([]*x509.Certificate) error // nil if root is unconstrained
}

// Roots returns the parsed bundle of roots from the NSS trust store. Constrained roots will have a non-nil
// Root.Constraint function. All other roots are unconstrained.
func Roots() []Root { … }

The existing x509roots/fallback package then uses the x509roots/fallback/bundle package like so (this is the entire updated fallback.go):

package fallback

import (
        "crypto/x509"

        "golang.org/x/crypto/x509roots/fallback/bundle"
)

func init() {
        x509.SetFallbackRoots(newFallbackCertPool())
}

func newFallbackCertPool() *x509.CertPool {
        p := x509.NewCertPool()
        for _, c := range bundle.Roots() {
                cert, err := x509.ParseCertificate(c.Certificate)
                if err != nil {
                        panic(err)
                }
                if c.Constraint == nil {
                        p.AddCert(cert)
                } else {
                        p.AddCertWithConstraint(cert, c.Constraint)
                }
        }
        return p
}

User code (outside x/crypto) can create a PEM-encoded ca-certificates.crt file like so:

func fallbackBundle() string {
	var certs []byte
	for _, c := range bundle.Roots() {
		certs = append(certs, pem.EncodeToMemory(&pem.Block{
			Type:  "CERTIFICATE",
			Bytes: c.Certificate,
		})...)
	}
	return string(certs)
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    ProposalProposal-CryptoProposal related to crypto packages or other security issues

    Type

    No type

    Projects

    Status

    Active

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions