ca_roots

tape
import ca_roots from "ca_roots";

ca_roots is the set of certificate authorities tape trusts by default for TLS: the roots Mozilla’s root store trusts for server authentication, as DER, compiled into your program. It is the same set on Windows, Linux, macOS and leivos, so a chain that validates on one validates on all of them. It changes when tape is released, not with operating system updates.

tape
import x509 from "x509";
import ca_roots from "ca_roots";

var anchors: [ca_roots.COUNT]x509.Cert;
var store: x509.TrustStore = x509.trust_store(anchors[0..ca_roots.COUNT]);
ca_roots.load(&store) or { @trap("ca_roots: store too small"); };
// x509.verify_chain(&store, chain, host, now) ...

The module is pure and works in every profile, t0 included. Importing it adds the bundle to your program’s read-only data, about 124 KiB.

Constants

Constant
NSS_VERSION: []const u8the NSS release the bundle comes from, e.g. "3.130"
DATE: []const u8that release’s date, "YYYY-MM-DD"
COUNT: i64the number of certificates in the bundle
DER: []const u8the bundle: the certificates’ DER, one after another
tape
io.println("{ca_roots.COUNT} roots from NSS {ca_roots.NSS_VERSION} ({ca_roots.DATE})");

Functions

tape
pub fn certs() -> x509.Der
pub fn load(store: *x509.TrustStore) -> void or x509.X509Error

certs reads the bundle one certificate at a time (each next is a whole DER certificate):

tape
var d: x509.Der = ca_roots.certs();
while (!d.is_empty()) {
    let t: x509.Tlv = d.next() or { break; };
    let c: x509.Cert = x509.parse(t.whole) or { continue; };
    io.println(c.common_name() or { "(no common name)" });
}

load adds every certificate to store. The store needs room for COUNT more anchors: give it [ca_roots.COUNT]x509.Cert (about 26 KiB), or more if you add your own roots too. When it runs out of room load returns StoreFull, keeping the anchors added so far. The anchors are views into DER, which lives as long as the program.

What is in it

Every root in Mozilla’s certdata.txt (from the NSS release named by NSS_VERSION) that Mozilla trusts to issue TLS server certificates, except:

  • roots Mozilla trusts only for certificates issued before a cut-off date (distrust-after), which x509 cannot express;
  • the few that x509 cannot use (a P-521 key).

Roots that sign themselves with SHA-1 are included: a root’s own signature is never checked; certificates they issue must still be signed with SHA-256 or better.

The file stdlib/ca_roots/SOURCE beside the module records where the bundle came from: the certdata.txt URL and its SHA-256, the bundle’s SHA-256, the count, and each left-out root with its reason.

Trusting other roots

The bundle is the default, not the only choice. To also trust a corporate CA, add its DER to the same store (give the store room for it):

tape
const ROOTS: i64 = ca_roots.COUNT + 1;      // an array size is a constant's name

var anchors: [ROOTS]x509.Cert;
var store: x509.TrustStore = x509.trust_store(anchors[0..ROOTS]);
ca_roots.load(&store) or { @trap("ca_roots: store too small"); };
store.add(corporate_root_der) or { io.println("corporate root: {err}"); };

A program that wants only its own roots doesn’t import ca_roots, and doesn’t carry the bundle.

Last modified: